Link to home
Start Free TrialLog in
Avatar of PaulCutcliffe
PaulCutcliffeFlag for United Kingdom of Great Britain and Northern Ireland

asked on

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

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.
ASKER CERTIFIED SOLUTION
Avatar of Gertone (Geert Bormans)
Gertone (Geert Bormans)
Flag of Belgium image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of PaulCutcliffe

ASKER

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.
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
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.
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
>Sorry, I'm not completely with you. In the XSLT file, you mean have this:

yes, that is what I meant
Okay, thanks - I'll have a look & make sure that my copy is exactly the same...
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.
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.
>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
Okay, thanks.

So any idea why your copy's working, whilst mine isn't?
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?
Avatar of jkmyoung
jkmyoung

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.
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.
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
> 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.
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.
I've sussed it!

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

... seems to work just fine!
>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
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.