Groups | Blog | Home
all groups > dotnet xml > february 2005 >

dotnet xml : Newbie problem: empty nodes won't collapse


Ross Presser
2/16/2005 5:13:03 PM
My app builds up a structure using a typed dataset; when it is done it
wants to output it as xml. Unfortunately, since <OBJECT> and <SOURCE>
elements appear in two different parts of the heirarchy, I had to rename
one pair as <OBJECT2> and <SOURCE2> in order to get the xsd to be valid. So
I have to rename them back when I write it out. Also, the xml generated is
wrapped in an outer <PPMLds>...</PPMLds> item, that I need to get rid of.

So I thought I'd use this simple problem as an opportunity to dive into
XSLT. I ended up with the transform shown below. It works fine with
msxsl.exe. It also works with System.XML.XSL.XSLTransform, but with one
difference: nodes with no subelements or text, that would should like

<OCCURRENCE Name="Coffee_Maker" />

have been turned into

<OCCURRENCE Name="Coffee_Maker">
</OCCURENCE_NAME>

I added the next-to-last template to my xslt precisely in order to get rid
of this problem, and that was what worked in msxsl.exe. But it doesn't work
in .NET. I tried nxslt, and it gave the same results as my VB.NET program.

The gripping hand is that the consumer of these PPML files, the EFI Fiery
RIP, chokes on the empty nodes. The job runs when they are collapsed, and
dies when they are not collapsed.

I'd appreciate any comments or suggestions you might have. I suspect that
my XSLT might be taking the wrong approach, and if there's a better way
then I'd help with that too.

Sample source XML

<PPMLds>
<PPML Creator="ImtekVDP">
<PAGE_DESIGN TrimBox="0 0 1296.0000 864.0000" />
<DOCUMENT_SET Label="Variable data run">
<REUSABLE_OBJECT>
<OBJECT Position="0 0">
<SOURCE Format="application/pdf" Dimensions="648.0000 414.0000">
<EXTERNAL_DATA Src="static/Kiosk Coin 15dollar.pdf" />
</SOURCE>
</OBJECT>
<OCCURRENCE_LIST>
<OCCURRENCE Name="Kiosk Coin 15dollar" />
</OCCURRENCE_LIST>
</REUSABLE_OBJECT>
<DOCUMENT DocumentCopies="1" Label="D:\temp\variable.001.pdf">
<PAGE Label="Batch 1 set 1 sheet 1">
<MARK Position="432.0000 648.0000">
<OCCURRENCE_REF Ref="BackCover" />
</MARK>
<MARK Position="0 0">
<OBJECT2 Position="0 0">
<SOURCE2 Format="application/pdf"
Dimensions="1296.0000 864.0000">
<EXTERNAL_DATA_ARRAY Src="variable.001.pdf" Index="1" />
</SOURCE2>
</OBJECT2>
</MARK>
</PAGE>
</DOCUMENT>
</DOCUMENT_SET>
</PPML>
</PPMLds>

My XSLT file:

<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" encoding="utf-8" />

<xsl:template match="PPMLds">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="OBJECT2">
<xsl:element name="OBJECT">
<xsl:apply-templates select="node()|@*" />
</xsl:element>
</xsl:template>
<xsl:template match="SOURCE2">
<xsl:element name="SOURCE">
<xsl:apply-templates select="node()|@*" />
</xsl:element>
</xsl:template>
<xsl:template
match="EXTERNAL_DATA_ARRAY|EXTERNAL_DATA|OCCURRENCE_REF|OCCURRENCE|PAGE_DESIGN">
<xsl:copy>
<xsl:apply-templates select="@*" />
</xsl:copy>
</xsl:template>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Expected output

<?xml version="1.0" encoding="utf-8"?>
<PPML Creator="ImtekVDP">
<PAGE_DESIGN TrimBox="0 0 1296.0000 864.0000" />
<DOCUMENT_SET Label="Variable data run">
<REUSABLE_OBJECT>
<OBJECT Position="0 0">
<SOURCE Format="application/pdf" Dimensions="648.0000 414.0000">
<EXTERNAL_DATA Src="static/Kiosk Coin 15dollar.pdf" />
</SOURCE>
</OBJECT>
<OCCURRENCE_LIST>
<OCCURRENCE Name="Kiosk Coin 15dollar" />
</OCCURRENCE_LIST>
</REUSABLE_OBJECT>
<DOCUMENT DocumentCopies="1" Label="D:\temp\variable.001.pdf">
<PAGE Label="Batch 1 set 1 sheet 1">
<MARK Position="432.0000 648.0000">
<OCCURRENCE_REF Ref="BackCover" />
</MARK>
<MARK Position="0 0">
<OBJECT Position="0 0">
<SOURCE Format="application/pdf" Dimensions="1296.0000
864.0000">
<EXTERNAL_DATA_ARRAY Src="variable.001.pdf" Index="1" />
</SOURCE>
</OBJECT>
</MARK>
</PAGE>
</DOCUMENT>
</DOCUMENT_SET>
</PPML>

Actual output:

<?xml version="1.0" encoding="utf-8"?>
<PPML Creator="ImtekVDP">
<PAGE_DESIGN TrimBox="0 0 1296.0000 864.0000"></PAGE_DESIGN>
<DOCUMENT_SET Label="Variable data run">
<REUSABLE_OBJECT>
<OBJECT Position="0 0">
<SOURCE Format="application/pdf" Dimensions="648.0000 414.0000">
<EXTERNAL_DATA Src="static/Kiosk Coin
15dollar.pdf"></EXTERNAL_DATA>
</SOURCE>
</OBJECT>
<OCCURRENCE_LIST>
<OCCURRENCE Name="Kiosk Coin 15dollar"></OCCURRENCE>
</OCCURRENCE_LIST>
</REUSABLE_OBJECT>
<DOCUMENT DocumentCopies="1" Label="D:\temp\variable.001.pdf">
<PAGE Label="Batch 1 set 1 sheet 1">
<MARK Position="432.0000 648.0000">
<OCCURRENCE_REF Ref="BackCover"></OCCURRENCE_REF>
</MARK>
<MARK Position="0 0">
<OBJECT Position="0 0">
<SOURCE Format="application/pdf" Dimensions="1296.0000
864.0000">
<EXTERNAL_DATA_ARRAY Src="variable.001.pdf"
Index="1"></EXTERNAL_DATA_ARRAY>
</SOURCE>
</OBJECT>
</MARK>
</PAGE>
</DOCUMENT>
</DOCUMENT_SET>
Derek Harmon
2/17/2005 12:28:36 AM
[quoted text, click to view]

Wrap an XmlTextWriter around the output from the XslTransform's Transform( )
method and intercepting calls that XslTransform makes to the WriteFullEndElement( )
method into calls that your custom XmlTextWriter makes to WriteEndElement( ).

It's the WriteEndElement( ) that produces the more compact empty element (it
also maintains state about the existence of any child nodes, and so produces
a full end tag where one is necessary).

- - - XmlTextWriterEE.cs
using System;
using System.IO;
using System.Xml;


/// <summary>
/// Wrapper that forces more compact empty element end
/// tags to be written whenever possible.
/// </summary>
public class XmlTextWriterEE : XmlTextWriter {
public XmlTextWriterEE( TextWriter sink) : base( sink) {;}
public override void WriteFullEndElement( ) {
base.WriteEndElement( );
}
}
- - -

This XmlTextWriter subclass would work something like this in your application,

xslDoc.Transform( xmlDoc.CreateNavigator( ),
null, new XmlTextWriterEE( Console.Out) );

[quoted text, click to view]

There is nothing wrong with the XSLT, and changing the XSLT won't affect the
output you're seeing. XslTransform just happens to always write out a full end
tag like that when it drives XmlWriter to produce output. Using an XmlText-
Writer like in the above example will intercept these method calls and produce
the more compact-looking tags where possible.


Derek Harmon

Ross Presser
2/17/2005 11:03:47 AM
[quoted text, click to view]

Thank you! This was precisely what was needed. (I also added "Formatting =
AddThis Social Bookmark Button