insert PCDATA from the child element into an attribute - xslt

I want to insert PCDATA from the child element into a selected node attribute
XML
<root>
<tag>
<tag1>SOME TEXT</tag1>
</tag>
</root>
MY XSL
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xi="http://www.w3.org/2001/XInclude" version="1.0">
<xsl:template match="root">
<tag-out>
<xsl:attribute name="text">
<!-- What should I select? -->
<xsl:value-of select="tag/tag1/???"/>
</xsl:attribute>
<tag-out>
</xsl:template>
...........
</xsl:stylesheet>
Desired output XML
<root-out text="SOME TEXT">
<tag-out/>
</root-out>
Thanks

What's wrong with simply doing
<tag-out text="{tag/tag1}"></tag-out>
? Of course your sample with
<tag-out>
<xsl:attribute name="text">
<xsl:value-of select="tag/tag1"/>
</xsl:attribute>
<tag-out>
is also possible. But as your post is tagged XSLT 2.0 I would at least do
<tag-out>
<xsl:attribute name="text" select="tag/tag1"/>
<tag-out>
if you really need xsl:attribute.

Related

Process an element both through a generic and a targeted template

I'm new at XSL and still not sure of some of my terminology. I'm currently facing something I can't seem to crack. I'm attempting to
Search through all data nodes (leaf elements?) of input XML and replace text
Search through all attribute values in input XML and replace text
Copy over other nodes to output
Copy over processor instructions and comments to output
Match and process specific nodes in the input
The issues I'm facing are:
A. Not sure of the terminology (see the comments in files below) and the tack I'm taking in attacking this
B. The template (5) above, whenever it matches a node, seems to be preventing the other templates (1 and 2) from processing it
If it makes a difference, I'm running this on Windows, using Microsoft's processor and am using XSLT 1.0. I've included simplified versions of the input (Input.xml), XSLT (Transform.xslt) and the output (Output.xml) I'm getting.
I did try using "mode" to run the targeted template (5) from the generic search and replace templates (1 and 2) but, in that case, template 1 and 2 run but the targeted template (5) itself doesn't run.
I'd appreciate any comments and suggestions.
Input.xml
<?xml version="1.0" encoding="UTF-8"?>
<List>
<Item Name="Item1" Text="abcd"/>
<Item Name="Itembc" Text="qrst"/>
<Item Name="Special" Text="Hello, Worldbc"/>
<Item Name="Special" Text=""/>
</List>
Transform.xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:myjs="urn:custom-javascript" exclude-result-prefixes="msxsl myjs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<msxsl:script language="JavaScript" implements-prefix="myjs">
<![CDATA[
function EscapeRegExp(str)
{
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
function StringReplace(strWhere, strWhat, strBy, strFlags)
{
return strWhere.replace ( new RegExp(EscapeRegExp(strWhat), strFlags), strBy);
}
]]>
</msxsl:script>
<!-- ********************************************************************************************************************** -->
<!-- -->
<!-- Because of the following 4 templates, the identity transform is not needed in this XSLT -->
<!-- -->
<!-- ********************************************************************************************************************** -->
<!-- 1 of 4: Copy all nodes from source XML to the final XML, searching and replacing -->
<!-- Modify (1 of 4) and (2 of 4) to support additional replacement -->
<!-- Search and Replace in attributes -->
<xsl:template match="#*">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
<xsl:variable name="TempAttrValue" select="."/>
<xsl:value-of select="myjs:StringReplace(string($TempAttrValue), 'bc', '2', 'g')"/>
</xsl:attribute>
<!-- xsl:apply-templates mode="TargetedTemplate"/ -->
</xsl:template>
<!-- 2 of 4: Copy all nodes from source XML to the final XML, searching and replacing -->
<!-- Modify (1 of 4) and (2 of 4) to support additional replacement -->
<!-- Search and Replace in data nodes -->
<xsl:template match="text()">
<xsl:variable name="TempTextValue" select="."/>
<xsl:value-of select="myjs:StringReplace(string($TempTextValue), 'bc', '3', 'g')"/>
<!-- xsl:apply-templates mode="TargetedTemplate"/ -->
</xsl:template>
<!-- 3 of 4: Copy all nodes from source XML to the final XML, searching and replacing -->
<!-- Process element nodes but not attributes -->
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- 4 of 4: Copy all nodes from source XML to the final XML, searching and replacing -->
<!-- Leave the comment nodes and processing instruction nodes alone -->
<xsl:template match="comment() | processing-instruction()">
<xsl:copy/>
</xsl:template>
<!-- 5 : Process specific nodes -->
<!-- Assumes an item in the input called Special and fills it with (No Data) if it is empty -->
<!-- Seems to be interfering with 1-4 above. Changing the [#Name='Special'] to [#Name='SpecialA'] will let 1-4 above to work -->
<xsl:template match="Item[#Name='Special']/#Text">
<xsl:attribute name="Text">
<xsl:variable name="TempSpecialText" select="."/>
<xsl:choose>
<xsl:when test="($TempSpecialText = '')">(No Data)</xsl:when>
<xsl:otherwise><xsl:value-of select="$TempSpecialText"/></xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
Output.xml
<?xml version="1.0" encoding="UTF-8"?>
<List>
<Item Name="Item1" Text="a2d">
</Item>
<Item Name="Item2" Text="qrst">
</Item>
<Item Name="Special" Text="Hello, Worldbc">
</Item>
<Item Name="Special" Text="(No Data)">
</Item>
</List>
Output - Desired.xml
<?xml version="1.0" encoding="UTF-8"?>
<List>
<Item Name="Item1" Text="a2d">
</Item>
<Item Name="Item2" Text="qrst">
</Item>
<Item Name="Special" Text="Hello, World2">
</Item>
<Item Name="Special" Text="(No Data)">
</Item>
</List>
I think the main problem is with your final template
<xsl:template match="Item[#Name='Special']/#Text">
<xsl:attribute name="Text">
<xsl:variable name="TempSpecialText" select="."/>
<xsl:choose>
<xsl:when test="($TempSpecialText = '')">(No Data)</xsl:when>
<xsl:otherwise><xsl:value-of select="$TempSpecialText"/></xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:template>
This will match your <Item Name="Special" Text="Hello, Worldbc"> element ahead of the template that just matches #*. However, the Text attribute is not empty, and your xsl:otherwise simply outputs the value again, and doesn't do the replace you want. The <xsl:apply-templates/> here is unnecessary because attributes do not have any child nodes to select.
What you can do is this...
<xsl:template match="Item[#Name='Special']/#Text">
<xsl:attribute name="Text">
<xsl:variable name="TempSpecialText" select="."/>
<xsl:choose>
<xsl:when test="($TempSpecialText = '')">(No Data)</xsl:when>
<xsl:otherwise>
<xsl:value-of select="myjs:StringReplace(string($TempSpecialText), 'bc', '2', 'g')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:template>
But this is duplication of code. A better solution would be to change the template only match empty attributes, like so:
<xsl:template match="Item[#Name='Special']/#Text[. = '']">
<xsl:attribute name="Text">(No Data)</xsl:attribute>
</xsl:template>
That way it will only match the <Item Name="Special" Text=""/> element, where as the <Item Name="Special" Text="Hello, Worldbc"/> will be matched by the generic #* template.
Be wary of using Microsoft specific extension functions, as this obviously limits you to running on Microsoft platforms. If you limited to XSLT 1.0, this means using a recursive template. (See Find and replace entity in xslt as an example). Alternatively, if you can switch to XSLT 2.0, the replace function comes as standard.
Would this work for you?
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:myjs="urn:custom-javascript"
exclude-result-prefixes="msxsl myjs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<msxsl:script language="JavaScript" implements-prefix="myjs">
<![CDATA[
function EscapeRegExp(str)
{
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
function StringReplace(strWhere, strWhat, strBy, strFlags)
{
return strWhere.replace ( new RegExp(EscapeRegExp(strWhat), strFlags), strBy);
}
]]>
</msxsl:script>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*[contains(., 'bc')]">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
<xsl:value-of select="myjs:StringReplace(string(.), 'bc', '2', 'g')"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="#*[not(string())]">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
<xsl:text>(No Data)</xsl:text>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>

Split an .xml-file with XSLT

I have written an XSL-file, that reads some filenames from the source file and uses this filenames, to split another file (which is opened in the XSL-file via the document() function). The filenames are used to create several output files and certain parts of the loaded file are written to these output files.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Root">
<xsl:apply-templates select="//Link"/>
</xsl:template>
<xsl:template match="Link">
<xsl:result-document href="{#url}" method="xml">
<xsl:apply-templates select="document('Input.xml')//Node"/>
</xsl:result-document>
</xsl:template>
<xsl:template match="Node">
<xsl:copy-of select="."/>
<xsl:if test="following-sibling::*[1][self::NextPart]">
<!-- write some test node -->
<xsl:element name="FoundNextPart"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The sourcefile looks something like this
<Root>
<SomeNode>
<Link url="part_0.xml"/>
<Link url="part_1.xml"/>
<Link url="part_2.xml"/>
</SomeNode>
</Root>
The Input.xml file will have a structure like this
<Root>
<Node>
<PartContent>
<ImportantContent>0</ImportantContent>
</PartContent>
</Node>
<Node>
<PartContent>
<ImportantContent>0</ImportantContent>
</PartContent>
</Node>
<NextPart/>
<Node>
<PartContent>
<ImportantContent>1</ImportantContent>
</PartContent>
</Node>
<Node>
<PartContent>
<ImportantContent>1</ImportantContent>
</PartContent>
</Node>
<NextPart/>
</Root>
My problem is now with the
<xsl:template match="Node">
I want to copy the content of the Input.xml up to the first appearance of the
<NextPart/>
node. Then I want to somehow break out of the current nodeset (//Node of the Input.xml) and continue with the next //Link. But for this next Link (file) I want to copy the content of the Input.xml between the first and the second appearance of the
<NextPart/>
node.
I'm not sure if this is feasible in any way. Also I'm not sure if my approach can be used for this.
I've read something like using
<xsl:call-template name="copy">
to use the following-sibling of the current node as a parameter. But anyway I have to pass the current count of the
<NextPart/>
so that I know, which content to copy!?
How about processing and grouping that Input.xml once with e.g.
<xsl:variable name="groups">
<xsl:for-each-group select="document('Input.xml')/Root/*" group-ending-with="NextPart">
<group>
<xsl:copy-of select="current-group()[self::Node]"/>
</group>
</xsl:for-each-group>
</xsl:variable>
in a global variable, then in your template you do
<xsl:template match="Link">
<xsl:variable name="pos" select="position()"/>
<xsl:result-document href="{#url}" method="xml">
<xsl:copy-of select="$groups/group[$pos]/Node"/>
</xsl:result-document>
</xsl:template>
to output the Node elements grouped earlier.

XSLT: merge two tags using attribute from one, tag from another with a transform/mapping

I have two tags in the input file, variable and type:
<variable baseType="int" name="X">
</variable>
<type baseType="structure" name="Y">
<variableInstance name="X" />
</type>
And I need to generate the following output file:
<Item name="Y">
<Field name="X" type="Long" />
</Item>
So conceptually my approach here has been to convert the type tag into the Item tage, the variable instance to the Field. That's working fine:
<xsl:for-each select="type[#baseType='structure']">
<Item>
<xsl:attribute name="name"><xsl:value-of select="#name" /></xsl:attribute>
<xsl:for-each select="variableInstance">
<Field>
<xsl:attribute name="name"><xsl:value-of select="#name" /></xsl:attribute>
<xsl:attribute name="type">**THIS IS WHERE I'M STUCK**</xsl:attribute>
</Field>
</xsl:for-each>
</Item>
</xsl:for-each>
The problem I'm stuck on is:
I don't know how to get the variableInstance/Field tag to match on the variable tag by name, so I can access the baseType.
I need to map "int" to "Long" once I'm able to do 1.
Thanks in advance!
PROBLEM 1.
For the first problem that you have you can use a key:
<xsl:key name="variable-key" match="//variable" use="#name" />
That key is going to index all variable elements in the document, using their name. So now, we can access any of those elements by using the following XPath expression:
key('variable-key', 'X')
Using this approach is efficient when you have a lot of variable elements.
NOTE: this approach is not valid if each variable has its own scope (i.e. you have local variables which are not visible in different parts of the document). In that case this approach should be modified.
PROBLEM 2.
For mapping attributes you could use a template like the following:
<xsl:template match="#baseType[. = 'int']">
<xsl:attribute name="baseType">
<xsl:value-of select="'Long'" />
</xsl:attribute>
</xsl:template>
The meaning of this transformation is: each time that we match a baseType attribute with int value, it has to be replaced by a Long value.
This transformation would be in place for each #baseType attribute in the document.
Using the described strategies a solution could be:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<!-- Index all variable elements in the document by name -->
<xsl:key name="variable-key"
match="//variable"
use="#name" />
<!-- Just for demo -->
<xsl:template match="text()" />
<!-- Identity template: copy attributes by default -->
<xsl:template match="#*">
<xsl:copy>
<xsl:value-of select="." />
</xsl:copy>
</xsl:template>
<!-- Match the structure type -->
<xsl:template match="type[#baseType='structure']">
<Item>
<xsl:apply-templates select="*|#*" />
</Item>
</xsl:template>
<!-- Match the variable instance -->
<xsl:template match="variableInstance">
<Field>
<!-- Use the key to find the variable with the current name -->
<xsl:apply-templates select="#*|key('variable-key', #name)/#baseType" />
</Field>
</xsl:template>
<!-- Ignore attributes with baseType = 'structure' -->
<xsl:template match="#baseType[. = 'structure']" />
<!-- Change all baseType attributes with long values to an attribute
with the same name but with an int value -->
<xsl:template match="#baseType[. = 'int']">
<xsl:attribute name="baseType">
<xsl:value-of select="'Long'" />
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
That code is going to transform the following XML document:
<!-- The code element is present just for demo -->
<code>
<variable baseType="int" name="X" />
<type baseType="structure" name="Y">
<variableInstance name="X" />
</type>
</code>
into
<Item name="Y">
<Field baseType="Long" name="X"/>
</Item>
Oki I've got a solution for point 1 xsltcake slice or with use of templates. For point two I would probably use similar template to the one that Pablo Pozo used in his answer

how to call my template and my function sequentially in xslt 2.0?

I am using xslt2.0 for convert one xml format to another xml format. This is my sample xml document.
<w:document>
<w:body>
<w:p>Para1</w:p>
<w:p>Para2</w:p>
<w:p>Para3</w:p>
<w:p>Para4</w:p>
</w:body>
</w:document>
Initially this is my xml format.so, i handled each and every <w:p> elements through my function in xslt given below...
<xsl:template match="document">
<Document>
<xsl:sequence select="mf:group(body/p, 1,count(//w:body//w:p)-1)"/>
</Document>
</xsl:template>
So,In that xslt function, i have coded how to reformat those elements.It's working fine...
But now,Xml format is restructured like given below...
<w:document>
<w:body>
<w:tbl><!--some text with children elements--></w:tbl>
<w:tbl><!--some text with children elements--></w:tbl>
<w:p>Para1</w:p>
<w:p>Para2</w:p>
<w:p>Para3</w:p>
<w:p>Para4</w:p>
</w:body>
</w:document>
So, As of now i have to handle both and elements in a same sequence.....
What i want to do is,
If i encounter elemtents then i have to call my template given below...
<xsl:template match="document">
<Document>
<xsl:for-each select="w:tbl">
<xsl:apply-templates select="w:tbl">
</xsl:apply-templates>
</xsl:for-each>
<xsl:sequence select="mf:group(body/p, 1,count(//w:body//w:p)-1)"/>
</Document>
</xsl:template>
<xsl:template match="w:tbl">
<!--xslt code here -->
</xsl:template>
But the for-each statement is not executed when I trying transformation...
So, Please guide me to get out of this issue...
I think instead of
<xsl:template match="document">
<Document>
<xsl:for-each select="w:tbl">
<xsl:apply-templates select="w:tbl">
</xsl:apply-templates>
</xsl:for-each>
<xsl:sequence select="mf:group(body/p, 1,count(//w:body//w:p)-1)"/>
</Document>
</xsl:template>
you simply want
<xsl:template match="document">
<Document>
<xsl:apply-templates select="w:body/w:tbl"/>
<xsl:sequence select="mf:group(body/p, 1,count(//w:body//w:p)-1)"/>
</Document>
</xsl:template>
If that does not do what you want then please show the result you want.

xslt to add an attribute

I need to add a namespace, and to add an attribute to certain nodes. With this input:
<root>
<Node1>test</Node1>
<DateTo />
</root>
I want this output:
<my:root xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2010-07-28T07:33:11">
<my:Node1>test</my:Node1>
<my:DateTo xsi:nil="true"/>
</my:root>
The DateTo node needs to have this attribute set.
I successfully added the namespace with this transform, but cannot get the attribute added.
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
<xsl:template match='*'>
<xsl:element name='my:{local-name()}' namespace='http://schemas.microsoft.com/office/infopath/2003/myXSD/2010-07-28T07:33:11' >
<xsl:apply-templates />
</xsl:element>
</xsl:template>
</xsl:stylesheet>"
I get this error "Attribute and namespace nodes cannot be added to the parent element after a text, comment, pi, or sub-element node has already been added." Be grateful for any help here.
You can try the following to insert the additional attribute:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
version='1.0'>
<xsl:template match='*'>
<xsl:element name='my:{local-name()}'
xmlns:my='http://schemas.microsoft.com/office/infopath/2003/myXSD/2010-07-28T07:33:11'>
<xsl:if test="not(*) and not(normalize-space())">
<xsl:attribute name="xsi:nil">
<xsl:value-of select="true()"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
</xsl:stylesheet>