Wrox Programmer Forums

Need to download code?

View our list of code downloads.

Go Back   Wrox Programmer Forums > XML > XSLT
Password Reminder
Register
| FAQ | Members List | Calendar | Search | Today's Posts | Mark Forums Read
XSLT General questions and answers about XSLT. For issues strictly specific to the book XSLT 1.1 Programmers Reference, please post to that forum instead.
Welcome to the p2p.wrox.com Forums.

You are currently viewing the XSLT section of the Wrox Programmer to Programmer discussions. This is a community of tens of thousands of software programmers and website developers including Wrox book authors and readers. As a guest, you can read any forum posting. By joining today you can post your own programming questions, respond to other developers’ questions, and eliminate the ads that are displayed to guests. Registration is fast, simple and absolutely free .
DRM-free e-books 300x50
Reply
 
Thread Tools Search this Thread Display Modes
  #1 (permalink)  
Old January 30th, 2009, 03:07 AM
Authorized User
 
Join Date: Jan 2009
Posts: 17
Thanks: 2
Thanked 0 Times in 0 Posts
Default Stuck on XSLT problem

Hi All,

I'm actually quite new to XSLT and I got this issue I need to resolve. I'm converting this web service XML request to an XML file my system will understand using XSLT. The problem is the guy who wrote it quit so now I have to make some mods to new functionality and I'm kind of stuck. The crux of the problem is that the current xslt will take an XML such as
<Table>
<TypeA>
<SomeVar> A </SomeVar>
</Type>
<TypeA>
<SomeVar> B </SomeVar>
</TypaA>
</Table>

and convert it to:

<group name = TypeA>
<Item>
<SomeVar> A </SomeVar>
</Item>
<Item>
<SomeVar> B </SomeVar>
</Item>
</group>

Now I need to get rid of the <Table> tags from the input XML altogether and still produce the same output after XSLT is applied.

Here is the XSLT which currently handles this conversion:

<xsl:template name="GroupTable">
<xsl:element name="Group">
<xsl:attribute name="name">
<xsl:value-of select="local-name(node()[local-name()][1])"/>
</xsl:attribute>
<xsl:copy-of select="@*"/>
<xsl:for-each select="node()">
<xsl:if test="local-name() != ''">
<Item>
<xsl:for-each select="node()">
<xsl:call-template name="CheckNode"/>
</xsl:for-each>
</Item>
</xsl:if>
</xsl:for-each>
</xsl:element>

I dont know what a couple of things do:

1) <xsl:copy-of select="@*"/> .. Which nodes does this include?
2) "local-name(node()[local-name()][1])"/ what does this evaluate to?
3) Why there are two for loops to go through the set, shouldn't it go through the nodes only once?

Sorry if some of these are stupid questions - I'm very new to this.

Thanks,

Jiggs
Reply With Quote
  #2 (permalink)  
Old January 30th, 2009, 04:53 AM
mhkay's Avatar
Wrox Author
Points: 18,487, Level: 59
Points: 18,487, Level: 59 Points: 18,487, Level: 59 Points: 18,487, Level: 59
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Apr 2004
Location: Reading, Berks, United Kingdom.
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

A few observations.

Firstly, if you remove the <Table> start and end tag from your input XML then it will no longer be well-formed, as it now has two top-level elements, which means you will not be able to process it at all. So I'm unclear what you are really trying to do.

Secondly, your code is (twice) selecting all the child nodes using node(), and then rejecting them unless they have a name (by testing local-name()). The effect is that it only processes the child nodes that are elements, which it could do much more simply by selecting them using "*" rather than "node()". Not vitally important, but it's an important clue to your maintenance strategy: the person who wrote this code was not an XSLT expert.

Specific answers:

1) <xsl:copy-of select="@*"/> .. Which nodes does this include?

It copies all the attributes of the current element.

2) "local-name(node()[local-name()][1])"/ what does this evaluate to?

It's the same as local-name(*[1]) - it gives the name of the first element child (TypeA). That's because node()[local-name()] selects all child nodes that have a name, that is, all child elements.

For this kind of data, it would be better to get rid of the whitespace text nodes using <xsl:strip-space elements="*">. Then all the children of <Table> would be elements, and you wouldn't have to write messy code to tiptoe around the whitespace.

3) Why there are two for loops to go through the set, shouldn't it go through the nodes only once?

The two for-each loops are nested, and they are there because your input is a tree with two levels of hierarchy. A better XSLT programmer would not have written two nested for-each loops, but would have used template rules instead.
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
Reply With Quote
The Following 2 Users Say Thank You to mhkay For This Useful Post:
jiggs (January 30th, 2009), mrame (January 30th, 2009)
  #3 (permalink)  
Old January 30th, 2009, 05:12 AM
Authorized User
 
Join Date: Jan 2009
Posts: 17
Thanks: 2
Thanked 0 Times in 0 Posts
Default

Hi Michael,

Thanks for the detailed response. I've had some more time to understand XSLT and how it works and the syntax so I think I can formulate my questiob better now especially given your answers below.

Firstly, the original XML would be still valid without the <Table> because I only gave a snippet of the actual XML. The actual request is more along the line of:

...
<request>
<Table>
<TypeA>
<SomeVar>Value</SomeVar>
</TypeA>
<TypeA>
<SomeVar>Value</SomeVar>
</Typea>
</Table>
</request>
...

So removing the <Table> basically changes the parent of the <TypeA> tag. The key here is that there must be at least 2 <TypeA> elements in order for this code to get executed and the <Item> tags to be inserted by the XSLT.

So in the old setup when the XSLT hit a <Table> Element it would verify that it had multiple descendents (i.e. TypeA> and then process each node (TypeA) - first for loop, and insert <Item> then process the children of TypeA - second for loop.

The problem now is that I'm not sure how to tie the TypeA elements together since their parents <request> has multiple other nodes which are not TypeA (so they cant be processed together with TypeA nodes). I essentially need to get a subset of all TypeA nodes and process them together.

What would be the best way of doing this? Would maybe just doing a select of all nodes that have TypeA so instead of copy-of "@*" I would get all the TypeA nodes and then process as before?

Thanks again.

Jiggs
Reply With Quote
  #4 (permalink)  
Old January 30th, 2009, 05:34 AM
mhkay's Avatar
Wrox Author
Points: 18,487, Level: 59
Points: 18,487, Level: 59 Points: 18,487, Level: 59 Points: 18,487, Level: 59
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Apr 2004
Location: Reading, Berks, United Kingdom.
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

This is precisely why the code should have been written using template rules rather than nested for-each in the first place - template rules make it much easier to adapt the code to structural changes in the input XML. A good lesson to learn, but not one that helps you directly.

I'm a bit puzzled as to why you are removing the <Table> level, since it seems to represent a useful level of structure and makes the code easier to write. Without it you are having to infer this level of structure implicitly.

I'm afraid it looks as if you have oversimplified the problem so your snippets no longer capture its essence. But it doesn't look difficult - you can always process the child TypeA elements by doing select="TypeA". (But are they always TypeA? If so, why was the code previously generating a name attribute with a dynamically computed value rather than a fixed value?)
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
Reply With Quote
  #5 (permalink)  
Old January 30th, 2009, 05:38 AM
Authorized User
 
Join Date: Jan 2009
Posts: 17
Thanks: 2
Thanked 0 Times in 0 Posts
Default

I should perhaps also add the other part of the current XSLT code which calls the template to translate <TypeA>

<xsl:template name="CheckNode">

<xsl:choose>

<!-- decide if we are a group variable -->

<xsl:when test="count(descendant::*) > 0 and not(starts-with(local-name(),'Table'))">

<xsl:call-template name="Group"/>

</xsl:when>

<!-- call TableNode to decide if we are a groupTable or just a table -->

<xsl:when test="starts-with(local-name(),'Table')">

<xsl:call-template name="GroupTable">
</xsl:call-template>

</xsl:when>

<!--deal with simple variables-->

<xsl:when test="not(local-name()='ProcessClassID' or local-name()='LocaleID' or local-name()='StatisticLogging')">

<xsl:call-template name="Variable"/>

</xsl:when>

</xsl:choose>

</xsl:template>

I've tried changing the condition to loop for 'TypeA' instead of Table but that still leaves me with the problem of other TypeA nodes. The Table element was the parent of all TypeA elements so it was easy to process each node cause you had the in the same set -- copy-of "@*" ...

So how do I get a copy of all the TypeA elements now that their parent has changed and it contains other elements not only those of Type A?

Thanks,
Jiggs
Reply With Quote
  #6 (permalink)  
Old January 30th, 2009, 05:51 AM
Authorized User
 
Join Date: Jan 2009
Posts: 17
Thanks: 2
Thanked 0 Times in 0 Posts
Default

Quote:
Originally Posted by mhkay View Post
This is precisely why the code should have been written using template rules rather than nested for-each in the first place - template rules make it much easier to adapt the code to structural changes in the input XML. A good lesson to learn, but not one that helps you directly.
Yeah I understand what you mean now - but I inherited this mess unfortunately.
Quote:
Originally Posted by mhkay View Post
I'm a bit puzzled as to why you are removing the <Table> level, since it seems to represent a useful level of structure and makes the code easier to write. Without it you are having to infer this level of structure implicitly.
The <Table> is actually being removed by a third party vendor but our application still must have the same structure when processing their XML. I fully agree that it would be much easier with it.
Quote:
Originally Posted by mhkay View Post
I'm afraid it looks as if you have oversimplified the problem so your snippets no longer capture its essence. But it doesn't look difficult - you can always process the child TypeA elements by doing select="TypeA". (But are they always TypeA? If so, why was the code previously generating a name attribute with a dynamically computed value rather than a fixed value?)
Right now they are always going to be TypeA but in the future it might grow to other elements which are these GroupTables as per the XSL above. The code previously was designed to generate dynamic names because it was not written with this vendor in mind. So we are trying to adapt what works with our system with the vendor if that makes sense.

If I use select=TypeA though wouldn't this code get executed each time for each TypeA node then? I.e duplicate the output?

Last edited by jiggs; January 30th, 2009 at 05:54 AM..
Reply With Quote
  #7 (permalink)  
Old January 30th, 2009, 06:00 AM
mhkay's Avatar
Wrox Author
Points: 18,487, Level: 59
Points: 18,487, Level: 59 Points: 18,487, Level: 59 Points: 18,487, Level: 59
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Apr 2004
Location: Reading, Berks, United Kingdom.
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

<xsl:template name="CheckNode">
<xsl:choose>
<!-- decide if we are a group variable --> <xsl:when test="count(descendant::*) > 0 and not(starts-with(local-name(),'Table'))">
<xsl:call-template name="Group"/>
</xsl:when>
<!-- call TableNode to decide if we are a groupTable or just a table --> <xsl:when test="starts-with(local-name(),'Table')">

This code is just horrible. Whoever wrote it just didn't understand template rules, and was reinventing the mechanism and coding it "by hand". You should never use xsl:choose to switch on the element name - always use template rules with a match pattern.

I'm not sure what to advise here. If I was doing it, I would throw this code away and rewrite it from scratch. That wouldn't be prudent in your case as you're a beginner yourself. But the problem is, if you follow this coding style you are going to learn some really bad habits.
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
Reply With Quote
  #8 (permalink)  
Old January 30th, 2009, 06:07 AM
Authorized User
 
Join Date: Jan 2009
Posts: 17
Thanks: 2
Thanked 0 Times in 0 Posts
Default

Quote:
Originally Posted by mhkay View Post
<xsl:template name="CheckNode">
<xsl:choose>
<!-- decide if we are a group variable --> <xsl:when test="count(descendant::*) > 0 and not(starts-with(local-name(),'Table'))">
<xsl:call-template name="Group"/>
</xsl:when>
<!-- call TableNode to decide if we are a groupTable or just a table --> <xsl:when test="starts-with(local-name(),'Table')">

This code is just horrible. Whoever wrote it just didn't understand template rules, and was reinventing the mechanism and coding it "by hand". You should never use xsl:choose to switch on the element name - always use template rules with a match pattern.

I'm not sure what to advise here. If I was doing it, I would throw this code away and rewrite it from scratch. That wouldn't be prudent in your case as you're a beginner yourself. But the problem is, if you follow this coding style you are going to learn some really bad habits.
Yeah unfortunately it's not an option to rewrite it at the moment because there are a many tools that use this currently and I have a short window to implement this. We are going to eventually overhaul this entire piece but that doesn't help me at the moment.

My main problem now is getting a subset of these TypeA nodes. Since this CheckNode gets called recusively each time TypeA get invoked it will execute the same code which I don't want. Also I tried copy-of select "TypeA" and it didnt give me the proper results.

Just to understand how it works, if in the template I call copy-of select "TypeA", and then in the next line I do a for-each node() ... will it travese each TypeA node one by one. It doesn't appear to be doing that for me.

Thanks again, this is a great help.

Jiggs
Reply With Quote
  #9 (permalink)  
Old January 30th, 2009, 06:49 AM
Authorized User
 
Join Date: Jan 2009
Posts: 17
Thanks: 2
Thanked 0 Times in 0 Posts
Default

With XSLT how do I select all nodes with the same name and step through them?

I'm trying now

<
xsl:copy-of select="TypeA"/>

<xsl:for-each select="node()">


But its only giving the first occurence of the TypeA even though my input has 2 for example. What in fact this seems to be doing is that for the first element it traverse is the first child node of TypeA not TypeA itself which is the problem.
Reply With Quote
  #10 (permalink)  
Old January 30th, 2009, 01:05 PM
Authorized User
 
Join Date: Jan 2009
Posts: 17
Thanks: 2
Thanked 0 Times in 0 Posts
Default Completely stuck on this now

Hi Everyone,

I've been stuck on a XSLT problem trying to convert an XML doc to another XML and now I'm wondering if it's even possible to be done with XSTL. Here is a very simple breakdown of what I'm trying to do:

Convert this XML:
Code:
<Request>
   <SomeTypeA>
       <Variable>A</Variable>
   </SomeTypeA>
    <SomeTypeB>
       <Variable>B</Variable>
   </SomeTypeB>
   <SomeTypeB>
       <Variable>B</Variable>
   </SomeTypeB>
</Request>
To:
Code:
<Group name = Request>
   <Group name = SomeTypeA>
       <Variable>A</Variable>
   </Group>
   <Group name = SomeTypeB>
      <Item>
           <Variable>A</Variable>
      </Item>
      <Item>
           <Variable>A</Variable>
      </Item>
    </Group>
</Group>
The key here is I want to group all Nodes under <Request> that have the same name, in the example above <SomeTypeB> and process them together so that they belong to the same <Group name = SomeTypeB> after the conversion and they are separate by the new <Item> tags.

So far everything I tried has failed, the closest I get is that I get two sets <Group name = SomeTypeB> tags which is incorrect for my needs.

Can this even be done with XSLT or do I have to use Java or something else?

Much appreciated if you could help me with this!!

Thanks
Reply With Quote
Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off
Trackbacks are Off
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
dynamic xslt -> xslt creation namespace problem jkmyoung XSLT 2 July 15th, 2006 12:42 AM
Help - I'm Completely Stuck hugh@kmcnetwork.com ASP.NET 1.x and 2.0 Application Design 1 May 16th, 2006 10:12 AM
Help!! I am completely stuck... andrewba Classic ASP Components 5 May 12th, 2005 04:50 AM
Data Shaping Problem .. im stuck! jeuriks SQL Server ASP 2 March 19th, 2004 11:09 AM
I'm stuck! budman Classic ASP Databases 2 November 3rd, 2003 12:52 AM



All times are GMT -4. The time now is 07:50 AM.


Powered by vBulletin®
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
© 2013 John Wiley & Sons, Inc.