Сложный 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&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&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&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() < 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() < 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&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.) In this thesis, we study the dynamics of an elastic body english 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)
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>	</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text> </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>	</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:if test="position()!=last()">
<xsl:text> </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
Примечание: я не мог понять роль "лидера" ни на входе, ни на выходе.
Это возможно и в 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="'	'" /><!-- vertical delimiter -->
<xsl:param name="vDelim" select="'
'" /><!-- 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>
Этот шаблон генерирует с вашими входными данными:
Лидер 001 005 007 008 020a 1004 100a 245a 260b 260c 300a 520a 546a 650a 653a 7004 700a 856u 856z 02179 002893u 12789 20120521 cuuuu---auuuu 120521s|||| xx o 0 u ||| | 9789089640574 Рой ван ван, Роберт авт. Новые перспективы в играх и взаимодействии Amsterdam University Press 2008 1 электронный ресурс (330 стр.) Этот том представляет собой сборник статей... Математика | Философия (общее)| Экономическая теория. Демография Экономика | Философия | Математика | Экономика | Философия |Wiskunde Apt,Krzysztof aut http://www.doabooks.org/doab?func=fulltext&rid=12789|http://www.oapen.org/download?type=document&docid=340074 Описание прав в Каталоге книг открытого доступа (DOAB): атрибуция некоммерческая (CC by-nc) 01452 001933u 15497 20140217 cuuuu---auuuu 140217s|||| xx o 0 u ||| | 9788867050673 Emanuele Haus aut Динамика упругого спутника с внутренним трением. Ledizioni - LediPublishing 2013 1 электронный ресурс (стр.) В этом тезисе мы изучаем динамику... Математика http://www.doabooks.org/doab?func=fulltext&rid=15497|http://www.ledizioni.it/stag/wp-content/uploads/2014/02/tesi_haus.pdf Описание прав в Каталоге книг открытого доступа (DOAB): некоммерческая атрибуция Share Alike (CC by-nc-sa) english
Вы говорите "если в записи отсутствует определенное поле" - из этого я делаю вывод, что у вас должен быть список полей, которые вы хотите экспортировать. (Все MARC? Каждое теоретически возможное поле от 000 до 999? Только вы можете сказать, а вы еще не сказали.) Если у вас нет списка полей, которые вы хотите экспортировать, то ваша задача состоит в противоречив, и вам нужно лучше понять проблему.
Допустим, например, что вы хотите экспортировать поля, перечисленные в переменной $fields.
<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 распространены, когда мы готовим данные для не-XML-систем, таких как электронные таблицы, которые не очень хорошо разбираются в изменениях в структуре; они также распространены среди процедурных программистов, которые не знают другого способа думать о проблемах. И то и другое заставляет некоторых программистов XSLT немного взглянуть на таблицы стилей извлечения, но если вы правильно описали свою проблему, то вам нужна таблица стилей извлечения.
Исходя из сказанного выше, вы должны увидеть, что ваша проблема в том, что шаблон для / формирует вывод путем обработки ввода с <xsl:apply-templates select="*/*" />
, Если на входе нет 546 полей, нет возможности вставить вкладку, где бы они появились, без особых излишних усилий.
Вы хотите заменить текущий apply-templates
, который перебирает внуков, с конструкцией, которая перебирает номера полей в $fields, и для каждого номера поля выдает вкладку и любую другую соответствующую информацию, где другая соответствующая информация зависит от того, присутствуют ли поля с этим номером на входе или нет. В XSLT 3.0 вы сможете применять шаблоны к последовательности значений, чтобы вы могли написать <xsl:apply-templates select="$fields"/>
, но в 2.0 это не вариант. Опции, доступные в 2.0, включают в себя:
Представлять поля $ не как последовательность строк, а как последовательность элементов; вызов
<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>