Tuesday, 9 December 2014

Complex TSV to XML using XSLT



This is a follow-up question to a previous post. I'm working with a set of library catalog records (in MARC XML format), which I want to clean and enhance using a tool called OpenRefine. OpenRefine does not work very well with XML data, so I needed to transform the MARC XML to TSV.


The solution to my earlier post helped me do that. However, once I export from OpenRefine, I need to round-trip the data back into MARC XML. The output from OpenRefine is more complex than typical TSV: a single record can be spread across multiple lines because certain fields can have multiple values:



leader 001 005 007 008 020__$a 1001_$a 1001_$4 2451_$a 260__$b 260__$c 300__$a 520__$a 546__$a 650_0$a 653__$a 7001_$a 7001_$4 85640$u 85640$z
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 ... Mathematics Economics Apt ,Krzysztof aut http://ift.tt/1IvHFRH Description of rights in Directory of Open Access Books (DOAB): Attribution Non-commercial (CC by-nc)
Philosophy (General) Philosophy http://ift.tt/12K7OLe
Economic theory. Demography Mathematics
Economie
Filosofie
Wiskunde
01914 am a 002413u 13087 20120521 cuuuu---auuuu 120521s|||| xx o 0 u ||| | 9783938616352 Roquette, Peter aut Helmut Hasse und Emmy Noether ; die Korrespondenz 1925 - 1935. Universitätsverlag Göttingen 2006 1 electronic resource ( p.) This book reproduces ... German Science (General) mathematics Lemmermeyer, Franz aut http://ift.tt/1IvHDsV Description of rights in Directory of Open Access Books (DOAB): Attribution No Derivatives (CC by-nd)
Mathematics correspondence http://ift.tt/12K7MmE
02345 am a 002773u 13241 20120521 cuuuu---auuuu 120521s|||| xx o 0 u ||| | 9783940344502 Roquette, Peter aut Emil Artin und Helmut Hasse Universitätsverlag Göttingen 2008 1 electronic resource ( p.) This book contains ... German Mathematics history of mathematics Lemmermeyer, Franz aut http://ift.tt/1IvHFRL Description of rights in Directory of Open Access Books (DOAB): Attribution No Derivatives (CC by-nd)
Geschichte der Mathematik Frei, Günther aut http://ift.tt/12K7OLg
OAPEN Noether, Emmy aut
Hasse, Helmut aut


I am trying to modify an XSLT 2.0 stylesheet that converts from TSV to XML (based on a solution proposed here):



<xsl:stylesheet version="2.0" xmlns:xsl="http://ift.tt/tCZ8VR">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>


<!-- File path parameter. -->
<xsl:param name="filePath">stack_test-tsv-2.tsv</xsl:param>

<!-- Main template that parses the TSV and creates structured XML. -->
<xsl:template match="dummy">
<marc:collection xmlns:marc="http://ift.tt/1sMy6Fc"
xmlns:xsi="http://ift.tt/ra1lAU"
xsi:schemaLocation="http://ift.tt/1sMy6Fc http://ift.tt/1wDzy1b">

<!-- Read in TSV file. -->
<xsl:variable name="text" select="unparsed-text($filePath,'UTF-8')"/>
<xsl:variable name="header">
<xsl:analyze-string select="$text" regex="(..*)">
<xsl:matching-substring>
<xsl:if test="position()=1">
<xsl:value-of select="replace(regex-group(1),'\t','|')"/>
</xsl:if>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:variable name="headerTokens" select="tokenize($header,'\|')"/>
<xsl:variable name="recordBody">
<xsl:analyze-string select="$text" regex="(..*)">
<xsl:matching-substring>
<xsl:if test="not(position()=1)">

<!-- Begin creating the records.
Assign column headers to field elements as @name attributes. -->
<xsl:analyze-string select="." regex="([^\t][^\t]*)\t?|\t">
<xsl:matching-substring>
<xsl:variable name="pos" select="position()"/>
<xsl:variable name="headerToken" select="$headerTokens[$pos]"/>
<xsl:if test="regex-group(1)[position() = 1]">
<field name="{$headerToken}">
<xsl:value-of select="regex-group(1)"/>
</field>
</xsl:if>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:if>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>

<!-- Split into record chunks. -->
<xsl:variable name="recompile">
<xsl:for-each select="$recordBody/field[@name='leader'][.!='']">
<xsl:variable name="ID" select="."/>
<record>
<xsl:sequence select="."/>
<xsl:for-each select="following-sibling::field[. != $ID]">
<xsl:if
test="preceding-sibling::field[@name='leader'][5][. != ''] = $ID
and not(self::field[@name='leader'])">
<xsl:sequence select="."/>
</xsl:if>
</xsl:for-each>
</record>
</xsl:for-each>
</xsl:variable>

<!-- Rebuild MARC record. -->
<xsl:for-each select="$recompile/record">
<marc:record>
<marc:leader>
<xsl:value-of select="child::node()[@name='leader']"/>
</marc:leader>
<xsl:if test="child::node()/@name='001'">
<marc:controlfield tag="001">
<xsl:value-of select="child::node()[@name='001']"/>
</marc:controlfield>
</xsl:if>
<xsl:if test="child::node()/@name='005'">
<marc:controlfield tag="005">
<xsl:value-of select="child::node()[@name='005']"/>
</marc:controlfield>
</xsl:if>
<xsl:if test="child::node()/@name='007'">
<marc:controlfield tag="007">
<xsl:value-of select="child::node()[@name='007']"/>
</marc:controlfield>
</xsl:if>
<xsl:if test="child::node()/@name='008'">
<marc:controlfield tag="008">
<xsl:value-of select="child::node()[@name='008']"/>
</marc:controlfield>
</xsl:if>

<xsl:for-each-group
select="child::node()[number(substring(@name,1, 3)) &gt;= 020]"
group-adjacent="substring(@name, 1, 3)">
<xsl:sort select="current-grouping-key()"/>

<xsl:choose>
<xsl:when test="starts-with(current-grouping-key(),'6')">
<xsl:for-each select="current-group()">
<marc:datafield tag="{current-grouping-key()}"
ind1="
{if (substring(@name,4,1) = '_')
then ' '
else substring(@name, 4, 1)}"
ind2="{
if (substring(@name, 5, 1) = '_')
then ' '
else substring(@name, 5, 1)}">
<marc:subfield code="{substring(@name, 7, 1)}">
<xsl:value-of select="."/>
</marc:subfield>
</marc:datafield>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<marc:datafield tag="{current-grouping-key()}"
ind1="{if (substring(@name,4,1) = '_')
then ' '
else substring(@name, 4, 1)}"
ind2="{
if (substring(@name, 5, 1) = '_')
then ' '
else substring(@name, 5, 1)}">
<xsl:for-each select="current-group()">
<marc:subfield code="{substring(@name, 7, 1)}">
<xsl:value-of select="."/>
</marc:subfield>
</xsl:for-each>
</marc:datafield>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</marc:record>
</xsl:for-each>
</marc:collection>
</xsl:template>
</xsl:stylesheet>


This gives me basically what I need, but it is not flexible, and it would be challenging to account for all the possible variations in the source data using this approach.


Instead, I would like to modify the xsl:analyze-string regex in order to process this more complex tab-delimited structure up front. Basically, every time there is a value for "leader," there should be a new record. The individual values that appear on subsequent lines should be parsed as separate XML elements, like so:



<?xml version="1.0" encoding="UTF-8" ?>
<marc:collection xmlns:marc="http://ift.tt/1sMy6Fc"
xmlns:xsi="http://ift.tt/ra1lAU"
xsi:schemaLocation="http://ift.tt/1sMy6Fc http://ift.tt/1wDzy1b">
<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://ift.tt/1tRbuCv;
<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://ift.tt/1tRbuSP;
</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://ift.tt/1tRbsdK;
<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://ift.tt/1ywm5YL;
</marc:datafield>
</marc:record>
</marc:collection>

No comments:

Post a Comment