簡體   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