Сложный XML в TSV с использованием XSLT


Я нашел пару предыдущих вопросов, которые касаются некоторых частей моей проблемы (см. здесь и здесь, но у меня возникли проблемы с их интеграцией. У меня есть набор XML-записей, которые я хочу преобразовать в формат с разделителями табуляции. Однако не все записи XML содержат все поля, а некоторые содержат несколько экземпляров поля.

Два примера XML-записей:

<?xml version="1.0" encoding="UTF-8" ?>
<marc:collection xmlns:marc="http://www.loc.gov/MARC21/slim"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd">
    <marc:record>
        <marc:leader>02179 am a  002893u     </marc:leader>
        <marc:controlfield tag="001">12789</marc:controlfield>
        <marc:controlfield tag="005">20120521</marc:controlfield>
        <marc:controlfield tag="007">cuuuu---auuuu</marc:controlfield>
        <marc:controlfield tag="008">120521s||||    xx      o     0   u ||| |</marc:controlfield>
        <marc:datafield tag="020" ind1=" " ind2=" ">
            <marc:subfield code="a">9789089640574</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="100" ind1="1" ind2=" ">
            <marc:subfield code="a">Rooij van ,Robert</marc:subfield>
            <marc:subfield code="4">aut</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="245" ind1="1" ind2=" ">
            <marc:subfield code="a">New Perspectives on Games and Interaction</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="260" ind1=" " ind2=" ">
            <marc:subfield code="b">Amsterdam University Press</marc:subfield>
            <marc:subfield code="c">2008</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="300" ind1=" " ind2=" ">
            <marc:subfield code="a">1 electronic resource (330 p.)</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="520" ind1=" " ind2=" ">
            <marc:subfield code="a">This volume is a collection of papers ...</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="650" ind1=" " ind2="0">
            <marc:subfield code="a">Mathematics</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="650" ind1=" " ind2="0">
            <marc:subfield code="a">Philosophy (General)</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="650" ind1=" " ind2="0">
            <marc:subfield code="a">Economic theory. Demography</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="653" ind1=" " ind2=" ">
            <marc:subfield code="a">Economics</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="653" ind1=" " ind2=" ">
            <marc:subfield code="a">Philosophy</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="653" ind1=" " ind2=" ">
            <marc:subfield code="a">Mathematics</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="653" ind1=" " ind2=" ">
            <marc:subfield code="a">Economie</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="653" ind1=" " ind2=" ">
            <marc:subfield code="a">Filosofie</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="653" ind1=" " ind2=" ">
            <marc:subfield code="a">Wiskunde</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="700" ind1="1" ind2=" ">
            <marc:subfield code="a">Apt ,Krzysztof</marc:subfield>
            <marc:subfield code="4">aut</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="856" ind1="4" ind2="0">
            <marc:subfield code="u">http://www.doabooks.org/doab?func=fulltext&amp;rid=12789</marc:subfield>
            <marc:subfield code="z">Description of rights in Directory of Open Access Books (DOAB): Attribution Non-commercial (CC by-nc)</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="856" ind1="4" ind2="0">
            <marc:subfield code="u">http://www.oapen.org/download?type=document&amp;docid=340074</marc:subfield>
        </marc:datafield>
    </marc:record>
    <marc:record>
        <marc:leader>01452 am a  001933u     </marc:leader>
        <marc:controlfield tag="001">15497</marc:controlfield>
        <marc:controlfield tag="005">20140217</marc:controlfield>
        <marc:controlfield tag="007">cuuuu---auuuu</marc:controlfield>
        <marc:controlfield tag="008">140217s||||    xx      o     0   u ||| |</marc:controlfield>
        <marc:datafield tag="020" ind1=" " ind2=" ">
            <marc:subfield code="a">9788867050673</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="100" ind1="1" ind2=" ">
            <marc:subfield code="a">Emanuele Haus</marc:subfield>
            <marc:subfield code="4">aut</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="245" ind1="1" ind2=" ">
            <marc:subfield code="a">Dynamics of an elastic satellite with internal friction.</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="260" ind1=" " ind2=" ">
            <marc:subfield code="b">Ledizioni - LediPublishing</marc:subfield>
            <marc:subfield code="c">2013</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="300" ind1=" " ind2=" ">
            <marc:subfield code="a">1 electronic resource ( p.)</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="520" ind1=" " ind2=" ">
            <marc:subfield code="a">n this thesis, we study the dynamics...</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="546" ind1=" " ind2=" ">
            <marc:subfield code="a">english</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="650" ind1=" " ind2="0">
            <marc:subfield code="a">Mathematics</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="856" ind1="4" ind2="0">
            <marc:subfield code="u">http://www.doabooks.org/doab?func=fulltext&amp;rid=15497</marc:subfield>
            <marc:subfield code="z">Description of rights in Directory of Open Access Books (DOAB): Attribution Non-commercial Share Alike (CC by-nc-sa)</marc:subfield>
        </marc:datafield>
        <marc:datafield tag="856" ind1="4" ind2="0">
            <marc:subfield code="u">http://www.ledizioni.it/stag/wp-content/uploads/2014/02/tesi_haus.pdf</marc:subfield>
        </marc:datafield>
    </marc:record>
</marc:collection>

Я пытался адаптировать XSLT из этого предыдущего ответа, с небольшим успехом, так что далеко:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xpath-default-namespace="http://www.loc.gov/MARC21/slim">
    <xsl:output method="text"/>
    <xsl:variable name="delimiter" select="'	'"/>

    <xsl:strip-space elements="*"/>
    <xsl:output method="text"/>

    <xsl:key name="field" 
      match="/collection/record/datafield/subfield" 
      use="concat(../@tag,@code)"/>

    <!-- variable containing the first occurrence of each field -->
    <xsl:variable name="allFields"
        select="/collection/record/datafield/subfield
                [generate-id()
                 =generate-id(key('field', 
                                   concat(../@tag,@code))[1])]" />

    <xsl:template match="/">

        <xsl:for-each select="$allFields">
            <xsl:sort select="substring(concat(../@tag,@code),1,3)"
                      data-type="number"/>
            <xsl:value-of select="concat(../@tag,@code)" />
            <xsl:if test="position() &lt; last()">
                <xsl:value-of select="$delimiter" />
            </xsl:if>
        </xsl:for-each>
        <xsl:text>
</xsl:text>
        <xsl:apply-templates select="*/*" />
    </xsl:template>

    <xsl:template match="*">
        <xsl:variable name="this" select="." />

        <xsl:for-each select="$allFields">
            <xsl:sort 
              select="substring(concat(../@tag,@code),1,3)" 
              data-type="number"/>
            <xsl:value-of 
              select="$this/*[@code = current()/@code]" />
            <xsl:if test="position() &lt; last()">
                <xsl:value-of select="$delimiter" />
            </xsl:if>
        </xsl:for-each>
        <xsl:text>
</xsl:text>
    </xsl:template>
</xsl:stylesheet>

В выводе, который я пытаюсь достичь, заголовок будет состоять из leader, за которым следуют уникальные значения @tag (объединенные с subfield/@code для подполей), отсортированных в порядке возрастания по tag:

leader  001 005 007 008 020a    100a    1004    245a    260b    260c    300a    520a    546a    650a    653a    700a    7004    856u    856z

Если запись имеет несколько значений для одной комбинации field/subfield, я хочу объединить их вместе, например:

653a
Economics|Philosophy|Mathematics

Однако, если в записи отсутствует определенное поле, я хочу просто вывести символ табуляции, чтобы сохранить все выровненный.

Полный образец вывода TSV:

leader  001 005 007 008 020a    100a    1004    245a    260b    260c    300a    520a    546a    650a    653a    700a    7004    856u    856z                                        
02179 am a  002893u         12789   20120521    cuuuu---auuuu   120521s||||    xx      o     0   u ||| |    9789089640574   Rooij van ,Robert   aut New Perspectives on Games and Interaction   Amsterdam University Press  2008    1 electronic resource (330 p.)  This volume is a collection of papers       Mathematics|Philosophy (General)|Economic theory. Demography    Economics|Philosophy|Mathematics|Economie|Filosofie|Wiskunde    Apt ,Krzysztof< aut http://www.doabooks.org/doab?func=fulltext&amp;rid=12789|http://www.oapen.org/download?type=document&amp;docid=340074   Description of rights in Directory of Open Access Books (DOAB): Attribution Non-commercial (CC by-nc)                                       
01452 am a  001933u         15497   20140217    cuuuu---auuuu   140217s||||    xx      o     0   u ||| |    9788867050673   Emanuele Haus   aut Dynamics of an elastic satellite with internal friction.    Ledizioni - LediPublishing  2013    1 electronic resource ( p.) In this thesis, we study the dynamics of an elastic body    english Mathematics             http://www.doabooks.org/doab?func=fulltext&amp;rid=15497|http://www.ledizioni.it/stag/wp-content/uploads/2014/02/tesi_haus.pdf  Description of rights in Directory of Open Access Books (DOAB): Attribution Non-commercial Share Alike (CC by-nc-sa)                                        
3 2

3 ответа:

Я бы предложил вам попробовать это следующим образом:

XSLT 2.0

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:marc="http://www.loc.gov/MARC21/slim"
exclude-result-prefixes="marc">
<xsl:output method="text" encoding="UTF-8"/>

<xsl:variable name="fields">
    <xsl:for-each-group select="/marc:collection/marc:record/marc:datafield" group-by="@tag">
        <xsl:sort select="@tag"/>
            <xsl:for-each select="marc:subfield">
                <xsl:sort/>
                <field tag="{current-grouping-key()}" code="{@code}">a</field>
            </xsl:for-each>
    </xsl:for-each-group>
</xsl:variable>

<xsl:template match="/">
    <!-- header -->
    <xsl:for-each select="$fields/field">
        <xsl:value-of select="@tag"/>
        <xsl:value-of select="@code"/>
        <xsl:if test="position()!=last()">
            <xsl:text>&#9;</xsl:text>
        </xsl:if>
    </xsl:for-each>
    <xsl:text>&#10;</xsl:text>
    <!-- data -->
    <xsl:for-each select="marc:collection/marc:record">
        <xsl:variable name="current-record" select="." />
        <xsl:for-each select="$fields/field">
            <xsl:value-of select="$current-record/marc:datafield[@tag=current()/@tag]/marc:subfield[@code=current()/@code]" separator="|"/>
            <xsl:if test="position()!=last()">
                <xsl:text>&#9;</xsl:text>
            </xsl:if>
        </xsl:for-each>
        <xsl:if test="position()!=last()">
            <xsl:text>&#10;</xsl:text>
        </xsl:if>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Результат , примененный к вашему примеру ввода:

020a    100a    1004    245a    260c    260b    300a    520a    546a    650a    653a    700a    7004    856z    856u
9789089640574   Rooij van ,Robert   aut New Perspectives on Games and Interaction   2008    Amsterdam University Press  1 electronic resource (330 p.)  This volume is a collection of papers ...       Mathematics|Philosophy (General)|Economic theory. Demography    Economics|Philosophy|Mathematics|Economie|Filosofie|Wiskunde    Apt ,Krzysztof  aut Description of rights in Directory of Open Access Books (DOAB): Attribution Non-commercial (CC by-nc)   http://www.doabooks.org/doab?func=fulltext&rid=12789|http://www.oapen.org/download?type=document&docid=340074
9788867050673   Emanuele Haus   aut Dynamics of an elastic satellite with internal friction.    2013    Ledizioni - LediPublishing  1 electronic resource ( p.) n this thesis, we study the dynamics... english Mathematics             Description of rights in Directory of Open Access Books (DOAB): Attribution Non-commercial Share Alike (CC by-nc-sa)    http://www.doabooks.org/doab?func=fulltext&rid=15497|http://www.ledizioni.it/stag/wp-content/uploads/2014/02/tesi_haus.pdf

Примечание : я не мог понять роль "лидера" ни во входных, ни в выходных данных.

Вы говорите: "если в записи отсутствует определенное поле" - из этого я делаю вывод, что у вас должен быть список полей, которые вы хотите экспортировать. (Весь Марк? Каждое теоретически возможное поле От 000 до 999? только вы можете сказать, А вы не сказали.) Если у вас нет списка полей, которые вы хотите экспортировать, то ваша формулировка проблемы является самопротиворечивой, и вам нужно лучше понять проблему.

Предположим, например, что вы хотите экспортировать поля, перечисленные в переменное поле.

<xsl:variable name="fields" as="xs:string*"
  select="tokenize('001 005 007 008 020 
                    100 245 260 260 300 
                    520 546 650 653 700 
                    856', '\s+')"/>
Ваша текущая проблема заключается в том, что ваши выходные данные формируются полями, присутствующими во входных данных, в том, что многие программисты XSLT называют "push" таблицей стилей. Вы хотите, чтобы выходные данные формировались по списку полей в $fields, а не по входным данным - вам нужно то, что эти программисты XSLT называют таблицей стилей "pull". Таблицы стилей Pull часто используются при подготовке данных для систем, отличных от XML, таких как электронные таблицы, которые не очень хорошо справляются с изменениями структуры; они также являются распространено среди процедурных программистов, которые не знают другого способа думать о проблемах. Оба они заставляют некоторых программистов XSLT смотреть свысока на таблицы стилей pull, но если вы правильно описали свою проблему, то таблица стилей pull-это то, что вам нужно.

Из того, что было сказано до сих пор, вы должны быть в состоянии увидеть, что ваша проблема заключается в том, что шаблон для / строит выходные данные, обрабатывая входные данные с помощью <xsl:apply-templates select="*/*" />. Если входные данные не содержат 546 полей, то нет никакой возможности вставить вкладку туда, где они бы появились, без большого количества лишних усилий.

Вы хотите заменить текущую apply-templates, которая повторяется над внуками, на конструкцию, которая повторяется над номерами полей в $fields, и для каждого номера поля выдает вкладку и любую другую соответствующую информацию, где другая соответствующая информация зависит от того, присутствуют ли поля с этим номером во входных данных или нет. В XSLT 3.0 вы сможете применять шаблоны к последовательности значений, так что вы можете написать <xsl:apply-templates select="$fields"/>, но в 2.0 это не вариант. Опции, доступные в версии 2.0, включают:

  • Представьте $fields не как последовательность строк, а как последовательность элементов; вызовите <xsl:apply-templates select="$fields"/>, чтобы перебрать нужные номера полей. Вам нужно будет не забыть передать узел из входного документа (корень-хороший выбор), чтобы вы могли вернуться в него из шаблона для номера поля.

  • Вызов именованного шаблона с $fields в качестве параметр; в именованном шаблоне выберите первый номер поля из списка, обработайте его, а затем рекурсивно вызовите тот же именованный шаблон с остальной частью списка. Если нет первого номера поля, последовательность номеров полей пуста, и вы закончили.

  • Напишите рекурсивную функцию, которая работает так же, как только что описанный именованный шаблон.

  • Напишите функцию, которая обрабатывает один номер поля для одной записи MARC, и вызовите ее из Выражение XPath for:

    <xsl:template match="marc:record">
      ...
      <xsl:sequence select="for $fn in $fields
         return my:one-field-one-record($fn, .)
         "/>
      ...
    </xsl:template>
    

Это возможно и в XSLT 1.0.

Следующее решение строится на основе общего для всего документа списка уникальных тегов и итерации этого списка для каждой записи. По сути, это позволяет выводить разделители даже тогда, когда конкретный тег отсутствует в записи.

<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:marc="http://www.loc.gov/MARC21/slim"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
  <xsl:output method="text" encoding="Windows-1252" />

  <xsl:param name="hDelim" select="'&#x9;'" /><!-- vertical delimiter -->
  <xsl:param name="vDelim" select="'&#xA;'" /><!-- horizontal delimiter -->
  <xsl:param name="sDelim" select="'|'" /><!-- subfield delimiter -->

  <!-- group tags by @tag + @code -->
  <xsl:key name="kAllTags" match="marc:controlfield | marc:subfield" use="
    concat(@tag, ../@tag, @code)
  " />
  <!-- group tags by record ID +  @tag + @code -->
  <xsl:key name="kRecordTags" match="marc:controlfield | marc:subfield" use="
    concat(generate-id(ancestor::marc:record), ':', @tag|../@tag, @code)
  " />
  <!-- build a list of unique tags to iterate over -->
  <xsl:variable name="uniqueTags" select="
    (//marc:controlfield | //marc:subfield)[
      generate-id() = generate-id(key('kAllTags', concat(@tag | ../@tag, @code)))
    ]
  " />

  <xsl:template match="marc:collection">
    <!-- write header line -->
    <xsl:text>leader</xsl:text>
    <xsl:value-of select="$hDelim" />

    <xsl:apply-templates select="$uniqueTags" mode="head">
      <xsl:sort select="concat(@tag|../@tag, @code)" />
    </xsl:apply-templates>
    <xsl:value-of select="$vDelim" />

    <!-- write individual records -->
    <xsl:apply-templates select="marc:record" />
  </xsl:template>

  <xsl:template match="marc:record">
    <xsl:variable name="recordId" select="generate-id()" />

    <xsl:value-of select="marc:leader" />
    <xsl:value-of select="$hDelim" />

    <!-- for each unique tag, find the fields that have that tag on this record -->
    <xsl:for-each select="$uniqueTags">
      <xsl:variable name="tagKey" select="concat($recordId, ':', @tag|../@tag, @code)" />
      <xsl:apply-templates select="key('kRecordTags', $tagKey)" mode="data" />
      <xsl:if test="position() != last()"><xsl:value-of select="$hDelim" /></xsl:if>
    </xsl:for-each>
    <xsl:if test="position() != last()"><xsl:value-of select="$vDelim" /></xsl:if>
  </xsl:template>

  <xsl:template match="marc:controlfield | marc:subfield" mode="head">
    <xsl:value-of select="concat(@tag|../@tag, @code)" />
    <xsl:if test="position() != last()"><xsl:value-of select="$hDelim" /></xsl:if>
  </xsl:template>

  <xsl:template match="marc:controlfield | marc:subfield" mode="data">
    <xsl:value-of select="normalize-space()" />
    <xsl:if test="position() != last()"><xsl:value-of select="$sDelim" /></xsl:if>
  </xsl:template>
</xsl:stylesheet>

Этот шаблон генерирует с вашими входными данными:

leader  001 005 007 008 020a    1004    100a    245a    260b    260c    300a    520a    546a    650a    653a    7004    700a    856u    856z
02179 am a  002893u         12789   20120521    cuuuu---auuuu   120521s|||| xx o 0 u ||| |  9789089640574   Rooij van ,Robert   aut New Perspectives on Games and Interaction   Amsterdam University Press  2008    1 electronic resource (330 p.)  This volume is a collection of papers ...   Mathematics|Philosophy (General)|Economic theory. Demography    Economics|Philosophy|Mathematics|Economie|Filosofie|Wiskunde    Apt ,Krzysztof  aut http://www.doabooks.org/doab?func=fulltext&rid=12789|http://www.oapen.org/download?type=document&docid=340074   Description of rights in Directory of Open Access Books (DOAB): Attribution Non-commercial (CC by-nc)   
01452 am a  001933u         15497   20140217    cuuuu---auuuu   140217s|||| xx o 0 u ||| |  9788867050673   Emanuele Haus   aut Dynamics of an elastic satellite with internal friction.    Ledizioni - LediPublishing  2013    1 electronic resource ( p.) n this thesis, we study the dynamics... Mathematics             http://www.doabooks.org/doab?func=fulltext&rid=15497|http://www.ledizioni.it/stag/wp-content/uploads/2014/02/tesi_haus.pdf  Description of rights in Directory of Open Access Books (DOAB): Attribution Non-commercial Share Alike (CC by-nc-sa)    english