need to merge data from two xml files based on record id - xslt-3.0

i have the following two xml files.
File A:
<breakfast_menu>
<food>
<id>1</id>
<name>Belgian Waffles</name>
<price>$5.95</price>
<calories>650</calories>
</food>
<food>
<id>2</id>
<name>Strawberry Belgian Waffles</name>
<price>$7.95</price>
<calories>900</calories>
</food>
</breakfast_menu>
and file B:
<breakfast_menu>
<food>
<id>1</id>
<description>here we have already a description element</description>
<otherData>s3650</otherData>
</food>
<food>
<id>2</id>
<otherData>s3250</otherData>
<otherData2>g508</otherData2>
<otherData3>sidi6098</otherData3>
</food>
</breakfast_menu>
what i cannot achieve, is for each record that matches based on the record id, take all info from the xml file named A, and put all this info in the description element of file B, but also keep the element it came from. If the matching record does not have a description element, we add one. If it already has one, then we must be aware of where the info taken from file A starts, with a textual separator, ie "-followingInfoTakenFromFileA-". So with the above in mind, the desired output should be like:
<breakfast_menu>
<food>
<id>1</id>
<description>here we have already a description element-followingInfoTakenFromFileA-name:Belgian Waffles-price:$5.95-calories:650</description>
<otherData>s3650</otherData>
</food>
<food>
<id>2</id>
<otherData>s3250</otherData>
<otherData2>g508</otherData2>
<otherData3>sidi6098</otherData3>
<description>name:Strawberry Belgian Waffles-price:$7.95-calories:900</description>
</food>
</breakfast_menu>

It seems, given the sorted id merge keys, a simple task for xsl:merge:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:param name="doc-A">
<breakfast_menu>
<food>
<id>1</id>
<name>Belgian Waffles</name>
<price>$5.95</price>
<calories>650</calories>
</food>
<food>
<id>2</id>
<name>Strawberry Belgian Waffles</name>
<price>$7.95</price>
<calories>900</calories>
</food>
</breakfast_menu>
</xsl:param>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:merge>
<xsl:merge-source for-each-item="." select="food">
<xsl:merge-key select="id"/>
</xsl:merge-source>
<xsl:merge-source for-each-item="$doc-A" select="//food">
<xsl:merge-key select="id"/>
</xsl:merge-source>
<xsl:merge-action>
<xsl:copy>
<xsl:apply-templates select="*">
<xsl:with-param name="merge-data"
select="let $data := string-join(current-merge-group()[2]/(* except id)!(name() || ':' || .), '-')
return if (description)
then '-followingInfoTakenFrom' || document-uri(root(current-merge-group()[2])) || $data
else $data"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:merge-action>
</xsl:merge>
</xsl:copy>
</xsl:template>
<xsl:template match="food/description">
<xsl:param name="merge-data"/>
<xsl:copy>{.}{$merge-data}</xsl:copy>
</xsl:template>
<xsl:template match="food[not(description)]/*[last()]">
<xsl:param name="merge-data"/>
<xsl:next-match/>
<description>{$merge-data}</description>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pPzifqo
That examples inlines the first document as a parameter for self-containedness of the sample but in the real world you could load from a URL with e.g. <xsl:param name="doc-A" select="doc('fileA.xml')"/>.

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.

XSLT 3.0 incremental merge

I have two XSLT workflows: Full (XSLT2) and Incremental (XSLT3).
Below full3.xml is the merge & split result of Full workflow:
<Account>
<metadata>
<_uri>full3.xml</_uri>
<created>2020-11-26T23:16:08.076-07:00</created>
<lastModified>2020-11-26T23:16:08.076-07:00</lastModified>
<merge-lineage>
<merged-uri>lot960151-3.xml</merged-uri>
<merged-uri>lot860150-3.xml</merged-uri>
</merge-lineage>
</metadata>
<accountPersistentID>51b10faa</accountPersistentID>
<accountID>ACC300</accountID>
<accountName>bonafide-3</accountName>
<Item>
<contract>
<amount>
<currency>USD</currency>
<amount>5000000.00</amount>
</amount>
</contract>
<contract>
<amount>
<currency>USD</currency>
<amount>4000000.00</amount>
</amount>
</contract>
</Item></Account>
I have subsequent incremental input XMLs to be matched & (if matched) merged; if no match, then transform it as above similar structure.
lot660152-3.xml is the raw document structure. The match & merge criteria are the accountId and accountName
<ContractServicing>
<account id="ACC3">
<accountId>ACC300</accountId>
<accountName>bonafide-3</accountName>
<accountBeneficiary href="party5"/>
<servicingParty href="party6"/>
</account>
<contract>
<amount>
<currency>USD</currency>
<amount>5700000.00</amount>
</amount>
</contract>
<contract>
<amount>
<currency>USD</currency>
<amount>4000000.00</amount>
</amount>
</contract></ContractServicing>
The desired results of the Incremental XSLT workflow should be:
lot660152-3.xml is matched and its each contract shall be merged into Item element in full3.xml
(NOTE: _uri is generated during the Full workflow and can be changed during the incremental workflow. But the accountPersistentID generated during the Full workflow shall be untouched. Also lastModified and merged-uri shall be updated if any merge event.
<Account>
<metadata>
<_uri>full3.xml</_uri>
<created>2020-11-26T23:16:08.076-07:00</created>
<lastModified>2020-11-29T00:00:00.000-00:00</lastModified>
<merge-lineage>
<merged-uri>lot960151-3.xml</merged-uri>
<merged-uri>lot860150-3.xml</merged-uri>
<merged-uri>lot660152-3.xml</merged-uri>
</merge-lineage>
</metadata>
<accountPersistentID>51b10faa</accountPersistentID>
<accountID>ACC300</accountID>
<accountName>bonafide-3</accountName>
<Item>
<contract>
<amount>
<currency>USD</currency>
<amount>5000000.00</amount>
</amount>
</contract>
<contract>
<amount>
<currency>USD</currency>
<amount>4000000.00</amount>
</amount>
</contract>
<contract>
<amount>
<currency>USD</currency>
<amount>5700000.00</amount>
</amount>
</contract>
<contract>
<amount>
<currency>USD</currency>
<amount>4000000.00</amount>
</amount>
</contract>
</Item>
</Account>
Incremental workflow shall create a new document similar to full3.xml for no-matched raw document.
As it currently stands, I am pleased with Full workflow but I can’t seem to get any line on XSLT3 merge instruction during the Incremental workflow.
My XSLT Incremental workflow
<xsl:template match="/">
<xsl:merge>
<xsl:merge-source name="full" streamable="yes" for-each-source="$full-docs" select="Account">
<xsl:merge-key select="accountID"/>
</xsl:merge-source>
<xsl:merge-source name="incremental" for-each-source="$incre-docs" select="ContractServicing">
<xsl:merge-key select="account/accountId"/>
</xsl:merge-source>
<xsl:merge-action>
<xsl:choose>
<xsl:when test="current-merge-group('incremental')/account/accountId = current-merge-group('full')/accountID">
<xsl:apply-templates select="current-merge-group('full')"/>
<xsl:for-each select="current-merge-group('full')/Item">
<xsl:copy-of select="current-merge-group('incremental')/contract"/>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-merge-group('incremental')" />
</xsl:otherwise>
</xsl:choose>
</xsl:merge-action>
</xsl:merge>
</xsl:template>
The result is none of the matched incremental contract merged into Item and the no-match document has not been transformed. (This has been resolved by Michael Kay)
Null Pointer
I added some boilerplate:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
expand-text="yes"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:mode name="merge-contracts" on-no-match="shallow-copy"/>
<xsl:mode name="unmatched" on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
and then changed the xsl:merge-action to
<xsl:merge-action>
<xsl:choose>
<xsl:when test="current-merge-group('incremental')/account/accountId = current-merge-group('full')/accountID">
<xsl:apply-templates select="current-merge-group('full')" mode="merge-contracts">
<xsl:with-param name="extra" select="current-merge-group('incremental')"
tunnel="yes" as="element(ContractServicing)"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-merge-group('incremental')" mode="unmatched"/>
</xsl:otherwise>
</xsl:choose>
</xsl:merge-action>
so if there's a match, it's processing the main document in mode merge-contracts with the incremental document passed in a tunnel parameter; and if there's no match, it's processing the incremental document in mode "unmatched".
The merged-contracts mode has three template rules:
<xsl:template match="lastModified/text()" mode="merge-contracts">
<xsl:value-of select="current-dateTime()"/>
</xsl:template>
<xsl:template match="merge-lineage" mode="merge-contracts">
<xsl:param name="extra" tunnel="yes" as="element(ContractServicing)"/>
<xsl:copy>
<xsl:copy-of select="*"/>
<merged-uri>{
tokenize($extra/root()/document-uri(),'/')[last()]
}</merged-uri>
</xsl:copy>
</xsl:template>
<xsl:template match="Item" mode="merge-contracts">
<xsl:param name="extra" tunnel="yes" as="element(ContractServicing)"/>
<xsl:copy>
<xsl:copy-of select="*, $extra//contract"/>
</xsl:copy>
</xsl:template>
which might not do everything you want to do, but I think it captures the essence.
Where the incremental document isn't matched, it becomes a very routine transformation which I approximated with:
<xsl:template match="ContractServicing" mode="unmatched">
<xsl:result-document href="unmatched.xml">
<Account>
<metadata>...</metadata>
<accountPersistentID>...</accountPersistentID>
<xsl:copy-of select="//contract"/>
</Account>
</xsl:result-document>
</xsl:template>
I'm not really sure where your difficulties arose. Your code refers to an "item" element that doesn't exist; and you didn't show us any code for combining the merge-lineage element or for merging the contracts. I don't know if that's because you had no problems with this code, or because you didn't know how to go about writing it.

Replace new line char with <br /> XSL

I have a Sharepoint list which I want to convert to a JSON via an XSL dataview.
I have an XSL recursive replace function which I use to replace all special characters (escape backslash, double quotes to " etc) which gives me nice clean JSON which parses correctly in the users browser.
The final thing that I need to escape / replace is the new line char. The new line causes errors in parsing the JSON in some browsers.
Here is some xsl which tests if the contents of title has a new line char, if it does we output a paragraph:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="catalog/cd">
<xsl:if test='contains(title,"
")'>
<p>Found <xsl:value-of select="title" /></p>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Here is some sample xml:
<catalog>
<cd>
<title>Empire
Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
<cd>
<title>Greatest Hits</title>
<artist>Dolly Parton</artist>
<country>USA</country>
<company>RCA</company>
<price>9.90</price>
<year>1982</year>
</cd>
</catalog>
"Empire Burlesque" should be the only item to pass the test, but all three titles pass the if statement and are outputted.
EDIT
Modifying the solution below, I assume this should work if I wanted to do the search and replace on a individual node basis? I won't be able to test it in Sharepoint until tomorrow.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:for-each select="catalog/cd">
<xsl:variable name="title_clean">
<xsl:call-template name="repNL">
<xsl:with-param name="pText" select="title"/>
</xsl:call-template>
</xsl:variable>
<p><xsl:value-of select='$title_clean' /></p>
</xsl:for-each>
</xsl:template>
<xsl:template name="repNL">
<xsl:param name="pText" select="."/>
<xsl:copy-of select="substring-before(concat($pText,'
'),'
')"/>
<xsl:if test="contains($pText, '
')">
<br />
<xsl:call-template name="repNL">
<xsl:with-param name="pText" select=
"substring-after($pText, '
')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
"Empire Burlesque" should be the only
item to pass the test, but all three
titles pass the if statement and are
outputted.
Cannot reproduce the alleged problem -- tested with 9 different XSLT processors, including all from Microsoft.
Anyway:
This transformation replaces any NL character in a text node with a br element:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()" name="repNL">
<xsl:param name="pText" select="."/>
<xsl:copy-of select=
"substring-before(concat($pText,'
'),'
')"/>
<xsl:if test="contains($pText, '
')">
<br />
<xsl:call-template name="repNL">
<xsl:with-param name="pText" select=
"substring-after($pText, '
')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document (the provided one with an added cd to make it more interesting):
<catalog>
<cd>
<title>Line1
Line2
Line3
</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Empire
Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
<cd>
<title>Greatest Hits</title>
<artist>Dolly Parton</artist>
<country>USA</country>
<company>RCA</company>
<price>9.90</price>
<year>1982</year>
</cd>
</catalog>
the wanted, correct result is produced:
<catalog>
<cd>
<title>Line1<br/> Line2<br/> Line3<br/>
</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Empire<br/> Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
<cd>
<title>Greatest Hits</title>
<artist>Dolly Parton</artist>
<country>USA</country>
<company>RCA</company>
<price>9.90</price>
<year>1982</year>
</cd>
</catalog>
Explanation:
The identity rule/template copies every node "as-is".
The overriding template that matches any text node (also named as "repNL") prforms the following processing:
Using a sentinel (a NL character appended to the string), the substring before the first NL character (or the complete string, if th no NL character is contained) is copied to the output.
If a NL character is really contained, then a br element is generated and the template calls itself recursively for the remaining string after this NL character.
If you are using XslCompiledTransform(C#), I believe you will encounter the issue if you use below API to transform the XML:
XslCompiledTransform.Transform(XmlReader input, XmlWriter results)
However, below API works well:
XslCompiledTransform.Transform(string, string);
This is wired, but I don't figure out why...

Resources