Я проектирую какой-то конвейер ETL, где я хотел бы сначала разбить входной набор данных XML на отдельные файлы XML, связанные с каждым элементом. Входные наборы данных являются в основном экспортом метаданных по конкретным моделям (текущий пример - EDM). Я довольно доволен XSLT и надеялся использовать это, чтобы избежать слишком большого количества Python в этом вопросе, который, предположительно, не настолько сложен.
Я просмотрел много тем, включая Лизу Дейли Фаст_итер (см. 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>
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>
... нет выхода. Также пытался написать, но не работает
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)
Я думаю, что проблема связана с тем фактом, что я обрабатываю пространства имен неправильно или, может быть, потому, что я все еще использую XSLT-подход к данным, когда это Python... некоторая помощь будет оценена :)
Поскольку вы пытаетесь обработать XSLT с помощью lxml, вы застряли с XSLT 1.0. Поскольку 1.0 не поддерживает xsl:result-document
, вам придется использовать расширение document
exlst (которое, к счастью, поддерживает 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>
питон
from lxml import etree
tree = etree.parse("test.xml")
xslt = etree.parse("test.xsl")
tree.xslt(xslt)
Вывод (имена файлов основаны на сгенерированном идентификаторе, поэтому они могут отличаться при запуске моего кода.)
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>
питон
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)
В качестве альтернативы рассмотрите возможность передачи параметра в XSLT из Python с использованием lxml
для итерации и создания отдельных файлов 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>
питон
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)
lxml
анализирует и запускает?