XSLT: change node type - xslt

I have an application which stores its data as xml-serialized classes. Due to a refactoring being performed internal collections were changed to be type safe. I.e. before they were of type object[] even though each collection stored only a particular type of objects and now they are MyType[].
For example a collection called Parameters was serialized:
<Parameters>
<anyType xsi:type="Parameter">
...
</anyType>
</Parameters>
Become:
<Parameters>
<Parameter>
...
</Parameter>
</Parameters>
I need to convert old xml files to a new format. So I need to convert all nodes matching
<anyType xsi:type="Parameter"> to <Parameter> and so for 10 or 20 other collections and types.
Can anyone advise what kind of a transformation this would be?

A more verbose version which retrieves the name of the new element from the xsi:type and also copies other attributes if present. Now you have three approaches and you should be able to compile your custom version:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="anyType[#xsi:type]">
<xsl:element name="{#xsi:type}">
<xsl:apply-templates select="node()|#*[not(name()='xsi:type')]" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>

You just need a single template rule for this:
<xsl:template match="anyType">
<Parameter><xsl:apply-templates/></Parameter>
</xsl:template>
together with the identity template rule which copies everything else unchanged.

Related

How do I match on any node that itself or any child has an attribute with a value in XSLT Template?

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

Issue while applying apply templates

I have a xml which I want to get in the expected format as shown below. I am trying to use apply templates concept to this. But some how, I am not able to see the expected result.
<?xml version="1.0" encoding="UTF-8"?>
<SOAP:ENV>
<SOAP:HEADER/>
<SOAP:BODY>
<OutputResponse>
<RespStructure ID="1">
<RespStatus>Success</RespStatus>
<RespMessage>
<Country>Australia</Country>
<Capital>Canberra</Capital>
</RespMessage>
<RespMessage>
<Country>England</Country>
<Capital>London</Capital>
</RespMessage>
<RespMessage>
<Country>China</Country>
<Capital>Beijing</Capital>
</RespMessage>
</RespStructure>
</OutputResponse>
</SOAP:BODY>
</SOAP:ENV>
Now in this message, I am having RespMessage and RespStatus both as part of RespStructure. But Respstatus is a single nodeset where RespMessage is a multiple values nodeset(Country, Capital). When I am using apply templates, either only first RespMessage is getting selected (2nd repetition not appearing) or Respstatus not giving it's value. I try to get below output.
<?xml version="1.0" encoding="UTF-8"?>
<SOAP:ENV>
<SOAP:HEADER/>
<SOAP:BODY>
<OutputResponse>
<RespStructure ID="1">
<TransactionStatus>Success</TransactionStatus>
<ListOfCountries>
<SelectedCountry>Australia</SelectedCountry>
<FIrstSelectedCapital>Canberra</FIrstSelectedCapital>
</ListOfCountries>
<ListOfCountries>
<SelectedCountry>England</SelectedCountry>
<FIrstSelectedCapital>London</FIrstSelectedCapital>
</ListOfCountries>
<ListOfCountries>
<SelectedCountry>China</SelectedCountry>
<FIrstSelectedCapital>Beijing</FIrstSelectedCapital>
</ListOfCountries>
</RespStructure>
</OutputResponse>
</SOAP:BODY>
</SOAP:ENV>
I am changing all the names of fields, but I should get all multiple nodes without any missing.
I used below code snippet from forum, but I failed to apply. How can I populate RespStatus using below snippet. When I gave different template, both are not called and only one got printed. I tried to change apply templates to more specific nodeset also.
<xsl:template match="#*|node()">
<xsl:apply-templates select="#*|node()" />
</xsl:template>
<xsl:template match="Body">
<SOAPENV>
<Header/>
<OutputResponse>
<xsl:apply-templates select="#*|node()" />
</OutputResponse>
</SOAPENV>
</xsl:template>
<xsl:template match="RespMessage">
<ListOfCountries>
<SelectedCountry><xsl:value-of select="Country" /></SelectedCountry>
<FIrstSelectedCapital><xsl:value-of select="Capital" /></FIrstSelectedCapital>
</ListOfCountries>
</xsl:template>
Thanks for response. Apologies for any spell mistakes.
Looking at your XSL snippet, it should match, and transform all RespMessage elements, unless there is some other part of the XSLT, which you have not shown, that is affecting things.
One issue you do have, and this might be just how you have editted your question, is that your original XML uses the namespace prefix "SOAP", but you have not declared a namespace for this prefix. I would expect the first line to be something like this
<SOAP:ENV xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
Without this, you will not be able to apply your XSLT on it all.
The use of namespaces also means the second template, matching body, won't match anything
<xsl:template match="Body">
(Actually, it won't match anything because it is case sensitive, and in the original XML it is SOAP:BODY)
Looking at the rest of the XSLT, if you want to copy across existing elements without changing, you should be using the identity template. The first template in your XSLT should look like this
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
With this in place, all you need is to add a template to match, and transform RespStatus.
<xsl:template match="RespStatus">
<xsl:element name="TransactionStatus">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="RespStatus">
<xsl:element name="TransactionStatus">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<xsl:template match="RespMessage">
<xsl:element name="ListOfCountries">
<SelectedCountry><xsl:value-of select="Country" /></SelectedCountry>
<FIrstSelectedCapital><xsl:value-of select="Capital" /></FIrstSelectedCapital>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

Having trouble selecting properties with XSLT

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/>

XSL parent selector

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.

How to copy all child nodes of any type of a template context element

I am transforming XML into HTML using XSLT.
I have the following XML structure:
<root>
<element>
<subelement>
This is some html text which should be <span class="highlight">displayed highlighted</span>.
</subelement>
</element>
</root>
I use the following template for the transformation:
<xsl:template name="subelement">
<xsl:value-of select="." />
</xsl:template>
Unfortunately, I lose the <span>-tags.
Is there a way to keep them so the HTML is displayed correctly (highlighted)?
The correct way to get the all the contents of the current matching node (text nodes included) is:
<xsl:template match="subelement">
<xsl:copy-of select="node()"/>
</xsl:template>
This will copy everything descendent.
Try using <xsl:copy-of... instead of <xsl:value-of... for example:
<xsl:template name="subelement">
<xsl:copy-of select="*" />
</xsl:template>
Note the * which will stop the <subelement></subelement> bits being output to the results, rather than using . which will include the <subelement></subelement> bits .
For example, the xsl stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="root/element">
<output>
<xsl:apply-templates select="subelement"/>
</output>
</xsl:template>
<xsl:template match="subelement">
<xsl:copy-of select="*"/>
</xsl:template>
</xsl:stylesheet>
when applied to your example xml file returns:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<span class="highlight">displayed highlighted</span>
</output>
The <xsl:value-of> declaration takes the concatenated contents of all text nodes within the element, in sequential order, and doesn't output any elements at all.
I'd recommend using <xsl:apply-templates> instead. Where it finds a text node, it will output the contents as-is, but you would need to define a template for handling span tags to convert them to html ones. If that span tag IS an html tag, then strictly speaking, you should have separate namespaces for your own document structure and html.