采用&lt; ...&gt;的正则表达式作为“foo bar <hello world =”“>等中的一项”(目标:简单音乐/ lilypond解析)</hello>

时间:2013-02-10 18:32:50

标签: python regex lilypond

我在Python(3)中使用re模块,并希望以下列格式替换(re.sub(regex,replace,string))字符串

"foo <bar e word> f ga <foo b>" 

"#foo <bar e word> #f #ga <foo b>"

甚至

"#foo #<bar e word> #f #ga #<foo b>" 

但是我不能在&lt; ...&gt;中的单词边界中隔离单个单词。构造

帮助会很好!

P.S 1

整个故事是一个音乐剧: 我有Lilypond格式的字符串(或更好,非常简单的核心格式的子集,只是注释和持续时间),并希望将它们转换为python对int(持续时间),列表(音高串)。性能并不重要,所以我可以来回转换它们,迭代python列表,拆分字符串并再次连接它们等。 但对于上述问题,我没有找到答案。

源字符串

"c'4 d8 < e' g' >16 fis'4 a,, <g, b'> c''1"

应该导致

[
(4, ["c'"]),
(8, ["d"]),
(16, ["e'", "g'"]),
(4, ["fis'"]),
(0, ["a,,"]),
(0, ["g", "b'"]),
(1, ["c''"]),
]

基本格式是字符串+数字,如下所示:e4 bes16

  • 列表项
  • 该字符串可以包含多个,至少一个[a-zA-Z]字符
  • 该字符串后跟零个或多个数字:e bes g4 c16
  • 字符串后跟零或更多'或,(未合并):e'bes,f'''2 g ,, 4
  • 字符串可以用字符串列表替换,列表限制器是&lt;&gt ;:4数字在&gt;后面,不允许空格

P.S。 2

目标不是创建Lilypond解析器。它是否真的只适用于非常短的片段,没有其他功能,没有插入注释的扩展名。如果这不起作用,我会选择另一种格式(简化),如ABC。所以任何与Lilypond有关的事情(“Run it trough lilypond,让它给出Scheme中的音乐数据,解析那个”)或其工具链肯定不是这个问题的答案。包装甚至没有安装。

2 个答案:

答案 0 :(得分:2)

我知道你不是在寻找一般的解析器,但pyparsing使这个过程变得非常简单。你的格式似乎与我作为最早的pyparsing例子之一写的chemical formula parser非常相似。

以下是使用pyparsing实现的问题:

from pyparsing import (Suppress,Word,alphas,nums,Combine,Optional,Regex,Group,
                       OneOrMore)

"""
List item
 -the string can consist of multiple, at least one, [a-zA-Z] chars
 -the string is followed by zero or more digits: e bes g4 c16
 -the string is followed by zero or more ' or , (not combined): 
  e' bes, f'''2 g,,4
 -the string can be substituted by a list of strings, list limiters are <>;
  the number comes behind the >, no space allowed
"""

LT,GT = map(Suppress,"<>")

integer = Word(nums).setParseAction(lambda t:int(t[0]))

note = Combine(Word(alphas) + Optional(Word(',') | Word("'")))
# or equivalent using Regex class
# note = Regex(r"[a-zA-Z]+('+|,+)?")

# define the list format of one or more notes within '<>'s
note_list = Group(LT + OneOrMore(note) + GT)

# each item is a note_list or a note, optionally followed by an integer; if
# no integer is given, default to 0
item = (note_list | Group(note)) + Optional(integer, default=0)

# reformat the parsed data as a (number, note_or_note_list) tuple
item.setParseAction(lambda t: (t[1],t[0].asList()) )

source = "c'4 d8 < e' g' >16 fis'4 a,, <g, b'> c''1"
print OneOrMore(item).parseString(source)

使用此输出:

[(4, ["c'"]), (8, ['d']), (16, ["e'", "g'"]), (4, ["fis'"]), (0, ['a,,']), 
 (0, ['g,', "b'"]), (1, ["c''"])]

答案 1 :(得分:1)

你的第一个问题可以用这种方式回答:

>>> import re
>>> t = "foo <bar e word> f ga <foo b>"
>>> t2 = re.sub(r"(^|\s+)(?![^<>]*?>)", " #", t).lstrip()
>>> t2
'#foo #<bar e word> #f #ga #<foo b>'

我添加lstrip()以删除此模式结果之前出现的单个空格。如果您想使用第一个选项,只需将#<替换为<

您的第二个问题可以通过以下方式解决,但您可能需要考虑,等列表中的['g,', "b'"]。你的字符串中的逗号是否应该存在?可能有更快的方法。以下仅是概念证明。列表理解可能取代最终元素,尽管它很复杂。

>>> s = "c'4 d8 < e' g' >16 fis'4 a,, <g, b'> c''1"
>>> q2 = re.compile(r"(?:<)\s*[^>]*\s*(?:>)\d*|(?<!<)[^\d\s<>]+\d+|(?<!<)[^\d\s<>]+")
>>> s2 = q2.findall(s)
>>> s3 = [re.sub(r"\s*[><]\s*", '', x) for x in s2]
>>> s4 = [y.split() if ' ' in y else y for y in s3]
>>> s4
["c'4", 'd8', ["e'", "g'16"], "fis'4", 'a,,', ['g,', "b'"], "c''1"]
>>> q3 = re.compile(r"([^\d]+)(\d*)")
>>> s = []
>>> for item in s4:
    if type(item) == list:
            lis = []
            for elem in item:
                    lis.append(q3.search(elem).group(1))
                    if q3.search(elem).group(2) != '':
                            num = q3.search(elem).group(2)
            if q3.search(elem).group(2) != '':
                    s.append((num, lis))
            else:
                    s.append((0, lis))
    else:
            if q3.search(item).group(2) != '':
                    s.append((q3.search(item).group(2), [q3.search(item).group(1)]))
            else:
                    s.append((0, [q3.search(item).group(1)]))


>>> s
[('4', ["c'"]), ('8', ['d']), ('16', ["e'", "g'"]), ('4', ["fis'"]), (0, ['a,,']), (0, ['g,', "b'"]), ('1', ["c''"])]