I need to select Property1, and SubProperty2 and strip out any other properties. I need to make this future proof so that any new properties added to the xml won't break validation. iow's new fields have to be stripped by default.
<Root>
<Property1/>
<Property2/>
<Thing>
<SubProperty1/>
<SubProperty2/>
</Thing>
<VariousProperties/>
</Root>
so in my xslt I did this:
<xsl:template match="Property1">
<Property1>
<xsl:apply-templates/>
</Property1>
</xsl:template>
<xsl:template match="/Thing">
<SubProperty1>
<xsl:apply-templates select="SubProperty1" />
</SubProperty1>
</xsl:template>
<xsl:template match="*" />
The last line should strip anything I haven't defined to be selected.
This works to select my property1 but it always selects an empty node for SubProperty. The match on * seems to strip out the deeper object before my match on them can work.
I removed the match on * and it select my SubProperty with a value. So, how can I select the sub properties and still strip everything away that I am not using.
Thanks for any advise.
There are two problems:
<xsl:template match="*"/>
This ignores any element for which there isn't an overriding, more specific template.
Because there isn't a specific template for the top element Root it is ignored together with all of its subtree -- which is the complete document -- no output at all is produced.
The second problem is here:
<xsl:template match="/Thing">
This template matches the top element named Thing.
However in the provided document the top element is named Root. Therefore the above template doesn't match any node from the provided XML document and is never selected for execution. As the code inside its body is supposed to generate SubProperty1, no such output is generated.
Solution:
Change
<xsl:template match="*"/>
to:
<xsl:template match="text()"/>
And change
<xsl:template match="/Thing">
to
<xsl:template match="Thing">
The whole transformation becomes:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="Property1">
<Property1>
<xsl:apply-templates/>
</Property1>
</xsl:template>
<xsl:template match="Thing">
<SubProperty1>
<xsl:apply-templates select="SubProperty1" />
</SubProperty1>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
And when applied on the following XML document (as the provided is severely malformed it had to be fixed):
<Root>
<Property1/>
<Property2/>
<Thing>
<SubProperty1/>
<SubProperty2/>
</Thing>
<VariousProperties/>
</Root>
the result now is what is wanted:
<Property1/>
<SubProperty1/>
Related
Say I have XML data like this:
<root>
<subs>
<sub>
<values>
<value attribute="a">1</value>
<value attribute="a">2</value>
<value attribute="c">3</value>
<value attribute="c">4</value>
</values>
</sub>
<subOther>
<otherValues attribute="c">
<otherValue attribute="a">1</value>
<otherValue attribute="a">2</value>
<otherValue attribute="b">3</value>
<otherValue attribute="a">4</value>
</otherValues>
</subOther>
</subs>
</root>
I am trying to create an XSLT template that matches all the nodes in the path to /root/subs/subOther/otherValues/otherValue[attribute="b"].
So far, this is the closest I have gotten:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<!--IDENTITY TEMPLATE -->
<xsl:template match="#*|node()">
<xsl:apply-templates select="node()" />
</xsl:template>
<xsl:template match="//*[ancestor-or-self::[#attribute='b']]">
<xsl:copy>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
But that throws an error saying there is an unexpected token [. I have tried several combinations but they either don't match anything at all, match too much (i.e. everything), or they throw some sort of error.
Edit: I updated the example and expected to be a little more clear. Also note that this is a highly-simplified XML. In my actual file the attribute in question can be at any leaf node on any valid element for that level, so I have to use a more generic path using * and unknown paths with //. So, for instance, one of the value elements could be the one with attribute="b" and it would trigger the same result.
Edit 2: The expected result is to select the nodes that have a path that lead to any left-child w/ an attribute that is equal to a specific value. In my XSD schema there's a total of about 100 possible leaf nodes spread all over the place. The use case is that the attribute in question marks which data elements have had changes, and I need to basically create a "diff" where the full file is whittled down to only nodes where the results are only those items that have changed and their parents. In the small example above, attrubute="b" is the indication I need to copy that node, and thus I would expect this exact result:
<root> <!-- Copied because part of the path -->
<subs> <!-- Copied because part of the path -->
<sub> <!-- Copied because part of the path -->
<values> <!-- Copied because part of the path -->
<value attribute="b">3</value> <!-- Copied because it matches the attribute -->
</values>
</sub>
</subs>
</root>
I hope that makes better sense. Also, I fixed the typo on the xsl:stylesheet being self-closing.
It looks like you have changed the identity template to ignore elements (the change will also drop attributes and text nodes), and added a template to copy the elements you need.
I think you need to reverse your logic. Instead of thinking about things you want to copy, think of it as removing things you don't want to copy.
So, you have the identity template to do the generic copying of elements, and have a second template to remove the things you don't want (the elements which don't have a "b" attribute either on its self or its descendants).
Try this XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<!--IDENTITY TEMPLATE -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(descendant-or-self::*[#attribute = 'b'])]" />
</xsl:stylesheet>
See it in action at http://xsltfiddle.liberty-development.net/ncntCS6
I have the following problem. I have an xls stylesheet to add the xsi:nil="true" null attribute to blank nodes in my xml data. However if a node already has an attribute <test s:id"121"/> the xls is deleting it and overwriting w/ <test xsi:nil="true"/> thinking it's blank. I don't want this behavior, I only want the xsi:nil="true" added if the node is truly blank <test/> w/ no attribute. Can someone help me modify the following xls stylesheet w/ the proper conditional statement to skip over nodes like this <test s:id"121"/> that have an attribute present? The attribute may vary, I just need to ignore the null attribute insert if any type of attribute already exists.
Would it also be possible to change the xsl below to also look for blank nodes like this <test></test> and turn it into <test xsi:nil="true"\> this. Right now it only works to add the null attribute if it's in this format <test/> It would be nice to work both ways. Tnx.
XLS example that overwrites existing node attributes:
<xsl:stylesheet version="1.0"`
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(text())]">
<xsl:copy>
<xsl:attribute name="xsi:nil">true</xsl:attribute>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You just need to add a check for attributes in your template match too...
<xsl:template match="*[not(text()) and not(#*)]">
<xsl:copy>
<xsl:attribute name="xsi:nil">true</xsl:attribute>
</xsl:copy>
</xsl:template>
You might want to be precise as to your definition of empty. For example, with this current template match, the a element would be considered empty in the following XML, as a has no child text nodes or attributes.
<a><b>Hello</b></a>
So, perhaps the template match should be like this....?
<xsl:template match="*[not(text()) and not(*) and not(#*)]">
Info
Why is there different behaviour of Recursive Descent operator between
● template's match attribute where it is ignored and only children are selected ignoring their descendants
● for-each's select attribute where it works properly
Two examples of test.xsl are given which both operate on the following test.xml.
test.xml
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<people>
<person id="(1)">
<name>Lucy</name>
</person>
<class>
<person id="(2)">
<name>David</name>
<person id="(21)">
<name>David</name>
</person>
</person>
</class>
</people>
match="//person"
In this example we are trying to use match="//person" to select ALL
person elements from document which doesn't work. Instead of
selecting ALL root descendants person elements, person elements which
are inside other person elements (like id="(21)") are not
included.
test.xsl
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="text()"/>
<xsl:template match="//person">
<xsl:value-of select="#id"/>
</xsl:template>
</xsl:stylesheet>
output.xml
(1)(2)
select="//person"
In this example we are using select="//person" to select ALL person
elements from the document. This will properly select ALL root
descendants person elements including id="(21)". Value of
match="class" is irrelevant since select="//person" uses absolute
path.
test.xsl
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="text()"/>
<xsl:template match="class">
<xsl:for-each select="//person">
<xsl:value-of select="#id"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
output.xml
(1)(2)(21)
There is a difference between a match pattern and a select expression. A match pattern does not select anything - it is only used to see if the current node matches the pattern. For this reason, the match pattern match="//person" is the same as match="person".
If a node never gets to be the current node (i.e. templates are not applied to it), then it doesn't matter if it matches any template's match pattern or not. This is shown in your first example: the built-in template rules are applied recursively until the <person id="(2)"> node is encountered; this matches a pattern of a more specific template - but that template has no instructions to apply templates to any of its descendants, so the chain breaks at this point, and the node <person id="(21)"> is never examined to see if there is a template matching it.
Well a template with a match on its own does not cause any processing and your template with
<xsl:template match="//person">
<xsl:value-of select="#id"/>
</xsl:template>
outputs the id attribute and does no further processing. So you will need to make sure you have an apply-templates e.g.
<xsl:template match="//person">
<xsl:value-of select="#id"/>
<xsl:apply-templates/>
</xsl:template>
or you will need a template like
<xsl:template match="/">
<xsl:apply-templates select="//person"/>
</xsl:template>
that makes sure all person elements are processed.
Also note that a match="//person" is the same as match="person" so all you need is
<xsl:template match="/">
<xsl:apply-templates select="//person"/>
</xsl:template>
and
<xsl:template match="person">
<xsl:value-of select="#id"/>
</xsl:template>
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>
I need to modify on-the-fly the "content" of all the "a" tags present in a specific div (#navigation).
Is there a diazo rule or xslt pattern?
Thank's
Vito
The following demonstrates adding an attribute (in this case target) to each of the a tags that are child elements under an element with the id of navigation (so corresponding to #navigation in CSS). All content and other attributes from the original tags are maintained (although order may not be - though this shouldn't be an issue).
<?xml version="1.0" encoding="UTF-8"?>
<rules xmlns="http://namespaces.plone.org/diazo"
xmlns:css="http://namespaces.plone.org/diazo/css"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<after css:theme="#target" css:content="#navigation" />
<xsl:template match="*[#id='navigation']//a">
<xsl:copy>
<xsl:attribute name="target">_blank</xsl:attribute>
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
</rules>
Adjust the match condition accordingly with additional conditions if you want to match specific a tags. The xsl:template will be executed after all standard Diazo rules, so ensure that you adjust the match condition accordingly if you happen to change the structure of where the a tags are in your resulting document.
This was extended an example in the official Diazo documentation at http://docs.diazo.org/en/latest/recipes/adding-an-attribute/index.html
Not sure what you mean.
If you want to create an XSLT that copies everything but tweak only the "a" elements inside the div id='navigation' you should do something like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
<xsl:template match="#*|node()" priority="-1">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//div[#id='navigation']//a">
<a>
<xsl:attribute name='href'>
<xsl:value-of select='#href' />
</xsl:attribute>
<!-- Change your content here -->
</a>
</xsl:template>
</xsl:stylesheet>