问题
I have the following problem that, for me, is kind of tricky,
Basically I need to be able to modify a XML input file with data that I have stored in another XML file, so I would have to use 2 input XML files,
I have the following XML file, which is the one I want to modify (basically, just make additions to it):
<?xml version="1.0" encoding="UTF-8"?>
<report xmlns="http://www.eclipse.org/birt/2005/design" version="3.2.23" id="1">
<text-prop name="displayName">PersonTemplate</text-prop>
<setup>
<simple-master-page name="MasterPage" id="2">
<footer>
<text id="3">
<prop name="contentType">html</prop>
<text-prop name="content"><![CDATA[<value-of>new Date()</value-of>]]></text-prop>
</text>
</footer>
</simple-master-page>
</setup>
<body>
<table id="4">
<column id="17"/>
<column id="18"/>
<column id="19"/>
<header>
<row id="5">
<cell id="6">
<label id="20">
<text-prop name="text">NameTitle</text-prop>
</label>
</cell>
<cell id="7">
<label id="21">
<text-prop name="text">CityTitle</text-prop>
</label>
</cell>
<cell id="8">
<label id="22">
<text-prop name="text">AgeTitle</text-prop>
</label>
</cell>
</row>
</header>
<detail>
<row id="9">
<cell id="10"/>
<cell id="11"/>
<cell id="12"/>
</row>
</detail>
</table>
</body>
</report>
And I want to make modify/make additions to it by consulting another XML file, this one, that gives me the data I want to put in the first XML file:
<?xml version="1.0" encoding="utf-8"?>
<model>
<layouts>
<layout ID="001" name="PersonTemplate" format="Table" nFields="3" >
<fields>
<field name="NameTitle"/>
<field name="CityTitle"/>
<field name="AgeTitle"/>
</fields>
</layout>
<layout ID="002" name="SchoolTemplate" format="Table" nFields="3" >
<fields>
<field name="NameTitle"/>
<field name="LocationTitle"/>
<field name="MaxCapacityTitle"/>
</fields>
</layout>
</layouts>
<reports>
<report layoutID="001">
<params>
<sources>
<source name="source1" dbURL="sampledb1.com" user="user1" password="dXNlcjE=" driver="dbDriver"/>
<source name="source2" dbURL="sampledb2.com" user="user2" password="dXNlcjI=" driver="dbDriver"/>
</sources>
<set name="set1" source="source1" querie="select Name, City, Age from PeopleTable" >
<qFields>
<qField name="Name" type="string"/>
<qField name="City" type="string"/>
<qField name="Age" type="integer"/>
</qFields>
</set>
</params>
</report>
<report layoutID="002">
<params>
<sources>
<source name="source1" dbURL="sampledb1.com" user="user1" password="dXNlcjE=" driver="dbDriver"/>
</sources>
<set name="Data Set" dataSource="source1" querie="select Name, Location, MaxCapacity from SchoolsTable" >
<qFields>
<qField name="Name" type="string"/>
<qField name="Location" type="string"/>
<qField name="MaxCapacity" type="integer"/>
</qFields>
</set>
</params>
</report>
</reports>
</model>
So, I want to produce the following XML file:
<?xml version="1.0" encoding="UTF-8"?>
<report xmlns="http://www.eclipse.org/birt/2005/design" version="3.2.23" id="1">
<text-prop name="displayName">PersonTemplate</text-prop>
<data-sources>
<data-source extensionID="this.is.a.fixed.value" name="source1">
<prop name="DriverClass">dbDriver</prop>
<prop name="databaseURL">sampledb1.com</prop>
<prop name="dbUser">user1</prop>
<encrypted-prop name="dbPassword" encryptionID="base64">dXNlcjE=</encrypted-prop>
</data-source>
</data-sources>
<data-sets>
<data-set extensionID="this.is.a.fixed.value" name="set1">
<list-prop name="columnHints">
<struct>
<prop name="columnName">Name</prop>
<text-prop name="displayName">Name</text-prop>
<text-prop name="heading">Name</text-prop>
</struct>
<struct>
<prop name="columnName">City</prop>
<text-prop name="displayName">City</text-prop>
<text-prop name="heading">City</text-prop>
</struct>
<struct>
<prop name="columnName">Age</prop>
<text-prop name="displayName">Age</text-prop>
<text-prop name="heading">Age</text-prop>
</struct>
</list-prop>
<struct name="cachedMetaData">
<list-prop name="resultSet">
<struct>
<prop name="position">1</prop>
<prop name="name">Name</prop>
<prop name="dataType">string</prop>
</struct>
<struct>
<prop name="position">2</prop>
<prop name="name">City</prop>
<prop name="dataType">string</prop>
</struct>
<struct>
<prop name="position">3</prop>
<prop name="name">Age</prop>
<prop name="dataType">integer</prop>
</struct>
</list-prop>
</struct>
<prop name="dataSource">source1</prop>
<list-prop name="resultSet">
<struct>
<prop name="position">1</prop>
<prop name="name">Name</prop>
<prop name="dataType">string</prop>
</struct>
<struct>
<prop name="position">2</prop>
<prop name="name">City</prop>
<prop name="dataType">string</prop>
</struct>
<struct>
<prop name="position">3</prop>
<prop name="name">AGE</prop>
<prop name="dataType">integer</prop>
</struct>
</list-prop>
<xml-prop name="queryText"><![CDATA[select Name, City, Age from PeopleTable]]></xml-prop>
</data-set>
</data-sets>
<setup>
<simple-master-page name="MasterPage" id="2">
<footer>
<text id="3">
<prop name="contentType">html</prop>
<text-prop name="content"><![CDATA[<value-of>new Date()</value-of>]]></text-prop>
</text>
</footer>
</simple-master-page>
</setup>
<body>
<table id="4">
<prop name="dataSet">set1</prop>
<list-prop name="boundDataColumns">
<struct>
<prop name="name">Name</prop>
<text-prop name="displayName">Name</text-prop>
<expression name="expression" type="javascript">dataSetRow["Name"]</expression>
<prop name="dataType">string</prop>
</struct>
<struct>
<prop name="name">City</prop>
<text-prop name="displayName">City</text-prop>
<expression name="expression" type="javascript">dataSetRow["City"]</expression>
<prop name="dataType">string</prop>
</struct>
<structure>
<prop name="name">Age</prop>
<text-prop name="displayName">Age</text-prop>
<expression name="expression" type="javascript">dataSetRow["Age"]</expression>
<prop name="dataType">integer</prop>
</structure>
</list-prop>
<column id="17"/>
<column id="18"/>
<column id="19"/>
<header>
<row id="5">
<cell id="6">
<label id="20">
<text-prop name="text">NameTitle</text-prop>
</label>
</cell>
<cell id="7">
<label id="21">
<text-prop name="text">CityTitle</text-prop>
</label>
</cell>
<cell id="8">
<label id="22">
<text-prop name="text">AgeTitle</text-prop>
</label>
</cell>
</row>
</header>
<detail>
<row id="9">
<cell id="10">
<data>
<prop name="resultSetColumn">Name</prop>
</data>
</cell>
<cell id="11">
<data>
<prop name="resultSetColumn">City</prop>
</data>
</cell>
<cell id="12">
<data>
<prop name="resultSetColumn">Age</prop>
</data>
</cell>
</row>
</detail>
</table>
</body>
</report>
Note: The encryptionID
and extensionID
both in data-source and data-set are fix values.
So basically the data comes from set set1
that uses source source1
, and it also comes directly from source1
, so I need to have a way of retrieving the correct data from the second XML file.
As you can see, I have can have many layout
and report
elements in the second XML file. So, first I think I need to find the displayName
property in the first XML file and then find the layout
element that in which the name
attribute matches displayName
. Then, I need to, in the second XML file, by the ID
attribute in layout
element, find the report
element that has that same value in the layoutID
attribute. From here on I would have found the correct report
element. Only now I would begin to change/make additions to the first XML file. This part I don't really know how to do. Is what I'm trying to do possible?
I know you can use the document
function to work with 2 XML files, but I don't really know how,
I really need help guys, thank you!
EDIT
I basically want to copy everything in the first XML file (already done) and then make additions to it with data stored in the second XML file (the one that starts with the <model>
element
UPDATE
Because I want the output to be an addition to the first input XML file, I started, as @Sojimanatsu recomended, by applying an identity transformation, like this (with @TimC's help on a post for the specific problem of the the special characters (XML - XSLT - Escape special characters) ):
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xmlbirtns="http://www.eclipse.org/birt/2005/design"
xpath-default-namespace="http://www.eclipse.org/birt/2005/design">
<xsl:output method="xml" omit-xml-declaration="no" indent="yes" encoding="utf-8" />
<xsl:strip-space elements="*"/>
<!--copy the whole input XML file-->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!--special treatment for setup/text-prop element-->
<xsl:template match="report/setup/simple-master-page/footer/text/text-prop">
<xsl:copy>
<xsl:attribute name="name">
<xsl:text>content</xsl:text>
</xsl:attribute>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:value-of select="." disable-output-escaping="yes"/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
NOTE: The XSLT code that deals with the setup/text-prop element is optional, because my output XML can have <value-of>new Date()</value-of>
instead of <![CDATA[<value-of>new Date()</value-of>]]>
So now I'm being able to print in the output XML exactly what I have in the first input XML. Now, as @Sojimanatsu said, I want to select specific tags and edit them with new data coming from the sencond input XML file, and I also want to add new tags/elements, like the data-sources
and data-sets
elements, but I don't know how to do this. I know I have to use the document() function but how??
To start, how can I add a new element, <data-sources>
below the <report>
element and before the <setup>
element? (<data-sources>
and <setup>
are siblings)
I tried doing this, adding a new <xsl:template>
:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xmlbirtns="http://www.eclipse.org/birt/2005/design"
xpath-default-namespace="http://www.eclipse.org/birt/2005/design">
<xsl:output method="xml" omit-xml-declaration="no" indent="yes" encoding="utf-8" />
<xsl:strip-space elements="*"/>
<!--copy the whole input XML file-->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!--special treatment for setup/text-prop element-->
<xsl:template match="report/setup/simple-master-page/footer/text/text-prop">
<xsl:copy>
<xsl:attribute name="name">
<xsl:text>content</xsl:text>
</xsl:attribute>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:value-of select="." disable-output-escaping="yes"/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:copy>
</xsl:template>
<xsl:template match="report/text-prop">
<xsl:copy-of select="."/>
<dataSources>DATA SOURCE VALUE</dataSources>
</xsl:template>
</xsl:stylesheet>
But I'm getting this output (the dataSource tag comes in the correct place but with some attributes that I don't know how got there):
<?xml version="1.0" encoding="utf-8"?>
<report xmlns="http://www.eclipse.org/birt/2005/design" version="3.2.23" id="1">
<text-prop name="displayName">PersonTemplate</text-prop>
<dataSources xmlns=""
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xmlbirtns="http://www.eclipse.org/birt/2005/design">DATA SOURCE VALUE</dataSources>
<setup>
<simple-master-page name="MasterPage" id="2">
<footer>
<text id="3">
<prop name="contentType">html</prop>
<text-prop name="content"><![CDATA[<value-of>new Date()</value-of>]]></text-prop>
</text>
</footer>
</simple-master-page>
</setup>
<body>
<table id="4">
<column id="17"/>
<column id="18"/>
<column id="19"/>
<header>
<row id="5">
<cell id="6">
<label id="20">
<text-prop name="text">NameTitle</text-prop>
</label>
</cell>
<cell id="7">
<label id="21">
<text-prop name="text">CityTitle</text-prop>
</label>
</cell>
<cell id="8">
<label id="22">
<text-prop name="text">AgeTitle</text-prop>
</label>
</cell>
</row>
</header>
<detail>
<row id="9">
<cell id="10"/>
<cell id="11"/>
<cell id="12"/>
</row>
</detail>
</table>
</body>
</report>
My real first and second input XML files are considerably bigger but once I have this example working I can produce my real XML output,
Thanks guys!
回答1:
I think you are looking for this function basically.
document() function
So the idea of this functions is to be able to select another xml file and based on some xpath iterations, use some values from that xml file, inside your new xml or whatever.
回答2:
In general, to reference elements by a certain value, you can define keys with xsl:key
and use the key
function then to find a referenced element. In case of two separate documents, if you want to find a value in a second document, you need to make sure you use the third parameter of the key
function to pass in the target document.
Additionally, in your case you are dealing with one document with elements in a namespace and the second one with elements in no namespace, that requires using prefixes for the two namespaces in the XSLT document or to make use of xpath-default-namespace
to ensure you select the right namespace.
And if you want to create new result elements in a certain namespace, like the one of your primary input document, you need to make sure you have result elements in that namespace by declaring xmlns="http://www.eclipse.org/birt/2005/design"
in the stylesheet.
It is not clear to me whether you only want to select certain data sources or sets or what are the criteria do select only one, the following processes all referenced sources and sets. Additionally, only for data sources I have attempted to add templates that transform the attribute centric secondary input data into the target structure, for the sets you have to implement that yourself, currently the default templates simply copy them.
The stylesheet is (with the secondary XML inline, of course in your real code you can use <xsl:param name="doc2" select="doc('file2.xml')"/>
)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="http://www.eclipse.org/birt/2005/design"
xmlns="http://www.eclipse.org/birt/2005/design"
exclude-result-prefixes="xs"
expand-text="yes"
version="3.0">
<!-- second document inlined for the example,
use param name="doc2" select="doc('file2.xml')
instead to load external file -->
<xsl:param name="doc2" xmlns="">
<model>
<layouts>
<layout ID="001" name="PersonTemplate" format="Table" nFields="3" >
<fields>
<field name="NameTitle"/>
<field name="CityTitle"/>
<field name="AgeTitle"/>
</fields>
</layout>
<layout ID="002" name="SchoolTemplate" format="Table" nFields="3" >
<fields>
<field name="NameTitle"/>
<field name="LocationTitle"/>
<field name="MaxCapacityTitle"/>
</fields>
</layout>
</layouts>
<reports>
<report layoutID="001">
<params>
<sources>
<source name="source1" dbURL="sampledb1.com" user="user1" password="dXNlcjE=" driver="dbDriver"/>
<source name="source2" dbURL="sampledb2.com" user="user2" password="dXNlcjI=" driver="dbDriver"/>
</sources>
<set name="set1" source="source1" querie="select Name, City, Age from PeopleTable" >
<qFields>
<qField name="Name" type="string"/>
<qField name="City" type="string"/>
<qField name="Age" type="integer"/>
</qFields>
</set>
</params>
</report>
<report layoutID="002">
<params>
<sources>
<source name="source1" dbURL="sampledb1.com" user="user1" password="dXNlcjE=" driver="dbDriver"/>
</sources>
<set name="Data Set" dataSource="source1" querie="select Name, Location, MaxCapacity from SchoolsTable" >
<qFields>
<qField name="Name" type="string"/>
<qField name="Location" type="string"/>
<qField name="MaxCapacity" type="integer"/>
</qFields>
</set>
</params>
</report>
</reports>
</model>
</xsl:param>
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:key name="layout-ref" match="layout" use="@name" xpath-default-namespace=""/>
<xsl:key name="report-ref" match="report" use="@layoutID" xpath-default-namespace=""/>
<xsl:template match="report/text-prop[@name = 'displayName']">
<xsl:next-match/>
<xsl:variable name="layout" select="key('layout-ref', ., $doc2)"/>
<xsl:variable name="report" select="key('report-ref', $layout/@ID, $doc2)"/>
<dataSources>
<xsl:apply-templates select="$report//source" xpath-default-namespace=""/>
</dataSources>
<data-sets>
<xsl:apply-templates select="$report//set" xpath-default-namespace=""/>
</data-sets>
</xsl:template>
<xsl:template match="source" xpath-default-namespace="">
<data-source extensionID="this.is.a.fixed.value" name="{@name}">
<xsl:apply-templates select="@* except @name"/>
</data-source>
</xsl:template>
<xsl:template match="source/@dbURL" xpath-default-namespace="">
<prop name="databaseURL">{.}</prop>
</xsl:template>
<xsl:template match="source/@user" xpath-default-namespace="">
<prop name="dbUser">{.}</prop>
</xsl:template>
<xsl:template match="source/@driver" xpath-default-namespace="">
<prop name="DriverClass">{.}</prop>
</xsl:template>
<xsl:template match="source/@password" xpath-default-namespace="">
<encrypted-prop name="dbPassword" encryptionID="base64">{.}</encrypted-prop>
</xsl:template>
<xsl:template match="set" xpath-default-namespace="">
<data-set extensionID="this.is.a.fixed.value" name="{@name}">
<xsl:apply-templates select="@*, *"/>
</data-set>
</xsl:template>
</xsl:stylesheet>
Online sample at https://xsltfiddle.liberty-development.net/bFDb2Ck.
来源:https://stackoverflow.com/questions/51200894/xml-xslt-using-two-xml-files-additions-to-xml-file-consulting-another-xml