All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.ma.json.JsonHandlerXML Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2015 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.ma.json;

import net.sf.saxon.Configuration;
import net.sf.saxon.event.Builder;
import net.sf.saxon.event.NamespaceReducer;
import net.sf.saxon.event.Receiver;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.expr.parser.ExplicitLocation;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.lib.StandardEntityResolver;
import net.sf.saxon.om.*;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.transform.sax.SAXSource;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Stack;

/**
 * Handler to generate an XML representation of JSON from a series of events
 */
public class JsonHandlerXML extends JsonHandler {

    private Receiver out;
    private Builder builder;

    private Stack keys;
    private Stack inMap = new Stack();

    public boolean allowAnyTopLevel;
    public boolean liberal;
    public boolean validate;
    public boolean checkForDuplicates;

    public static final String SCHEMA_URI = "http://www.w3.org/2005/xpath-functions.xsd";
    public static final String JSON_NS = NamespaceConstant.JSON;
    public static final String PREFIX = "";

    private NamePool namePool;
    private FingerprintedQName mapQN;
    private FingerprintedQName arrayQN;
    private FingerprintedQName stringQN;
    private FingerprintedQName numberQN;
    private FingerprintedQName booleanQN;
    private FingerprintedQName nullQN;
    private FingerprintedQName keyQN;
    private FingerprintedQName escapedQN;
    private FingerprintedQName escapedKeyQN;

    private static final Untyped UNTYPED = Untyped.getInstance();
    private static final AnySimpleType SIMPLE_TYPE = AnySimpleType.getInstance();
    private static final BuiltInAtomicType BOOLEAN_TYPE = BuiltInAtomicType.BOOLEAN;
    private static final BuiltInAtomicType STRING_TYPE = BuiltInAtomicType.STRING;

    public HashMap types;
    private Stack> mapKeys = new Stack>();


    /**
     * Create a QName in null namespace
     *
     * @param s the local name
     * @return the QName
     */
    private FingerprintedQName qname(String s) {
        FingerprintedQName fp = new FingerprintedQName("", "", s);
        fp.allocateNameCode(namePool);
        return fp;
    }

    /**
     * Create a QName in the JSON namespace
     *
     * @param s the local name
     * @return the QName
     */
    private FingerprintedQName qnameNS(String s) {
        FingerprintedQName fp = new FingerprintedQName(PREFIX, JSON_NS, s);
        fp.allocateNameCode(namePool);
        return fp;
    }

    /**
     * Make the handler to construct the XML tree representation for JSON
     *
     * @param context       the context in which the result tree is to be built
     * @param staticBaseUri the static base URI, used for the base URI of the constructed tree
     * @param flags         flags indicating the chosen options
     * @throws XPathException if initialization fails, for example because of problems loading the schema
     */
    public JsonHandlerXML(XPathContext context, String staticBaseUri, int flags) throws XPathException {
        init(context, flags);
        builder = context.getController().makeBuilder();
        builder.setSystemId(staticBaseUri);
        builder.setTiming(false);
        out = new NamespaceReducer(builder);
        out.open();
        out.startDocument(0);
    }

    /**
     * Initialise the tree builder.
     * 

This also ensures the appropriate schema is loaded when type validation is required

* * @param context the context in which the result tree is to be built * @param flags flags indicating the chosen options * @throws XPathException if anything goes wrong (for example, with getting schema information) */ private void init(XPathContext context, int flags) throws XPathException { keys = new Stack(); /* This may not need to be a stack as there should only be at most one pre-selected key * However, the stack neatly indicates its empty state * */ setContext(context); charChecker = context.getConfiguration().getValidCharacterChecker(); escape = (flags & JsonParser.ESCAPE) != 0; allowAnyTopLevel = (flags & JsonParser.ALLOW_ANY_TOP_LEVEL) != 0; validate = (flags & JsonParser.VALIDATE) != 0; checkForDuplicates = validate || (flags & JsonParser.DUPLICATES_RETAINED) == 0; types = new HashMap(); namePool = context.getConfiguration().getNamePool(); mapQN = qnameNS("map"); arrayQN = qnameNS("array"); stringQN = qnameNS("string"); numberQN = qnameNS("number"); booleanQN = qnameNS("boolean"); nullQN = qnameNS("null"); keyQN = qname("key"); escapedQN = qname("escaped"); escapedKeyQN = qname("escaped-key"); if (validate) { // Note, we do not actually perform schema validation, because we assume the XML we are generating // is valid. Instead, we just set type annotations "on trust", as if we were validating. // Currently this means we aren't detecting duplicate keys, which would cause validation to fail. // The spec needs clarification in this area. try { Configuration config = context.getConfiguration(); //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (config) { config.checkLicensedFeature(Configuration.LicenseFeature.SCHEMA_VALIDATION, "validation", -1); if (!config.isSchemaAvailable(JSON_NS)) { InputSource is = StandardEntityResolver.getInstance().resolveEntity(null, SCHEMA_URI); if (config.isTiming()) { config.getLogger().info("Loading a schema from resources for: " + JSON_NS); } config.addSchemaSource(new SAXSource(is)); } } String typeNames[] = {"mapType", "arrayType", "stringType", "numberType", "booleanType", "nullType", "mapWithinMapType", "arrayWithinMapType", "stringWithinMapType", "numberWithinMapType", "booleanWithinMapType", "nullWithinMapType"}; for (String t : typeNames) { setType(t, config.getSchemaType(new StructuredQName(PREFIX, JSON_NS, t))); } } catch (SchemaException e) { throw new XPathException(e); } catch (SAXException e) { throw new XPathException(e); } catch (IOException e) { throw new XPathException(e); } } } /** * Record a SchemaType for a particular name * * @param name the name to be used for the type, e.g. "arrayType" * @param st the schema type to be used for typing such entities */ public void setType(String name, SchemaType st) { types.put(name, st); } /** * Set the key to be written for the next entry in an object/map * * @param unEscaped the key for the entry (null implies no key) in unescaped form (backslashes, * if present, do not signal an escape sequence) * @param reEscaped the key for the entry (null implies no key) in reescaped form. In this form * special characters are represented as backslash-escaped sequences if the escape * option is yes; if escape=no, the reEscaped form is the same as the unEscaped form. * @return true if the key is already present in the map, false if it is not */ public boolean setKey(String unEscaped, String reEscaped) { this.keys.push(unEscaped); return checkForDuplicates && !mapKeys.peek().add(reEscaped); } /** * Return the complete parsed result * * @return the XML document for this JSON * @throws XPathException */ public Item getResult() throws XPathException { out.endDocument(); out.close(); return builder.getCurrentRoot(); } /** * Check whether a string contains an escape sequence * * @param literal the string to be checked * @return true if the string contains a backslash */ protected boolean containsEscape(String literal) { return literal.indexOf('\\') >= 0; } private boolean isInMap() { return !inMap.isEmpty() && inMap.peek(); } /** * Start an element in the result tree * * @param qn the QName of the element * @param typeName the string name of the type to use * @throws XPathException if a dynamic error occurs */ private void startElement(FingerprintedQName qn, String typeName) throws XPathException { startElement(qn, types.get(typeName)); } /** * Start an element in the result tree, which will have a key attribute if a key has been pre-selected * * @param qn the QName of the element * @param st the schema type to use when validating - null implies untyped. * @throws XPathException if a dynamic error occurs */ private void startElement(FingerprintedQName qn, SchemaType st) throws XPathException { out.startElement(qn, validate && st != null ? st : UNTYPED, ExplicitLocation.UNKNOWN_LOCATION, 0); if (isInMap()) { String k = keys.pop(); k = reEscape(k, true); if (escape) { markAsEscaped(k, true); } out.attribute(keyQN, validate ? STRING_TYPE : SIMPLE_TYPE, k, ExplicitLocation.UNKNOWN_LOCATION, 0); } } /** * Write characters as a text node in the tree * * @param s the string to be added * @throws XPathException if a dynamic error occurs */ private void characters(String s) throws XPathException { out.characters(s, ExplicitLocation.UNKNOWN_LOCATION, 0); } /** * End the current element in the treel * * @throws XPathException if a dynamic error occurs */ private void endElement() throws XPathException { out.endElement(); } /** * Open a new array * * @throws XPathException if a dynamic error occurs */ public void startArray() throws XPathException { startElement(arrayQN, isInMap() ? "arrayWithinMapType" : "arrayType"); inMap.push(false); } /** * Close the current array * * @throws XPathException if a dynamic error occurs */ public void endArray() throws XPathException { inMap.pop(); endElement(); } /** * Start a new object/map * * @throws XPathException if a dynamic error occurs */ public void startMap() throws XPathException { startElement(mapQN, isInMap() ? "mapWithinMapType": "mapType"); if (checkForDuplicates) { mapKeys.push(new HashSet()); } inMap.push(true); } /** * Close the current object/map * * @throws XPathException if a dynamic error occurs */ public void endMap() throws XPathException { inMap.pop(); if (checkForDuplicates) { mapKeys.pop(); } endElement(); } /** * Write a numeric value * * @param asString the string representation of the value * @param asDouble the double representation of the value * @throws XPathException if a dynamic error occurs */ public void writeNumeric(String asString, double asDouble) throws XPathException { startElement(numberQN, isInMap() ? "numberWithinMapType" : "numberType"); characters(asString); endElement(); } /** * Write a string value * * @param val the string to be written. This will be in unescaped form if unescaping was requested * in the flags, otherwise it may contain JSON escape sequences * @throws XPathException if a dynamic error occurs */ public void writeString(String val) throws XPathException { startElement(stringQN, isInMap() ? "stringWithinMapType" : "stringType"); CharSequence escaped = reEscape(val, false); if (escape) { markAsEscaped(escaped, false); } characters(escaped.toString()); endElement(); } @Override protected void markAsEscaped(CharSequence escaped, boolean isKey) throws XPathException { if (containsEscape(escaped.toString()) && escape) { NodeName name = isKey ? escapedKeyQN : escapedQN; out.attribute(name, validate ? BOOLEAN_TYPE : SIMPLE_TYPE, "true", ExplicitLocation.UNKNOWN_LOCATION, 0); } } /** * Write a boolean value * * @param value the boolean value to be written * @throws XPathException if a dynamic error occurs */ public void writeBoolean(boolean value) throws XPathException { startElement(booleanQN, isInMap() ? "booleanWithinMapType" : "booleanType"); characters(Boolean.toString(value)); endElement(); } /** * Write a null value * * @throws XPathException if a dynamic error occurs */ public void writeNull() throws XPathException { startElement(nullQN, isInMap() ? "nullWithinMapType" : "nullType"); endElement(); } } // Copyright (c) 2014-2015 Saxonica Limited. All rights reserved.




© 2015 - 2025 Weber Informatics LLC | Privacy Policy