开发者

Optimization of XSLT

I'm relatively new to XSL and am attempting to elegantly transform a Google Calendar feed into something more readable.

I would appreciate your eyes on whether there are optimizations to be made. In particular, I would like your advice on template use. I've read a lot about how for-each is not appropriate to use willy-nilly (rather, one should attempt to make judicious use of templates).

Thank you very much.

Original XML (showing only one event):

<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gCal='http://schemas.google.com/gCal/2005' xmlns:gd='http://schemas.google.com/g/2005'>
    <id>http://www.google.com/calendar/feeds/bachya1208%40gmail.com/public/full</id>
    <updated>2011-09-19T21:32:50.000Z</updated>
    <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/>
    <title type='text'>John Doe</title>
    <subtitle type='text'>John Doe</subtitle>
    <link rel='alternate' type='text/html' href='https://www.google.com/calendar/embed?src=bachya1208@gmail.com'/>
    <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full'/>
    <link rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/batch'/>
    <link rel='self' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full?max-results=25'/>
    <link rel='next' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full?start-index=26&amp;max-results=25'/>
    <author>
        <name>John Doe</name>
        <email>johndoe@gmail.com</email>
    </author>
    <generator version='1.0' uri='http://www.google.com/calendar'>Google Calendar</generator>
    <openSearch:totalResults>1334</openSearch:totalResults>
    <openSearch:startIndex>1</openSearch:startIndex>
    <openSearch:itemsPerPage>25</openSearch:itemsPerPage>
    <gCal:timezone value='America/Denver'/>
    <gCal:timesCleaned value='0'/>
    <entry>
        <id>http://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/lp0upnpndnkp0ruqht7ef84kds</id>
        <published>2011-09-14T21:15:16.000Z</published>
        <updated>2011-09-14T21:15:16.000Z</updated>
        <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/>
        <title type='text'>Oil Change</title>
        <content type='text'/>
        <link rel='alternate' type='text/html' href='https://www.google.com/calendar/event?eid=bHAwdXBucG5kbmtwMHJ1cWh0N2VmODRrZHMgYmFjaHlhMTIwOEBt' title='alternate'/>
        <link rel='self' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/lp0upnpndnkp0ruqht7ef84kds'/>
        <author>
            <name>John Doe</name>
            <email>johndoe@gmail.com</email>
        </author>
        <gd:comments>
            <gd:feedLink href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/lp0upnpndnkp0ruqht7ef84kds/comments'/>
        </gd:comments>
        <gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'/>
        <gd:where valueString='9955 E Arapahoe Road, Englewood, CO 80112 (Go Subaru Arapahoe)'/>
        <gd:who email='johndoe@gmail.com' rel='http://schemas.google.com/g/2005#event.organizer' valueString='bachya1208@gmail.com'/>
        <gd:when endTime='2011-09-29T11:30:00.000-06:00' startTime='2011-09-29T10:30:00.000-06:00'/>
        <gd:transparency value='http://schemas.google.com/g/2005#event.opaque'/>
        <gCal:anyoneCanAddSelf value='false'/>
        <gCal:guestsCanInviteOthers value='true'/>
        <gCal:guestsCanModify value='false'/>
        <gCal:guestsCanSeeGuests value='true'/>
        <gCal:sequence value='0'/>
        <gCal:uid value='lp0upnpndnkp0ruqht7ef84kds@google.com'/>
    </entry>
</feed>

XSLT:

<?xml version="1.0"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">
    <xsl:template name="formatDateTime">
        <xsl:param name="dateTime" />
        <xsl:value-of select="concat(substring-before($dateTime, 'T'), ' ', substring-before(substring-after($dateTime, 'T'), '.'))" />
    </xsl:template>
    <xsl:template match="/">
        <Events>
            <xsl:apply-templates select="/*/*[local-name()= 'entry']" />
        </Events>
    </xsl:template>
    <xsl:template match="*[local-name()= 'entry']">
        <xsl:variable name="startDateTime" select="*[name() = 'gd:when']/@*[local-name() = 'startTime']" />
        <xsl:variable name="endDateTime" select="*[name() = 'gd:when']/@*[local-name() = 'endTime']" />
        <Event>
            <EventTitle>
                <xsl:value-of select="*[local-name() = 'title'][1]" />
            </EventTitle>
            <StartDateTime>
                <xsl:call-template name="formatDateTime">
                    <xsl:with-param name="dateTime" select="$startDateTime" />
                </xsl:call-template>
            </StartDateTime>
            <EndDateTime>
                <xsl:call-template name="formatDateTime">
                    <xsl:with-param name="dateTime" select="$endDateTime" />
                </xsl:call-template>
            </EndDateTime>
            <Who>
                <xsl:value-of select="*[local-name() = 'author']/*[local-name() = 'name']" />
            </Who>
            <Where>
                <xsl:value-of select="*[name() = 'gd:where']/@*[local-name() = 'valueString']" />
            </Where>
            <Status>
                <xsl:value-of select="*[name() = 'gd:eventStatus']/@*[local-name() = 'value']" />
            </Status>
        </Event>
    </xsl:template>
</xsl:stylesheet>

Output:

<?xml version="1.0" encoding="UTF-16"?>
<Events>
    <Event>
        <EventTitle>Oil Change</EventTitle>
        <StartDateTime>2011-09-29 10:30:00</StartDateTime>
        <EndDateTime>2011-09-29 11:30:00</EndDateTime>
        <Who>John Doe</Who>
        <Where>9955 E Arapahoe Road, Englewood, CO 80112 (Go Subaru Arapahoe)</Where>
        <Status>http://schemas.google.com/g/2005#event.confirmed</开发者_C百科Status>
    </Event>
</Events>


Your approach looks fine to me. I think your XPath code would be much cleaner and would probably run faster if you used regular element selection instead of local-name. The reason you probably struggled with your XPath was because you're consuming XML that has a default namespace of http://www.w3.org/2005/Atom, and that namespace isn't declared in your stylesheet. Here's a snippet of how a more simplified stylesheet could look, using an f: prefix for the feed namespace:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:f="http://www.w3.org/2005/Atom"
                xmlns:gd="http://schemas.google.com/g/2005">

  <!-- ... -->

  <xsl:template match="/">
    <Events>
        <xsl:apply-templates select="//f:entry" />
    </Events>
  </xsl:template>

  <xsl:template match="f:entry">
    <xsl:variable name="startDateTime" select="gd:when/@startTime" />
    <xsl:variable name="endDateTime" select="gd:when/@endTime" />
    <Event>
        <EventTitle>
            <xsl:value-of select="f:title[1]" />
        </EventTitle>
        <StartDateTime>
            <xsl:call-template name="formatDateTime">
                <xsl:with-param name="dateTime" select="$startDateTime" />
            </xsl:call-template>
        </StartDateTime>
        <EndDateTime>
            <xsl:call-template name="formatDateTime">
                <xsl:with-param name="dateTime" select="$endDateTime" />
            </xsl:call-template>
        </EndDateTime>
        <Who>
            <xsl:value-of select="f:author/f:name" />
        </Who>
        <Where>
            <xsl:value-of select="gd:where/@valueString" />
        </Where>
        <Status>
            <xsl:value-of select="gd:eventStatus/@value" />
        </Status>
    </Event>
  </xsl:template>

  <!-- etc -->

</xsl:stylesheet>


I would be inclined to replace the formatDateTime template with a match template:

<xsl:template match="@*" mode="formatDateTime">
    <xsl:value-of select="concat(substring-before(., 'T'),
         ' ', substring-before(substring-after(., 'T'), '.'))" />
</xsl:template>

and change the calls to

<StartDateTime>
    <xsl:apply-templates select="$startDateTime" mode="formatDateTime"/>
</StartDateTime>
<EndDateTime>
    <xsl:apply-templates select="$endDateTime" mode="formatDateTime"/>
</EndDateTime>

Just because the call-template syntax is so verbose.

(and I would probably inline the variables too - they don't see to add value).


Here is a complete transformation that is derived from the provided, solving the default namespace problem (as already done by @Jacob), but also completely eliminating the unnecessary template matching the document node (/) and assuring that two unwanted namespaces will not appear on every (literal result) element in the output:

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:a="http://www.w3.org/2005/Atom"
  xmlns:gd="http://schemas.google.com/g/2005"
  exclude-result-prefixes="a gd">

  <xsl:output omit-xml-declaration="yes" indent="yes"/>

   <xsl:template match="a:entry[1]">
    <Events>
      <xsl:apply-templates select="../a:entry" mode="process"/>
    </Events>
   </xsl:template>

   <xsl:template match="a:entry" mode="process">
    <xsl:variable name="startDateTime" select="gd:when/@startTime" />
    <xsl:variable name="endDateTime" select="gd:when/@endTime" />
    <Event>
        <EventTitle>
            <xsl:value-of select="a:title[1]" />
        </EventTitle>
        <StartDateTime>
            <xsl:call-template name="formatDateTime">
                <xsl:with-param name="dateTime" select="$startDateTime" />
            </xsl:call-template>
        </StartDateTime>
        <EndDateTime>
            <xsl:call-template name="formatDateTime">
                <xsl:with-param name="dateTime" select="$endDateTime" />
            </xsl:call-template>
        </EndDateTime>
        <Who>
            <xsl:value-of select="a:author/a:name" />
        </Who>
        <Where>
            <xsl:value-of select="gd:where/@valueString" />
        </Where>
        <Status>
            <xsl:value-of select="gd:eventStatus/@value" />
        </Status>
    </Event>
  </xsl:template>

    <xsl:template name="formatDateTime">
        <xsl:param name="dateTime" />
        <xsl:value-of select="concat(substring-before($dateTime, 'T'), ' ', substring-before(substring-after($dateTime, 'T'), '.'))" />
    </xsl:template>

 <xsl:template match="text()|a:entry"/>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gCal='http://schemas.google.com/gCal/2005' xmlns:gd='http://schemas.google.com/g/2005'>
    <id>http://www.google.com/calendar/feeds/bachya1208%40gmail.com/public/full</id>
    <updated>2011-09-19T21:32:50.000Z</updated>
    <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/>
    <title type='text'>John Doe</title>
    <subtitle type='text'>John Doe</subtitle>
    <link rel='alternate' type='text/html' href='https://www.google.com/calendar/embed?src=bachya1208@gmail.com'/>
    <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full'/>
    <link rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/batch'/>
    <link rel='self' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full?max-results=25'/>
    <link rel='next' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full?start-index=26&amp;max-results=25'/>
    <author>
        <name>John Doe</name>
        <email>johndoe@gmail.com</email>
    </author>
    <generator version='1.0' uri='http://www.google.com/calendar'>Google Calendar</generator>
    <openSearch:totalResults>1334</openSearch:totalResults>
    <openSearch:startIndex>1</openSearch:startIndex>
    <openSearch:itemsPerPage>25</openSearch:itemsPerPage>
    <gCal:timezone value='America/Denver'/>
    <gCal:timesCleaned value='0'/>
    <entry>
        <id>http://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/lp0upnpndnkp0ruqht7ef84kds</id>
        <published>2011-09-14T21:15:16.000Z</published>
        <updated>2011-09-14T21:15:16.000Z</updated>
        <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/>
        <title type='text'>Oil Change</title>
        <content type='text'/>
        <link rel='alternate' type='text/html' href='https://www.google.com/calendar/event?eid=bHAwdXBucG5kbmtwMHJ1cWh0N2VmODRrZHMgYmFjaHlhMTIwOEBt' title='alternate'/>
        <link rel='self' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/lp0upnpndnkp0ruqht7ef84kds'/>
        <author>
            <name>John Doe</name>
            <email>johndoe@gmail.com</email>
        </author>
        <gd:comments>
            <gd:feedLink href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/lp0upnpndnkp0ruqht7ef84kds/comments'/>
        </gd:comments>
        <gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'/>
        <gd:where valueString='9955 E Arapahoe Road, Englewood, CO 80112 (Go Subaru Arapahoe)'/>
        <gd:who email='johndoe@gmail.com' rel='http://schemas.google.com/g/2005#event.organizer' valueString='bachya1208@gmail.com'/>
        <gd:when endTime='2011-09-29T11:30:00.000-06:00' startTime='2011-09-29T10:30:00.000-06:00'/>
        <gd:transparency value='http://schemas.google.com/g/2005#event.opaque'/>
        <gCal:anyoneCanAddSelf value='false'/>
        <gCal:guestsCanInviteOthers value='true'/>
        <gCal:guestsCanModify value='false'/>
        <gCal:guestsCanSeeGuests value='true'/>
        <gCal:sequence value='0'/>
        <gCal:uid value='lp0upnpndnkp0ruqht7ef84kds@google.com'/>
    </entry>
</feed>

the wanted, correct result is produced:

<Events>
  <Event>
    <EventTitle>Oil Change</EventTitle>
    <StartDateTime>2011-09-29 10:30:00</StartDateTime>
    <EndDateTime>2011-09-29 11:30:00</EndDateTime>
    <Who>John Doe</Who>
    <Where>9955 E Arapahoe Road, Englewood, CO 80112 (Go Subaru Arapahoe)</Where>
    <Status>http://schemas.google.com/g/2005#event.confirmed</Status>
  </Event>
</Events>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜