要处理这些数据,您需要编写一个简单的解析器。
解析器是一个小型状态机。根据当前状态和接下来看到的输入,它会移动到某个新状态。当它在状态之间移动时,它会收集输入并创建某种树结构。您可以尝试通过为每个状态绘制圆圈和连接它们的线来可视化这一点,因为我描述了您的解析器需要经过的状态。用您需要的输入标记每一行,以便在状态之间移动。
您的数据从初始状态开始(等待标题),期待带有日期范围的标题行:
Book of sales du 01/01/2014 au 31/12/2050;;;;;;;;;;;
您需要阅读该行,检查它是否与预期格式匹配,提取并保留日期范围,然后进入下一个状态(等待表)。
您的表格似乎以名称开头,后跟 11 个分号。例如:
Sales;;;;;;;;;;;
如果您检测到以 11 个分号结尾的行,那么您需要使用给定的表名开始一个新表并移动到状态(期望字段名)。
字段名称是用分号分隔的字符串:
Date;Invoice;Client;Txt;Price
您需要用分号分割标题行,保存字段名称并移动到新状态(处理数据)。
您的数据实际上与标题相同,都是用分号分隔的字段值:
19/02/2015;1;Johnny;coloris: 002, taille: 54/18;82,03€
再次,您阅读、拆分和保存。这次您停留在当前状态(处理数据),因为您的表中可能有不止一条记录。
表格的摘要行具有相同的字段,在这种情况下使用 Total 而不是 Date:
Total;;;;10 700,74€
因此,如果您在(处理数据)中看到以“Total”开头的行,您将转换到状态(摘要)并保存总计。
从 (summary) 过渡到 (end table),这是一行分号:
;;;;;;;;;;;
或者这是一个空表,并不是每个表都以分号结尾。我无法确定,因为您的示例数据集只有两个表,最后一个位于文件末尾。
处理完(结束表)后,您转换回(等待表),为下一张表做好准备。相反,如果您看到文件结尾,则转换到(最终状态)并完成解析。
由于表头、数据和汇总行都是基本相同的结构,我们可以将它们折叠成一个状态(处理数据),处理完成后,我们可以专门处理表格的第一行和最后一行,因为然后很容易找到它们。这使得解析器更简单,更容易调试。
同样,如果我们引入空表的概念,我们可以进一步简化解析器,因为我们不需要(结束表)状态。我们只是将其视为没有标题、数据或摘要的空表的开始。当我们完成解析时,很容易忽略空表。此外,我们可以假设第一个表在一个空表之后开始,所以我们可以从(处理数据)状态开始,一切正常。
输入中每行之间的空白行(可能是回车+窗口换行)可以被删除。
把它放在一起,解析器变成:
with open file:
# transition to (awaiting header)
read header
# transition to (processing data)
current table = empty
for each line:
# strip blank
if blank: # blank does not change state
continue
if new table line:
# transition to (new table)
save current table if any
make new table and set it to current table
# transition to (processing data)
else:
# transition to (processing data)
split line into data columns
# stay in (processing data)
save current table if any
# transition to (final state)
让它更 Pythonic:
tables = []
with open(filename) as file:
line = file.readline()
start_date, end_date = line[17:27], line[31:41]
table_name, table_rows = "", [] # pretend we start with a blank table
for line in file: # reads lines one at a time until the end
line = line.strip() # remove linefeed from end of line
if not line: # check for blank line
continue
if line.endswith(';'*11): # line ends with 11 semicolons
# save the existing table; the first table will be blank
tables.append((table_name, table_rows))
table_name = line[:-11] # name is all but the last 11 semicolons
table_rows = []
else:
fields = line.split(';')
table_rows.append(fields)
# save the current table
table.append((table_name, table_rows))
您现在应该有一个表格列表(其中一些可能是空的)。
# maybe record start_date, end_date
# process each table
for table_name, table_rows in tables:
if not table_rows: # table is blank
continue
fields = table_rows[0]
if table_rows[-1][0] == "Total":
rows = table_rows[1:-1]
summary = table_rows[-1]
else:
rows = table_rows[1:]
summary = None
save_table(table_name, fields, rows, summary)
这个解析器一开始很简单,我们采用了一些捷径使其更简单。
如果语言稍微复杂一点,我们会以更抽象的形式保留它:
state = 0
for line in file:
if state == 0:
if line matches transition from 0 to 0:
state = 0
save line data for state 0
elif line matches transition from 0 to 1:
state = 1
save line data for state 1
elif line matches transition from 0 to 2:
...
elif state == 1:
if line matches transition from 1 to 0:
state = 0
save line data for state 0
elif line matches transition from 1 to 1:
state = 1
save line data for state 1
elif line matches transition from 1 to 2:
...
elif state == 2:
...
这篇文章中的想法将涵盖您需要处理的大部分数据。
对于具有深度嵌套结构的更复杂的数据格式,您必须将起始结构与结束结构匹配,您需要使用真正的解析器,例如 pyparsing 提供的解析器。如果没有解析器工具,即使是带括号的简单数学表达式也很难完成。