XML Bestiary: XmlTransformingReader

| No Comments | No TrackBacks

Here is another beast for XML bestiary I've created yesterday just for fun to encapsulate commonly used functionality in an efficient way. It's XmlTransformingReader class. The idea is extremelly simple: XmlTransformingReader is XmlReader implementation, which encapsulates arbitrary XSL Transformation of input XML stream. Or to put it in another way - XmlTransformingReader reads input XML stream, transforms it internally using provided XSLT stylesheet and allows the resulting XML stream to be read from itself. For code-minded geeks here is the implementation:

public class XmlTransformingReader : XmlReader {
    private XmlReader _outReader;

    #region Constructors
    public XmlTransformingReader(string source, string transformSource) {
        XPathDocument doc = new XPathDocument(source);
        XslTransform xslt = new XslTransform();
        xslt.Load(transformSource);
        _outReader = xslt.Transform(doc, null, new XmlUrlResolver());
    }
    //...Dozen other constructors ...
    #endregion

    #region XmlReader impl methods	
    public override int AttributeCount {
        get { return _outReader.AttributeCount;}
    }
    public override string BaseURI {
        get { return _outReader.BaseURI; }
    }
    //The rest 20+ XmlReader methods/properies implemented in the same way
}
Probably even too simple, but still quite usable. It allows to modify XML on the fly, but of course it's not streaming plumbing as it embeds XSLT. Such reader can be useful to encapsulte complex XML transformations into a single XmlReader. Sure it allows also to implement easily simple local XML modifications, traditionally performed at SAX/XmlReader level, such as renaming/filtering nodes, converting attributes to elements etc., but I urge you to keep streaming processing. The main goal of XmlTransformingReader is to enable complex XML modifications, such as involve sorting, grouping, anyone that cannot be done in forward-only non-caching way XmlReader works.
It's time for a sample. Here is how one can read three most expensive items from an inventory list :

inventory.xml

<parts>
    <item SKU="1001" name="Hairdrier" price="39.99"/>
    <item SKU="1001" name="Lawnmower" price="299.99"/>
    <item SKU="1001" name="Spade" price="19.99"/>
    <item SKU="1001" name="Electric drill" price="99.99"/>
    <item SKU="1001" name="Screwdriver" price="9.99"/>
</parts>
filtering stylesheet
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="parts">
        <parts>
            <xsl:apply-templates select="item">
                <xsl:sort data-type="number" 
                order="descending" select="@price"/>
            </xsl:apply-templates>
        </parts>
    </xsl:template>
    <xsl:template match="item">
        <xsl:if test="position() &lt;= 3">
            <xsl:copy-of select="."/>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>
And finally the code:
XmlReader xtr = new XmlTransformingReader("inventory.xml", "filter.xsl");
//That's it, now let's dump out XmlTransformingReader to see what it returns
XmlTextWriter w = new XmlTextWriter(Console.Out);
w.Formatting = Formatting.Indented;
w.WriteNode(xtr, false);
xtr.Close();
w.Close();
The result:
<parts>
  <item SKU="1001" name="Lawnmower" price="299.99" />
  <item SKU="1001" name="Electric drill" price="99.99" />
  <item SKU="1001" name="Hairdrier" price="39.99" />
</parts>

I've uploaded XmlTransformingReader sources to GotDotNet.com user samples section and surprisingly it was downloaded already 81 times in first 10 hours. Well, honestly that's not something new, all this stuff's able to do is to save couple of lines for an experienced developer, but my hope is it will be used by average users and will help them to avoid so common and so ugly piping of transformations with interim XmlDocument. Or may be it's just an exercise in advertising during these boring days. :)

Related Blog Posts

No TrackBacks

TrackBack URL: http://www.tkachenko.com/cgi-bin/mt-tb.cgi/114

Leave a comment