XSLT - Remove output parent element if no child element was mapped - xslt-1.0

Imagine the following schema as input
<xs:schema>
<xs:element name="In">
<xs:complexType>
<xs:sequence>
<xs:element name="InA">
<xs:complexType>
<xs:sequence>
<xs:element name="InA1" type="xs:string" />
<xs:element name="InA2" type="xs:string" />
<xs:element name="InA3" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="InB">
<xs:complexType>
<xs:sequence>
<xs:element name="InB1" type="xs:string" />
<xs:element name="InB2" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
And example would be
<In>
<InA>
<InA1>InA1_0</InA1>
<InA2>InA2_0</InA2>
<InA3>InA3_0</InA3>
</InA>
<InB>
<InB1>InB1_0</InB1>
<InB2>InB2_0</InB2>
</InB>
</In>
Now, i need to map this to another schema, as some of the rules are:
Out/Out1 mapped from In/InA/InA1 if value != ''
Out/Out2 mapped from In/InB/InB2 if value != ''
Out/Out3 mapped from In/InA/InA3 if value != ''
Out should only be present if at least one of the childs was mapped
The only way I figured to do this would be to do an if before Out element, but this is very tedious and problematic the more childs Out has.
<xsl:if test="/In/InA/InA1 != '' and /In/InA/InB2 != '' and /In/InA/InA3 != ''">
<Out>
<xsl:if test="/In/InA/InA1 != ''">
<Out1>
<xsl:value-of select="/In/InA/InA1/text()">
</Out1>
</xsl:if>
<xsl:if test="/In/InA/InB2 != ''">
<Out2>
<xsl:value-of select="/In/InB/InB2/text()">
</Out2>
</xsl:if>
<xsl:if test="/In/InA/InA3 != ''">
<Out3>
<xsl:value-of select="/In/InA/InA3/text()">
</Out3>
</xsl:if>
</Out>
</xsl:if>
Anyone has any other way of doing this in a single map?

here is what I came out with. The idea is to do the inner map in a variable and then check if the variable is empty. If it is not, then i can map.
<xsl:variable name="out-Node">
<xsl:if test="/In/InA/InA1 != ''">
<Out1>
<xsl:value-of select="/In/InA/InA1/text()">
</Out1>
</xsl:if>
<xsl:if test="/In/InA/InB2 != ''">
<Out2>
<xsl:value-of select="/In/InB/InB2/text()">
</Out2>
</xsl:if>
<xsl:if test="/In/InA/InA3 != ''">
<Out3>
<xsl:value-of select="/In/InA/InA3/text()">
</Out3>
</xsl:if>
</xsl:variable>
<xsl:if test="$out-Node != ''">
<Out>
<xsl:copy-of select="$out-Node"/>
</Out>
</xsl:if>
Of course, this approach must have it issues, I guess one of them would be performance.

Related

Extract value from column (xmltype)

We are using an ERP system. I'm newbie in XML.
In our system, we have a column XML_DATA which TYPE is xmltype(2000)
Schema:
<?xml version="1.0" ?>
- <xs:schema xmlns:xs=" " attributeFormDefault="qualified" elementFormDefault="qualified">
- <xs:element name="XK6">
- <xs:complexType>
- <xs:choice maxOccurs="unbounded">
- <xs:element name="Product">
- <xs:complexType>
- <xs:sequence>
- <xs:element name="Product_row" minOccurs="0" maxOccurs="unbounded">
- <xs:complexType>
- <xs:sequence>
<xs:element name="DETAIL" type="Product_DETAIL" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
- <xs:simpleType name="Product_DETAIL">
- <xs:restriction base="xs:string">
<xs:maxLength value="1000" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
EDIT:
The XML:
<?xml version="1.0" encoding="utf-8" ?>
- <XP6>
+<collapsed_node>
+<collapsed_node>
+<collapsed_node>
- <Product>
- <Product_row>
<DETAIL>sometext </DETAIL>
</Product_row>
</Product>
</XP6>
How can i extract the column ?
Or need more data to do it ?
I want to get the Product_Detail value.
Use the "XMLTABLE" function (see https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions228.htm):
with d as (
select xmltype('
<XK6>
<Product>
<Product_row>
<DETAIL>First product detail</DETAIL>
</Product_row>
<Product_row>
<DETAIL>Second product detail</DETAIL>
</Product_row>
</Product>
</XK6>'
) as thexml from dual)
select detail from d,
xmltable('/XK6/Product/Product_row'
passing d.thexml
columns detail varchar2(100) path 'DETAIL')

Get enum value from string using xslt and xsd

I am trying to get the enum value of the corresponding string. I am translating one XML into another.
For example, the source element is
<VehicleBodyType>Van</VehicleBodyType>
I need to transform it to
<VehicleTypeCode>5</VehicleTypeCode>
This is in an XSD:
<xs:simpleType name="VehicleBodyType">
<xs:restriction base="xs:string">
<xs:enumeration value="NotProvided" />
<xs:enumeration value="NotApplicable" />
<xs:enumeration value="PassengerCar" />
<xs:enumeration value="TruckPickupOrPassengerTruck" />
<xs:enumeration value="Van" />
<xs:enumeration value="TruckSingleUnitTruck2Axles" />
<xs:enumeration value="MotorHomeRecreationalVehicle" />
</xs:restriction>
</xs:simpleType>
I tried this:
<VehicleTypeCode>
<xsl:template match="xs:simpleType[#name='VehicleBodyType']/xs:restriction">
<xsl:for-each select="xs:enumeration">
<xsl:value-of select="#value"/>
</xsl:for-each>
</xsl:template>
</VehicleTypeCode>
but would get an error indicating that I can't do a template as a child of 'VehicleTypeCode' element.
I would prefer to use an XSD, but I can put this into the XSLT as well, if that makes it easier. I don't even know if the for-each is valid, but I found it on here and it looks like what I want.
Here is a sample of the source xml:
<?xml version="1.0" encoding="UTF-8"?>
<Crash>
<Vehicles>
<Vehicle>
<VehicleBodyType>Van</VehicleBodyType>
<VehicleColor>Red</VehicleColor>
</Vehicle>
</Vehicles>
</Crash>
Here the stripped down xslt i'm using:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" exclude-result-prefixes="xsl xs fn xsi">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/Crash" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CrashEntity>
<WorkZoneWorkers>
<xsl:value-of select="WorkersPresentCde"/>
</WorkZoneWorkers>
<xsl:choose>
<xsl:when test="not(Vehicles/Vehicle)">
<CrashUnits/>
</xsl:when>
<xsl:otherwise>
<CrashUnits>
<xsl:apply-templates select="Vehicles/Vehicle" />
</CrashUnits>
</xsl:otherwise>
</xsl:choose>
</CrashEntity>
</xsl:template>
<!-- Vehicles/Vehicle -->
<xsl:template match="Vehicles/Vehicle">
<CrashUnitsEntity>
<VehicleTypeCode>
<xsl:value-of select="VehicleBodyType"/>
</VehicleTypeCode>
<!--Can't use a template within a template-->
<!--<xsl:template match="xs:simpleType[#name='VehicleBodyType']/xs:restriction">
<VehicleTypeCode>
<xsl:value-of select="count(xs:enumeration[#value = 'Van']/preceding-sibling::xs:enumeration) + 1"/>
</VehicleTypeCode>
</xsl:template>-->
<!--Can't use a template within a template (original attempt)-->
<!--<VehicleTypeCode>
<xsl:template match="xs:simpleType[#name=$data]/xs:restriction">
<xsl:for-each select="xs:enumeration">
<xsl:value-of select="#value"/>
</xsl:for-each>
</xsl:template>
</VehicleTypeCode>-->
<!--Try to call a function-->
<VehicleTypeCode function="Enum1">
<xsl:call-template name="getEnum1">
<xsl:with-param name="type" select="VehicleBodyType"/>
<xsl:with-param name="data" select="VehicleBodyType"/>
</xsl:call-template>
</VehicleTypeCode>
<!--Try to call a function-->
<VehicleTypeCode function="Enum2">
<xsl:call-template name="getEnum2">
<xsl:with-param name="type" select="VehicleBodyType"/>
<xsl:with-param name="data" select="VehicleBodyType"/>
</xsl:call-template>
</VehicleTypeCode>
<Vehicle>
<UnitNumber>
<xsl:value-of select="#VehicleNumber"/>
</UnitNumber>
</Vehicle>
</CrashUnitsEntity>
</xsl:template>
<!-- ##################################### functions ##################################### -->
<!-- Can't call a template inside another template-->
<xsl:template name="getEnum1">
<xsl:param name="type"/>
<xsl:param name="data"/>
<xsl:if test="$data">
<!--<xsl:template match="xs:simpleType[#name=$type]/xs:restriction">
<xsl:for-each select="xs:enumeration">
<xsl:value-of select="#value"/>
</xsl:for-each>
</xsl:template>-->
</xsl:if>
</xsl:template>
<!-- Can't call a template inside another template-->
<xsl:template name="getEnum2">
<xsl:param name="type"/>
<xsl:param name="data"/>
<xsl:if test="$data">
<!--<xsl:template match="xs:simpleType[#name=$type]/xs:restriction">
<xsl:value-of select="count(xs:enumeration[#value = $data]/preceding-sibling::xs:enumeration) + 1"/>
</xsl:template>-->
</xsl:if>
</xsl:template>
<xs:simpleType name="VehicleBodyType">
<xs:restriction base="xs:string">
<xs:enumeration value="NotProvided" />
<xs:enumeration value="NotApplicable" />
<xs:enumeration value="PassengerCar" />
<xs:enumeration value="Van" />
<xs:enumeration value="Truck" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="VehicleColor">
<xs:restriction base="xs:string">
<xs:enumeration value="NotProvided" />
<xs:enumeration value="Red" />
<xs:enumeration value="Green" />
<xs:enumeration value="Blue" />
</xs:restriction>
</xs:simpleType>
</xsl:stylesheet>
Expected output xml:
<?xml version="1.0" encoding="UTF-8"?>
<CrashEntity>
<WorkZoneWorkers/>
<CrashUnits>
<CrashUnitsEntity>
<VehicleTypeCode>4</VehicleTypeCode>
<VehicleColor>2</VehicleColor>
</CrashUnitsEntity>
</CrashUnits>
</CrashEntity>

XML validation using XSD assertion condition

I have below XML and XSD and I am getting some error while validation.
My XML
<parent>
<child1>Y</child1>
<child2>Y</child2>
</parent>
My XSD
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="parent">
<xs:complexType>
<xs:sequence>
<xs:element name="child1" type="xs:string" />
<xs:element name="child2" type="xs:string" />
</xs:sequence>
<xs:assertion test="child1 != 'N' and child2 != 'N'" />
</xs:complexType>
</xs:element>
</xs:schema>`
The error I'm getting
s4s-elt-invalid-content.1: The content of '#AnonType_parent' is invalid. Element 'assertion' is invalid, misplaced, or occurs too often.
I want to pass the XML validation If I get other than 'N' value for child elements.
Can anyone suggest what I am doing wrong?

XSLT 2.0: Adding a new element in its valid place in a sequence of elements as specified by an xsd:sequence

Consider the following schema:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.zoo.org/animals" targetNamespace="http://www.zoo.org/animals" elementFormDefault="unqualified" attributeFormDefault="unqualified" version="1.0.0">
<xsd:complexType name="Animals_Type">
<xsd:sequence>
<xsd:element ref="Cat" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Dog" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Mouse" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Lion" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Tiger" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Bear" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Penguin" minOccurs="0" maxOccurs="unbounded" />
<xsd:element ref="Monkey" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:element name="Animals" type="Animals_Type"/>
<xsd:element name="Cat" type="xsd:string"/>
<xsd:element name="Dog" type="xsd:string"/>
<xsd:element name="Mouse" type="xsd:string"/>
<xsd:element name="Lion" type="xsd:string"/>
<xsd:element name="Tiger" type="xsd:string"/>
<xsd:element name="Bear" type="xsd:string"/>
<xsd:element name="Penguin" type="xsd:string"/>
<xsd:element name="Monkey" type="xsd:string"/>
</xsd:schema>
With the following input xml:
<Animals xmlns="http://www.zoo.org/animals">
<Cat>Pixel</Cat>
<Dog>Ada</Dog>
<Mouse>Minnie</Mouse>
<Lion>Donnie</Lion>
<Tiger>Phil</Tiger>
<Bear>Susie</Bear>
<Penguin>Bob</Penguin>
<Monkey>Lennie</Monkey>
</Animals>
Where the desired output xml is to add a Bear named Billy:
<Animals xmlns="http://www.zoo.org/animals">
<Cat>Pixel</Cat>
<Dog>Ada</Dog>
<Mouse>Minnie</Mouse>
<Lion>Donnie</Lion>
<Tiger>Phil</Tiger>
<Bear>Susie</Bear>
<Bear>Billy</Bear>
<Penguin>Bob</Penguin>
<Monkey>Lennie</Monkey>
</Animals>
The following xslt will add Billy the Bear to the xml, however it will add Billy at the end after all other elements are copied and thus will create a schema invalid xml:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://www.zoo.org/animals">
<!-- element template that copies over elements -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Animals">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
<xsl:element name="Bear" namespace="{namespace-uri()}">Billy</xsl:element>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Creates schema invalid xml due to sequence out of order:
<?xml version="1.0" encoding="UTF-8"?>
<Animals xmlns="http://www.zoo.org/animals">
<Cat>Pixel</Cat>
<Dog>Ada</Dog>
<Mouse>Minnie</Mouse>
<Lion>Donnie</Lion>
<Tiger>Phil</Tiger>
<Bear>Susie</Bear>
<Penguin>Bob</Penguin>
<Monkey>Lennie</Monkey>
<Bear>Billy</Bear>
</Animals>
A better xslt that will add Billy the Bear in the correct location is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://www.zoo.org/animals">
<xsl:strip-space elements="*" />
<!-- element template that copies over elements -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Animals">
<xsl:copy>
<xsl:apply-templates select="#*|node()[not(self::Penguin)][not(self::Monkey)]" />
<xsl:element name="Bear" namespace="{namespace-uri()}">Billy</xsl:element>
<xsl:apply-templates select="#*|node()[not(self::Cat)][not(self::Dog)][not(self::Mouse)][not(self::Lion)][not(self::Tiger)][not(self::Bear)]" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Creates correct xml:
<?xml version="1.0" encoding="UTF-8"?>
<Animals xmlns="http://www.zoo.org/animals">
<Cat>Pixel</Cat>
<Dog>Ada</Dog>
<Mouse>Minnie</Mouse>
<Lion>Donnie</Lion>
<Tiger>Phil</Tiger>
<Bear>Susie</Bear>
<Bear>Billy</Bear>
<Penguin>Bob</Penguin>
<Monkey>Lennie</Monkey>
</Animals>
The concern I have with this xslt is that it is directly coupled to the current schema. For instance if the schema is later updated with a simple element addition to the sequence, this xslt will break.
What is the most flexible way to add an element to the middle of a sequence? Is there a transform that can be written such that new elements being added to the sequence in the future will not break the transform?
I believe you want something like this:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:zoo="http://www.zoo.org/animals">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pAddElementName" select="'Bear'"/>
<xsl:param name="pAddValue" select="'Billy'"/>
<xsl:variable name="vSchema" select="document('animals.xsd')"/>
<xsl:variable name="vElementNameSpecified" select=
"$vSchema/*/xs:complexType[#name='Animals_Type']
/xs:sequence/xs:element[#ref=$pAddElementName]"/>
<xsl:variable name="vPreceding" select=
"$vSchema/*/xs:complexType[#name='Animals_Type']
/xs:sequence
/xs:element[following-sibling::xs:element
[#ref=$pAddElementName]]/#ref/string()"/>
<xsl:variable name="vFollowing" select=
"$vSchema/*/xs:complexType[#name='Animals_Type']
/xs:sequence
/xs:element[preceding-sibling::xs:element
[#ref=$pAddElementName]]/#ref/string()"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*[$vElementNameSpecified]/zoo:*[name()=$vPreceding][last()]">
<xsl:call-template name="identity"/>
<xsl:element name="{$pAddElementName}" namespace="http://www.zoo.org/animals">
<xsl:sequence select="$pAddValue"/>
</xsl:element>
</xsl:template>
<xsl:template match="/*[$vElementNameSpecified][not(zoo:*[name()=$vPreceding])]
/zoo:*[name()=$vFollowing][1]">
<xsl:element name="{$pAddElementName}" namespace="http://www.zoo.org/animals">
<xsl:sequence select="$pAddValue"/>
</xsl:element>
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="/*[$vElementNameSpecified]
[not(zoo:*[name()=$vPreceding])
and not(zoo:*[name()=$vFollowing])]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:element name="{$pAddElementName}" namespace="http://www.zoo.org/animals">
<xsl:sequence select="$pAddValue"/>
</xsl:element>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Explanation:
The XML Schema is accessed using the document() function.
All different names of elements that can precede the element to be added, are dynamically identified.
All different names of elements that can follow the element to be added, are dynamically identified.
There are three cases: a) preceding elements exist; b) preceding elements don't exist, but following elements exist; c) neither preceding elements nor following elements exist. The transformation contains a template for each of these cases, that adds the wanted new element in its correct place.

transformation in particular sequence

I would like to know that is it possible to move xml element up or down in element tree.
The input xml.
<ROOT>
<A1>A</A1>
<B1>B</B1>
<C1>C</C1>
<D1>D</D1>
</ROOT>
The required output xml.
<ROOT>
<A1>A</A1>
<D1>D</D1>
<B1>B</B1>
<C1>C</C1>
</ROOT>
XSD Sequence Rule
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="ROOT">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="A1"/>
<xs:element type="xs:string" name="D1"/>
<xs:element type="xs:string" name="B1"/>
<xs:element type="xs:string" name="C1"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Can anyone provide me some XSLT example to do this such of thing? Thank you for any help in advance.
Martin Honnen help me to get answer from other post, hence just thought to share answer to help others but still I am wondering if there is any other way to do this - as in this approach if there is change in XSD tag sequence one has to change xsl sequence too.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ROOT">
<ROOT>
<xsl:apply-templates select="A1"/>
<xsl:apply-templates select="D1"/>
<xsl:apply-templates select="C1"/>
<xsl:apply-templates select="B1"/>
</ROOT>
</xsl:template>
<xsl:template match="A1">
<a1>
<xsl:apply-templates />
</a1>
</xsl:template>
<xsl:template match="B1">
<b1>
<xsl:apply-templates />
</b1>
</xsl:template>
<xsl:template match="C1">
<c1>
<xsl:apply-templates />
</c1>
</xsl:template>
<xsl:template match="D1">
<d1>
<xsl:apply-templates />
</d1>
</xsl:template>
</xsl:stylesheet>