changeset 13:197a850b1f6f default tip

working version of xml2json transformation
author cin
date Mon, 09 Apr 2018 16:27:26 +0300
parents 191b81b2052b
children
files data/jsondata.xml xslt/json-person.xsl xslt/json.xsl
diffstat 3 files changed, 195 insertions(+), 68 deletions(-) [+]
line wrap: on
line diff
--- a/data/jsondata.xml	Mon Apr 09 06:43:46 2018 +0300
+++ b/data/jsondata.xml	Mon Apr 09 16:27:26 2018 +0300
@@ -4,11 +4,15 @@
 	<firstName>Craig</firstName>
 	<lastName>Jackson</lastName>
 	<age>49</age>
-	<address order="1">
-		<street>1st avenue</street>
-	</address>
-	<home>
+	<xaddress order="1" shared="true">
+		<street>1st avenue,
+		new street</street>
+	</xaddress>
+	<xhome>
 		<tel>123-23-12</tel>
-	</home>
+	</xhome>
+	<xwork>
+		<tel>213-55-66</tel>
+	</xwork>
 	<unemployed>false</unemployed>
 </person>
\ No newline at end of file
--- a/xslt/json-person.xsl	Mon Apr 09 06:43:46 2018 +0300
+++ b/xslt/json-person.xsl	Mon Apr 09 16:27:26 2018 +0300
@@ -6,26 +6,47 @@
 	<xsl:output method="text" />
 	
 	<xsl:template match="firstName | lastName" mode="json-member-value">
-		<xsl:call-template name="write-value">
-			<xsl:with-param name="value" select="key('members',concat(generate-id(..),local-name(.)))[last()]"/>
+		<xsl:param name="values" select="."/>
+		<xsl:call-template name="write-array">
+			<xsl:with-param name="values" select="$values"/>
 		</xsl:call-template>
 	</xsl:template>
 	
-	<xsl:template match="address" mode="json-member-value">
-		<xsl:call-template name="write-array"/>
+	<xsl:template match="address | work | home" mode="json-object">
+		<xsl:call-template name="write-member-string">
+			<xsl:with-param name="name" select="'_type'"/>
+			<xsl:with-param name="value" select="local-name(.)"/>
+		</xsl:call-template>
+		
+		<xsl:variable name="members" select="@order | *"/>
+		<xsl:if test="$members">
+			<xsl:call-template name="write-separator"/>
+			<xsl:call-template name="write-members">
+				<xsl:with-param name="members" select="$members"/>
+			</xsl:call-template>
+		</xsl:if>
 	</xsl:template>
 	
-	<xsl:template match="work | home">
-		<address>
-			<_type><xsl:value-of select="local-name(.)"/></_type>
-			<xsl:copy-of select="*"/>
-		</address>
+	<xsl:template match="person" mode="json-object">
+		<xsl:variable name="address" select="home | work | address"/>
+		<xsl:variable name="members" select="*[not(self::home | self::work | self::address)]"/>
+		
+		<xsl:if test="$address">
+			<xsl:call-template name="write-member-array">
+				<xsl:with-param name="name" select="'address'"/>
+				<xsl:with-param name="values" select="$address"/>
+			</xsl:call-template>
+		</xsl:if>
+		
+		<xsl:if test="count($address) > 0 and count($members) > 0">
+			<xsl:call-template name="write-separator"/>
+		</xsl:if>
+		
+		<xsl:call-template name="write-members">
+			<xsl:with-param name="members" select="$members"/>
+		</xsl:call-template>
+		
 	</xsl:template>
 	
-	<xsl:template match="@*">
-		<xsl:element name="{local-name(.)}" >
-			<xsl:value-of select="."/>
-		</xsl:element>
-	</xsl:template>
 	
 </xsl:stylesheet>
\ No newline at end of file
--- a/xslt/json.xsl	Mon Apr 09 06:43:46 2018 +0300
+++ b/xslt/json.xsl	Mon Apr 09 16:27:26 2018 +0300
@@ -5,52 +5,38 @@
 	xmlns:exsl="http://exslt.org/common">
 	<xsl:output method="text" />
 	
-	<xsl:key name="members" match="*" use="concat(generate-id(..),local-name(.))"/>
-
 	<xsl:template match="/">
-		<xsl:variable name="doc">
-			<xsl:apply-templates/>
-		</xsl:variable>
-		<xsl:apply-templates select="exsl:node-set($doc)" mode="json-value" />
+		<xsl:apply-templates mode="json-value" />
 	</xsl:template>
 	
-	<xsl:template match="*">
-		<xsl:copy>
-			<xsl:apply-templates select="@*"/>
-			<xsl:apply-templates/>
-		</xsl:copy>
-	</xsl:template>
-	
-	<xsl:template match="@*">
-		<xsl:copy/>
-	</xsl:template>
 	
 	<!-- handle json-object -->
 	
-	<xsl:template match="*" mode="json-object-members">
-		<xsl:apply-templates select="." mode="json-object-members-internal"/>
-	</xsl:template>
-	
-	<xsl:template match="*" mode="json-object-members-internal">
-		<xsl:variable name="oid" select="generate-id(.)"/>
-		<xsl:variable name="grouped" select="*[generate-id(.) = generate-id(key('members',concat($oid,local-name(.))))]"/>
-		<xsl:apply-templates select="$grouped" mode="json-object-member"/>
+	<xsl:template match="*" mode="json-object">
+		<xsl:call-template name="write-members"/>
 	</xsl:template>
 	
-	<xsl:template match="*" mode="json-object-member">
+	<xsl:template match="*|@*" mode="json-member">
+		<xsl:param name="values" select="."/>
 		<xsl:call-template name="write-string">
-			<xsl:with-param name="text" select="local-name(.)"/>
+			<xsl:with-param name="text"><xsl:apply-templates select="." mode="json-member-name"/></xsl:with-param>
 		</xsl:call-template>
 		<xsl:text> : </xsl:text>
-		<xsl:apply-templates select="." mode="json-member-value"/>
-		<xsl:if test="position() != last()">
-			<xsl:text>, </xsl:text>
-		</xsl:if>
+		<xsl:apply-templates select="." mode="json-member-value">
+			<xsl:with-param name="values" select="$values"/>
+		</xsl:apply-templates>
 	</xsl:template>
 	
-	<xsl:template match="*" mode="json-member-value">
-		<xsl:variable name="oid" select="generate-id(..)"/>
-		<xsl:variable name="values" select="key('members',concat($oid,local-name(.)))"/>
+	<xsl:template match="*" mode="json-member-name">
+		<xsl:value-of select="local-name(.)"/>
+	</xsl:template>
+	
+	<xsl:template match="@*" mode="json-member-name">
+		<xsl:value-of select="concat('_',local-name(.))"/>
+	</xsl:template>
+	
+	<xsl:template match="*|@*" mode="json-member-value">
+		<xsl:param name="values" select="."/>
 		<xsl:choose>
 			<xsl:when test="count($values) > 1">
 				<xsl:call-template name="write-array">
@@ -63,7 +49,7 @@
 		</xsl:choose>
 	</xsl:template>
 	
-	<xsl:template match="*" mode="json-array-item">
+	<xsl:template match="*|@*" mode="json-array-item">
 		<xsl:apply-templates select="." mode="json-value"/>
 		<xsl:if test="position() != last()">
 			<xsl:text>, </xsl:text>
@@ -72,21 +58,21 @@
 	
 	<!-- handle json-value -->
 
-	<xsl:template match="text()[. = 'true']" mode="json-value">
+	<xsl:template match="text()[. = 'true'] | @*[. = 'true']" mode="json-value">
 		<xsl:text>true</xsl:text>
 	</xsl:template>
 
-	<xsl:template match="text()[. = 'false']"
+	<xsl:template match="text()[. = 'false']  | @*[. = 'false']"
 		mode="json-value">
 		<xsl:text>false</xsl:text>
 	</xsl:template>
 
-	<xsl:template match="text()[string(number(.)) != 'NaN']"
+	<xsl:template match="text()[string(number(.)) != 'NaN'] | @*[string(number(.)) != 'NaN']"
 		mode="json-value">
 		<xsl:value-of select="number(.)" />
 	</xsl:template>
 
-	<xsl:template match="text()" mode="json-value">
+	<xsl:template match="text()|@*" mode="json-value">
 		<xsl:call-template name="write-string">
 			<xsl:with-param name="text" select="."/>
 		</xsl:call-template>
@@ -104,47 +90,163 @@
 	
 	<xsl:template name="write-value">
 		<xsl:param name="value" select="."/>
-		<xsl:apply-templates select="exsl:node-set($value)" mode="json-value"/>
+		<xsl:apply-templates select="$value" mode="json-value"/>
 	</xsl:template>
 	
-	<xsl:template name="write-named-value">
+	<xsl:template name="write-member">
+		<xsl:param name="name"/>
+		<xsl:param name="value"/>
+		<xsl:call-template name="write-string">
+			<xsl:with-param name="text" select="$name"/>
+		</xsl:call-template>
+		<xsl:text> : </xsl:text>
+		<xsl:apply-templates select="$value" mode="json-value"/>
+	</xsl:template>
+	
+	<xsl:template name="write-member-string">
 		<xsl:param name="name"/>
 		<xsl:param name="value"/>
 		<xsl:call-template name="write-string">
 			<xsl:with-param name="text" select="$name"/>
 		</xsl:call-template>
 		<xsl:text> : </xsl:text>
-		<xsl:apply-templates select="exsl:node-set($value)"/>
+		<xsl:call-template name="write-string">
+			<xsl:with-param name="text" select="$value"/>
+		</xsl:call-template>
+	</xsl:template>
+	
+	<xsl:template name="write-member-array">
+		<xsl:param name="name"/>
+		<xsl:param name="values"/>
+		<xsl:call-template name="write-string">
+			<xsl:with-param name="text" select="$name"/>
+		</xsl:call-template>
+		<xsl:text> : </xsl:text>
+		<xsl:call-template name="write-array">
+			<xsl:with-param name="values" select="$values"/>
+		</xsl:call-template>
+	</xsl:template>
+	
+	<xsl:template name="write-separator">
+		<xsl:text>, </xsl:text>
 	</xsl:template>
 	
 	<!-- specialized template traits -->
 	
 	<xsl:template name="write-string">
 		<xsl:param name="text"/>
-		<xsl:value-of select="concat('&quot;', $text,'&quot;')" />
+		<xsl:text>&quot;</xsl:text>
+		<xsl:call-template name="escape-bs-string">
+			<xsl:with-param name="s" select="$text"/>
+		</xsl:call-template>
+		<xsl:text>&quot;</xsl:text>
 	</xsl:template>
 	
 	<xsl:template name="write-object">
 		<xsl:param name="value" select="."/>
 		<xsl:text>{ </xsl:text>
-		<xsl:apply-templates select="$value" mode="json-object-members"/>
+		<xsl:apply-templates select="$value" mode="json-object"/>
 		<xsl:text> }</xsl:text>
 	</xsl:template>
 	
 	<xsl:template name="write-array">
-		<xsl:param name="oid" select="generate-id(..)"/>
-		<xsl:param name="values" select="key('members',concat($oid,local-name(.)))"/>
+		<xsl:param name="values"/>
 		
 		<xsl:text>[ </xsl:text>
 		<xsl:apply-templates select="$values" mode="json-array-item"/>
 		<xsl:text> ]</xsl:text>
 	</xsl:template>
 	
-	<xsl:template name="write-object-members">
-		<xsl:param name="object" select="."/>
-		<xsl:for-each select="exsl:node-set($object)">
-			<xsl:apply-templates select="." mode="json-object-members-internal"/>
+	<xsl:template name="write-members">
+		<xsl:param name="members" select="*"/>
+
+		<xsl:for-each select="$members">
+			<xsl:variable name="current" select="."/>
+			<xsl:variable name="values" select="$members[local-name(.) = local-name($current)]"/>
+			<xsl:if test="generate-id($current) = generate-id($values)">
+				<xsl:if test="position()>1">
+					<xsl:call-template name="write-separator"/>
+				</xsl:if>
+				<xsl:apply-templates select="$current" mode="json-member">
+					<xsl:with-param name="values" select="$values"/>
+				</xsl:apply-templates>
+			</xsl:if>
 		</xsl:for-each>
 	</xsl:template>
+	
+	<!-- escape string -->
+	<!--
+  Copyright (c) 2006,2008 Doeke Zanstra
+  All rights reserved.
+  https://github.com/doekman/xml2json-xslt/blob/master/xml2json.xslt
+	-->
+	 <!-- Escape the backslash (\) before everything else. -->
+  <xsl:template name="escape-bs-string">
+    <xsl:param name="s"/>
+    <xsl:choose>
+      <xsl:when test="contains($s,'\')">
+        <xsl:call-template name="escape-quot-string">
+          <xsl:with-param name="s" select="concat(substring-before($s,'\'),'\\')"/>
+        </xsl:call-template>
+        <xsl:call-template name="escape-bs-string">
+          <xsl:with-param name="s" select="substring-after($s,'\')"/>
+        </xsl:call-template>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:call-template name="escape-quot-string">
+          <xsl:with-param name="s" select="$s"/>
+        </xsl:call-template>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+  
+  <!-- Escape the double quote ("). -->
+  <xsl:template name="escape-quot-string">
+    <xsl:param name="s"/>
+    <xsl:choose>
+      <xsl:when test="contains($s,'&quot;')">
+        <xsl:call-template name="encode-string">
+          <xsl:with-param name="s" select="concat(substring-before($s,'&quot;'),'\&quot;')"/>
+        </xsl:call-template>
+        <xsl:call-template name="escape-quot-string">
+          <xsl:with-param name="s" select="substring-after($s,'&quot;')"/>
+        </xsl:call-template>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:call-template name="encode-string">
+          <xsl:with-param name="s" select="$s"/>
+        </xsl:call-template>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+  
+  <!-- Replace tab, line feed and/or carriage return by its matching escape code. Can't escape backslash
+       or double quote here, because they don't replace characters (&#x0; becomes \t), but they prefix 
+       characters (\ becomes \\). Besides, backslash should be seperate anyway, because it should be 
+       processed first. This function can't do that. -->
+  <xsl:template name="encode-string">
+    <xsl:param name="s"/>
+    <xsl:choose>
+      <!-- tab -->
+      <xsl:when test="contains($s,'&#x9;')">
+        <xsl:call-template name="encode-string">
+          <xsl:with-param name="s" select="concat(substring-before($s,'&#x9;'),'\t',substring-after($s,'&#x9;'))"/>
+        </xsl:call-template>
+      </xsl:when>
+      <!-- line feed -->
+      <xsl:when test="contains($s,'&#xA;')">
+        <xsl:call-template name="encode-string">
+          <xsl:with-param name="s" select="concat(substring-before($s,'&#xA;'),'\n',substring-after($s,'&#xA;'))"/>
+        </xsl:call-template>
+      </xsl:when>
+      <!-- carriage return -->
+      <xsl:when test="contains($s,'&#xD;')">
+        <xsl:call-template name="encode-string">
+          <xsl:with-param name="s" select="concat(substring-before($s,'&#xD;'),'\r',substring-after($s,'&#xD;'))"/>
+        </xsl:call-template>
+      </xsl:when>
+      <xsl:otherwise><xsl:value-of select="$s"/></xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
 
 </xsl:stylesheet>
\ No newline at end of file