簡體   English   中英

如何使用Python和XSLT文件迭代解析巨大的XML並寫入CSV

[英]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="'&#xd;'" />

<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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM