This is a slightly version of other question posted here:
XSLT: change node inner text
Imagine i use XSLT to transform the document:
<a>
<b/>
<c/>
</a>
into this:
<a>
<b/>
<c/>
Hello world
</a>
In this case i can't use neither the
<xsl:strip-space elements="*"/>
element or the [normalize-space() != ''] predicate since there is no text in the place where i need to put new text. Any ideas? Thanks.
Here is what I would do:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<!-- identity template to copy everything unless otherwise noted -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- match the first text node directly following a <c> element -->
<xsl:template match="text()[preceding-sibling::node()[1][self::c]]">
<!-- ...and change its contents -->
<xsl:text>Hello world</xsl:text>
</xsl:template>
</xsl:stylesheet>
Note that text nodes contain "surrounding" whitespace - in the sample XML in the question the matched text node is whitespace only, which is why the above works. It will stop to work as soon as the input document looks like this:
<a><b/><c/></a>
because here is no text node following <c>. So if this is too brittle for your use case, an alternative would be:
<!-- <c> nodes get a new adjacent text node -->
<xsl:template match="c">
<xsl:copy-of select="." />
<xsl:text>Hello world</xsl:text>
</xsl:template>
<!-- make sure to remove the first text node directly following a <c> node-->
<xsl:template match="text()[preceding-sibling::node()[1][self::c]]" />
In any case, stuff like the above makes clear why intermixing of text nodes and element nodes is best avoided. This is not always possible (see XHTML). But when you have the chance and the XML is supposed to be purely a container for structural data, staying clear of mixed content makes your life easier.
This transformation inserts the desired text (for generality) after the element named a7:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a7">
<xsl:call-template name="identity"/>
<xsl:text>Hello world</xsl:text>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<a>
<a1/>
<a2/>
.....
<a7/>
<a8/>
</a>
the desired result is produced:
<a>
<a1/>
<a2/>
.....
<a7/>Hello world
<a8/>
</a>
Do note:
The use of the identity rule for copying every node of the source XML document.
The overriding of the identity rule by a specific template that carries out the insertion of the new text.
How the identity rule is both applied (on every node) and called by name (for a specific need).
edit: fixed my fail to put proper syntax in.
<xsl:template match='a'>
<xsl:copy-of select="." />
<xsl:text>Hello World</xsl:text>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
Related
<a>
<z/>
<b/>
<b/>
<b/>
<c/>
</a>
I want to find the count of 'b' within 'a' when my parsing current node is 'c' using XSLT.
Is it possible to do this using XSLT?
I am not aware of what the element name 'b' would be, i.e. for its preceding sibling.
If you are positioned on c tag, or whatever the element is actually called, then to get the count of the preceding siblings, you would do this...
<xsl:value-of select="count(preceding-sibling::*)" />
EDIT: In answer to your comment, if you don't want to count all siblings, but only the count of the immediately preceding one, and the ones with the same name before that, you could try this...
<xsl:value-of select="count(preceding-sibling::*[name() = name(current()/preceding-sibling::*[1])])" />
This would not work though in the case you had multiple c nodes under one parent...
<a>
<z/>
<b/>
<b/>
<b/>
<c/>
<z/>
<b/>
<c/>
</a>
In this case, you could define a key like this, to group elements by the unique id of the first following element with a different name:
<xsl:key name="keyc" match="*" use="generate-id(following-sibling::*[name() != name(current())][1])" />
Then you can get the count like so:
<xsl:value-of select="count(key('keyc', generate-id()))" />
Here are the three options in action....
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="keyc" match="*" use="generate-id(following-sibling::*[name() != name(current())][1])" />
<xsl:template match="c">
<c>
<example1>
<xsl:value-of select="count(preceding-sibling::*)" />
</example1>
<example2>
<xsl:value-of select="count(preceding-sibling::*[name() = name(current()/preceding-sibling::*[1])])" />
</example2>
<example3>
<xsl:value-of select="count(key('keyc', generate-id()))" />
</example3>
</c>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Use xsl:number. It prints out the numer of the current element, formatted as required.
There are various options concerning how to perform the numeration,
e.g. multi-level or alphabetic one.
Actually it is quite a powerful tool.
I want to find the count of 'b' within 'a' when my parsing current node is 'c'
Let me rephrase that:
you want to count all occurrences of <b> which are on the same level as <c>.
This XSLT does the job by calling an <xsl:template> with a parameter:
the local-name of the element to be counted (in this case 'b'):
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="//c"> <!-- select the desired target element -->
<xsl:call-template name="count">
<xsl:with-param name="elemName" select="'b'" /> <!-- name of the element -->
</xsl:call-template>
</xsl:template>
<xsl:template name="count"> <!-- count all matching elements before and after -->
<xsl:param name="elemName" />
<xsl:value-of select="count(preceding-sibling::*[local-name() = $elemName]) + count(following-sibling::*[local-name() = $elemName])" />
</xsl:template>
</xsl:stylesheet>
In the case of your example the output simply is:
3
I just wanna clear a element value if element value is a specific string value
Input xml
<A>
<B>
<C>BOLD</C>
</B>
</A>
Desired Output
<A>
<B>
<C/>
</B>
</A>
my xslt looks like following which doesn't work it just clears everything
<xsl:template match="A/B/C/text()">
<xsl:if test="text()='BOLD'">
<xsl:text></xsl:text>
</xsl:if>
</xsl:template>
<!--Copy the rest of the document as it is-->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
Please help thanks
Given you've started with an identity template there's really no need to use an explicit xsl:if, just put the condition in the match expression of an empty template:
<xsl:template match="A/B/C/text()[. = 'BOLD']"/>
Text nodes that are not under an A/B/C or whose content is not BOLD will be handled by the identity template and copied as normal.
The caveat with using text() in your match expressions is that it means the template is looking at only one text node at a time, and only at text that is directly inside the C element. If the C can have other content you will get some odd edge cases:
<C>BOLD<br/></C> --> <C><br/></C>
<C><b>B</b>OLD</C> --> unchanged
<C><b>BOLD</b></C> --> unchanged
If you want to treat the full text under the C as one unit regardless of child elements then you need something more like
<xsl:template match="C[. = 'BOLD']">
<C/>
</xsl:template>
without mentioning text(). This would clear all three of my examples above.
The logic in your first template isn't outputting anything if the text isn't "BOLD", so if the text is bold, its spits out nothing, otherwise it still spits out nothing. also, the text() node won't have its own text.
So, replace the first template it with this, and it should work fine.
<xsl:template match="A/B/C/text()">
<xsl:if test="not(.='BOLD')">
<xsl:copy/>
</xsl:if>
</xsl:template>
And applied to this:
<A>
<B>
<C>Bold</C>
<C>BOLD</C>
<C>Italic</C>
</B>
</A>
Gives this:
<A>
<B>
<C>Bold</C>
<C/>
<C>Italic</C>
</B>
</A>
Another way to fix this issue is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[text()='BOLD']">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
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 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>
Please, I'm trying to extract "plain text" from "annotated text" (or plain content from complex content).
This is the input XML I have:
<l>string</l>
<l>string<g><b/>string2</g></l>
<l>string<g><b/>string2</b>string3</g></l>
<l>string<b/>string2<b/>string3</l>
and this is the output I need:
<word>string</word>
<word>string1 string2</word>
<word>string1 string2 string3</word>
<word>string1 string2 string3</word>
Essentially: (i) I do not need the element and (ii) replace empty elements by blank spaces
Many thanks!
You could achieve this by making use of the identity transform, but overridding it with your special cases, like so:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="no"/>
<!-- Replace elements under root element with word element -->
<xsl:template match="/*/*">
<word>
<xsl:apply-templates select="node()"/>
</word>
</xsl:template>
<!-- Match, but don't copy, elements -->
<xsl:template match="#*|node()">
<xsl:apply-templates select="#*|node()"/>
</xsl:template>
<!-- Copy out text nodes -->
<xsl:template match="text()">
<xsl:copy/>
</xsl:template>
<!-- Replace empty element by space -->
<xsl:template match="*[not(node())]">
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>
When applied on the following XML
<data>
<l>string</l>
<l>string<g><b/>string2</g></l>
<l>string<g><b/>string2<b/>string3</g></l>
<l>string<b/>string2<b/>string3</l>
</data>
The output is as follows:
<word>string</word>
<word>string string2</word>
<word>string string2 string3</word>
<word>string string2 string3</word>