在 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)
我正在设计某种 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)