【问题标题】:Converting complex XML to CSV using Python or XSLT使用 Python 或 XSLT 将复杂的 XML 转换为 CSV
【发布时间】:2020-06-10 14:38:30
【问题描述】:

使用 Python 或 XSLT,我想知道如何将高度复杂的分层嵌套 XML 文件转换为包含所有子元素的 CSV,并且无需硬编码尽可能少的元素节点,还是合理/有效?

请查找随附的简化 XML 示例和输出 CSV,以便更好地了解我想要实现的目标。

实际的 XML 文件有更多元素,但数据层次结构和嵌套与示例中的一样。 <InvoiceRow> 元素及其子元素是 XML 文件中唯一重复的元素,所有其他元素都是静态的,在输出 CSV 中重复的次数与 XML 文件中的 <InvoiceRow> 元素一样多。

给我带来麻烦的是重复的<InvoiceRow> 元素。不重复的元素很容易转换为 CSV,无需对任何元素进行硬编码。

复杂的 XML 场景,分层数据结构和多个一对多关系都存储在单个 XML 文件中。结构化文本文件。

示例 XML 输入:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Invoice>
    <SellerDetails>
        <Identifier>1234-1</Identifier>
        <SellerAddress>
            <SellerStreet>Street1</SellerStreet>
            <SellerTown>Town1</SellerTown>
        </SellerAddress>
    </SellerDetails>
    <BuyerDetails>
        <BuyerIdentifier>1234-2</BuyerIdentifier>
        <BuyerAddress>
            <BuyerStreet>Street2</BuyerStreet>
            <BuyerTown>Town2</BuyerTown>
        </BuyerAddress>
    </BuyerDetails>
    <BuyerNumber>001234</BuyerNumber>
    <InvoiceDetails>
        <InvoiceNumber>0001</InvoiceNumber>
    </InvoiceDetails>
    <InvoiceRow>
        <ArticleName>Article1</ArticleName>
        <RowText>Product Text1</RowText>
        <RowText>Product Text2</RowText>
        <RowAmount AmountCurrencyIdentifier="EUR">10.00</RowAmount>
    </InvoiceRow>
    <InvoiceRow>
        <ArticleName>Article2</ArticleName>
        <RowText>Product Text11</RowText>
        <RowText>Product Text22</RowText>
        <RowAmount AmountCurrencyIdentifier="EUR">20.00</RowAmount>
    </InvoiceRow>
    <InvoiceRow>
        <ArticleName>Article3</ArticleName>
        <RowText>Product Text111</RowText>
        <RowText>Product Text222</RowText>
        <RowAmount AmountCurrencyIdentifier="EUR">30.00</RowAmount>
    </InvoiceRow>
    <EpiDetails>
        <EpiPartyDetails>
            <EpiBfiPartyDetails>
                <EpiBfiIdentifier IdentificationSchemeName="BIC">XXXXX</EpiBfiIdentifier>
            </EpiBfiPartyDetails>
        </EpiPartyDetails>
    </EpiDetails>
    <InvoiceUrlText>Some text</InvoiceUrlText>
</Invoice>


CSV 输出示例:

Identifier,SellerStreet,SellerTown,BuyerIdentifier,BuyerStreet,BuyerTown,BuyerNumber,InvoiceNumber,ArticleName,RowText,RowText,RowAmount,EpiBfiIdentifier,InvoiceUrlText
1234-1,Street1,Town1,1234-2,Street2,Town2,1234,1,Article1,Product Text1,Product Text2,10,XXXXX,Some text
1234-1,Street1,Town1,1234-2,Street2,Town2,1234,1,Article2,Product Text11,Product Text22,20,XXXXX,Some text
1234-1,Street1,Town1,1234-2,Street2,Town2,1234,1,Article3,Product Text111,Product Text222,30,XXXXX,Some text

【问题讨论】:

    标签: python xml csv parsing xslt


    【解决方案1】:

    考虑以下示例:

    XML

    <Invoice>
        <SellerDetails>
            <Identifier>1234-1</Identifier>
            <SellerAddress>
                <SellerStreet>Street1</SellerStreet>
                <SellerTown>Town1</SellerTown>
            </SellerAddress>
        </SellerDetails>
        <BuyerDetails>
            <BuyerIdentifier>1234-2</BuyerIdentifier>
            <BuyerAddress>
                <BuyerStreet>Street2</BuyerStreet>
                <BuyerTown>Town2</BuyerTown>
            </BuyerAddress>
        </BuyerDetails>
        <BuyerNumber>001234</BuyerNumber>
        <InvoiceDetails>
            <InvoiceNumber>0001</InvoiceNumber>
        </InvoiceDetails>
        <InvoiceRow>
            <ArticleName>Article1</ArticleName>
            <RowText>Product Text1</RowText>
            <RowText>Product Text2</RowText>
            <RowAmount AmountCurrencyIdentifier="EUR">10.00</RowAmount>
        </InvoiceRow>
        <InvoiceRow>
            <ArticleName>Article2</ArticleName>
            <RowText>Product Text11</RowText>
            <RowText>Product Text22</RowText>
            <RowAmount AmountCurrencyIdentifier="EUR">20.00</RowAmount>
        </InvoiceRow>
        <InvoiceRow>
            <ArticleName>Article3</ArticleName>
            <RowText>Product Text111</RowText>
            <RowText>Product Text222</RowText>
            <RowAmount AmountCurrencyIdentifier="EUR">30.00</RowAmount>
        </InvoiceRow>
        <EpiDetails>
            <EpiPartyDetails>
                <EpiBfiPartyDetails>
                    <EpiBfiIdentifier IdentificationSchemeName="BIC">XXXXX</EpiBfiIdentifier>
                </EpiBfiPartyDetails>
            </EpiPartyDetails>
        </EpiDetails>
        <InvoiceUrlText>Some text</InvoiceUrlText>
    </Invoice>
    

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    
    <xsl:template match="Invoice">
        <xsl:variable name="common-head">
            <xsl:value-of select="SellerDetails/Identifier"/>
            <xsl:text>,</xsl:text>
            <xsl:value-of select="BuyerDetails/BuyerIdentifier"/>
            <xsl:text>,</xsl:text>
            <xsl:value-of select="InvoiceDetails/InvoiceNumber"/>
            <xsl:text>,</xsl:text>
            <!-- add more here -->
        </xsl:variable>
        <xsl:variable name="common-tail">
            <xsl:value-of select="EpiDetails/EpiPartyDetails/EpiBfiPartyDetails/EpiBfiIdentifier"/>
            <xsl:text>,</xsl:text>
            <!-- add more here -->
            <xsl:value-of select="InvoiceUrlText"/>
        </xsl:variable>
        <!-- header -->
        <xsl:text>SellerIdentifier,BuyerIdentifier,InvoiceNumber,ArticleName,RowText,RowText,RowAmount,EpiBfiIdentifier,InvoiceUrlText&#10;</xsl:text>
        <!-- data -->
        <xsl:for-each select="InvoiceRow">
            <xsl:copy-of select="$common-head"/>
            <xsl:value-of select="ArticleName"/>
            <xsl:text>,</xsl:text>  
            <xsl:value-of select="RowAmount"/>
            <xsl:text>,</xsl:text>  
            <!-- add more here -->
            <xsl:copy-of select="$common-tail"/>
            <xsl:text>&#10;</xsl:text>
        </xsl:for-each>
    </xsl:template>
    
    </xsl:stylesheet>
    

    结果

    SellerIdentifier,BuyerIdentifier,InvoiceNumber,ArticleName,RowText,RowText,RowAmount,EpiBfiIdentifier,InvoiceUrlText
    1234-1,1234-2,0001,Article1,10.00,XXXXX,Some text
    1234-1,1234-2,0001,Article2,20.00,XXXXX,Some text
    1234-1,1234-2,0001,Article3,30.00,XXXXX,Some text
    

    添加以响应:

    在 XSLT 中有没有一种方法可以使用循环获得相同的结果?例如循环并输出除 InvoiceRow 元素之外的所有元素和子元素,反之亦然?

    如果您愿意,可以尝试以下方法:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    
    <xsl:template match="Invoice">
        <xsl:variable name="invoice-fields" select="//*[not(*) and not(ancestor::InvoiceRow)]" />
        <xsl:variable name="common-data">
            <xsl:for-each select="$invoice-fields">
                <xsl:value-of select="."/>
                <xsl:text>,</xsl:text>  
            </xsl:for-each> 
        </xsl:variable>
        <!-- header -->
        <xsl:for-each select="$invoice-fields">
            <xsl:value-of select="name()"/>
            <xsl:text>,</xsl:text>  
        </xsl:for-each>
        <xsl:for-each select="InvoiceRow[1]/*">
            <xsl:value-of select="name()"/>
            <xsl:if test="position()!=last()">,</xsl:if>
        </xsl:for-each>
        <xsl:text>&#10;</xsl:text>
        <!-- data -->
        <xsl:for-each select="InvoiceRow">
            <xsl:copy-of select="$common-data"/>
            <xsl:for-each select="*">
                <xsl:value-of select="."/>
                <xsl:if test="position()!=last()">,</xsl:if>
            </xsl:for-each> 
            <xsl:text>&#10;</xsl:text>
        </xsl:for-each>
    </xsl:template>
    
    </xsl:stylesheet>
    

    这里的结果是:

    Identifier,SellerStreet,SellerTown,BuyerIdentifier,BuyerStreet,BuyerTown,BuyerNumber,InvoiceNumber,EpiBfiIdentifier,InvoiceUrlText,ArticleName,RowText,RowText,RowAmount
    1234-1,Street1,Town1,1234-2,Street2,Town2,001234,0001,XXXXX,Some text,Article1,Product Text1,Product Text2,10.00
    1234-1,Street1,Town1,1234-2,Street2,Town2,001234,0001,XXXXX,Some text,Article2,Product Text11,Product Text22,20.00
    1234-1,Street1,Town1,1234-2,Street2,Town2,001234,0001,XXXXX,Some text,Article3,Product Text111,Product Text222,30.00
    

    即在行字段之前列出所有发票字段。

    【讨论】:

    • 非常感谢您提供的出色示例,非常感谢。您的 XSLT 方法效果很好,但希望少一些硬编码元素。 XSLT 中有没有办法使用循环获得相同的结果?例如循环并输出除 InvoiceRow 元素之外的所有元素和子元素,反之亦然?
    • 我想这是可能的,但我没有看到优势。并且很难维持您在问题中显示的输出顺序。
    • 我在我的回答中添加了一个例子——你可以看到它不是很优雅,恕我直言,它会更难维护。
    【解决方案2】:

    我已经完成了与您的要求类似的案例,我创建了一个基于 untangle 的包,该包可以将您的 XML 解析为纯 python 对象,例如:

    <?xml version="1.0"?>
    <root>
        <child name="child1"/>
    </root>
    

    obj.root.child['name'] # u'child1'
    

    那么你就可以很容易的写一些代码来遍历对象来得到你想要的了。 例如,您可以执行get_items_by_tag(InvoiceRow) 之类的操作。 希望对您有所帮助!

    【讨论】:

      猜你喜欢
      • 2013-11-14
      • 2015-02-07
      • 1970-01-01
      • 2021-10-29
      • 2019-02-02
      • 2017-05-23
      • 2011-10-28
      相关资源
      最近更新 更多