Детализация больших XML-файлов в Python

1

Я проектирую какой-то конвейер 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>


ТЕКУЩИЕ ПРОБЫ

Попытка использовать 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>

... нет выхода. Также пытался написать, но не работает

Попытка через 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)

Я думаю, что проблема связана с тем фактом, что я обрабатываю пространства имен неправильно или, может быть, потому, что я все еще использую XSLT-подход к данным, когда это Python... некоторая помощь будет оценена :)

  • 0
    Где ваш XSLT, язык программирования, который lxml анализирует и запускает?
  • 0
    @Parfait вот оно (в идеале было бы неплохо передать верхний дочерний узел в качестве аргумента при запуске таблицы стилей ...)
Теги:
xslt
lxml
elementtree

2 ответа

2
Лучший ответ

Поскольку вы пытаетесь обработать 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)
  • 0
    Спасибо @ Дэниел-Хейли, это сработало. Это большая помощь! И последнее, также должна быть возможность передать параметр в xslt, например, xpath узла item? Вы используете / * / *, чтобы выбрать эти узлы в xslt, но мне, возможно, придется быть более конкретным (пока не уверен насчет сложности моделей данных, с которыми я буду работать)
  • 0
    @ Pierre-EdouardBarrault - я не думаю, что вы можете передать параметр и использовать его в качестве xpath вместо простой строки. Вместо этого вы можете обновить XSLT, указав путь перед обработкой. Я добавил пример.
Показать ещё 4 комментария
0

В качестве альтернативы рассмотрите возможность передачи параметра в 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)
  • 0
    Спасибо @Parfait, это может быть полезно, чтобы обойти текущую проблему, я буду сравнивать ее с другим ответом

Ещё вопросы

Сообщество Overcoder
Наверх
Меню