Просмотр исходного кода

HPCC-10034 Move all JSON validation and pretty printing to client side

Disable or remove JSON pretty printing in roxie and WsECL for
efficiency over the wire.

Rewrite the WsECL JSON test page to use dojo to do AJAX calls
and handle html events.  Add client side JSON validation and pretty
printing.

Signed-off-by: Anthony Fishbeck <anthony.fishbeck@lexisnexis.com>
Anthony Fishbeck 11 лет назад
Родитель
Сommit
da94c5bf4b

+ 2 - 3
common/thorhelper/roxiehelper.cpp

@@ -1084,9 +1084,8 @@ void FlushingStringBuffer::incrementRowCount()
 
 void FlushingJsonBuffer::encodeXML(const char *x, unsigned flags, unsigned len, bool utf8)
 {
-    StringBuffer t;
-    ::encodeJSON(t, x);
-    append(t.length(), t.str());
+    CriticalBlock b(crit);
+    appendJSONValue(s, NULL, len, x);
 }
 
 void FlushingJsonBuffer::startDataset(const char *elementName, const char *resultName, unsigned sequence, bool _extend)

+ 30 - 60
esp/services/ws_ecl/ws_ecl_service.cpp

@@ -756,11 +756,6 @@ static void buildReqXml(StringStack& parent, IXmlType* type, StringBuffer& out,
     }
 }
 
-inline void indenter(StringBuffer &s, int count)
-{
-    s.appendN(count*3, ' ');
-}
-
 IException *MakeJSONValueException(int code, const char *start, const char *pos, const char *tail, const char *intro="Invalid json format: ")
 {
      StringBuffer s(intro);
@@ -831,8 +826,6 @@ StringBuffer &appendJSONNumericString(StringBuffer &s, const char *value, bool a
     return s;
 }
 
-inline const char *jsonNewline(unsigned flags){return ((flags & REQXML_ESCAPEFORMATTERS) ? "\\n" : "\n");}
-
 typedef enum _JSONFieldCategory
 {
     JSONField_String,
@@ -855,9 +848,8 @@ JSONField_Category xsdTypeToJSONFieldCategory(const char *xsdtype)
     return JSONField_String;
 }
 
-static void buildJsonAppendValue(StringStack& parent, IXmlType* type, StringBuffer& out, const char* tag, const char *value, unsigned flags, int &indent)
+static void buildJsonAppendValue(StringStack& parent, IXmlType* type, StringBuffer& out, const char* tag, const char *value, unsigned flags)
 {
-    indenter(out, indent);
     if (tag && *tag)
         out.appendf("\"%s\": ", tag);
     StringBuffer sample;
@@ -889,16 +881,12 @@ static void buildJsonAppendValue(StringStack& parent, IXmlType* type, StringBuff
         out.append("null");
 }
 
-static void buildJsonMsg(StringStack& parent, IXmlType* type, StringBuffer& out, const char* tag, IPropertyTree *parmtree, unsigned flags, int &indent)
+static void buildJsonMsg(StringStack& parent, IXmlType* type, StringBuffer& out, const char* tag, IPropertyTree *parmtree, unsigned flags)
 {
     assertex(type!=NULL);
 
     if (flags & REQXML_ROOT)
-    {
         out.append("{");
-        out.append(jsonNewline(flags));
-        indent++;
-    }
 
     const char* typeName = type->queryName();
     if (type->isComplexType())
@@ -907,11 +895,9 @@ static void buildJsonMsg(StringStack& parent, IXmlType* type, StringBuffer& out,
             return; // recursive
 
         int startlen = out.length();
-        indenter(out, indent++);
         if (tag)
-            out.appendf("\"%s\": {", tag).append(jsonNewline(flags));
-        else
-            out.append("{").append(jsonNewline(flags));
+            appendJSONName(out, tag);
+        out.append('{');
         int taglen=out.length()+1;
         if (typeName)
             parent.push_back(typeName);
@@ -920,12 +906,10 @@ static void buildJsonMsg(StringStack& parent, IXmlType* type, StringBuffer& out,
             if (parmtree)
             {
                 const char *attrval = parmtree->queryProp(NULL);
-                indenter(out, indent);
                 out.appendf("\"%s\" ", (attrval) ? attrval : "");
             }
             else if (flags & REQXML_SAMPLE_DATA)
             {
-                indenter(out, indent);
                 out.append("\"");
                 type->queryFieldType(0)->getSampleValue(out,tag);
                 out.append("\" ");
@@ -940,19 +924,17 @@ static void buildJsonMsg(StringStack& parent, IXmlType* type, StringBuffer& out,
                 if (first)
                     first=false;
                 else
-                    out.append(",").append(jsonNewline(flags));
+                    out.append(',');
                 IPropertyTree *childtree = NULL;
                 const char *childname = type->queryFieldName(idx);
                 if (parmtree)
                     childtree = parmtree->queryPropTree(childname);
-                buildJsonMsg(parent, type->queryFieldType(idx), out, childname, childtree, flags & ~REQXML_ROOT, indent);
+                buildJsonMsg(parent, type->queryFieldType(idx), out, childname, childtree, flags & ~REQXML_ROOT);
             }
-            out.append(jsonNewline(flags));
         }
 
         if (typeName)
             parent.pop_back();
-        indenter(out, indent--);
         out.append("}");
     }
     else if (type->isArray())
@@ -969,14 +951,10 @@ static void buildJsonMsg(StringStack& parent, IXmlType* type, StringBuffer& out,
             parent.push_back(typeName);
 
         int startlen = out.length();
-        indenter(out, indent++);
         if (tag)
-            out.appendf("\"%s\": {%s", tag, jsonNewline(flags));
-        else
-            out.append("{").append(jsonNewline(flags));
-        indenter(out, indent++);
-        out.appendf("\"%s\": [", itemName).append(jsonNewline(flags));
-        indent++;
+            out.appendf("\"%s\": ", tag);
+        out.append('{');
+        out.appendf("\"%s\": [", itemName);
         int taglen=out.length();
         if (parmtree)
         {
@@ -991,14 +969,13 @@ static void buildJsonMsg(StringStack& parent, IXmlType* type, StringBuffer& out,
                     if (first)
                         first=false;
                     else
-                        out.append(",").append(jsonNewline(flags));
+                        out.append(",");
                     StringBuffer itempath;
                     itempath.append(itemName).append(idx);
                     IPropertyTree *itemtree = parmtree->queryPropTree(itempath.str());
                     if (itemtree)
-                        buildJsonMsg(parent,itemType,out, NULL, itemtree, flags & ~REQXML_ROOT, indent);
+                        buildJsonMsg(parent,itemType,out, NULL, itemtree, flags & ~REQXML_ROOT);
                 }
-                out.append(jsonNewline(flags));
             }
             else if (parmtree->hasProp(itemName))
             {
@@ -1009,10 +986,9 @@ static void buildJsonMsg(StringStack& parent, IXmlType* type, StringBuffer& out,
                     if (first)
                         first=false;
                     else
-                        out.append(",").append(jsonNewline(flags));
-                    buildJsonMsg(parent,itemType,out, NULL, &items->query(), flags & ~REQXML_ROOT, indent);
+                        out.append(",");
+                    buildJsonMsg(parent,itemType,out, NULL, &items->query(), flags & ~REQXML_ROOT);
                 }
-                out.append(jsonNewline(flags));
             }
             else
             {
@@ -1023,33 +999,30 @@ static void buildJsonMsg(StringStack& parent, IXmlType* type, StringBuffer& out,
                     items.appendList(s, "\n");
                     ForEachItemIn(i, items)
                     {
-                        delimitJSON(out, true, 0!=(flags & REQXML_ESCAPEFORMATTERS));
-                        buildJsonAppendValue(parent, type, out, NULL, items.item(i), flags & ~REQXML_ROOT, indent);
+                        delimitJSON(out, false);
+                        buildJsonAppendValue(parent, type, out, NULL, items.item(i), flags & ~REQXML_ROOT);
                     }
-                    out.append(jsonNewline(flags));
                 }
 
             }
         }
         else
-            buildJsonMsg(parent, itemType, out, NULL, NULL, flags & ~REQXML_ROOT, indent);
+            buildJsonMsg(parent, itemType, out, NULL, NULL, flags & ~REQXML_ROOT);
 
-        indenter(out, indent--);
-        out.append("]").append(jsonNewline(flags));
+        out.append(']');
 
         if (typeName)
             parent.pop_back();
-        indenter(out, indent--);
         out.append("}");
     }
     else // simple type
     {
         const char *parmval = (parmtree) ? parmtree->queryProp(NULL) : NULL;
-        buildJsonAppendValue(parent, type, out, tag, parmval, flags, indent);
+        buildJsonAppendValue(parent, type, out, tag, parmval, flags);
     }
 
     if (flags & REQXML_ROOT)
-        out.append(jsonNewline(flags)).append("}");
+        out.append('}');
 
 }
 
@@ -1283,8 +1256,6 @@ StringBuffer &appendEclXsdName(StringBuffer &content, const char *name, bool ist
 
 void appendEclXsdStartTag(StringBuffer &content, IPropertyTree *element, int indent, const char *attrstr=NULL, bool forceclose=false)
 {
-    //while (indent--)
-    //  content.append('\t');
     const char *name = element->queryName();
     appendEclXsdName(content.append('<'), name);
     if (strieq(name, "xs:element"))
@@ -1888,8 +1859,7 @@ void CWsEclBinding::getWsEclJsonRequest(StringBuffer& jsonmsg, IEspContext &cont
             if (type)
             {
                 StringStack parent;
-                int indent=0;
-                buildJsonMsg(parent, type, jsonmsg, wsinfo.queryname.sget(), parmtree, flags|REQXML_ROOT|REQXML_ESCAPEFORMATTERS, indent);
+                buildJsonMsg(parent, type, jsonmsg, wsinfo.queryname.sget(), parmtree, flags|REQXML_ROOT);
             }
         }
     }
@@ -1923,25 +1893,25 @@ void CWsEclBinding::getWsEclJsonResponse(StringBuffer& jsonmsg, IEspContext &con
         if (node->hasProp("Result"))
             node = node->queryPropTree("Result");
 
-        jsonmsg.appendf("{\n  \"%s\": {\n", element.str());
+        jsonmsg.appendf("{\"%s\": {", element.str());
         Owned<IPropertyTreeIterator> exceptions = node->getElements("Exception");
         Owned<IPropertyTreeIterator> datasets = node->getElements("Dataset");
         if (wuid && *wuid)
-            appendJSONValue(jsonmsg.pad(3), "Wuid", wuid);
+            appendJSONValue(jsonmsg, "Wuid", wuid);
         if ((!exceptions || !exceptions->first()) && (!datasets || !datasets->first()))
         {
             jsonmsg.append("  }\n}");
             return;
         }
 
-        appendJSONName(jsonmsg.pad(3), "Results").append("{\n");
+        appendJSONName(jsonmsg, "Results").append("{\n");
         if (exceptions && exceptions->first())
         {
-            appendJSONName(jsonmsg.pad(3), "Exceptions").append("{\n");
-            appendJSONName(jsonmsg.pad(4), "Exception").append("[\n");
+            appendJSONName(jsonmsg, "Exceptions").append("{");
+            appendJSONName(jsonmsg, "Exception").append("[");
             ForEach(*exceptions)
-                appendJSONExceptionItem(jsonmsg.pad(2), exceptions->query().getPropInt("Code"), exceptions->query().queryProp("Message"), NULL, NULL);
-            jsonmsg.append("\n   ]\n    }\n");
+                appendJSONExceptionItem(jsonmsg, exceptions->query().getPropInt("Code"), exceptions->query().queryProp("Message"), NULL, NULL);
+            jsonmsg.append("]}");
         }
 
         ForEach(*datasets)
@@ -1961,15 +1931,15 @@ void CWsEclBinding::getWsEclJsonResponse(StringBuffer& jsonmsg, IEspContext &con
                         if (type)
                         {
                             StringStack parent;
-                            int indent=4;
                             StringBuffer outname(dsname);
-                            buildJsonMsg(parent, type, jsonmsg, outname.replace(' ', '_').str(), &ds, 0, indent);
+                            delimitJSON(jsonmsg);
+                            buildJsonMsg(parent, type, jsonmsg, outname.replace(' ', '_').str(), &ds, 0);
                         }
                     }
                 }
             }
         }
-        jsonmsg.append("    }\n  }\n}");
+        jsonmsg.append("}}}");
     }
     catch (IException *e)
     {

+ 133 - 644
esp/xslt/wsecl3_jsontest.xsl

@@ -23,660 +23,149 @@
 ]>
 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"
     exclude-result-prefixes="fo">
-    <xsl:output method="html" indent="yes" omit-xml-declaration="yes" 
-      doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"/> 
+    <xsl:output method="html" indent="yes" omit-xml-declaration="yes"
+      doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"/>
     <!-- ===============================================================================
-  parameters 
+  parameters
   ================================================================================ -->
-    <xsl:param name="pageName" select="'SOAP Test'"/>
+    <xsl:param name="pageName" select="'JSON Test'"/>
     <xsl:param name="serviceName" select="'FormTest'"/>
     <xsl:param name="methodName" select="'BasicTest'"/>
-    <xsl:param name="wuid"/>
     <xsl:param name="destination" select="'zz'"/>
     <xsl:param name="header" select="'xx'"/>
-    <xsl:param name="inhouseUser" select="false()"/>
-    <xsl:param name="showhttp" select="false()"/>
     <!-- ===============================================================================-->
     <xsl:template match="/">
     <html>
-        <head>
-            <title>Soap Test Page</title>
-                <link rel="shortcut icon" href="/esp/files/img/affinity_favicon_1.ico" />
-                <link rel="stylesheet" type="text/css" href="/esp/files/yui/build/fonts/fonts-min.css" />
-                <link rel="stylesheet" type="text/css" href="/esp/files/css/espdefault.css" />
-
-                <script type="text/javascript" src="/esp/files/get_input.js"/>
-                <script type="text/javascript" src="/esp/files/stack.js"/>
-                <script type="text/javascript" src="/esp/files/stringbuffer.js"/>
-
-<script type="text/javascript">
+      <head>
+        <title>JSON Test Page</title>
+        <link rel="shortcut icon" href="/esp/files/img/affinity_favicon_1.ico" />
+        <link rel="stylesheet" type="text/css" href="/esp/files/yui/build/fonts/fonts-min.css" />
+        <link rel="stylesheet" type="text/css" href="/esp/files/css/espdefault.css" />
+
+        <script>dojoConfig = {async:true, parseOnLoad:false}</script>
+        <script src='/esp/files/dojo/dojo.js'></script>
+        <script type="text/javascript">
 <xsl:text disable-output-escaping="yes">
-<![CDATA[ 
-  var xmlhttp = null;
-
-function isSpace(ch) {
-    if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') 
-        return true;
-    return false;
-}
- 
-// return true if succeeded
-function loadJsonDoc(url, user, passwd)
-{
-  // code for Mozilla, etc.
-   if (window.XMLHttpRequest)  {
-     xmlhttp=new XMLHttpRequest();
-  }
-  // code for IE
-  else if (window.ActiveXObject)  {
-     try {
-        xmlhttp=new ActiveXObject("Msxml2.XMLHTTP.4.0");
-    } catch (e) {
-       try {
-           xmlhttp=new ActiveXObject("Msxml2.XMLHTTP");
-      } catch (e) {
-         xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
-     }
-   }
-    if (xmlhttp == null) {
-      alert("Can not create XMLHTTP in IE");
-      return false;
-    }
-  }
-  
-   if (xmlhttp)  {
-     xmlhttp.onreadystatechange = xmlhttpChange;
-//   alert("url: "+url);
-     xmlhttp.open("POST",url,true, user, passwd);
-
-     //Set headers
-     try {
-        var header = document.getElementById("req_header").value;
-        var lines = header.split('\n');
-        for (var i = 0; i<lines.length;i++) {
-            var line = lines[i];
-            var idx = line.indexOf(':');
-            if (idx <= 0) {
-                alert("Invalid header line: "+line);
-                return false;
-           }
-           //alert("Set header: " +   line.substring(0, idx) + "=" +line.substring(idx+1,line.length));
-           xmlhttp.setRequestHeader(line.substring(0, idx), line.substring(idx+1,line.length));
-        }
-     } catch (e) {
-         alert("Exception when setRequestHeader(): "+e);
-     }
-    
- //    alert("Request: "+document.getElementById("req_body").value);
-     var jsonmsg = document.getElementById("req_body").value;
-//    alert("Trying: url="+url+"\nuser="+user+"\npasswd="+passwd+"\nxml="+xml);          
-      var button = document.getElementById("sendButton");
-      if (button)
-      {
-          button.value = "Please wait ...";
-          button.disabled = true;
-     }
-
-      document.getElementById("body").style.cursor = "wait";
-      xmlhttp.send(jsonmsg);
-  }
-  
-   return true;
-}
-
-function xmlEncode(val) 
-{
-    return val.toString().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");    
-}
-
-function getAttrs(node)
-{
-}
-
-function allSpaces(s)
-{
-   if (s==null) return true;
-   for (var i=0; i<s.length; i++)
-      if ( !isSpace(s.charAt(i)) )
-        return false;
-   return true;
-}
-
-function toXML2(buf, tree)
-{
-      //alert("toXML2: name="+tree.tagName + "; type="+tree.nodeType);
-      switch (tree.nodeType)
-      { 
-      case 1: // document.ELEMENT_NODE:
-           var count = tree.childNodes.length;
-           if (count==0)  {
-                 buf.push("<" + tree.tagName + getAttrs(tree) + "/>");
-           } else  {
-               var val = "";
-               var started = false;
-               for (var i=0; i<count; i++) 
-               {
-                   var node = tree.childNodes[i];
-                   
-                    switch (node.nodeType)
-                    {
-                    case 3: // document.TEXT_NODE:
-                      if (!allSpaces(node.nodeValue))
-                        val += xmlEncode(node.nodeValue);
-                     //else  alert("Ignore text node: ["+node.nodeValue+"]");
-                     break;
-                    case 8: // document.COMMENT_NODE:
-                       val += "<!" + "--"+node.nodeValue+"--" + ">";
-                       break;
-                  case 4: // document.CDATA_SECTION_NODE:
-                        val += "<![" + "CDATA[" + node.nodeValue + "]]" + ">";                  
-                        break;
-               case 7: // document.PROCESSING_INSTRUCTION_NODE:
-                     val += "<?"+node.target + " " + node.data + "?>";
-                     break;
-               case 1: // document.ELEMENT_NODE:
-                    if (!started) {
-                             buf.push( "<"+tree.tagName+getAttrs(tree)+">");                        
-                             started = true;
-                        } 
-                        if (val.length>0) {
-                           buf.push(val); 
-                           val = '';
-                        }
-                       toXML2(buf, node);
-                       break;
-                       
-                    default:
-                        alert("Unhandled node [1]: <" + node.tagName + ">; type: " + node.nodeType );
-                  }
-               }
-
-               if (!started)
-                     buf.push( "<"+tree.tagName+getAttrs(tree) + ">");
-                  buf.push(val + "</" + tree.tagName + ">");               
-         }
-         break;
-         
-      case 7: // document.PROCESSING_INSTRUCTION_NODE:
-         buf.push( "<?"+tree.target + " " + tree.data + "?>");                 
-         break;
-         
-      case 8: // document.COMMENT_NODE:
-         buf.push("<!" + "--"+node.nodeValue+"--" + ">");
-         break;
-          
-      defailt: 
-           alert("Unhandled node [2]: <" + tree.tagName + ">; type: " + tree.nodeType );
-      }     
-      
-      //alert("buf = " + buf.join('|'));
-}
-
-function toXML(tree)
-{
-     var buf = new Array();
-     if (tree)
-         toXML2(buf,tree);
- 
-     return buf.join('');
-}
-
-function toXMLIndented2(buf, tree, indent)
-{
-      //alert("toXML2: name="+tree.tagName + "; type="+tree.nodeType);
-      switch (tree.nodeType)
-      { 
-      case 1: // document.ELEMENT_NODE:
-           var count = tree.childNodes.length;
-           if (count==0)  {
-                 buf.push(indent + "<" + tree.tagName + getAttrs(tree) + "/>");
-           } else  {
-               var val = "";
-               var started = false;
-               for (var i=0; i<count; i++) 
-               {
-                   var node = tree.childNodes[i];
-                   
-                  switch (node.nodeType)
-                  {
-                  case 3: // document.TEXT_NODE:
-                    if (!allSpaces(node.nodeValue))
-                       val += xmlEncode(node.nodeValue);
-                    //alert("Text node: ["+node.nodeValue+"]");
-                    break;
-                   case 8: // document.COMMENT_NODE:
-                       val += "<!" + "--"+node.nodeValue+"--" + ">";
-                       break;
-                case 4: // document.CDATA_SECTION_NODE:
-                        val += "<![" + "CDATA[" + node.nodeValue + "]]" + ">";                  
-                        break;
-               case 7: // document.PROCESSING_INSTRUCTION_NODE:
-                     if (val.length==0) 
-                         val += indent + " <?"+node.target + " " + node.data + "?>\n";
-                     else // in mixed content environment
-                         val += "<?"+node.target + " " + node.data + "?>";
-                     break;
-               case 1: // document.ELEMENT_NODE:
-                    if (!started) {
-                       buf.push( indent + "<"+tree.tagName+getAttrs(tree) + ">");
-                       started = true;
-                    }
-                     
-                     if (val.length>0) {
-                        buf.push(val);
-                        val = '';
-                     }      
-                       toXMLIndented2(buf, node,indent+' ');
-                       break;
-                       
-                 default:
-                        alert("Unhandled node [1]: <" + node.tagName + ">; type: " + node.nodeType );
+<![CDATA[
+          require(["dojo/ready", "dojo/request/handlers", "dojo/request/xhr", "dojo/json", "dojo/dom", "dojo/on", "dojo/domReady!"],
+          function(ready, handlers, xhr, JSON, dom, on){
+            ready(function(){
+              var jsonreq = ']]></xsl:text><xsl:value-of select="/srcxml/jsonreq"/><xsl:text disable-output-escaping="yes"><![CDATA[';
+              try {
+                var obj = JSON.parse(jsonreq);
+                var output = JSON.stringify(obj, undefined, 3);
+                dom.byId("req_body").value = output;
+              } catch (err){
+                dom.byId("req_body").value = err.toString() + ": \n\n" + jsonreq;
+              };
+            });
+            handlers.register("json_show_headers", function(response){
+              dom.byId("resp_header").value = "Content-Type: " + response.getHeader("Content-Type");
+              return JSON.parse(response.text);
+            });
+            on(dom.byId("sendButton"), "click", function(){
+              dom.byId("resp_body").value = "";
+              var jsonreq = dom.byId("req_body").value;
+              if (dom.byId("check_req").checked)
+              {
+                try {
+                  JSON.parse(jsonreq);
+                } catch (err){
+                  alert(err.toString() + ": \n\n" + jsonreq);
+                  return;
                 }
-               }
-                
-                if (!started)
-                     buf.push( indent + "<"+tree.tagName+getAttrs(tree) + ">" + val + "</" + tree.tagName + ">");
-                  else
-                     buf.push(indent + val + "</" + tree.tagName + ">");               
-         }
-         break;
-         
-      case 7: // document.PROCESSING_INSTRUCTION_NODE:
-         buf.push(indent+ "<?"+tree.target + " " + tree.data + "?>");                 
-         break;
-         
-      case 8: // document.COMMENT_NODE:
-         buf.push("<!" + "--"+node.nodeValue+"--" + ">");
-         break;
-          
-      defailt: 
-           alert("Unhandled node [2]: <" + tree.tagName + ">; type: " + tree.nodeType );
-      }     
-      
-      //alert("buf = " + buf.join('|'));
-}
-
-function toXMLIndented(tree)
-{
-     var buf = new Array();
-     if (tree)
-         toXMLIndented2(buf,tree,"");
- 
-     return buf.join('\n');
-}
-
-function setResponseBodyHeader()
-{
-      document.getElementById("resp_body").value = xmlhttp.responseText;
-      document.getElementById("resp_header").value = xmlhttp.getAllResponseHeaders();
-      if (xmlhttp.responseXML && xmlhttp.responseXML.parseError && xmlhttp.responseXML.parseError.errorCode!=0)
-      {
-          var parseError = xmlhttp.responseXML.parseError;
-          alert("\nError in line " + parseError.line + "\nposition " + parseError.linePos + "\nError Code: " + parseError.errorCode + "\nError Reason: " + parseError.reason + "\nError Line: " + parseError.srcText);
-    }
-
-}
-
-function xmlhttpChange()
-{
-   // if xmlhttp shows "loaded"
-  if (xmlhttp.readyState==4)
-  {
-     var button = document.getElementById("sendButton");
-      if (button)
-      {
-          button.value = "Send Request";
-          button.disabled = false;
-     }
-
-     document.getElementById("body").style.cursor = "default";
-
-    // if "OK"
-    if (xmlhttp.status==200)
-        setResponseBodyHeader();
-    else
-    {
-         setResponseBodyHeader();
-
-         var msg = "Problem occurred in response:\n ";
-         msg += "Status Code: " + xmlhttp.status+"\n";
-         msg += "Messsage: "+xmlhttp.statusText + "\n";
-         msg += "Response: see Response Body";
-         alert(msg);         
-    }
-  }
-}
-
-function onSendRequest()
-{
-    // clear
-    document.getElementById("resp_body").value = "";
-    document.getElementById("resp_header").value = "";
-    
-    var url = "]]></xsl:text><xsl:value-of disable-output-escaping="yes" select="$destination"/><xsl:text disable-output-escaping="yes"><![CDATA[";
-    var user = document.getElementById("username").value;
-    var passwd = document.getElementById("password").value;
-    loadJsonDoc(url,user,passwd);
-    return true;
-}
-
-//-------------------------------------------------------------------
-
-// mozilla only
-function checkForParseError (xmlDocument) 
-{
-    var errorNamespace = 'http://www.mozilla.org/newlayout/xml/parsererror.xml';
-    var documentElement = xmlDocument.documentElement;
-    var parseError = { errorCode : 0 };
-    if (documentElement.nodeName == 'parsererror' &&
-        documentElement.namespaceURI == errorNamespace) {
-          parseError.errorCode = 1;
-         var sourceText = documentElement.getElementsByTagNameNS(errorNamespace, 'sourcetext')[0];
-         if (sourceText != null) {
-           parseError.srcText = sourceText.firstChild.data
-        }
-        parseError.reason = documentElement.firstChild.data;
-    }
-    return parseError;
-}
-
-function parseXmlString(xml)
-{
-   var xmlDoc = null;
-   
-   try {
-      var dom = new DOMParser();      
-      xmlDoc = dom.parseFromString(xml, 'text/xml'); 
-      var error = checkForParseError(xmlDoc);
-      if (error.errorCode!=0)
-      {
-         alert(error.reason + "\n" + error.srcText);
-         return null;
-      }
-   } catch (e) {
-     try {
-        xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
-        xmlDoc.async="false";
-        xmlDoc.loadXML(xml);
-        if (xmlDoc.parseError != 0)
-        {
-          alert("XML Parse Error: " + xmlDoc.parseError.reason);
-          return null;
-        }
-     } catch(e) {
-        alert("Error: " + e.name + "\n" + e.message);
-        return null;
-     }
-  }
-     
-   if (!xmlDoc)
-     alert("Create xmlDoc failed! You browser is not supported yet.");
-
-   return xmlDoc;
-}
-
-function getRootElement(xmlDom)
-{
-   var root = null;
-   if (xmlDom)
-   {
-     root = xmlDom.firstChild;
-     if (root && root.nodeType!=1) // IE treat <?xml ?> as first child
-     {
-         //alert("firstChild type = " + root.nodeType);
-      root = xmlDom.childNodes[1]; 
-     }
-   }
-   
-   return root;
-}
-
-function isBlank(s)
-{
-   var len = s ? s.length : 0;
-   for (var i=0; i<len; i++)
-   {
-       var ch = s.charAt(i);
-       if (ch != ' ' && ch != '\t' && ch!='\n')
-          return false;
-   }
-   return true;
-}
-
-//-------------------------------------------------------------------
-
-function hasAttr(tree)
-{
-    // some browser (such as IE) does not support hasAttributes()
-    if (tree.hasAttributes)
-        return tree.hasAttributes();
-    else
-        return tree.attributes!=null && tree.attributes.length>0;
-}
-
-function removeEmptyNodes(tree)
-{
-//     alert("Node type: " + tree.nodeType + "\nNode value:"+tree.nodeValue+"\nTag name:"+tree.tagName);
-
-      if (tree.nodeType==1) // ELEMENT_NODE
-      {
-          var count = tree.childNodes.length;
-          if (count==0 && !hasAttr(tree))  
-             return null;
-          for (var i = count-1; i>=0; i--)  // do backward so the remove would not invalid the list
-          {
-              var node = tree.childNodes[i];
-              var newnode = removeEmptyNodes(node);
-              if (!newnode)
-                  tree.removeChild(node);
-              else
-                  tree.replaceChild(newnode,node);
-         }
-         return (tree.hasChildNodes()  || hasAttr(tree)) ? tree : null;
-    } else if (tree.nodeType==3) {
-        if ( (isBlank(tree.nodeValue) || tree.nodeValue=='0') && !hasAttr(tree) ) 
-          return null;
-   }
-
-    return tree;
-}
-
-function getFirstElementNode(tree)
-{
-      if (tree)
-      {
-          var nodes = tree.childNodes;
-        for (var i=0; i<nodes.length; i++)
-        {
-             var node = nodes[i];
-             if (node.nodeType == 1)
-                return node;
-          }
-     }
-      
-     return null;           
-}
-
-function clearEmptySoapFields(doc, root)
-{
-}
-
-function clearEmptyFields(xml)
-{
-}
-
-function onClearEmptyFields()
-{
-}
-
-function prettifyXML(inXML)
-{
-}
-
-function prettifyXMLDom(doc)
-{
-}
-function onPrettifyXML(txtCtrl, chkCtrl)
-{
-}
- 
-function constructXmlFromConciseForm(txt)
-{
-}
- 
-function inputReturnMethod()
-{
-    var txt = gWndObj.value;
-    gWndObjRemoveWatch();
-    
-    if (txt!=null && txt!="")
-    {  
-       var xml = constructXmlFromConciseForm(txt)
-       
-       var tagLen = gMethodName.length+2;
-       if ( xml.substr(0, tagLen) != ('<'+gMethodName+'>') ) {
-           alert("The request must starts with <"+gMethodName+">");
-           return;
-      }
-      
-      var head = '<?xml version="1.0" encoding="UTF-8"?>'
-             + '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"'
-             + ' xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"'
-             + ' xmlns="urn:LNHPCC:' + gServiceName + '">'
-             + ' <soap:Body><'
-             + gMethodName + 'Request>';
-        var end = '</' + gMethodName + 'Request></soap:Body></soap:Envelope>';        
-        xml = head + xml.substring(tagLen, xml.length-tagLen-1) + end;
-        document.getElementById('req_body').value =  xml;
-        
-        /*TODO: request prettify_req. Why this does not work??
-        var checked = document.getElementById('prettify_req').checked;
-        if (checked) { xml = prettifyXml(xml); }
-        alert(document.getElementById('req_body'));
-        document.getElementById('req_body').value =  xml;
-        alert(document.getElementById('req_body').value);
-        */
-     }
-     document.getElementById('import').disabled = false;
-}
-
-function onImportConciseRequest()
-{
-      document.getElementById('import').disabled = true;
-      showGetInputWnd('The Concise Request Text (from esp log):', 'inputReturnMethod()');     
-}
-
-var jsonreq = ']]></xsl:text><xsl:value-of select="/srcxml/jsonreq"/><xsl:text disable-output-escaping="yes"><![CDATA[';
-
-function setJsonReq()
-{
-     var ctrl = document.getElementById("req_body");
-     if (!ctrl) return;
-     ctrl.value = jsonreq;
-     return true;
-}
-
-]]></xsl:text>
-
-var gServiceName = "<xsl:value-of select="$serviceName"/>";
-var gMethodName = "<xsl:value-of select="$methodName"/>";;
-
-</script>
-        </head>
-    <body class="yui-skin-sam" id="body" onload="setJsonReq()">
-            <h3>
-                
-                    <table cellSpacing="0" cellPadding="1" width="100%" bgColor="#4775FF" border="0" >
-                        <tr align="left">
-                            <td height="23" bgcolor="000099" align="center"><font color="#ffffff"><b><xsl:value-of select="concat('  ', $pageName, '  ')"/></b></font></td>
-                            <td height="23" align="center"><font color="#ffffff"><b><xsl:value-of select="concat($serviceName, ' / ', $methodName)"/></b></font></td>
-                        </tr>
-                    </table>
-                
-            </h3>
-                    <b>&nbsp;&nbsp;Destination:  </b> <xsl:value-of select="$destination"/>
-                     <span id="auth_" style="display:none"> Username: <input type="text" name="username" id="username" value="" size="10"/>
-                            Password:   <input type="password" name="password" id="password" size="10"/>
-                    </span>
-
-            <p/> 
-            <xsl:if test="$showhttp">
-            </xsl:if>
-
-                      <hr/>                      
-             <table width="100%">
-                <tr><th align="left">Request: </th> <th align="left">Response: </th></tr>
-                <tr>
-                  <td width="50%">
-                 <table width="100%" border="0" cellspacing="0" cellpadding="1">
-                    <xsl:if test="$showhttp">
-                        <tr>
-                            <td> <b>Headers:</b> </td>
-                        </tr>
-                        <tr>
-                            <td>
-                                <textarea id="req_header" style="width:100%" rows="4"><xsl:value-of select="$header"/></textarea>
-                            </td>
-                        </tr>
-                    </xsl:if>
-                    <tr>
-                        <td> 
-                           <table width="100%">
-                            <tr> <!--td align="left"><b>Request Body:</b></td--> 
-                             <td align="right">
-                                 </td></tr>
-                           </table>   
-                        </td>
-                    </tr>
-                    <tr>
-                        <td>
-                            <textarea id="req_body" name="req_body" style="width:100%" rows="25" wrap="on"/>
-                        </td>
-                    </tr>
-                           </table>
-
-                          </td>
-                            
-                           <td width="50%">
-                  <table width="100%" border="0" cellspacing="0" cellpadding="1">
-                    <xsl:if test="$showhttp">
-                    <tr>
-                        <td> <b>Headers:</b> </td>
-                    </tr>
-                    <tr>
-                        <td>
-                            <textarea id="resp_header" cols="10" style="width:100%" rows="4" readonly="true"></textarea>
-                        </td>
-                    </tr>
-                    </xsl:if>                           
-                    <tr>
-                      <td>
-                       <table width="100%">
-                          <tr>
-                        <!--td> <b>Response Body:</b>  </td-->
-                        <td align="right"/>
-                        </tr>
-                       </table>
-                       </td>
-                    </tr>
-                    <tr>
-                        <td>
-                            <textarea id="resp_body" name="response" style="width:100%" rows="25" wrap="on" readonly="true"></textarea>
-                        </td>
-                    </tr>
-                 </table>
-                </td>
-                </tr>
-                    <tr>
-                        <td align="center" colspan="2"> 
-                            <input type="button" id="sendButton" value="Send Request" onclick="onSendRequest()"/>  <input type="checkbox" checked="true" id="check_req"> Check well-formness before send</input>
-                        </td>
-                    </tr>
-                
-                </table>
-
-             <!-- </form>  -->
-        </body>
-    </html>
+              }
+              xhr("]]></xsl:text><xsl:value-of disable-output-escaping="yes" select="$destination"/><xsl:text disable-output-escaping="yes"><![CDATA[", {
+                handleAs: "json_show_headers",
+                method: "POST",
+                data: jsonreq,
+                headers: { 'Content-Type': 'application/json' }
+              }).then(function(data){
+                dom.byId("resp_body").value = JSON.stringify(data, undefined, 3);
+              }, function(err){
+                dom.byId("resp_body").value = err.toString() + ": \n\n" + err.response.text;
+              });
+            });
+          });
+]]>
+</xsl:text>
+
+          var gServiceName = "<xsl:value-of select="$serviceName"/>";
+          var gMethodName = "<xsl:value-of select="$methodName"/>";;
+        </script>
+    </head>
+    <body class="yui-skin-sam" id="body">
+      <h3>
+        <table cellSpacing="0" cellPadding="1" width="100%" bgColor="#4775FF" border="0" >
+          <tr align="left">
+            <td height="23" bgcolor="000099" align="center"><font color="#ffffff"><b><xsl:value-of select="concat('  ', $pageName, '  ')"/></b></font></td>
+            <td height="23" align="center"><font color="#ffffff"><b><xsl:value-of select="concat($serviceName, ' s/s ', $methodName)"/></b></font></td>
+          </tr>
+        </table>
+      </h3>
+      <b>&nbsp;&nbsp;Destination:  </b> <xsl:value-of select="$destination"/>
+      <p/>
+      <hr/>
+      <table width="100%">
+      <tr><th align="left" id="req_label">Request: </th> <th align="left" id="resp_label">Response: </th></tr>
+      <tr>
+      <td width="50%">
+        <table width="100%" border="0" cellspacing="0" cellpadding="1">
+        <tr>
+          <td> <b>Headers:</b> </td>
+        </tr>
+        <tr>
+          <td>
+            <textarea id="req_header" style="width:100%" rows="4"><xsl:value-of select="$header"/></textarea>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <table width="100%">
+              <tr><td align="right"></td></tr>
+            </table>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <textarea id="req_body" name="req_body" style="width:100%" rows="25" wrap="on"/>
+          </td>
+        </tr>
+        </table>
+      </td>
+      <td width="50%">
+        <table width="100%" border="0" cellspacing="0" cellpadding="1">
+          <tr>
+            <td> <b>Headers:</b> </td>
+          </tr>
+          <tr>
+            <td>
+              <textarea id="resp_header" cols="10" style="width:100%" rows="4" readonly="true"></textarea>
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <table width="100%">
+                <tr><td align="right"/></tr>
+              </table>
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <textarea id="resp_body" name="response" style="width:100%" rows="25" wrap="on" readonly="true"></textarea>
+            </td>
+          </tr>
+        </table>
+      </td>
+    </tr>
+    <tr>
+      <td align="center" colspan="2">
+        <input type="button" id="sendButton" value="Send Request"/>
+        <input type="checkbox" checked="true" id="check_req"> Check well-formness before send</input>
+      </td>
+    </tr>
+    </table>
+  </body>
+</html>
    </xsl:template>
 </xsl:stylesheet>

+ 2 - 2
roxie/ccd/ccdcontext.cpp

@@ -3007,7 +3007,7 @@ public:
         StringBuffer responseHead, responseTail;
         appendfJSONName(responseHead, "%sResponse", queryName.get()).append(" {");
         appendJSONValue(responseHead, "sequence", seqNo);
-        appendJSONName(responseHead, "Results").append(" {\n ");
+        appendJSONName(responseHead, "Results").append(" {");
 
         unsigned len = responseHead.length();
         client->write(responseHead.detach(), len, true);
@@ -3027,7 +3027,7 @@ public:
                         break;
                     if (needDelimiter)
                     {
-                        StringAttr s(",\n "); //write() will take ownership of buffer
+                        StringAttr s(","); //write() will take ownership of buffer
                         size32_t len = s.length();
                         client->write((void *)s.detach(), len, true);
                         needDelimiter=false;

+ 4 - 1
roxie/ccd/ccdserver.cpp

@@ -19331,7 +19331,10 @@ public:
                 response->startDataset("Dataset", helper.queryName(), sequence, (helper.getFlags() & POFextend) != 0);
                 if (response->mlFmt==MarkupFmt_XML || response->mlFmt==MarkupFmt_JSON)
                 {
-                    writer.setown(createIXmlWriter(serverContext->getXmlFlags(), 1, response, (response->mlFmt==MarkupFmt_JSON) ? WTJSON : WTStandard));
+                    unsigned int writeFlags = serverContext->getXmlFlags();
+                    if (response->mlFmt==MarkupFmt_JSON)
+                        writeFlags |= XWFnoindent;
+                    writer.setown(createIXmlWriter(writeFlags, 1, response, (response->mlFmt==MarkupFmt_JSON) ? WTJSON : WTStandard));
                     writer->outputBeginArray("Row");
                 }
             }