Сложный 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
Примечание : я не мог понять роль "лидера" ни во входных, ни в выходных данных.
Вы говорите: "если в записи отсутствует определенное поле" - из этого я делаю вывод, что у вас должен быть список полей, которые вы хотите экспортировать. (Весь Марк? Каждое теоретически возможное поле От 000 до 999? только вы можете сказать, А вы не сказали.) Если у вас нет списка полей, которые вы хотите экспортировать, то ваша формулировка проблемы является самопротиворечивой, и вам нужно лучше понять проблему.
Предположим, например, что вы хотите экспортировать поля, перечисленные в переменное поле.
Ваша текущая проблема заключается в том, что ваши выходные данные формируются полями, присутствующими во входных данных, в том, что многие программисты XSLT называют "push" таблицей стилей. Вы хотите, чтобы выходные данные формировались по списку полей в $fields, а не по входным данным - вам нужно то, что эти программисты XSLT называют таблицей стилей "pull". Таблицы стилей Pull часто используются при подготовке данных для систем, отличных от XML, таких как электронные таблицы, которые не очень хорошо справляются с изменениями структуры; они также являются распространено среди процедурных программистов, которые не знают другого способа думать о проблемах. Оба они заставляют некоторых программистов XSLT смотреть свысока на таблицы стилей pull, но если вы правильно описали свою проблему, то таблица стилей pull-это то, что вам нужно.<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+')"/>
Из того, что было сказано до сих пор, вы должны быть в состоянии увидеть, что ваша проблема заключается в том, что шаблон для / строит выходные данные, обрабатывая входные данные с помощью
<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="'	'" /><!-- 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>
Этот шаблон генерирует с вашими входными данными:
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