繁体   English   中英

Python正则表达式用于读取类似CSV的行

[英]Python regex for reading CSV-like rows

我想解析传入的类似CSV的数据行。 值用逗号分隔(逗号周围可能有前导和尾随空格),并且可以用'或者“引用。例如 - 这是一个有效的行:

    data1, data2  ,"data3'''",  'data4""',,,data5,

但这个是畸形的:

    data1, data2, da"ta3", 'data4',

- 引号只能以空格为前缀或尾随。

应该识别这种格式错误的行 - 最好是以某种方式在行内标记格式错误的值,但如果正则表达式与整行不匹配,则它也是可接受的。

我正在尝试使用findall()的match()来编写能够解析它的正则表达式,但是我正在使用的每个正则表达式都存在边缘情况的一些问题。

那么,也许有解析类似经验的人可以帮助我吗? (或者这对于正则表达式来说太复杂了,我应该写一个函数)

EDIT1:

csv模块在这里没什么用处:

    >>> list(csv.reader(StringIO('''2, "dat,a1", 'dat,a2',''')))
    [['2', ' "dat', 'a1"', " 'dat", "a2'", '']]

    >>> list(csv.reader(StringIO('''2,"dat,a1",'dat,a2',''')))
    [['2', 'dat,a1', "'dat", "a2'", '']]

- 除非可以调整?

EDIT2:一些语言编辑 - 我希望它现在更有效

EDIT3:谢谢你的所有答案,我现在很确定正则表达式在这里不是一个好主意,因为(1)覆盖所有边缘情况可能很棘手(2)编写器输出不规则。 写这个,我决定检查提到的pyparsing并使用它,或编写自定义FSM类解析器。

虽然csv模块在这里是正确的答案,但是可以做到这一点的正则表达式是非常可行的:

import re

r = re.compile(r'''
    \s*                # Any whitespace.
    (                  # Start capturing here.
      [^,"']+?         # Either a series of non-comma non-quote characters.
      |                # OR
      "(?:             # A double-quote followed by a string of characters...
          [^"\\]|\\.   # That are either non-quotes or escaped...
       )*              # ...repeated any number of times.
      "                # Followed by a closing double-quote.
      |                # OR
      '(?:[^'\\]|\\.)*'# Same as above, for single quotes.
    )                  # Done capturing.
    \s*                # Allow arbitrary space before the comma.
    (?:,|$)            # Followed by a comma or the end of a string.
    ''', re.VERBOSE)

line = r"""data1, data2  ,"data3'''",  'data4""',,,data5,"""

print r.findall(line)

# That prints: ['data1', 'data2', '"data3\'\'\'"', '\'data4""\'', 'data5']

编辑:要验证行,您可以重复使用上面的正则表达式添加少量:

import re

r_validation = re.compile(r'''
    ^(?:    # Capture from the start.
      # Below is the same regex as above, but condensed.
      # One tiny modification is that it allows empty values
      # The first plus is replaced by an asterisk.
      \s*([^,"']*?|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\s*(?:,|$)
    )*$    # And don't stop until the end.
    ''', re.VERBOSE)

line1 = r"""data1, data2  ,"data3'''",  'data4""',,,data5,"""
line2 = r"""data1, data2, da"ta3", 'data4',"""

if r_validation.match(line1):
    print 'Line 1 is valid.'
else:
    print 'Line 1 is INvalid.'

if r_validation.match(line2):
    print 'Line 2 is valid.'
else:
    print 'Line 2 is INvalid.'

# Prints:
#    Line 1 is valid.
#    Line 2 is INvalid.

虽然通过预处理,使用csv模块,后处理和正则表达式的使用可能有可能,但您声明的要求不适合csv模块的设计,也不适合正则表达式(取决于您可能必须处理的嵌套引号的复杂性。

在复杂的解析案例中, pyparsing始终是一个很好的解决方案。 如果这不是一次性的情况,它可能会产生最直接和可维护的结果,代价可能需要一点额外的努力。 但是,考虑到投资可以快速回报,因为您可以省去调试正则表达式解决方案来处理极端情况的额外工作......

您可以轻松找到基于pyparsing的CSV解析的示例, 这个问题可能足以让您入门。

Python有一个标准的库模块来读取csv文件:

import csv

reader = csv.reader(open('file.csv'))

for line in reader:
    print line

对于您的示例输入,将打印

['data1', ' data2 ', "data3'''", ' \'data4""\'', '', '', 'data5', '']

编辑:

您需要添加skipinitalspace = True以在您提供的额外示例的双引号之前允许空格。 还不确定单引号。

>>> list(csv.reader(StringIO('''2, "dat,a1", 'dat,a2','''), skipinitialspace=True))
[['2', 'dat,a1', "'dat", "a2'", '']]

>>> list(csv.reader(StringIO('''2,"dat,a1",'dat,a2','''), skipinitialspace=True))
[['2', 'dat,a1', "'dat", "a2'", '']]

无法给出答案,因为您尚未完全指定编写者正在使用的协议。

它显然包含如下规则:

如果某个字段包含任何逗号或单引号,请使用双引号引用它。
否则,如果该字段包含任何双引号,请使用单引号引用它。
注意:如果在上面的2个子句中交换double和single,结果仍然有效。
否则不引用它。
结果字段可以具有前置或附加的空格(或其他空格?)。
如此增强的字段被组合成一行,用逗号分隔并由平台的换行符(LF或CRLF)终止。

没有提到的是作者在这些情况下所做的事情:
(0)字段包含单引号和双引号
(1)字段包含前导非换行空格
(2)字段包含尾随的非换行空格
(3)字段包含任何换行符。
如果作者忽略任何这些案例,请说明您想要的结果。

你还提到“引号只能以空格为前缀或尾随” - 当然你的意思也是允许使用逗号,否则你的例子'data4""',,,data5,在第一个逗号上失败。

您的数据是如何编码的?

这可能听起来太简单,但实际上从你正在寻找包含[a-zA-Z0-9] [“'] + [a-zA-Z0-9]的字符串看起来,我的意思是没有在对数据进行深度测试你真正想要的是字母之间的引号或双引号(或任何组合)(你也可以在那里添加数字)。

根据你的要求,它是一个CSV真的无关紧要,重要的是你有不符合的数据。 我相信只是在搜索一封信,然后是一个或多个“或”和另一个字母的任意组合。

现在你想要获得一个“数量”或只是包含它的行的打印输出,以便你知道哪些要返回并修复?

对不起,我不知道python正则表达式,但在perl中,这看起来像这样:

# Look for one or more letter/number at least one ' or " or more and at least one    
#  or more letter/number
if ($line =~ m/[a-zA-Z0-9]+['"]+[a-zA-Z0-9]+/ig)
{
    # Prints the line if the above regex is found
    print $line;

}

只需简单地转换为当你看一条线时。

如果我误解了这个问题,我很抱歉

我希望它有所帮助!

如果您的目标是将数据转换为XML(或JSON或YAML),请查看此示例以获取产生以下输出的Gelatin语法:

<xml>
  <line>
    <column>data1</column>
    <column>data2  </column>
    <column>data3'''</column>
    <column>data4""</column>
    <column/>
    <column/>
    <column>data5</column>
    <column/>
  </line>
</xml>

请注意,Gelatin还有一个Python API:

from Gelatin.util import compile, generate_to_file
syntax = compile('syntax.gel')
generate_to_file(syntax, 'input.csv', 'output.xml', 'xml')

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM