Two phase XSLT transformation converting string to XML first - xslt

I have the following XML:
<?xml version="1.0" encoding="utf-8"?>
<string>
<Table>
<Rows>
<Row Id="0">
<Column Name="INS_NAME" XPath="Ins.Name">Jane</Column>
<Column Name="INS_LASTNAME" XPath="Ins.LastName">Smith</Column>
</Row>
<Row Id="1">
<Column Name="INS_NAME" XPath="Ins.Name">Joe</Column>
<Column Name="INS_LASTNAME" XPath="Ins.LastName">Miller</Column>
</Row>
<Row Id="2">
<Column Name="INS_NAME" XPath="Ins.Name">George</Column>
<Column Name="INS_LASTNAME" XPath="Ins.LastName">Ramsey</Column>
</Row>
</Rows>
</Table>
</string>
and I would like to transform it to this XML using a single XSLT:
<?xml version="1.0" encoding="utf-8"?>
<Customers>
<Customer><Name>Jane</Name><LastName>Smith</LastName></Customer>
<Customer><Name>Joe</Name><LastName>Miller</LastName></Customer>
<Customer><Name>George</Name><LastName>Ramsey</LastName></Customer>
</Customers>
I can do it with two different XSLT's:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="/" disable-output-escaping="yes" />
</xsl:template>
</xsl:stylesheet>
and then:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Customers>
<xsl:for-each select="Table/Rows/Row">
<Customer>
<Name><xsl:value-of select="Column[#Name='INS_NAME']" /></Name>
<LastName><xsl:value-of select="Column[#Name='INS_LASTNAME']" /></LastName>
</Customer>
</xsl:for-each>
</Customers>
</xsl:template>
</xsl:stylesheet>
I have been reading about multi phase transformations but I can't seem to get it. I have tried saving the first XSLT in a variable but it seems disable-output-escaping="yes" does not work when saving to a variable.
Can anybody help?
Thank you.
New information (Edit)
I am now translating the string this way:
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="stringXml">
<?xml version="1.0" encoding="utf-8"?>
<xsl:value-of select="translate(translate(/,'>','>'),'<','<')" />
</xsl:variable>
...
How can I do a transformation on the resulting XML stored in stringXML?
Final Solution (Edit)
<msxml:script implements-prefix="myLib" language="C#">
<msxml:assembly name="System.Web"/>
<msxml:using namespace="System.Web"/>
<![CDATA[
public System.Xml.XPath.XPathNodeIterator convertText(string text)
{
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.LoadXml(text);
return doc.CreateNavigator().Select("/");
}
]]>
</msxml:script>

it seems disable-output-escaping="yes" does not work when saving to a
variable.
Your observation is correct.
DOE only affects the serialization of the (final) result of the transformation and isn't applied on intermediary trees.
Here is what the W3C XSLT 1.0 specification explicitly says:
"An XSLT processor will only be able to disable output escaping if it
controls how the result tree is output. This may not always be the
case. For example, the result tree may be used as the source tree for
another XSLT transformation instead of being output."
The same negative answer holds for trying to use a variable, whose value is a string, containing a textual representation of an XML document.

I had a similar situation where I needed to parse an escaped XML inside my actual XML. I will post up my solution to also help someone else. Please also note that I am also using Saxon-PE parser.
In my situation I have the original XML that contains an escaped XML in a child node. I needed to get the inner XML inside the RootNode of the escaped XML.
Source XML:
<?xml version="1.0" encoding="utf-8"?>
<MyTestXml>
<SomeXmlStuff>
<Text1>Hello</Text1>
<Text2>World</Text2>
</SomeXmlStuff>
<SomeEscapedXml><RootNode><FirstNode>Hello</FirstNode><SecondNode>World</SecondNode><ThirdNode>Again</ThirdNode></RootNode></SomeEscapedXml>
</MyTestXml>
When you unescaped the XML, it looks like this:
<RootNode>
<FirstNode>Hello</FirstNode>
<SecondNode>World</SecondNode>
<ThirdNode>Again</ThirdNode>
</RootNode>
With the following XSLT transformation is applied on the source XML:
<?xml version='1.0' encoding='utf-8' ?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:saxon="http://saxon.sf.net/"
exclude-result-prefixes="xsl saxon">
<xsl:template match="/">
<MyOutput>
<xsl:call-template name="GetRootNodeInnerXml">
<xsl:with-param name="escapedXml" select="MyTestXml/SomeEscapedXml" />
</xsl:call-template>
</MyOutput>
</xsl:template>
<xsl:template name="GetRootNodeInnerXml">
<xsl:param name="escapedXml" required="yes" />
<xsl:copy-of select="saxon:parse($escapedXml)/RootNode/node()"/>
<!-- You can also use this line below if you're not using saxon parser. Just make sure your parser supports XSL 3.0 -->
<!--
<xsl:copy-of select="fn:parse-xml($escapedXml)/RootNode/node()" xmlns:fn="http://www.w3.org/2005/xpath-functions"/>
-->
</xsl:template>
</xsl:stylesheet>
This gives you the following output:
<?xml version='1.0' ?>
<MyOutput>
<FirstNode>Hello</FirstNode>
<SecondNode>World</SecondNode>
<ThirdNode>Again</ThirdNode>
</MyOutput>

Related

Inserting XML String into XSLT between XSLT templates

I have a problem on inserting XML strings into the XSLT I have. Particularly, I have a sample XML string here:
<md:People>
<md:Job>
<md:JobFunction>Actor</md:JobFunction>
<md:BillingBlockOrder>1</md:BillingBlockOrder>
</md:Job>
<md:Name>
<md:DisplayName language="en-US">Vice Ganda</md:DisplayName>
</md:Name>
</md:People>
and I want to insert it into the XSLT I have (see <!-- INSERT "People" Metadata XML STRING HERE -->):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soa="urn:telestream.net:soa:core" exclude-result-prefixes='soa' version="1.0">
<xsl:variable name="basicContentID"><xsl:value-of select="/soa:Label/soa:Parameter[#name='basicContentID']/text()"/></xsl:variable>
<xsl:variable name="movieTitle"><xsl:value-of select="/soa:Label/soa:Parameter[#name='movieTitle']/text()"/></xsl:variable>
<xsl:variable name="releaseYear"><xsl:value-of select="/soa:Label/soa:Parameter[#name='releaseYear']/text()"/></xsl:variable>
<xsl:variable name="releaseDate"><xsl:value-of select="/soa:Label/soa:Parameter[#name='releaseDate']/text()"/></xsl:variable>
<xsl:template match="/">
<mdmec:CoreMetadata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:md="http://www.movielabs.com/schema/md/v2.6/md" xmlns:mdmec="http://www.movielabs.com/schema/mdmec/v2.6" xsi:schemaLocation="http://www.movielabs.com/schema/mdmec/v2.6/mdmec-v2.6.xsd">
<mdmec:Basic ContentID="{/soa:Label/soa:Parameter[#name='basicContentID']/text()}">
<md:LocalizedInfo language="{/soa:Label/soa:Parameter[#name='metadataLanguage']/text()}">
<md:TitleDisplayUnlimited><xsl:value-of select="$movieTitle"/></md:TitleDisplayUnlimited>
</md:LocalizedInfo>
<!-- INSERT "People" Metadata XML STRING HERE -->
</mdmec:Basic>
<md:ReleaseYear><xsl:value-of select="$releaseYear"/></md:ReleaseYear>
<md:ReleaseDate><xsl:value-of select="$releaseDate"/></md:ReleaseDate>
</mdmec:CoreMetadata>
</xsl:template>
</xsl:stylesheet>
...to have an XML output like this:
<?xml version="1.0" encoding="utf-8"?>
<mdmec:CoreMetadata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:md="http://www.movielabs.com/schema/md/v2.6/md" xmlns:mdmec="http://www.movielabs.com/schema/mdmec/v2.6" xsi:schemaLocation="http://www.movielabs.com/schema/mdmec/v2.6/mdmec-v2.6.xsd">
<mdmec:Basic ContentID="md:cid:org:abs_cbn:StarCinema-BeautyAndTheBestie2015">
<md:LocalizedInfo language="en-US">
<md:TitleDisplayUnlimited>Beauty and The Bestie</md:TitleDisplayUnlimited>
</md:LocalizedInfo>
<!-- Where "People" Metadata should be appearing -->
<md:People>
<md:Job>
<md:JobFunction>Actor</md:JobFunction>
<md:BillingBlockOrder>1</md:BillingBlockOrder>
</md:Job>
<md:Name>
<md:DisplayName language="en-US">Vice Ganda</md:DisplayName>
</md:Name>
</md:People>
</mdmec:Basic>
<md:ReleaseYear><xsl:value-of select="$releaseYear"/></md:ReleaseYear>
<md:ReleaseDate><xsl:value-of select="$releaseDate"/></md:ReleaseDate>
</mdmec:CoreMetadata>
Basically, the XML string I wanted to insert is in between the <mdmec:basic> code, and defining an xslt template in-between the root is not allowed. How can I go through this?
Thanks for all your help in advance!
EDIT: I tried to reproduce the sample from this thread [https://stackoverflow.com/questions/54535142/xml-string-to-xml-by-xslt] by re-creating the XML string I have:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root>
<md:People>
<md:Job>
<md:JobFunction>Actor</md:JobFunction>
<md:BillingBlockOrder>1</md:BillingBlockOrder>
</md:Job>
<md:Name>
<md:DisplayName language="en-US">Vice Ganda</md:DisplayName>
</md:Name>
</md:People>
</Root>
...and inserted into the XSLT I have:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soa="urn:telestream.net:soa:core" exclude-result-prefixes='soa' version="1.0">
<xsl:variable name="basicContentID"><xsl:value-of select="/soa:Label/soa:Parameter[#name='basicContentID']/text()"/></xsl:variable>
<xsl:variable name="movieTitle"><xsl:value-of select="/soa:Label/soa:Parameter[#name='movieTitle']/text()"/></xsl:variable>
<xsl:variable name="releaseYear"><xsl:value-of select="/soa:Label/soa:Parameter[#name='releaseYear']/text()"/></xsl:variable>
<xsl:variable name="releaseDate"><xsl:value-of select="/soa:Label/soa:Parameter[#name='releaseDate']/text()"/></xsl:variable>
<xsl:output omit-xml-declaration="yes" />
<xsl:template match="/">
<mdmec:CoreMetadata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:md="http://www.movielabs.com/schema/md/v2.6/md" xmlns:mdmec="http://www.movielabs.com/schema/mdmec/v2.6" xsi:schemaLocation="http://www.movielabs.com/schema/mdmec/v2.6/mdmec-v2.6.xsd">
<mdmec:Basic ContentID="{/soa:Label/soa:Parameter[#name='basicContentID']/text()}">
<md:LocalizedInfo language="{/soa:Label/soa:Parameter[#name='metadataLanguage']/text()}">
<md:TitleDisplayUnlimited><xsl:value-of select="$movieTitle"/></md:TitleDisplayUnlimited>
</md:LocalizedInfo>
<xsl:template match="/Root">
<xsl:value-of select="normalize-space(.)" disable-output-escaping="yes" />
</xsl:template>
</mdmec:Basic>
<md:ReleaseYear><xsl:value-of select="$releaseYear"/></md:ReleaseYear>
<md:ReleaseDate><xsl:value-of select="$releaseDate"/></md:ReleaseDate>
</mdmec:CoreMetadata>
</xsl:template>
</xsl:stylesheet>
It appears to have an error: 'xsl:template' cannot be a child of the 'mdmec:Basic' element
If you manage to pass the XML string into your XSL transformation as a parameter, you can output it with escaping disabled:
<xsl:stylesheet ...>
<xsl:param name="xmlstring"/>
...
</md:LocalizedInfo>
<!-- INSERT "People" Metadata XML STRING HERE -->
<xsl:value-of select="$xmlstring" disable-output-escaping="yes"/>
</mdmec:Basic>
...

Transform to output xml based on the value of an element in the input xml

I am trying to learn the basics of XSLT, but am stuck on a particular use case. What I want to achieve is to transform one xml file into another xml (I am using XSLT 2.0), but a condition is that the grouping of elements in the output xml is decided by the value of one particular element in the input xml.
I will try to exemplify my question through a made-up example.
Lets say this is an input xml:
<products>
<shoes>
<shoe>
<name>Ecco City</name>
<category>Urban</category>
</shoe>
<shoe>
<name>Timberland Forest</name>
<category>Wildlife</category>
</shoe>
<shoe>
<name>Asics Gel-Kayano</name>
<category>Running</category>
</shoe>
</shoes>
<clothes>
<shorts>
<name>North Face</name>
<category>Wildlife</category>
</shorts>
<shorts>
<name>Adidas Running Shorts</name>
<category>Running</category>
</shorts>
</clothes>
Based on the value of the category element I want to, for each product, list similar products, that is, other products having the same category in the input xml, like this:
<output>
<forSale>
<item>Asics Gel-Kayano</item>
<similarItem>Adidas Running Shorts</similarItem>
</forSale>
</output>
This doesn't seem to be a grouping problem as such. If I understand correctly, you want to do something like:
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:key name="product-by-category" match="*" use="category" />
<xsl:template match="/products">
<output>
<xsl:for-each select="*/*">
<forSale>
<item>
<xsl:value-of select="name" />
</item>
<xsl:for-each select="key('product-by-category', category) except .">
<similarItem>
<xsl:value-of select="name" />
</similarItem>
</xsl:for-each>
</forSale>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<forSale>
<item>Ecco City</item>
</forSale>
<forSale>
<item>Timberland Forest</item>
<similarItem>North Face</similarItem>
</forSale>
<forSale>
<item>Asics Gel-Kayano</item>
<similarItem>Adidas Running Shorts</similarItem>
</forSale>
<forSale>
<item>North Face</item>
<similarItem>Timberland Forest</similarItem>
</forSale>
<forSale>
<item>Adidas Running Shorts</item>
<similarItem>Asics Gel-Kayano</similarItem>
</forSale>
</output>

Transforming attribute values into xml element

Using xslt 1.0, I need to transform below input xml to output xml
--input xml
<Row>
<Column name="NUMBER" sqltype="int">123</Column>
<Column name="DEPT1" sqltype="int">A</Column>
<Column name="CUST_EMPTYPE" sqltype="int">1</Column>
<Column name="CUST_TIJD" sqltype="int">31</Column>
</Row>
--output xml
<EMPLOYEE xmlns="http://xmlns.oracle.com/Employee">
<NUMBER>123</NUMBER>
<DEPT1>IHC</DEPT1>
<CUST_EMPTYPE>1</LASTNAME>
<CUST_TIJD>31</FIRSTNAME>
</EMPLOYEE>
the Column names from input xml are not known at design time, the Column can grow..
Can anyone let me know how to achieve this?
Thank you very much,
Yes. It is quite simple. One minor difference is the deviation between your DEPT1 source and destination value (I really don't know where 'IHC' may have been coming from). I harmonized it in the following XSLT code which just sets the Column/#name nodes to new EMPLOYEE elements with the content of the old text() content:
<?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" />
<xsl:template match="/Row">
<EMPLOYEE xmlns="http:xmlns.oracle.com/Employee">
<xsl:for-each select="Column">
<xsl:element name="{#name}">
<xsl:value-of select="text()" />
</xsl:element>
</xsl:for-each>
</EMPLOYEE>
</xsl:template>
</xsl:stylesheet>
The result of this is:
<EMPLOYEE xmlns="http:xmlns.oracle.com/Employee">
<NUMBER>123</NUMBER>
<DEPT1>A</DEPT1>
<CUST_EMPTYPE>1</CUST_EMPTYPE>
<CUST_TIJD>31</CUST_TIJD>
</EMPLOYEE>

XSL query inner xml

Say this is my xml :
<History>
<University>TSU</University>
<Payload>
<Attrib Order="0">OVERSEA</Attrib>
<Attrib Order="1">GRADE2</Attrib>
<Attrib Order="2"><Person><ID>TQR344</ID></Person></Attrib>
<Attrib Order="3">3566644</Attrib>
</Payload>
</History>
And I want to query the inner XML inside Order=2 tag and read ID of the person.
I have created this so far :
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="xml" encoding="UTF-8" indent="yes" omit-xml-declaration="no" />
<xsl:template match="/History">
<xsl:apply-templates select="/History" />
</xsl:template>
<xsl:template name="Person" match="//History">
<Event>
<Uni><xsl:value-of select="University" /></Uni>
<ID><xsl:value-of select="Payload/Attrib[#Order='2']/Person/ID" disable-output-escaping="yes" /></ID>
</Event>
</xsl:template>
</xsl:stylesheet>
But as you can see it is not working.
Also I assigned the inner XML into a variable and tried to query that variable and It didn't work too.
Is it possible to do that via xsl ?
Limitations : I cannot change xml format. But maybe I was able to move from xsl ver 1 to new versions.
I want to query the inner XML inside Order=2 tag
The tag in question does not contain any XML; its content is a string and needs to be manipulated using string functions. Try:
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:template match="/History">
<Event>
<Uni>
<xsl:value-of select="University" />
</Uni>
<ID>
<xsl:value-of select="substring-before(substring-after(Payload/Attrib[#Order='2'], '<ID>'),'</ID><')"/>
</ID>
</Event>
</xsl:template>
</xsl:stylesheet>
Note:
1. This:
<xsl:template match="/History">
<xsl:apply-templates select="/History" />
</xsl:template>
creates an infinite loop and will crash your processor.
2. Alternatively, you could serialize the string back into XML and process the result as XML; in XSLT 1.0, this can be done only by outputting the string with the escaping disabled, saving the result as a new document, then processing the new document with another XSLT stylesheet. Using XSLT 3.0 (or a processor that supports serializing as an extension) this can be all done during the same transformation.

exclude empty elements

in my sample xml file, i have this:
<AAA mandatory = "true"> good </AAA>
<BBB mandatory = "true"></BBB>
<CCC />
in the resulting xml, the result should be like this:
<AAA> good </AAA>
<BBB></BBB>
what should i put in my transformation file xslt to produce this xml?
currently, i have this:
<xsl:template match="node()[(#mandatory='true' or (following-sibling::*[#mandatory='true' and string-length(normalize-space(.)) > 0] or preceding-sibling::*[#mandatory='true' and string-length(normalize-space(.)) > 0])) or descendant-or-self::*[string-length(normalize-space(.)) > 0]]">
but this keeps displaying
<CCC />
When I run your XSLT on the input XML I do not get any output.
Your provided XML is not well formed and the XPATH in your "match" is too complicated I think.
I came up with a XSL 1.0 solution but I do not know if you can use that in XSL 2.0. I do not have experience with XSL 2.0.
This XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<list>
<xsl:apply-templates/>
</list>
</xsl:template>
<xsl:template match="*[#mandatory='true']">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
applied to this input XML:
<?xml version="1.0" encoding="UTF-8"?>
<list>
<AAA mandatory="true"> good </AAA>
<BBB mandatory="true"/>
<CCC/>
</list>
gives this output XML:
<?xml version="1.0" encoding="UTF-8"?>
<list>
<AAA> good </AAA>
<BBB/>
</list>
I am not sure if you also want to check on the text length of an element or only on the attribute mandatory. I only check on the attribute in my XSL.