Implementing Key Value Concept in XSLT - xslt

I am working on XSLT, where I need to implement something as follows.
My Source XML sample looks like this.
<?xml version="1.0" encoding="ISO-8859-1"?>
<catalog>
<cd>
<title>A</title>
<title>B</title>
<title>C</title>
</cd>
</catalog>
Consider there is some key value pair list is there.
Key Value
A Algebra
B Biology
C Chemistry
D Data Analysis
--- ---
---- ---
I need to write an xslt such that for every occurance of key 'A', need to replace with appropriate value.
I also need to mention the list of Key value pairs in the same XSLT.
Sample Output:
<Data>
<Subject>Algebra</Subject>
<Subject>Biology</Subject>
<Subject>Chemistry</Subject>
</Data>
Can any one help me out how to do it.
Thank you.

I. Simple XSLT 1.0 Solution
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:codes>
<code key="A" value="Algebra"/>
<code key="B" value="Biology"/>
<code key="C" value="Chemistry"/>
<code key="D" value="Data Analysis"/>
</my:codes>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"title/text()[. = document('')/*/my:codes/*/#key]">
<xsl:value-of select=
"document('')/*/my:codes/*[#key=current()]/#value"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<catalog>
<cd>
<title>A</title>
<title>B</title>
<title>C</title>
</cd>
</catalog>
produces the wanted, correct result:
<catalog>
<cd>
<title>Algebra</title>
<title>Biology</title>
<title>Chemistry</title>
</cd>
</catalog>
Explanation:
This is the standard way of including inline XML node as a global element (child element of xsl:stylesheet) that belongs to a (non-empty) namespace, different than the xsl namespace.
II. More efficient XSLT 1.0 solution, using keys:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:codes>
<code key="A" value="Algebra"/>
<code key="B" value="Biology"/>
<code key="C" value="Chemistry"/>
<code key="D" value="Data Analysis"/>
</my:codes>
<xsl:key name="kCodeByName" match="code" use="#key"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"title/text()[. = document('')/*/my:codes/*/#key]">
<xsl:variable name="vCur" select="."/>
<xsl:for-each select="document('')">
<xsl:value-of select=
"key('kCodeByName', $vCur)/#value"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the same XML document (above), the same correct, wanted result is produced:
<catalog>
<cd>
<title value="Algebra"/>
<title value="Biology"/>
<title value="Chemistry"/>
</cd>
</catalog>
Explanation:
Accessing data via the key() function is typically sub-linear -- often O(1) and is extremely faster than linear search (which is important if the number of nodes to be searched is big).
Accessing a node of one document via an index (xsl:key) while processing a node of another document is possible if the document containing the node to be looked-up is the current document. To access nodes from the other document, its root (or node of interest need to be saved and referenced off a variable.)
III. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vCodes">
<codes>
<code key="A" value="Algebra"/>
<code key="B" value="Biology"/>
<code key="C" value="Chemistry"/>
<code key="D" value="Data Analysis"/>
</codes>
</xsl:variable>
<xsl:key name="kCodeByName" match="code" use="string(#key)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"title/text()[key('kCodeByName', ., $vCodes)]">
<xsl:sequence select=
"key('kCodeByName', ., $vCodes)/#value"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the same XML document (above), the same correct, wanted result is produced:
<catalog>
<cd>
<title value="Algebra"/>
<title value="Biology"/>
<title value="Chemistry"/>
</cd>
</catalog>
Explanation:
Almost the same as the efficient XSLT 1.0 solution, but:
In XSLT 2.0 a template match pattern can contain a variable reference.
In XSLT 2.0 there is no need for acrobatic tricks manipulating the current and the indexed documents -- the 3rd argument of the key() function is to specify the tree whose index to use.

Related

Identity Transformation - compare attributes and limit output

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>

XSLT - Bring in external document value that matches on an element

I have XML files where I need to replace the value of an element (idno[#type='code']) with a value from an external document.
I have to match the value based off of an eISBN in that external document.
XML file:
<book>
<idno type="online">3456789012345</idno>
<idno type="subject">Environment</idno>
<idno type="subject">Water</idno>
<idno type="subject">Policy</idno>
<idno type="code">135/B</idno>
<idno type="ID">43396</idno>
</book>
External document (a2r-test.xml):
<root>
<row>
<eISBN>1234567890123</eISBN>
<uri>book1</uri>
<a2r>89364</a2r>
</row>
<row>
<eISBN>3456789012345</eISBN>
<uri>book2</uri>
<a2r>E135</a2r>
</row>
<row>
<eISBN>5678901234567</eISBN>
<uri>book3</uri>
<a2r>B10765/B</a2r>
</row>
</root>
XSL:
<?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:tei="http://www.tei-c.org/ns/1.0"
exclude-result-prefixes="xs"
version="2.0">
<xsl:variable name="a2r" select="document('a2r-test.xml')"/>
<xsl:template match="tei:idno[#type='code']">
<xsl:if test="$a2r//row/eISBN = preceding-sibling::tei:idno[#type='online']">
<xsl:element name="idno">
<xsl:attribute name="type" select="'code'"/>
**<xsl:value-of select="?"/>**
</xsl:element>
</xsl:if>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
What do I place in the xsl:value-of select to pull in the value of "a2r" (following-sibling::eISBN - that matches the idno[#type='online'])
Final expected result:
<book>
<idno type="online">3456789012345</idno>
<idno type="subject">Environment</idno>
<idno type="subject">Water</idno>
<idno type="subject">Policy</idno>
<idno type="code">E135</idno>
<idno type="ID">43396</idno>
</book>
Thanks in advance.
In general, it's best to use a key to resolve cross-references. Here's an example matching your given inputs:
XSLT 2.0
<xsl:stylesheet version="2.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:variable name="a2r-doc" select="document('a2r-test.xml')"/>
<xsl:key name="a2r-key" match="row" use="eISBN" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="idno[#type='code']">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:value-of select="key('a2r-key', ../idno[#type='online'], $a2r-doc)/a2r"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note:
You didn't show us your real input, which - judging from your XSLT - is in a namespace. In such case, you cannot use xsl:copy (through the identity transform template) for some elements and xsl:element (without a namespace) for others, or you will end up with the result being a mix of elements in different namespaces.

XSLT: how to reverse the tree?

I need to transform a document like this:
<root>
<products>
<ProductInfo>
<ProductID>0</ProductID>
<ProductName>Hello world!</ProductName>
</ProductInfo>
<M>
<ModelInfo>
<ModelID>0</ModelID>
<ModelName>Hello world!</ModelName>
</ModelInfo>
</M>
</products>
</root>
Into this:
<root>
<products>
<M>
<ModelInfo>
<ModelName>Hello world!</ModelName>
<ModelID>0</ModelID>
</ModelInfo>
</M>
<ProductInfo>
<ProductName>Hello world!</ProductName>
<ProductID>0</ProductID>
</ProductInfo>
</products>
</root>
So all the tags in the output should be in reversed order.
I need this for testing: I need to ensure that some external application accepts the tags in any order; and also I need it to test that my XML Schema allows tags in any order.
Not reverse the tree, but reverse the order of sibling branches (at all levels):
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="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="node()">
<xsl:sort select="position()" data-type="number" order="descending"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Grouping of same tags under an element in xslt

I am working XSLT where the source looks like this.
Source:
<Data>
<AB>all</AB>
<AB>all2</AB>
<CD>hhhhhh</CD>
<DE>hhhshhh</DE>
</Data>
Need to write XSLT to get output as
<Info>
<XXX>
<TTT value="all"/>
<TTT value="all2"/>
</XXX>
<!-- ....-->
<!-- ..to het all the elements.. -->
</Info>
I have to write xslt to match tag.
<xsl:template match="AB">
</xsl:template>
I can do it by matching Data tag.
<?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" indent="yes"/>
<xsl:template match="Data">
<info>
<XXX>
<xsl:for-each select="AB">
<TTT>
<xsl:attribute name="value">
<xsl:value-of select="."/>
</xsl:attribute>
</TTT>
</xsl:for-each>
</XXX>
</info>
</xsl:template>
</xsl:stylesheet>
Can any one help me out how to do it by matching AB tag
<xsl:template match="AB">
</xsl:template>
Thank you.
I think you are asking how do you use xsl:apply-templates. If so, your XSLT would look like this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Data">
<info>
<XXX>
<xsl:apply-templates select="AB"/>
</XXX>
</info>
</xsl:template>
<xsl:template match="AB">
<TTT value="{.}"/>
</xsl:template>
</xsl:stylesheet>
Do also note the use of Attribute Value Templates in the AB template to simplify the XSLT.
If you also require the other non-AB elements to be output unchanged, you would make use of the identity transform in your XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Data">
<info>
<XXX>
<xsl:apply-templates select="AB"/>
</XXX>
<xsl:apply-templates select="node()[not(self::AB)]" />
</info>
</xsl:template>
<xsl:template match="AB">
<TTT value="{.}"/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This would also match all non-AB elements, outputing following the XXX element. In your case, it would output the following:
<info>
<XXX>
<TTT value="all" />
<TTT value="all2" />
</XXX>
<CD>hhhhhh</CD>
<DE>hhhshhh</DE>
</info>
Of course, there is no reason you couldn't have other templates matching elements like CD or DE to transform those too.

XSLT Transformation: Search nodes, and return hierarchical parents

hoping this hasn't been asked before, but I have the following XML:
<Company id="1000" name="Company1000">
<Company id="1020" name="Company1020" />
<Company id="1004" name="Company1004">
<Company id="1005" name="Company1005" />
</Company>
<Company id="1022" name="Company1022" />
</Company>
I have the following XPath to search for nodes: //*[contains(translate(#name, "ABCDEFGHJIKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"), "005")]
I would like this to return:
<Company id="1000" name="Company1000">
<Company id="1004" name="Company1004">
<Company id="1005" name="Company1005" />
</Company>
</Company>
So this matches the Company1005 node, and all its respective parents. I would like the above to also be returned if I were searching for "100", which in that case, would match each element in turn, but I clearly don't want duplication of nodes.
I've been struggling with this for hours now, so your help will be much appreciated!!!
Thanks.
The solution is to copy only descendant or self nodes which match your requirement (containing the string you want).
Look this picture at the bottom of this page for the XPath axes you need.
Long version:
<?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" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Default: copy everything -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- just copy Company which descendant-or-self contain the matching string -->
<xsl:template match="Company">
<xsl:if test="descendant-or-self::Company[contains(translate(#name, 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '005')]">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Short version:
<?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" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Default: copy everything -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- do not copy Company which do not have a descendant-or-self matching string -->
<xsl:template match="Company[not(descendant-or-self::Company[contains(translate(#name, 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '005')])]"/>
</xsl:stylesheet>
Output of your xml:
<?xml version="1.0" encoding="UTF-8"?>
<Company id="1000" name="Company1000">
<Company id="1004" name="Company1004">
<Company id="1005" name="Company1005"/>
</Company>
</Company>