Add a new sibling node after matching attribute value - xslt

I need to add a node after a template match.
The template match is on an attribute value, based on a parameter.
I have been successful at adding the node as a child of the matched attribute. However, I need the added node to be a sibling node, not a child node.
Is there a way to add the note as a sibling, not as a child of the matched attribute?
This is my xml file:
<WORK SRCDBID="DBIDxx" DSTDBID="SERVER" WORKTYPE="DELTA" SETNUMBER="1">
<TXID SRCDBID="DBIDxx" CPDATE="2021200932651" TYPE="0">
<OP ACTION="I" TBL="RTD-WORKORDER">
<COLS>
<COL NAME="WoNum" VAL="303105525"/>
<COL NAME="NumWoLin" VAL="1"/>
<COL NAME="LinNum" VAL="1"/>
<COL NAME="RtrdTag" VAL="527395802"/>
</COLS>
</OP>
</TXID>
</WORK>
My failing result:
<WORK SRCDBID="DBIDxx" DSTDBID="SERVER" WORKTYPE="DELTA" SETNUMBER="1">
<TXID SRCDBID="DBIDxx" CPDATE="2021200932651" TYPE="0">
<OP ACTION="I" TBL="RTD-WORKORDER">
<COLS>
<COL NAME="WoNum" VAL="303105525"/>
<COL NAME="NumWoLin" VAL="1"/>
<COL NAME="LinNum" VAL="1"/>
<COL NAME="RtrdTag" VAL="527395802">
<COL NAME="DuplicateTag" VAL="0303105525|31"/>
</COL>
</COLS>
</OP>
</TXID>
</WORK>
My desired result:
<WORK SRCDBID="DBIDxx" DSTDBID="SERVER" WORKTYPE="DELTA" SETNUMBER="1">
<TXID SRCDBID="DBIDxx" CPDATE="2021200932651" TYPE="0">
<OP ACTION="I" TBL="RTD-WORKORDER">
<COLS>
<COL NAME="WoNum" VAL="303105525"/>
<COL NAME="NumWoLin" VAL="1"/>
<COL NAME="LinNum" VAL="1"/>
<COL NAME="RtrdTag" VAL="527395802"/>
<COL NAME="DuplicateTag" VAL="031123123|31"/>
</COLS>
</OP>
</TXID>
</WORK>
My xsl:
Parameter values are:
rtdTag = "527395802"
rtdDupTag = "0303105525|31"
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" media-type="text/xml" method="xml" />
<xsl:param name="rtdTag" />
<xsl:param name="rtdDupTag" />
<!-- This is the default template that copied everything -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- This is the "override" template for specific elements that match the passed in tag. -->
<xsl:template match="#*[. = $rtdTag]">
<!-- Copy the element everything inside it -->
<xsl:copy>
<xsl:copy-of select="node()"/>
</xsl:copy>
<!-- Add new node -->
<xsl:element name="COL">
<xsl:attribute name="NAME">
<xsl:text>DuplicateTag</xsl:text>
</xsl:attribute>
<xsl:attribute name="VAL">
<xsl:value-of select="$rtdDupTag"/>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

Change your template to match on the element that has that attribute, and then add your new element after copying that element.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" media-type="text/xml" method="xml" />
<xsl:param name="rtdTag" />
<xsl:param name="rtdDupTag" />
<!-- This is the default template that copied everything -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- This is the "override" template for specific elements that match the passed in tag. -->
<xsl:template match="*[#* = $rtdTag]">
<!-- Copy the element everything inside it -->
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
<!-- Add new node -->
<xsl:element name="COL">
<xsl:attribute name="NAME">
<xsl:text>DuplicateTag</xsl:text>
</xsl:attribute>
<xsl:attribute name="VAL">
<xsl:value-of select="$rtdDupTag"/>
</xsl:attribute>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
And if you know the names of the element and attribute statically, I prefer element and attribute literals instead of using xsl:element and xsl:attribute:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" media-type="text/xml" method="xml" />
<xsl:param name="rtdTag" />
<xsl:param name="rtdDupTag" />
<!-- This is the default template that copied everything -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- This is the "override" template for specific elements that match the passed in tag. -->
<xsl:template match="*[#* = $rtdTag]">
<!-- Copy the element everything inside it -->
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
<!-- Add new node -->
<COL NAME="DuplicateTag" VAL="{$rtdDupTag}"/>
</xsl:template>
</xsl:stylesheet>

Related

XSLT modify attribute value at EXACT element by passing the original value to a template

I'm struggling to get this abomination called XSLT to work. I need to get an EXACT attribute at EXACT path, pass its original value to a template and rewrite this value with the result from the template.
I'm having a file like this:
<?xml version="1.0" encoding="windows-1251"?>
<File>
<Document ReportYear="17">
...
...
</Document>
</File>
So I made an XSLT like this:
<?xml version="1.0" encoding="windows-1251"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" encoding="windows-1251" indent="yes" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template name="formatYear">
<xsl:param name="year" />
<xsl:value-of select="$year + 2000" />
</xsl:template>
<xsl:template match="File/Document">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:attribute name="ReportYear">
<xsl:call-template name="formatYear">
<xsl:with-param name="year" select="#ReportYear" />
</xsl:call-template>
</xsl:attribute>
</xsl:copy>
<xsl:apply-templates />
</xsl:template>
</xsl:stylesheet>
This works fine except it closes the <Document> tag immediately and places its content immediately after itself.
Also, can I address the ReportYear attribute value without repeating it twice? I tried current() but it didn't work.
If you're closing <xsl:copy> before applying templates to the remainder of the content of <Document>, then of course <Document> will be closed before the remainder of the content of <Document> appears in the output.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" encoding="windows-1251" indent="yes" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Document">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:attribute name="ReportYear">
<xsl:value-of select="#ReportYear + 2000" />
</xsl:attribute>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
outputs
<?xml version="1.0" encoding="windows-1251"?>
<File>
<Document ReportYear="2017">
...
...
</Document>
</File>
I don't think an extra template just for adding 2000 to #ReportYear is necessary. But if you must, you can streamline the whole thing like so
<xsl:template name="formatYear">
<xsl:param name="year" select="#ReportYear" /> <!-- you can define a default value -->
<xsl:value-of select="$year + 2000" />
</xsl:template>
and
<xsl:attribute name="ReportYear">
<xsl:call-template name="formatYear" /> <!-- ...and can use it implicitly here -->
</xsl:attribute>
If you need to process the contents of the Document element with apply-templates and want to keep the result of the applied templates as the children then you need to move the apply-templates inside of the copy:
<xsl:template match="File/Document">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="ReportYear">
<xsl:call-template name="formatYear">
<xsl:with-param name="year" select="#ReportYear"/>
</xsl:call-template>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
Not sure why you haven't simply used
<xsl:template match="File/Document/#ReportYear">
<xsl:attribute name="{name()}">
<xsl:value-of select=". + 2000"/>
</xsl:attribute>
</xsl:template>
together with the identity transformation template.

XSL to merge two xml

I'm trying to created an xsl that will copy file1, search file2 for matching file1 node and change that node's attribute to file2 value. I'm struggle to get the below code to work. It transform the first node correctly but on the second node it uses the previously found attribute.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="no" indent="yes"/>
<xsl:param name="fileName" select="'file2'" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="stylesheet/variable">
<xsl:for-each select=".">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:if test="document($fileName)/stylesheet/variable[#name = #name]">
<xsl:attribute name="value">
<xsl:value-of select="document($fileName)/stylesheet/variable[#name = #name]/#select"/>
</xsl:attribute>
</xsl:if>
</xsl:copy>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Here's the xml file I'm trying to merge into one file
<!--File1 xml -->
<stylesheet>
<variable name="Test" />
<variable name="Test2" select="'yy'"/>
<variable name="Test3" select="'xx'"/>
</sytlesheet>
<!--File2 xml -->
<stylesheet>
<variable name="Test" select="'x'" />
<variable name="Test2" select="'y'" />
</sytlesheet>
Any idea where I'm going wrong?
variable[#name = #name] is not what you want, you want variable[#name = current()/#name]. The use of <xsl:for-each select="."> is superfluous.

xsl to move node under precibling parent based on matching of parent attribute

I'm trying to handle a xml converted from a pdf to another xml file in some format. First I want to move / group some text / node together based on the geometry of the text but failed to do so. The following is my input & what I wanted:
input xml:
<Pages>
<Page>
<PAGENUMBER>1</PAGENUMBER>
<Box llx="59.40" lly="560.64" urx="68.58" ury="571.68">
<Text>5.</Text>
</Box>
<Box llx="81.84" lly="560.64" urx="194.39" ury="571.68">
<Text>Equipment list</Text>
</Box>
<Box llx="257.40" lly="560.64" urx="265.36" ury="571.68">
<Text>C</Text>
</Box>
<Box llx="315.84" lly="535.32" urx="325.63" ury="546.36">
<Text>a)</Text>
</Box>
</Page>
<Page>
same structure as above...
</Page>
</Pages>
Output xml:
<Pages>
<Page>
<PAGENUMBER>1</PAGENUMBER>
<Box llx="59.40" lly="560.64" urx="68.58" ury="571.68">
<Text>5. Equipment list C</Text>
</Box>
<Box llx="315.84" lly="535.32" urx="325.63" ury="546.36">
<Text>a)</Text>
</Box>
</Page>
<Page>
same structure as above...
</Page>
</Pages>
What i have:
<xsl:template match="#*|node()" name = "identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Box">
<xsl:choose>
<xsl:when test="#ury = following-sibling::Box/#ury">
<xsl:call-template name="identity"/>
<xsl:apply-templates select ="#*"/>
<xsl:copy-of select="following-sibling::Box/Text"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
1.It doesn't copy the wanted nodes 2. i don't know how to exclude the following nodes. I hope someone can help me on this. Many thanks in advance.
I tried the following to exclude the duplicates but it doesn't copy what i want anyways:
<xsl:template match="Box[#ury != preceding-sibling::Box/#ury]/Text">
<xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>
This is a case of muenchian grouping in which you need to group the nodes based on certain common criteria and process them to provide an output.
Based on the version of XSLT being used, the solution differs for XSLT 1.0 and XSLT 2.0
XSLT 1.0
Version 1.0 uses a <xsl:key> to group the elements based on common criteria. In this case, the grouping is being done based on the value of attribute #ury so we define a key
<xsl:key name="groupingKey" match="Box" use="#ury" />
Using this key, the templates are grouped together for processing.
<xsl:template match="Box[generate-id() = generate-id(key('groupingKey', #ury)[1])]">
Finally within the grouped elements, a loop is run over the <Text> elements to concatenate its values.
<Text>
<xsl:variable name="fullText">
<xsl:for-each select="key('groupingKey', #ury)/Text">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="normalize-space($fullText)" />
</Text>
Below is the complete XSLT 1.0
<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:key name="groupingKey" match="Box" use="#ury" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Box[generate-id() = generate-id(key('groupingKey', #ury)[1])]">
<xsl:copy>
<xsl:apply-templates select="#*" />
<Text>
<xsl:variable name="fullText">
<xsl:for-each select="key('groupingKey', #ury)/Text">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="normalize-space($fullText)" />
</Text>
</xsl:copy>
</xsl:template>
<xsl:template match="Box" />
</xsl:stylesheet>
XSLT 2.0
Version 2.0 is advanced and provides a simpler approach as compared to XSLT 1.0. The <xsl:for-each-group> and group-by feature can be used to group the elements together.
<xsl:for-each-group select="Box" group-by="#ury">
Below is the complete XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Page">
<xsl:copy>
<xsl:apply-templates select="PAGENUMBER" />
<xsl:for-each-group select="Box" group-by="#ury">
<xsl:copy>
<xsl:apply-templates select="#*" />
<Text>
<xsl:variable name="fullText">
<xsl:for-each select="current-group()/Text">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="normalize-space($fullText)" />
</Text>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Both the XSLT provide the required output
<Pages>
<Page>
<PAGENUMBER>1</PAGENUMBER>
<Box llx="59.40" lly="560.64" urx="68.58" ury="571.68">
<Text>5. Equipment list C</Text>
</Box>
<Box llx="315.84" lly="535.32" urx="325.63" ury="546.36">
<Text>a)</Text>
</Box>
</Page>
</Pages>

Count Nodes and Duplicate with no values in XSLT

I am super rusty at XSLT and was wondering if someone can give me some pointers.
Edit: Using XSLT 1.0
Original XML:
<gic>
<application>
<agent>
...child nodes
</agent>
<client>
...child nodes
</client>
<bank>
...child nodes
</bank>
</application>
</gic>
I need to transform the given XML INPUT to have 5 client nodes. The input can contain 1-5 client nodes populated. I need to ensure there is always 5 in the output. In this case, one is provide, so I need to insert 4 client nodes with all child nodes. Values for all child nodes need to be empty. Output in XML
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="application" as="node()*">
<xsl:copy>
<xsl:apply-templates select="agent" />
<!-- copy existing cliens -->
<xsl:apply-templates select="client" />
<!-- add new clients -->
<xsl:call-template name="AddClients">
<xsl:with-param name="times" select="10 - count(client)" />
</xsl:call-template>
<!-- copy banks -->
<xsl:apply-templates select="bank" />
</xsl:copy>
</xsl:template>
<xsl:template name="AddClients">
<xsl:param name="times" select="1" />
<xsl:if test="number($times) > 0">
<!-- new element here -->
<xsl:element name="client">
<xsl:attribute name="a1">
<xsl:value-of select="asas" />
</xsl:attribute>
</xsl:element>
<xsl:call-template name="AddClients">
<xsl:with-param name="times" select="$times - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<!-- default -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Wrap two nodes with values greater than 0 with a div

I need to convert the following xml with xslt
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>0</item>
<item>6</item>
into the following html
<div>
<i>1</i>
<i>2</i>
</div>
<div>
<i>3</i>
<i>6</i>
</div>
In other words to remove nodes with 0 value and to wrap every 2 nodes with one div
I would do it like this:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="value" select="0"/>
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="item[not(. = $value)][position() mod 2 = 1]" mode="group"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item" mode="group">
<div>
<xsl:apply-templates select=". | following-sibling::item[not(. = $value)][1]"/>
</div>
</xsl:template>
</xsl:stylesheet>
then with the input being
<root>
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>0</item>
<item>6</item>
</root>
you get the result
<root>
<div>
<item>1</item>
<item>2</item>
</div>
<div>
<item>3</item>
<item>6</item>
</div>
</root>
If you also want to transform item to i elements simply add the template
<xsl:template match="item">
<i>
<xsl:apply-templates/>
</i>
</xsl:template>
in the stylesheet.