Reputation: 69
This is a very involved problem, and is above my knowledge of XSLT - I am still learning and no matter how much I read through O'Reilly's XSLT book, I am in above my head.
I have a multi faciated problem, which I have produced an input XML file for, and am going to try and explain the requirements afterwards too.
INPUT
<roottag>
<body>
<header>
<r>
<c>
<d>Header Tag</d><!-- This can include spaces-->
<e>System generated trash</e>
</c>
</r>
<r>
<c>
<d>Sub Header Tag A</d>
<e>System generated trash</e>
</c>
<c>
<d>Sub Header Value A</d>
<e>System generated trash</e>
</c>
</r>
<r>
<c>
<d>Sub Header Tag B</d>
<e>System generated trash</e>
</c>
<c>
<d>Sub Header Value B</d>
<e>System generated trash</e>
</c>
</r>
<r>
<c>
<d>Sub Header Tag C</d>
<e>System generated trash</e>
</c>
<c>
<d>Sub Header Value C</d>
<e>System generated trash</e>
</c>
</r>
</header>
<information>
<r>Body of document</r>
<r>Appears here but have an XSLT that deals with this</r>
</informtaion>
<footer>
<r>
<c>
<d>Footer Tag</d><!-- This can include spaces-->
<e>System generated trash</e>
</c>
</r>
<r>
<c>
<d>Sub Footer Tag A</d>
<e>System generated trash</e>
</c>
<c>
<d>Sub Footer Value A</d>
<e>System generated trash</e>
</c>
</r>
<r>
<c>
<d>Sub Footer Tag B</d>
<e>System generated trash</e>
</c>
<c>
<d>Sub Footer Value B</d>
<e>System generated trash</e>
</c>
</r>
<r>
<c>
<d>Sub Footer Tag C</d>
<e>System generated trash</e>
</c>
<c>
<d>Sub Footer Value C</d>
<e>System generated trash</e>
</c>
</r>
</footer>
</body>
</roottag>
OUTPUT
<?xml version="1.0" encoding="utf-8"?>
<roottag>
<body>
<header>
<HeaderTag>
<!-- without spaces -->
<HeaderName>Header Tag</HeaderName>
<!-- This needs to preserve spaces-->
</HeaderTag>
<SubHeaderTagA>
<!-- without spaces -->
<HeaderName>Sub Header Tag A</HeaderName>
<!-- This needs to preserve spaces-->
<HeaderValue>Sub Header Value A</HeaderValue>
</SubHeaderTagA>
<SubHeaderTagB>
<HeaderName>Sub Header Tag B</HeaderName>
<HeaderValue>Sub Header Value B</HeaderValue>
</SubHeaderTagB>
<SubHeaderTagC>
<HeaderName>Sub Header Tag C</HeaderName>
<HeaderValue>Sub Header Value C</HeaderValue>
</SubHeaderTagC>
</header>
<information>
<r>Body of document</r>
<r>Appears here but have an XSLT that deals with this</r>
</information>
<footer>
<FooterTag>
<FooterName>Footer Tag</FooterName>
</FooterTag>
<SubFooterTagA>
<FooterName>Sub Footer Tag A</FooterName>
<FooterValue>Sub Footer Value A</FooterValue>
</SubFooterTagA>
<SubFooterTagB>
<FooterName>Sub Footer Tag B</FooterName>
<FooterValue>Sub Footer Value B</FooterValue>
</SubFooterTagB>
<SubFooterTagC>
<FooterName>Sub Footer Tag C</FooterName>
<FooterValue>Sub Footer Value C</FooterValue>
</SubFooterTagC>
</footer>
</body>
</roottag>
So to explain the problem as I see it, and the problems I have faced.
Header or Footer Name/Value: Again, my knowledge is limited if this is even possible to, or an individual match is required for each tag?
Movement of header and footer tag: I've not included this within my required output, but I think I may need to do this - is it possible to move the header tag and footer tag outside of the body tag? so XML would be: roottag-header-body-information-/body-footer/-/rt
If you require any more clarifications, please let me know.
Upvotes: 2
Views: 774
Reputation: 12075
Deriving an element name from the element content in the source is generally a bad idea- although you can remove spaces, there's always the possibility of other special characters, and even if you do remove them all, you potentially end up with unintended duplicates. Two elements containing 1 Tag
and 2 Tag
for example, would both need to be stripped down to Tag
.
However, something like this should do the job:
<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:variable name="allowed">ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvxyz_</xsl:variable>
<xsl:template match="r[c/d]">
<xsl:variable name="elemName" select="translate(c/d,translate(c/d,$allowed,''),'')" />
<xsl:element name="{$elemName}">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="c">
<xsl:apply-templates select="@* | node()"/>
</xsl:template>
<xsl:template match="c[1]/d">
<HeaderName>
<xsl:apply-templates />
</HeaderName>
</xsl:template>
<xsl:template match="c[2]/d">
<HeaderValue>
<xsl:apply-templates />
</HeaderValue>
</xsl:template>
<xsl:template match="e" />
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This uses the 'double translate' method to strip all undesirable characters from a string. the translate
function can be used to remove all characters from a list, by specifying an empty string to translate these characters to. You can use this to remove all VALID characters from a string, leaving a string containing only the INVALID characters. You then use translate a second time to remove all those INVALID characters from the original string.
If you do need your header/footer outside the body, add in these templates:
<xsl:template match="roottag">
<xsl:copy>
<xsl:apply-templates select="body/header" />
<xsl:apply-templates select="body" />
<xsl:apply-templates select="body/footer" />
</xsl:copy>
</xsl:template>
<xsl:template match="body">
<xsl:copy>
<xsl:apply-templates select="information" />
</xsl:copy>
</xsl:template>
To ignore r
nodes where c\d
contains an equals, add this template BELOW the one matching "r[c/d]
":
<xsl:template match="r[contains(c/d,'=')]" />
Upvotes: 2
Reputation: 12154
Well there are multiple questions! So multiple answers .. I will try to write as systematic as possible hope you understand it! And feel free to ask questions!!
1.First step is to get rid of additional unwanted spaces between tags .. To achieve this have below two statements as header just below XML declaration:
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
2.Next step is to define identity temlpate! Its job is to output as is from input (except what is commanded not to output)
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
3.In your output you don't want to see <r>
element! So we'll have below lines
<xsl:template match="/roottag/body/header/r
|/roottag/body/footer/r">
<xsl:apply-templates select="@*|node()"/>
</xsl:template>
This will not copy <r>
element but let the child elements take over !! that I will write below..
4.You want to copy only first <c>
tag in every <r>
element.. and ignore rest of the <c>
nodes from output .. so define below template-override ..
<xsl:template match="/roottag/body/header/r/c
|/roottag/body/footer/r/c"/>
This will actually remove all <c>
elements from output! but below override will take care of first <c>
element only!
5.This code is for building <Header>
element in output!
<xsl:template match="/roottag/body/header/r/c[1]">
<xsl:variable name="ElementName">
<xsl:call-template name="RemoveSpaceFromValueD">
<xsl:with-param name="text" select="d/."/>
<xsl:with-param name="replace" select="' '"/>
</xsl:call-template>
</xsl:variable>
<xsl:element name="{$ElementName}">
<xsl:element name="HeaderName">
<xsl:value-of select="d/."/>
</xsl:element>
<xsl:if test="ancestor::r != ancestor::header/r[1]">
<xsl:element name="HeaderValue">
<xsl:value-of select="e/."/>
</xsl:element>
</xsl:if>
</xsl:element>
</xsl:template>
By using this code you don't have to declare different template for different <header>, <footer> and <subHeaderTag>s
what I am doing here is:
RemoveSpaceFromValueD
which will copy text from element d
, remove space from the text and assign it to variable ElementName
. ElementName
will be used to create a new Element! So Sub Header Tag C
will have <SubHeaderTagC>
as a parent :)<d>
into <HeaderName>
! <d>
from next <c>
into <HeaderValue>
!6.Repeat the same code for <Footer>
as well.
<xsl:template match="/roottag/body/footer/r/c[1]">
<xsl:variable name="ElementName">
<xsl:call-template name="RemoveSpaceFromValueD">
<xsl:with-param name="text" select="d/."/>
<xsl:with-param name="replace" select="' '"/>
</xsl:call-template>
</xsl:variable>
<xsl:element name="{$ElementName}">
<xsl:element name="FooterName">
<xsl:value-of select="d/."/>
</xsl:element>
<xsl:if test="ancestor::r != ancestor::footer/r[1]">
<xsl:element name="FooterValue">
<xsl:value-of select="e/."/>
</xsl:element>
</xsl:if>
</xsl:element>
</xsl:template>
7.And template RemoveSpaceFromValueD
:
<xsl:template name="RemoveSpaceFromValueD">
<xsl:param name="text"/>
<xsl:param name="replace"/>
<xsl:choose>
<xsl:when test="contains($text,$replace)">
<xsl:value-of select="substring-before($text,$replace)"/>
<xsl:call-template name="RemoveSpaceFromValueD">
<xsl:with-param name="text" select="substring-after($text,$replace)"/>
<xsl:with-param name="replace" select="$replace"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
8.And the whole XSLT code looks like this:
Test this! and let me know if you have any questions or difficulties in understanding.
<?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:strip-space elements="*"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/roottag/body/header/r
|/roottag/body/footer/r">
<xsl:apply-templates select="@*|node()"/>
</xsl:template>
<xsl:template match="/roottag/body/header/r/c
|/roottag/body/footer/r/c"/>
<xsl:template match="/roottag/body/header/r/c[1]">
<xsl:variable name="ElementName">
<xsl:call-template name="RemoveSpaceFromValueD">
<xsl:with-param name="text" select="d/."/>
<xsl:with-param name="replace" select="' '"/>
</xsl:call-template>
</xsl:variable>
<xsl:element name="{$ElementName}">
<xsl:element name="HeaderName">
<xsl:value-of select="d/."/>
</xsl:element>
<xsl:if test="ancestor::r != ancestor::header/r[1]">
<xsl:for-each select="../c[2]">
<xsl:element name="HeaderValue">
<xsl:value-of select="d/."/>
</xsl:element>
</xsl:for-each>
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template match="/roottag/body/footer/r/c[1]">
<xsl:variable name="ElementName">
<xsl:call-template name="RemoveSpaceFromValueD">
<xsl:with-param name="text" select="d/."/>
<xsl:with-param name="replace" select="' '"/>
</xsl:call-template>
</xsl:variable>
<xsl:element name="{$ElementName}">
<xsl:element name="FooterName">
<xsl:value-of select="d/."/>
</xsl:element>
<xsl:for-each select="../c[2]">
<xsl:element name="FooterValue">
<xsl:value-of select="d/."/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
<xsl:template name="RemoveSpaceFromValueD">
<xsl:param name="text"/>
<xsl:param name="replace"/>
<xsl:choose>
<xsl:when test="contains($text,$replace)">
<xsl:value-of select="substring-before($text,$replace)"/>
<xsl:call-template name="RemoveSpaceFromValueD">
<xsl:with-param name="text" select="substring-after($text,$replace)"/>
<xsl:with-param name="replace" select="$replace"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1