Reputation: 57
Input XML:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<text>
<body>
<head>title1</head>
<div n="1">
<head>title2</head>
<div n="1.1">
<head>title3</head>
<p>xyz</p>
<p>xyz</p>
</div>
<div n="1.2">
<head>title4</head>
<p>xyz</p>
<p>xyz</p>
<div n="1.2.1">
<p>xyz</p>
<p>xyz</p>
</div>
</div>
</div>
</body>
</text>
</Root>
I would like to add an attribute "id=1" to all <div>
elements and an attribute "level=0" to the <div>
which have no <div>
child.
This is my actual XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="@*|node()" priority="-1">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="body//div">
<xsl:copy>
<xsl:apply-templates select="node()[not(descendant::div)]" mode="level"/>
<xsl:apply-templates select="node()[descendant::div]" mode="id"/>
</xsl:copy>
</xsl:template>
<xsl:template match="body//div[not(descendant::div)]" mode="level">
<xsl:copy>
<xsl:for-each select=".">
<xsl:attribute name="id">1</xsl:attribute>
<xsl:attribute name="level">0</xsl:attribute>
</xsl:for-each>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="body//div[descendant::div]" mode="id">
<xsl:copy>
<xsl:for-each select=".">
<xsl:attribute name="id">1</xsl:attribute>
</xsl:for-each>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Template with mode="id" should match <div>
elements with <div>
child (adding attribute "id"), template with mode="level" should match elements without <div>
child (adding both attribut "id" and "level"). But for some reason I get some <div>
(those with n=1 and n=1.2.1) not being processed by any template.
Actual output XML:
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<text>
<body>
<head>title1</head>
<div>
title2
<div id="1" level="0" n="1.1">
<head>title3</head>
<p>xyz</p>
<p>xyz</p>
</div>
<div id="1" n="1.2">
<head>title4</head>
<p>xyz</p>
<p>xyz</p>
<div>
xyz
xyz
</div>
</div>
</div>
</body>
</text>
</Root>
I'm pretty sure I'm making some oddly obvious mistake (maybe in the use of template with mode?), but haven't spotted it so far. Any suggestion would be very appreciated. Thank you all.
Upvotes: 2
Views: 1122
Reputation: 22637
I'm not entirely sure why you use modes in the first place. The solution below achieves what you want using only "common" templates.
Templates with modes are designed specifically for iterating the tree more than once and hence accessing and processing the nodes twice or more times.
As a rule, interrupt an identity transform as seldom as possible. That is why the stylesheet below matches
div
elements with div
children (div[div]
) div[not(div)]
),applies minimal changes and then returns the remainder of the nodes to the identity transform again with apply-templates
.
Note: this suits the input XML you have shown. Yet, to make it work with your actual input, you might have to change
<xsl:template match="div[div]">
to
<xsl:template match="div[descendant::div]">
depending on your input structure.
Stylesheet
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.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="div[div]">
<xsl:copy>
<xsl:attribute name="id">1</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="div[not(div)]">
<xsl:copy>
<xsl:attribute name="id">1</xsl:attribute>
<xsl:attribute name="level">0</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<text>
<body>
<head>title1</head>
<div id="1">
<head>title2</head>
<div id="1" level="0">
<head>title3</head>
<p>xyz</p>
<p>xyz</p>
</div>
<div id="1">
<head>title4</head>
<p>xyz</p>
<p>xyz</p>
<div id="1" level="0">
<p>xyz</p>
<p>xyz</p>
</div>
</div>
</div>
</body>
</text>
</Root>
Upvotes: 4