You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@abdera.apache.org by jm...@apache.org on 2007/09/28 22:40:58 UTC

svn commit: r580481 - in /incubator/abdera/java/trunk/extensions/json/src/main/java/org/apache/abdera/ext/json: JSONStream.java JSONUtil.java

Author: jmsnell
Date: Fri Sep 28 13:40:58 2007
New Revision: 580481

URL: http://svn.apache.org/viewvc?rev=580481&view=rev
Log:
Additional refinements on the JSON output based on the feedback from the initial drop. Refactoring
to clean up the code. Pretty printing of the output to make it readable

Added:
    incubator/abdera/java/trunk/extensions/json/src/main/java/org/apache/abdera/ext/json/JSONStream.java
Modified:
    incubator/abdera/java/trunk/extensions/json/src/main/java/org/apache/abdera/ext/json/JSONUtil.java

Added: incubator/abdera/java/trunk/extensions/json/src/main/java/org/apache/abdera/ext/json/JSONStream.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/extensions/json/src/main/java/org/apache/abdera/ext/json/JSONStream.java?rev=580481&view=auto
==============================================================================
--- incubator/abdera/java/trunk/extensions/json/src/main/java/org/apache/abdera/ext/json/JSONStream.java (added)
+++ incubator/abdera/java/trunk/extensions/json/src/main/java/org/apache/abdera/ext/json/JSONStream.java Fri Sep 28 13:40:58 2007
@@ -0,0 +1,202 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.ext.json;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Date;
+import java.util.Stack;
+
+import javax.activation.MimeType;
+
+import org.apache.abdera.i18n.iri.IRI;
+import org.apache.abdera.util.EntityTag;
+
+public class JSONStream {
+
+  private final Writer writer;
+  private int depth = 0;
+  private Stack<Boolean> sepstack = new Stack<Boolean>();
+  
+  private void pushStack() {
+    sepstack.push(true);
+  }
+  
+  private boolean isStart() {
+    boolean b = sepstack.peek();
+    if (b) sepstack.set(sepstack.size()-1, false);
+    return b;
+  }
+  
+  private void popStack() {
+    sepstack.pop();
+  }
+  
+  public JSONStream(Writer writer) {
+    this.writer = writer;
+  }
+  
+  private void inc() {
+    depth++;
+  }
+  
+  private void dec() {
+    depth--;
+  }
+  
+  private void writeIndent() throws IOException {
+    for (int n = 0; n < depth; n++)
+      writer.write(' ');
+  }
+  
+  private void writeNewLine() throws IOException {
+    writer.write('\n');
+  }
+  
+  public void startObject() throws IOException {
+    writer.write('{');
+    inc();
+    pushStack();
+  }
+  
+  public void endObject() throws IOException {
+    popStack();
+    dec();
+    writeNewLine();
+    writeIndent();
+    writer.write('}');
+  }
+  
+  public void startArray() throws IOException {
+    writer.write('[');
+    inc();
+  }
+  
+  public void endArray() throws IOException {
+    dec();
+    writeNewLine();
+    writeIndent();
+    writer.write(']');
+  }
+
+  public void writeSeparator() throws IOException {
+    writer.write(',');
+  }
+  
+  private void writeColon() throws IOException {
+    writer.write(':');
+  }
+  
+  public void writeQuoted(String value) throws IOException {
+    writer.write('"');
+    writer.write(escape(value));
+    writer.write('"');
+  }
+
+  public void writeField(String name) throws IOException {
+    if (!isStart()) writeSeparator();
+    writeNewLine();
+    writeIndent();
+    writeQuoted(name);
+    writeColon();    
+  }
+
+  
+  
+  public void writeField(String name, Date value) throws IOException {
+    if (value != null) writeField(name, value.getTime());
+  }
+  
+  public void writeField(String name, IRI value) throws IOException {
+    if (value != null) writeField(name, value.toASCIIString());
+  }
+  
+  public void writeField(String name, MimeType value) throws IOException {
+    if (value != null) writeField(name, value.toString());
+  }
+  
+  public void writeField(String name, EntityTag value) throws IOException {
+    if (value != null) writeField(name, value.toString());
+  }
+  
+  public void writeField(String name, String value) throws IOException {
+    if (value != null) {
+      writeField(name);
+      writeQuoted(value);
+    }
+  }
+  
+  public void writeField(String name, Number value) throws IOException {
+    if (value != null) {
+      writeField(name);
+      writer.write(value.toString());
+    }
+  }
+
+  public void writeField(String name, Boolean value) throws IOException {
+    if (value != null) {
+      writeField(name);
+      writer.write(value.toString());
+    }
+  }
+  
+  private static String escape(String value) {
+    if (value == null) return null;
+    StringBuffer buf = new StringBuffer();
+    char[] chars = value.toCharArray();
+    char b = 0;
+    String t = null;
+    for (char c : chars) {
+      switch(c) {
+        case '\\':
+        case '"':
+          buf.append('\\');
+          buf.append(c);
+          break;
+        case '/':
+          if (b == '<') buf.append('\\');
+          buf.append(c);
+          break;
+        case '\b':
+          buf.append("\\b");
+          break;
+        case '\t':
+          buf.append("\\t");
+          break;
+        case '\n':
+          buf.append("\\n");
+          break;
+        case '\f':
+          buf.append("\\f");
+          break;
+        case '\r':
+          buf.append("\\r");
+          break;
+        default:
+          if (c < ' ' || c > 127) {
+            t = "000" + Integer.toHexString(c);
+            buf.append("\\u" + t.substring(t.length() - 4));
+          } else {
+            buf.append(c);
+          }
+        }
+        b = c;
+      }
+    return buf.toString();
+    }
+}

Modified: incubator/abdera/java/trunk/extensions/json/src/main/java/org/apache/abdera/ext/json/JSONUtil.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/extensions/json/src/main/java/org/apache/abdera/ext/json/JSONUtil.java?rev=580481&r1=580480&r2=580481&view=diff
==============================================================================
--- incubator/abdera/java/trunk/extensions/json/src/main/java/org/apache/abdera/ext/json/JSONUtil.java (original)
+++ incubator/abdera/java/trunk/extensions/json/src/main/java/org/apache/abdera/ext/json/JSONUtil.java Fri Sep 28 13:40:58 2007
@@ -27,7 +27,7 @@
 import org.apache.abdera.ext.history.FeedPagingHelper;
 import org.apache.abdera.ext.thread.InReplyTo;
 import org.apache.abdera.ext.thread.ThreadHelper;
-import org.apache.abdera.ext.thread.Total;
+import org.apache.abdera.i18n.iri.IRI;
 import org.apache.abdera.model.Base;
 import org.apache.abdera.model.Categories;
 import org.apache.abdera.model.Category;
@@ -55,706 +55,360 @@
 public class JSONUtil {
 
   public static void toJson(Base base, Writer writer) throws IOException {
+    JSONStream jstream = new JSONStream(writer);
     if (base instanceof Document)
-      toJson((Document)base,writer);
+      toJson((Document)base,jstream);
     else if (base instanceof Element)
-      toJson((Element)base,writer);
+      toJson((Element)base,jstream);
     writer.flush();
   }
   
-  private static Object[] getChildren(Element element) {
-    Abdera abdera = element.getFactory().getAbdera();
-    XPath xpath = abdera.getXPath();
-    List<Object> nodes = xpath.selectNodes("node()", element);
-    return nodes.toArray(new Object[nodes.size()]);
-  }
-  
-  private static void toJson(Element element, Writer writer) throws IOException {
-    writer.write('{');
-        
+  private static boolean isSameAsParentBase(Element element) {
+    IRI parentbase = null;
+    if (element.getParentElement() != null) {
+      parentbase = element instanceof Document ?
+        ((Document)element).getBaseUri() :
+        ((Element)element).getResolvedBaseUri();
+    }
+    if (parentbase == null && element.getResolvedBaseUri() != null) return false;
+    return parentbase.equals(element.getResolvedBaseUri());
+  }
+  
+  private static void toJson(Element element, JSONStream jstream) throws IOException {
+    jstream.startObject();
+
+    writeLanguageFields(element, jstream);
+    if (!isSameAsParentBase(element))
+      jstream.writeField("xml:base", element.getResolvedBaseUri());
+    
     if (element instanceof Categories) {
       Categories categories = (Categories) element;
-      writeField("fixed", categories.isFixed()?"true":"false", writer);
-      if (categories.getScheme() != null) {
-        writer.write(',');
-        writeField("scheme", categories.getScheme().toASCIIString(), writer);
-      } 
-      writer.write(',');
-      writeList("categories",categories.getCategories(),writer);
-      
-      writer.write(',');
-      writeExtensions((ExtensibleElement)element,writer);
+      jstream.writeField("fixed", categories.isFixed()?"true":"false");
+      jstream.writeField("scheme", categories.getScheme());
+      writeList("categories",categories.getCategories(),jstream);
+      writeExtensions((ExtensibleElement)element,jstream);
     } else if (element instanceof Category) {
-      Category category = (Category) element;
-
-      writeLanguageFields(element, writer);
-      
-      writeField("term", category.getTerm(), writer);
-      if (category.getScheme() != null) {
-        writer.write(',');
-        writeField("scheme", category.getScheme().toASCIIString(), writer);
-      }
-      if (category.getLabel() != null) {
-        writer.write(',');
-        writeField("label", category.getLabel(), writer);
-      }
-      
-      writer.write(',');
-      writeExtensions((ExtensibleElement)element,writer);
+      Category category = (Category) element;      
+      jstream.writeField("term", category.getTerm());
+      jstream.writeField("scheme", category.getScheme());
+      jstream.writeField("label", category.getLabel());
+      writeExtensions((ExtensibleElement)element,jstream);
     } else if (element instanceof Collection) {
       Collection collection = (Collection)element;
-      if (collection.getResolvedHref() != null) {
-        writeField("href", collection.getResolvedHref().toASCIIString(), writer);
-        writer.write(',');
-      }
-      if (collection.getTitleElement() != null) {
-        writeElement("title",collection.getTitleElement(),writer);
-        writer.write(',');
-      }
-      writer.write("\"accept\":[");
+      jstream.writeField("href", collection.getResolvedHref());
+      writeElement("title",collection.getTitleElement(),jstream);
       String[] accepts = collection.getAccept();
-      for (int n = 0; n < accepts.length; n++) {
-        writer.write("\"" + escape(accepts[n]) + "\"");
-        if (n < accepts.length - 1) writer.write(',');
+      if (accepts != null || accepts.length > 0) {
+        jstream.writeField("accept");
+        jstream.startArray();
+        for (int n = 0; n < accepts.length; n++) {
+          jstream.writeQuoted(accepts[n]);
+          if (n < accepts.length - 1) jstream.writeSeparator();
+        }
+        jstream.endArray();
       }
-      writer.write("]");
-      writer.write(',');
-      writeList("categories",collection.getCategories(),writer);
-      
-      writer.write(',');
-      writeExtensions((ExtensibleElement)element,writer);
+      List<Categories> cats = collection.getCategories();
+      if (cats.size() > 0)
+        writeList("categories",collection.getCategories(),jstream);
+      writeExtensions((ExtensibleElement)element,jstream);
     } else if (element instanceof Content) {
       Content content = (Content)element;      
-      writeLanguageFields(element, writer);
-
-      if (element.getResolvedBaseUri() != null) {
-        writeField("base", element.getResolvedBaseUri().toASCIIString(), writer);
-        writer.write(',');
-      }      
-      if (content.getResolvedSrc() != null) {
-        writeField("src", content.getResolvedSrc().toASCIIString(), writer);
-        writer.write(',');
-      }
-     
+      jstream.writeField("src", content.getResolvedSrc());
       switch(content.getContentType()) {
         case TEXT:
-          writeField("type","text",writer);
-          writer.write(',');
-          writeField("value",content.getValue(),writer);
+          jstream.writeField("type","text");
+          jstream.writeField("value",content.getValue());
           break;
         case HTML:
-          writeField("type","html",writer);
-          writer.write(',');
-          writeField("value",content.getValue(),writer);
+          jstream.writeField("type","html");
+          jstream.writeField("value",content.getValue());
           break;
         case XHTML:
-          writeField("type","xhtml",writer);
-          writer.write(',');
-          writeXHTMLValue((Div)content.getValueElement(), writer);
-          writer.write(',');
-          writeField("display",content.getValue(), writer);
+          jstream.writeField("type","xhtml");
+          jstream.writeField("value",content.getValue());
+          writeElementValue((Div)content.getValueElement(), jstream);
           break;
         case MEDIA:
-          writeField("type",content.getMimeType().toString(),writer);
-          if (content.getSrc() == null) {
-            writer.write(',');
-            writeField("value",content.getValue(),writer);
-          }
-          break;
         case XML:
-          writeField("type",content.getMimeType().toString(),writer);
-          if (content.getSrc() == null) {
-            writer.write(',');
-            writeField("value",content.getValue(),writer);
-          }
+          jstream.writeField("type",content.getMimeType());
+          if (content.getSrc() == null)
+            jstream.writeField("value",content.getValue());
           break;
       }
     } else if (element instanceof Control) {
       Control control = (Control)element;
-      writeField("draft", control.isDraft()?"true":"false",writer);
-      
-      writer.write(',');
-      writeExtensions((ExtensibleElement)element,writer);
+      jstream.writeField("draft", control.isDraft()?"true":"false");
+      writeExtensions((ExtensibleElement)element,jstream);
     } else if (element instanceof Entry) {
       Entry entry = (Entry)element;
-
-      writeField("id", entry.getId().toASCIIString(),writer);
-      writer.write(',');
-      
-      writeElement("title", entry.getTitleElement(),writer);
-      writer.write(',');
-      
-      if (entry.getSummary() != null) {
-        writeElement("summary", entry.getSummaryElement(),writer);
-        writer.write(',');
-      }
-      
-      if (entry.getRights() != null) {
-        writeElement("rights", entry.getRightsElement(),writer);
-        writer.write(',');
-      }
-      
-      if (entry.getContentElement() != null) {
-        writeElement("content", entry.getContentElement(),writer);
-        writer.write(',');
-      }
-      
-      writeField("updated", entry.getUpdated().getTime(),writer);
-      writer.write(',');
-      
-      if (entry.getPublished() != null) {
-        writeField("published", entry.getPublished().getTime(),writer);
-        writer.write(',');
-      }
-      
-      if (entry.getEdited() != null) {
-        writeField("edited", entry.getEdited().getTime(),writer);
-        writer.write(',');
-      }
-      
-      if (entry.getSource() != null) {
-        writeElement("source", entry.getSource(),writer);
-        writer.write(',');
-      }
-      
-      writeList("authors",entry.getAuthors(),writer);
-      writer.write(',');
-
-      writeList("contributors",entry.getContributors(),writer);
-      writer.write(',');
-      
-      writeList("links",entry.getLinks(),writer);
-      writer.write(',');
-      
-      writeList("categories",entry.getCategories(),writer);
-      writer.write(',');
-      
-      writeList("inreplyto",ThreadHelper.getInReplyTos(entry),writer);
-      writer.write(',');
-      
-      List<Link> links = entry.getLinks("replies");
-      writer.write("\"replies\":[");
-      Total total = ThreadHelper.getTotal(entry);
-      if (total != null) {
-        writer.write("\n{");
-        writeField("count",total.getValue(),writer);
-        writer.write('}');
-        if (links.size() > 0) writer.write(',');
-      }
-      for (int n = 0; n < links.size(); n++) {
-        Link link = links.get(n);
-        writer.write("\n{");
-        writeField("href",link.getResolvedHref().toASCIIString(),writer);
-        writer.write(',');
-        if (link.getMimeType() != null) {
-          writeField("type",link.getMimeType().toString(),writer);
-          writer.write(',');
-        }
-        writeField("count",ThreadHelper.getCount(link),writer);
-        writer.write('}');
-        if (n < links.size()-1) writer.write(',');
-      }
-      writer.write("]");
-      writer.write(',');
-      
-      links = entry.getLinks("license");
-      writer.write("\"licenses\":[");
-      for (int n = 0; n < links.size(); n++) {
-        Link link = links.get(n);
-        writer.write("\n{");
-        writeField("href",link.getResolvedHref().toASCIIString(),writer);
-        if (link.getMimeType() != null) {
-          writer.write(',');
-          writeField("type",link.getMimeType().toString(),writer);
-        }
-        writer.write('}');
-        if (n < links.size()-1) writer.write(',');
-      }
-      writer.write("]");
-      
-      writer.write(',');
-      writeExtensions((ExtensibleElement)element,writer);
-      
+      jstream.writeField("id", entry.getId());
+      writeElement("title", entry.getTitleElement(),jstream);
+      writeElement("summary", entry.getSummaryElement(),jstream);
+      writeElement("rights", entry.getRightsElement(),jstream);     
+      writeElement("content", entry.getContentElement(),jstream);
+      jstream.writeField("updated", entry.getUpdated());
+      jstream.writeField("published", entry.getPublished());
+      jstream.writeField("edited", entry.getEdited());
+      writeElement("source", entry.getSource(),jstream);
+      writeList("authors",entry.getAuthors(),jstream);
+      writeList("contributors",entry.getContributors(),jstream);
+      writeList("links",entry.getLinks(),jstream);
+      writeList("categories",entry.getCategories(),jstream);
+      writeList("inreplyto",ThreadHelper.getInReplyTos(entry),jstream);            
+      writeExtensions((ExtensibleElement)element,jstream);      
     } else if (element instanceof Generator) {
       Generator generator = (Generator)element;
-      
-      writeLanguageFields(element, writer);
-      
-      if (generator.getVersion() != null) {
-        writeField("version", generator.getVersion(), writer);
-      }    
-      if (generator.getResolvedUri() != null) {
-        writer.write(',');  
-        writeField("uri", generator.getResolvedUri().toASCIIString(), writer);
-      }
-      if (generator.getText() != null) {
-        writer.write(',');
-        writeField("value", generator.getText(), writer);
-      }      
+      jstream.writeField("version", generator.getVersion());
+      jstream.writeField("uri", generator.getResolvedUri());
+      jstream.writeField("value", generator.getText());
     } else if (element instanceof Link) {
-      Link link = (Link)element;
-      
-      writeLanguageFields(element, writer);
-
-      writeField("href", link.getResolvedHref().toASCIIString(), writer);
-      
-      if (link.getRel() != null) {
-        writer.write(',');
-        writeField("rel", link.getRel(), writer);
-      }
-      if (link.getTitle() != null) {
-        writer.write(',');
-        writeField("title", link.getTitle(), writer);
-      }
-      if (link.getMimeType() != null) {
-        writer.write(',');
-        writeField("type", link.getMimeType().toString(), writer);
-      }
-      if (link.getHrefLang() != null) {
-        writer.write(',');
-        writeField("hreflang", link.getHrefLang(), writer);
-      }
-      if (link.getLength() > -1) {
-        writer.write(',');
-        writeField("length", link.getLength(), writer);
-      }
-
-      writer.write(',');
-      writeExtensions((ExtensibleElement)element,writer);
+      Link link = (Link)element;      
+      jstream.writeField("href", link.getResolvedHref());
+      jstream.writeField("rel", link.getRel());
+      jstream.writeField("title", link.getTitle());
+      jstream.writeField("type", link.getMimeType());
+      jstream.writeField("hreflang", link.getHrefLang());
+      if (link.getLength() > -1)
+        jstream.writeField("length", link.getLength());
+      writeExtensions((ExtensibleElement)element,jstream);
     } else if (element instanceof Person) {
-      Person person = (Person)element;
-      
-      writeLanguageFields(element, writer);
-      
-      writeField("name",person.getName(),writer);
-      writer.write(',');
-      writeField("email",person.getEmail(),writer);
-      writer.write(',');
-      if (person.getUriElement() != null)
-        writeField("uri",person.getUriElement().getResolvedValue().toASCIIString(),writer);
-      
-      writer.write(',');
-      writeExtensions((ExtensibleElement)element,writer);
+      Person person = (Person)element;      
+      jstream.writeField("name",person.getName());
+      jstream.writeField("email",person.getEmail());
+      jstream.writeField("uri",person.getUriElement().getResolvedValue());
+      writeExtensions((ExtensibleElement)element,jstream);
     } else if (element instanceof Service) {
       Service service = (Service)element;
-      writeList("workspaces",service.getWorkspaces(),writer);
-      
-      writer.write(',');
-      writeExtensions((ExtensibleElement)element,writer);
+      writeList("workspaces",service.getWorkspaces(),jstream);
+      writeExtensions((ExtensibleElement)element,jstream);
     } else if (element instanceof Source) {
       Source source = (Source)element;
-
-      writeField("id", source.getId().toASCIIString(),writer);
-      writer.write(',');
-      
-      writeElement("title", source.getTitleElement(),writer);
-      writer.write(',');
-      
-      if (source.getSubtitle() != null) {
-        writeElement("subtitle", source.getSubtitleElement(),writer);
-        writer.write(',');
-      }
-      
-      if (source.getRights() != null) {
-        writeElement("rights", source.getRightsElement(),writer);
-        writer.write(',');
-      }
-      
-      writeField("updated", source.getUpdated().getTime(),writer);
-      writer.write(',');
-
-      if (source.getGenerator() != null) {
-        writeElement("generator", source.getGenerator(),writer);
-        writer.write(',');
-      }
-      
-      if (source.getIconElement() != null) {
-        writeField("icon", source.getIconElement().getResolvedValue().toASCIIString(),writer);
-        writer.write(',');
-      }
-      
-      if (source.getLogoElement() != null) {
-        writeField("logo", source.getLogoElement().getResolvedValue().toASCIIString(),writer);
-        writer.write(',');
-      }
-      
-      writeList("authors",source.getAuthors(),writer);
-      writer.write(',');
-
-      writeList("contributors",source.getContributors(),writer);
-      writer.write(',');
-      
-      writeList("links",source.getLinks(),writer);
-      writer.write(',');
-      
-      writeList("categories",source.getCategories(),writer);
-      writer.write(',');
-      
-      writeField("complete",FeedPagingHelper.isComplete(source), writer);
-      writer.write(',');
-      
-      writeField("archive",FeedPagingHelper.isArchive(source), writer);
-      
+      jstream.writeField("id", source.getId());
+      writeElement("title", source.getTitleElement(),jstream);
+      writeElement("subtitle", source.getSubtitleElement(),jstream);
+      writeElement("rights", source.getRightsElement(),jstream);
+      jstream.writeField("updated", source.getUpdated());
+      writeElement("generator", source.getGenerator(),jstream);
+      jstream.writeField("icon", source.getIconElement().getResolvedValue());
+      jstream.writeField("logo", source.getLogoElement().getResolvedValue());
+      writeList("authors",source.getAuthors(),jstream);
+      writeList("contributors",source.getContributors(),jstream);
+      writeList("links",source.getLinks(),jstream);
+      writeList("categories",source.getCategories(),jstream);
+      if (FeedPagingHelper.isComplete(source))
+        jstream.writeField("complete",true);      
+      if (FeedPagingHelper.isArchive(source))
+        jstream.writeField("archive",true);
       if (source instanceof Feed) {
-        writer.write(',');
-        writeList("entries",((Feed)source).getEntries(),writer);
+        writeList("entries",((Feed)source).getEntries(),jstream);
       }
-      
-      writer.write(',');
-      writeExtensions((ExtensibleElement)element,writer);
-      
+      writeExtensions((ExtensibleElement)element,jstream);      
     } else if (element instanceof Text) {
-      Text text = (Text)element;
-      
-      writeLanguageFields(element, writer);
-      
-      if (element.getResolvedBaseUri() != null) {
-        writeField("base", element.getResolvedBaseUri().toASCIIString(), writer);
-        writer.write(',');
-      }
-      
+      Text text = (Text)element;      
       switch(text.getTextType()) {
         case TEXT:
-          writeField("type","text",writer);
-          writer.write(',');
-          writeField("value",text.getValue(),writer);
+          jstream.writeField("type","text");
+          jstream.writeField("value",text.getValue());
           break;
         case HTML:
-          writeField("type","html",writer);
-          writer.write(',');
-          writeField("value",text.getValue(),writer);
+          jstream.writeField("type","html");
+          jstream.writeField("value",text.getValue());
           break;
         case XHTML:
-          writeField("type","xhtml",writer);
-          writer.write(',');
-          writeXHTMLValue(text.getValueElement(), writer);
-          writer.write(',');
-          writeField("display",text.getValue(), writer);
+          jstream.writeField("type","xhtml");
+          jstream.writeField("value",text.getValue());
+          writeElementValue(text.getValueElement(), jstream);
           break;
       }
     } else if (element instanceof Workspace) {
       Workspace workspace = (Workspace)element;
-
-      writeElement("title",workspace.getTitleElement(),writer);
-      writer.write(',');
-      writeList("collections",workspace.getCollections(),writer);
-      
-      writer.write(',');
-      writeExtensions((ExtensibleElement)element,writer);
+      writeElement("title",workspace.getTitleElement(),jstream);
+      writeList("collections",workspace.getCollections(),jstream);
+      writeExtensions((ExtensibleElement)element,jstream);
     } else if (element instanceof InReplyTo) {
-      InReplyTo irt = (InReplyTo)element;
-      
-      writeField("ref",irt.getRef().toASCIIString(),writer);
-      
-      if (irt.getHref() != null) {
-        writer.write(',');
-        writeField("href",irt.getResolvedHref().toASCIIString(),writer);
-      }
-      
-      if (irt.getMimeType() != null) {
-        writer.write(',');
-        writeField("type",irt.getMimeType().toString(),writer);
-      }
-      
-      if (irt.getSource() != null) {
-        writer.write(',');
-        writeField("source",irt.getResolvedSource().toASCIIString(),writer);
-      }
-      
+      InReplyTo irt = (InReplyTo)element;      
+      jstream.writeField("ref",irt.getRef());
+      jstream.writeField("href",irt.getResolvedHref());
+      jstream.writeField("type",irt.getMimeType());
+      jstream.writeField("source",irt.getResolvedSource());
     } else {
-      writeQName(element.getQName(),writer);
-      writer.write(',');
-      writeLanguageFields(element, writer);
-
-      if (element.getResolvedBaseUri() != null) {
-        writeField("base", element.getResolvedBaseUri().toASCIIString(), writer);
-        writer.write(',');
-      }
-      writer.write("\"attributes\":");
-      writer.write('[');
-      List<QName> attributes = element.getAttributes();
-      for (int n = 0; n < attributes.size(); n++) {
-        QName qname = attributes.get(n);
-        writeAttribute(qname, element.getAttributeValue(qname), writer);
-        if (n < attributes.size()-1) writer.write(',');
-      }
-      writer.write(']');
-      writer.write(',');    
-      writer.write("\"children\":");
-      writeChildren(element, writer);
+      writeElement(element,null,jstream);
     }
-    writer.write('}');
+    jstream.endObject();
   }
     
-  private static void writeXHTMLValue(Div div, Writer writer) throws IOException {
-    writer.write("\n\"value\":");
-    writeXHTMLChildren(div, writer);
+  private static void writeElementValue(Element element, JSONStream jstream) throws IOException {
+    jstream.writeField("valuehash");
+    writeElementChildren(element, jstream);
   }
 
-  private static void writeXHTMLChildren(Element element, Writer writer) throws IOException {
-    writer.write('[');
-    Object[] children = getChildren(element);
-    for (int n = 0; n < children.length; n++) {
-      Object child = children[n];
-      if (child instanceof Element) {
-        Element childel = (Element) child;
-        QName childqname = childel.getQName();
-        writer.write("[\n");
-        if (childqname.getNamespaceURI() != null && childqname.getNamespaceURI().equals(Constants.XHTML_NS)) {
-          writer.write('"');
-          writer.write(escape(childqname.getLocalPart()));
-          writer.write('"');
-        } else {
-          writeQName(childqname,writer,false);
-        }
-        writer.write(',');
-        List<QName> attributes = childel.getAttributes();
-        writer.write('{');
-        for (int i = 0; i < attributes.size(); i++) {
-          QName attr = attributes.get(i);
-          writer.write('"');
-          writer.write(escape(attr.getLocalPart()));
-          writer.write('"');
-          writer.write(':');
-          writer.write('"');
-          writer.write(escape(childel.getAttributeValue(attr)));
-          writer.write('"');
-          if (i < attributes.size()-1) writer.write(',');
-        }
-        writer.write('}');
-        writer.write(',');
-        writeXHTMLChildren((Element)child,writer);
-        writer.write("]\n");      
-      } else if (child instanceof TextValue) {
-        TextValue textvalue = (TextValue) child;
-        writer.write("\"" + escape(textvalue.getText()) + "\"");
-      }
-      if (n < children.length-1) writer.write(',');
-    }
-    writer.write(']');
+  private static void writeElement(Element child, QName parentqname, JSONStream jstream) throws IOException {
+    QName childqname = child.getQName();
+    String prefix = childqname.getPrefix();
+    String uri = childqname.getNamespaceURI();
+    jstream.startArray();
+    
+    if (prefix != null && !"".equals(prefix)) {
+      jstream.writeQuoted(childqname.getPrefix() + ":" + childqname.getLocalPart());
+    } else {
+      jstream.writeQuoted(childqname.getLocalPart());
+    } 
+    jstream.writeSeparator();
+    List<QName> attributes = child.getAttributes();
+    jstream.startObject();
+    if (!Constants.XHTML_NS.equals(uri) && !isSameNamespace(childqname, parentqname)) {
+      if (prefix != null && !"".equals(prefix))
+        jstream.writeField("xmlns:" + prefix);
+      else 
+        jstream.writeField("xmlns");
+      jstream.writeQuoted(childqname.getNamespaceURI());
+    }
+    if (!isSameAsParentBase(child))
+      jstream.writeField("xml:base",child.getResolvedBaseUri());
+    writeLanguageFields(child, jstream);
+    for (QName attr : attributes) {
+      jstream.writeField(attr.getLocalPart(),child.getAttributeValue(attr));
+    }
+    jstream.endObject();
+    jstream.writeSeparator();
+    writeElementChildren((Element)child,jstream);
+    jstream.endArray();
   }
   
-  private static void writeChildren(Element element, Writer writer) throws IOException {
-    writer.write('[');
+  private static void writeElementChildren(Element element, JSONStream jstream) throws IOException {
+    jstream.startArray();
     Object[] children = getChildren(element);
+    QName parentqname = element.getQName();
     for (int n = 0; n < children.length; n++) {
       Object child = children[n];
       if (child instanceof Element) {
-        toJson((Element)child, writer);
+        writeElement((Element)child, parentqname, jstream);
+        if (n < children.length-1) jstream.writeSeparator();
       } else if (child instanceof TextValue) {
         TextValue textvalue = (TextValue) child;
-        writer.write("\"" + escape(textvalue.getText()) + "\"");
+        String value = textvalue.getText();
+        if (!element.getMustPreserveWhitespace()) {
+          if (!value.matches("\\s*")) {
+            jstream.writeQuoted(value.trim());
+            if (n < children.length-1) jstream.writeSeparator();
+          }
+        } else { 
+          jstream.writeQuoted(value);
+          if (n < children.length-1) jstream.writeSeparator();
+        }
       }
-      if (n < children.length-1) writer.write(',');
     }
-    writer.write(']');
+    jstream.endArray();
   }
   
-  private static void writeExtensions(ExtensibleElement element, Writer writer) throws IOException {
-    writeList("extensions",element.getExtensions(),writer);
-    writer.write(',');
-    writer.write("\"attributes\":");
-    writer.write('[');
+  private static void writeExtensions(ExtensibleElement element, JSONStream jstream) throws IOException {
+    writeExtensions(element,jstream,true);
+  }
+  
+  private static void writeExtensions(ExtensibleElement element, JSONStream jstream, boolean startsep) throws IOException {
     List<QName> attributes = element.getExtensionAttributes();
-    for (int n = 0; n < attributes.size(); n++) {
-      QName qname = attributes.get(n);
-      writeAttribute(qname, element.getAttributeValue(qname), writer);
-      if (n < attributes.size()-1) writer.write(',');
+    writeList("extensions",element.getExtensions(),jstream);
+    if (attributes.size() > 0) {
+      jstream.writeField("attributes");
+      jstream.startArray();
+      for (int n = 0; n < attributes.size(); n++) {
+        QName qname = attributes.get(n);
+        writeAttribute(qname, element.getAttributeValue(qname), jstream);
+        if (n < attributes.size()-1) jstream.writeSeparator();
+      }
+      jstream.endArray();
     }
-    writer.write(']');
   }
   
-  private static void writeLanguageFields(Element element, Writer writer) throws IOException {
+  private static void writeLanguageFields(Element element, JSONStream jstream) throws IOException {
+    String parentlang = null;
+    BidiHelper.Direction parentdir = BidiHelper.Direction.UNSPECIFIED;
+    if (element.getParentElement() != null) {
+      Base parent = element.getParentElement();
+      parentlang = parent instanceof Document ?
+        ((Document)parent).getLanguage() :
+        ((Element)parent).getLanguage();
+      if (parent instanceof Element) {
+        parentdir = BidiHelper.getDirection((Element)parent);
+      }
+    }
     String lang = element.getLanguage();
-    boolean whitespace = element.getMustPreserveWhitespace();
     BidiHelper.Direction dir = BidiHelper.getDirection(element);
-    if (lang != null) {
-      writeField("language",lang,writer);
-    }
-    
-    if (!whitespace) {
-      if (lang != null) writer.write(',');
-      writeField("whitespace", "false", writer);
-    }
-    
-    if (dir != null && dir != BidiHelper.Direction.UNSPECIFIED) {
-      if (lang != null || !whitespace) writer.write(',');
-      writeField("dir", dir.name().toLowerCase(), writer);
+    if (parentlang == null || (parentlang != null && !parentlang.equalsIgnoreCase(lang)))
+      
+      jstream.writeField("xml:lang",lang);
+    if (dir != null && dir != BidiHelper.Direction.UNSPECIFIED && !dir.equals(parentdir)) {
+      jstream.writeField("dir", dir.name().toLowerCase());
     }
-    
-    if (lang != null || 
-        !whitespace || 
-        (dir != null && dir != BidiHelper.Direction.UNSPECIFIED)) 
-      writer.write(',');
   }
   
-  private static void writeElement(String name, Element element, Writer writer) throws IOException {
+  private static void writeElement(String name, Element element, JSONStream jstream) throws IOException {
     if (element != null) {
-      writer.write("\n\"" + escape(name) + "\":");
-      toJson(element,writer);
+      jstream.writeField(name);
+      toJson(element,jstream);
     }
   }
   
-  private static void writeList(String name, List list, Writer writer) throws IOException {
-    writer.write("\n\"" + escape(name) + "\":[");
+  private static boolean writeList(String name, List list, JSONStream jstream) throws IOException {
+    if (list == null || list.size() == 0) return false;
+    jstream.writeField(name);
+    jstream.startArray();
     for (int n = 0; n < list.size(); n++) {
-      toJson((Element)list.get(n),writer);
-      if (n < list.size()-1) writer.write(",\n");
+      toJson((Element)list.get(n),jstream);
+      if (n < list.size()-1) jstream.writeSeparator();
     }
-    writer.write("]");
+    jstream.endArray();
+    return true;
   }
   
-  private static void toJson(Document document, Writer writer) throws IOException {
-    writer.write('{');
-    
-    if (document.getBaseUri() != null) {
-      writeField("base", document.getBaseUri().toASCIIString(), writer);
-      writer.write(',');
-    }
-    
-    if (document.getCharset() != null) {
-      writeField("charset", document.getCharset(), writer);
-      writer.write(',');
-    }
-    
-    if (document.getContentType() != null) {
-      writeField("contenttype", document.getContentType().toString(), writer);
-      writer.write(',');
-    }    
-    
-    if (document.getEntityTag() != null) {
-      writeField("etag", document.getEntityTag().toString(), writer);
-      writer.write(',');
-    }
-    
-    if (document.getLanguage() != null) {
-      writeField("language", document.getLanguage().toString(), writer);
-      writer.write(',');
-    }
-    
-    if (document.getSlug() != null) {
-      writeField("slug", document.getSlug(), writer);
-      writer.write(',');
-    }
-    
-    if (document.getLastModified() != null) {
-      writeField("lastmodified", document.getLastModified().getTime(), writer);
-      writer.write(',');
-    }
-    
-    if (!document.getMustPreserveWhitespace()) {
-      writeField("whitespace", "false", writer);
-      writer.write(',');
-    }
-    
-    Element root = document.getRoot();
-    if (root != null) {
-      writer.write("\n\"root\":");
-      toJson(root,writer);
+  private static void toJson(Document document, JSONStream jstream) throws IOException {
+    jstream.startObject();    
+    jstream.writeField("base", document.getBaseUri());
+    jstream.writeField("charset", document.getCharset());
+    jstream.writeField("contenttype", document.getContentType());
+    jstream.writeField("etag", document.getEntityTag());
+    jstream.writeField("language", document.getLanguage());
+    jstream.writeField("slug", document.getSlug());
+    jstream.writeField("lastmodified", document.getLastModified());
+    jstream.writeField("whitespace", "false");
+    writeElement("root",document.getRoot(),jstream);
+    jstream.endObject();
+  }
+  
+  private static void writeAttribute(QName qname, String value, JSONStream jstream) throws IOException {
+    jstream.startArray();
+    String prefix = qname.getPrefix();
+    String localpart = qname.getLocalPart();
+    String uri = qname.getNamespaceURI();
+    if (prefix != null && !"".equals(prefix)) {
+      jstream.writeQuoted(prefix + ":" + localpart);
+      jstream.writeSeparator();
+      jstream.startObject();
+      jstream.writeField("xmlns:" + prefix, uri);
+      jstream.endObject();
     } else {
-      writer.write("\"root\":null");
+      jstream.writeQuoted(localpart);
     }
-    
-    writer.write('}');
+    jstream.writeSeparator();
+    jstream.writeQuoted(value);
+    jstream.endArray();
+  }
+  
+  private static Object[] getChildren(Element element) {
+    Abdera abdera = element.getFactory().getAbdera();
+    XPath xpath = abdera.getXPath();
+    List<Object> nodes = xpath.selectNodes("node()", element);
+    return nodes.toArray(new Object[nodes.size()]);
+  }
+  
+  private static boolean isSameNamespace(QName q1, QName q2) {
+    if (q1 == null && q2 != null) return false;
+    if (q1 != null && q2 == null) return false;
+    String p1 = q1 == null ? "" : q1.getPrefix() != null ? q1.getPrefix() : "";
+    String p2 = q2 == null ? "" : q2.getPrefix() != null ? q2.getPrefix() : "";
+    String n1 = q1 == null ? "" : q1.getNamespaceURI() != null ? q1.getNamespaceURI() : "";
+    String n2 = q2 == null ? "" : q2.getNamespaceURI() != null ? q2.getNamespaceURI() : "";
+    return n1.equals(n2) && p1.equals(p2);
   }
-
-  private static void writeField(String name, boolean value, Writer writer) throws IOException {
-    writer.write("\n\"");
-    writer.write(escape(name));
-    writer.write('"');
-    writer.write(':');
-    writer.write(value?"true":"false");
-  }
-  
-  private static void writeField(String name, String value, Writer writer) throws IOException {
-    if (value == null) return;
-    writer.write("\n\"");
-    writer.write(escape(name));
-    writer.write('"');
-    writer.write(':');
-    writer.write('"');
-    writer.write(escape(value));
-    writer.write('"');
-  }
-  
-  private static void writeField(String name, Number value, Writer writer) throws IOException {
-    if (value == null) return;
-    writer.write("\n\"");
-    writer.write(escape(name));
-    writer.write('"');
-    writer.write(':');
-    writer.write(value.toString());
-  }
-  
-  private static void writeQName(QName qname, Writer writer) throws IOException {
-    writeQName(qname,writer,true);
-  }
-  
-  private static void writeQName(QName qname, Writer writer, boolean includefieldname) throws IOException {
-    if (includefieldname) writer.write("\n\"qname\":");
-    writer.write('{');
-    writeField("name",escape(qname.getLocalPart()),writer);
-    if (qname.getNamespaceURI() != null) {
-      writer.write(',');
-      writeField("ns",escape(qname.getNamespaceURI()),writer);
-    }
-    if (qname.getPrefix() != null) {
-      writer.write(',');
-      writeField("prefix",escape(qname.getPrefix()),writer);
-    }
-    writer.write('}');
-  }
-  
-  private static void writeAttribute(QName qname, String value, Writer writer) throws IOException {
-    writer.write("\n{");
-    writeQName(qname,writer);
-    writer.write(',');
-    writeField("value",escape(value),writer);
-    writer.write('}');
-  }
-  
-  private static String escape(String value) {
-    StringBuffer buf = new StringBuffer();
-    char[] chars = value.toCharArray();
-    char b = 0;
-    String t = null;
-    for (char c : chars) {
-      switch(c) {
-        case '\\':
-        case '"':
-          buf.append('\\');
-          buf.append(c);
-          break;
-        case '/':
-          if (b == '<') buf.append('\\');
-          buf.append(c);
-          break;
-        case '\b':
-          buf.append("\\b");
-          break;
-        case '\t':
-          buf.append("\\t");
-          break;
-        case '\n':
-          buf.append("\\n");
-          break;
-        case '\f':
-          buf.append("\\f");
-          break;
-        case '\r':
-          buf.append("\\r");
-          break;
-        default:
-          if (c < ' ' || c > 127) {
-            t = "000" + Integer.toHexString(c);
-            buf.append("\\u" + t.substring(t.length() - 4));
-          } else {
-            buf.append(c);
-          }
-        }
-        b = c;
-      }
-    return buf.toString();
-    }
     
 }