package org.basex.query.util.json; import static org.basex.data.DataText.*; import static org.basex.query.util.Err.*; import static org.basex.util.Token.*; import org.basex.query.QueryException; import org.basex.query.item.ANode; import org.basex.query.item.FAttr; import org.basex.query.item.FElem; import org.basex.query.item.FTxt; import org.basex.query.item.QNm; import org.basex.query.util.*; import org.basex.util.InputInfo; import org.basex.util.Util; import org.basex.util.XMLToken; import org.basex.util.hash.TokenObjMap; /** * <p>This class converts a <a href="http://jsonml.org">JsonML</a> * document to XML. * The specified JSON input is first transformed into a tree representation * and then converted to an XML document. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class JsonMLConverter extends XMLConverter { /** Cached names. */ private final TokenObjMap<QNm> qnames = new TokenObjMap<QNm>(); /** * Constructor. * @param ii input info */ public JsonMLConverter(final InputInfo ii) { super(ii); } @Override public ANode parse(final byte[] q) throws QueryException { // create and return XML fragment return create(new JSONParser(q, input).parse()); } /** * Converts the JSON tree to XML. * @param value node to be converted * @return root node * @throws QueryException query exception */ private FElem create(final JValue value) throws QueryException { return elem((JArray) check(value, T_ARRAY, "element constructor")); } /** * Converts an element node. * @param value node to be converted * @return root node * @throws QueryException query exception */ private FElem elem(final JArray value) throws QueryException { FElem elem = null; boolean txt = false; for(int s = 0; s < value.size(); s++) { final JValue val = value.value(s); if(s == 0) { final JString str = (JString) check(val, T_STRING, "element name"); elem = new FElem(qname(str.value)); } else if(s == 1 && val instanceof JObject) { attr(elem, (JObject) val); } else if(val instanceof JArray) { elem.add(elem((JArray) val)); txt = false; } else if(val instanceof JString) { if(txt) error("No subsequent texts allowed"); txt = true; elem.add(new FTxt(((JString) val).value)); } else { error("No % allowed at this stage", val.type()); } } if(elem == null) error("No element name specified in array"); return elem; } /** * Converts attributes. * @param elem root node * @param attr attributes * @throws QueryException query exception */ private void attr(final FElem elem, final JObject attr) throws QueryException { for(int s = 0; s < attr.size(); s++) { final JString v = (JString) check(attr.value(s), T_STRING, "attribute value"); elem.add(new FAttr(qname(attr.name(s)), v.value)); } } /** * Returns a cached {@link QNm} instance for the specified name. * @param name name * @return cached QName * @throws QueryException query exception */ private QNm qname(final byte[] name) throws QueryException { // retrieve name from cache, or create new instance QNm qname = qnames.get(name); if(qname == null) { if(!XMLToken.isNCName(name)) error("Invalid name: \"%\"", name); qname = new QNm(name); qnames.add(name, qname); } return qname; } /** * Checks the type of the specified value. * @param val value to be checked * @param type expected type * @param ext error extension * @return checked value * @throws QueryException query exception */ private JValue check(final JValue val, final byte[] type, final String ext) throws QueryException { if(!eq(val.type(), type)) error("% expected for %, % found", type, ext, val.type()); return val; } /** * Raises an error with the specified message. * @param msg error message * @param ext error details * @throws QueryException query exception */ private void error(final String msg, final Object... ext) throws QueryException { throw JSONMLPARSE.thrw(input, Util.inf(msg, ext)); } }