Adding missing output elements to an identity transform (newbie-ish)

K

Kolossi

After reading lots of helpful posts here, and www.dpawson.co.uk, I
think I've got the hang of processing exceptions to the identity
transform (I'll call it "identity-"), but have a reasonably
straightforward problem that I'd like to find the most elegant solution
to.

I need to do an identity transform, but if certain elements are
missing, add them, (I'll call this "identity+"). Since XSL rules are
based on matching elements, I can't see how to code a rule as an
exception to the identity transform if I haven't got any input to match
it on.

Here's an example. I want to copy the input, but if it doesn't have an
<author> element anywhere under the root node, I want to add one at the
end, so I want to turn

<a>
<b>foo</b>
<c>bar</c>
<d>boof</d>
</a>

into

<a>
<b>foo</b>
<c>bar</c>
<d>boof</d>
<author>UNKNOWN</author>
</a>

but just identity transform

<a>
<author>me<author>
<e>1</e>
<f>2</f>
</a>

into

<a>
<author>me<author>
<e>1</e>
<f>2</f>
</a>

If I knew all the possible elements below the root node, I guess I
could match the root node, then explicitly copy them all and add the
<author> element at the end if I hadn't set a variable to say I'd found
one, but I'm hoping there is a more elegant solution.

TIA

Paul Sweeney
 
?

=?ISO-8859-1?Q?Joachim_Wei=DF?=

Kolossi said:
After reading lots of helpful posts here, and www.dpawson.co.uk, I
think I've got the hang of processing exceptions to the identity
transform (I'll call it "identity-"), but have a reasonably
straightforward problem that I'd like to find the most elegant solution
to.
....

Try somthing like this:

<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>

<xsl:template match="a[count(author)=1]">
<xsl:apply-templates />
</xsl:template>

<xsl:template match="a[count(author)=0]">
<xsl:element name="author">unknown</xsl:element>
<xsl:apply-templates />
</xsl:template>


<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>

HIH

Jo
 
J

Joris Gillis

Tempore 19:54:02 said:
Kolossi said:
After reading lots of helpful posts here, and www.dpawson.co.uk, I
think I've got the hang of processing exceptions to the identity
transform (I'll call it "identity-"), but have a reasonably
straightforward problem that I'd like to find the most elegant solution
to.
...

Try somthing like this:

<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>

<xsl:template match="a[count(author)=1]">
<xsl:apply-templates />
</xsl:template>

<xsl:template match="a[count(author)=0]">
<xsl:element name="author">unknown</xsl:element>
<xsl:apply-templates />
</xsl:template>

"a[author]" and "a[not(author)]" would also work
 
K

Kolossi

Thanks to both Joachim and Joris, great answers! :)

Unfortunately, I over-simpified my example, as the "real" problem has
multiple of these potentially-missing elements, so maybe I have to add
default values for

<author>UNKNOWN</author>
<title>UNKNOWN</title>
<location>
<physical>UNKNOWN</physical>
<web>http://UNKNOWN</web>
</location>

of course all, some or none of these could be missing in the input.

So how could I extend your solution to multiple elements?

Thanks for your help

Paul Sweeney
 
?

=?ISO-8859-1?Q?Joachim_Wei=DF?=

Kolossi said:
Thanks to both Joachim and Joris, great answers! :)

Unfortunately, I over-simpified my example, as the "real" problem has
multiple of these potentially-missing elements, so maybe I have to add
default values for

Hi Paul,

<xsl:template match="a">
<xsl:if test="not(author)">
<xsl:element ...
</xsl:if>
<xsl:if test="not(title)">
<xsl:element ...
</xsl:if>
...

<xsl:apply-templates />
</xsl:template>


would be my first quick and dirty approach to this
if you need a flexible solution, eg if the structure of your input
varies, i would use sth like this

my test input:

<?xml version="1.0" encoding="UTF-8"?>
<in>
<a>
<b>defined</b>
<c>defined</c>
<d>always defined</d>
<link>www.somwhere.com</link>
</a>
<a>
<d>always defined</d>
<link>www.nowhere.com</link>
</a>

<a>
<c>defined</c>
<d>always defined</d>
</a>
</in>


------------------------------
the stylesheet

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:eek:utput method="xml" />
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>

<xsl:template match="in">
<xsl:element name="out">
<xsl:apply-templates />
</xsl:element>
</xsl:template>

<!-- here come the interesting part -->
<xsl:template match="a">
<xsl:element name="a">
<!-- calling the check template -->
<xsl:call-template name="check">
<!-- with the node name to check as param -->
<xsl:with-param name="node">b</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="check">
<xsl:with-param name="node">c</xsl:with-param>
</xsl:call-template>

<!-- for links we construct a special template -->
<xsl:call-template name="link" />

<xsl:apply-templates />
</xsl:element>
</xsl:template>

<xsl:template name="check">
<xsl:param name="node" />
<!-- if the node does not exist -->
<xsl:if test="not(*[name()=$node])">
<!-- create it with the value undefined -->
<xsl:element name="{$node}">undefined</xsl:element>
</xsl:if>
</xsl:template>

<!-- the special link template -->
<xsl:template name="link">
<xsl:if test="not(link)">
<xsl:element name="link">www.undefined.com</xsl:element>
</xsl:if>
</xsl:template>

<!-- default work -->
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>

</xsl:stylesheet>

---------------------

Hope it helps


Jo
 
J

Joris Gillis

Hi,

Tempore 11:15:47 said:
Unfortunately, I over-simpified my example, as the "real" problem has
multiple of these potentially-missing elements, so maybe I have to add
default values for

<author>UNKNOWN</author>
<title>UNKNOWN</title>
<location>
<physical>UNKNOWN</physical>
<web>http://UNKNOWN</web>
</location>

of course all, some or none of these could be missing in the input.

Here's a juicy generic solution:

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

<xsl:eek:utput method="xml" indent="yes"/>

<var:root>
<author>UNKNOWN</author>
<title>UNKNOWN</title>
<location>
<physical>UNKNOWN</physical>
<web>http://UNKNOWN</web>
</location>
</var:root>

<xsl:variable name="var" select="document('')/xsl:stylesheet/var:root"/>

<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="@*" />
<xsl:variable name="names">
<xsl:for-each select="*">
<xsl:text/>|<xsl:value-of select="name()"/>|<xsl:text/>
</xsl:for-each>
</xsl:variable>
<xsl:apply-templates select="
$var[current()/self::a]/*
[not(contains($names,concat('|',name(),'|')))]|
$var[current()/self::location]/location/*
[not(contains($names,concat('|',name(),'|')))]|
node()" />
</xsl:element>
</xsl:template>

</xsl:stylesheet>


regards,
 
K

Kolossi

You two guys make a great team :)

These look like just the solutions I was after, although I haven't had
a chance to try them yet.

To a newbie, these also show a number of XSL techniques that are
difficult to start to use without good examples.

Many thanks

Paul Sweeney.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,906
Latest member
SkinfixSkintag

Latest Threads

Top