another XSLT problem

N

ned786

Hello,

I'm trying to solve an XSLT problem, and I'm hoping someone can give a
little guidance. I am transforming XML to HTML.

Here's an example of the XML file I'm dealing with, greatly simplified:

-----------------
<root>

<object name="a">
<item name="a1">Text here</item>
<item name="readonlya2">Text here</item>
<item name="a3">Text here</item>
<item name="readonlya4">Text here</item>
<item name="a5">Text here</item>
</object>

<object name="b">
<item name="b1">Text here</item>
<item name="b2">Text here</item>
<item name="b3">Text here</item>
<item name="b4">Text here</item>
<item name="b5">Text here</item>
</object>

</root>
----------------------

An <object> element contains several <item> elements. I handle each
<object> separately.

If an <object> contains no read-only <item> elements (identified by
name="readonly.."), I want to print "None" in the HTML output.

If there is one or more read-only <item> in an <object>, I will display
those read-only <item> elements in an HTML table.

It seemed simple, but I have tried using a key, and a recursive
template, and anything else I could think of. I can't find out if there
are any read-only <item>s before putting something in the result tree.
If I could set a global variable from within a template, then it would
be easy to check that variable to tell whether or not to create a table
or print "None." But that is apparently not possible.

Have I missed something?

Thanks!
Mark
 
D

Dimitre Novatchev

ned786 said:
Hello,

I'm trying to solve an XSLT problem, and I'm hoping someone can give a
little guidance. I am transforming XML to HTML.

Here's an example of the XML file I'm dealing with, greatly simplified:

-----------------
<root>

<object name="a">
<item name="a1">Text here</item>
<item name="readonlya2">Text here</item>
<item name="a3">Text here</item>
<item name="readonlya4">Text here</item>
<item name="a5">Text here</item>
</object>

<object name="b">
<item name="b1">Text here</item>
<item name="b2">Text here</item>
<item name="b3">Text here</item>
<item name="b4">Text here</item>
<item name="b5">Text here</item>
</object>

</root>
----------------------

An <object> element contains several <item> elements. I handle each
<object> separately.

If an <object> contains no read-only <item> elements (identified by
name="readonly.."), I want to print "None" in the HTML output.

If there is one or more read-only <item> in an <object>, I will display
those read-only <item> elements in an HTML table.

It seemed simple, but I have tried using a key, and a recursive
template, and anything else I could think of. I can't find out if there
are any read-only <item>s before putting something in the result tree.
If I could set a global variable from within a template, then it would
be easy to check that variable to tell whether or not to create a table
or print "None." But that is apparently not possible.

Use:

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

<xsl:template match="object">
<xsl:choose>
<xsl:when test="not(*[starts-with(@name,'readonly')])">
None
</xsl:when>
<xsl:eek:therwise>
<table>
<xsl:for-each select="*[starts-with(@name,'readonly')]">
<tr><td><xsl:value-of select="@name"/></td></tr>
</xsl:for-each>
</table>
</xsl:eek:therwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
 
N

ned786

Thank you! I will check this out. It worked on my simplified XML file,
I will see what I get on the real thing.

I didn't know you could put a predicate [...] on an asterisk (*), as in
*[starts-with...etc].

Mark
 
P

Peter Flynn

ned786 said:
Hello,

I'm trying to solve an XSLT problem, and I'm hoping someone can give a
little guidance. I am transforming XML to HTML.

Here's an example of the XML file I'm dealing with, greatly simplified:

-----------------
<root>

<object name="a">
<item name="a1">Text here</item>
<item name="readonlya2">Text here</item>
<item name="a3">Text here</item>
<item name="readonlya4">Text here</item>
<item name="a5">Text here</item>
</object>

<object name="b">
<item name="b1">Text here</item>
<item name="b2">Text here</item>
<item name="b3">Text here</item>
<item name="b4">Text here</item>
<item name="b5">Text here</item>
</object>

</root>
----------------------

An <object> element contains several <item> elements. I handle each
<object> separately.

If an <object> contains no read-only <item> elements (identified by
name="readonly.."), I want to print "None" in the HTML output.

If there is one or more read-only <item> in an <object>, I will display
those read-only <item> elements in an HTML table.

It seemed simple, but I have tried using a key, and a recursive
template, and anything else I could think of. I can't find out if there
are any read-only <item>s before putting something in the result tree.

<xsl:template match="object">
<xsl:variable name="roi"
select="count(item[starts-with(@name,'readonly')])"/>
<xsl:choose>
<xsl:when test="$roi=0">
<xsl:text>None</xsl:text>
</xsl:when>
<xsl:eek:therwise>
...do some table stuff...
</xsl:eek:therwise>
</xsl:choose>
If I could set a global variable from within a template, then it would
be easy to check that variable to tell whether or not to create a table
or print "None." But that is apparently not possible.

Have I missed something?

Set a variable. Test it. Keep it simple :)
The trick is getting to grips with what XPath can do.

///Peter
 
S

Soren Kuula

ned786 said:
I didn't know you could put a predicate [...] on an asterisk (*), as in
*[starts-with...etc].

That's not the beginning of the predicate; it's the end of the selection
path (and in this case all of the selection path) before it.

Soren
 
N

ned786

Peter,

Wow, that fixes the problem, and it's elegant to boot! Thanks!

Here's the way I had to modify it for my actual XML files:

<xsl:variable name="ro"
select="count(items/item[starts-with(@iname,'dr') or
starts-with(@iname,'hr') or
starts-with(@iname,'usr') or
starts-with(@iname,'szr') or
starts-with(@iname,'ulr') or
starts-with(@iname,'ipr') or
starts-with(@iname,'br') or
starts-with(@iname,'ucr')])"/>
<xsl:choose>
<xsl:when test="$ro=0" >
<p>No read-only items.</p>
</xsl:when>
<xsl:eek:therwise>
<table>... etc.
</table>
</xsl:eek:therwise>
</xsl:choose>

As you can see, there are actually 8 different strings that indicate
"read-only" (dr, hr, etc.). Now I am trying to figure out how to "NOT"
that count() function above so I can do the same thing for the
remaining writable items: Print "None" if there are none, and put them
in a table if they exist. The following two have failed:

<xsl:variable name="ro"
select="count(items/item[not(starts-with(@iname,'dr')) or
not(starts-with(@iname,'hr')) or
... ])"/>

<xsl:variable name="ro"
select="count(items/item[@iname != starts-with(@iname,'dr') or
@iname != starts-with(@iname,'hr') or

... ])"/>


I'm trying to find the <item> elements that do not have the read-only
indication. If I can get this to work (find NOT read-only), it will
save work. Do you have any further enlightenment?

Regards,
Mark
 
P

Peter Flynn

ned786 said:
Peter,

Wow, that fixes the problem, and it's elegant to boot! Thanks!

Here's the way I had to modify it for my actual XML files:

<xsl:variable name="ro"
select="count(items/item[starts-with(@iname,'dr') or
starts-with(@iname,'hr') or
starts-with(@iname,'usr') or
starts-with(@iname,'szr') or
starts-with(@iname,'ulr') or
starts-with(@iname,'ipr') or
starts-with(@iname,'br') or
starts-with(@iname,'ucr')])"/>
<xsl:choose>
<xsl:when test="$ro=0" >
<p>No read-only items.</p>
</xsl:when>
<xsl:eek:therwise>
<table>... etc.
</table>
</xsl:eek:therwise>
</xsl:choose>

As you can see, there are actually 8 different strings that indicate
"read-only" (dr, hr, etc.). Now I am trying to figure out how to "NOT"
that count() function above so I can do the same thing for the
remaining writable items: Print "None" if there are none, and put them
in a table if they exist. The following two have failed:

<xsl:variable name="ro"
select="count(items/item[not(starts-with(@iname,'dr')) or
not(starts-with(@iname,'hr')) or
... ])"/>

The above should work if you change all "or" to "and".
You're negating the condition, so you must negate the conjunction too.

///Peter
<xsl:variable name="ro"
select="count(items/item[@iname != starts-with(@iname,'dr') or
@iname != starts-with(@iname,'hr') or

... ])"/>


I'm trying to find the <item> elements that do not have the read-only
indication. If I can get this to work (find NOT read-only), it will
save work. Do you have any further enlightenment?

Regards,
Mark
 
N

ned786

Your last suggestion also works, Peter, and you are my hero. Thanks!
The problem is fixed and it saves me a tedious maintenance chore.

If you're interested, I had to add in yet another check to make it all
work, and the final gnarly XPath looks like this:

<xsl:variable name="wri"
select="count(items/item[(not(@doc = 'no') and @iname !=
starts-with(@iname,'dr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'hr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'usr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'szr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'ulr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'ipr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'br')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'ucr'))])"/>

Weirdly, "not(@doc = no)" worked when "@doc != 'no'" did not. But I'm
happy.

Mark
 
P

Peter Flynn

ned786 said:
Your last suggestion also works, Peter, and you are my hero. Thanks!
The problem is fixed and it saves me a tedious maintenance chore.

My pleasure...
If you're interested, I had to add in yet another check to make it all
work, and the final gnarly XPath looks like this:

<xsl:variable name="wri"
select="count(items/item[(not(@doc = 'no') and @iname !=
starts-with(@iname,'dr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'hr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'usr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'szr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'ulr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'ipr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'br')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'ucr'))])"/>

I'm not clear what this is trying to achieve. @iname is an attribute:
testing its inequality against a boolean like starts-with will probably
have unexpected effects, depending on whether the attribute is present
or not. If present, and starting with 'hr', then the first test should
always evaluate false.
Weirdly, "not(@doc = no)" worked when "@doc != 'no'" did not. But I'm
happy.

I have the feeling I've seen this too...possibly not all processors are
happy with negated boolean conditions involving attributes.

///Peter
 
N

ned786

Peter said:
<xsl:variable name="wri"
select="count(items/item[(not(@doc = 'no') and @iname !=
starts-with(@iname,'dr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'hr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'usr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'szr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'ulr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'ipr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'br')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'ucr'))])"/>

I'm not clear what this is trying to achieve. @iname is an attribute:
testing its inequality against a boolean like starts-with will probably
have unexpected effects, depending on whether the attribute is present
or not. If present, and starting with 'hr', then the first test should
always evaluate false.

Here's what this achieves. This XPath finds all the writable <item>
elements for me, because I can identify them when the iname attribute
does NOT start with one of the read-only strings (hr, usr, ulr, etc.).

If you are questioning the syntax of saying "@iname !=
starts-with(@iname,...)" instead of just "!= starts-with(@iname,...)",
I did it that way because it seemed I had to or it didn't work. When I
remove the "@iname !=", I get the error "Unexpected token != in
expression" and it stops the transform. (I'm using Saxon at the command
line.)

I also tried putting the expression in parentheses like (!=
starts-with(@iname,...)), and the same error occurred. It only worked
when I put "@iname != starts-with(@iname,...)".

At this point I think I will just take the money and run. I can count
on there always being an iname attribute.

Thanks again,
Mark
 
P

Peter Flynn

ned786 said:
<xsl:variable name="wri"
select="count(items/item[(not(@doc = 'no') and @iname !=
starts-with(@iname,'dr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'hr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'usr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'szr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'ulr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'ipr')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'br')) and
(not(@doc = 'no') and @iname != starts-with(@iname,'ucr'))])"/>

I'm not clear what this is trying to achieve. @iname is an attribute:
testing its inequality against a boolean like starts-with will probably
have unexpected effects, depending on whether the attribute is present
or not. If present, and starting with 'hr', then the first test should
always evaluate false.

Here's what this achieves. This XPath finds all the writable <item>
elements for me, because I can identify them when the iname attribute
does NOT start with one of the read-only strings (hr, usr, ulr, etc.).

If you are questioning the syntax of saying "@iname !=
starts-with(@iname,...)" instead of just "!= starts-with(@iname,...)",
I did it that way because it seemed I had to or it didn't work. When I
remove the "@iname !=", I get the error "Unexpected token != in
expression" and it stops the transform. (I'm using Saxon at the command
line.)

It's the = sign I'm questioning. What about !starts-with or
not(starts-with(...))

I am reminded of the programming language built into the P-Stat stats
package (PPL) which contained the amazingly useful syntax

if any(varname,varname,varname,...) among (value,value,value,...)

:)

///Peter
 
N

ned786

I tried out what you suggest.

This works:
(not(@doc = 'no') and not(starts-with(@iname,'dr')))

This doesn't work and stops the transform:
(not(@doc = 'no') and !starts-with(@iname,'dr'))

with this error:
"!" without "=" in expression count(...etc.

Your "not(starts-with..." makes the XPath simpler and easier for
someone to understand, so I'll use it.

Thanks yet again!
Mark
 

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,766
Messages
2,569,569
Members
45,045
Latest member
DRCM

Latest Threads

Top