在 Python 中列出大 xml 文件

Itemizing big xml files in Python

我正在设计某种 ETL 管道,我希望首先将输入 XML 数据集拆分为与每个项目相关的单独 XML 文件。输入数据集基本上是特定模型下元数据的导出(当前示例是 EDM)。我对 XSLT 相当满意,并希望使用它来避免在这个问题上过多 Python,这应该不是那么复杂。

我浏览了很多主题,包括 Lisa Daly 的 Fast_iter(参见 https://www.ibm.com/developerworks/xml/library/x-hiperfparse/)。我尝试了不同的方法,但在编写文件时我总是卡住(没有输出或序列化问题)。请寻找一些经验丰富的反馈?!

数据集结构

<rdf:RDF ...many namespaces...>
    <!--ITEM1 NODE-->
    <ore:aggregates>
        <edm:ProvidedCHO rdf:about="http://some/url"/>
        <ore:Aggregation rdf:about="http://some/url">
            <...>
        </ore:Aggregation>
        <ore:Proxy rdf:about="http://some/url">
            <...>
        </ore:Proxy>
        <edm:EuropeanaAggregation rdf:about="http://some/url">
            <...>      
        </edm:EuropeanaAggregation>
    </ore:aggregates>

    <!--ITEM2 NODE-->
    <ore:aggregates>
        <...>      
    </ore:aggregates>

    <!--ITEM3 NODE-->
    <ore:aggregates>
        <...>      
    </ore:aggregates>
</rdf:RDF>

预期结果

<!--ITEM 1-->
<rdf:RDF ...many namespaces...>
    <edm:ProvidedCHO rdf:about="http://some/url"/>
    <ore:Aggregation rdf:about="http://some/url">
        <...>
    </ore:Aggregation>
    <ore:Proxy rdf:about="http://some/url">
        <...>
    </ore:Proxy>
    <edm:EuropeanaAggregation rdf:about="http://some/url">
        <...>      
    </edm:EuropeanaAggregation>
</rdf:RDF>


当前试训

尝试使用 lxml 应用一次逐项 XSLT (script+xslt)

from lxml import etree as ET
    dom = ET.parse(source)
    xslt = ET.parse(xsl_filename)
    transform = ET.XSLT(xslt)
    newdom = transform(dom)
    print(ET.tostring(newdom, pretty_print=True))
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet exclude-result-prefixes="xsi xlink xml" version="2.0"
    xmlns:many="namespaces">

    <xsl:output encoding="UTF-8" indent="yes"/>

    <!--<xsl:param name="output" select="'/Users/yep/Code/+dev/test data/output/'"/>-->
    <xsl:param name="output" select="'/home/yep/data/split/'"/>
    <xsl:param name="children" select="/rdf:RDF/ore:aggregates"/>

    <!-- ROOT MATCH -->
    <xsl:template match="/">
        <xsl:for-each select="$children">
            <xsl:call-template name="itemize"/>
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="itemize">

            <xsl:variable name="uri" select="translate(ore:Proxy/dc:identifier, ' ', '_')"/>
            <xsl:variable name="ns"/>
            <xsl:variable name="fullOutput" select="concat($output, $uri)"/>
            <xsl:result-document href="{$fullOutput}.xml" method="xml">
                <xsl:element name="rdf:RDF">
                    <xsl:copy-of select="namespace::*"/>
                    <xsl:copy-of select="*"/>
                </xsl:element>
            </xsl:result-document>
    </xsl:template>

</xsl:stylesheet>

...没有输出。也试过 'write' 但没有用

正在通过 ETree 尝试

import xml.etree.ElementTree as ET
    root = ET.parse(source).getroot()

    # namespaces variable generated from a json file
    jsonFile = open("application/models/namespaces.json")
    jsonStr = jsonFile.read()
    namespaces = json.loads(jsonStr)

    for item in root.findall("ore:aggregates",namespaces):
        newTree = ET.parse("/home/yep/application/services/create/sample.xml")
        newroot = newTree.getroot()

        for node in item.findall("edm:ProvidedCHO",namespaces):
            newroot.append(node)
            ET.SubElement(newroot,node)

        filename = "/home/yep/data/split/" + str(i) + ".xml"
        newTree.write(filename)

TypeError: cannot serialize <Element '{http://www.europeana.eu/schemas/edm/}ProvidedCHO' at 0x7f4768a03688> (type Element)

我认为这个问题与我没有正确处理名称空间这一事实有关,或者可能是因为当数据是 Python 时我仍然采用 XSLT 方法来处理数据...一些帮助将不胜感激: )

由于您尝试使用 lxml 处理 XSLT,因此您只能使用 XSLT 1.0。由于 1.0 不支持 xsl:result-document,您必须使用 exlst document 扩展(幸运的是 lxml 支持)。

这是一个例子...

XML 输入 (test.xml)

<rdf:RDF xmlns:rdf="http://some rdf uri" xmlns:edm="http://some edm uri" xmlns:ore="http://some ore uri">
    <!--ITEM1 NODE-->
    <ore:aggregates>
        <edm:ProvidedCHO rdf:about="http://some/url">from item1</edm:ProvidedCHO>
        <ore:Aggregation rdf:about="http://some/url">from item1</ore:Aggregation>
        <ore:Proxy rdf:about="http://some/url">from item1</ore:Proxy>
        <edm:EuropeanaAggregation rdf:about="http://some/url">from item1</edm:EuropeanaAggregation>
    </ore:aggregates>

    <!--ITEM2 NODE-->
    <ore:aggregates>
        <edm:ProvidedCHO rdf:about="http://some/url">from item2</edm:ProvidedCHO>
        <ore:Aggregation rdf:about="http://some/url">from item2</ore:Aggregation>
        <ore:Proxy rdf:about="http://some/url">from item2</ore:Proxy>
        <edm:EuropeanaAggregation rdf:about="http://some/url">from item2</edm:EuropeanaAggregation>
    </ore:aggregates>

    <!--ITEM3 NODE-->
    <ore:aggregates>
        <edm:ProvidedCHO rdf:about="http://some/url">from item3</edm:ProvidedCHO>
        <ore:Aggregation rdf:about="http://some/url">from item3</ore:Aggregation>
        <ore:Proxy rdf:about="http://some/url">from item3</ore:Proxy>
        <edm:EuropeanaAggregation rdf:about="http://some/url">from item3</edm:EuropeanaAggregation>
    </ore:aggregates>
</rdf:RDF>

XSLT 1.0 (test.xsl)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:exsl="http://exslt.org/common"
  extension-element-prefixes="exsl">
  <xsl:strip-space elements="*"/>

  <xsl:template match="/*/*">
    <xsl:apply-templates select=".." mode="copy">
      <xsl:with-param name="target_id" select="generate-id()"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="/*" mode="copy">
    <xsl:param name="target_id"/>
    <exsl:document href="{$target_id}.xml" indent="yes">
      <xsl:copy>
        <xsl:copy-of select="@*|*[generate-id()=$target_id]/*"/>
      </xsl:copy>      
    </exsl:document>
  </xsl:template>

</xsl:stylesheet>

Python

from lxml import etree

tree = etree.parse("test.xml")
xslt = etree.parse("test.xsl")

tree.xslt(xslt)

输出(文件名基于生成的 ID,因此当 运行 我的代码时它们可能会有所不同。)

idm253366124.xml

<?xml version="1.0"?>
<rdf:RDF xmlns:rdf="http://some_rdf_uri" xmlns:edm="http://some_edm_uri" xmlns:ore="http://some_ore_uri">
  <edm:ProvidedCHO rdf:about="http://some/url">from item1</edm:ProvidedCHO>
  <ore:Aggregation rdf:about="http://some/url">from item1</ore:Aggregation>
  <ore:Proxy rdf:about="http://some/url">from item1</ore:Proxy>
  <edm:EuropeanaAggregation rdf:about="http://some/url">from item1</edm:EuropeanaAggregation>
</rdf:RDF>

idm219411756.xml

<?xml version="1.0"?>
<rdf:RDF xmlns:rdf="http://some_rdf_uri" xmlns:edm="http://some_edm_uri" xmlns:ore="http://some_ore_uri">
  <edm:ProvidedCHO rdf:about="http://some/url">from item2</edm:ProvidedCHO>
  <ore:Aggregation rdf:about="http://some/url">from item2</ore:Aggregation>
  <ore:Proxy rdf:about="http://some/url">from item2</ore:Proxy>
  <edm:EuropeanaAggregation rdf:about="http://some/url">from item2</edm:EuropeanaAggregation>
</rdf:RDF>

idm219410244.xml

<?xml version="1.0"?>
<rdf:RDF xmlns:rdf="http://some_rdf_uri" xmlns:edm="http://some_edm_uri" xmlns:ore="http://some_ore_uri">
  <edm:ProvidedCHO rdf:about="http://some/url">from item3</edm:ProvidedCHO>
  <ore:Aggregation rdf:about="http://some/url">from item3</ore:Aggregation>
  <ore:Proxy rdf:about="http://some/url">from item3</ore:Proxy>
  <edm:EuropeanaAggregation rdf:about="http://some/url">from item3</edm:EuropeanaAggregation>
</rdf:RDF>

更新动态路径...

XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:rdf="http://some_rdf_uri" xmlns:edm="http://some_edm_uri" 
  xmlns:ore="http://some_ore_uri"
  xmlns:exsl="http://exslt.org/common"
  extension-element-prefixes="exsl">
  <xsl:strip-space elements="*"/>

  <xsl:key name="elem_by_id" match="*" use="generate-id()"/>

  <xsl:template match="/*" name="root">
    <xsl:apply-templates select="*"/>
  </xsl:template>

  <xsl:template match="*">
    <xsl:apply-templates select="/*" mode="copy">
      <xsl:with-param name="target_id" select="generate-id()"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="/*" mode="copy">
    <xsl:param name="target_id"/>
    <exsl:document href="temp/{$target_id}.xml" indent="yes">
      <xsl:copy>
        <xsl:copy-of select="@*|key('elem_by_id',$target_id)/*"/>
      </xsl:copy>      
    </exsl:document>
  </xsl:template>

</xsl:stylesheet>

Python

from lxml import etree

tree = etree.parse("test.xml")
xslt = etree.parse("test.xsl")

target_path = "/rdf:RDF/ore:aggregates"

try:
    elem = xslt.xpath("/xsl:stylesheet/xsl:template[@name='root']/xsl:apply-templates",
                      namespaces={"xsl": "http://www.w3.org/1999/XSL/Transform"})[0]
    elem.attrib["select"] = target_path
except IndexError:
    print("Could not find xsl:template to update.")

tree.xslt(xslt)

或者,考虑使用 lxml 将参数从 Python 传递给 XSLT 以迭代并创建单独的 XML 文件 position() 每个 ore:aggregate 的文件:

XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                              xmlns:rdf="rdf.com" 
                              xmlns:ore="ore.com" 
                              xmlns:edm="edm.com">
    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>

    <!-- XSL PARAM -->
    <xsl:param name="item_num"/>

    <!-- IDENTITY TRANSFORM -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <!-- EMPTY TEMPLATE TO REMOVE NON-SELECTED ITEMS -->        
    <xsl:template match="ore:aggregates[position()!=$item_num]"/>

    <xsl:template match="comment()"/>
</xsl:stylesheet>

Python

import lxml.etree as et

# LOAD XML AND XSL SCRIPT
ns = {"ore": "ore.com"}                    # ORE NAMESPACE
xml = et.parse('/path/to/input/xml')
xsl = et.parse('/path/to/XSLT/script.xsl')
transform = et.XSLT(xsl)

# LOOP THROUGH ALL NODE COUNTS AND PASS PARAMETER TO XSLT
ore_agg_count = len(xml.xpath('//ore:aggregates', namespaces=ns))
for i in range(ore_agg_count):
   n = et.XSLT.strparam(str(i))            # NAME OF XSL PARAMETER
   result = transform(xml, item_num=n)

   # SAVE XML TO FILE
   with open('ore_aggregates_{}.xml'.format(i), 'wb') as f:
       f.write(result)