Xslt - Move node to one "level" up - xslt

I want an xml attribute one level up in an xml structure.
As for requested I show a more detailed example:
<items>
<kitchen>
<furnitures>
<chairs type="wood">
<chair_1 color="green" legs="4"/>
</chairs>
<tables type="stone">
</tables>
</furnitures>
</kitchen>
</items>
And I want to output this:
<items>
<kitchen>
<furnitures>
<chairs type="wood"/>
<chair_1 color="green" legs="4"/>
<tables type="stone">
</tables>
</furnitures>
</kitchen>
</items>
As you see I move char_1 to under from under
<xsl:template match="node()">
<xls:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*"/>
<xsl:apply-templates select="text()">
</xsl:copy>
</xsl:template>
<xsl:template match="/items/kitchen/furnitures/chairs">
<xsl:choose>
<xsl:when test="chair_1">
<xsl:copy>
<xsl:apply-templates select="child::node()[not(self:chair_1)]|#*|text()"/>
</xsl:copy>
<xsl:apply-templates select="chair_1"/>
</xsl:when>
<!----- edit -->
<xsl:otherwise>
<xsl:copy>
<xls:apply-templates select ="#*|node()"/>
</xsl:copy>
<xsl:apply-templates select="settings"/>
<xsl:text>
</xsl:text>
<chair_1 color="green" legs="4"/>
</xls:otherwise>
</xls:choose>
</xsl:template>
So, my main problem is, that my copy don't contain line-breaks.
Please keep in mind, I am using PHP:Xsltproc, on my dev-comp the indentation works fine, but with PHP's xsltproc it isn't fine, and drops the line breaks.
so the output like this:
<items>
<kitchen>
<furnitures>
<chairs type="wood"/><chair_1 color="green" legs="4"/>
<tables type="stone">
</tables>
</furnitures>
</kitchen>
</items>
Which is fine, but not correctly indented.
(Disclaimer: there could be some typos, as this is not the original XML, and of course I am using the required stylsheet, version, phpversion, xml version tags, and of course my output method is xml and indent="yes" )
UPDATE:
when I have the second "WHEN", (in case there is no chair_1) I want to "paste" it into the code. But the indentation fails, it makes the whole copy into one line. What could be the problem?

The templates you present do not effect the transformation you say they do, at least not by themselves. In fact, they are not even valid XSL.
After the obvious syntax errors are corrected, the resulting template matching node() explicitly rearranges whitespace around elements (where it is not stripped) and does nothing effective at preserving attributes. You seem to intend for it to be an identity transform, but the conventional identity transform goes like this:
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
Observe in particular that the node() test matches both elements and text nodes (and comments and processing instructions), but not attributes, and that if you do not want to rearrange an element's text nodes relative to its child elements then you must transform them all via the same xsl:apply-templates directive.
Additionally, do note that in many XML applications, whitespace-only runs of text separating tags are insignificant. I don't see any reason to think that your particular application is among the exceptions, so you really ought to ask yourself is "does it matter?"
Supposing that it does matter -- e.g. because you want improved human readability even though the XML is primarily meant to be consumed by a computer program that doesn't care about the indentation -- you should consider letting your XSL processor provide indentation for you. To do this, start by stripping all the insignificant whitespace from the input document:
<xsl:strip-space elements="*"/>
and follow up by asking the processor to provide hierarchical indentation for you:
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
Both of those directives have global effect, and they need to appear as direct children of the xsl:stylesheet or xsl:transform element. Here's the cleaned up and updated version:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/items/kitchen/furnitures/chairs">
<xsl:choose>
<xsl:when test="chair_1">
<xsl:copy>
<!-- also simplified the 'select' expression below: -->
<xsl:apply-templates select="node()[not(self::chair_1)]|#*"/>
</xsl:copy>
<xsl:apply-templates select="chair_1"/>
</xsl:when>
<xsl:otherwise>
BUNCH of code there if we don't have chair_1
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Related

XSL Sort specifc value last

Is it possible to sort nodes as follows:
Example XML
<record>
<id>0</id>
<sku>0</sku>
<name>Title</name>
<prop>456</prop>
<number>99</number>
</record>
If I apply this template
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="record/*">
<xsl:param select="." name="value"/>
<div>
<xsl:value-of select="concat(local-name(), ' - ', $value)"/>
</div>
</xsl:template>
</xsl:stylesheet>
Ouput:
<div>id - 0</div>
<div>sku - 0</div>
<div>name - Title</div>
<div>prop - 456</div>
<div>number - 99</div>
However, I would like all 0 values to be outputted last, as so:
<div>name - Title</div>
<div>prop - 456</div>
<div>number - 99</div>
<div>id - 0</div>
<div>sku - 0</div>
Is this possible by applying a sort to the <xsl:apply-templates/>?
There is an easy way of achieving this with XSLT-1.0. Just use a predicate on xsl:apply-templates checking if the content is zero:
<xsl:template match="record/*">
<div>
<xsl:value-of select="concat(local-name(), ' - ', .)"/>
</div>
</xsl:template>
<xsl:template match="/record">
<xsl:apply-templates select="*[normalize-space(.) != '0']" />
<xsl:apply-templates select="*[normalize-space(.) = '0']" />
</xsl:template>
This does not sort the output, but groups it the way you want it. The xsl:param is unnecessary.
As far as I see your question, the issue is not any sort at all.
You even didn't write what is the specific value, which you mentioned
it the title. It is rather an arbitrary sequence of child elements of record.
Try the following script, performing just that:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="record">
<xsl:copy>
<xsl:apply-templates select="name, prop, number, id, sku"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record/*">
<div><xsl:value-of select="concat(local-name(), ' - ', .)"/></div>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>
I used XSLT 2.0, because initially you did't specify the XSLT version.
Could you move on to version 2.0? As you can see, it allows to write quite
an elegent solution (impossible in version 1.0).
I also changed your template matching record/*. You actually don't
need any param. It is enough to use . - the value of the current
element.
Edit
Another possibility is that you want the following sort:
First, elements with non-numeric value (in your case, only name),
maybe without any sort.
Then elements with numeric value, sorted descending on this value.
If this is the case, then change the template matching record to the
following:
<xsl:template match="record">
<xsl:copy>
<xsl:apply-templates select="*[not(. castable as xs:integer)]"/>
<xsl:apply-templates select="*[. castable as xs:integer]">
<xsl:sort select="." order="descending" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
And add:
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
to transform tag.
But I still can't see anything, which can be called the specific value.

How do I match the elements generated by xsl-fo?

I have a .xsl file like this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:exslt="http://exslt.org/common">
<xsl:template match="/>
<fo:root>
<fo:block>...</fo:block>
</fo:root>
</xsl:template>
</xsl:stylesheet>
How can I use templates to match and style the generated fo elements? For example, if I want to give my fo:table-cells red backgrounds, I'd like to be able to do
<xsl:template match="fo:table-cell">
<xsl:attribute name="background-color">red</xsl:attribute>
</xsl:template>
I found this and then tried something along the lines of
<xsl:template match="/>
<xsl:variable name="foRoot">
<fo:root>
<fo:block>...</fo:block>
</fo:root>
</xsl:variable>
<xsl:apply-templates select="exslt:node-set($foRoot)" />
</xsl:template>
but this results in a stack overflow due to endless recursion. When I try to avoid this, for example by doing
<xsl:apply-templates select="exslt:node-set($foRoot)/*" />
I get an empty document. When trying to fix that by adding
<xsl:copy-of select="$foRoot" />
right after, I don't get any errors but the table-cells still have a default white background.
If you really use an XSLT 2 processor then first of all you don't need exsl:node-set.
As for your template
<xsl:template match="fo:table-cell">
<xsl:attribute name="background-color">red</xsl:attribute>
</xsl:template>
that would match a FO table-cell but transform it into an attribute. So you rather want
<xsl:template match="fo:table-cell">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="background-color">red</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
as that adds the attribute to a shallow copy of the element and then keeps processing alive for child elements with apply-templates.
Of course you will also need to add the identity transformation template
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
to make sure the elements you don't want to change are copied through. It might be necessary to use modes to separate processing steps if the other templates you have interfere with the identity transformation.

Is it possible to make xsl:template sensitive to the pushed-from node without using xsl:param?

I'm pretty sure the answer to this is no, but since the only alternative is what I deem inelegant code, I thought I'd throw this out and see if I'm missing something while hoping this hasn't been asked.
Given this source XML:
<root>
<p>Hello world</p>
<move elem="content" item="test"/>
<p>Another text node.</p>
<content item="test">I can't <b>figure</b> this out.</content>
</root>
I want this result:
<root>
<block>Hello world</block>
<newContent>I can't <hmmm>figure</hmmm> this out.</newContent>
<block>Another text node.</block>
</root>
An ordinary language description:
Replace <move .../> with the result of processing
the element whose name matches move's #elem attribute and whose #item
matches move's #item attribute (e.g., in this case the content of the element [<content>] is processed so <b> is replaced by <hmm>).
Prevent the element from step 1 from
being written out to the result tree in its original document order
The problem is the input XML document will be considerably more complex and variable. And the stylesheet is a third-party transform that I am extending. The template I'd have to copy in order to use a mode-based solution is pretty significant in size and that seems inelegant to me. I know, for example, this would work:
<xsl:template match="b">
<hmmm>
<xsl:apply-templates/>
</hmmm>
</xsl:template>
<xsl:template match="p">
<block>
<xsl:apply-templates/>
</block>
</xsl:template>
<xsl:template match="move">
<xsl:variable name="elem" select="#elem"/>
<xsl:variable name="item" select="#item"/>
<xsl:apply-templates select="//*[name()=$elem and #item=$item]" mode="copy-and-process"/>
</xsl:template>
<xsl:template match="content"/>
<xsl:template match="content" mode="copy-and-process">
<newContent><xsl:apply-templates/></newContent>
</xsl:template>
What I would like to do is have the <xsl:template> that matches "content" be sensitive to what node pushes to it. So, that I can have an <xsl:template match="content"/> that is only executed (and therefore its matching node and children are suppressed) when the node pushed from is <root> and not <move>. The virtue in this is that if the third-party stylesheet's relevant template is updated, I don't have to worry about updating a copy of the stylesheet that processes the <content> node. I'm pretty sure this isn't possible, but I thought it was worth asking about.
Simply do:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kMover" match="move" use="concat(#elem,'+',#item)"/>
<xsl:key name="kToMove" match="*" use="concat(name(),'+',#item)"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="move">
<newContent>
<xsl:apply-templates mode="move" select=
"key('kToMove', concat(#elem,'+',#item))/node()"/>
</newContent>
</xsl:template>
<xsl:template match="p">
<block><xsl:apply-templates/></block>
</xsl:template>
<xsl:template match="b" mode="move">
<hmmm><xsl:apply-templates/></hmmm>
</xsl:template>
<xsl:template match="*[key('kMover', concat(name(),'+',#item))]"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<root>
<p>Hello world</p>
<move elem="content" item="test"/>
<p>Another text node.</p>
<content item="test">I can't <b>figure</b> this out.</content>
</root>
the wanted, correct result is produced:
<root>
<block>Hello world</block>
<newContent>I can't <hmmm>figure</hmmm> this out.</newContent>
<block>Another text node.</block>
</root>

remove elements based on external file

I have an external setting file which has some nodes holiding attribute values of main xml document. I need to remove certian nodes from mian xml file if the attribute value is there in the setting file.
My setting file looks like this:
setting.xml
<xml>
<removenode titlename="abc" subtitlename="xyz"></removenode>
<removenode titlename="dvd" subtitlename="dvd"></removenode>
</xml>
Main.xml
<xml>
<title titlename="abc">
<subtitle subtitlename="xyz"></subtitle>
</title>
<title titlename="book">
<subtitle subtitlename="book sub title"></subtitle>
</title>
</xml>
Need a script which look for setting.xml file and remove the title element if titlename and subtitlename found in main.xml. The output should be
output.xml
<xml>
<title titlename="book">
<subtitle subtitlename="book sub title"></subtitle>
</title>
</xml>
I tried using document to read setting.xml file but not able to find how to do the match on main.xml file
<xsl:variable name="SuppressionSettings" select="document('Setting.xml')" />
<xsl:variable name="SuppressSetting" select="$SuppressionSettings/xml/removenode" />
.
Any hint how to implement it?
The key is to use an identity/copy pattern and, before each output, check the current (context) node isn't prohibited by the suppression rules nodeset.
<!-- get suppression settings -->
<xsl:variable name='suppression_settings' select="document('http://www.mitya.co.uk/xmlp/settings.xml')/xml/removenode" />
<!-- begin identity/copy -->
<xsl:template match="node()|#*">
<xsl:if test='not($suppression_settings[#titlename = current()/#titlename and #subtitlename = current()/subtitle/#subtitlename])'>
<xsl:copy>
<xsl:apply-templates select='node()|#*' />
</xsl:copy>
</xsl:if>
</xsl:template>
You can run it here (see output source - the 'abc' title node is omitted):
http://www.xmlplayground.com/9oCYKp
This XSLT indicated below works for the given document.
Note that I'm storing the contents of Setting.xml in a variable as you did, however, I'd then use that variable directly in my queries.
An important issue here is that in the match element of a template, variables cannot be used. Therefore, my template matches any <title> elements and then determines in an <xsl:choose> element whether the attributes match any values given in the settings file - if so, the <title> element will be omitted in the output.
As an explanation for why that test attribute in the <xsl:when> does what it should, imagine a comparison of someAttribute = someOtherAttribute not as a restriction that the attribute someAttribute must have the same value as the attribute someOtherAttribute, but rather as the condition that there must be any two attributes someAttribute and someOtherAttribute with the same value.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="SuppressionSettings" select="document('Setting.xml')" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//title">
<xsl:choose>
<xsl:when test="(#titlename = $SuppressionSettings/xml/removenode/#titlename) and (subtitle/#subtitlename = $SuppressionSettings/xml/removenode/#subtitlename)"/>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Here's a more generic answer where the names of the attributes are not hard coded into the XSLT. Like O. R. Mapper pointed out, in XSLT 1.0 you can't use variable references in the match, so I put the document() directly in the predicate. This may not be as efficient as using a variable and then testing the variable.
XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[#* = document('setting.xml')/*/removenode/#*]"/>
</xsl:stylesheet>
XML Output (using your 2 xml files with main.xml as the input)
<xml>
<title titlename="book">
<subtitle subtitlename="book sub title"/>
</title>
</xml>

Can an XSLT processor preserve empty CDATA sections?

I'm processing an XML document (an InstallAnywhere .iap_xml installer) before handing it off to another tool (InstallAnywhere itself) to update some values. However, it appears that the XSLT transform I am using is stripping CDATA sections (which appear to be significant to InstallAnywhere) from the document.
I'm using Ant 1.7.0, JDK 1.6.0_16, and a stylesheet based on the identity:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="UTF-8" cdata-section-elements="string" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Basically, "string" nodes that look like:
<string><![CDATA[]]></string>
are being processed into:
<string/>
From reading XSLT FAQs, I can see that what is happening is legal as far as the XSLT spec is concerned. Is there any way I can prevent this from happening and convince the XSLT processor to emit the CDATA section?
Found a solution:
<xsl:template match="string">
<xsl:element name="string">
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text><xsl:value-of select="text()" disable-output-escaping="yes" /><xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:element>
</xsl:template>
I also removed the cdata-section-elements attribute from the <xsl:output> element.
Basically, since the CDATA sections are significant to the next tool in the chain, I take output them manually.
To do this, you'll need to add a special case for empty string elements and use disable-output-escaping. I don't have a copy of Ant to test with, but the following template worked for me with libxml's xsltproc, which exhibits the same behavior you describe:
<?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" omit-xml-declaration="yes" cdata-section-elements="string"/>
<xsl:template match="string">
<xsl:choose>
<xsl:when test=". = ''">
<string>
<xsl:text disable-output-escaping="yes"><![CDATA[]]></xsl:text>
</string>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Input:
<input>
<string><![CDATA[foo]]></string>
<string><![CDATA[]]></string>
</input>
Output:
<input>
<string><![CDATA[foo]]></string>
<string><![CDATA[]]></string>
</input>
Once the XML parser has finished with the XML, there is absolutely no difference between <![CDATA[abc]]> and abc. And the same is true for an empty string - <![CDATA[]]> resolves to nothing at all, and is silently ignored. It has no representation in the XML model. In fact, there is no way to tell the difference from CDATA and regular strings, and neither has any representation in the XML model.
Sorry.
Now, why would you want this? Perhaps there is another solution which can help you?