XSLT: How can I create an output using the source and this second file?
I have two XMLs, one for defining the templates for the objects of my app. and another with the actual objects. Basically in each object only a few values are changed, that's why I wanted to provide some kind of template mechanism and apply XSL to transform them into the final one.
This is a sample object:
<config>
<objects>
<object code="1000" name="object1">
<template name="decoration" buyCoins="60" />
</object>
</objects>
And this is a sample template for that object:
<config xmlns:template="object-template">
<templates>
<template name="decoration">
<connection type="make" />
<placeable width="1" length="1" moveable="true" collision="D" />
<buyable>
<requirement template:coins="buyCoins"/>
<reward xp="1" />
</buyable>
<sellable>
<reward coins="1"/>
</sellable></template></templates></config>
This is my current XSL:
<xsl:variable name="templates" select="document('../templates.xml')/config/templates//template" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//template">
<xsl:variable name="itemTemplate" select="."/>
<xsl:variable name="templateName" select="@name"/>
<xsl:variable name="selectedTemplate" select="$templates[@name = $templateName]/*" />
<xsl:for-each select="$selectedTemplate">
<!-- This part is only a test to get the values that I need -->
<xsl:for-each select=".//@*[namespace-uri() = 'object-template']">
<xsl:variable name="attributeName" select="name()"/>
<xsl:variable name="attributeValue" select="."/>
<xsl:variable name="finalValue" select="$itemTemplate/@*[local-name() = $attributeValue]"/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template match="comment()"/>
I just need to do the following:
- Iterate every item that contains a template tag
- For each one get the associated template of the templates document
- Copy everything inside the template into the object (*)
- Replace the values of the attributes inside the item (now with the template pasted) with the real value in the original template (**)
Explanation:
(*) Apply
<template name="test"><node></template>
to
<object><template name="test"></object>
becomes
<object><node></object>
(**) In the original sample above, the value of buyCoins of the item's template tag should replace the template's value of the text "buyCoins" before sending it into the output. For easy lookup and to avoid reg. exp. I am using namespaces. So what I do in the XSL is to search for all the attributes inside the template with the right namespace and search for the values. The value "60" should be put instead of "buyCoins" inside the coins attribute.
My problem is that I don't understand how to copy everything (I believe that is called an identity copy) but replace the value that I need.
Any help will be appreciated, thanks!!!
UPDATE:
The current output is:
<config xmlns:template="item-template">
<objects>
<object code="1000" name="object1" type="decorations">
</object>
</object>
If I put:
<xsl:copy-of select="."/>
Below:
<xsl:for-each select="$selectedTemplate">
Then I get:
<objects>
<object code="1000" name="object1">
<connection type="make" /><placeable width="1" length="1" moveable="true" collision="D" />
<buyable>
<requirement template:coins="buyCoins"/>
<reward xp="1" />
</buyable>
<sellable>
<reward 开发者_Python百科coins="1"/>
</sellable>
</object></objects>
That is the first part of what I need, to put on each output item the content of the template associated to it. I am having problems replacing the values now.
These tree lines in the XSL represents the data that I need:
<xsl:variable name="attributeName" select="name()"/>
<xsl:variable name="attributeValue" select="."/>
<xsl:variable name="finalValue" select="$itemTemplate/@*[local-name() = $attributeValue]"/>
For the only item in this example this is the content of each variable: attributeName will contain "template:coins" attributeValue will contain "buyCoins" finalValue will contain "60"
I need to put finalValue instead of attributeValue in the tag of that attributeName.
At that point I am stuck :(
Thanks!
Update 2:
To avoid misunderstandings, here is the EXACT output that I need:
<objects>
<object code="1000" name="object1">
<connection type="make" /><placeable width="1" length="1" moveable="true" collision="D" />
<buyable>
<requirement coins="60"/>
<reward xp="1" />
</buyable>
<sellable>
<reward coins="1"/>
</sellable>
</object></objects>
Instead of "buyCoins" in the attribute coins I need it to be "60" (The value in the input objects file). Also the best possible output should remove the namespace templates of the attributes too, because its only a flag for the XSL to know which attributes to modify.
Just for fun, this XSLT 1.0 stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:template="object-template"
exclude-result-prefixes="template">
<xsl:key name="kTemplateByName" match="templates/template" use="@name"/>
<xsl:template match="*">
<xsl:param name="pContext"/>
<xsl:element name="{name()}">
<xsl:copy-of select="namespace::*[.!='object-template']"/>
<xsl:apply-templates select="node()|@*">
<xsl:with-param name="pContext" select="$pContext"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="@*">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="templates"/>
<xsl:template match="config">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="template">
<xsl:apply-templates select="key('kTemplateByName',@name)/node()">
<xsl:with-param name="pContext" select="."/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="@template:*">
<xsl:param name="pContext"/>
<xsl:attribute name="{local-name()}">
<xsl:value-of select="$pContext//@*[name()=current()]"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
With this input:
<config xmlns:template="object-template">
<objects>
<object code="1000" name="object1">
<template name="decoration" buyCoins="60" />
</object>
</objects>
<templates>
<template name="decoration">
<connection type="make" />
<placeable width="1" length="1" moveable="true" collision="D" />
<buyable>
<requirement template:coins="buyCoins"/>
<reward xp="1" />
</buyable>
<sellable>
<reward coins="1"/>
</sellable>
</template>
</templates>
</config>
Output:
<objects>
<object code="1000" name="object1">
<connection type="make"></connection>
<placeable width="1" length="1" moveable="true" collision="D"></placeable>
<buyable>
<requirement coins="60"></requirement>
<reward xp="1"></reward>
</buyable>
<sellable>
<reward coins="1"></reward>
</sellable>
</object>
</objects>
Here is a traditional XSLT solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:template="object-template" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:param name="pParams"/>
<xsl:copy>
<xsl:apply-templates select="node()|@*">
<xsl:with-param name="pParams"
select="$pParams"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="template">
<xsl:apply-templates select="
/*/templates/template[@name = current()/@name]/node()">
<xsl:with-param name="pParams" select="@*"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="@template:*">
<xsl:param name="pParams"/>
<xsl:attribute name="{local-name()}">
<xsl:value-of select="$pParams[name()=current()]"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="templates"/>
</xsl:stylesheet>
when this transformation is applied on the followinf XML document:
<config xmlns:template="object-template">
<objects>
<object code="1000" name="object1">
<template name="decoration"
buyCoins="60" />
</object>
</objects>
<templates>
<template name="decoration">
<connection type="make" />
<placeable width="1" length="1"
moveable="true" collision="D" />
<buyable>
<requirement template:coins="buyCoins"/>
<reward xp="1" />
</buyable>
<sellable>
<reward coins="1"/>
</sellable>
</template>
</templates>
</config>
the wanted, correct result is produced:
<config xmlns:template="object-template">
<objects>
<object code="1000" name="object1">
<connection type="make"/>
<placeable width="1" length="1" moveable="true" collision="D"/>
<buyable>
<requirement coins="60"/>
<reward xp="1"/>
</buyable>
<sellable>
<reward coins="1"/>
</sellable>
</object>
</objects>
</config>
Do note:
The identity rule (template) is used to copy all nodes "as-is". The using and overriding of the identity rule is the most fundamental XSLT design pattern.
The identity rule we are using is modified to accept and pass through a single parameter, named
pParams
. This parameter is the set of all attributes of theobject/template
element that references a particular template.Any
object/template
element is matched by an xsl:template that thus overrides the identity rule. It just issues<xsl:apply-templates>
to the nodes of the corresponding (matching by@name
-templates\template
element.All nodes below the matched
templates\template
element are copied as is by the identity rule with the exception of any attribute that is in the"object-template"
namespace.A template matches any atribute with name in the
"object-template"
namespace and thus overrides the identity rule. This xsl:template uses thepParams
parameter to find in it an attribute that has the same name as the value of the currently matched attribute. The value of the "parameter-attribute" thus found is used as the value of a newly-created attribute that has the samelocal-name()
as the currently matched attribute, but is in no namespace.An empty template matching any
templates
element prevents the identity rule of copying this subtree (for a second time).
精彩评论