Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations biv343 on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

How To Provide Missing Tags 1

Status
Not open for further replies.

Tweeeker

Programmer
May 9, 2005
3
US
Hi All,

I am a XML newbie.

I have a the following xml file
<lib>
<book>
<title>XXX</title>
<authour>Mr.A</authour>
</book>
<book>
<title>YYY</title>
<price>26</price>
</book>
<book>
<title>ZZZ</title>
</book>
</lib>

As you see only the 'Title' is required and other parameteres are optional.

I want to trasnform this XML to another XML in the following format

<lib>
<book>
<title>XXX</title>
<authour>Mr.A</authour>
<price>null</price>
</book>
<book>
<title>YYY</title>
<authour>null</authour>
<price>26</price>
</book>
<book>
<title>ZZZ</title>
<authour>null</authour>
<price>null</price>
</book>
</lib>

In my case I have like 10-15 optional fields and I want to know the best way to do this transformation in XSL.

Can someone provide me an example of XSL that I would need for the conversion?

Thanks,
Mohan
 
Try this:
Code:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform">[/URL]
  <xsl:template match="/">
    <lib>
    <xsl:for-each select="//book">
      <book>
        <title>
          <xsl:if test="title"><xsl:value-of select="title" /></xsl:if>
          <xsl:if test="not(title)">null</xsl:if>
        </title>
        <author>
          <xsl:if test="author"><xsl:value-of select="author" /></xsl:if>
          <xsl:if test="not(author)">null</xsl:if>
        </author>
        <price>
          <xsl:if test="price"><xsl:value-of select="price" /></xsl:if>
          <xsl:if test="not(price)">null</xsl:if>
        </price>
      </book>
    </xsl:for-each>
    </lib>
  </xsl:template>
</xsl:stylesheet>

Visit My Site
PROGRAMMER: (n) Red-eyed, mumbling mammal capable of conversing with inanimate objects.
 
Hi CubeE101,

This WORKS and thanks a lot for providing this solution. For now I will go with it until the next best solution is found.

This seems like it would generate lot of code in my XSL since I have over 20 Optional Fields. Is there a way to do it by having the Optional Fields name(XML elements) in a list and looping over this list to check if the element is missing and including it if it's missing?

Thanks,
Mohan
 
Here you go...

It gets a little tricky ;-)

XML: (missing.xml)
Code:
<?xml version="1.0" encoding="utf-8" ?>
<?xml-stylesheet type="text/xsl" href="missing.xsl"?>
<lib>
   <book>
       <title>XXX</title>
       <authour>Mr.A</authour>
   </book>
   <book>
       <title>YYY</title>
       <price>26</price>
   </book>
   <book>
       <title>ZZZ</title>
   </book>
</lib>

Supporting XML: (tags.xml)
Code:
<?xml version="1.0" encoding="utf-8" ?>
<tags>
  <tag>title</tag>
  <tag>author</tag>
  <tag>price</tag>
  <tag>test</tag>
  <tag>test2</tag>
</tags>

XSL: (missing.xsl)
Code:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform">[/URL]
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:template match="/">
    <lib>
    <xsl:for-each select="//book">
      <book>
        <xsl:call-template name="Tags">
          <xsl:with-param name="Item" select="."/>
        </xsl:call-template>
      </book>
    </xsl:for-each>
    </lib>
  </xsl:template>
  <xsl:template name="Tags">
    <xsl:param name="Item"/>
    [b]<xsl:for-each select="document('[COLOR=red]tags.xml[/color]')/tags/tag[/b]">
		<xsl:variable name="tag" select="."></xsl:variable>
		<xsl:variable name="tagval">
			<xsl:value-of select="$Item/*[name()=$tag]" />
		</xsl:variable>
        <xsl:element name="{$tag}">
          <xsl:if test="$tagval != ''"><xsl:value-of select="$tagval" /></xsl:if>
          <xsl:if test="$tagval = ''">null</xsl:if>
        </xsl:element>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Result:
Code:
<lib>
<book>
<title>XXX</title>
<author>null</author>
<price>null</price>
<test>null</test>
<test2>null</test2>
</book>
<book>
<title>YYY</title>
<author>null</author>
<price>26</price>
<test>null</test>
<test2>null</test2>
</book>
<book>
<title>ZZZ</title>
<author>null</author>
<price>null</price>
<test>null</test>
<test2>null</test2>
</book>
</lib>


Visit My Site
PROGRAMMER: (n) Red-eyed, mumbling mammal capable of conversing with inanimate objects.
 
You can use a key to work out which tags are unique by name, then crosscheck that list against the nodes in each book:
Code:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform">[/URL]
  <xsl:output method="xml" indent="yes"/>
  <xsl:key name="field" match="book/*" use="local-name(.)"/>
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="book">
    <book>
      <xsl:variable name="book" select="."/>
      <xsl:for-each select="/lib/book/node()[count(.|key('field', local-name(.))[1])=1]">
        <xsl:element name="{local-name(.)}">
          <xsl:choose>
            <xsl:when test="$book/node()[local-name(.) = local-name(current())]">
              <xsl:value-of select="$book/node()[local-name(.) = local-name(current())]"/>
            </xsl:when>
            <xsl:otherwise>null</xsl:otherwise>
          </xsl:choose>
        </xsl:element>
      </xsl:for-each>
    </book>
  </xsl:template>
</xsl:stylesheet>

Jon

"There are 10 types of people in the world... those who understand binary and those who don't.
 
What does match="@*|node()" match?

Any Attribute or Node?

In other words, what is the difference between:
Code:
  [b]<xsl:template match="@*|node()">[/b]
    <xsl:copy>
      [b]<xsl:apply-templates select="@*|node()"/>[/b]
    </xsl:copy>
  </xsl:template>

And:
Code:
  [b]<xsl:template match="/">[/b]
    <xsl:copy>
      [b]<xsl:apply-templates select="*"/>[/b]
    </xsl:copy>
  </xsl:template>


Visit My Site
PROGRAMMER: (n) Red-eyed, mumbling mammal capable of conversing with inanimate objects.
 
Yes, it matches any node or attribute.

If the XSL processor fails to match a node to a template it will use the default template, which will output the value of the node.

So, in the second case it will match the root node ("/"), then it will try to apply templates to the child nodes of the root. As there are no templates, it will revert to the default templates, thus output the value.

With 'match="@*|node()"' it sort of acts as the default template, recursively copying all the nodes (with attributes). This is quite powerful, for example:
Code:
<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="book">
  <magazine>
    <xsl:apply-templates select="@*|node()"/>
  </magazine>
</xsl:template>
This would change all the book nodes to magazine nodes, but keep the rest of the structure intact.

Jon

"There are 10 types of people in the world... those who understand binary and those who don't.
 
Oops, I meant Match="*" above, Match="/" gives you this:
Code:
<book>
<title>XXX</title>
<authour>Mr.A</authour>
<price>null</price>
</book>
<book>
<title>YYY</title>
<authour>null</authour>
<price>26</price>
</book>
<book>
<title>ZZZ</title>
<authour>null</authour>
<price>null</price>
</book>

And I guess copy effectively copies the processor tags, right?

With Copy and @*|node(), I get this:
Code:
[b]<?xml-stylesheet type="text/xsl" href="missing3.xsl"?>[/b]

<lib>
<book>
<title>XXX</title>
<authour>Mr.A</authour>
<price>null</price>
</book>
<book>
<title>YYY</title>
<authour>null</authour>
<price>26</price>
</book>
<book>
<title>ZZZ</title>
<authour>null</authour>
<price>null</price>
</book>
</lib>
*Note the XSL tag at the top...

With out either/or, I get this:
Code:
<lib>
<book>
<title>XXX</title>
<authour>Mr.A</authour>
<price>null</price>
</book>
<book>
<title>YYY</title>
<authour>null</authour>
<price>26</price>
</book>
<book>
<title>ZZZ</title>
<authour>null</authour>
<price>null</price>
</book>
</lib>

Other than that, is there any difference?

Then, the difference between name() and local-name() is that local-name() removes the namespace, right?

So is there any particular reason you are using local-name in this case?

I played around with it a little, and I get the same result with this:
Code:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform">[/URL]
  <xsl:output method="xml" indent="yes"/>
  <xsl:key name="field" match="book/*" use="[b]name[/b](.)"/>
  <xsl:template match="@*|node()">
    <xsl:copy>
      [b]<xsl:apply-templates select="*"/>[/b]
    </xsl:copy>
  </xsl:template>
  <xsl:template match="book">
    <book>
      <xsl:variable name="book" select="."/>
      <xsl:for-each select="//book/node()[count(.|key('field', [b]name[/b](.))[1])=1]">
        [b]<xsl:variable name="tag" select="$book/node()[name(.) = name(current())]"/>[/b]
        <xsl:element name="{[b]name[/b](.)}">
          <xsl:choose>
            [b]<xsl:when test="$tag"><xsl:value-of select="$tag"/></xsl:when>[/b]
            <xsl:otherwise>null</xsl:otherwise>
          </xsl:choose>
        </xsl:element>
      </xsl:for-each>
    </book>
  </xsl:template>
</xsl:stylesheet>

Anyway, I learned a few things... So I'll give you a Star ;-)

Visit My Site
PROGRAMMER: (n) Red-eyed, mumbling mammal capable of conversing with inanimate objects.
 
Thanks a lot for your input guys. Looks like one must play a lot with Xpath before doing any complex XSL stuff. They are quite a few learning points here for me. Thanks again.
 
xsl:copy doesn't automatically copy the attributes or the nodes (unlike xsl:copy-of). This means you have to specify them directly with '@*' and 'node()'. Using '*' will be just the same as just using 'node()' with xsl:copy.

To illustrate, try putting an attribute on the root element, eg:
Code:
<lib foo="bar">
I didn't know it outputted the stylesheet declaration too, as I'm using XML spy and don't have a declaration on my xml file. You could get rid of that if you wanted by specifying a null template for it (or possibly with omit-xml-declaration):
Code:
<xsl:template match="node()[name(.) = 'xml-stylesheet']"/>
You're right about name() and local-name(). I just used local-name() out of habit.

Jon

"There are 10 types of people in the world... those who understand binary and those who don't.
 
Oh, duh, xsl:copy is where <lib> comes from in the output... I don't know what I was thinking...

I was getting it crossed with copy-of but in some skewed way...
I was using <xmp> tags around the document to view the output xml in the browser... I did not notice that It dropped the <lib> tags, and I guess <xmp> became the root tag and avoided the error...
(O-well)

>>I just used local-name() out of habit.

That's what I thought, I just wanted to make sure I was not missing anything...
(In the long run, that is a good habit)

Now moving forward...

Tweeeker,
You do have several things to work with here...

Jon's method is great if all of the sub tags exist somewhere in the document and you want to fill in the blanks...

You can use my method if you want to verify all the tags you want are there, and you can also modify it a little to provide defaults for the tags, based on what tag is being added, And get a little more functionality out of it...

Say you have a catolog, all books are $5.00 unless otherwise stated...

You can add an attribute to the <price> tag definition in the Tags.xml, such as:
Code:
...
  <tag default="5">price</tag>
...
then tweak the xsl a bit to accomidate it...

You can then leave out all the price tags in the main xml which have a value of 5, making the file size smaller...

as well as getting away from placing a generic null on everything...

I will provide an example of this in a little while...

Visit My Site
PROGRAMMER: (n) Red-eyed, mumbling mammal capable of conversing with inanimate objects.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top