We're doing a Snaplogic proof of concept and the UI is quite pretty so I thought I would write some blog posts about it. This will make a break from the normal boring badly spelled content I publish! Snaplogic works very well with JSON data in its pipelines but the integrations that we need to build make heavy use of XML. One solution is to use the XML Parser snap which will convert the XML into JSON. Unfortunately, the XML Parser failed for the first pipeline we tried in our POC!
The error we were seeing was:
-
{error:Failed to convert xml to json, stacktrace:com.snaplogic.api.ExecutionException: Encountered XML Mixed content
-
\n at com.snaplogic.snap.api.xml.XmlUtilsImpl.throwMixedContentEx...}
-
"error": "Failed to convert xml to json"
-
"stacktrace": "com.snaplogic.api.ExecutionException: Encountered XML Mixed content
-
\n at com.snaplogic.snap.api.xml.XmlUtilsImpl.throwMixedContentException(XmlUtilsImpl.java:1162)
-
\n at com.snaplogic.snap.api.xml.XmlUtilsImpl.copyEventsFrom(XmlUtilsImpl.java:746)
-
\n at com.snaplogic.snap.api.xml.XmlUtilsImpl.convertToJson(XmlUtilsImpl.java:262)
-
\n at com.snaplogic.snaps.transform.XmlParser.doWork(XmlParser.java:375)
-
\n at com.snaplogic.snap.api.SimpleBinarySnap.execute(SimpleBinarySnap.java:57)
-
\n at com.snaplogic.cc.snap.common.SnapRunnableImpl.executeSnap(SnapRunnableImpl.java:770)
-
\n at com.snaplogic.cc.snap.common.SnapRunnableImpl.executeForSuggest(SnapRunnableImpl.java:617)
-
\n at com.snaplogic.cc.snap.common.SnapRunnableImpl.doRun(SnapRunnableImpl.java:826)
-
\n at com.snaplogic.cc.snap.common.SnapRunnableImpl.access$000(SnapRunnableImpl.java:115)
-
\n at com.snaplogic.cc.snap.common.SnapRunnableImpl$1.run(SnapRunnableImpl.java:362)
We were able to isolate the issue to the presence of mixed content in the input XML. This was because the API we are using is designed for presenting results onto web pages and as they have html elements mixed with plain text they have mixed content. The Saxon parse used by Snaplogic doesn't know how to deal with this.
We created an example XML that causes this error:
-
<root>
-
<a>Text A</a>
-
<b>Text B</b>
-
<c>Text C</c>
-
<mixedContent><tag> </tag>Text In mixed content</mixedContent>
-
</root>
The problem is with the element 'mixedContent' which has two child nodes. One is an element node, and the other is a text node. (In XML Text sequences are considered nodes even though they don't have start and end tags.)
Lucky for us years of fighting with XML and XSLT helped us come up with a solution. We can use the XSLT snap to remove the mixed content from the input XML before passing that onwards to the XML Parser snap. The following XSLT provides a generic solution which can be applied here:
-
<?xml version="1.0"?>
-
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
-
<xsl:output method="xml"/>
-
-
<xsl:template match="text()[string-length(normalize-space(.))>0]">
-
<xsl:if test="count(../*)=0"><xsl:copy-of select="."/></xsl:if>
-
<xsl:if test="count(../*)>0"><mixed_content_wrapper><xsl:copy-of select="."/></mixed_content_wrapper></xsl:if>
-
</xsl:template>
-
-
<xsl:template match="@*|*|processing-instruction()|comment()">
-
<xsl:copy>
-
<xsl:apply-templates select="*|@*|text()|processing-instruction()|comment()"/>
-
</xsl:copy>
-
</xsl:template>
-
-
</xsl:stylesheet>
This gives us a happy XML parser and output JSON:
This approach can be further generalized by creating it as a Snaplogic Pattern - that will be a future learning exercise.
It does rely on converting the entire XML document into a JSON document. In an XML -> XML integration I think it would be better to directly transform the XML document directly into a target XML document. I am not sure how that will mix together with the other Snaps which seem to expect JSON, e.g. the mapper snap seems to only go from JSON to JSON.