As per http://xsltfiddle.liberty-development.net/jxNakA5/1, I am missing the following descendant node in the output:
<test-child>123</test-child>
Any idea what can be wrong here?
Source XML
?xml version="1.0" encoding="UTF-8"?>
<library>
<content content-id="a">
<test>
<test-child>123</test-child>
</test>
<data xml:lang="x-default">{"product" : 123 }</data>
<content-links>
<content-link content-id="a1"/>
<content-link content-id="a2"/>
</content-links>
</content>
<content content-id="b">
<data xml:lang="x-default">{"product" : 123 }</data>
<content-links>
<content-link content-id="b1"/>
<content-link content-id="b2"/>
</content-links>
</content>
<content content-id="a1">
<data xml:lang="x-default">{"product" : 123 }</data>
<content-links>
<content-link content-id="a11"/>
<content-link content-id="a12"/>
</content-links>
</content>
<content content-id="a2">
<data xml:lang="x-default">{"product" : 123 }</data>
<content-links>
<content-link content-id="a21"/>
<content-link content-id="a22"/>
</content-links>
</content>
<content content-id="a11">
<data xml:lang="x-default">{"product" : 123 }</data>
</content>
<content content-id="a12"/>
<content content-id="a21">
<data xml:lang="x-default">{"product" : 123 }</data>
</content>
</library>
XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:function name="mf:get-related-elements" as="element()*">
<xsl:param name="element" as="element()"/>
<xsl:sequence
select="$element ! (. | * | key('ref', content-links/content-link/#content-id)/mf:get-related-elements(.))"/>
</xsl:function>
<xsl:key name="ref" match="/*//*" use="#content-id"/>
<xsl:param name="cid" select="'a'" />
<xsl:variable name="start" select="key('ref', $cid)"/>
<xsl:variable name="related-elements" select="mf:get-related-elements($start)"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="/*//*[not(. intersect $related-elements)]"/>
</xsl:stylesheet>
See whether using
<xsl:sequence
select="$element ! (. | .//* | key('ref', content-links/content-link/#content-id)/mf:get-related-elements(.))"/>
works for your latest requirement.
But it is hard to tell whether the original approach patched each time you add a requirement is the right way to go unless you start describing with some sentences what the criteria are to copy and not to copy nodes through to the output.
Well, it's pretty clear why it isn't being copied: it's not a "related element".
I don't know what you're trying to achieve (you haven't told us), but your code is treating children as related, but not deeper descendants. The function follows the relationship through #content-id recursively, but it doesn't recurse when selecting the children of the supplied element, so only the first-level children are selected.
Related
I am looking to shorten my XSLT codebase by seeing if XSLT can increase a text number for each match. The text number exists in both the attribute value "label-period0" and the "xls:value-of" value.
The code works, no errors so this is more a question of how to shorten the code and make use of some sort of iteration on a specific character in a string.
I added 2 similar code structures for "period0" and "period1" to better see what exactly are the needed changes in terms of the digit in the text strings.
Source XML file:
<data>
<periods>
<period0><from>2016-01-01</from><to>2016-12-01</to></period0>
<period1><from>2015-01-01</from><to>2015-12-01</to></period1>
<period2><from>2014-01-01</from><to>2014-12-01</to></period2>
<period3><from>2013-01-01</from><to>2013-12-01</to></period3>
</periods>
<balances>
<balance0><instant>2016-12-31</instant></balance0>
<balance1><instant>2015-12-31</instant></balance1>
<balance2><instant>2014-12-31</instant></balance2>
<balance3><instant>2013-12-31</instant></balance3>
</balances>
</data>
XSL file:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform
version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="xml" indent="yes"/>
<!-- Block all data that has no user defined template -->
<xsl:mode on-no-match="shallow-skip"/>
<xsl:template match="data">
<results>
<periods>
<periods label="period0">
<xsl:value-of
select =
"concat(periods/period0/from, '--', periods/period0/to)"
/>
</periods>
<periods label="period1">
<xsl:value-of
select =
"concat(periods/period1/from, '--', periods/period1/to)"
/>
</periods>
<!-- Etc for period [2 and 3]-->
</periods>
<balances>
<balance label="balance0">
<xsl:value-of select ="balances/balance0/instant"/>
</balance>
<!-- Etc for balance [1,2 and 3] -->
</balances>
</results>
</xsl:template>
</xsl:transform>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<results>
<periods>
<periods label="period0">2016-01-01--2016-12-01</periods>
<periods label="period1">2015-01-01--2015-12-01</periods>
</periods>
<balances>
<balance label="balance0">2016-12-31</balance>
</balances>
</results>
Wanted result:
(with an XSL that steps the digit in the text string, or any other logics in XSL that could cater for manipulating the digit in text string)
<?xml version="1.0" encoding="UTF-8"?>
<results>
<periods>
<periods label="period0">2016-01-01--2016-12-01</periods>
<periods label="period1">2015-01-01--2015-12-01</periods>
<periods label="period2">2014-01-01--2015-12-01</periods>
<periods label="period3">2013-01-01--2015-12-01</periods>
</periods>
<balances>
<balance label="balance0">2016-12-31</balance>
<balance label="balance1">2015-12-31</balance>
<balance label="balance2">2014-12-31</balance>
<balance label="balance3">2013-12-31</balance>
</balances>
</results>
Couldn't you do simply something like:
<xsl:template match="/data">
<results>
<periods>
<xsl:for-each select="periods/*">
<periods label="{name()}">
<xsl:value-of select="from"/>
<xsl:text>--</xsl:text>
<xsl:value-of select="to"/>
</periods>
</xsl:for-each>
</periods>
<balances>
<xsl:for-each select="balances/*">
<balance label="{name()}">
<xsl:value-of select="instant"/>
</balance>
</xsl:for-each>
</balances>
</results>
</xsl:template>
If you want to do your own numbering, you can change:
<periods label="{name()}">
to:
<periods label="period{position() - 1}">
I have an XSLT that looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no" encoding="utf-8" media-type="text/plain" />
<xsl:template match="/SOME/NODE">
<xsl:if test="./BLAH[foo]">
<xsl:value-of select="concat(#id, ',' , ./BLAH/bar/#id, ',' , ./blorb/text())"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The output looks something like this (it is going to be a CSV file):
1,2,3
4,456,22
90,5,some text
365,16,soasdkjasdjkasdf
9,43,more text
What I need is for it to be transformed into:
1,2,3
4,456,22
90,5,some text
365,16,soasdkjasdjkasdf
9,43,more text
The main problems are the blank lines (from nodes that do not match the IF condition) and the indentation. Is there any way to remove the blank lines and trim the indentation while preserving the line breaks after lines that are not blank?
I've tried using <xsl:strip-space elements="*"/>, but then the output looks like this:
1,2,3,4,456,22,90,5,some text,365,16,soasdkjasdjkasdf,9,43,more text
Which doesn't work since I need to have 3 values on each line.
As requested, a (heavily simplified) sample of the input:
<SOME>
<NODE>
<BLAH id="1">
<foo>The Foo</foo>
<bar id="2" />
<blorb> some text </blorb>
</BLAH>
</NODE>
<NODE>
<BLAH id="3">
<bar id="4" />
<blorb>some text that shouldn't be in output because there's no foo here</blorb>
</BLAH>
</NODE>
<NODE>
<BLAH id="5">
<foo>another Foo</foo>
<bar id="6" />
<blorb>some other text</blorb>
</BLAH>
</NODE>
</SOME>
I would suggest you approach it this way:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8" />
<xsl:template match="/SOME">
<xsl:for-each select="NODE/BLAH[foo]">
<xsl:value-of select="#id"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="bar/#id"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="blorb"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I have source xml looking like this :
<Data>
<ActionPlaces>
<ActionPlace>
<ActionPlaceID>74</ActionPlaceID>
<PlaceName>Theatre Of Classic</PlaceName>
</ActionPlace>
</ActionPlaces>
<Actions>
<CommonAction Id="2075" Name="King">
<Action>
<ActionID>4706</ActionID>
<ActionPlaceID>74</ActionPlaceID>
</Action>
</CommonAction>
</Actions>
</Data>
Which is to transform to this:
<category name="King">
<name>King</name>
<parent name="Theatre Of Classic" />
</category>
I want to use variable :
<xsl:template match="ActionPlaces">
<xsl:variable name="id" select="/ActionPlace/ActionPlaceID"/>
<xsl:template match="CommonAction" >
<category name="<xsl:value-of select="#name"/> >
<name><xsl:value-of select="#name"/></name>
<parent <xsl:if test="/Action/ActionPlaceID = $id">
name=/Action/ActionPlaceID/> <- how to get name of theatre here?
</xsl:template>
Can variable store not only id but name also? And how to get it? What is the most common approach to handle this ?
Here's one option using XSL keys (as #michael-hor257k suggested):
Input
<Root>
<ActionPlaces>
<ActionPlace>
<ActionPlaceID>74</ActionPlaceID>
<PlaceName>Theatre Of Classic</PlaceName>
</ActionPlace>
</ActionPlaces>
<Actions>
<CommonAction Id="2075" Name="King">
<Action>
<ActionID>4706</ActionID>
<ActionPlaceID>74</ActionPlaceID>
</Action>
</CommonAction>
</Actions>
</Root>
Stylesheet
<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"/>
<!-- Collect all <ActionPlace> elements into an XSL key -->
<xsl:key name="ActionPlaceById" match="ActionPlace" use="ActionPlaceID"/>
<xsl:template match="/">
<xsl:apply-templates select="Root/Actions/CommonAction"/>
</xsl:template>
<xsl:template match="CommonAction">
<category name="{#Name}">
<name>
<xsl:value-of select="#Name"/>
</name>
<!--
Using the ActionPlaceById key we created earlier, fetch the <ActionPlace>
element that has an <ActionPlaceID> child that has the same value as the
<ActionPlaceID> descendant of the current <CommonAction> element.
-->
<parent name="{key('ActionPlaceById', Action/ActionPlaceID)/PlaceName}"/>
</category>
</xsl:template>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="utf-8"?>
<category name="King">
<name>King</name>
<parent name="Theatre Of Classic"/>
</category>
I have to copy data of node element from file1.xml to file2.xml.
file1.xml
<?xml version="1.0" encoding="utf-8" ?>
<root>
<header>
<AsofDate>31-Dec-2012</AsofDate>
<FundName>This is Sample Fund</FundName>
<Description>This is test description</Description>
</header>
</root>
file2.xml
<?xml version="1.0" encoding="utf-8" ?>
<root id="1">
<header id="2">
<AsofDate id="3"/>
<FundName id="4" />
<Description id="5" />
</header>
</root>
after merging file1.xml into file2.xml, result should look below:
<?xml version="1.0" encoding="utf-8" ?>
<root id="1">
<header id="2">
<AsofDate id="3">31-Dec-2012</AsofDate>
<FundName id="4">This is Sample Fund</FundName>
<Description id="5">This is test description</Description>
</header>
</root>
I am using below XSLT to transform file.
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Below is the code is used to perform transformation:
XslCompiledTransform tf = new XslCompiledTransform();
tf.Load("TranFile.xsl");
tf.Transform("file1.xml", "file2.xml");
but above code is overwriting the file2 content with file1.xml content. This is just sample XML. In real case we don't know name of nodes and hierarchy of the xml file. But whatever structure would be will be same for both file and scenario will be exactly same. I am new to XSLT and not sure is this right approach to accomplish the result. Is it really possible to achieve result through XSLT.
The solution that I post is written having in mind the following:
The only thing that need to be merged are the attributes. Text and element nodes are copied as they appear from file1.xml.
The #id attributes are not numbered sequentially in file2.xml so the #id's in file2.xml could be (for example) 121 432 233 12 944 instead of 1 2 3 4 5. If the case is the latter then you would not need file2.xml to generate the desired output.
The document() function can be used to access files different than the current one. If XslCompiledTransform is giving an error when using the document function I would suggest to follow this using document() function in .NET XSLT generates error . I am using a different XSLT processor (xsltproc) and it works fine.
This solution is based on keeping a reference to the external file, so each time that we process an element in file1.xml the reference is moved to point at the same element in file2.xml. This can be done because according to the problem, both files present the same element hierarchy.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="no"/>
<!-- Match the document node as an entry point for matching the files -->
<xsl:template match="/">
<xsl:apply-templates select="node()">
<xsl:with-param name="doc-context" select="document('file2.xml')/node()" />
</xsl:apply-templates>
</xsl:template>
<!-- In this template we copy the elements and text nodes from file1.xml and
we merge the attributes from file2.xml with the attributes in file1.xml -->
<xsl:template match="node()">
<!-- We use this parameter to keep track of where we are in file2.xml by
mimicking the operations that we do in the current file. So we are at
the same position in both files at the same time. -->
<xsl:param name="doc-context" />
<!-- Obtain current position in file1.xml so we know where to look in file2.xml -->
<xsl:variable name="position" select="position()" />
<!-- Copy the element node from the current file (file1.xml) -->
<xsl:copy>
<!-- Merge attributes from file1.xml with attributes from file2.xml -->
<xsl:copy-of select="#*|$doc-context[position() = $position]/#*" />
<!-- Copy text nodes and process children -->
<xsl:apply-templates select="node()">
<xsl:with-param name="doc-context" select="$doc-context/node()" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
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>