/* * 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.abdera2.ext.json; import java.io.IOException; import java.io.Writer; import java.util.List; import javax.xml.namespace.QName; import org.apache.abdera2.Abdera; import org.apache.abdera2.ext.bidi.BidiHelper; import org.apache.abdera2.ext.bidi.BidiHelper.Direction; import org.apache.abdera2.ext.history.FeedPagingHelper; import org.apache.abdera2.ext.html.HtmlHelper; import org.apache.abdera2.ext.thread.InReplyTo; import org.apache.abdera2.ext.thread.ThreadHelper; import org.apache.abdera2.common.iri.IRI; import org.apache.abdera2.model.Base; import org.apache.abdera2.model.Categories; import org.apache.abdera2.model.Category; import org.apache.abdera2.model.Collection; import org.apache.abdera2.model.Content; import org.apache.abdera2.model.Control; import org.apache.abdera2.model.Div; import org.apache.abdera2.model.Document; import org.apache.abdera2.model.Element; import org.apache.abdera2.model.Entry; import org.apache.abdera2.model.ExtensibleElement; import org.apache.abdera2.model.Feed; import org.apache.abdera2.model.Generator; import org.apache.abdera2.model.Link; import org.apache.abdera2.model.Person; import org.apache.abdera2.model.Service; import org.apache.abdera2.model.Source; import org.apache.abdera2.model.Text; import org.apache.abdera2.model.TextValue; import org.apache.abdera2.model.Workspace; import org.apache.abdera2.xpath.XPath; import com.google.common.collect.Iterables; 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, jstream); } else if (base instanceof Element) { toJson((Element)base, jstream); } } private static boolean isSameAsParentBase(Element element) { IRI parentbase = null; if (element.getParentElement() != null) { parentbase = element instanceof Document ? ((Document)element).getBaseUri() : ((Element)element) .getResolvedBaseUri(); } IRI base = element.getResolvedBaseUri(); if (parentbase == null && base != null) { return false; } if (parentbase == null && base == null) { return true; } return parentbase.equals(element.getResolvedBaseUri()); } private static void toJson(Element element, JSONStream jstream) throws IOException { if (element instanceof Text) { Text text = (Text)element; Text.Type texttype = text.getTextType(); if (texttype.equals(Text.Type.TEXT) && !needToWriteLanguageFields(text)) { jstream.writeQuoted(text.getValue()); } else { jstream.startObject(); jstream.writeField("attributes"); jstream.startObject(); jstream.writeField("type", texttype.name().toLowerCase()); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) { jstream.writeField("xml:base", element.getResolvedBaseUri()); } jstream.endObject(); jstream.writeField("children"); switch (text.getTextType()) { case TEXT: jstream.startArray(); jstream.writeQuoted(text.getValue()); jstream.endArray(); break; case HTML: if (text.getValue() != null) { Div div = HtmlHelper.parse(text.getValue()); writeElementValue(div, jstream); } else { jstream.writeQuoted(""); } break; case XHTML: if (text.getValueElement() != null) { writeElementValue(text.getValueElement(), jstream); } else { jstream.writeQuoted(""); } break; } jstream.endObject(); } } else if (element instanceof Content) { Content content = (Content)element; Content.Type contenttype = content.getContentType(); if (contenttype.equals(Content.Type.TEXT) && !needToWriteLanguageFields(content)) { jstream.writeQuoted(content.getValue()); } else { jstream.startObject(); jstream.writeField("attributes"); jstream.startObject(); switch (content.getContentType()) { case TEXT: case HTML: case XHTML: jstream.writeField("type", contenttype.name().toLowerCase()); break; case MEDIA: case XML: jstream.writeField("type", content.getMimeType()); } writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) jstream.writeField("xml:base", element.getResolvedBaseUri()); writeLanguageFields(content, jstream); jstream.writeField("src", content.getResolvedSrc()); jstream.endObject(); jstream.writeField("children"); switch (content.getContentType()) { case TEXT: jstream.startArray(); jstream.writeQuoted(content.getValue()); jstream.endArray(); break; case HTML: Div div = HtmlHelper.parse(content.getValue()); writeElementValue(div, jstream); break; case XHTML: writeElementValue(content.getValueElement(), jstream); break; case MEDIA: case XML: jstream.startArray(); jstream.writeQuoted(content.getValue()); jstream.endArray(); } jstream.endObject(); } } else { if (element instanceof Categories) { jstream.startObject(); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) jstream.writeField("xml:base", element.getResolvedBaseUri()); Categories categories = (Categories)element; jstream.writeField("fixed", categories.isFixed() ? "true" : "false"); jstream.writeField("scheme", categories.getScheme()); writeList("categories", categories.getCategories(), jstream); writeExtensions((ExtensibleElement)element, jstream); jstream.endObject(); } else if (element instanceof Category) { jstream.startObject(); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) jstream.writeField("xml:base", element.getResolvedBaseUri()); Category category = (Category)element; jstream.writeField("term", category.getTerm()); jstream.writeField("scheme", category.getScheme()); jstream.writeField("label", category.getLabel()); writeExtensions((ExtensibleElement)element, jstream); jstream.endObject(); } else if (element instanceof Collection) { jstream.startObject(); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) jstream.writeField("xml:base", element.getResolvedBaseUri()); Collection collection = (Collection)element; jstream.writeField("href", collection.getResolvedHref()); writeElement("title", collection.getTitleElement(), jstream); String[] accepts = collection.getAccept(); 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(); } List cats = collection.getCategories(); if (cats.size() > 0) writeList("categories", collection.getCategories(), jstream); writeExtensions((ExtensibleElement)element, jstream); jstream.endObject(); } else if (element instanceof Control) { jstream.startObject(); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) { jstream.writeField("xml:base", element.getResolvedBaseUri()); } Control control = (Control)element; jstream.writeField("draft", control.isDraft() ? "true" : "false"); writeExtensions((ExtensibleElement)element, jstream); jstream.endObject(); } else if (element instanceof Entry) { jstream.startObject(); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)){ jstream.writeField("xml:base", element.getResolvedBaseUri()); } Entry entry = (Entry)element; 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); writeElement("control", entry.getControl(), jstream); writeExtensions((ExtensibleElement)element, jstream); jstream.endObject(); } else if (element instanceof Generator) { jstream.startObject(); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) { jstream.writeField("xml:base", element.getResolvedBaseUri()); } Generator generator = (Generator)element; jstream.writeField("version", generator.getVersion()); jstream.writeField("uri", generator.getResolvedUri()); jstream.writeField("value", generator.getText()); jstream.endObject(); } else if (element instanceof Link) { jstream.startObject(); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) { jstream.writeField("xml:base", element.getResolvedBaseUri()); } 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); jstream.endObject(); } else if (element instanceof Person) { jstream.startObject(); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) jstream.writeField("xml:base", element.getResolvedBaseUri()); Person person = (Person)element; jstream.writeField("name", person.getName()); if (person.getEmail() != null) jstream.writeField("email", person.getEmail()); if (person.getUri() != null) jstream.writeField("uri", person.getUriElement().getResolvedValue()); writeExtensions((ExtensibleElement)element, jstream); jstream.endObject(); } else if (element instanceof Service) { jstream.startObject(); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) jstream.writeField("xml:base", element.getResolvedBaseUri()); Service service = (Service)element; writeList("workspaces", service.getWorkspaces(), jstream); writeExtensions((ExtensibleElement)element, jstream); jstream.endObject(); } else if (element instanceof Source) { jstream.startObject(); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) jstream.writeField("xml:base", element.getResolvedBaseUri()); Source source = (Source)element; 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); if (source.getIconElement() != null) jstream.writeField("icon", source.getIconElement().getResolvedValue()); if (source.getLogoElement() != null) 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) { writeList("entries", ((Feed)source).getEntries(), jstream); } writeExtensions((ExtensibleElement)element, jstream); jstream.endObject(); } else if (element instanceof Workspace) { jstream.startObject(); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) jstream.writeField("xml:base", element.getResolvedBaseUri()); Workspace workspace = (Workspace)element; writeElement("title", workspace.getTitleElement(), jstream); writeList("collections", workspace.getCollections(), jstream); writeExtensions((ExtensibleElement)element, jstream); jstream.endObject(); } else if (element instanceof InReplyTo) { jstream.startObject(); writeLanguageFields(element, jstream); if (!isSameAsParentBase(element)) jstream.writeField("xml:base", element.getResolvedBaseUri()); InReplyTo irt = (InReplyTo)element; jstream.writeField("ref", irt.getRef()); jstream.writeField("href", irt.getResolvedHref()); jstream.writeField("type", irt.getMimeType()); jstream.writeField("source", irt.getResolvedSource()); jstream.endObject(); } else { writeElement(element, null, jstream); } } } private static void writeElementValue(Element element, JSONStream jstream) throws IOException { writeElementChildren(element, jstream); } private static String getName(QName qname) { String prefix = qname.getPrefix(); String name = qname.getLocalPart(); return (prefix != null && !"".equals(prefix)) ? prefix + ":" + name : name; } private static void writeElement(Element child, QName parentqname, JSONStream jstream) throws IOException { QName childqname = child.getQName(); String prefix = childqname.getPrefix(); jstream.startObject(); jstream.writeField("name", getName(childqname)); jstream.writeField("attributes"); List attributes = child.getAttributes(); jstream.startObject(); if (!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) { String name = getName(attr); jstream.writeField(name); if ("".equals(attr.getPrefix()) || "xml".equals(attr.getPrefix())) { String val = child.getAttributeValue(attr); if (val != null && ("href".equalsIgnoreCase(name) || "src".equalsIgnoreCase(name) || "action" .equalsIgnoreCase(name))) { IRI base = child.getResolvedBaseUri(); if (base != null) val = base.resolve(val).toASCIIString(); } jstream.writeQuoted(val); } else { jstream.startObject(); jstream.writeField("attributes"); jstream.startObject(); jstream.writeField("xmlns:" + attr.getPrefix()); jstream.writeQuoted(attr.getNamespaceURI()); jstream.endObject(); jstream.writeField("value"); jstream.writeQuoted(child.getAttributeValue(attr)); jstream.endObject(); } } jstream.endObject(); jstream.writeField("children"); writeElementChildren((Element)child, jstream); jstream.endObject(); } 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) { writeElement((Element)child, parentqname, jstream); if (n < children.length - 1) jstream.writeSeparator(); } else if (child instanceof TextValue) { TextValue textvalue = (TextValue)child; 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(); } } } jstream.endArray(); } 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 attributes = element.getExtensionAttributes(); writeList("extensions", element.getExtensions(), jstream); if (attributes.size() > 0) { jstream.writeField("attributes"); jstream.startObject(); for (int n = 0; n < attributes.size(); n++) { QName qname = attributes.get(n); jstream.writeField(getName(qname)); if ("".equals(qname.getPrefix()) || "xml".equals(qname.getPrefix())) { jstream.writeQuoted(element.getAttributeValue(qname)); } else { jstream.startObject(); jstream.writeField("attributes"); jstream.startObject(); jstream.writeField("xmlns:" + qname.getPrefix()); jstream.writeQuoted(qname.getNamespaceURI()); jstream.endObject(); jstream.writeField("value"); jstream.writeQuoted(element.getAttributeValue(qname)); jstream.endObject(); } } jstream.endObject(); } } private static boolean needToWriteLanguageFields(Element element) { return needToWriteLang(element) || needToWriteDir(element); } private static boolean needToWriteLang(Element element) { String parentlang = null; if (element.getParentElement() != null) { Base parent = element.getParentElement(); parentlang = parent instanceof Document ? ((Document)parent).getLanguage() : ((Element)parent).getLanguage(); } String lang = element.getLanguage(); return (parentlang == null && lang != null) || (lang != null && parentlang != null && !parentlang .equalsIgnoreCase(lang)); } private static boolean needToWriteDir(Element element) { Direction parentdir = Direction.UNSPECIFIED; Direction dir = BidiHelper.getDirection(element); if (element.getParentElement() != null) { Base parent = element.getParentElement(); if (parent instanceof Element) parentdir = BidiHelper.getDirection((Element)parent); } return dir != Direction.UNSPECIFIED && !dir.equals(parentdir); } private static void writeLanguageFields(Element element, JSONStream jstream) throws IOException { if (needToWriteLang(element)) { String lang = element.getLanguage(); jstream.writeField("lang", lang); } if (needToWriteDir(element)) { Direction dir = BidiHelper.getDirection(element); jstream.writeField("dir", dir.name().toLowerCase()); } } private static void writeElement(String name, Element element, JSONStream jstream) throws IOException { if (element != null) { jstream.writeField(name); toJson(element, jstream); } } private static boolean is_empty(Iterable list) { return Iterables.isEmpty(list); } private static boolean writeList(String name, Iterable list, JSONStream jstream) throws IOException { if (is_empty(list)) return false; jstream.writeField(name); jstream.startArray(); boolean start = true; for (Object obj : list) { Element el = (Element)obj; if (!(el instanceof InReplyTo) && !(el instanceof Control)) { if (!start) jstream.writeSeparator(); else start = false; toJson(el, jstream); } } jstream.endArray(); return true; } private static void toJson(Document document, JSONStream jstream) throws IOException { jstream.startObject(); jstream.writeField("base", document.getBaseUri()); jstream.writeField("content-type", document.getContentType()); jstream.writeField("etag", document.getEntityTag()); jstream.writeField("language", document.getLanguage()); jstream.writeField("slug", document.getSlug()); jstream.writeField("last-modified", document.getLastModified()); Element root = document.getRoot(); if (root != null) { String rootname = root.getQName().getLocalPart(); writeElement(rootname, document.getRoot(), jstream); } jstream.endObject(); } private static Object[] getChildren(Element element) { Abdera abdera = element.getFactory().getAbdera(); XPath xpath = abdera.getXPath(); List 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); } }