XSL-Transformation: escape tags and keep attributes within this tag - xslt-1.0

I'm an absolute beginner concerning xsl transformation and I have a problem one of you may can help me with. I have following xml block:
<Metrics>
<Metric name="DocAmount" value="123.21" currency="GBP" type="Total"/>
<Metric name="Invoices" value="113.21" currency="GBP" type="Total"/>
<Metric name="Credit" value="10.00" currency="GBP" type="Total"/>
</Metrics>
I have to escape the "<" and ">" from the inner elements "Metric" and keep at the same time all attributes with their values => I want to have this:
<Metrics>
<Metric name="DocAmount" value="123.21" currency="GBP" type="Total"/>
<Metric name="Invoices" value="113.21" currency="GBP" type="Total"/>
<Metric name="Credit" value="10.00" currency="GBP" type="Total"/>
</Metrics>
I already searched here in stackoverflow and found a way to escape the "<" and the ">" but with my xsl template the attributes arn't copied and I get this here:
<Metrics>
<Metric></Metric>
<Metric></Metric>
<Metric></Metric>
</Metrics>
To get this I used following xsl template definition:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="no" encoding="UTF-8"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Metric">
<xsl:value-of select="concat('<',name(),'>',.,'</',name(),'>')" />
</xsl:template>
</xsl:stylesheet>
Can anybody help me to setup a correct xsl template doing what I want to have?
Thanks a lot in advance for your help!

As your question is tagged as XSLT 2 and these days we have XSLT 3 and the main XSLT 2.0 implementations like Saxon 9 with Saxon 9.8 or Altova with Altova 2017 or 2018 have been updated to support XSLT 3 I think the easiest and most elegant solution is to move to XSLT 3 and use the XPath 3 serialize function:
<xsl:template match="Metric">
<xsl:value-of select="serialize(.)"/>
</xsl:template>
As your comment indicates you have namespaces in your input you don't want to be serialized you can use
<xsl:template match="Metric">
<xsl:variable name="copy" as="element(Metric)"><xsl:copy-of select="." copy-namespaces="no"/></xsl:variable>
<xsl:value-of select="serialize($copy)"/>
</xsl:template>

Related

XSLT to CSV - How to control the order of the same element with different attribute values?

I am quite a newbie to XSLT and I need to figure out how to retrieve repeating elements from a XML file in a specific order based on their attribute value to a CSV file. My result document is a CSV file that I ultimately need to import its content to SQL Server tables. Therefore, the order of the retrieved elements within the CSV file does matter as they need to match the table columns defined as headers.
My problem occurs with the Project Content_Detail element that exists in different languages and can appear in any order in the XML source. I only need to extract the German version with the Title, Goal element first, followed by the English version elements.
I use SSIS with MS Visual Studio 2019 to transform my XML to a CSV file. MS Visual Studio supports only XSLT 1.0.
Here is my XML file (EDITED):
<?xml version="1.0" encoding="utf-8"?>
<dta:Projects xmlns:dta="http://domain.test/dta">
<dta:Project>
<dta:Core-Basis>
<dta:Goal>1672</dta:Goal>
</dta:Core-Basis>
<dta:Basisinfo>
<dta:Content>
<dta:Content_Detail Lang="de">
<dta:Title>Wirtschaft</dta:Title>
<dta:Aim>Steigerung</dta:Aim>
</dta:Content_Detail>
<dta:Content_Detail Lang="en">
<dta:Title>Economy</dta:Title>
</dta:Content_Detail>
</dta:Content>
</dta:Basisinfo>
</dta:Project>
<dta:Project >
<dta:Core-Basis>
<dta:Goal>2035</dta:Goal>
</dta:Core-Basis>
<dta:Basisinfo>
<dta:Content>
<dta:Content_Detail Lang="en">
<dta:Title>Environmental Protection</dta:Title>
<dta:Aim>Facilitation</dta:Aim>
</dta:Content_Detail>
<dta:Content_Detail Lang="de">
<dta:Title>Naturschutz</dta:Title>
</dta:Content_Detail>
</dta:Content>
</dta:Basisinfo>
</dta:Project >
</dta:Projects>
This is my XSLT file (EDITED). I tried to add numerous order and sort arguments to the xsl:if block but I couldn’t come up with a working solution yet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dta="http://domain.test/dta" exclude-result-prefixes="dta">
<xsl:output method="text" encoding="UTF-8" indent="no"/>
<xsl:template match="/">
<xsl:text>DTA_Goal;DTA_Title_de;DTA_Aim_de;DTA_Title_en;DTA_Aim_en;
</xsl:text>
<xsl:apply-templates mode="find_content"/>
</xsl:template>
<xsl:template match="text()|#*" mode="find_content"/>
<xsl:template match="dta:Project" mode="find_content">
<xsl:value-of select="concat(dta:Core-Basis/dta:Goal,';')"/>
<xsl:apply-templates mode="find_content"/>
</xsl:template>
<xsl:template match="dta:Content_Detail" mode="find_content">
<xsl:if test="#Lang='de'">
<xsl:value-of select="concat(dta:Title,';')"/>
<xsl:value-of select="concat(dta:Aim,';')"/>
</xsl:if>
<xsl:if test="#Lang='en'">
<xsl:value-of select="concat(dta:Title,';')"/>
<xsl:value-of select="concat(dta:Aim,'
')"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
This is my CSV output so far, not sorting the Content_detail based on their language value attribute:
DTA_Goal;DTA_Title_de;DTA_Aim_de;DTA_Title_en;DTA_Aim_en;
1672;Wirtschaft;Steigerung;Economy;
2035;Environmental Protection;Facilitation
Naturschutz;;
This is what I need:
DTA_Goal; DTA_Title_de; DTA_Aim_de; DTA_Title_en; DTA_Aim_en;
1672;Wirtschaft;Steigerung;Economy;;
2035;Naturschutz;;Environmental Protection;Facilitation;
As I am not a professional programmer, any help or comment also on the rest of my code is highly appreciated.
In your input, one of the dta:Project elements has a dta:Core-Basis child, while the other has dta:Core-Basisdaten. Assuming that's a mistake*, you could produce the needed output simply by:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dta="http://domain.test/dta">
<xsl:output method="text" encoding="UTF-8" />
<xsl:template match="/dta:Projects">
<!-- header -->
<xsl:text>DTA_Goal;DTA_Title_de;DTA_Aim_de;DTA_Title_en;DTA_Aim_en;
</xsl:text>
<!-- data -->
<xsl:for-each select="dta:Project">
<xsl:value-of select="dta:Core-Basis/dta:Goal"/>
<xsl:text>;</xsl:text>
<!-- de -->
<xsl:variable name="content-de" select="dta:Basisinfo/dta:Content/dta:Content_Detail[#Lang='de']" />
<xsl:value-of select="$content-de/dta:Title"/>
<xsl:text>;</xsl:text>
<xsl:value-of select="$content-de/dta:Aim"/>
<xsl:text>;</xsl:text>
<!-- en -->
<xsl:variable name="content-en" select="dta:Basisinfo/dta:Content/dta:Content_Detail[#Lang='en']" />
<xsl:value-of select="$content-en/dta:Title"/>
<xsl:text>;</xsl:text>
<xsl:value-of select="$content-en/dta:Aim"/>
<xsl:text>;</xsl:text>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
(*) If not, change:
<xsl:value-of select="dta:Core-Basis/dta:Goal"/>
to:
<xsl:value-of select="(dta:Core-Basis|dta:Core-Basisdaten)/dta:Goal"/>
P.S. Not sure why you need the trailing ; at the end of each line.

Looping a variable length array with namespaces in XSLT

My previous question[1] is related to this. I found the answer for that. Now I want to loop a variable length array with namespaces. My array:
<ns:array xmlns:ns="http://www.example.org">
<value>755</value>
<value>5861</value>
<value>4328</value>
<value>2157</value>
<value>1666</value>
</ns:array>
My XSLT code:(have added the namespace in the root)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="http://www.example.org">
<xsl:template match="/">
<xsl:variable name="number" select="ns:array" />
<xsl:for-each select="$number">
<xsl:value-of select="$number" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
[1]https://stackoverflow.com/questions/20287219/looping-a-variable-length-array-in-xslt
IMHO you confused yourself by introducing a variable called number which actually contains a node set of value tags. Then, as a consequence you used your variable as singe item/node which does not yield the desired result (presumingly, since you did not really tell us what you want to do with the values).
Also, I think your question does not really have anything to with namespace issues as such. You just have to make sure that the namespaces in your select expressions match the namespaces in your input file.
I would suggest to do without the variable and change the way you retrieve the current value:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="http://www.example.org">
<xsl:template match="/">
<xsl:for-each select="ns:array">
<!-- Inside here you can work with the `value` tag as the _current node_.
There are two most likely ways to do this. -->
<!-- a) Copy the whole tag to the output: -->
<xsl:copy-of select="." />
<!-- or b1) Copy the text part contained in the tag to the output: -->
<xsl:value-of select="." />
<!-- If you want to be on the safe side with respect to white space
you can also use this b2). This would handle the case that your output
is required not to have any white space in it but your imput XML has
some. -->
<xsl:value-of select="normalize-space(.)" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Adding number in a CSV in XSLT

How can you add numbers in a CSV in XSLT 1 ?
I want to take:
<num>1,2,3</num>
and get the sum of the numbers in the element, so we would get 6 from the above.
Using FXSL and the str-split-to-words template (I am lazy to write recursive templates which is time consuming and error-prone :) ):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:import href="strSplit-to-Words.xsl"/>
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:variable name="vwordNodes">
<xsl:call-template name="str-split-to-words">
<xsl:with-param name="pStr" select="."/>
<xsl:with-param name="pDelimiters" select="','"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vNums" select="ext:node-set($vwordNodes)/*"/>
<xsl:value-of select="sum($vNums)"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<num>1,2,3</num>
the wanted, correct result is produced:
6

XSLT Data Map with the character < in the data

I'm writing an XSLT data map from one XML schema to another XML schema. The target schema has a String field that will have a copy of the original XML in it. I know that I must convert the control characters in the XML to < and >. I have done that. The issue I'm having is that the data in the XML also has < and > in it as well. This causes problems because I'm sending the XML to a WCF-SQL adapter and when it hits the < in my data it attempts to convert that to < causing XML validation failure.
Here is a snippet of my XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="*" mode="serialize">
<xsl:text><</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>></xsl:text>
<xsl:apply-templates mode="serialize"/>
<xsl:text></</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>></xsl:text>
</xsl:template>
<xsl:template match="/">
<proc_Insert xmlns="http://schemas.microsoft.com/Sql/2008/05/TypedProcedures/dbo">
<StagingXML>
<xsl:variable name="nodestring">
<xsl:apply-templates select="*" mode="serialize"/>
</xsl:variable>
<xsl:value-of select="$nodestring"/>
</StagingXML>
</proc_Insert>
</xsl:template>
</xsl:stylesheet>
You need to quote the content, a truely ancient bit of code (predating xslt 1.0 I think:-) that does that is to be found
http://www.openmath.org/cdfiles2/xsl/verb.xsl
although there are newer versions around as well/

Recursive transformations using xslt, xpath:document() and mediawiki

I want to use the Wikipedia API to find the French pages including the ''SQLTemplate:Infobox Scientifique'' missing in the English version. So, my idea was to process the following document with xproc:
http://fr.wikipedia.org/w/api.php?action=query&format=xml&list=embeddedin&eititle=Template:Infobox%20Scientifique&eilimit=400
and the following xslt stylesheet:
<?xml version='1.0' ?>
<xsl:stylesheet
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
version='1.0'
>
<xsl:output method='text' indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="api"/>
</xsl:template>
<xsl:template match="api">
<xsl:for-each select="query/embeddedin/ei">
<xsl:variable name="title" select="translate(#title,&apos; &apos;,&apos;_&apos;)"/>
<xsl:variable name="english-title">
<xsl:call-template name="englishTitle"><xsl:with-param name="title" select="#title"/></xsl:call-template>
</xsl:variable>
<xsl:value-of select="$english-title"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="englishTitle">
<xsl:param name="title"/>
<xsl:variable name="uri1" select="concat(&apos;http://fr.wikipedia.org/w/api.php?action=query&format=xml&prop=langlinks&lllimit=500&titles=&apos;,translate($title,&apos; &apos;,&apos;_&apos;))"/>
<xsl:message><xsl:value-of select="$uri1"/></xsl:message>
<xsl:message>count=<xsl:value-of select="count(document($uri1,/api/query/pages/page/langlinks/ll))"/></xsl:message>
</xsl:template>
</xsl:stylesheet>
The XSLT extract all the articles containing the Template and for each article I wanted to call Wikipedia to get the links between the wikis. Here the template englishTitle calls the xpath function document().
But it always says that count(ll)=1 whereas there are plenty nodes. (e.g. http://fr.wikipedia.org/w/api.php?action=query&format=xml&prop=langlinks&lllimit=500&titles=Carl_Sagan ).
Can't I process the nodes returned by the document() function?
You should try:
<xsl:value-of select="count(document($uri1)/api/query/pages/page/langlinks/ll)"/>
On a different note - what is
translate(#title,&apos; &apos;,&apos;_&apos;)
supposed to mean? What's wrong with:
translate(#title, ' ', '_')
There is no need to encode single quotes in XML attributes unless you want to use a type of quote that delimits the attribute value. All of these are valid:
name="foo"'foo"
name='foo&apos;"foo'
Your entire transformation can be reduced to something like this:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:param name="baseUrl" select="'http://fr.wikipedia.org/w/api.php?action=query&format=xml&prop=langlinks&lllimit=500&titles='" />
<xsl:template match="ei">
<xsl:variable name="uri" select="concat($baseUrl ,translate(#title,' ','_'))"/>
<xsl:variable name="doc" select="document($uri)"/>
<xsl:value-of select="$uri"/>
<xsl:text>
</xsl:text>
<xsl:text>count=</xsl:text>
<xsl:value-of select="count($doc/api/query/pages/page/langlinks/ll)"/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Let the XSLT default templates work for you - they do all of the recursion in the background, all you have to do is catch the nodes you want to process (and prevent output of unnecessary text by overriding the default text() template with an empty one).