I am creating a XSD based on the below Xml using a XSL file. Actually I want to make Element2 block mandatory (minOccurs=1) only when Element1 block is available in each node (Sample, Sample1, etc..). If Element1 block is not available then Element2 block should become optional (minOccurs=0). I tried ancestor::[], match etc., but nothing is working. Please help.
Xml
<Root>
<Sample>
<Element1>
<SubElement1>0</SubElement1>
</Element1>
<Element2>
<X>1</X>
<Y>2</Y>
</Element2>
</Sample>
<Sample1>
<Element2>
<X>1</X>
<Y>2</Y>
</Element2>
</Sample1>
</Root>
XSL
<xsl:choose>
<xsl:when test="(local-name() = 'Element2' and //*[matches(local-name(), 'Element1')])">
<xs:element name="{local-name()}" minOccurs="1">
<xsl:call-template name="renderChildElements" />
</xs:element>
</xsl:when>
<xsl:otherwise>
<xs:element name="{local-name()}" minOccurs="0">
<xsl:call-template name="renderChildElements" />
</xs:element>
</xsl:otherwise>
</xsl:choose>
<xsl:template name="renderChildElements">
<xs:complexType>
<xs:all minOccurs="0">
<xsl:apply-templates select="*"/>
</xs:all>
<xsl:apply-templates select="#*"/>
</xs:complexType>
</xsl:template>
It sounds as if you only want to make Element2 have minOccurs="1" when it has an Element1 sibling?
The XPath //*[matches(local-name(), 'Element1')]) is using the descendant axis, so it is jumping up to the top of the XML tree and looking through the entire document to test whether there is an Element1 element.
Instead, you want to constrain it to just the elements under the Element2 parent: ..//*[local-name() eq 'Element1'] or if you just care about the children of the parent of Element2 you could use: ../*[local-name() eq 'Element1']
I think you can further simplify and consolidate, and move the conditional logic inside of the minOccurs attribute, using an attribute value template to compute the value of either 1 or 0:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output indent="yes"/>
<xsl:template match="*">
<xs:element name="{local-name()}"
minOccurs="{if (local-name() = 'Element2' and ..//*[local-name() eq 'Element1']) then 1 else 0}">
<xsl:call-template name="renderChildElements" />
</xs:element>
</xsl:template>
<xsl:template name="renderChildElements">
<xs:complexType>
<xs:all minOccurs="0">
<xsl:apply-templates select="*"/>
</xs:all>
<xsl:apply-templates select="#*"/>
</xs:complexType>
</xsl:template>
</xsl:stylesheet>
Related
I am looking for someone to assist in solving a seemingly simple problem.
I want to map a node of /fields[x]/message_id to a static node /MessageID0x for 5 entries in a list.
The source node is optional and may not exist.
The schema is below
I am just not seeing the obvious, I hope.
The source is defined as:
<xs:element name="fields">
<xs:complexType>
<xs:sequence>
<xs:element name="tenant_id" type="xs:normalizedString" minOccurs="0"/>
<xs:element name="message_id" type="xs:normalizedString" minOccurs="0"/>
Target is defined as:
<xs:element name="MessageID01" type="xs:normalizedString" minOccurs="0"/>
<xs:element name="MessageID02" type="xs:normalizedString" minOccurs="0"/>
<xs:element name="MessageID03" type="xs:normalizedString" minOccurs="0"/>
<xs:element name="MessageID04" type="xs:normalizedString" minOccurs="0"/>
<xs:element name="MessageID05" type="xs:normalizedString" minOccurs="0"/>
=== FROM ===========
<root>
<ID>2019Nov12_17</ID>
<PingResult>OK</PingResult>
<StartDateTime>2019-11-12T16:16:01</StartDateTime>
<EndDateTime>2019-11-12T17:16:01.771Z</EndDateTime>
<start>0</start>
<numFound>1</numFound>
<fields>
<tenant_id>KOCHIND_AX2</tenant_id>
<message_id>lid://infor.landmark.lmrkmt/15d8f834-7680-541e-0000-001d5dae3e7b</message_id>
</fields>
<fields>
<tenant_id>KOCHIND_AX2</tenant_id>
<message_id>lid://infor.landmark.lmrkmt/0535a86a-7680-1868-0000-07625db833c1</message_id>
</fields>
<fields>
<tenant_id>KOCHIND_AX2</tenant_id>
<message_id>lid://infor.landmark.lmrkmt/0535a86a-7680-1864-0000-03445db849c8</message_id>
</fields>
<fields>
<tenant_id>KOCHIND_AX2</tenant_id>
<message_id>lid://infor.landmark.lmrkmt/0535a86a-7680-1867-0000-01151db125c8</message_id>
</fields>
</root>
TO ===================
<root>
<ID>2019Nov12_17</ID>
<PingResult>OK</PingResult>
<StartDateTime>2019-11-12T16:16:01</StartDateTime>
<EndDateTime>2019-11-12T17:16:01.771Z</EndDateTime>
<start>0</start>
<numFound>1</numFound>
<MessageID01>lid://infor.landmark.lmrkmt/15d8f834-7680-541e-0000-001d5dae3e7b</MessageID01>
<MessageID02>lid://infor.landmark.lmrkmt/0535a86a-7680-1868-0000-07625db833c1</MessageID02>
<MessageID03>lid://infor.landmark.lmrkmt/0535a86a-7680-1868-0000-07625db833c8</MessageID03>
<MessageID04>lid://infor.landmark.lmrkmt/15d8f834-1864—3322-0000-03445db125c8</MessageID04>
<MessageID05>lid://infor.landmark.lmrkmt/15d8f834-7680-1867-0000-01151db125g4</MessageID05>
</root>
I believe the requested result can be achieved rather simply by doing:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="fields">
<xsl:variable name="n">
<xsl:number format="01"/>
</xsl:variable>
<xsl:element name="MessageID{$n}">
<xsl:value-of select="message_id"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The result of transforming the example input will be:
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<root>
<ID>2019Nov12_17</ID>
<PingResult>OK</PingResult>
<StartDateTime>2019-11-12T16:16:01</StartDateTime>
<EndDateTime>2019-11-12T17:16:01.771Z</EndDateTime>
<start>0</start>
<numFound>1</numFound>
<MessageID01>lid://infor.landmark.lmrkmt/15d8f834-7680-541e-0000-001d5dae3e7b</MessageID01>
<MessageID02>lid://infor.landmark.lmrkmt/0535a86a-7680-1868-0000-07625db833c1</MessageID02>
<MessageID03>lid://infor.landmark.lmrkmt/0535a86a-7680-1864-0000-03445db849c8</MessageID03>
<MessageID04>lid://infor.landmark.lmrkmt/0535a86a-7680-1867-0000-01151db125c8</MessageID04>
</root>
which is different from the result you show - nevertheless, I suspect it is the correct one.
Do note that numbering sibling nodes by name is bad practice. It makes subsequent transformations much more difficult. If you need a number (although I don't see why you should), use an attribute.
How can you ensure the order of the output element in an XSLT?
I have an XSLT, used in a BizTalk map, which extracts the three dates Ok, but because of the order of the source data, the resulting XML is ShipmentDate, ScheduledDeliveryDate and then DocumentIssueDate.
<Dates>
<xsl:for-each select="s0:E2EDT13001GRP">
<xsl:variable name="qualifier" select="string(s0:E2EDT13001/s0:QUALF/text())" />
<xsl:variable name="isDocumentIssueDate" select="string(userCSharp:LogicalEq($qualifier , "015"))" />
<xsl:variable name="isSheduledDeliveryDate" select="string(userCSharp:LogicalEq($qualifier , "007"))" />
<xsl:variable name="isShipmentDate" select="string(userCSharp:LogicalEq($qualifier , "003"))" />
<xsl:variable name="date" select="s0:E2EDT13001/s0:NTANF/text()" />
<xsl:if test="$isDocumentIssueDate='true'">
<DocumentIssueDate>
<xsl:value-of select="$date" />
</DocumentIssueDate>
</xsl:if>
<xsl:if test="$isScheduledDeliveryDate='true'">
<ScheduledDeliveryDate>
<xsl:value-of select="$date" />
</ScheduledDeliveryDate>
</xsl:if>
<xsl:if test="$isShipmentDate='true'">
<ShipmentDate>
<xsl:value-of select="$date" />
</ShipmentDate>
</xsl:if>
</xsl:for-each>
</Dates>
When I test the map in Visual Studio, I get an error that the XML is invalid versus the XSD ...
<xs:complexType>
<xs:sequence>
<xs:element name="DocumentIssueDate" type="xs:string" />
<xs:element name="SheduledDeliveryDate" type="xs:string" />
<xs:element name="ShipmentDate" type="xs:string" />
</xs:sequence>
</xs:complexType>
So how can I output the dates in the "right order"?
Rather than using for-each, just pull out the bits you need one by one:
<Dates>
<DocumentIssueDate>
<xsl:value-of select="s0:E2EDT13001GRP[
userCSharp:LogicalEq(s0:E2EDT13001/s0:QUALF, '015')]
/s0:E2EDT13001/s0:NTANF" />
</DocumentIssueDate>
<ScheduledDeliveryDate>
<xsl:value-of select="s0:E2EDT13001GRP[
userCSharp:LogicalEq(s0:E2EDT13001/s0:QUALF, '007')]
/s0:E2EDT13001/s0:NTANF" />
</ScheduledDeliveryDate>
<!-- etc. -->
</Dates>
You have two options:
Does the destination type have to be a sequence? You can change it
to an xs:all.
Change where you're looping. You would have to enclose
the for-each on E2EDT13001GRP within three separate element
definitions, on each for DocumentIssueDate, SheduledDeliveryDate and
ShipmentDate. That way, the output would always appear in that order.
A global variable indicates, for each of various kinds of elements, what attributes need to be processed.
<xsl:variable name="attributes.rtf">
<element name="measure">
<att>type</att>
<att>quantity</att>
<att>unit</att>
</element>
<element name="milestone">
<att>n</att>
</element>
<element name="lb">
<att>ed</att>
<att>type</att>
<att>subtype</att>
<att>n</att>
</element>
</xsl:variable>
<xsl:variable name="attributes" select="exslt:node-set($attributes.rtf)" /> <!-- This is XSLT 1.0 -->
When the context is an element, I need a for-each that will loop through attributes for that kind of element. I don't see how to do this in one XPath expression. Right now I have these two:
<xsl:variable name="temp" select="." /> <!-- For, say, a <measure> element, -->
<xsl:for-each select="$attributes/element[#name=name($temp)]/att"> <!-- loop through the names "type", "quantity", and "unit" -->
<xsl:for-each select="$temp/#*[name()=current()]"> <!-- If an attribute of that name exists in the current <measure> element -->
<!-- (now stored as $temp), then process that attribute. -->
</xsl:for-each>
</xsl:for-each>
Is there a way to do this in one expression, without creating $temp.
But also, I need an outer test condition that indicates whether the context element has any of the listed attributes at all. For that test, I'd like one expression.
(Processing of the attributes does need to be ordered, so maybe that requires the two loops. But order would not be relevant in the test condition. So maybe just that could be done with one expression.)
I. The "simple" problem:
When the context is an element, I need a for-each that will loop through attributes for that kind of element.
No xsl-for-each is needed.
I don't see how to do this in one XPath expression.
Just use:
<xsl:apply-templates select=
"#*[name()=$attributes/element[#name=name(current())]/att]"/>
Explanation:
Proper use of the current() function.
II. The "complex" problem:
Processing of the attributes does need to be ordered, so maybe that requires the two loops
Here is a complete example showing that no other nested <xsl:apply-templates> is necessary to produce the attributes in the order specified in $attributes:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="attributes.rtf">
<element name="measure">
<att>type</att>
<att>quantity</att>
<att>unit</att>
</element>
<element name="milestone">
<att>n</att>
</element>
<element name="lb">
<att>ed</att>
<att>type</att>
<att>subtype</att>
<att>n</att>
</element>
</xsl:variable>
<xsl:variable name="attributes" select="exslt:node-set($attributes.rtf)" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="t/*">
<xsl:copy>
<xsl:apply-templates select="$attributes/element[#name=name(current())]/att">
<xsl:with-param name="pCurrent" select="current()"/>
</xsl:apply-templates>
<xsl:apply-templates select=
"#*[not(name() = $attributes/element[#name=name(current())]/att)]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="att">
<xsl:param name="pCurrent" select="/.."/>
<xsl:if test="$pCurrent[#*[name()=current()]]">
<xsl:attribute name="{.}">
<xsl:value-of select="concat(.,'-',.)"/>
</xsl:attribute>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document (none was provided!!!):
<t>
<lb type="normal" ed="uni" n="3"
subtype="initial" other="yes"/>
<milestone n="2" other="yes"/>
<measure quantity="3" unit="cm"
type="length" other="yes"/>
</t>
the wanted, correct result is produced:
<t>
<lb ed="ed-ed" type="type-type" subtype="subtype-subtype" n="n-n" other="yes"/>
<milestone n="n-n" other="yes"/>
<measure type="type-type" quantity="quantity-quantity" unit="unit-unit" other="yes"/>
</t>
I'm trying to generate very simple documentation from the annotations in a Relax NG XML Schema. For example, given the following Relax NG:
<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0" xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<start>
<element name="topNode">
<ref name="topNode-ref"/>
</element>
</start>
<define name="topNode-ref">
<a:documentation>This is the top of the doc.</a:documentation>
<oneOrMore>
<element name="level1">
<ref name="level1-ref"/>
</element>
</oneOrMore>
</define>
<define name="level1-ref">
<a:documentation>Here's some notes about level1.</a:documentation>
<attribute name="att1">
<a:documentation>Details about att1.</a:documentation>
</attribute>
<element name="subLevel2">
<ref name="subLevel2-ref"/>
</element>
</define>
<define name="subLevel2-ref">
<a:documentation>Notes on subLevel2.</a:documentation>
<attribute name="subAtt"/>
<zeroOrMore>
<element name="subLevel3">
<ref name="subLevel3-ref"/>
</element>
</zeroOrMore>
</define>
<define name="subLevel3-ref">
<a:documentation>And here is subLevel3.</a:documentation>
<attribute name="subSubAtt"/>
</define>
</grammar>
Which would be used to valid an XML file like:
<?xml version="1.0" encoding="UTF-8"?>
<topNode>
<level1 att1="some test">
<subLevel2 subAtt="more text"></subLevel2>
</level1>
<level1 att1="quick">
<subLevel2 subAtt="brown">
<subLevel3 subSubAtt="fox"></subLevel3>
</subLevel2>
</level1>
</topNode>
I'd like to be able to produce documentation that lists the basic XPath to each element/attribute and then display any corresponding documentation annotations. For example:
/topNode
This is the top of the doc.
/topNode/level1
Here's some notes about level1
/topNode/level1/#att1
Details about att1.
etc...
Eventually, I'll add in more documentation about "zeroOrMore", possible data types, etc... but I need to get this first step solved first.
I've found the Techquila RELAX-NG Documentation Tools. I've played around with the rng to docbook stylesheet, but it don't do what I'm looking for. It just lists elements individually with no details about the XPath as far as I can tell. I don't see how I can use it as a starting point to get the output I'm after.
Is it possible (and if so, how?) to produce this type of documentation output with XSLT given the RelaxNG example provided?
While XSLT would be ideal, it's not a requirement. I'm open for anything that gets the job done.
This will work for a very simple grammar like your example.
<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:r="http://relaxng.org/ns/structure/1.0"
xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0"
>
<xsl:output method="text" />
<xsl:template match="/">
<xsl:apply-templates select="//r:define[a:documentation] | //r:attribute[a:documentation]" />
</xsl:template>
<xsl:template match="r:define">
<xsl:variable name="doc" select="a:documentation" />
<xsl:call-template name="print-path">
<xsl:with-param name="elm" select="//r:element[r:ref/#name=current()/#name]" />
</xsl:call-template>
<xsl:value-of select="$doc" /><xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="r:attribute">
<xsl:variable name="doc" select="a:documentation" />
<xsl:call-template name="print-path">
<xsl:with-param name="elm" select="//r:element[r:ref/#name=current()/ancestor::r:define/#name]" />
<xsl:with-param name="path" select="concat('/#',#name)" />
</xsl:call-template>
<xsl:value-of select="$doc" /><xsl:text>
</xsl:text>
</xsl:template>
<xsl:template name="print-path">
<xsl:param name="elm" />
<xsl:param name="path" />
<xsl:variable name="parent" select="//r:ref[#name=$elm/ancestor::r:define/#name]/ancestor::r:element" />
<xsl:message><xsl:value-of select="$elm/#name" /></xsl:message>
<xsl:choose>
<xsl:when test="$parent">
<xsl:call-template name="print-path">
<xsl:with-param name="elm" select="$parent" />
<xsl:with-param name="path" select="concat('/',$elm/#name,$path)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('/',$elm/#name,$path)" /><xsl:text>
</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
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.