[XSL-LIST Mailing List Archive Home]
[By Thread]
[By Date]
[Recent Entries]
[Reply To This Message]
Re: How to design XPath queries and XSLT code that can
Regardless of what data techniques you decide to use, look into the
use of <xsl:import> and the writing of "core" stylesheet libraries
that are exploited by "shell" stylesheet contexts that deliver
results based on the core.
Here is a case study of a stylesheet library created for the US
intelligence community. How can multiple organizations (CENTCOM,
STRATCOM, Coast Guard, etc.) get multiple renderings of one piece of
XML without having to rewrite all of code? By putting the shared
logic in a shared library and putting the specific logic in a shell
(many, many, shells each adding more specialization to the core):
http://web.archive.org/web/20071113054804/http://www.idealliance.org/proceedings/xml05/ship/18/Holman-18.HTML
The (dreaded) XML namespaces is your friend here: build the library
using namespaces on every possible global construct (keys, variables,
template rules, etc.) to qualify the construct name in order to avoid
accidental collision by someone writing a shell and not knowing the
names of the constructs used in the library.
In fact I used two namespaces in the core: one for constructs that
are used internally by the core and not expected to be touched by the
shells, and one for constructs in the core available to be tweaked by
a shell. All to prevent future inadvertent behaviours because the
developers writing the shells are not always the developers writing
the core. The core can be made an effective "black box" by using namespaces.
To document all this I created an XSLT documentation methodology
named XSLStyle to enforce my stylesheet writing rules regarding the
appropriate use of namespaces on global constructs. At the same time
it produces summary documentation:
https://cranesoftwrights.github.io/resources/#xslstyle
And the documentation produced illuminates the import/include
hierarchy and produces an alphabetized index of all of the global constructs.
I hope this is helpful.
. . . . . . Ken
At 2019-08-02 18:08 +0000, Costello, Roger L. costello@xxxxxxxxx wrote:
Hi Folks,
How to design XPath queries and XSLT code so that a complete rewrite
isn't required every time there is a small change in
requirements? Stated another way, how to design XPath queries and
XSLT code that can be readily repurposed? Allow me to explain what I
mean with a concrete example, please ...
I have three XML files:
1. routes.xml ... this file shows the routes that cars take to
travel between two cities.
2. rest-stops.xml ... this file shows the rest stops on routes.
3. gas-stations.xml ... this file shows the location of gas
stations. Some rest stops have gas stations, others don't.
My initial task was to display the gas stations at rest stops on the
routes from Boston to NYC. I wrote some XPath queries and XSLT code.
It worked - yea!
Then I was tasked to display, for each route, the gas stations at
rest stops. For example, display the gas stations at the rest stops
on route I-84, display the gas stations at the rest stops on route
I-95, and so on.
I was shocked at the amount of re-coding that I had to do for the
second task. After all, in the first task I had already figured out
how to identify the rest stops along each route, which rest stops
have gas stations, and so on. The second task should have been a
simple matter of reusing the XPath query results from the first task
to display the results in a slightly different way. But no, I had to
do a complete rewrite.
And now I am getting tasked to display the data in still another way
- show the data as triples (route, rest stop, gas station). Eek! I
can already see that I will have to do a third rewrite.
Clearly my XPath queries and XSLT code are not suitable for
repurposing. I remember reading long ago a book about this issue of
writing code in a way that when a new requirement is introduced it
doesn't require a complete rewrite. Wish I could remember what the
book's solution to the problem is.
How can I design my XPath queries and XSLT code in a way that they
can be readily repurposed?
Here is routes.xml
<Routes>
<row>
<IDENT>I-84</IDENT>
<Start-End>BOS-NYC</Start-End>
</row>
<row>
<IDENT>I-95</IDENT>
<Start-End>BOS-NYC</Start-End>
</row>
<row>
<IDENT>RTE-1A</IDENT>
<Start-End>Bangor-Miami</Start-End>
</row>
</Routes>
The IDENT element is a primary key and also a foreign key into the
rest stop file. The rest stops for a route with IDENT i are those
rows in rest-stops.xml with the Road element equal to i.
Here is rest-stops.xml
<Rest-Stops>
<row>
<Name>Willington</Name>
<Road>I-84</Road>
<Direction>south</Direction>
<Gas>BP-Willington-South</Gas>
</row>
<row>
<Name>Willington</Name>
<Road>I-84</Road>
<Direction>north</Direction>
<Gas>BP-Willington-North</Gas>
</row>
<row>
<Name>Stormville</Name>
<Road>I-84</Road>
<Direction>north</Direction>
</row>
<row>
<Name>Southington</Name>
<Road>I-84</Road>
<Direction>south</Direction>
<Gas>Exxon-Southington</Gas>
</row>
<row>
<Name>Branford</Name>
<Road>I-95</Road>
<Direction>south</Direction>
<Gas>Mobil-Branford-South</Gas>
</row>
<row>
<Name>Branford</Name>
<Road>I-95</Road>
<Direction>north</Direction>
<Gas>Mobil-Branford-North</Gas>
</row>
<row>
<Name>Darien</Name>
<Road>I-95</Road>
<Direction>north</Direction>
</row>
</Rest-Stops>
The Gas element is a foreign key into the gas station file. The gas
station at rest stop r is the row in gas-stations.xml with the IDENT
element equal to r.
Here is gas-stations.xml
<Gas-Stations>
<row>
<IDENT>BP-Miami</IDENT>
<Gas>Shell</Gas>
<Location>Miami</Location>
</row>
<row>
<IDENT>BP-Washington</IDENT>
<Gas>BO</Gas>
<Location>Washington</Location>
</row>
<row>
<IDENT>BP-Willington-North</IDENT>
<Gas>BP</Gas>
<Location>Willington</Location>
</row>
<row>
<IDENT>BP-Willington-South</IDENT>
<Gas>BP</Gas>
<Location>Willington</Location>
</row>
<row>
<IDENT>Shell-Willington</IDENT>
<Gas>Shell</Gas>
<Location>Willington</Location>
</row>
<row>
<IDENT>Exxon-Southington</IDENT>
<Gas>Exxon</Gas>
<Location>Southington</Location>
</row>
<row>
<IDENT>Mobil-Branford-North</IDENT>
<Gas>Mobil</Gas>
<Location>Branford</Location>
</row>
<row>
<IDENT>Mobil-Branford-South</IDENT>
<Gas>Mobil</Gas>
<Location>Branford</Location>
</row>
</Gas-Stations>
Here are the results for the first task (show the gas stations at
rest stops on the routes from Boston to NYC):
<Gas-stations-at-rest-stops-on-routes-from-BOS-to-NYC>
<row>
<IDENT>BP-Willington-South</IDENT>
<Gas>BP</Gas>
<Location>Willington</Location>
</row>
<row>
<IDENT>BP-Willington-North</IDENT>
<Gas>BP</Gas>
<Location>Willington</Location>
</row>
<row>
<IDENT>Exxon-Southington</IDENT>
<Gas>Exxon</Gas>
<Location>Southington</Location>
</row>
<row>
<IDENT>Mobil-Branford-South</IDENT>
<Gas>Mobil</Gas>
<Location>Branford</Location>
</row>
<row>
<IDENT>Mobil-Branford-North</IDENT>
<Gas>Mobil</Gas>
<Location>Branford</Location>
</row>
</Gas-stations-at-rest-stops-on-routes-from-BOS-to-NYC>
In my XSLT program, I stored the three files in three variables:
<xsl:variable name="routes" select="doc('routes.xml')" />
<xsl:variable name="rest-stops" select="doc('rest-stops.xml')" />
<xsl:variable name="gas-stations" select="doc('gas-stations.xml')" />
I selected the rows in $routes that are for BOS-NYC:
<xsl:variable name="BOS-NYC-routes"
select="$routes//row[Start-End eq 'BOS-NYC']"
as="element(row)*"/>
I queried for the rest stops on those routes:
<xsl:variable name="rest-stops-on-BOS-NYC-routes" as="element(row)*">
<xsl:for-each select="$BOS-NYC-routes">
<xsl:variable name="route" select="." as="element(row)" />
<xsl:sequence select="$rest-stops//row[Road eq $route/IDENT]" />
</xsl:for-each>
</xsl:variable>
Not all the rest stops have gas stations; I queried for those with a
Gas element:
<xsl:variable name="rest-stops-on-BOS-NYC-routes-with-gas-station"
select="$rest-stops-on-BOS-NYC-routes/self::row[Gas]"
as="element(row)*"/>
I then used the Gas element as a foreign key into gas-stations.xml:
<xsl:variable name="gas-stations-on-BOS-NYC-routes" as="element(row)*">
<xsl:for-each select="$rest-stops-on-BOS-NYC-routes-with-gas-station">
<xsl:variable name="rest-stop" select="." as="element(row)" />
<xsl:sequence select="$gas-stations//row[IDENT eq $rest-stop/Gas]" />
</xsl:for-each>
</xsl:variable>
Lastly, I displayed the results (gas stations at rest stops on
routes from Boston to NYC):
<Gas-stations-at-rest-stops-on-routes-from-BOS-to-NYC>
<xsl:sequence select="$gas-stations-on-BOS-NYC-routes" />
</Gas-stations-at-rest-stops-on-routes-from-BOS-to-NYC>
The complete XSLT program is shown below. I also show the code to
implement the second task (display, for each route, the gas stations
at rest stops); notice that the code is essentially a complete
rewrite of the first task. How can I design the XPath queries and
XSLT code so that I don't have to do a complete rewrite every time
there is a requirement change? /Roger
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="3.1">
<xsl:output method="xml" />
<xsl:variable name="routes" select="doc('routes.xml')" />
<xsl:variable name="rest-stops" select="doc('rest-stops.xml')" />
<xsl:variable name="gas-stations" select="doc('gas-stations.xml')" />
<xsl:template match="/">
<xsl:variable name="BOS-NYC-routes"
select="$routes//row[Start-End eq 'BOS-NYC']" as="element(row)*"/>
<xsl:variable name="rest-stops-on-BOS-NYC-routes" as="element(row)*">
<xsl:for-each select="$BOS-NYC-routes">
<xsl:variable name="route" select="." as="element(row)" />
<xsl:sequence select="$rest-stops//row[Road eq
$route/IDENT]" />
</xsl:for-each>
</xsl:variable>
<xsl:variable
name="rest-stops-on-BOS-NYC-routes-with-gas-station"
select="$rest-stops-on-BOS-NYC-routes/self::row[Gas]" as="element(row)*"/>
<xsl:variable name="gas-stations-on-BOS-NYC-routes"
as="element(row)*">
<xsl:for-each
select="$rest-stops-on-BOS-NYC-routes-with-gas-station">
<xsl:variable name="rest-stop" select="."
as="element(row)" />
<xsl:sequence select="$gas-stations//row[IDENT eq
$rest-stop/Gas]" />
</xsl:for-each>
</xsl:variable>
<Results>
<Gas-stations-at-rest-stops-on-routes-from-BOS-to-NYC>
<xsl:sequence select="$gas-stations-on-BOS-NYC-routes" />
</Gas-stations-at-rest-stops-on-routes-from-BOS-to-NYC>
<!-- Eek! Creating a new display of the data involves
essentially a complete rewrite! -->
<Gas-stations-on-each-BOS-NYC-route>
<xsl:for-each select="$BOS-NYC-routes">
<xsl:variable name="route" select="."
as="element(row)" />
<xsl:variable
name="rest-stops-on-BOS-NYC-route" select="$rest-stops//row[Road eq
$route/IDENT]" as="element(row)*" />
<xsl:variable
name="rest-stops-on-BOS-NYC-route-with-gas-station"
select="$rest-stops-on-BOS-NYC-route/self::row[Gas]" as="element(row)*"/>
<xsl:variable
name="gas-stations-on-BOS-NYC-route" as="element(row)*">
<xsl:for-each
select="$rest-stops-on-BOS-NYC-route-with-gas-station">
<xsl:variable name="rest-stop"
select="." as="element(row)" />
<xsl:sequence
select="$gas-stations//row[IDENT eq $rest-stop/Gas]" />
</xsl:for-each>
</xsl:variable>
<Gas-stations-on-one-route>
<Route>
<xsl:sequence select="$route" />
</Route>
<Gas-stations>
<xsl:sequence
select="$gas-stations-on-BOS-NYC-route" />
</Gas-stations>
</Gas-stations-on-one-route>
</xsl:for-each>
</Gas-stations-on-each-BOS-NYC-route>
</Results>
</xsl:template>
</xsl:stylesheet>
--
Contact info, blog, articles, etc. http://www.CraneSoftwrights.com/s/ |
Check our site for free XML, XSLT, XSL-FO and UBL developer resources |
Streaming hands-on XSLT/XPath 2 training class @ US$45 (5 hours free) |
|
PURCHASE STYLUS STUDIO ONLINE TODAY!
Purchasing Stylus Studio from our online shop is Easy, Secure and Value Priced!
Download The World's Best XML IDE!
Accelerate XML development with our award-winning XML IDE - Download a free trial today!
Subscribe in XML format
RSS 2.0 |
|
Atom 0.3 |
|
|