I am transforming an XML file that needs to generate some elements based on the valid enumeration options defined in the XSD.
Suppose I have an XSD that declares a type and an element something like this:
<xs:simpleType name="optionType" nillable="true">
<xs:restriction base="xs:string">
<xs:maxLength value="50"/>
<xs:enumeration value="USERCHOICE">
</xs:enumeration>
<xs:enumeration value="DEFAULT">
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
...
<xs:element name="chosenOption" type='optionType'/>
...
<xs:element name="availableOption" type='optionType'/>
The input will only contain the chosen option, so you can imagine it looks like this:
<options>
<chosenOption>USERCHOICE</chosenOption>
</options>
I need to have an output that looks like this:
<options>
<chosenOption>USERCHOICE</chosenOption> <!-- This comes from incoming XML -->
<!-- This must be a list of ALL possible values for this element, as defined in XSD -->
<availableOptions>
<availableOption>USERCHOICE</availableOption>
<availableOption>DEFAULT</availableOption>
</availableOptions>
</options>
Is there a way to have the XSL extract the enumeration values USERCHOICE and DEFAULT from the XSD and produce them in the output?
This will run on WebSphere 6 and will be used by an XSLT 1.0 engine. :(
(The schema file does not change often but it will change now and then and I'd rather only have to update the schema file instead of update the schema file and XSLT)
Here's a prototype that assumes that your input XML and XSD are as simple as the samples above. To be tweaked according to ways in which they may vary. If you need help with that tweaking, let me know.
<?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"
exclude-result-prefixes="xs"
version="1.0">
<xsl:variable name="xsd" select="document('mySchema.xsd')"/>
<xsl:template match="/options">
<xsl:copy>
<xsl:for-each select="*">
<xsl:variable name="eltName" select="local-name()"/>
<xsl:copy-of select="." />
<availableOptions>
<xsl:variable name="optionType"
select="$xsd//xs:element[#name = $eltName]/#type"/>
<xsl:apply-templates
select="$xsd//xs:simpleType[#name = $optionType]/
xs:restriction/xs:enumeration"/>
</availableOptions>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="xs:enumeration">
<availableOption><xsl:value-of select="#value" /></availableOption>
</xsl:template>
</xsl:stylesheet>
Related
I have the following input structure:
<complexType name="InvoiceType">
<xs:element name="AdressIn" type="AdressType"/>
<xs:element name="AdressOut" type="AdressType" />
<xs:element name="Partner" type="PartnerType" />
<xs:element name="Date" type="DateType"/>
</complexType>
All the referenced types (AdressType,PartnerType,DateType) are also contained in that document as complex types (besides many other).
What i am trying to do is to copy the types that are used within a "InvoiceType"
to a new document.
My Problem is, the AdressType is used more then once within InvoiceType, and thus it appears more then once in my new document. How can i prevent that?
I need something like "if that is already pocessed ->skip" but that is not declarativ... maybe xslt is not the right way to achive this.
Thanx for any help!
Edit:
My XSLT i am using so far looks like that (modified to please the simple example)
<xsl:template match="xs:complexType">
<xsl:for-each select="./xs:element">
<xsl:variable name="elementType"><xsl:value-of select="#type"></xsl:value-of></xsl:variable>
<xsl:copy-of copy-namespaces="no" select="/xs:schema/xs:complexType[#name=$elementType]"/>
</xsl:for-each>
I am applying that template for the InvoceType. Basically i am going threw its contents, see what types are referenced, look them up in the document via their name and copy them.
Instead of
<xsl:for-each select="./xs:element">
<xsl:variable name="elementType"><xsl:value-of select="#type"></xsl:value-of></xsl:variable>
<xsl:copy-of copy-namespaces="no" select="/xs:schema/xs:complexType[#name=$elementType]"/>
</xsl:for-each>
use
<xsl:copy-of select="/xs:schema/xs:complexType[#name=current()/xs:element/#type]"/>
or define a key
<xsl:key name="complex-types" match="xs:schema/xs:complexType" use="#name"/>
and then you can use
<xsl:copy-of select="key('complex-types', xs:element/#type)"/>
I am working on a stylesheet that processes some XSDs.
The main XSD file includes 2 others. Of those 2 one also includes the other.
All XSDs have the same attributes and namespaces in the root element. The files are only separate for maintenance purposes.
The stylesheet:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:exa="http://www.example.com/"
exclude-result-prefixes="xsl xs exa">
<xsl:output method="xml" indent="yes" encoding="iso-8859-1" />
<!-- global variable to merge schema with it's includes
to be used for further processing the schema -->
<xsl:variable name="with_includes">
<xsl:apply-templates select="/xs:schema" mode="include"/>
</xsl:variable>
<!-- copy the main schema root element including attributes
then process all nodes in it -->
<xsl:template match="xs:schema" mode="include">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="node()" mode="include"/>
</xsl:copy>
</xsl:template>
<!-- all schemas have the same namespaces and targetnamespace defined
so do not copy namespaces -->
<xsl:template match="node()" mode="include">
<xsl:copy-of select="." copy-namespaces="no"/>
</xsl:template>
<!-- when matching an include, process all the nodes in the schema -->
<xsl:template match="xs:include" mode="include">
<xsl:apply-templates select="doc(#schemaLocation)/xs:schema/node()" mode="include"/>
</xsl:template>
<!-- only here to show the result -->
<xsl:template match="/xs:schema">
<xsl:copy-of select="$with_includes"/>
</xsl:template>
</xsl:stylesheet>
Some very basic example schemas to demonstrate the problem:
Schema A.xsd:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xs:schema
attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.com/example"
xmlns:exa="http://www.example.com/example">
<xs:include schemaLocation="B.xsd"/>
<xs:include schemaLocation="C.xsd"/>
<xs:element name="a" type="exa:t_a"/>
<xs:complexType name="t_a">
<xs:sequence>
<xs:element name="b" type="exa:t_b"/>
<xs:element name="c" type="exa:t_c"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Schema B.xsd:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xs:schema
attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.com/example"
xmlns:exa="http://www.example.com/example">
<xs:include schemaLocation="C.xsd"/>
<xs:complexType name="t_b">
<xs:sequence>
<xs:element name="c1" type="exa:t_c"/>
<xs:element name="c2" type="exa:t_c"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Schema C.xsd
<?xml version="1.0" encoding="ISO-8859-1"?>
<xs:schema
attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.com/example"
xmlns:exa="http://www.example.com/example">
<xs:simpleType name="t_c">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="20"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
If I use the stylesheet above, I get simpleType t_c twice in the result. I am looking for a way to prevent that.
Btw, I use Saxon.
Deduplicating the includes is relatively straightforward, but it becomes more complex if you need to handle cycles as well - cycles of xs:includes are permitted in XSD, though the 1.0 spec isn't entirely clear on the point. If you're not concerned about cycles, just build a list of all the includes by transitive expansion using a recursive function, preferably calling resolve-uri() to resolve each #schemaLocation against its base URI, then remove duplicates from the list using distinct-values(). If you need to eliminate cycles, you'll need to pass a parameter to your recursive function indicating the route by which the document was reached, and ignore a document if it's already on the list. If you've got a copy of my book, there's an example of cycle detection in the section on xsl:call-template. But you may find the book too expensive too ;-(
I am using Xalan-j 2.7.1. I have written a function using xalans implementation of exslt func:function extensions. I am trying to make my xslt cleaner by using repeatable portion of output xml into functions. The following function is a representation of what I am trying to do.
The expected output is a xml tree fragment but I am not seeing any output. I dont know why this doesn't work though it is mentioned in exslt.org documentation
xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:func="http://exslt.org/functions"
xmlns:common="http://exslt.org/common"
xmlns:my="http://my.org/my"
exclude-result-prefixes="func common my">
<xsl:output type="xml" indent="yes" />
<func:function name="my:personinfo">
<xsl:param name="name" />
<xsl:param name="address" />
<func:result>
<xsl:element name="details">
<xsl:element name="name" select="$name" />
<xsl:element name="address" select="$address" />
</xsl:element>
</func:result>
</func:function>
<xsl:element name="results">
<xsl:value-of select="my:personinfo('john', '02-234 pudding lane, london')" />
</xsl:element>
</xsl:stylesheet>
Well if you have nodes in a result tree fragment and want to output them to the result tree you need to use <xsl:copy-of select="my:personinfo('john', '02-234 pudding lane, london')"/>, not value-of.
Note however that xsl:element does not take a select attribute, if you want to create elements either simply use literal result elements like
<details>
<name><xsl:value-of select="$name"/></name>
<address><xsl:value-of select="$address"/></address>
</details>
or if you want to use xsl:element make sure you populate elements with the proper syntax e.g.
<xsl:element name="name"><xsl:value-of select="$name"/></xsl:element>
I'm transforming an XML Schema using XSLT 2.0. The first schema (s1.xsd) imports a second schema (s2.xsd) as follows:
Content of s1.xsd
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:xsd="http://www.w3.org/2001/XMLSchema.xsd"
xmlns:ns1="URI1" targetNamespace="URI2"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<import namespace="URI1" schemaLocation="s2.xsd"/>
<element name="element1"/>
<element name="element2"/>
</schema>
and content of s2.xsd
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:ns1="URI1" targetNamespace="URI1">
<attribute name="attr1"/>
<schema>
My XSLT declares the XS namespace as follows:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
I would like to merge the nodes of s2.xsd into the <schema>-element of s1.xsd. So far, I've tried
<xsl:template name="merge_imported_schemas">
<xsl:for-each select="/schema/import[#namespace = //namespace::*]">
<!-- file exists? -->
<xsl:choose>
<xsl:when test="boolean(document(#schemaLocation))">
<!-- schema found -->
<xsl:copy-of select="document(#schemaLocation)/*/node()"/>
</xsl:when>
<xsl:otherwise>
<!-- schema not found -->
<xsl:message terminate="yes">
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
but I don't get the desired result. Could anyone please tell me what I'm doing wrong? I suspect there is a namespace-collision here, but honestly I find using namespaces a little confusing. Thanks!
You need to qualify the elements in your XPath. At the moment select="/schema/import[#namespace = //namespace::*]"> doesn't match anything at all, because there is no element /schema. The XPath is trying to match elements with no namespace.
Change it to select="/xs:schema/xs:import[#namespace = //namespace::*]"> and it should work.
Remember, namespace prefixes are an alias for the namespace URI, and if you have a default namespace (as in your xsd files), elements with no prefix are still namespace-qualified.
As an aside, instead of <xsl:for-each select="/schema/import[#namespace = //namespace::*]">, you might have more success using <xsl:apply-templates select="/xs:schema/node()", and defining different templates for the different kinds of node that you wish to copy into the output tree.
Using this file as source, I have a situation where I need to retrieve an element from either the local source file or a related one noted in the imports. The type value uses a colon to separate the two values - substring-before(#type, ':') tells me which file to reference; substring-after(#type, ':') is the name of the element in the file I need to copy & iterate over its contents in the same fashion.
Example: I want the xs:complexType where the name is "PersonType", so I use the copy-of to grab it and its children. The next step is to look at those children - for those that are xs:element, I want to retrieve the element referenced in the type value ("AcRec:HighSchoolType"). The "AcRec" tells me which xsd I need to use, so I know I'll find something in that xsd where the name value is "HighSchoolType". Looking at the AcRec xsd, I know that "HighSchoolType" is an xs:complexType (which I already have a template defined to handle) so I should see the output.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="/">
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:core="urn:org:pesc:core:CoreMain:v1.2.0" xmlns:AcRec="urn:org:pesc:sector:AcademicRecord:v1.1.0">
<xsl:apply-templates select="//xs:complexType[#name='PersonType']" />
</xs:schema>
</xsl:template>
<xsl:template match="xs:complexType">
<xsl:copy>
<xsl:copy-of select="node()[not(xs:annotation | xs:restriction)]|#*"/>
</xsl:copy>
<xsl:apply-templates select=".//xs:element" />
</xsl:template>
<xsl:template match="xs:simpleType">
<xsl:copy>
<xsl:copy-of select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xs:element">
<xsl:choose>
<xsl:when test="substring-before(#type, ':') = 'AcRec'">
<xsl:text>AcRec</xsl:text>
<xsl:apply-templates select="document('[local file path]AcademicRecord_v1.3.0.xsd')//*[#name=substring-after(#type, ':')]" />
</xsl:when>
</xsl:choose>
</xsl:template>
Desired output would look like:
<xs:complexType name="PersonType">
<xs:sequence>
<xs:element name="HighSchool" type="AcRec:HighSchoolType" minOccurs="0">
</xs:sequence>
</xs:complexType>
<xs:complexType name="HighSchoolType">
<xs:sequence>
<xs:element name="OrganizationName" type="core:OrganizationNameType"/>
<xs:group ref="core:OrganizationIDGroup" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
What am I missing about looking within the document when I successfully enter the xsl:when? The xsl:text tells me I'm in, but the subsequent line returns no output.
Additionally, how do I exclude xs:annotation and xs:restriction elements from appearing when copying the xs:complextType & xs:simpleType elements? I haven't been able to get the examples mentioned on the dpawson site to work.
I have to say, and take no offense, that your question is really difficult to understand; could you break it down a little more?
Meanwhile, as far as excluding xs:annotation and xs:restriction elements, just change your copy-of statement to leave them out:
<xsl:copy-of select="node()[not(self::xs:annotation or self::xs:restriction)]|#*"/>
... and you have an xsl:choose element with only one xsl:when and no xsl:otherwise... you could simplify this with an xsl:if element:
<xsl:template match="xs:element">
<xsl:if test="substring-before(#type, ':') = 'AcRec'">
<xsl:text>AcRec</xsl:text>
<xsl:apply-templates select="document('[local file path]AcademicRecord_v1.3.0.xsd')//*[#name=substring-after(#type, ':')]" />
</xsl:if>
</xsl:template>
Upon further reflection, it looks like your select statement might be malformed: currently it is trying to match any element that has a name attribute that matches the type attribute after the colon. So a match would have to be
<element name="text" type="some:text"/>
If you don't have an element like this, then your match is empty, so no templates are run.
The issue was:
<xsl:apply-templates select="document('[local file path]AcademicRecord_v1.3.0.xsd')//*[#name=substring-after(#type, ':')]" />
...should be:
<xsl:variable name="name" select="substring-after(#type, ':')" />
<xsl:apply-templates select="document('[local file path]AcademicRecord_v1.3.0.xsd')//*[#name=$name]" />
ScottSEA - you were correct when you said it was comparing based on:
<element name="text" type="some:text"/>
What happens is that there is a context switch involved in the processing of that XPath expression. When you say //*[#name=substring-after(#type, ':')], you say "apply this template to those elements in the XSD document referenced, that have both a #name and a #type attribute, and whose #name is equal to substring-after ':' of THEIR #type. Whereas, if you use a variable, you of course get the substring-after from the current document.