Creating Valid XML Against Schema with XSLT
I'm sorting a list of elements based on type, as defined in my schema. I know that XSLT can validate against a given schema, but what I want to do is check to make sure that my operation (a copy in this case) is valid before I do it.
Simplified code:
Incoming Data:
<?xml version="1.0"?>
<sch:foo xmlns:sch="http://www.whatever.com/schema">
<sch:attr1>val1</sch:attr1>
<sch:attr2>val2</sch:attr2>
<sch:attr3>val3</sch:attr3>
<sch:attr4>val4</sch:attr4>
</sch:foo>
Desired Outgoing Data:
<?xml version="1.0"?>
<sch:fooOut xmlns:sch="http://www.whatever.com/schema">
<sch:bar>
<sch:attr1>val1</sch:attr1>
<sch:attr2>val2</sch:attr2>
</sch:bar>
<sch:stuff>
<sch:attr3>val3</sch:attr3>
<sch:attr4>val4</sch:attr4>
</sch:stuff>
</sch:fooOut>
Somewhere in the Schema File:
<complexType name="fooOut">
<sequence>
<!-- ... -->
<element name="bar">
<complexType>
<sequence>
<element name="attr1" type="sch:myBarType" minOccurs="0" maxOccurs="unbounded"/>
<element name="attr2" type="sch:myBarType" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
</element>
<element name="stuff">
<complexType>
<sequence>
<element name="attr3" type="sch:myStuffType" minOccurs="0" maxOccurs="unbounded"/>
<element name="attr4" type="sch:myStuffType" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
(I'm just learning how to work with .xsd's, so to say it in words: only attr1
and attr2
can go in bar
, and only attr3
and attr4
can go in stuff
)
Basically, in the real situation there's too many tags to feasibly separate them manually. I'd like to know if there's a way to check the schema for whether the elements are right for whichever type they need to be sorted into. If they belong in one category, they should go to just that category.
All help is appreciated and thanks!
EDIT
@Alejandro's code works for the above basic pseudocode, but I'm having trouble implementing it in my files, which are more complex. For that reason, I'm adding a more complicated example:
Incoming Data
<?xml version="1.0"?>
<sch:foo xmlns:sch="http://www.whatever.com/schema">
<sch:nesting>
<sch:myGroup>
<sch:mustHaveData>asdf</sch:mustHaveData>
<sch:attr1>val1</sch:attr1>
<sch:attr2>val2</sch:attr2>
<sch:attr3>val3</sch:attr3>
<sch:attr4>val4</sch:attr4>
</sch:myGroup>
<sch:myGroup>
<sch:mustHaveData>asdf2</sch:mustHaveData>
<sch:attr1>val5</sch:attr1>
<sch:attr2>val6</sch:attr2>
<sch:attr3>val7</sch:attr3>
<sch:attr4>val8</sch:attr4>
</sch:myGroup>
</sch:nesting>
</sch:foo>
Desired Outgoing Data:
<?xml version="1.0"?>
<sch:fooOut xmlns:sch="http://www.whatever.com/schema">
<sch:anotherGroup>
<sch:name>foobar</sch:name>
<sch:bar>
<sch:attr1>val1</sch:attr1>
<sch:attr2>val2</sch:attr2>
</sch:bar>
<sch:stuff>
<sch:attr3>val3</sch:attr3>
<sch:attr4>val4</sch:attr4>
</sch:stuff>
</sch:anotherGroup>
<sch:anotherGroup>
<sch:name>foobar</sch:name>
<sch:bar>
<sch:attr1>val5</sch:attr1>
<sch:attr2>val6</sch:attr2>
</sch:bar>
<sch:stuff>
<sch:attr3>val7</sch:attr3>
<sch:attr4>val8</sch:attr4>
</sch:stuff>
</sch:anotherGroup>
</sch:fooOut>
Somewhere in the Schema Files: (and somewhat more accurate than last time)
<complexType name="anotherGroup">
<sequence>
<element name="name" type="xs:string" minOccurs="1" maxOccurs="1" />
<element name="bar" type="barListType" minOccurs="0" maxOccurs="1" />
<element name="stuff" type="stuffListType" minOccurs="0" maxOccurs="1" />
</sequence>
</complexType>
<!-- in another .xsd -->
<complexType name="barListType">
<group ref="barGroup" maxOccurs="unbounded" />
</complexType>
<complexType name="stuffListType">
<group ref="stuffGroup" maxOccurs="unbounded" />
</complexType>
<!-- in yet another .xsd -->
<group name="barGroup">
<choice>
<element name="attr1" type="blah1" minOccurs="0" maxOccurs="1" />
<element name="attr2" type="blah2" minOccurs="0" maxOccurs="1" />
<!-- etc -->
</choice>
</group>
<group name ="stuffGroup">
<choice>
<element name="attr3" type="blah3" minOccurs="0" maxOccurs="1" />
<element name="attr4" type="blah4" minOccurs="0" maxOccurs="1" />
<!-- etc -->
</choice>
</group>
Finally, my xsl file
<xsl:output method="xml" encoding="UTF-8" />
<xsl:param name="schema-name" select="'myXsd.xsd'" />
<xsl:template match="/">
<xsl:apply-templates select="document($schema-name)/xs:complexType[@*]" />
<xsl:apply-templates select="sch:nesting"/>
</xsl:template>
<xsl:template match="sch:nesting/xs:element[xs:complexType]">
<xsl:element name="{@name}" namespace="http://www.whatever.com/schema">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="sch:nesting/xs:element[not(xs:complexType)]">
<xsl:element name="{@name}" namespace="http://www.whatever.com/schema">
<xsl:value-of select="/*/sch:*[name()=current()/@name or
substring-after(name(),':')=current()/@name]"/>
</xsl:element>
</xsl:template>
<xsl:template match="sch:nesting">
<xsl:element name="anotherGroup">
<xsl:element name="name">
<!-- Whatever -->
</xsl:element>
<xsl:apply-templates /> <!-- I want to drop the data here, but this is definitely wrong -->
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Thanks again for the help!
EDIT 2
So there's one minor change that I forgot to put in about my data file. Everything else should be the same except for the layout, which is nested like so:
<?xml version="1.0"?>
<sch:foo xmlns:sch="http://www.whatever.com/schema">
<sch:nesting>
<sch:myGroup>
<inner1>
<sch:mustHaveData>asdf</sch:mustHaveData>
<sch:attr1>val1</sch:attr1>
</inner1>
<inner2>
<sch:attr2>val2</sch:attr2>
<sch:attr3>val3</sch:attr3>
<sch:attr4>val4</sch:attr4>
</inner2>
</sch:myGroup>
<sch:myGroup>
<inner1>
<sch:mustHaveData>asdf2</sch:mustHaveData>
<sch:attr1>val5</sch:attr1>
</inner1>
<inner2>
<sch:attr2>val6</sch:attr2>
<sch:attr3>val7</sch:att开发者_Python百科r3>
<sch:attr4>val8</sch:attr4>
</inner2>
</sch:myGroup>
</sch:nesting>
</sch:foo>
What I'm trying to illustrate here is that within the groups are subcategories, and I need to match everything within those subcategories.
Alejandro, to make this worth your while (since you have been such an amazing help), you should put your response in a new answer, and when I try it and get it to work, I will up-vote and accept that answer.
Thanks again! You're really a lifesaver!
EDIT 3
I figured out the desired result. I changed the lines that said
<xsl:copy-of select="*[local-name()=document($schema-name)/*/*
to
<xsl:copy-of select="*/*[local-name()=document($schema-name)/*/*
Which gave me the extra bit that I needed.
Schema ("schema.xs"):
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.whatever.com/schema">
<element name="fooOut">
<complexType name="fooOut">
<sequence>
<element name="bar">
<complexType>
<sequence>
<element name="attr1" type="sch:myBarType" minOccurs="0" maxOccurs="unbounded"/>
<element name="attr2" type="sch:myBarType" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
</element>
<element name="stuff">
<complexType>
<sequence>
<element name="attr3" type="sch:myStuffType" minOccurs="0" maxOccurs="unbounded"/>
<element name="attr4" type="sch:myStuffType" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>
Input:
<sch:foo xmlns:sch="http://www.whatever.com/schema">
<sch:attr1>val1</sch:attr1>
<sch:attr2>val2</sch:attr2>
<sch:attr3>val3</sch:attr3>
<sch:attr4>val4</sch:attr4>
</sch:foo>
Stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:sch="http://www.whatever.com/schema">
<xsl:param name="schema-name" select="'schema.xs'"/>
<xsl:variable name="input" select="/"/>
<xsl:template match="/">
<xsl:apply-templates select="document($schema-name)/node()"/>
</xsl:template>
<xsl:template match="xs:element[xs:complexType]">
<xsl:element name="{@name}" namespace="http://www.whatever.com/schema">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="xs:element[not(xs:complexType)]">
<xsl:element name="{@name}" namespace="http://www.whatever.com/schema">
<xsl:value-of select="$input/*/sch:*[name()=current()/@name or
substring-after(name(),':')=current()/@name]"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-16"?>
<fooOut xmlns="http://www.whatever.com/schema">
<bar>
<attr1>val1</attr1>
<attr2>val2</attr2>
</bar>
<stuff>
<attr3>val3</attr3>
<attr4>val4</attr4>
</stuff>
</fooOut>
Note: This is an XSLT 1.0 solution, but I think this can be done better with XSLT 2.0.- Also, if the schema is a more proper one (elements definitions and types definitions) this method could work with keys. Another method (input driven and not schema driven), despite being more complex, also could be done but I'm runnig out of time.
Edit: Now, suppose these schemas
schemaA.xsd
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.whatever.com/schema">
<include schemaLocation="schemaB.xsd"/>
<element name="fooOut" type="fooOut"/>
<complexType name="fooOut">
<element name="anotherGroup" type="anotherGroup" minOccurs="0" maxOccurs="unbounded"/>
</complexType>
<complexType name="anotherGroup">
<sequence>
<element name="name" type="xs:string" minOccurs="1" maxOccurs="1" />
<element name="bar" type="barListType" minOccurs="0" maxOccurs="1" />
<element name="stuff" type="stuffListType" minOccurs="0" maxOccurs="1" />
</sequence>
</complexType>
</schema>
schemaB.xsd
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.whatever.com/schema">
<include schemaLocation="schemaC.xsd"/>
<complexType name="barListType">
<group ref="barGroup" maxOccurs="unbounded" />
</complexType>
<complexType name="stuffListType">
<group ref="stuffGroup" maxOccurs="unbounded" />
</complexType>
</schema>
schemaC.xsd
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.whatever.com/schema">
<group name="barGroup">
<choice>
<element name="attr1" type="blah1" minOccurs="0" maxOccurs="1" />
<element name="attr2" type="blah2" minOccurs="0" maxOccurs="1" />
<!-- etc -->
</choice>
</group>
<group name ="stuffGroup">
<choice>
<element name="attr3" type="blah3" minOccurs="0" maxOccurs="1" />
<element name="attr4" type="blah4" minOccurs="0" maxOccurs="1" />
<!-- etc -->
</choice>
</group>
</schema>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="xml" encoding="UTF-8" />
<xsl:param name="schema-name" select="'schemaA.xsd'" />
<xsl:variable name="input" select="/*/*/*" />
<xsl:variable name="root" select="document($schema-name)/*/xs:element[not(..//xs:element/@ref = @name)]" />
<xsl:variable name="uri" select="$root/../@targetNamespace" />
<xsl:template match="/" name="root">
<xsl:param name="schema" select="$root/../*"/>
<xsl:choose>
<xsl:when test="$schema[self::xs:include]">
<xsl:call-template name="root">
<xsl:with-param name="schema" select="$schema[not(self::xs:include)]|document($schema[self::xs:include]/@schemaLocation)/*/*"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$root">
<xsl:with-param name="schema" select="$schema"/>
<xsl:with-param name="input" select="$input"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="xs:element">
<xsl:param name="schema"/>
<xsl:param name="input"/>
<xsl:variable name="complex" select="xs:complexType|
$schema[self::xs:complexType][@name=current()/@type]"/>
<xsl:element name="{@name|@ref}" namespace="{$uri}">
<xsl:choose>
<xsl:when test="$complex">
<xsl:apply-templates select="$complex">
<xsl:with-param name="schema" select="$schema"/>
<xsl:with-param name="input" select="$input"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$input[local-name()=current()/@name and
namespace-uri()=$uri]"/>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:template>
<xsl:template match="xs:group[@ref]">
<xsl:param name="schema"/>
<xsl:param name="input"/>
<xsl:apply-templates select="$schema[self::xs:group][@name=current()/@ref]">
<xsl:with-param name="schema" select="$schema"/>
<xsl:with-param name="input" select="$input"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="xs:element[@name='name']" priority="1">
<xsl:element name="{@name}" namespace="{$uri}">foobar</xsl:element>
</xsl:template>
<xsl:template match="xs:element[@maxOccurs='unbounded']">
<xsl:param name="schema"/>
<xsl:param name="input"/>
<xsl:variable name="current" select="."/>
<xsl:for-each select="$input">
<xsl:element name="{$current/@name}" namespace="{$uri}">
<xsl:apply-templates select="$schema[self::xs:complexType][@name=$current/@type]">
<xsl:with-param name="schema" select="$schema"/>
<xsl:with-param name="input" select="./*"/>
</xsl:apply-templates>
</xsl:element>
</xsl:for-each>
</xsl:template>
<xsl:template match="*">
<xsl:param name="schema"/>
<xsl:param name="input"/>
<xsl:apply-templates>
<xsl:with-param name="schema" select="$schema"/>
<xsl:with-param name="input" select="$input"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
Result:
<fooOut xmlns="http://www.whatever.com/schema">
<anotherGroup>
<name>foobar</name>
<bar>
<attr1>val1</attr1>
<attr2>val2</attr2>
</bar>
<stuff>
<attr3>val3</attr3>
<attr4>val4</attr4>
</stuff>
</anotherGroup>
<anotherGroup>
<name>foobar</name>
<bar>
<attr1>val5</attr1>
<attr2>val6</attr2>
</bar>
<stuff>
<attr3>val7</attr3>
<attr4>val8</attr4>
</stuff>
</anotherGroup>
</fooOut>
Note: This works but your second questions (or problem) shows that there is no general case stylesheet. Why? Because with XSLT you must bind an input (with well known schema) to an output (with well known schema too). So this specific stylesheet could do the job:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sch="http://www.whatever.com/schema">
<xsl:output method="xml" encoding="UTF-8" />
<xsl:param name="schema-name" select="'schemaC.xsd'" />
<xsl:template match="sch:foo">
<sch:fooOut>
<xsl:apply-templates/>
</sch:fooOut>
</xsl:template>
<xsl:template match="sch:myGroup">
<sch:anotherGroup>
<sch:name>foobar</sch:name>
<sch:bar>
<xsl:copy-of select="*[local-name()=document($schema-name)/*/*[@name='barGroup']//@name]" />
</sch:bar>
<sch:stuff>
<xsl:copy-of select="*[local-name()=document($schema-name)/*/*[@name='stuffGroup']//@name]" />
</sch:stuff>
</sch:anotherGroup>
</xsl:template>
</xsl:stylesheet>
Result:
<sch:fooOut xmlns:sch="http://www.whatever.com/schema">
<sch:anotherGroup>
<sch:name>foobar</sch:name>
<sch:bar>
<sch:attr1>val1</sch:attr1>
<sch:attr2>val2</sch:attr2>
</sch:bar>
<sch:stuff>
<sch:attr3>val3</sch:attr3>
<sch:attr4>val4</sch:attr4>
</sch:stuff>
</sch:anotherGroup>
<sch:anotherGroup>
<sch:name>foobar</sch:name>
<sch:bar>
<sch:attr1>val5</sch:attr1>
<sch:attr2>val6</sch:attr2>
</sch:bar>
<sch:stuff>
<sch:attr3>val7</sch:attr3>
<sch:attr4>val8</sch:attr4>
</sch:stuff>
</sch:anotherGroup>
</sch:fooOut>
Not sure what you're looking for here. XSLT will copy from the source doc to the destination doc however you tell it to. For example:
<sch:fooOut>
<sch:bar>
<xsl:copy>
<sch1:attr1>
<sch1:attr2>
</xsl:copy>
</sch:bar>
</sch:fooOut>
What kind of validation are you looking for? You may be better off using something other than XSLT if you're looking for a more dynamic solution.
精彩评论