How to "genericize" an XML-to-XML XSL transform to work with files having different tags but same basic structure
I have a set of XML files similar to this:
Example Input File:
<a-list>
<a-item key_field="unique1" other="foo"/>
<a-item key_field="unique2" other="foo"/>
...
</a-list>
and I have a transform that will merge these files into a single file of the same structure. It generates the same outer level element (<a-list>
) containing all of the <a-item>
elements from the files, and discards duplicate entities (entities having the same key_field). I am now faced with several other sets of files, each very similar in its top level structure, that I need to perform almost the same exact kind of merge.
For example, these files might look like this:
<b-list>
<b-item different_key_field="unique93" other="foo"/>
<b-item different_key_field="unique94" other="foo"/>
...
</b-list>
These other files have different data in them, but the transform is generically the same: I need to take multiple input files that have the same structure (a root level containing a list of items, where the items are uniquely identified by a key attribute), and produce an output file that has the top level element containing all of the (unique) items from each of the input files.
Unfortunat开发者_如何学Cely, my "merge" transform operates specifically on the named elements in the input files (e.g. it has templates for <a-item>
nodes, and it specifically looks for duplicated key_field
attributes). It is fairly trivial to copy the transform, search/replace the element names and the key attribute name, but it seems horribly inefficient to duplicate the same code once for each type of input file. The only things that are different are the name of the root element, the name of the item element, and the name of the key attribute. What I want to do (merge) remains the same.
How can I write a transformation that can perform this kind of merge operation without specifying the exact names of the elements/attribute, so that I can invoke the same transform for each type of file? Is there any way to accept the root element name, item element name, and key attribute name as parameters?
As additional constraints, I'm limited to using Xalan, so I believe that means XSL 1.0 only, and it would be best to avoid any extensions.
I thought about transforming an XSL with another XSL, but that seems rather convoluted for such a simple thing.
I've tried searching here and around the internet via Google, but I'm somewhat new to XSL, and all of the words I can think to search in association with XSL or XSLT seem to have specific meanings in the XSL context that make them less useful as search terms (e.g. XSL template, generic XSL, etc.). Some vocab and some good links would be great, an example would be amazing.
Many Thanks, Russ
Here is a complete solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pDoc1File" select="'b-list-file1.xml'"/>
<xsl:param name="pDoc2File" select="'b-list-file2.xml'"/>
<xsl:param name="pElemName" select="'b-item'"/>
<xsl:param name="pKeyAttrName" select="'different_key_field'"/>
<xsl:variable name="vDoc1" select="document($pDoc1File)"/>
<xsl:variable name="vDoc2" select="document($pDoc2File)"/>
<xsl:template match="/">
<xsl:apply-templates select="$vDoc1/node()"/>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:copy-of select=
"*[name()=$pElemName]
[not(@*[name()=$pKeyAttrName]
=
$vDoc2/*/*[name()=$pElemName]
/@*[name()=$pKeyAttrName])
]"/>
<xsl:copy-of select=
"$vDoc2/*/*[name()=$pElemName]
[not(@*[name()=$pKeyAttrName]
=
$vDoc1/*/*[name()=$pElemName]
/@*[name()=$pKeyAttrName])
]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
File: b-list-file1.xml
<b-list>
<b-item different_key_field="unique93" other="foo"/>
<b-item different_key_field="unique94" other="foo"/> ...
</b-list>
File: b-list-file2.xml
<b-list>
<b-item different_key_field="unique92" other="foo"/>
<b-item different_key_field="unique93" other="foo"/> ...
</b-list>
When this transformation is applied on any XML document (not used / ignored), the wanted, correct result is produced:
<b-list>
<b-item different_key_field="unique94" other="foo"/>
<b-item different_key_field="unique92" other="foo"/>
</b-list>
Explanation:
The filepaths (URLs) to the documents to be processed are provided as global (external to the transformation) parameters.
The name of the elements is provided in a global parameter.
The name of the "key" attribute is provided as a global/external parameter.
Finally, the XPath expressions in the
select
attributes of the two<xsl:copy-of>
instructions, use the values of the parameters for selecting the appropriately-named elements and attributes.
Do note: Every XSLT processor has its own implementation of setting and passing parameters to the transformation. Read this in the Xalan documentation.
Well you can solve your problem with parameters. There is no notion of templated xsl like C++ templates for example, but with parameters you can achieve this effect. For example one of my xslt files looks like this :
<xsl:stylesheet xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:space="preserve" version="2.0">
<!--Output type to be used with the xsl:result-document-->
<xsl:output name="html" encoding="utf-8" method="html" indent="yes" doctype- system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"/>
<!--Global output type-->
<xsl:output encoding="utf-8" method="html" indent="yes" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"/>
<!--xml output-->
<xsl:output name="xml" encoding="utf-8" method="xml" indent="yes"/>
<xsl:param name="sapFile" as="xs:string" required="yes"/>
<xsl:param name="automaticDate" as="xs:string" required="yes"/>
<xsl:param name="generatePDF" as="xs:string" required="yes"/>
<xsl:param name="fileName" as="xs:string" required="yes"/>
.
.
.
The important element here is xsl:param. You can pass these parameter when you "call" your xsl transform. These parameters in your case would be for example : in one case a-list and in the other case b-list etc. Then you can use these parameters like $param
wherever you want in your xslt code so that you can achieve genericity.
I hope that give you some pointers :)
精彩评论