webMethods: how to create an XSD schema from NDF structure definition file(s)

One of our clients uses webMethods for EAI. I’m not a big expert in webMethods (WM), I know only my part – EAI, partners setup, testing and map development. WM uses it’s own way for the structures definition. I cannot use it in my work, I need standard XSD or at least DTD files. So I decided to find a way to somehow export or convert those NDF files into XSD. These NDF files are in XML format so I decided to develop an XSLT to transform them into XSD. Now I’d like to share it with you. Mention should be made that I’m not an WM expert – maybe this XSLT works for only these particular NDF files and won’t work for all the others, but if you have a similar task – just give it a try and please let me know if it works for you or not. Sometimes there are more than just one NDF file to describe the structure – my XSLT supports such cases (a variable “base” at the beginning is for the folder’s name). It might require some explanation. If you have problems with it – I could try to help, feel free to contact me (there is a contact form)

Here is the code.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
	<xsl:output indent="yes"/>

	<xsl:key name="reference" match="value[@name = 'rec_ref']" use="substring-after(., ':')" />

	<xsl:variable name="base" select="'canonical\DemandForecast'" />

	<xsl:variable name="mainRecordReferences">
		<xsl:for-each select="//value[@name = 'rec_ref']">
			<xsl:value-of select="'~'"/>
			<xsl:value-of select="substring-after(., ':')" />
			<xsl:value-of select="'~'"/>
		</xsl:for-each>
	</xsl:variable>

	<xsl:template match="/">
		<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

			<!-- root structure -->
			<xsl:for-each select="(//*[(name() = 'list' or name() = 'array') and @name = 'rec_fields']/record[value[@name = 'field_type'] = 'record' and not(starts-with(value[@name = 'field_name'], '@'))])[1]">
				<!-- top level -->
				<xsd:element>
					<xsl:attribute name="name">
						<xsl:value-of select="value[@name = 'field_name']"/>
					</xsl:attribute>
					<xsd:complexType>
						<xsd:sequence>
							<xsl:for-each select="*[(name() = 'list' or name() = 'array') and @name = 'rec_fields']/record[(value[@name = 'field_type'] = 'record' or value[@name = 'field_type'] = 'recref') and not(starts-with(value[@name = 'field_name'], '@'))]">
								<!-- 2nd level - header/body -->
								<xsd:element nillable="true">
									<xsl:attribute name="name">
										<xsl:value-of select="value[@name = 'field_name']"/>
									</xsl:attribute>
									
									<xsl:attribute name="type">
										<xsl:choose>
											<xsl:when test="value[@name = 'field_type'] = 'record'">
												<xsl:value-of select="'_'" />
												<xsl:value-of select="value[@name = 'field_name']"/>
											</xsl:when>
											<xsl:when test="value[@name = 'field_type'] = 'recref'">
												<xsl:value-of select="substring-after(value[@name = 'rec_ref'], ':')"/>
											</xsl:when>
											<xsl:when test="value[@name = 'field_type'] = 'string'">
												<xsl:value-of select="'xsd:string'" />
											</xsl:when>
										</xsl:choose>
									</xsl:attribute>

									<xsl:if test="value[@name = 'field_opt'] = 'true'">
										<xsl:attribute name="minOccurs">0</xsl:attribute>
									</xsl:if>

									<xsl:if test="value[@name = 'field_dim'] = '1'">
										<xsl:attribute name="maxOccurs">unbounded</xsl:attribute>
									</xsl:if>
								</xsd:element>
							</xsl:for-each>
						</xsd:sequence>

						<!-- attributes -->
						<xsl:for-each select="*[(name() = 'list' or name() = 'array') and @name = 'rec_fields']/record[ starts-with(value[@name = 'field_name'], '@')]">
							<xsl:call-template name="attribute" />
						</xsl:for-each>
					</xsd:complexType>
				</xsd:element>
			</xsl:for-each>

			<!-- records/structures -->
			<xsl:for-each select="//*[(name() = 'list' or name() = 'array') and @name = 'rec_fields']/record[value[@name = 'field_type'] = 'record' and not(starts-with(value[@name = 'field_name'], '@'))]">
				<xsl:if test="position() != 1">
					<xsl:call-template name="complexType" />
				</xsl:if>
			</xsl:for-each>

			<!-- strings -->
			<!--
			<xsl:for-each select="//*[(name() = 'list' or name() = 'array') and position() != 1 and @name = 'rec_fields']/record[value[@name = 'field_type'] = 'string' and not(starts-with(value[@name = 'field_name'], '@'))]">
				<xsl:call-template name="simpleTypeString" />
			</xsl:for-each>
			-->

			<!-- references -->
			<xsl:for-each select="//value[@name = 'rec_ref']">
				<xsl:call-template name="recordReferences" />
			</xsl:for-each>
		</xsd:schema>
	</xsl:template>

	<!--
		=== complexType ================================
	-->
	<xsl:template name="complexType">
		<xsl:param name="type_name" select="''" />
		<xsd:complexType>
			<xsl:attribute name="name">
				<xsl:choose>
					<xsl:when test="$type_name != ''">
						<xsl:value-of select="$type_name"/>
					</xsl:when>
					<xsl:otherwise>
						<xsl:value-of select="'_'" />
						<xsl:value-of select="value[@name = 'field_name']"/>
					</xsl:otherwise>
				</xsl:choose>
			</xsl:attribute>

			<xsl:choose>
				<!-- structure and attributes: -->
				<xsl:when test="count(*[(name() = 'list' or name() = 'array') and @name = 'rec_fields']/record[not(starts-with(value[@name = 'field_name'], '@')) and not(starts-with(value[@name = 'field_name'], '*'))]) != 0">
					<xsd:sequence>
						<xsl:for-each select="*[(name() = 'list' or name() = 'array') and @name = 'rec_fields']/record[not(starts-with(value[@name = 'field_name'], '@')) and not(starts-with(value[@name = 'field_name'], '*'))]">
							<!-- children -->
							<xsd:element nillable="true">
								<xsl:attribute name="name">
									<xsl:value-of select="value[@name = 'field_name']"/>
								</xsl:attribute>

								<xsl:if test="value[@name = 'field_opt'] = 'true'">
									<xsl:attribute name="minOccurs">0</xsl:attribute>
								</xsl:if>

								<xsl:if test="value[@name = 'field_dim'] = '1'">
									<xsl:attribute name="maxOccurs">unbounded</xsl:attribute>
								</xsl:if>

								<!-- choose if it's a structure or simple type -->
								<xsl:attribute name="type">
									<xsl:choose>
										<xsl:when test="value[@name = 'field_type'] = 'record'">
											<xsl:value-of select="'_'" />
											<xsl:value-of select="value[@name = 'field_name']"/>
										</xsl:when>
										<xsl:when test="value[@name = 'field_type'] = 'recref'">
											<xsl:value-of select="substring-after(value[@name = 'rec_ref'], ':')"/>
										</xsl:when>
										<xsl:when test="value[@name = 'field_type'] = 'string'">
											<xsl:value-of select="'xsd:string'" />
										</xsl:when>
									</xsl:choose>
								</xsl:attribute>
							</xsd:element>
						</xsl:for-each>
					</xsd:sequence>

					<!-- attributes -->
					<xsl:for-each select="*[(name() = 'list' or name() = 'array') and @name = 'rec_fields']/record[ starts-with(value[@name = 'field_name'], '@')]">
						<xsl:call-template name="attribute" />
					</xsl:for-each>
				</xsl:when>
				<!-- text and attributes, no structure: -->
				<xsl:otherwise>
					<xsd:simpleContent>
						<xsd:extension base="xsd:string">
							<!-- attributes -->
							<xsl:for-each select="*[(name() = 'list' or name() = 'array') and @name = 'rec_fields']/record[ starts-with(value[@name = 'field_name'], '@')]">
								<xsl:call-template name="attribute" />
							</xsl:for-each>
						</xsd:extension>
					</xsd:simpleContent>
				</xsl:otherwise>
			</xsl:choose>
		</xsd:complexType>
	</xsl:template>

	<!--
		=== simpleTypeString ===========================
	-->
	<xsl:template name="simpleTypeString">
		<xsd:element type="xsd:string">
			<xsl:attribute name="name">
				<xsl:value-of select="value[@name = 'field_name']"/>
			</xsl:attribute>

			<xsl:if test="value[@name = 'field_opt'] = 'true'">
				<xsl:attribute name="minOccurs">0</xsl:attribute>
			</xsl:if>

			<xsl:if test="value[@name = 'field_dim'] = '1'">
				<xsl:attribute name="maxOccurs">unbounded</xsl:attribute>
			</xsl:if>
		</xsd:element>
	</xsl:template>

	<!--
		=== recordReferences ===========================
	-->
	<xsl:template name="recordReferences">
		<xsl:variable name="ref_name" select="substring-after(., ':')" />
		<xsl:if test="generate-id(.) = generate-id(key('reference', $ref_name)[1])">

			<!-- path -->
			<xsl:variable name="path" select="concat($base, '/', $ref_name, '/node.ndf')" />

			<!-- reference complex structure -->
			<xsl:for-each select="document($path)/*/record[not(starts-with(value[@name = 'field_name'], '@')) and not(starts-with(value[@name = 'field_name'], '*'))]">
				<xsl:call-template name="complexType">
					<xsl:with-param name="type_name" select="$ref_name" />
				</xsl:call-template>
			</xsl:for-each>

			<!-- child complex structure-->
			<!-- records/structures -->
			<xsl:for-each select="document($path)//*[(name() = 'list' or name() = 'array') and @name = 'rec_fields']/record[value[@name = 'field_type'] = 'record' and not(starts-with(value[@name = 'field_name'], '@')) and not(starts-with(value[@name = 'field_name'], '*'))]">
				<xsl:call-template name="complexType" />
			</xsl:for-each>

			<!-- strings -->
			<!--
			<xsl:for-each select="document($path)//*[(name() = 'list' or name() = 'array') and position() != 1 and @name = 'rec_fields']/record[value[@name = 'field_type'] = 'string' and not(starts-with(value[@name = 'field_name'], '@')) and not(starts-with(value[@name = 'field_name'], '*'))]">
				<xsl:call-template name="simpleTypeString" />
			</xsl:for-each>
			-->

			<!-- references -->
			<xsl:for-each select="document($path)//value[@name = 'rec_ref']">
				<xsl:if test="not( contains($mainRecordReferences, concat('~', substring-after(., ':'), '~') ) )">
					<xsl:call-template name="recordReferences" />
				</xsl:if>
			</xsl:for-each>
		</xsl:if>
	</xsl:template>

	<xsl:template name="attribute">
		<!-- attributes -->
		<xsd:attribute>
			<xsl:attribute name="name">
				<xsl:value-of select="substring-after(value[@name = 'field_name'], '@')"/>
			</xsl:attribute>

			<xsd:simpleType>
				<xsd:restriction base="xsd:string">
					<xsl:for-each select="record[@name = 'field_content_type']/array/record/value[@name = 'lexRep']">
						<xsd:enumeration>
							<xsl:attribute name="value">
								<xsl:value-of select="."/>
							</xsl:attribute>
						</xsd:enumeration>
					</xsl:for-each>
				</xsd:restriction>
			</xsd:simpleType>
		</xsd:attribute>
	</xsl:template>
</xsl:stylesheet>

UPD: sometimes this code creates duplicate elements so I’ve added an additional step for these cases – cleaning up the generated XSDs

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
	<xsl:output indent="yes"/>

	<xsl:key name="complexType" match="xsd:complexType" use="@name" />

	<xsl:template match="/">
		<xsl:for-each select="*">
			<xsl:call-template name="copyElement"/>
		</xsl:for-each>
	</xsl:template>

	<xsl:template name="copyElement">
		<xsl:variable name="segName" select="name(.)" />
		<xsl:element name="{$segName}">
			<xsl:for-each select="@*">
				<xsl:attribute name="{name(.)}">
					<xsl:value-of select="."/>
				</xsl:attribute>
			</xsl:for-each>

			<xsl:choose>
				<xsl:when test="contains($segName, 'SegmentName')">
					<!-- custom code -->
				</xsl:when>
			</xsl:choose>


			<xsl:if test="normalize-space(text()) != ''">
				<xsl:value-of select="normalize-space(.)"/>
			</xsl:if>

			<xsl:for-each select="*">
				<xsl:choose>
					<xsl:when test="name() != 'xsd:complexType'">
						<xsl:call-template name="copyElement"/>
					</xsl:when>
					<xsl:when test="name() = 'xsd:complexType'">
						<xsl:if test="normalize-space(@name) = '' or generate-id(.) = generate-id(key('complexType', @name)[1])">
							<xsl:call-template name="copyElement"/>
						</xsl:if>
					</xsl:when>
				</xsl:choose>
			</xsl:for-each>
		</xsl:element>
	</xsl:template>
</xsl:stylesheet>

Gennady Kim

Advertisements

One thought on “webMethods: how to create an XSD schema from NDF structure definition file(s)

  1. Pingback: webMethods: how to create an XSD schema (2) | EDI shortcuts

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s