Convert All Node's Attributes into Child Nodes

Convert all node's attributes into child nodes

Tested on Oxygen/XML using Saxon6.5:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>

<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>

<xsl:template match="@*">
<xsl:element name="{name()}"><xsl:value-of select="."/></xsl:element>
</xsl:template>

</xsl:stylesheet>

This is based on using an identity template for element nodes and a template that converts attributes to elements.

Merge all values of a node's child nodes

Re your Update 2

The given input:

XML

<p class="s3">
<span class="s4">
<span>Read</span>
</span>
<span class="s4">
<span> </span>
</span>
<span class="s4">
<span>the</span>
</span>
<span class="s4">
<span> information </span>
</span>
<span class="s4">
<span>sheet</span>
</span>
<span class="s4">
<span> </span>
</span>
<span class="s4">
<span>carefully</span>
</span>
</p>

poses conflicting requirements:

  • on the one hand, you want to strip the whitespace nodes surrounding
    the inner span elements;

  • on the other hand, you want to preserve the spaces contained within the inner span elements.

Since both the inner and the outer elements are named the same, this is quite impossible.

What you could do is pre-process the input XML using the following stylesheet:

1st XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>

<xsl:template match="span[not(@class)]">
<new-span>
<xsl:apply-templates select="@*|node()"/>
</new-span>
</xsl:template>

</xsl:stylesheet>

to produce:

2nd XML

<p class="s3">
<span class="s4">
<new-span>Read</new-span>
</span>
<span class="s4">
<new-span> </new-span>
</span>
<span class="s4">
<new-span>the</new-span>
</span>
<span class="s4">
<new-span> information </new-span>
</span>
<span class="s4">
<new-span>sheet</new-span>
</span>
<span class="s4">
<new-span> </new-span>
</span>
<span class="s4">
<new-span>carefully</new-span>
</span>
</p>

Then process the result with the next stylesheet:

2nd XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:preserve-space elements="new-span"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>

<xsl:template match="p">
<xsl:copy>
<xsl:value-of select='.'/>
</xsl:copy>
</xsl:template>

</xsl:stylesheet>

to produce the final result:

<?xml version="1.0" encoding="UTF-8"?>
<p>Read the information sheet carefully</p>

Transform all child siblings into parent-like node

I would suggest to approach it like this:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>

<xsl:template match="line[*]">
<xsl:apply-templates/>
</xsl:template>

<xsl:template match="line/*">
<line>
<xsl:copy-of select="../@* | @*"/>
</line>
</xsl:template>

</xsl:stylesheet>

Converting a single XML attribute of element to child element

It might suffice to first process the attributes you want to copy as attributes with one apply-templates (or simply use copy-of for them) and to then apply-templates to the id attribute and any child nodes:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*[not(name()='id')]"/>
<xsl:apply-templates select="@id | node()"/>
</xsl:copy>
</xsl:template>

<xsl:template match="@id">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/bEzkntb/7

In XSLT, how to copy all attributes from all child nodes to root node of xml?

You could do quite simply:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/root">
<xsl:copy>
<xsl:copy-of select="//@*"/>
</xsl:copy>
</xsl:template>

</xsl:stylesheet>

However, an element cannot have two attributes with the same name. If your XML has more than one instance of the same attribute, they will overwrite each other and only the last one will be present in the output.

How to add attribute to all particular children of a node

First, let me point out that there is a difference in how this is done for just in-memory use versus updating the content of the database. For the latter, you could do:

for $add in $test//add
return
xdmp:node-insert-child(
$add,
attribute atta1 { 1 }
)

To change it in memory, which is what functx does, you'll be making a copy of the original, making changes in the copy as you build it. This is called recursive descent and is a pretty common pattern. I wrote a blog post a while ago that shows how to implement recursive descent, but essentially you'll do a typeswitch that, when it encounters an "add" element, creates the new attribute. You can use the functx function for that. Something along these lines (untested):

declare function local:change($node) 
{
typeswitch($node)
case element(add) return
functx:add-attributes($node, xs:QName('att1'), 1)
case element() return
element { fn:node-name($node) } {
$node/@*,
$node/node() ! local:change(.)
}
default return $node
};

This code assumes that an add element won't have add elements inside of it; if you will, then you'd want to do something like the second case for the first.

Attaching ancestor attributes to child nodes

To get the desired output, you can create a template that uses 2 params in the input and prints the data in the required structure/format.

<xsl:template name="printValues">
<xsl:param name="val1" />
<xsl:param name="val2" />

<xsl:text>"</xsl:text>
<xsl:value-of select="$val1" />
<xsl:text>"</xsl:text>

<xsl:text>,</xsl:text>
<xsl:text>"</xsl:text>
<xsl:value-of select="$val2" />
<xsl:text>"</xsl:text>
<xsl:text> </xsl:text>
</xsl:template>

This template can be called from the <xsl:for-each> loop using the values that need to printed.

<xsl:template match="/">
<xsl:for-each select="//Chemical[@displayName != '' and @displayName != 'INDEX NAME NOT YET ASSIGNED']">
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="@id" />
<xsl:with-param name="val2" select="@displayName" />
</xsl:call-template>
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="@id" />
<xsl:with-param name="val2" select="normalize-space(NameList/SystematicName/text())" />
</xsl:call-template>
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="@id" />
<xsl:with-param name="val2" select="normalize-space(NameList/Synonyms/text())" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>

This will give the desired output.

"000036884","Carotene"
"000036884","Carotenes and Carotenoids"
"000036884","Phytoxanthins"

Convert all XML elements without children to XML attributes of parent element

Below is a very generic solution to your problem. It makes only minimal assumptions about the nature of the input XML. What it does assume is only that elements that have child elements should include all of them as attributes.

Apart from that, any input XML will do. Tested with Saxon 6.5, try it online here. What it does, in plain English, is:

Match an element that has at least one child element. Copy this element to the
output. For each of its child element that does not itself have child
elements, add an attribute which is named after the "childless" child
element, and add its text content as the attribute value.

Stylesheet

EDIT: As a response to your comment. Now, the stylesheet gives the correct output for an arbitrary number of "levels".

 <?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />

<xsl:strip-space elements="*"/>

<xsl:template match="*[*]">
<xsl:copy>
<xsl:for-each select="*[not(*)]">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>

<xsl:template match="text()"/>
</xsl:transform>

XML Output

<?xml version="1.0" encoding="UTF-8"?>
<EnvelopeType1 CustomerNumber="1" CustomerName="Me">
<Header User="user1" Password="password"/>
<Address Number="5" StreetName="High St" State="MI"/>
</EnvelopeType1>


Related Topics



Leave a reply



Submit