[英]How to parse huge XML with Python and XSLT file iteratively and write to CSV
處理大型XML文件時,無法使用XSLT展平XML並將其轉換為CSV文件。
當前,我正在使用XSL文件使用lxml
解析嵌套的XML文件以將輸出展平,然后將輸出寫入CSV文件。
我的XML看起來像這樣:
<root>
<level1>
<level2>
<topid>1</topid>
<level3>
<subtopid>1</topid>
<level4>
<subid>1</id>
<descr>test</descr>
</level4>
<level4>
<subid>2</id>
<descr>test2</descr>
</level4>
...
</level3>
...
</level2>
</level1>
</root>
我想以以下CSV文件結尾:
topid,subtopid,subid,descr
1,1,1,test
1,1,2,test2
....
我的XSLT:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8" use-character-maps="map"/>
<xsl:character-map name="map">
<xsl:output-character character="," string=" "/>
</xsl:character-map>
<xsl:strip-space elements="*"/>
<xsl:variable name="delimiter" select="','"/>
<xsl:variable name="newline" select="'
'" />
<xsl:template match="/root">
<xsl:text>topid,subtopid,subid,descr</xsl:text>
<xsl:value-of select="$newline" />
<xsl:for-each select="level1/level2/level3/level4">
<xsl:value-of select="ancestor::root/level1/level2/topid" />
<xsl:value-of select="$delimiter" />
<xsl:value-of select="ancestor::root/level1/level2/level3/subtopid" />
<xsl:value-of select="$delimiter" />
<xsl:value-of select="subid" />
<xsl:value-of select="$delimiter" />
<xsl:value-of select="descr" />
<xsl:value-of select="$newline" />
</xsl:for-each>
</xsl:template>
我的Python代碼:
import lxml.etree as ET
xsltfile = ET.XSLT(ET.parse('transactions.xsl'))
xmlfile = ET.parse('myxmlfile.xml')
output = xsltfile(xmlfile).write_output('output.csv')
這對於小型文件非常有用,但是現在我想對+ -2.5gb的XML文件執行相同的操作。 使用etree.parse會將其加載到內存中,這顯然不適用於較大的文件。
我想在某個地方進行迭代,因此我不會將XML文件加載到內存中並逐行寫入CSV行,而仍然使用XSLT進行轉換。 我正在使用XSLT文件,因為這是我知道(現在)如何展平嵌套XML文件的唯一方法。
我寧願在Python中使用XSLT 3.0(甚至2.0!),但還沒有時間弄清楚如何使用Saxon / C。
另一種選擇是使用iterparse()
。
例...
XML輸入 (固定為良好和添加第二個level3
用於測試)
<root>
<level1>
<level2>
<topid>1</topid>
<level3>
<subtopid>1</subtopid>
<level4>
<subid>1</subid>
<descr>test</descr>
</level4>
<level4>
<subid>2</subid>
<descr>test2</descr>
</level4>
</level3>
<level3>
<subtopid>2</subtopid>
<level4>
<subid>1</subid>
<descr>test</descr>
</level4>
<level4>
<subid>2</subid>
<descr>test2</descr>
</level4>
</level3>
</level2>
</level1>
</root>
蟒蛇
from lxml import etree
import csv
context = etree.iterparse("test.xml", events=("start", "end"))
fields = ("topid", "subtopid", "subid", "descr")
with open("test.csv", "w", newline="", encoding="utf8") as xml_data_to_csv:
csv_writer = csv.DictWriter(xml_data_to_csv, fieldnames=fields,
delimiter=",", quoting=csv.QUOTE_MINIMAL)
csv_writer.writeheader()
topid = None
subtopid = None
values = {}
for event, elem in context:
tag = elem.tag
text = elem.text
if tag == "topid" and text:
topid = text
if tag == "subtopid" and text:
subtopid = text
if tag == "subid" and text:
values["subid"] = text
if tag == "descr" and text:
values["descr"] = text
if event == "start" and tag == "level4":
# Build a dict containing all of the "fields" with default values of "Unknown".
values = {key: "Unknown" for key in fields}
if event == "end" and tag == "level4":
values["topid"] = topid
values["subtopid"] = subtopid
csv_writer.writerow(values)
elem.clear()
CSV輸出
topid,subtopid,subid,descr
1,1,1,test
1,1,2,test2
1,2,1,test
1,2,2,test2
一種可能性是使用XSLT 3.0流。 這里有兩個挑戰:
(a)使您的代碼可流式傳輸。 如果不查看樣式表代碼,我們無法判斷這有多困難。
(b)安裝並運行XSLT 3.0流處理器。 這取決於您對Python環境的鎖定程度。 如果必須使用Python完成,則可以嘗試安裝Saxon / C。 另一種方法是調出不同的環境,在這種情況下,您有更多選擇,例如可以在Java上運行Saxon-EE。
后來
查看您發布的代碼,這很奇怪
<xsl:for-each select="level1/level2/level3/level4">
<xsl:value-of select="ancestor::root/level1/level2/topid" />
我懷疑您想輸出“當前” level2
元素的topid
,但這不是這樣做的(在XSLT 1.0中,它將打印第一個level2/topic
的值,在XSLT 2.0+中將打印所有的值。 level2/topic
元素。我懷疑您確實想要這樣的東西:
<xsl:for-each select="level1/level2/level3/level4">
<xsl:value-of select="ancestor::level2/topid" />
<xsl:value-of select="$delimiter" />
<xsl:value-of select="ancestor::level3/subtopid" />
<xsl:value-of select="$delimiter" />
<xsl:value-of select="subid" />
<xsl:value-of select="$delimiter" />
<xsl:value-of select="descr" />
<xsl:value-of select="$newline" />
</xsl:for-each>
那幾乎是可以流式傳輸的,但不完全是。 流式傳輸不允許您返回topid和subtopid元素。 使其可流式傳輸的最簡單方法可能是將這些元素的最新值保存在累加器中:
<xsl:accumulator name="topid" as="xs:string" initial-value="''">
<xsl:accumulator-rule match="topid/text()" select="string(.)"/>
</xsl:accumulator>
<xsl:accumulator name="subtopid" as="xs:string" initial-value="''">
<xsl:accumulator-rule match="subtopid/text()" select="string(.)"/>
</xsl:accumulator>
然后按以下方式訪問值:
<xsl:for-each select="level1/level2/level3/level4">
<xsl:value-of select="accumulator-before('topid')" />
<xsl:value-of select="$delimiter" />
<xsl:value-of select="accumulator-before('subtopid')" />
<xsl:value-of select="$delimiter" />
<xsl:value-of select="subid" />
<xsl:value-of select="$delimiter" />
<xsl:value-of select="descr" />
<xsl:value-of select="$newline" />
</xsl:for-each>
Saxon / C和python可以工作:
一位用戶已成功使用Boost.Python與C ++庫進行接口。
另一個用戶以不同的方式完成了接口: https : //github.com/ajelenak/pysaxon
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.