I have the following Input, Transform and Output XML. The Output XML is not what I am expecting. I am attempting to use the mode attribute in my templates to separate my processing into two steps. Step1 filters the Input XML and Step2 processes specific nodes in what I am hoping is the filtered Input XML. My Step2 templates simply output what they receive so I am expecting to see the full description of the nodes selected for each template. The Output XML contains only the top-level node labels (Patient,Encounters,Diagnoses) and only the text of the node itself.
I have the following XSLT fiddle at My XSLT fiddle that shows my issue, I hope clearly.
Input XML
<?xml version="1.0" encoding="utf-8" ?>
<root>
<Container>
<Patient>
<BirthTime>2001-01-01T00:00:00Z</BirthTime>
</Patient>
<Encounters>
<Encounter>
<EncounterNumber>1</EncounterNumber>
<FromTime>2021-04-01T00:00:00Z</FromTime>
</Encounter>
<Encounter>
<EncounterNumber>2</EncounterNumber>
<FromTime>2021-03-01T00:00:00Z</FromTime>
</Encounter>
</Encounters>
<Diagnoses>
<Diagnosis>
<EncounterNumber>1</EncounterNumber>
</Diagnosis>
<Diagnosis>
<EncounterNumber>2</EncounterNumber>
</Diagnosis>
</Diagnoses>
</Container>
</root>
Transform XML
<?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:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="encounterNumbers">
<EncounterNumber>2</EncounterNumber>
</xsl:variable>
<!-- START PROCESSING -->
<xsl:template match="node()|#*">
<xsl:apply-templates select="." mode="step1"/>
</xsl:template>
<!-- STEP1 TEMPLATES -->
<xsl:template name="filterSDA" mode="step1" match="node()|#*">
<xsl:variable name="filteredSDA">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:variable>
<xsl:apply-templates select="$filteredSDA" mode="step2"/>
</xsl:template>
<xsl:template match="//Encounters/Encounter[not(EncounterNumber = $encounterNumbers/EncounterNumber)]" mode="step1"/>
<xsl:template match="//Diagnoses/Diagnosis[not(EncounterNumber = $encounterNumbers/EncounterNumber)]" mode="step1"/>
<!-- STEP2 TEMPLATES -->
<xsl:template name="demographics" mode="step2" match="Patient">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template name="clinical" mode="step2" match="Encounters|Diagnoses">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
Output XML
<?xml version="1.0" encoding="UTF-8"?>
<Patient>
2001-01-01T00:00:00Z
</Patient>
<Encounters>
2
2021-03-01T00:00:00Z
</Encounters>
<Diagnoses>
2
</Diagnoses>
I think you are overcomplicating things by mixing named templates (which you seem to call nowhere) with matching templates.
Furthermore, if you want a two phase processing with two different modes, I would do that at the top level, pushing the result of processing the whole input through the first mode into a variable and then applying the second mode on that variable:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="encounterNumbers">
<EncounterNumber>2</EncounterNumber>
<EncounterNumber>3</EncounterNumber>
<EncounterNumber>4</EncounterNumber>
</xsl:variable>
<xsl:mode name="step1" on-no-match="shallow-copy"/>
<xsl:mode name="step2" on-no-match="shallow-copy"/>
<!-- START PROCESSING -->
<xsl:template match="/">
<xsl:variable name="step-1-result">
<xsl:apply-templates mode="step1"/>
</xsl:variable>
<xsl:apply-templates select="$step-1-result/node()" mode="step2"/>
</xsl:template>
<!-- mode step1 templates -->
<xsl:template match="//Encounters/Encounter[not(EncounterNumber = $encounterNumbers/EncounterNumber)]" mode="step1"/>
<xsl:template match="//Diagnoses/Diagnosis[not(EncounterNumber = $encounterNumbers/EncounterNumber)]" mode="step1"/>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/asoTKZ/20
Related
I am trying to copy all child nodes to a specific node, except a few. Haven't been able to get this to work? Any pointers of what I am doing wrong?
Using this XML:
<ns0:Envelope xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/">
<ns0:Header>
<wsse:Sec xmlns:wsse="http://docs.x.org/wsse/">
<saml:Ass xmlns:saml="http://docs.x.org/saml/">
<ds:Sign xmlns:ds="http://docs.x.org/ds/">
<ds:SignVal>SignatureValue</ds:SignVal>
</ds:Sign>
<saml:subj>SubjectValue</saml:subj>
</saml:Ass>
</wsse:Sec>
<To>http://localhost:8080/Test/</To>
<Action>SendTest</Action>
</ns0:Header>
<ns0:Body>...</ns0:Body>
</ns0:Envelope>
The wanted result is to just get the Sec tag and all children:
<wsse:Sec xmlns:wsse="http://docs.x.org/wsse/">
<saml:Ass xmlns:saml="http://docs.x.org/saml/">
<ds:Sign xmlns:ds="http://docs.x.org/ds/">
<ds:SignVal>SignatureValue</ds:SignVal>
</ds:Sign>
<saml:subj>SubjectValue</saml:subj>
</saml:Ass>
</wsse:Sec>
I have tried numerous XSL including this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="Header">
<xsl:copy-of select="*"/>
</xsl:template>
<!-- Exclude these -->
<xsl:template match="To" />
<xsl:template match="Action" />
</xsl:stylesheet>
The result is I get values but no tags...
You have not accounted for namespaces in your XSLT. In your XML, Header is in namespace http://schemas.xmlsoap.org/soap/envelope/, but your XSLT is trying to match a Header in no namespace.
You need to declare the namespaces in your XSLT, and use them in the template matches
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsse="http://docs.x.org/wsse/">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="ns0:Header">
<xsl:copy-of select="wsse:Sec"/>
</xsl:template>
<xsl:template match="ns0:Body" />
</xsl:stylesheet>
Note this XSLT doesn't need templates matching "To" and "Action" because of the explicit copy of wsse:Sec using this approach. However, you do need to template to ensure any test within ns0:Body isn't picked up.
Another approach is to use the identity template, and then you would have the templates to exclude To and Action (and Body)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsse="http://docs.x.org/wsse/">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns0:Envelope|ns0:Header">
<xsl:apply-templates />
</xsl:template>
<!-- Exclude these -->
<xsl:template match="ns0:Body|To|Action" />
</xsl:stylesheet>
Note there is a template matching ns0:Envelope and ns0:Header as although you don't want these elements themselves, you do need to process the child nodes.
You would need to use XSLT 2 or 3 with
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:wsse="http://docs.x.org/wsse/"
exclude-result-prefixes="#all"
version="3.0">
<xsl:template match="/">
<xsl:copy-of select="//wsse:Sec" copy-namespaces="no"/>
</xsl:template>
</xsl:stylesheet>
to get the posted result with a simple copy instruction: https://xsltfiddle.liberty-development.net/bnnZVw
In XSLT 1 the copy will always copy the in-scope namespace xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/" so to remove it from the result you would need to run your code through some kind of transformation stripping in-scope namespaces (other than the one of the element itself):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wsse="http://docs.x.org/wsse/"
exclude-result-prefixes="wsse"
version="1.0">
<xsl:template match="#*">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="//wsse:Sec"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bnnZVw/1
What would be the XSLT to change this XML
<?xml version="1.0" encoding="utf-8"?>
<cas:ADDRESS_DETAILS PRIMARY_ADDRESS_INDICATOR="1" ADDRESS_ID="-289495914026885120" ADDRESS_TYPE="45001" ADDRESS_ACTIVE_FROM_DATE="2006-12-23" PERSON_ID="14512823342202880">
<cas:ADDRESS_ELEMENT VALUE="McMurchy Avenue" TYPE="ADD2" />
<cas:ADDRESS_ELEMENT VALUE="ON" TYPE="PROV" />
<cas:ADDRESS_ELEMENT VALUE="CA" TYPE="COUNTRY" />
<cas:ADDRESS_ELEMENT VALUE="Brampton" TYPE="CITY" />
<cas:ADDRESS_ELEMENT VALUE="440" TYPE="ADD1" />
</cas:ADDRESS_DETAILS>
In to this format
<?xml version="1.0" encoding="utf-8"?>
<cas:ADDRESS_DETAILS PRIMARY_ADDRESS_INDICATOR="1" ADDRESS_ID="-289495914026885120" ADDRESS_TYPE="45001" ADDRESS_ACTIVE_FROM_DATE="2006-12-23" PERSON_ID="14512823342202880" ADD2 ="McMurchy" PROV="ON" COUNTRY="CA" CITY="Brampton" ADD1="440">
</cas:ADDRESS_DETAILS>
Assuming you want to merge all ADDRESS_ELEMENTs inside their parent you can use
<xsl:template match="ADDRESS_ELEMENT[1]">
<xsl:copy>
<xsl:apply-templates select="../ADDRESS_ELEMENT" mode="to-attribute"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ADDRESS_ELEMENT[position() > 1]"/>
<xsl:template match="ADDRESS_ELEMENT" mode="to-attribute">
<xsl:attribute name="{#TYPE}" select="#VALUE"/>
</xsl:template>
plus the identity transformation to handle the rest (i.e. <xsl:mode on-no-match="shallow-copy"/> in XSLT 3 (https://xsltfiddle.liberty-development.net/6qM2e2q) or the corresponding template in earlier versions)
If you want to transform the child elements into attributes of the parent, as your edit seems to indicate, you can simplify the code. Using a namespace requires some adaption however:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://example.com/cas"
version="3.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="ADDRESS_DETAILS">
<xsl:copy>
<xsl:apply-templates select="#*, ADDRESS_ELEMENT"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ADDRESS_ELEMENT">
<xsl:attribute name="{#TYPE}" select="#VALUE"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/6qM2e2q/2
i am trying to copy entire input xml in a string for further processing and also i have a requirement to remove text from a particular node (plancode) before copying in the variable. May I know how can i achieve this using xslt
INPUT XML:
<CallMember>
<PlanD>
<abcpr>you</abcpr>
<Desd>Protection</Desd>
<plancode>76789</plancode>
<plaDesc>goody</plaDesc>
</PlanD>
<fType>ONLINE</fType>
</CallMember>
XSLT i am trying :
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt" xmlns:func="http://exslt.org/functions" xmlns:dp="http://www.datapower.com/extensions" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:tglfn="http://test.com/tgl" xmlns:date="http://exslt.org/dates-and-times" exclude-result-prefixes="#all" extension-element-prefixes="dp regexp">
<xsl:output method="xml" encoding="UTF-8" indent="yes" omit-xml-declaration="no"/>
<xsl:template match="/">
<xsl:variable name="InputRequest">
<xsl:copy-of select="."/>
</xsl:variable>
<xsl:variable name="modifiedRequest">
<xsl:copy-of select="."/>
<plancode></plancode>
</xsl:variable>
</xsl:template>
OUTput i am expecting in the modifiedRequest variable:
<CallMember>
<PlanD>
<abcpr>you</abcpr>
<Desd>Protection</Desd>
<plancode></plancode> <!-- this value needs to get emptied -->
<plaDesc>goody</plaDesc>
</PlanD>
<fType>ONLINE</fType>
</CallMember>
Use an identity template in combination with an (nearly) empty template for filtering and apply-templates to it:
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xslt"
xmlns:func="http://exslt.org/functions"
xmlns:dp="http://www.datapower.com/extensions"
xmlns:regexp="http://exslt.org/regular-expressions"
xmlns:tglfn="http://test.com/tgl"
xmlns:date="http://exslt.org/dates-and-times"
exclude-result-prefixes="#all" extension-element-prefixes="dp regexp">
<xsl:output method="xml" encoding="UTF-8" indent="yes" omit-xml-declaration="no"/>
<!-- identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="InputRequest">
<xsl:copy-of select="."/>
</xsl:variable>
<!-- copies subtree except matching empty template -->
<xsl:variable name="modifiedRequest">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:variable>
</xsl:template>
<!-- (nearly) empty template copies only element without content -->
<xsl:template match="plancode">
<xsl:copy />
</xsl:template>
</xsl:stylesheet>
I want to compare the attributes of two xml-Files and identity transform the input file in the same step. The output xml should only contain elements whose attributes occur in the comparing xml. As shown in the given example, the last concept node should not be outputted, as there is no matching attribute in the comparing.xml
input.xml
<navigation
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<facets>
<facet id="d1e12000000000000000000000011111">
<title xml:lang="en">sometxt</title>
<title xml:lang="de">eintxt</title>
<concepts>
<concept id="d1e12000000000000000000000000000">
<title xml:lang="en">sometxt</title>
<title xml:lang="de">eintxt</title>
<concepts>
<concept id="d1e19000000000000000000000000000">
<title xml:lang="en">sometxt</title>
<title xml:lang="de">eintxt</title>
<concepts>
</concepts>
</concept>
</concepts>
</concept>
</concepts>
</facet>
</facets>
part of comparing.xml with indefinite heading-levels
<foo>
<heading class="d1e12000000000000000000000011111|d1e12000000000000000000000000000">Myheading</heading>
<chapter>
<heading class="d1e12000000000000000000000011111|d1e12000000000000000000000000000">myheading</heading>
<operation>
<heading class="d1e12000000000000000000000011111|d1e12000000000000000000000000000">another heading</heading>
</operation>
</chapter>
desired output.xml with only applicable id's
<nav:navigation
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:nav="http://www.nav.de/">
<nav:facets>
<nav:facet id="d1e12000000000000000000000011111">
<nav:title xml:lang="en">sometxt</nav:title>
<nav:title xml:lang="de">eintxt</nav:title>
<nav:concepts>
<nav:concept id="d1e12000000000000000000000000000">
<nav:title xml:lang="en">sometxt</nav:title>
<nav:title xml:lang="de">eintxt</nav:title>
<nav:concepts>
</nav:concepts>
</nav:concept>
</nav:concepts>
</nav:facet>
</nav:facets>
my xsl so far
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:nav="http://www.nav.de/"
version="2.0" >
<xsl:output method="xml" indent="yes" encoding="utf-8"/>
<xsl:variable name="docu" select="document(comparing.xml)"/>
<xsl:template match="*">
<xsl:element name="nav:{name()}" namespace="http://www.nav.de/">
<xsl:copy-of select="namespace::*"/>
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
EDIT: sorry for posting this in the comment-section. I've tried something along those lines, but it didn't work
<xsl:template match="concept | facet">
<xsl:variable name="foo-id" select="#id"/>
<xsl:for-each select="$docu//heading">
<xsl:if test="contains(./#class, $foo-id)">
<xsl:apply-templates/>
</xsl:if>
</xsl:for-each>
</xsl:template>
I would suggest you try it this way:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:nav="http://www.nav.de/">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="comparing-url" select="'comparing.xml'"/>
<xsl:key name="comp" match="#class" use="tokenize(., '\|')" />
<xsl:template match="*">
<xsl:element name="nav:{name()}" >
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="*[#id][not(key('comp', #id, document($comparing-url)))]"/>
</xsl:stylesheet>
I need to transform the following xml
<node1 xmlns:ns1="namespace1">
<node2 xmlns:ns2="namespace2">
<node3...>
<node4...>
</node2>
</node1>
To
<NewNode2 xmlns:ns2="namespace2">
<node3...>
<node4...>
</NewNode2>
I use this XSLT
<?xml version="1.0" encoding="utf-16" ?>
<xsl:stylesheet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:ns1="namespace1"
xmlns:ns2="namespace2">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates select="/" />
</xsl:template>
<xsl:template match="/" >
<NewNode2>
<xsl:copy-of select="//*[local-name()='node2']" />
</NewNode2 >
</xsl:template>
</xsl:stylesheet>
But this throws error in visual studio -
input validation error - element 'namespace1:node1' not declared
and element 'namespace2:node2' not declared
Your goal van be achieved with the following XSLT:
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="namespace1"
xmlns:ns2="namespace2"
xmlns="namespace2">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="#*|*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns1:node1">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="ns2:node2">
<ns2:NewNode2>
<xsl:apply-templates />
</ns2:NewNode2>
</xsl:template>
</xsl:stylesheet>
The statement <xsl-templates select="/" /> inside <xsl:stylesheet match="/"> causes an infinite loop, since the matching template for the called 'root' is the template itself, which is calling the root.
The template <xsl template match="#*|*> belongs in almost all stylesheets, because this copies the content of all elements which are not otherwise specified (most applicable selection rule).
The other two templates specify specific behaviour for ns1:node1 (do not output any information at this level, but continue the template matching process for all further levels) and for ns2:node2 (create ns2:NewNode2 and continue to include all other availble information inside).