Keep schemaversion while doing <xsl:apply-templates select="#*|node()"/> - xslt

I have requirements to copy whole node including schemaversion and namespaces. (Below is part of XML)
<ns0:Body>
<std:test com:schemaVersion = "4.1" xmlns:com = "http://www.test.com/DI/D2P/Broker/Schemas/CommonTypes" xmlns:std = "http://www.test1.com/DI/D2P/Broker/Schemas/test" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance">
<com:SystemMessageHeader>
...
...
I am using below xslt to do this task. (i have mentioned only needed portion of XSLT.) With this XSLT, it does copy of whole node including namespaces but it does not copy com:schemaVersion = "4.1". Please help me how can i fix it ?
<nos:Body>
<xsl:for-each select ="nos:GRRCall/nos:Response/ns0:Body/*">
<xsl:copy>
<xsl:variable name="v1" select="position()"/>
<xsl:variable name="v2" select="count(../../../../*)"/>
<xsl:apply-templates />
</xsl:copy>
</xsl:for-each>
</nos:Body>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:apply-templates select="com:SystemMessageHeader"/>
</xsl:copy>
</xsl:template>

When you perform an apply-templates without specifying a select expression -- as in your stylesheet fragment -- the attributes of the context node are not selected for transformation. That's why the canonical identity transform explicitly selects attributes:
<!-- Identity transform -->
<xsl:template match="node()|#*">
<xsl:copy>
<!-- HERE: -->
<xsl:apply-templates select="node()|#* />
</xsl:copy>
</xsl:template>
Your first alternative, then, would be to follow the model of the identity transform when you apply templates to the children of <ns0:Body> elements.
Your second alternative would be to explicitly transform the wanted attribute by adding ...
<xsl:apply-templates select="#com:schemaVersion" />
... which will not affect other attributes, and will do nothing if the source element has no such attribute.
A third alternative would be to ensure that the transformed elements have the wanted attribute, even if the source elements didn't, by inserting code to create one into the template:
<xsl:attribute name="com:schemaVersion">
<xsl:value-of select="#com:schemaVersion" />
</xsl:attribute>
That particular version will provide an empty string as the attribute value when the source node has no such attribute; you could modify it to provide a default value instead if desired.

Related

XSLT copy excluding attributes

I've got an XSLT that is translating from XML A to XML B and the 2 are 95% the same except for the fact XML B has fewer attributes. So, for example, I need to translate:
<PaymentDetail PaymentType="CC" ItemNbr="6" CcardNbr="9999999999999999" CcardExp="0523" CcardVend="AA" PayRmrk="" Description="Advance Payment" Total="999.99" />
to
<PaymentDetail PaymentType="CC" ItemNbr="6" CcardNbr="9999999999999999" CcardExp="0523" CcardVend="AA" Total="999.99" />
I've tried copy with an attribute list, but that seems to be long-winded, the same as just doing element = PaymentDetail, attribute name=..... & basically "hard-coding" the whole thing.
I'm using version 1
The standard solution uses template rules:
<!-- Default template: copy nodes unchanged -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Drop selected attributes -->
<xsl:template match="#PayRmrk | #Description"/>
You could exclude the attributes you don't need in your Xpath statement :
<xsl:template match="PaymentDetail">
<xsl:copy>
<xsl:copy-of select="#* except (#PayRmrk, #Description)"/>
</xsl:copy>
</xsl:template>
<!-- Identity template -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
See it working here : http://xsltfiddle.liberty-development.net/6pS26mv

XSLT: create node if does not exists

I know similar questions are already there but none of them seem to work for me.
So shortly, I have XML file with tag "Lokal" that in most cases does not appear but it should. Not making things easier: I also need to change a name of "Lokal" to let's say "Lokal_test". My goal is modify node name(if exists) or create it and rename (if does not exists).
Data from XML will be imported to MS Access data so they need to match perfectly with table...
Sample XML:
<Dane>
<InformacjeOWpisie>
<DaneAdresowe>
<AdresGlownegoMiejscaWykonywaniaDzialalnosci>
<Budynek>3a</Budynek>
<Wojewodztwo>podlaskie</Wojewodztwo>
</AdresGlownegoMiejscaWykonywaniaDzialalnosci>
</DaneAdresowe>
</InformacjeOWpisie>
<InformacjeOWpisie>
<DaneAdresowe>
<AdresGlownegoMiejscaWykonywaniaDzialalnosci>
<Budynek>8r</Budynek>
<Lokal>2</Lokal>
<Wojewodztwo>mazowieckie</Wojewodztwo>
</AdresGlownegoMiejscaWykonywaniaDzialalnosci>
</DaneAdresowe>
</InformacjeOWpisie>
</Dane>
Desired output:
<Dane>
<InformacjeOWpisie>
<DaneAdresowe>
<AdresGlownegoMiejscaWykonywaniaDzialalnosci>
<Budynek>3a</Budynek>
<Lokal_test/>
<Wojewodztwo>podlaskie</Wojewodztwo>
</AdresGlownegoMiejscaWykonywaniaDzialalnosci>
</DaneAdresowe>
</InformacjeOWpisie>
<InformacjeOWpisie>
<DaneAdresowe>
<AdresGlownegoMiejscaWykonywaniaDzialalnosci>
<Budynek>8r</Budynek>
<Lokal_test>2</Lokal_test>
<Wojewodztwo>mazowieckie</Wojewodztwo>
</AdresGlownegoMiejscaWykonywaniaDzialalnosci>
</DaneAdresowe>
</InformacjeOWpisie>
</Dane>
This question(XSLT: create node if not exists seemed to be the awnser to my problems but when trying to use it does not work.
Not sure why?
<xsl:template match="InformacjeOWpisie/DaneAdresowe/AdresGlownegoMiejscaWykonywaniaDzialalnosci/Lokal">
<Lokal_test>
<xsl:apply-templates select="#*|node()" />
</Lokal_test>
</xsl:template>
EDIT:
When I get rid of parent Lokal_test dissapears. I use below code to say "bye bye" to parent:
<xsl:template match="InformacjeOWpisie/DaneAdresowe/AdresGlownegoMiejscaWykonywaniaDzialalnosci">
<xsl:apply-templates select="#*|node()" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="AdresGlownegoMiejscaWykonywaniaDzialalnosci/Budynek">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
<xsl:choose>
<xsl:when test="exists(following-sibling::Lokal)">
<Lokal_test>
<xsl:value-of select="following-sibling::Lokal"/>
</Lokal_test>
</xsl:when>
<xsl:when test="not(following-sibling::Lokal)">
<xsl:element name="Lokal_test"/>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="Lokal"/>
You approach was right, but incomplete. You only created the new Local_test element.
So try these two templates in combination with the indentity template:
<!-- Handles the replacement of the 'Lokal' element -->
<xsl:template match="AdresGlownegoMiejscaWykonywaniaDzialalnosci/Lokal">
<Lokal_test>
<xsl:apply-templates select="node()|#*" />
</Lokal_test>
</xsl:template>
<!-- Creates a new 'Lokal_test' element if no 'Lokal' element exists -->
<xsl:template match="AdresGlownegoMiejscaWykonywaniaDzialalnosci[not(Lokal)]">
<xsl:copy>
<xsl:apply-templates select="node()/following-sibling::Wojewodztwo/preceding-sibling::*|#*" /> <!-- Copy nodes before 'Wojewodztwo' -->
<Lokal_test />
<xsl:apply-templates select="Wojewodztwo|Wojewodztwo/following-sibling::*|#*" /> <!-- Copy nodes after 'Wojewodztwo' (including) -->
</xsl:copy>
</xsl:template>
The second template puts the Lokal_test element before the Wojewodztwo element and copies the surrounding nodes.

Removing attributes from elements when they have a certain value

I'm writing a stylesheet to "canonicalize" XML schemas, in order to make comparing them easier. It will sort top-level elements by name, order attribute in a fixed order etc. This is what I have for attributes so far:
<xsl:template match="xsd:attribute">
<xsl:copy>
<xsl:copy-of select="#name"/>
<xsl:copy-of select="#type"/>
<xsl:copy-of select="#ref"/>
<xsl:copy-of select="#use"/>
<xsl:copy-of select="#default"/>
<xsl:apply-templates select="#*">
<xsl:sort select="name()"/>
</xsl:apply-templates>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
Now I would like to also remove redundant attributes. For example, use = "optional" should be dropped, since optional is the default anyway.
So my question is: what would be the easiest way to enhance the code above in order to drop the use attribute when its value is optional?
#JohnBollinger and #MartinHonnen correctly pointed out that the #* condition will insert all the attributes, even the ones selected above. So the condition has to be refined. It was also pointed out that according to the specification the order of attributes cannot be ensured. The fact that they are being ordered is simply an artifact of how my processor (Saxon9-HE) happens to work. I'm fine with this though. Here is the solution I arrived at:
<xsl:template match="xsd:attribute">
<xsl:copy>
<xsl:copy-of select="#name" />
<xsl:copy-of select="#type" />
<xsl:copy-of select="#ref" />
<xsl:copy-of select="#use[string() != 'optional']" />
<xsl:copy-of select="#default" />
<xsl:apply-templates select="#*[name(.) != 'use']">
<xsl:sort select="name()" />
</xsl:apply-templates>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
I didn't add the other attribute names to the negative filter on #* since Saxon won't duplicate attributes and leaves them in the required order even if it's re-inserted by the #* clause. So this does fulfill my present needs with the least effort, even though it's not a generic solution (which I don't actually need).

Is it possible to preprocess xml source within the same XSLT Stylesheet?

Within the same XSLT (2.0) Stylesheet and transformation I would like to:
1) first preprocess the whole XML Datasource (Add a attribute
with a specific calculation to certain elements)
and then
2: transform the changed XML Datasource with the sylesheet's templates.
How can I achieve this? A code example would be nice?
Yes it is possible. One possibility would be to to proceed as follows:
<xsl:template match="/">
<!-- store the modified content in a variable -->
<xsl:variable name="preprocessed.doc">
<xsl:apply-templates mode="preprocess" />
</xsl:variable>
<!-- process the modified contents -->
<xsl:apply-templates select="$preprocessed.doc/*" />
</xsl:template>
<!-- first pass: sample process to add an attribute named "q" -->
<xsl:template match="*" mode="preprocess">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:attribute name="q"><xsl:number count="*" level="any" /></xsl:attribute>
<xsl:apply-templates mode="preprocess" />
</xsl:copy>
</xsl:template>
<!-- "normal" processing of the modified content. It is able to used the newly processed attribute. -->
<xsl:template match="*[#q <= 5]">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="*"/>
In the first pass, an attribute is added, counting the element in the input XML.
In the processing, we only retain the elements having the value of the q attribute set a number less or equals to 5.

What is the default select of XSLT apply-templates?

The identity template looks like this:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
Does <xsl:apply-templates select="#*|node()" /> select more than <xsl:apply-templates />, or could the identity template have been like this?
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
What exactly is selected when I do the following?
<xsl:apply-templates />
Does <xsl:apply-templates select="#*|node()" /> select more than
<xsl:apply-templates />, or could the identity template have been
like this?
<xsl:apply-templates/>
is equivalent to:
<xsl:apply-templates select="node()"/>
and this is a shorter former of:
<xsl:apply-templates select="child::node()"/>
and this is a equivalent to:
<xsl:apply-templates select="* | text() | comment() | processing-instruction()"/>
As we see from the last instruction, the xsl:apply-templates instruction you are asking about, doesn't select any attributes, therefore it cannot be used as a shorthand for:
<xsl:apply-templates select="#*|node()"/>
The default select for <xsl:apply-templates/> is just "node()", it doesn't include attributes.
The default selection of apply-templates is node(), which is shorthand for child::node(). This XPath expressions is evaluated as follows:
First, all nodes from the "child" axis are taken. This are all the direct children of the current element, i.e. other elements, text, and comments, but not the attributes.
Then this node set is filtered with the node test "node()". In this case, no elements are filtered because that test matches everything.
So with <xsl:apply-templates />, templates for the child elements are applied but not for the attributes. In case of the copy template this would mean that the attributes are not copied.