Identity Transformation - compare attributes and limit output - xslt

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>

Related

XSLT: Copy an element where a child element matches an attribute

It's abundantly clear that a variation of this question has been asked many times before. I've sifted through dozens of other questions, and still can't seem to find the answer.
Given an XML doc that looks like this:
<Media Attribute="4">
<Printed SomeAttribute="3">
<Book ID="1" OtherAttribute="2">
<Author ID="A">Author Name1</Author>
<Title>Some Title</Title>
</Book>
<Book ID="2" OtherAttribute="2">
<Author ID="A">Author Name2</Author>
<Title>Another Book Name</Title>
</Book>
</Printed>
</Media>
I am looking to extract the book where #ID="1" such that the output looks as follows:
<Media Attribute="4">
<Printed SomeAttribute="3">
<Book ID="1" OtherAttribute="2">
<Author ID="A">Author Name1</Author>
<Title>Some Title</Title>
</Book>
</Printed>
</Media>
I've tried different variations of the following, but it's not working:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Media">
<xsl:copy>
<xsl:copy-of select="#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/Media/Printed/Book[#ID='1']]">
<xsl:copy>
<xsl:copy-of select="#*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I can successfully copy the root node, and can successfully copy the Book elements recursively using copy-of, but I'm not sure how to match/select the parent nodes (Media/Printed) non-recursively while also copying the Book element with recursion at the same time.
Thanks so much!
how to match/select the parent nodes (Media/Printed) non-recursively
Why not do it recursively all the way:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Printed">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="Book[#ID='1']"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Alternatively, you could do:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/Media">
<xsl:copy>
<xsl:copy-of select="#*"/>
<Printed>
<xsl:copy-of select="Printed/#*"/>
<xsl:copy-of select="Printed/Book[#ID='1']"/>
</Printed>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
While the suggestion posted by michael.hors257k was useful, it didn't produce the precise result that I wanted, mostly because I didn't properly explain what I needed. What I ended up with was:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/Media">
<xsl:copy>
<xsl:for-each select="Printed[Book/#ID='1']">
<Printed>
<xsl:copy-of select="#*" />
<xsl:copy-of select="Book[#ID='1']" />
</Printed>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Thanks so much!

Assigning a Unique reference and reference it to another collection

I am new to xml and am trying to add in a link between two collections by inserting a Position() number which I have added into the StyleCollection. I now need to insert this as a StyleLink into the PackingLineCollection where the StyleNumber is equal to the StyleNumber in the StyleCollection. Can anybody point me in the right direction?
Here is the Input:
<Order>
<UniqueReferenceNumber>Order1234</UniqueReferenceNumber>
<StyleCollection>
<Style>
<StyleNumber>1234</StyleNumber>
<StyleType>
<Code>abc</Code>
</StyleType>
<LocationNumber></LocationNumber>
</Style>
<Style>
<StyleNumber>567</StyleNumber>
<StyleType>
<Code>xyz</Code>
</StyleType>
<LocationNumber></LocationNumber>
</Style>
</StyleCollection>
<SubOrderCollection>
<SubOrder>
<UniqueReferenceNumber>SubOrder1</UniqueReferenceNumber>
<PackingLineCollection>
<PackingLine>
<StyleNumber>1234</StyleNumber>
</PackingLine>
</PackingLineCollection>
</SubOrder>
<SubOrder>
<UniqueReferenceNumber>SubOrder2</UniqueReferenceNumber>
<PackingLineCollection>
<PackingLine>
<StyleNumber>xyz</StyleNumber>
</PackingLine>
</PackingLineCollection>
</SubOrder>
</SubOrderCollection>
</Order>
Here is the xslt:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" mlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:template match ="/">
<Order>
<Reference>
<xsl:value-of select="Order/UniqueReferenceNumber"/>
</Reference>
<StyleCollection>
<xsl:for-each select="Order/StyleCollection/Style">
<Style>
<StyleNumber>
<xsl:value-of select="StyleNumber"/>
</StyleNumber>
<LocationNumber><xsl:value-of select="position()"/>
</LocationNumber>
<StyleType>
<Code>
<xsl:value-of select="StyleType/Code"/>
</Code>
</StyleType>
</Style>
</xsl:for-each>
</StyleCollection>
<SubOrderCollection>
<xsl:for-each select="Order/SubOrderCollection/SubOrder">
<SubOrder>
<Reference><xsl:value-of select="UniqueReferenceNumber"/>
</Reference>
<PackingLineCollection>
<xsl:for-each select="PackingLineCollection/PackingLine">
<PackingLine>
<StyleNumber>
<xsl:value-of select="StyleNumber" />
</StyleNumber>
<StyleLink><xsl:value-of
Select="Order/StyleCollection/Style/LocationNumber"/></StyleLink>
</PackingLine>
</xsl:for-each>
</PackingLineCollection>
</SubOrder>
</xsl:for-each>
</SubOrderCollection>
</Order>
</xsl:template>
</xsl:stylesheet>
I have implemented it using a key and xsl:number as follows (in XSLT 3 but the key and xsl:number should work in XSLT 1 or 2 as well, you would need to replace the used <xsl:mode on-no-match="shallow-copy"/> with the identity transformation template):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="3.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:key name="style-ref" match="StyleCollection/Style" use="StyleNumber"/>
<xsl:template match="Style/LocationNumber">
<xsl:copy>
<xsl:number from="StyleCollection" level="any"/>
</xsl:copy>
</xsl:template>
<xsl:template match="PackingLine/StyleNumber">
<xsl:next-match/>
<xsl:apply-templates select="key('style-ref', .)" mode="count"/>
</xsl:template>
<xsl:template match="Style" mode="count">
<StyleLink>
<xsl:number level="any" from="StyleCollection"/>
</StyleLink>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/nc4NzQt/1 however does not find a matching Style for the second PackingLine/StyleNumber, not sure whether your example data is meant to have no reference there or whether I misunderstood your requirements.
XSLT 1 is
<?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"
exclude-result-prefixes="xs"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="#* | node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:key name="style-ref" match="StyleCollection/Style" use="StyleNumber"/>
<xsl:template match="Style/LocationNumber">
<xsl:copy>
<xsl:number from="StyleCollection" level="any"/>
</xsl:copy>
</xsl:template>
<xsl:template match="PackingLine/StyleNumber">
<xsl:call-template name="identity"/>
<xsl:apply-templates select="key('style-ref', .)" mode="count"/>
</xsl:template>
<xsl:template match="Style" mode="count">
<StyleLink>
<xsl:number level="any" from="StyleCollection"/>
</StyleLink>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/nc4NzQt/2

remove text from a node in input XML

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>

Xslt gouping issue

I have an xml like below
<XYZ>
<Name>abc</Name>
<Year>1984</Year>
<Model>qwe</Model>
<Name>abc</Name>
<Year>1987</Year>
<Model>qwe</Model>
<Name>qweqr</Name>
<Year>1977</Year>
<Model>tryet</Model>
</XYZ>
Which needs to be transformed to
<XYZ>
<Name>abc</Name>
<Year>1984</Year>
<Year>1987</Year>
<Model>qwe</Model>
<Name>qweqr</Name>
<Year>1977</Year>
<Model>tryet</Model>
</XYZ>
Here Name can be considered as key
With XSLT 1.0, this can be done using Muenchian Grouping with following XSLT:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:key name="names" match="Name" use="text()"/>
<xsl:key name="models" match="Model" use="text()"/>
<xsl:template match="XYZ">
<XYZ>
<xsl:for-each select="//Name[generate-id()=generate-id(key('names',text())[1])]">
<xsl:variable name="current" select="."/>
<xsl:apply-templates select="."/>
<xsl:apply-templates select="//Name[.=$current]/following-sibling::Year[1]"/>
<xsl:for-each select="//Name[.=$current]/following-sibling::Model[1]
[generate-id()=generate-id(key('models',text())[1])]">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:for-each>
</XYZ>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:transform>
Result:
<XYZ>
<Name>abc</Name>
<Year>1984</Year>
<Year>1987</Year>
<Model>qwe</Model>
<Name>qweqr</Name>
<Year>1977</Year>
<Model>tryet</Model>
</XYZ>
The duplicate nodes for Year and Model are removed as only unique values are selected in the xsl:for-each:
<xsl:for-each select="//Name[generate-id()=generate-id(key('names',text())[1])]">
The unique Name is copied, then all years that have a preceding Name with the value of the current unique Name:
<xsl:apply-templates select="//Name[.=$current]/following-sibling::Year[1]"/>
Then, the corresponding unique Model nodes are copied with a second xsl:for-each selecting only unique following Model nodes:
<xsl:for-each select="//Name[.=$current]/following-sibling::Model[1]
[generate-id()=generate-id(key('models',text())[1])]">
As there are already many answers on Stackoverflow for XSLT grouping using the Muenchian method, I just recommend the detailed explanation in this article by Jeni Tennison http://www.jenitennison.com/xslt/grouping/muenchian.xml.
As additional reference for XSLT grouping you can have a look at http://www.dpawson.co.uk/xsl/sect2/N4486.html
Update: As suggested as comment, this won't work in case of duplicate model values for different name values. Following adjusted XSLT
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8 indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:key name="names" match="Name" use="text()"/>
<xsl:template match="XYZ">
<XYZ>
<xsl:for-each select="//Name[generate-id()=generate-id(key('names',text())[1])]">
<xsl:variable name="current" select="."/>
<xsl:apply-templates select="."/>
<xsl:apply-templates select="//Name[.=$current]/following-sibling::Year[1]"/>
<xsl:for-each select="//Name[.=$current]/following-sibling::Model[1]
[not(.= preceding-sibling::Name[.=$current]
/preceding-sibling::Name[.=$current]
/following-sibling::Model)]">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:for-each>
</XYZ>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:transform>
works for the example XML provided in OP as well as the example XML suggested in the comment - only unique name values with optional multiple years and unique model values for a name, but the same model can also be listed for a different name.
I think you can use this stylesheet, which uses keys to select Year and Model(groups the Models too based on their Name and then with their value) based on their preceding Name(after grouping Names):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="Name" match="Name" use="."/>
<xsl:key name="Year" match="Year" use="preceding::Name[1]"/>
<xsl:key name="Model" match="Model" use="preceding::Name[1]"/>
<xsl:template match="XYZ">
<xsl:copy>
<xsl:for-each select="Name[count(. | key('Name', .)[1]) = 1]">
<xsl:copy-of select="."/>
<xsl:copy-of select="key('Year', .)"/>
<xsl:copy-of select="key('Model', .)[not(. = preceding::Model[preceding::Name[1] = current()])]"/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

xslt extract and sort leaf nodes by name - unexpected result

I wanted to extract leaf nodes and have them sorted.
My XSL gives unexpected results. How can I solve this?
Input
<root>
<b>
<b33 zzz="2" fff="3"></b33>
<b11></b11>
<b22></b22>
</b>
<a>
<a27></a27>
<a65 fff="0" eee="2" zzz="10"></a65>
<a11></a11>
</a>
</root>
Xsl
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:call-template name="leafnodes"/>
</root>
</xsl:template>
<xsl:template match="*[not(*)]|#*" name="leafnodes">
<xsl:copy>
<xsl:apply-templates select="node()">
<xsl:sort select="name()"/>
</xsl:apply-templates>
<xsl:apply-templates select="#*">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output (I would expected it to be sorted, it is not)
<root>
<b33 fff="3" zzz="2" />
<b11 />
<b22 />
<a27 />
<a65 eee="2" fff="0" zzz="10" />
<a11 />
</root>
I would expect the nodes in the order a11, a27, a65, b11, b22, b33.
If I leave out the '[not(*)]', the xsl takes all nodes and sorts them properly.
How can this be solved?
To output all element which have no child sorted by name and the attributes also sorted by name. Try this;
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="//*[not(*)]">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="*|#*">
<xsl:copy>
<xsl:apply-templates select="#*" >
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Which will generate following output:
<root>
<a11/>
<a27/>
<a65 eee="2" fff="0" zzz="10"/>
<b11/>
<b22/>
<b33 fff="3" zzz="2"/>
</root>