[Date Prev]
| [Thread Prev]
| [Thread Next]
| [Date Next]
--
[Date Index]
| [Thread Index]
RE: Declarative programming requires a different mindset
- From: "Costello, Roger L." <costello@mitre.org>
- To: "xml-dev@lists.xml.org" <xml-dev@lists.xml.org>
- Date: Sat, 20 Mar 2010 12:35:55 -0400
Hi Folks,
The imperative mindset dies hard.
Yesterday I wrote an XSLT transform. I was very proud of it. This morning I realized that I had written it imperatively. Oops!
I re-wrote it, in a declarative fashion. I'd like to share with you the imperative version and then the declarative version.
Perhaps you've had a similar experience? Would you show how you started with an imperative implementation and then re-worked it into a declarative implementation?
Problem: create an XSLT transform that changes an XML document's namespaces and prefixes.
Example: Suppose an XML document uses these namespaces:
- http://www.secret-domain.org
- http://www.temperature.org
And it uses these namespace prefixes:
- Secret
- Temp
My XSLT transform maps each namespace to a new namespace, e.g.,
Change:
- http://www.secret-domain.org
to:
- http://www.namespaces.org/NS1/
My XSLT also creates a new namespace prefix, e.g., change the prefix, Secret, to the prefix, NS1.
Here's a sample XML document:
<Secret:Weather xmlns:Secret="http://www.secret-domain.org">
<Secret:Location>Atlantistan</Secret:Location>
<Secret:Date>2009-09-30T12:26:00</Secret:Date>
<Temp:Temperature xmlns:Temp="http://www.temperature.org">
91
</Temp:Temperature>
</Secret:Weather>
Here's the output of my XSLT transform:
<NS1:Weather xmlns:NS1="http://www.namespaces.org/NS1/">
<NS1:Location>Atlantistan</NS1:Location>
<NS1:Date>2009-09-30T12:26:00</NS1:Date>
<NS2:Temperature xmlns:NS2="http://www.namespaces.org/NS2/">
91
</NS2:Temperature>
</NS1:Weather>
Notice that the namespaces and prefixes have been changed.
I used XSLT 1.0 to implement this.
VERSION #1 (IMPERATIVE MINDSET)
I created two string variables, one to hold the namespaces of the input XML document and a second variable to hold the namespaces of the output XML document (and I prefixed the latter namespaces):
<xsl:variable name="input-document-namespaces"
select="'http://www.secret-domain.org
http://www.temperature.org'"/>
<xsl:variable name="use-these-namespaces"
select="'NS1:http://www.namespaces.org/NS1/
NS2:http://www.namespaces.org/NS2/'"/>
I wrote an XSLT named template to walk down the two string variables in parallel. My "list walking" code is at the end of this message.
"Walk down two lists" ... that's an imperative mindset. Oops!
VERSION #2 (DECLARATIVE MINDSET)
This morning I remembered a fabulous technique that I learned from Dimitre Novatchev.
Rather than creating string variables, create some XML and insert it directly into the XSLT transform document:
<f:namespace-mappings>
<f:namespace-map>
<f:input-document>
<f:namespace>
http://www.secret-domain.org
</f:namespace>
</f:input-document>
<f:output-document>
<f:namespace>
http://www.namespaces.org/NS1/
</f:namespace>
<f:prefix>
NS1
</f:prefix>
</f:output-document>
</f:namespace-map>
<f:namespace-map>
<f:input-document>
<f:namespace>
http://www.temperature.org
</f:namespace>
</f:input-document>
<f:output-document>
<f:namespace>
http://www.namespaces.org/NS2/
</f:namespace>
<f:prefix>
NS2
</f:prefix>
</f:output-document>
</f:namespace-map>
</f:namespace-mappings>
Note: the XML must be in a namespace other than the XSL namespace. I placed this namespace declaration at the top of my XSLT document:
xmlns:f="f:8B9C63F4-F4AB5D11-994A0001-B4CD626F"
The XML contains a <namespace-map> element for each input namespace. It maps an input namespace to the desired output namespace and to a prefix. For example, it maps this input namespace:
http://www.secret-domain.org
to this output namespace:
http://www.namespaces.org/NS1/
and to this prefix:
NS1:
Suppose the value of a variable, $ns, is an input namespace (such as http://www.secret-domain.org). The output namespace and prefix is obtained simply by looking them up in the XML:
<xsl:variable name="namespace-map"
select="document('')/*//f:namespace-map
[f:input-document/f:namespace=$ns]" />
<xsl:variable name="use-this-namespace"
select="$namespace-map/f:output-document/f:namespace" />
<xsl:variable name="use-this-prefix"
select="$namespace-map/f:output-document/f:prefix" />
Recap: the mappings are in XML. Select the desired values simply by navigating the XML.
That's declarative. That's ultra cool.
I am very pleased with my new, declarative, XSLT transform. It is:
1. Shorter than the imperative version.
2. Much easier to understand.
/Roger
IMPERATIVE CODE THAT WALKS TWO LISTS IN PARALLEL
In parallel, walk down list1 and list2. Upon finding list1-value (in list1), return the corresponding value in list2.
<xsl:template name="walk-two-lists">
<xsl:param name="list1" />
<xsl:param name="list2" />
<xsl:param name="list1-value" />
<xsl:choose>
<xsl:when test="$list1 = $list1-value">
<xsl:value-of select="$list2" />
</xsl:when>
<xsl:when test="substring-before($list1, ' ') = $list1-value">
<xsl:value-of select="substring-before($list2, ' ')" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="walk-two-lists">
<xsl:with-param name="list1"
select="substring-after($list1, ' ')" />
<xsl:with-param name="list2"
select=" substring-after($list2, ' '))" />
<xsl:with-param name="list1-value"
select="$list1-value" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
[Date Prev]
| [Thread Prev]
| [Thread Next]
| [Date Next]
--
[Date Index]
| [Thread Index]