如何将一定长度的字符串与正则表达式匹配

时间:2009-06-02 05:55:06

标签: python regex

对于我的项目,我正在尝试实现BitTorrent协议的一小部分,可以找到here。具体来说,我想使用它的“Bencoding”部分,这是一种安全编码数据以便通过套接字传输的方法。格式如下:

8:a string => "a string"
i1234e => 1234
l1:a1:be => ['a', 'b']
d1:a1:b3:one3:twoe => {'a':'b', 'one':two}

编码部分很简单,但解码变得非常麻烦。例如,如果我有一个字符串列表,我无法将它们分成单独的字符串。我尝试了几种不同的解决方案,包括PyParsing和自定义令牌解析器。我目前正在尝试使用正则表达式,它看起来相当不错,但我仍然挂在字符串问题上。我目前的正则表达式是:

(?P<length>\d+):(?P<contents>.{\1})

但是,我似乎无法使用第一组作为第二组的长度。有什么好办法吗?或者我接近这一切都错了,答案就在我面前?

5 个答案:

答案 0 :(得分:8)

你用于此的任何解析器都需要是有状态的(即记住东西),并且正则表达式基本上不是有状态的。他们是这项工作的错误工具。

如果这些是您唯一需要担心的数据类型,我想我只是为每种数据类型编写自定义解析器,在读取第一个字符后将控制权传递给相应的解析器。

我现在实际上已经实现了一个,但现在已经很晚了。

好吧我决定写一个实现:

from StringIO import StringIO
import string

inputs = ["10:a stringly",
         "i1234e" ,
         "l1:a1:be",
         "d1:a1:b3:one3:twoe"]

# Constants
DICT_TYPE = 'd'
LIST_TYPE = 'l'
INT_TYPE  = 'i'
TOKEN_EOF = ''
TOKEN_END = 'e'
COLON     = ':'


class BadTypeIndicatorException(Exception):pass


def read_int(stream):

   s = ""

   while True:
      ch = stream.read(1)
      if ch not in [TOKEN_EOF, TOKEN_END, COLON]:
         s += ch
      else:
         break

   return s


def tokenize(stream):

   s = ""

   while True:

      ch = stream.read(1)

      if ch == TOKEN_END or ch == TOKEN_EOF:
         return 

      if ch == COLON:
         length = int(s)
         yield stream.read(length)
         s = ""

      else:
         s += ch


def parse(stream):

   TYPE = stream.read(1)

   if TYPE in string.digits:
      length = int( TYPE + read_int(stream) )
      return stream.read(length)

   elif TYPE is INT_TYPE: 
      return int( read_int(stream) )

   elif TYPE is LIST_TYPE: 
      return list(tokenize(stream))

   elif TYPE is DICT_TYPE:
      tokens = list(tokenize(stream))
      return dict(zip(tokens[0::2], tokens[1::2]))

   else: 
      raise BadTypeIndicatorException



for input in inputs:
   stream = StringIO(input)
   print parse(stream)

答案 1 :(得分:2)

如果您将字符串解析两次,则可以执行此操作。应用第一个正则表达式以获得长度。在第二个正则表达式中连接长度以形成有效的表达式。

不确定如何在python中完成,但C#中的示例将是:

string regex = "^[A-Za-z0-9_]{1," + length + "}$"

匹配1到长度no的chars,可以是alpanumeric或_,其中length是从先前只检索长度的正则表达式确定的。

希望这会有所帮助:)

答案 2 :(得分:2)

您需要分两步完成此操作。对于像这样简单的解析问题,正则表达式实际上有点过分。我就是这样做的:

def read_string(stream):
    pos = stream.index(':')
    length = int(stream[0:pos])
    string = stream[pos+1:pos+1+length]
    return string, stream[pos+1+length:]

这是一种功能风格的解析方式,它返回解析的值和流的其余部分。

对于列表,可能:

def read_list(stream):
    stream = stream[1:]
    result = []
    while stream[0] != 'e':
        obj, stream = read_object(stream)
        result.append(obj)
    stream = stream[1:]
    return result

然后你定义了一个read_object来检查流的第一个字符并进行适当的调度。

答案 3 :(得分:1)

你正在使用错误的工具...这需要某种状态保持,一般来说,正则表达式是无状态的。


我可以找到PERL中的bdecoding(和bencoding)示例实现here

关于该功能如何工作的解释(因为我从来没有对它进行评论[oops]):

基本上你需要做的是设置一个递归函数。此函数接受一个字符串引用(因此可以修改)并返回“something”(这意味着它可能是一个数组,一个哈希表,一个int或一个字符串)。

函数本身只是检查字符串中的第一个字符,并根据它决定要做什么:

  • 如果是i,则解析 i 与第一个 e 之间的所有文字,并尝试将其解析为int遵守规则。
  • 如果是数字,则读取所有数字,直到,然后从字符串中读取多个字符。

列表和词典是事情开始变得有趣的地方......如果第一个字符有 l d ,那么你需要剥离{ {1}} / l,然后将当前字符串传递回函数,以便它可以开始解析列表或字典中的元素。然后将返回的值存储在适当结构中的适当位置,直到您点击 d ,然后返回您剩下的结构。

请记住,我实现它的功能是破坏性的。当函数返回时传入的字符串是空的,因为它是通过引用传递的,或者更确切地说,它将没有解析和返回的任何内容(这就是为什么它可以递归使用:它不处理的任何东西都留下了不变)。在大多数初始调用的情况下,除非你做了一些奇怪的事情,否则这应该处理所有事情,所以上述情况适用。

答案 4 :(得分:1)

伪代码,没有语法检查:

define read-integer (stream):
    let number 0, sign 1:
        if string-equal ('-', (c <- read-char (stream))):
            sign <- -1
          else:
            number <- parse-integer (c)
        while number? (c <- read-char (stream)):
            number <- (number * 10) + parse-integer (c)
        return sign * number

define bdecode-string (stream):
    let count read-integer (stream):
        return read-n-chars (stream, count)

define bdecode-integer (stream):
    ignore read-char (stream)
    return read-integer (stream)

define bdecode-list (stream):
    ignore read-char (stream)
    let list []:
        while not string-equal ('e', peek-char (stream)):
            append (list, bdecode (stream))
        return list

define bdecode-dictionary (stream):
    let list bdecode-list stream:
        return dictionarify (list)

define bdecode (stream):
    case peek-char (stream):
        number? => bdecode-string (stream)
        'i' => bdecode-integer (stream)
        'l' => bdecode-list (stream)
        'd' => bdecode-dictionary (stream)