[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
?
Solved

Using XSLT, I want to use the Name (not value) of the current node's grandparent node in my output file - but how?

Posted on 2006-03-29
21
Medium Priority
?
529 Views
Last Modified: 2013-11-19
I am working on an XSLT file to transform from one form of XML to another form (DXL, or Domino XML Language, the form used by Lotus Domino/Notes).

In my input file, I have the following:

<Root>
  <Header>
    <From>
      <Credential domain="Test">
        <Identity>Test</Identity>
      </Credential>
    </From>
    <To>
      <Credential domain="Test">
        <Identity>Test Partner, Inc.</Identity>
      </Credential>
    </To>
    <Sender>
      <Credential domain="Test">
        <Identity>Test</Identity>
        <SharedSecret>xXxXx</SharedSecret>
      </Credential>
      <UserAgent>Test User Agent</UserAgent>
    </Sender>
  </Header>
</Root>

And I want this to appear in my output file:

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE document SYSTEM "C:\Program Files\lotus\notes\xmlschemas/domino_6_5.dtd">
<document xmlns='http://www.lotus.com/dxl' version='6.5' form='BO'>
  <item name='HeaderFrom'>
    <text>TestFrom</text>
  </item>
  <item name='HeaderTo'>
    <text>TestTo</text>
  </item>
  <item name='TestSender'>
    <text>TestSender</text>
  </item>
  <item name='SharedSecretSender'>
    <text>xXxXx</text>
  </item>
</document>

So far, I have this:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" encoding="iso-8859-1" indent="yes" doctype-system="C:\Program Files\lotus\notes\xmlschemas/domino_6_5.dtd" />
  <xsl:template match="/">
    <document xmlns="http://www.lotus.com/dxl" version="6.5"  form="BO">
      <xsl:apply-templates select="*"/>
    </document>
  </xsl:template>
 
  <xsl:template match="Header">
    <xsl:for-each select="//Identity">
      <item>
        <xsl:attribute name="name">
>>        <xsl:name-of select="../.."/>        <<
        </xsl:attribute>
        <xsl:value-of select="."/>
      </item>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

But of course, "name-of" is not a valid element. Is there something that I can use here, to get me the name of the current node's grandparent node?

Also, as I'm new to XML/XSLT, I've allowed a lot of points for this question, as I can't help feeling that I might need more help. For example, I am finding that certain text values are being copied through to the output file, along with a load of blank lines. I think it's the <xsl:apply-templates select="*"/> line. But without it, nothing gets processed. What should I put here instead? And what about the way I have addressed "Header", & then "//Identity" - will that pick up the three Identity elements, as I intend?

Any thoughts or comments welcomed.

Thanks.
0
Comment
Question by:PaulCutcliffe
  • 11
  • 9
21 Comments
 
LVL 60

Accepted Solution

by:
Geert Bormans earned 2000 total points
ID: 16322376
use <xsl:value-of select="name(../..)"/>

cheers
0
 

Author Comment

by:PaulCutcliffe
ID: 16322479
Fantastic. Thought it'd be simple - when you know how!

However, I've found that my selection is not picking up these elements. Can you see why? I've currently got:

  <xsl:template match="Header">
    <xsl:for-each select="//Identity">

... as I think the current context should be the <Root> element, so "Header" should pick up the Header element, & "//Identity" should, as far as I can work out, pick up all three of the descendant Identity nodes. But it doesn't.

Thanks.
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16322527
another thing I would do is moving the xmlns="http://www.lotus.com/dxl"
to the stylesheet element
It then has effect on the entire XSLT,
which will result in all output elements having the same namespace

If you don't do that, you will get a xmlns="" in every item element
because you don't tell that you expect them to be in the lotus namespace (which is default)
so the processor has to switch off the namespace node
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 

Author Comment

by:PaulCutcliffe
ID: 16322638
Sorry, I'm not completely with you. In the XSLT file, you mean have this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.lotus.com/dxl">
  <xsl:output method="xml" encoding="iso-8859-1" indent="yes" doctype-system="C:\Program Files\lotus\notes\xmlschemas/domino_6_5.dtd" />
  <xsl:template match="/">
    <document version="6.5"  form="BO">
      <xsl:apply-templates select="*"/>
    </document>
  </xsl:template>

... instead of:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" encoding="iso-8859-1" indent="yes" doctype-system="C:\Program Files\lotus\notes\xmlschemas/domino_6_5.dtd" />
  <xsl:template match="/">
    <document xmlns="http://www.lotus.com/dxl" version="6.5"  form="BO">
      <xsl:apply-templates select="*"/>
    </document>
  </xsl:template>

... so I've moved the xmlns: element from the document element to the stylesheet element?

Thanks.
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16322775
I am very much surprised that it doesn't work with you.
I copied your XML and your XSLT and after the two changes I suggested
(using the name() function and moving the namespace node)
it works like a charm here

here is the XSLT so you can try

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet xmlns="http://www.lotus.com/dxl" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" encoding="iso-8859-1" indent="yes" doctype-system="C:\Program Files\lotus\notes\xmlschemas/domino_6_5.dtd" />
    <xsl:template match="/">
        <document  version="6.5"  form="BO">
            <xsl:apply-templates select="*"/>
        </document>
    </xsl:template>
   
    <xsl:template match="Header">
        <xsl:for-each select="//Identity">
            <item>
                <xsl:attribute name="name">
                   <xsl:value-of select="name(../..)"/>
                </xsl:attribute>
                <xsl:value-of select="."/>
            </item>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

you use the template for match="/" to get things going

XSLT is rule based, so you have a bunch of templates with matching rules
inside the templates there are "actions"
These actions get executed when the matching rules match

with apply-templates, you push nodes to the templates
you can be very unspecific and push all childnodes to the templates
<xsl:apply-templates/>
or very specific about the nodeset you push
<xsl:apply-templates select="//Header"/>

If you are annoyed by a number of empty lines, you would do the latter

by saying
      <xsl:apply-templates select="*"/>
you send the child elements nodes to the templates, so that is the "Root" element

Since you don't have a template for Root, you end up with default processing, which is <apply-templates>,
hence the white-space nodes and possibly a reason for getting text nodes you did not expect

You are right...
//Identity is in context for Header, so there should be no problem
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16322792
>Sorry, I'm not completely with you. In the XSLT file, you mean have this:

yes, that is what I meant
0
 

Author Comment

by:PaulCutcliffe
ID: 16322933
Okay, thanks - I'll have a look & make sure that my copy is exactly the same...
0
 

Author Comment

by:PaulCutcliffe
ID: 16323070
I've checked & double-checked, & my XSLT is the same as posted here, yet my output file looks like this:

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE document SYSTEM "C:\Program Files\lotus\notes\xmlschemas/domino_6_5.dtd">
<document version="6.5" form="BO" xmlns="http://www.lotus.com/dxl">TestFromTestToTestSenderxXxXx</document>

... so (i) I get no output from the Root/Header/From, Root/Header/To or Root/Header/Sender, & (ii) I get all the top-level text node values on their own.

Any idea why this is?

Surely, I want 'everything' to be processed, so I need the <xsl:apply-templates select="*"/> as it is, but what is making it output the text nodes on their own? In this case, is <xsl:apply-templates select="*"/> the same as <xsl:apply-templates/>?

Thanks again.
0
 

Author Comment

by:PaulCutcliffe
ID: 16323119
It's odd that it's only picking up these text values from the Header, but it's nothing to do with my <xsl:template match="Header"> section, as it still does this even with this whole section commented out.
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16323339
>but what is making it output the text nodes on their own?

The default processing of an element is "process its children"
The default processing of a text node is "output the text node"

unless you cut out a subtree
(eg. by having a template doing nothing like this <xsl:template match="Header"/>)
there is a fair chance that text nodes just make it to the output

For that it is recommended to develop a stylesheet as close as possible against the original XML

cheers
0
 

Author Comment

by:PaulCutcliffe
ID: 16324747
Okay, thanks.

So any idea why your copy's working, whilst mine isn't?
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16325871
Are you 100% sure we have the same XSLT?
if you for instance misspelled Header (eg. header)
you would get the result you are having

did you actually try my XSLT?
or can you post yours?
0
 
LVL 12

Expert Comment

by:jkmyoung
ID: 16327508
You could also try adding the name of the root to the first template:
<xsl:template match="/Root">

or if that doesn't work, try changing the apply-templates in that function to:
<xsl:apply-templates select="//Header"/>
and see what you get.
0
 

Author Comment

by:PaulCutcliffe
ID: 16330648
Morning.

Well, I've found out where I am going wrong, & how come everything I try to do with dummy XML files & tutorials works fine, whilst every time I try & do anything with my real XML file, absolutely nothing works!

I stripped the real file down to its bare bones & built it back up bit by bit. When I re-added the namespace declaration in the input file, I couldn't seem to find anything with any of my XPath expressions again. So I figured I am having a namespace issue.

I knew removing the namespace from the input file was a cheat rather than a fix, as I obviously can't edit each incoming file & remove the namespace declaration! So I looked at my XSLT file. In it, I was setting the default namespace to that of the output file, i.e. xmlns="http://www.lotus.com/dxl", which I think meant that it was looking for stuff in the DXL namespace, instead of the input file's namespace, hence it wasn't foinding anything.

So I guess my XSLT file's default namespace should actually be the same as the input file, not the output file. Is that correct? However, even when it is, I still can't find anything. In fact, the only way I can find anything at all is to remove the namespace declaration from both files. What am I doing wrong?

I think I'm nearly there now!

Thanks.
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16331100
Well, I must say that your analysis is very sharp,
and everyone gets bitten by this at least once

since the default namespace has been used by the output tree,
you need a different prefix for the input namespace.
There is no need to have the default namespace of the input as the default in the XSLT
since prefixes are not important, the namespace URI is
so you can give the input default namespace any dummy prefix
eg. xmlns:in="urn:namespace:input"
then in your XPaths you have to prefix all the element names of course
(that is a bit clumsy but it is the only way)

   <xsl:template match="in:Header">
        <xsl:for-each select="//in:Identity">

then you only need to make sure this "in" prefix doesn't make it to the output
by adding an exclude-result-prefixes="in" in the stylesheet element

of course the ="urn:namespace:input" should be replaced by the actual input namespace

cheers
0
 

Author Comment

by:PaulCutcliffe
ID: 16331153
> Well, I must say that your analysis is very sharp...

Yes, it certainly took a while to get there! Of course, all the simple examples that describe books & CDs etc - none of them ever deal with namespaces, presumably to save the confusion, which is does, until you try a real example that does use different namespaces!

Anyway, I will try to build in your suggestions for handling the two namespaces, & come back to you.

Thanks for all your help so far.
0
 

Author Comment

by:PaulCutcliffe
ID: 16331471
Okay, right I've almost done it. That is to say, where I'm picking out elements from the source file one by one, so my XPath expression is simple, I know what to put, i.e. "Root/@timeStamp" has become "in:Root/@timeStamp".

However, part of my XSLT file picks up a number of elements in the source file, e.g.

      <xsl:for-each select="//Credential[@domain='Test']//Identity">
        <item>
          <xsl:attribute name="name">
            <xsl:value-of select="concat(name(../..), '_Identity')"/>
          </xsl:attribute>
          <text>
            <xsl:value-of select="."/>
          </text>
        </item>
      </xsl:for-each>

... which basically picks up the following:

> Root/Header/From/Credential/Identity (where Credential/@domain = 'Test')
> Root/Header/To/Credential/Identity (where Credential/@domain = 'Test')
> Root/Header/Sender/Credential/Identity (where Credential/@domain = 'Test')

... & creates Item elements called From_Identity, To_Identity & Sender_Identity, but I can't get these to work with an explicit namespace.

I've tried <xsl:for-each select="in:Root//Credential[@domain='Test']//Identity"> and <xsl:for-each select="//in:Credential[@domain='Test']//Identity">, but that doesn't seem to pick them up. Do I need to prefix every single name with "in:" ? Like this: <xsl:for-each select="//in:Credential[@in:domain='Test']//in:Identity"> ?

Other than this tiny issue, I think I'm all done, so thanks very much.
0
 

Author Comment

by:PaulCutcliffe
ID: 16331487
I've sussed it!

<xsl:for-each select="//in:Credential[@domain='Test']//in:Identity">

... seems to work just fine!
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16331522
>Do I need to prefix every single name with "in:"

yes, of course,
every element in your source file is in this namespace,
so you need to prefix every single element

cheers
0
 

Author Comment

by:PaulCutcliffe
ID: 16331577
Excellent, thanks for all your help. Now I've got past this namespace issue, I can make some progress.

Thanks again for all your help.
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16331626
welcome
0

Featured Post

Upgrade your Question Security!

Add Premium security features to your question to ensure its privacy or anonymity. Learn more about your ability to control Question Security today.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction Since I wrote the original article about Handling Date and Time in PHP and MySQL several years ago, it seemed like now was a good time to update it for object-oriented PHP.  This article does that, replacing as much as possible the pr…
JavaScript has plenty of pieces of code people often just copy/paste from somewhere but never quite fully understand. Self-Executing functions are just one good example that I'll try to demystify here.
Viewers will learn about the regular for loop in Java and how to use it. Definition: Break the for loop down into 3 parts: Syntax when using for loops: Example using a for loop:
The viewer will learn how to create a basic form using some HTML5 and PHP for later processing. Set up your basic HTML file. Open your form tag and set the method and action attributes.: (CODE) Set up your first few inputs one for the name and …
Suggested Courses

834 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question