reshape xml using xslt-3, splitting a record to many records if it has a certain element - xslt-3.0

given the following xml file, i need to select only those records that have a
<marc:datafield tag="911", and out of each 911, extract only the elements that have code h, or j: <marc:subfield code="h"> or <marc:subfield code="j">. Both can have text values, ie digits and text. Then the selected records and elements should be altered, so we keep the 001 value as RECNO, and we add a unique incremental value of RECORD ID, starting with 1. If h or j is not present, the record does not have the corresponding element. Name_1 is the new element name for , and Name_2 is the new element name for`
<?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:controlfield tag="001">7</marc:controlfield>
</marc:datafield>
<marc:datafield tag="911" ind1=" " ind2=" ">
<marc:subfield code="o">KEN</marc:subfield>
<marc:subfield code="b">MAIN</marc:subfield>
<marc:subfield code="e">20171027</marc:subfield>
<marc:subfield code="n">V.6</marc:subfield>
<marc:subfield code="d">001000000918</marc:subfield>
<marc:subfield code="a">001000000918</marc:subfield>
<marc:subfield code="h">v.1</marc:subfield>
<marc:subfield code="j">1686</marc:subfield>
</marc:datafield>
<marc:datafield tag="911" ind1=" " ind2=" ">
<marc:subfield code="o">KEN</marc:subfield>
<marc:subfield code="b">MAIN</marc:subfield>
<marc:subfield code="e">20171027</marc:subfield>
<marc:subfield code="n">V.6</marc:subfield>
<marc:subfield code="d">001000000921</marc:subfield>
<marc:subfield code="a">001000000921</marc:subfield>
<marc:subfield code="h">v.2</marc:subfield>
<marc:subfield code="j">1687</marc:subfield>
</marc:datafield>
<marc:datafield tag="911" ind1=" " ind2=" ">
<marc:subfield code="o">KEN</marc:subfield>
<marc:subfield code="b">MAIN</marc:subfield>
<marc:subfield code="e">20171027</marc:subfield>
<marc:subfield code="n">V.6</marc:subfield>
<marc:subfield code="d">001000000920</marc:subfield>
<marc:subfield code="a">001000000920</marc:subfield>
<marc:subfield code="h">v.2</marc:subfield>
<marc:subfield code="j">1687</marc:subfield>
</marc:datafield>
<marc:datafield tag="911" ind1=" " ind2=" ">
<marc:subfield code="o">KEN</marc:subfield>
<marc:subfield code="b">MAIN</marc:subfield>
<marc:subfield code="e">20171027</marc:subfield>
<marc:subfield code="n">V.6</marc:subfield>
<marc:subfield code="d">001000000919</marc:subfield>
<marc:subfield code="a">001000000919</marc:subfield>
<marc:subfield code="h">v.1</marc:subfield>
<marc:subfield code="j">1686</marc:subfield>
</marc:datafield>
</marc:record>
<marc:record>
<marc:controlfield tag="001">12481</marc:controlfield>
<marc:datafield tag="911" ind1=" " ind2=" ">
<marc:subfield code="o">KEN</marc:subfield>
<marc:subfield code="b">MAIN</marc:subfield>
<marc:subfield code="e">20160324</marc:subfield>
<marc:subfield code="n">II.5</marc:subfield>
<marc:subfield code="d">061000019180</marc:subfield>
<marc:subfield code="a">061000019180</marc:subfield>
<marc:subfield code="h">v.5</marc:subfield>
</marc:datafield>
<marc:datafield tag="911" ind1=" " ind2=" ">
<marc:subfield code="o">KEN</marc:subfield>
<marc:subfield code="b">MAIN</marc:subfield>
<marc:subfield code="e">20160324</marc:subfield>
<marc:subfield code="n">II.5</marc:subfield>
<marc:subfield code="d">061000019181</marc:subfield>
<marc:subfield code="a">061000019181</marc:subfield>
<marc:subfield code="h">v.4</marc:subfield>
</marc:datafield>
</marc:record>
<marc:record>
<marc:controlfield tag="001">1</marc:controlfield>
</marc:record>
</marc:collection>
expected output:
<?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">
<RECORD ID="1">
<RECNO>7</RECNO>
<NAME_1>v.1</NAME_1>
<NAME_2>1686</NAME_2>
</RECORD>
<RECORD ID="2">
<RECNO>7</RECNO>
<NAME_1>v.2</NAME_1>
<NAME_2>1687</NAME_2>
</RECORD>
<RECORD ID="3">
<RECNO>7</RECNO>
<NAME_1>v.2</NAME_1>
<NAME_2>1687</NAME_2>
</RECORD>
<RECORD ID="4">
<RECNO>7</RECNO>
<NAME_1>v.4</NAME_1>
<NAME_2>16887</NAME_2>
</RECORD>
<RECORD ID="5">
<RECNO>12481</RECNO>
<NAME_1>v.5</NAME_1>
</RECORD>
<RECORD ID="6">
<RECNO>12481</RECNO>
<NAME_1>v.4</NAME_1>
</RECORD>
</marc:collection>
How the above result could be achieved, using xslt-3? (Saxon 9.8 HE)

I don't see any real splitting, you seem to simply want to map the record/datafield[subfield/#code = ('h', 'j')] elements to RECORD elements and then the subfield elements to the NAME_1/NAME_2 elements:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="http://www.loc.gov/MARC21/slim"
expand-text="yes"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output indent="yes"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="record/datafield[subfield/#code = ('h', 'j')]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="datafield">
<RECORD ID="{position()}">
<RECNO>{ancestor::record/controlfield}</RECNO>
<xsl:apply-templates select="subfield[#code = ('h', 'j')]"/>
</RECORD>
</xsl:template>
<xsl:template match="subfield[#code = 'h']">
<NAME_1>{.}</NAME_1>
</xsl:template>
<xsl:template match="subfield[#code = 'j']">
<NAME_2>{.}</NAME_2>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bdxtrh

Related

extract and print values only when they exist using xslt

The source xml is:
<?xml version="1.0" encoding="UTF-8" ?>
<mr:collection
xmlns:mr="http://www.lc.gov/mr2/slim"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.lc.gov/mr2/slim http://www.lc.gov/standards/mrxml/schema/mr21slim.xsd">
<mr:rc>
<mr:ctr tg="1000">311</mr:ctr>
<mr:dtf tg="12000" i1="1" i2=" ">
<mr:sbf cd="d">John Diter</mr:sbf>
</mr:dtf>
</mr:rc>
</mr:collection>
the xsl that i use:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:d="http://www.example.com"
xpath-default-namespace="http://www.lc.gov/mr2/slim"
xmlns:mr="http://www.lc.gov/mr2/slim"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="collection">
<O-PM>
<ListRcs>
<xsl:apply-templates/>
</ListRcs>
</O-PM>
</xsl:template>
<xsl:template match="rc">
<rc>
<xsl:apply-templates/>
</rc>
</xsl:template>
<xsl:template match="mr:ctr[#tg = 1000]">
<xsl:variable name="dz" as="xs:string">{tg ='1000'}!</xsl:variable><d:z xml:lang="en">{normalize-space($dz)}</d:z>
</xsl:template>
<xsl:template match="mr:dtf[#tg = 12000]">
<xsl:variable name="ds" as="xs:string">{sbf[#cd = 'a']}!</xsl:variable><d:s xml:lang="en">{normalize-space($ds)}</d:s>
<xsl:variable name="dp" as="xs:string">{sbf[#cd = 'c']}!</xsl:variable><d:p>{normalize-space($dp)}</d:p>
<xsl:variable name="dc" as="xs:string">{sbf[#cd = 'd']}!</xsl:variable><d:c>{normalize-space($dc)}</d:c>
</xsl:template>
</xsl:stylesheet>
Current output:
<?xml version="1.0" encoding="UTF-8"?>
<O-PM>
<ListRcs>
<rc>
<d:z xmlns:d="http://www.example.com">false!</d:z>
<d:s xmlns:d="http://www.example.com" xml:lang="en">!</d:s>
<d:p xmlns:d="http://www.example.com">!</d:p>
<d:c xmlns:d="http://www.example.com">John Diter!</d:c>
</rc>
</ListRcs>
</O-PM>
Desired output:
<?xml version="1.0" encoding="UTF-8"?>
<O-PM>
<ListRcs>
<rc>
<d:z xmlns:d="http://www.example.com">311</d:z>
<d:c xmlns:d="http://www.example.com">John Diter!</d:c>
</rc>
</ListRcs>
</O-PM>
For starters i need to get as output the value of tg = 1000 and not false,
and secondly, how can one print only the values that exist?
In the above example only the value that matches the criterion cd = d is TRUE.
https://xsltfiddle.liberty-development.net/asoTKA/2
It seems, this time you can just match on the elements that interest you:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:d="http://www.example.com"
xpath-default-namespace="http://www.lc.gov/mr2/slim"
xmlns:mr="http://www.lc.gov/mr2/slim"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:mode on-no-match="shallow-skip"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="collection">
<O-PM>
<ListRcs>
<xsl:apply-templates/>
</ListRcs>
</O-PM>
</xsl:template>
<xsl:template match="rc">
<rc>
<xsl:apply-templates/>
</rc>
</xsl:template>
<xsl:template match="mr:ctr[#tg = 1000]">
<d:z xml:lang="en">{.}</d:z>
</xsl:template>
<xsl:template match="dtf[#tg = 12000]/sbf[#cd = 'd']">
<d:c>{.}!</d:c>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/asoTKA/3
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:d="http://www.example.com"
xpath-default-namespace="http://www.lc.gov/mr2/slim"
xmlns:mr="http://www.lc.gov/mr2/slim"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="collection">
<O-PM>
<ListRcs>
<xsl:apply-templates/>
</ListRcs>
</O-PM>
</xsl:template>
<xsl:template match="rc">
<rc>
<xsl:apply-templates/>
</rc>
</xsl:template>
<xsl:template match="mr:ctr[#tg = 1000]">
<xsl:variable name="dz" as="xs:string"><xsl:value-of select="."/></xsl:variable>
<d:z xml:lang="en"><xsl:value-of select="normalize-space($dz)"/></d:z>
</xsl:template>
<xsl:template match="mr:dtf[#tg = 12000]">
<xsl:variable name="dc" as="xs:string">
<xsl:if test="mr:sbf[#cd = 'd']"><xsl:value-of select="normalize-space(.)"/> </xsl:if>
</xsl:variable>
<d:c xml:lang="en"><xsl:value-of select="normalize-space($dc)"/></d:c>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/asoTKA/5

i need to remove prefixes using xslt-3and exclude-result-prefixes

input xml
<?xml version="1.0" encoding="UTF-8" ?>
<mr:collection
xmlns:mr="http://www.lc.gov/mr2/slim"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.lc.gov/mr2/slim http://www.lc.gov/standards/mrxml/schema/mr21slim.xsd">
<mr:rc>
<mr:dtf tg="2000" i1="1" i2=" ">
<mr:sbf cd="a">Christoph Kolumbus</mr:sbf>
<mr:sbf cd="d">John Diter</mr:sbf>
<mr:sbf cd="b">Julie Nat</mr:sbf>
<mr:sbf cd="f">Darius Milhaud</mr:sbf>
<mr:sbf cd="g">Erich kleiber</mr:sbf>
<mr:sbf cd="g">Franz Ludwig Horth</mr:sbf>
</mr:dtf>
<mr:dtf tg="3000" i1="1" i2=" ">
<mr:sbf cd="a">Christoph Kolumbus</mr:sbf>
<mr:sbf cd="d">Serg</mr:sbf>
<mr:sbf cd="b">Mak</mr:sbf>
<mr:sbf cd="f">DarMil</mr:sbf>
<mr:sbf cd="g">Erikl</mr:sbf>
<mr:sbf cd="g">LudHorth</mr:sbf>
</mr:dtf>
</mr:rc>
<mr:rc>
<mr:dtf tg="2000" i1="1" i2="0">
<mr:sbf cd="a">Chris Prante</mr:sbf>
<mr:sbf cd="e">"Chris Dietz"</mr:sbf>
</mr:dtf>
</mr:rc>
</mr:collection>
with the following xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="http://www.lc.gov/mr2/slim"
xmlns:e="https://example.com"
xmlns:dc="https://examples.com"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="collection">
<O-PM>
<ListRcs>
<xsl:apply-templates/>
</ListRcs>
</O-PM>
</xsl:template>
<xsl:template match="rc">
<e:rc>
<xsl:apply-templates/>
</e:rc>
</xsl:template>
<xsl:template match="dtf[#tg = 2000]">
<mdtd>
<rc>
<dc:title xml:lang="el">{sbf[#cd = 'a']} {sbf[#cd = 'b']}{sbf[#cd = 'e']!(':', .)} {sbf[#cd = 'f']!('/', .)}{(sbf[#cd = 'g'] => string-join(' ; '))!('', .)}</dc:title>
</rc>
</mdtd>
</xsl:template>
<xsl:template match="dtf[#tg != 2000]"/>
</xsl:stylesheet>
we get
<?xml version="1.0" encoding="UTF-8"?>
<O-PM>
<ListRcs>
<e:rc xmlns:e="https://example.com">
<mdtd>
<rc>
<dc:title xmlns:dc="https://examples.com" xml:lang="el">Christoph Kolumbus Julie Nat / Darius Milhaud Erich kleiber ; Franz Ludwig Horth</dc:title>
</rc>
</mdtd>
</e:rc>
<e:rc xmlns:e="https://example.com">
<mdtd>
<rc>
<dc:title xmlns:dc="https://examples.com" xml:lang="el">Chris Prante : "Chris Dietz" </dc:title>
</rc>
</mdtd>
</e:rc>
</ListRcs>
</O-PM>
how can one get <e:rc> instead of <e:rc xmlns:e="https://example.com">?
exclude-result-prefixes should work for literal result elements we create in our XSLT code... I need to declare namespaces and i am not getting the output i want.
ie <e:rc> instead of <e:rc xmlns:e="https://example.com">
and <dc:title xml:lang="el"> instead of <dc:title xmlns:dc="https://examples.com" xml:lang="el">
If you use
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="http://www.lc.gov/mr2/slim"
xmlns:e="https://example.com"
xmlns:dc="https://examples.com"
exclude-result-prefixes="xs"
expand-text="yes"
version="3.0">
i.e. don't exclude the prefixes e and dc you declared for serialization you will get a root element declaring them e.g.
<O-PM xmlns:dc="https://examples.com" xmlns:e="https://example.com">
and content like
<e:rc>
<mdtd>
<rc>
<dc:title xml:lang="el">Christoph Kolumbus Julie Nat / Darius Milhaud Erich kleiber ; Franz Ludwig Horth</dc:title>
</rc>
</mdtd>
</e:rc>

Key Match using accumulators in XSLT3

I am looking to use accumulators in my xslt3 below and process only unmatched keys and ignore others.
I want loop through each All_Time_Offs/Time_Off and if Time_Off_Key is present in the Payroll_Input/Input_Key -> Then do not process. Else process the record
<?xml version="1.0" encoding="UTF-8"?>
<Aggregated_TimeOffs>
<All_Payroll_Inputs>
<Payroll_Input>
<RefID>PAYROLL_INPUT-6-122898</RefID>
<Emp_ID>101058</Emp_ID>
<Earning>101D</Earning>
<Start_Date/>
<Adjustment>0</Adjustment>
<Hours>4</Hours>
<Input_Key>PAYROLL_INPUT-6-122898101D4</Input_Key>
</Payroll_Input>
<Payroll_Input>
<RefID>PAYROLL_INPUT-6-122898</RefID>
<Emp_ID>101058</Emp_ID>
<Earning>101D</Earning>
<Start_Date/>
<Adjustment>0</Adjustment>
<Hours>4</Hours>
<Input_Key>PAYROLL_INPUT-6-122898101D9</Input_Key>
</Payroll_Input>
</All_Payroll_Inputs>
<All_Time_Offs>
<Time_Off>
<RefID/>
<Emp_ID>29519</Emp_ID>
<Earning/>
<Date>2020-09-10</Date>
<Adjustment/>
<Hours>7</Hours>
<Cost_Center/>
<Week>Week_2</Week>
<Time_Off_Key>PAYROLL_INPUT-6-122898101D4</Time_Off_Key>
</Time_Off>
<Time_Off>
<RefID/>
<Emp_ID>68413</Emp_ID>
<Earning/>
<Date>2020-09-09</Date>
<Adjustment/>
<Hours>8</Hours>
<Cost_Center/>
<Week>Week_2</Week>
<Time_Off_Key>INT024_PAYROLL_INPUT_2020-09-098</Time_Off_Key>
</Time_Off>
</All_Time_Offs>
</Aggregated_TimeOffs>
Below is the XSLT I am trying, but not working.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:err="http://www.w3.org/2005/xqt-errors"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ext="urn:SomeExternalSource"
xmlns:xtt="urn:com.workday/xtt"
xmlns:wd="urn:com.workday/bsvc"
xmlns:this="urn:com.workday/this"
exclude-result-prefixes="xs ext map wd xtt this"
version="3.0">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"></xsl:output>
<xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="Time_Off_Key Payroll_Input_lookup emp.id"/>
<xsl:accumulator name="Payroll_Input_lookup" as="xs:string" initial-value="''" streamable="yes">
<xsl:accumulator-rule match="Input_Key/text()" select="."/>
</xsl:accumulator>
<xsl:accumulator name="Time_Off_Key" as="map(xs:string,xs:string)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="Time_Off_Key/text()" select="map:put($value, string(.), accumulator-before('Payroll_Input_lookup'))"/>
</xsl:accumulator>
<xsl:accumulator name="emp.id" streamable="yes" as="xs:string" initial-value="''">
<xsl:accumulator-rule match="Emp_ID/text()" select="."/>
</xsl:accumulator>
<xsl:template match="Aggregated_TimeOffs">
<xsl:for-each select="All_Time_Offs/Time_Off/copy-of()">
<xsl:variable name="input_exists">
<xsl:value-of select="accumulator-before('Time_Off_Key')(normalize-space(Time_Off_Key))"/>
</xsl:variable>
<xsl:if test="string-length($input_exists) < 0">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
If you only want to compare the single values Time_Off_Key occured during "earlier" parsing of the Payroll_Input/Input_Key values then a single, xs:string*, i.e. string sequence based accumulator should suffice:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="Payroll_Input_Input_Keys"/>
<xsl:accumulator name="Payroll_Input_Input_Keys" as="xs:string*" initial-value="()" streamable="yes">
<xsl:accumulator-rule match="Payroll_Input/Input_Key/text()" select="., $value"/>
</xsl:accumulator>
<xsl:template match="Time_Off[copy-of()[not(Time_Off_Key = accumulator-before('Payroll_Input_Input_Keys'))]]">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
It might not be streamable, however, as the use of copy-of() in the predicate of the pattern breaks the motion-less requirement of streamable patterns: so we need to write
<xsl:template match="Time_Off">
<xsl:copy-of select="copy-of()[not(Time_Off_Key = accumulator-before('Payroll_Input_Input_Keys'))]"/>
</xsl:template>
instead to make that code work with streaming.

Quetion about the xsl:template returning a xsl:map

I know that "Cannot add a map to an XML tree", especially take the xsl:map as result document.
But if I assign a xsl:template to a variable, it can take a xsl:map as result of the xsl:template. For example:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="xs"
version="3.0">
<xsl:template match="/">
<xsl:variable name="m1" as="map(*)">
<xsl:call-template name="tmap1"/>
</xsl:variable>
<xsl:variable name="m2" as="map(*)">
<xsl:call-template name="tmap2"/>
</xsl:variable>
</xsl:template>
<xsl:template name="tmap1">
<xsl:sequence select="map{1:'abc', 2:'ccd'}"/>
</xsl:template>
<xsl:template name="tmap2" as="map(*)">
<xsl:map>
<xsl:map-entry key="'Mo'" select="'Monday'"/>
<xsl:map-entry key="'Tu'" select="'Tuesday'"/>
<xsl:map-entry key="'We'" select="'Wednesday'"/>
<xsl:map-entry key="'Th'" select="'Thursday'"/>
<xsl:map-entry key="'Fr'" select="'Friday'"/>
<xsl:map-entry key="'Sa'" select="'Saturday'"/>
<xsl:map-entry key="'Su'" select="'Sunday'"/>
<xsl:map-entry key="'z2'" select="'day'"/>
</xsl:map>
</xsl:template>
These will run well. But I has an error with my work code, and I make a minimum but complete demo to reproduce it.
the code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:rel="http://schemas.openxmlformats.org/package/2006/relationships"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:map = "http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="xs w rel r map v"
version="3.0">
<xsl:template match="/">
<xsl:apply-templates select="w:document/w:body/w:p"/>
</xsl:template>
<xsl:template match="w:p">
<xsl:element name="p">
<xsl:variable name="p_css" as="map(*)*">
<xsl:if test="w:pPr">
<xsl:variable name="t">
<xsl:apply-templates select="w:pPr" mode="style_item"/>
</xsl:variable>
<xsl:if test="not($t instance of map(*))">
<xsl:message>error</xsl:message>
</xsl:if>
</xsl:if>
</xsl:variable>
<xsl:if test="not(empty($p_css))">
<xsl:where-populated>
<xsl:attribute name="style" select="$p_css?2"/>
</xsl:where-populated>
</xsl:if>
<xsl:apply-templates select="w:t"/>
</xsl:element>
</xsl:template>
<xsl:template match="w:pPr" mode="style_item" as="map(*)*">
<xsl:sequence select="map{1:'hi', 2:'hello', 3:'world'}"/>
</xsl:template>
</xsl:stylesheet>
the source document:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" mc:Ignorable="w14 wp14">
<w:body>
<w:p w:rsidR="00F708CA" w:rsidRDefault="00F708CA" w:rsidP="006E4E72">
<w:pPr>
<w:outlineLvl w:val="0"/>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>你好,大家好。</w:t>
</w:r>
<w:bookmarkStart w:id="0" w:name="_GoBack"/>
<w:bookmarkEnd w:id="0"/>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="Normal"/>
<w:rPr>
<w:b/>
<w:b/>
<w:bCs/>
<w:color w:val="C9211E"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:b/>
<w:bCs/>
<w:color w:val="C9211E"/>
</w:rPr>
<w:t xml:space="preserve">12345good day. </w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="Normal"/>
<w:rPr>
<w:i/>
<w:i/>
<w:iCs/>
<w:color w:val="77BC65"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
<w:highlight w:val="yellow"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:i/>
<w:iCs/>
<w:color w:val="77BC65"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
<w:highlight w:val="yellow"/>
</w:rPr>
<w:t>64789hello world</w:t>
</w:r>
</w:p>
<w:sectPr>
<w:type w:val="nextPage"/>
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:left="1134" w:right="1134" w:header="0" w:top="1134" w:footer="0"
w:bottom="1134" w:gutter="0"/>
<w:pgNumType w:fmt="decimal"/>
<w:formProt w:val="false"/>
<w:textDirection w:val="lrTb"/>
</w:sectPr>
</w:body>
</w:document>
The error displays "Cannot add a map to an XML tree" when runtime,
my xslt proccessor is saxon-he 9.8.0.12
If you want a result (principal or secondary) to show/serialize a map then use the output method adaptive (or json if the map represents JSON) for that result:
<xsl:output method="adaptive" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="m1" as="map(*)">
<xsl:call-template name="tmap1"/>
</xsl:variable>
<xsl:variable name="m2" as="map(*)">
<xsl:call-template name="tmap2"/>
</xsl:variable>
<xsl:sequence select="$m1, $m2"/>
</xsl:template>
https://xsltfiddle.liberty-development.net/gWEamLu
<xsl:output method="json" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="m1" as="map(*)">
<xsl:call-template name="tmap1"/>
</xsl:variable>
<xsl:variable name="m2" as="map(*)">
<xsl:call-template name="tmap2"/>
</xsl:variable>
<xsl:sequence select="[$m1, $m2]"/>
</xsl:template>
https://xsltfiddle.liberty-development.net/gWEamLu/1
My problem is that the '<xsl:variable name="t">' variable don't have a explicit type declaration.

how to concatenate string with two different delimiters with xsl

I have a XML
<main>
<DATA_RECORD>
<COMPONENT_SID>100</COMPONENT_SID>
<GROUP_ID>1</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>200</COMPONENT_SID>
<GROUP_ID>1</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>400</COMPONENT_SID>
<GROUP_ID>1</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>10</COMPONENT_SID>
<GROUP_ID>2</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>20</COMPONENT_SID>
<GROUP_ID>2</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>2</COMPONENT_SID>
<GROUP_ID>3</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>4</COMPONENT_SID>
<GROUP_ID>3</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>8</COMPONENT_SID>
<GROUP_ID>3</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>16</COMPONENT_SID>
<GROUP_ID>3</GROUP_ID>
</DATA_RECORD>
</main>
I would like to use xsl to parse into another XML. The output would be
<comp value="100,200,400|10,20|2,4,8,16"/>
where the component_sids belonged to different group_id are separated by "|" . The component_sids belonged to the same group id should be concatenated with ",". I used the following xsl
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:element name="comp">
<xsl:attribute name="value">
<xsl:call-template name="join">
<xsl:with-param name="list" select="//DATA_RECORD[GROUP_ID=1]/COMPONENT_SID" />
<xsl:with-param name="separator" select="','" />
</xsl:call-template>
</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template name="join">
<xsl:param name="list" />
<xsl:param name="separator"/>
<xsl:for-each select="$list">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="$separator" />
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The result is
<comp value="100,200,400"/>
But I could not figured out how to separate other groups of component_sid with "|". Can someone help me?
Thanks in advance
The XSLT 2.0 solution can be shortened further to:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vResult">
<xsl:for-each-group select="*/*" group-by="GROUP_ID">
<xsl:if test="not(position()=1)">|</xsl:if>
<xsl:value-of select="current-group()/COMPONENT_SID" separator=","/>
</xsl:for-each-group>
</xsl:variable>
<comp value="{$vResult}"/>
</xsl:template>
</xsl:stylesheet>
I. XSLT 1.0 solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kRecByGrId" match="DATA_RECORD"
use="GROUP_ID"/>
<xsl:variable name="vGrIds" select=
"/*/DATA_RECORD
[generate-id()
=
generate-id(key('kRecByGrId', GROUP_ID)[1])
]
/GROUP_ID
"/>
<xsl:template match="/">
<xsl:variable name="vResult">
<xsl:for-each select="$vGrIds">
<xsl:if test="not(position()=1)">|</xsl:if>
<xsl:apply-templates select=
"key('kRecByGrId', .)/COMPONENT_SID"/>
</xsl:for-each>
</xsl:variable>
<comp value="{$vResult}"/>
</xsl:template>
<xsl:template match="COMPONENT_SID">
<xsl:if test="not(position()=1)">,</xsl:if>
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<main>
<DATA_RECORD>
<COMPONENT_SID>100</COMPONENT_SID>
<GROUP_ID>1</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>200</COMPONENT_SID>
<GROUP_ID>1</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>400</COMPONENT_SID>
<GROUP_ID>1</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>10</COMPONENT_SID>
<GROUP_ID>2</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>20</COMPONENT_SID>
<GROUP_ID>2</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>2</COMPONENT_SID>
<GROUP_ID>3</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>4</COMPONENT_SID>
<GROUP_ID>3</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>8</COMPONENT_SID>
<GROUP_ID>3</GROUP_ID>
</DATA_RECORD>
<DATA_RECORD>
<COMPONENT_SID>16</COMPONENT_SID>
<GROUP_ID>3</GROUP_ID>
</DATA_RECORD>
</main>
produces the wanted, correct result:
<comp value="100,200,400|10,20|2,4,8,16"/>
Explanation:
Muenchian method for grouping -- to find all distinct values of GROUP_ID .
Simple logic to precede an item (or group) with a delimiter, whenever this item (or group) isn't the first.
II. XSLT 2.0 Solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vResult">
<xsl:for-each-group select="*/*" group-by="GROUP_ID">
<xsl:if test="not(position()=1)">|</xsl:if>
<xsl:for-each select="current-group()/COMPONENT_SID">
<xsl:if test="not(position()=1)">,</xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:for-each-group>
</xsl:variable>
<comp value="{$vResult}"/>
</xsl:template>
</xsl:stylesheet>
when applied to the same XML document (above), again the wanted, correct result is produced:
<comp value="100,200,400|10,20|2,4,8,16"/>
Explanation:
Use of <xsl:for-each-group>
Use of current-group()
The same simple logic for preceding each item (or group), which isn't the first, with the respective delimiter.

Resources