selecting one of several elements that differ in attribute - xslt

I'm an experienced programmer, but a novice to XSLT and am finding it quite baffling. I apologize if this is a question that's been asked before, but I'm so frustrated by XSLT that I'm not even sure what to search for...
I have a problem that if a certain XML element appears only once, I want its contents output, but if it occurs more than once, I want only the contents of those that have a certain attribute.
For example, suppose I have one XML file (call it "file 1") that contains
<food>
<snack>Chips</snack>
<snack type="nuts">Peanuts</snack>
</food>
and another XML file ("file 2") that contains
<food>
<snack>Cheese puffs</snack>
</food>
I need an XSLT that outputs only "Peanuts" (but not "Chips") upon processing file 1, but still outputs "Cheese puffs" for file 2 (i.e. I can't just select only those elements that have a "type" attribute, that would be too easy).
This is probably simple, but I'm stuck...

This transformation:
<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=
"*[snack/#type]/snack[not(#type)]"/>
</xsl:stylesheet>
produces the wanted results in both cases.
Explanation:
The identity rule/template copies every node "as-is".
The second template is overriding the identity template for any snack element without a type attribute that has sibling snack elements that have a type attribute. This template has empty body, which effectively "deletes" the matched element from (being copied to) the output.

if a certain XML element appears only once, I want its contents output, but if it occurs more than once, I want only the contents of those that have a certain attribute.
A direct translation of that would be
if (count(snack) = 1) then snack else snack[#type='nuts']
which is valid XPath 2.0 syntax - if you need to do it in 1.0 then it translates fairly directly (though verbosely) into an equivalent xsl:choose.
If you want something even more concise than the above, you can also write in XPath 2.0
(snack[#type='nuts'], snack)[1]
which builds a list containing first the snacks with type='nuts', then all the snacks, and then selects the first item from this list.

Related

XSLT: for-each loop with key not returning all nodes

I am a novice XSLT developer. I have been asked to fix an issue on a project where the original developer is no longer with us. In the XSLT, there is a for-each loop using a key and a count
<xsl:for-each select="ns0:BOM[count(. | key('subsat', ns0:BomText01)[1]) = 1][ns0:BomText01]">
...
This is the key:
<xsl:key name="subsat" match="ns0:Parts/ns0:BOM[ns0:FindNum!='0']" use="ns0:BomText01" />
In the XML file being transformed, there are two sibling nodes that represent sub-parts:
<ns0:BOM referentId="10000:65091335:65359080">
<ns0:BomText01>3069260-303-SUB0027</ns0:BomText01>
<ns0:ItemNumber>My_part_1</ns0:ItemNumber>
<ns0:ItemType>Part</ns0:ItemType>
<ns0:Qty>67</ns0:Qty>
</ns0:BOM>
<ns0:BOM referentId="10000:65102551:86713230">
<ns0:BomText01>3069260-303-SUB0027</ns0:BomText01>
<ns0:ItemNumber>My_part_2</ns0:ItemNumber>
<ns0:ItemType>Part</ns0:ItemType>
<ns0:Qty>67</ns0:Qty>
</ns0:BOM>
However, the loop is only picking up the first node (My_part_1). I suspect it's because of the count=1 but I really don't know. And I don't know how to modify it. Ideas? If I need to include more data, let me know.
Assuming that the relevant part of your XSLT looks something like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="ns0" version="1.0">
<xsl:key name="subsat" match="ns0:BOM[ns0:FindNum!='0']" use="ns0:BomText01"/>
<xsl:template match="ns0:Parts">
<xsl:for-each
select="ns0:BOM[count(. | key('subsat', ns0:BomText01)[1]) = 1][ns0:BomText01]">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
It will only print the first of the elements because it is selecting the BOM elements which have an unique BomText01 value. That's the expected result.
If the BomText01 is an ID field (as it seems it is) and you expected to get both result (perhaps, because their ItemNumber contains different values), the error is possibly in your source (which assigned equal IDs when it should not have done so).
If you change one of those values in the source, you should be able to select both and verify this.

Counting elements that are generated in XSLT1

I'm trying to count the elements my transformation generates (must use XLST1). For example, my transformation creates:
<Parent>
<ElementX Att1="2"/>
<ElementY Att1="1"/>
<ElementZ Att1="6"/>
</Parent>
I need to print 3 within the same transformation, because there are 3 child elements.
Can this be done?
Thanks.
It would help a lot if you provide some extract of your XSLT.
I cn't give you a XSLT code without it. I'll try to give some "way" to the answer :
One solution could be to store the output into a nodeset (use the XSLT 1.0 extension which provides the nodeset() function) and apply the XPath count() function on this variable. After that just output your variable with xsl:value-of, and your count result the same way.
Here is a demo how to do this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates/>
</xsl:variable>
<xsl:value-of select="count(ext:node-set($vrtfPass1)/*/*)"/>
</xsl:template>
<xsl:template match="/*">
<Parent>
<ElementX Att1="2"/>
<ElementY Att1="1"/>
<ElementZ Att1="6"/>
</Parent>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used in this Demo), the wanted, correct result is produced:
3
Explanation:
A general way to process the result of the transformation (in a single transformation), is to organize it in two passes where we save the result of the first pass in a variable.
In the second pass we access the result and do the additional processing.
Do note that in XSLT 1.0 if the variable that captures the result of the first pass is of the infamous RTF (Result Tree Fragment) type and needs to be converted to a regular tree in order of any nodes inside this tree to be accessible (xsl:copy-of and string() are still allowed on an RTF).
This conversion to a regular tree is done by an extension function, which most often has the name node-set and always belongs to a vendor-defined namespace. In this demo we are using the node-set() extension function that belongs to the EXSLT namespace -- because most XSLT 1.0 processors implement EXSLT.
For more information on multi-pass processing, see this: Two phase processing: Do not output empty tags from phase-1 XSLT 2.0 processing

how to check an existing node and its contains in an xml file using xslt and differenciate the node by an id number

Is there any possibility to check if it exists a duplicate node in an xml file, then check the contains of this node and if possible differenciate the both node by an Id number using xslt?
I have for example this xml file
<Racine>
<el1>
<fils1>context1</fils1>
<fils2>test1</fils2>
<fils1>context1</fils1>
</el1>
<el2>
<fils1>context2</fils1>
<fils2>test2</fils2>
<fils1>context2</fils1>
</el2>
<el3>...........<el3>
</Racine>
In this case for example, the node "fils1" appears twice in the node "el1" and the contains of each of them is the same. I need in this case to transform my xml file so that this node will become
<fils1 id=1>context1</fils1>.
I add automatically an Id-number to differenciate the both node with the same name and same contain. And when the node got the same name with different contains( also a space in the contain of the node has to be considered as a difference), then lets the node as they are.
How can i make this possible using XSLT? Could someone here help me to transform that?
Thanks a lot for your help.
Franky
Thanks for yours remarks. Here is the expected output:
<Racine>
<el1>
<fils1 id=1>context1</fils1>
<fils2>test1</fils2>
<fils1 id=2>context1</fils1>
</el1>
<el2>
<fils1 id=1>context2</fils1>
<fils2>test2</fils2>
<fils1 id=2>context2</fils1>
</el2>
<el3>...........
<fils1 id=3>context1</fils1>
<el3>
</Racine>
Conerning the node "fils2" in "el1" and "el2", the contain is different, then i want to keep them unchange and when not, i want to add an id number to make the difference.
This XSLT 1.0 style-sheet...
<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:key name="kCommon" match="*[starts-with(name(),'fils')]"
use="concat(name(),'|',.)" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(name(),'fils')]
[count(key('kCommon',concat(name(),'|',.))) >= 2]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:variable name="this-key" select="concat(name(),'|',.)" />
<xsl:attribute name="id"><xsl:value-of select="
count(preceding::*
[starts-with(name(),'fils')]
[count(.|key('kCommon',$this-key)) =
count( key('kCommon',$this-key)) ]
) + 1" /></xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
...will transform this input...
<Racine>
<el1>
<fils1>context1</fils1>
<fils2>test1</fils2>
<fils1>context1</fils1>
</el1>
<el2>
<fils1>context2</fils1>
<fils2>test2</fils2>
<fils1>context2</fils1>
</el2>
<el3>
<fils1>context1</fils1>
</el3>
</Racine>
...into...
<Racine>
<el1>
<fils1 id="1">context1</fils1>
<fils2>test1</fils2>
<fils1 id="2">context1</fils1>
</el1>
<el2>
<fils1 id="1">context2</fils1>
<fils2>test2</fils2>
<fils1 id="2">context2</fils1>
</el2>
<el3>
<fils1 id="3">context1</fils1>
</el3>
</Racine>
Explanation
We build a key ('kCommon') of the fils elements that have both common name and common text content. We look for elements which have membership in thier key group of at least two - in other words filX type elements that have a name and text in common with at least one other filX element. This is our last template.
For each such common element, we build two sets:
The set of all preceding fil elements in the common group. This set is given by...
Set 1
preceding::*[starts-with(name(),'fils')]
And this node's common name+content group, which is...
Set 2
key('kCommon',concat(name(),'|',.))
But because we will reference concat(name(),'|',.) quiet a few times, and in places where we don't have direct access to this context node, we just compute the concat() and substitute into Set 2 like this. Set 2 is now quick to compute.
Set 2
key('kCommon',$this-key)
And then we take a set intersection using the Kaysian method.
$set1[count(.|$set2)=count($set2)]
This intersection is the set of all like-minded element to this one that preceded it. We just count them up and add one, and this gives us an ordinal for the id property.

XSLT: How to remove synonymous namespaces

I have a large collection of XML files which I need to transform using XSLT. The problem is that many of these files were hand-written by different people and they do not use consistent names to refer to the schemas. For example, one file might use:
xmlns:itemType="http://example.com/ItemType/XSD"
where another might use the prefix "it" instead of "itemType":
xmlns:it="http://example.com/ItemType/XSD"
If that's not bad enough, there are several files which use two or three synonyms for the same thing!
<?xml version="1.0"?>
<Document
xmlns:it="http://example.com/ItemType/XSD"
xmlns:itemType="http://example.com/ItemType/XSD"
xmlns:ItemType="http://example.com/ItemType/XSD"
...
(there's clearly been a lot of cutting and pasting going on)
Now, because the pattern matching in the XSLT file appears to work on the namespace prefix (as opposed to the schema it relates to) the pattern only matches one of the variants. So if I write something like:
<xsl:template match="SomeNode[#xsi:type='itemType:SomeType']">
...
</xsl:template>
Then it only matches a subset of the cases that I want it to.
Question 1: Is there any way to get the XSLT to match all the variants?
Question 2: Is there any way to remove the duplicates so all the output files use consistent naming?
I naïvely tried using "namespace-alias" but I guess I've misunderstood what that does because I can't get it to do anything at all - either match all the variants or affect the output XML.
<?xsl:stylesheet
version="1.0"
...
xmlns:it="http://example.com/ItemType/XSD"
xmlns:itemType="http://example.com/ItemType/XSD"
xmlns:ItemType="http://example.com/ItemType/XSD"
...
<xsl:output method="xml" indent="yes"/>
<xsl:namespace-alias stylesheet-prefix="it" result-prefix="ItemType"/>
<xsl:namespace-alias stylesheet-prefix="itemType" result-prefix="ItemType"/>
Attribute values or text nodes won't be cast to QName unless you explicitly say so. Although this is only posible in XSLT/XPath 2.0
In XSLT/XPath 1.0 you must do this "manually":
<xsl:template match="SomeNode">
<xsl:variable name="vPrefix" select="substring-before(#xsi:type,':')"/>
<xsl:variable name="vNCName"
select="translate(substring-after(#xsi:type,$vPrefix),':','')"/>
<xsl:if test="namespace::*[
name()=$vPrefix
] = 'http://example.com/ItemType/XSD'
and
$vNCName = 'SomeType'">
<!-- Content Template -->
<xsl:if>
</xsl:template>
Edit: All in one pattern (less readable, maybe):
<xsl:template match="SomeNode[
namespace::*[
name()=substring-before(../#xsi:type,':')
] = 'http://example.com/ItemType/XSD'
and
substring(
concat(':',#xsi:type),
string-length(#xsi:type) - 7
) = ':SomeType'
]">
<!-- Content Template -->
</xsl:template>
In XSLT 2.0 (whether or not you use schema-awareness) you can write the predicate as [#xsi:type=xs:QName('it:SomeType')] where "it" is the prefix declared in the stylesheet for this namespace. It doesn't have to be the same as the prefix used in the source document.
Of course matching of element and attribute names (as distinct from QName-valued content) uses namespace URIs rather than prefixes in both XSLT 1.0 and XSLT 2.0.

Select the value of the attribute

<Surcharge>
<Rentalplus desc="Rental plus">75.00</Rentalplus>
<Gasket desc="Seals and gasket">50.00</Gasket>
<WearandTear desc"Wear and Tear">100.00</WearandTear>
</Surcharge>
from the above xml i want to extract the "desc". keep in mind i have different tag names under the node.
Thanks for the help
How about a minimalist solution ?
//#desc
Or more precise
/Surcharge//#desc
Or even more precise
/Surcharge/*[self::Rentalplus|self::Gasket|self::WearandTear]/#desc
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/*">
<xsl:apply-templates select="*/#desc"/>
</xsl:template>
</xsl:stylesheet>
Exploits built-in rules. Result will be:
Rental plusSeals and gasketWear and Tear
Use:
/*/*/#desc
This selects all desc attributes of all children of the top element of the XML document.
Never use the // abbreviation when the structure of the document is well-known. Using the // abbreviation may result in significantly slow evaluation, because it causes traversal of the whole XML document.
Should be something like this:
//#desc
See syntax from the w3schools site http://www.w3schools.com/xsl/xpath_syntax.asp