XML
<dsInventory>
<Room>
<RoomName>kantoor 1</RoomName>
<RoomId>1376257</RoomId>
</Room>
<Room>
<RoomName>Hal</RoomName>
<RoomId>1376258</RoomId>
</Room>
<Method>
<RoomId>1376257</RoomId>
<Name>test</Name>
</Method>
<Method>
<RoomId>1376258</RoomId>
<Name>test</Name>
</Method>
</dsInventory>
XSL
<xsl:for-each select="/dsInventory/Room">
<xsl:for-each select="/dsInventory/Method">
<xsl:if test="RoomId=../RoomId">
</xsl:if>
</xsl:for-each>
</xsl:for-each>
Problem
I'm trying to select the methods for the corresponding rooms based on the roomid. however the selector ../RoomId doesn't work. How do i get the value of the RoomId from the first for-each? Or what is the proper way to select the methods for the rooms?
Your problem here is context. The if condition is failing because it's running from the context of Method nodes, and therefore the XPath expression ../RoomID is looking for a RoomID node on dsInventory. There is no such node.
You don't say what output you want, but based on the assumption that you simply want to iterate over rooms and find the method for each, try this: (note also I use templates rather than for-each loops, which are best avoided in XSL).
You can run it and see the output at this XML Playground.
<!-- kick things off from the point of dsInventory -->
<xsl:template match="dsInventory">
<ul><xsl:apply-templates select='Room' /></ul>
</xsl:template>
<!-- room nodes -->
<xsl:template match='Room'>
<xsl:variable name='method' select='../Method[RoomId = current()/RoomId]' />
<li>
<xsl:value-of select='concat(RoomName," (",RoomId,")")' />:
method = <xsl:value-of select='concat($method/Name," (",$method/RoomId,")")' />
</li>
</xsl:template>
You havn't specified the version of XSLT you are working with or your expected output. I am going to assume you want XSLT 1.0, want to copy the document, but add the linked method name under the Room and drop the Methods.
This XSLT 1.0 style-sheet ...
<?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"/>
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Method"/>
<xsl:template match="Room">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:apply-templates select="../Method[RoomId=current()/RoomId]/Name"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
... when applied to your sample input document, will produce ...
<dsInventory>
<Room>
<RoomName>kantoor 1</RoomName>
<RoomId>1376257</RoomId>
<Name>test</Name>
</Room>
<Room>
<RoomName>Hal</RoomName>
<RoomId>1376258</RoomId>
<Name>test</Name>
</Room>
</dsInventory>
Explanation
Use the current() function to specify the current item relative to the sequence constructor for the Room template, which in this case is a Room element. In the predicate we compare current()/RoomId with the RoomId of the Method step of our select expression. Thus linked Names are copied under Room. No xsl:if, no xsl:for-each, no variables. All can be achieved in the select expression using the current() function.
<xsl:for-each select="/dsInventory/Room">
<xsl:for-each select="/dsInventory/Method">
<xsl:if test="RoomId=../RoomId"></xsl:if>
</xsl:for-each>
</xsl:for-each>
As the people, who answered this question pointed out, you are trying to compare a /dsInventory/Method/RoomId to a /dsInventory/RoomId and the latter doesn't exist in the provided XML document.
I'm trying to select the methods for the corresponding rooms based on
the roomid.
Here is a complete transformation, showing how to do this:
<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()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Room">
<xsl:copy>
<xsl:apply-templates/>
<xsl:apply-templates mode="grab"
select="/*/Method[RoomId = current()/RoomId]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Method" mode="grab">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="Method | Method/RoomId"/>
</xsl:stylesheet>
When this transformation is applied on the following XML document (the provided one with changed method names -- to be different from each other):
<dsInventory>
<Room>
<RoomName>kantoor 1</RoomName>
<RoomId>1376257</RoomId>
</Room>
<Room>
<RoomName>Hal</RoomName>
<RoomId>1376258</RoomId>
</Room>
<Method>
<RoomId>1376257</RoomId>
<Name>test1</Name>
</Method>
<Method>
<RoomId>1376258</RoomId>
<Name>test2</Name>
</Method>
</dsInventory>
the wanted, correct result is produced (methods added to rooms and the Method children of the top element deleted):
<dsInventory>
<Room>
<RoomName>kantoor 1</RoomName>
<RoomId>1376257</RoomId>
<Method>
<Name>test1</Name>
</Method>
</Room>
<Room>
<RoomName>Hal</RoomName>
<RoomId>1376258</RoomId>
<Method>
<Name>test2</Name>
</Method>
</Room>
</dsInventory>
Explanation:
The identity rule copies "as-is" every node for which it is selected for execution.
A template, overriding the identity template, matches Room. Here the Room element is "shallow-copied and templates are applied to its children (which selects the identity template for them and they are copied "as-is" to the output) and then templates are applied to any Method, whose RoomId child has the same value as the RoomId child of the currently matched (current()) Room element. Because we also want to later delete any Method element, here we specify that the wanted processing (different from deletion) is to be done in "grab" mode.
A second template overriding the identity template deletes all Method elements by simply matching them and having an empty body.
The template that matches a Method in mode "grab" simply shallow-copies it and applies templates to its children. Of its children the Name is matched only by the identity template and is copied "as-is". The RoomId child is matched by a more specific template than the identity template and the former is chosen for execution. The chosen template has no body, which effectively "deletes" the matched Method/RoomId from the output.
Related
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 am trying to transform an xml file that contain a list of words and I am trying to exclude some elements from the resulting document, more concretely and
My List is as follows:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="merge.xsl"?>
<dic:englishWords xmlns:dic = "dictionary">
<dic:words>
<dic:englishWords xmlns:dic = "dictionary">
<dic:title>
English Dictionary
</dic:title>
<dic:author>
<dic:authorsName>
Author:
<dic:name>
User
</dic:name>
<dic:lastName>
Name
</dic:lastName>
</dic:authorsName>
</dic:author>
<dic:words>
<dic:name>Water</dic:name><br/>
<dic:name>Room</dic:name><br/>
<dic:name>Computer</dic:name><br/>
<dic:name>Book</dic:name><br/>
<dic:name>Garage</dic:name><br/>
<dic:name>Car</dic:name><br/>
<dic:name>Ship</dic:name><br/>
<dic:name>Food</dic:name><br/>
<dic:name>Coffee</dic:name><br/>
<dic:name>Program</dic:name><br/>
</dic:words>
</dic:englishWords>
The path to the list of words is contained in an xml file as follows:
<dic:dictionary xmlns:dic = "dictionary">
<dic:Logo>Logo</dic:Logo>
<dic:Author>User Name</dic:Author>
<dic:EnglishWords>english</dic:EnglishWords>
<dic:SwedishTranslation>swedish</dic:SwedishTranslation>
<dic:SwedishWords>swedish</dic:SwedishWords>
<dic:EnglishTranslation>english</dic:EnglishTranslation>
</dic:dictionary>
my transformation is as follows
<!--Declare a parameter with the nodes to be removed-->
<xsl:param name="removeElementsNamed" select="'|dic:author|dic:title'"/>
<!--create a template and call it remove node-->
<xsl:template match="node()|#*" name="removeNode">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!--remove the actual nodes-->
<xsl:template match="*">
<xsl:if test="not(contains($removeElementsNamed, concat('|',name(),'|')))">
<xsl:call-template name="removeNode"/>
</xsl:if>
</xsl:template>
I am trying to follow an example which I have found here:
how to exclude elements
...but in my case it does not work.
Any help will be appreciated...
Bluetxxth
So it looks like you're trying to exclude elements based on whether |ELEMENTNAME| is present in $removeElementsNamed, but dic:author is the only item in that list that has pipes on both sides. It might almost work if you did this:
<xsl:param name="removeElementsNamed" select="'|dic:author|dic:title|'"/>
However this is a bit of a hack.
A better approach would to just do something like this:
<xsl:template match="dic:author | dic:title" />
This should exclude dic:author and dic:title from the output.
Another issue is that this template is misnamed:
<xsl:template match="node()|#*" name="removeNode">
What this template would actually do, if it worked, would be to include nodes that were sent its way, but a template can't both have a match attribute and a name attribute. I would suggest rewriting your XSLT to be like this and starting from here:
<!--Declare a parameter with the nodes to be removed-->
<xsl:template match="dic:author | dic:title" />
<!--create a template and call it remove node-->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
You could match the element you don't want but not output anything.
<xsl:template match="nodeToMatch" />
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/>
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>
I have a small question regarding XSLT template overriding.
For this segment of my XML:
<record>
<medication>
<medicine>
<name>penicillin G</name>
<strength>500 mg</strength>
</medicine>
</medication>
</record>
In my XSLT sheet, I have two templates in the following order:
<xsl:template match="medication">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="medicine/name">
<text>!unauthorized information!</text>
</xsl:template>
What I want to do is to copy everything under the medication element to the output other than the "name" element (or any other element that I explicitly define). The final xml will be shown to the user in RAW XML form. In other words, the result I want is:
<record>
<medication>
<medicine>
<text>! unauthorized information!</text>
<strength>500 mg</strength>
</medicine>
</medication>
</record>
Whereas I am getting the same XML as input, i.e. without the element replaced by text. Any ideas why the second template match is not overriding the name element in the first one? Thanks in advance
--
Ali
Template order does not matter. The only case it possibly becomes considered (and this is processor-dependent) is when you have an un-resolvable conflict, i.e. an error condition. In that case, it's legal for the XSLT processor to recover from the error by picking the one that comes last. However, you should never write code that depends on this behavior.
In your case, template priority isn't even the issue. You have two different template rules, one matching <medication> elements and one matching <name> elements. These will never collide, so it's not a question of template priority or overriding. The issue is that your code never actually applies templates to the <name> element. When you say <xsl:copy-of select="."/> on <medication>, you're saying: "perform a deep copy of <medication>". The only way any of the template rules will fire for descendant nodes is if you explicitly apply templates (using <xsl:apply-templates/>.
The solution I have for you is basically the same as alamar's, except that it uses a separate processing "mode", which isolates the rules from all other rules in your stylesheet. The generic match="#* | node()" template causes template rules to be recursively applied to children (and attributes), which gives you the opportunity to override the behavior for certain nodes.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- ...placeholder for the rest of your code... -->
<xsl:template match="/record">
<record>
<xsl:apply-templates/>
</record>
</xsl:template>
<!-- end of placeholder -->
<xsl:template match="medication">
<!-- Instead of copy-of, whose behavior is to always perform
a deep copy and cannot be customized, define your own
processing mode. Rules with this mode name are isolated
from the rest of your code. -->
<xsl:apply-templates mode="copy-medication" select="."/>
</xsl:template>
<!-- By default, copy all nodes and their descendants -->
<xsl:template mode="copy-medication" match="#* | node()">
<xsl:copy>
<xsl:apply-templates mode="copy-medication" select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- But replace <name> -->
<xsl:template mode="copy-medication" match="medicine/name">
<text>!unauthorized information!</text>
</xsl:template>
</xsl:stylesheet>
The rule for "medicine/name" overrides the rule for "#* | node()", because the format of the pattern (which contains a "/") makes its default priority (0.5) higher than the default priority of "node()" (-1.0).
A complete but concise description of how template priority works can be found in "How XSLT Works" on my website.
Finally, I noticed you mentioned you want to display "RAW XML" to the user. Does that mean you want to display, for example, the XML, with all the start and end tags, in a browser? In that case, you'd need to escape all markup (e.g., "<" for "<"). Check out the XML-to-string utility on my website. Let me know if you need an example of how to use it.
Add
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
to your <xsl:template match="medicine/name">
And remove <xsl:template match="medication"> altogether!
<?xml version="1.0" encoding="windows-1251"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="medicine/name">
<text>!unauthorized information!</text>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>