PaulCutcliffe
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</Share dSecret>
</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\xmlschem as/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\xmlschem as/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.
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</Share
</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\xmlschem
<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
<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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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
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
ASKER
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\xmlschem as/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\xmlschem as/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.
<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
<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
<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\xmlschem as/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
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
<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
yes, that is what I meant
ASKER
Okay, thanks - I'll have a look & make sure that my copy is exactly the same...
ASKER
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\xmlschem as/domino_ 6_5.dtd">
<document version="6.5" form="BO" xmlns="http://www.lotus.com/dxl">TestFromTestToTes tSenderxXx Xx</docume nt>
... 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.
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE document SYSTEM "C:\Program Files\lotus\notes\xmlschem
<document version="6.5" form="BO" xmlns="http://www.lotus.com/dxl">TestFromTestToTes
... 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.
ASKER
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
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
ASKER
Okay, thanks.
So any idea why your copy's working, whilst mine isn't?
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?
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?
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.
<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.
ASKER
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'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:in put"
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="i n" in the stylesheet element
of course the ="urn:namespace:input" should be replaced by the actual input namespace
cheers
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:in
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="i
of course the ="urn:namespace:input" should be replaced by the actual input namespace
cheers
ASKER
> 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.
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.
ASKER
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[@doma in='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/Credentia l/Identity (where Credential/@domain = 'Test')
> Root/Header/To/Credential/ Identity (where Credential/@domain = 'Test')
> Root/Header/Sender/Credent ial/Identi ty (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//Credentia l[@domain= 'Test']//I dentity"> and <xsl:for-each select="//in:Credential[@d omain='Tes t']//Ident ity">, 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[@i n:domain=' Test']//in :Identity" > ?
Other than this tiny issue, I think I'm all done, so thanks very much.
However, part of my XSLT file picks up a number of elements in the source file, e.g.
<xsl:for-each select="//Credential[@doma
<item>
<xsl:attribute name="name">
<xsl:value-of select="concat(name(../..)
</xsl:attribute>
<text>
<xsl:value-of select="."/>
</text>
</item>
</xsl:for-each>
... which basically picks up the following:
> Root/Header/From/Credentia
> Root/Header/To/Credential/
> Root/Header/Sender/Credent
... & 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//Credentia
Other than this tiny issue, I think I'm all done, so thanks very much.
ASKER
I've sussed it!
<xsl:for-each select="//in:Credential[@d omain='Tes t']//in:Id entity">
... seems to work just fine!
<xsl:for-each select="//in:Credential[@d
... 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
yes, of course,
every element in your source file is in this namespace,
so you need to prefix every single element
cheers
ASKER
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.
Thanks again for all your help.
welcome
ASKER
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.