1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2007, 2009, 2010, 2013, 2014, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2007, 2009, 2010, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 //
 25 // Base class
 26 //
 27 
 28 AjxSerializer = function() {
 29     if (arguments.length == 0) return;
 30 }
 31 
 32 // Public methods
 33 
 34 AjxSerializer.prototype.serialize = function(node) { throw "NOT IMPLEMENTED"; };
 35 
 36 //
 37 // Classes
 38 //
 39 
 40 /**
 41  * This class attempts to serialize a DOM document in the same format
 42  * as the Zimbra Server.
 43  * @class
 44  * @constructor
 45  * @private
 46  */
 47 AjxJsonSerializer = function(minimize) {
 48     AjxSerializer.call(this, null);
 49     this._minimize = Boolean(minimize);
 50 }
 51 AjxJsonSerializer.prototype = new AjxSerializer;
 52 AjxJsonSerializer.prototype.constructor = AjxJsonSerializer;
 53 
 54 // Constants
 55 
 56 AjxJsonSerializer.E_ATTRS = "_attrs";
 57 AjxJsonSerializer.A_CONTENT = "_content";
 58 AjxJsonSerializer.A_NAMESPACE = "_jsns";
 59 
 60 // AjxSerializer methods
 61 
 62 // TODO: handle namespaces
 63 AjxJsonSerializer.prototype.serialize = function(node) {
 64     // gather elements and content
 65     var elems = {};
 66     var elemCount = 0;
 67     var content = null;
 68     for (var child = node.firstChild; child; child = child.nextSibling) {
 69         var type = child.nodeType;
 70         // elements
 71         if (type == 1) {
 72             var name = child.nodeName;
 73             if (!elems[name]) {
 74                 elems[name] = [];
 75                 elemCount++;
 76             }
 77             elems[name].push(child);
 78             continue;
 79         }
 80         // text and CDATA nodes
 81         if (type == 3 || type == 4) {
 82             if (!content) {
 83                 content = [];
 84             }
 85             content.push(child.nodeValue);
 86             continue;
 87         }
 88     }
 89 
 90     // gather attributes
 91     var attrs = {};
 92     var attrCount = 0;
 93     if (node.attributes) {
 94         for (var i = 0; i < node.attributes.length; i++) {
 95             var attr = node.attributes[i];
 96             var name = attr.nodeName;
 97             var value = this.quote(attr.nodeValue);
 98             if (elems[name]) {
 99                 attrs[name] = value;
100                 attrCount++;
101             }
102             else {
103                 elems[name] = value;
104                 elemCount++;
105             }
106         }
107     }
108 
109     // escape content
110     content = content && content.join("");
111     if (content && content.match(/^\s*$/)) {
112         content = null;
113     }
114     if (content) {
115         content = this.quote(content);
116 
117         // do we only have content?
118         if (attrCount == 0 && elemCount == 0) {
119             return content;
120         }
121     }
122 
123     // is there anything to do?
124     if (content == null && elemCount == 0 && attrCount == 0) {
125 //        return "null";
126         return "{}";
127     }
128 
129     // serialize content
130     var a = [ "{" ];
131     if (content) {
132         a.push(this.quote(AjxJsonSerializer.A_CONTENT), ":", content);
133         if (attrCount > 0 || elemCount > 0) {
134             a.push(",");
135         }
136     }
137 
138     // serialize attributes
139     if (attrCount > 0) {
140         a.push(this.quote(AjxJsonSerializer.E_ATTRS), ": { ");
141         var i = 0;
142         for (var name in attrs) {
143             if (i > 0) {
144                 a.push(",");
145             }
146             a.push(this.quote(name), ":", attrs[name]);
147             i++;
148         }
149         a.push("}");
150         if (elemCount > 0) {
151             a.push(", ");
152         }
153     }
154 
155     // serialize elements
156     var j = 0;
157     for (var name in elems) {
158         if (j > 0) {
159             a.push(",");
160         }
161         var elem = elems[name];
162         if (typeof elem == "string") {
163             a.push(this.quote(name), ":", elem);
164             j++;
165             continue;
166         }
167         a.push(this.quote(name), ":");
168         if (!this._minimize || elem.length > 1) {
169             a.push("[");
170         }
171         for (var i = 0; i < elem.length; i++) {
172             if (i > 0) {
173                 a.push(",");
174             }
175             a.push(this.serialize(elem[i]));
176         }
177         if (!this._minimize || elem.length > 1) {
178             a.push("]");
179         }
180         j++;
181     }
182     a.push("}");
183 
184     return a.join("");
185 };
186 
187 // convenience methods
188 
189 AjxJsonSerializer.prototype.quote = function(s) {
190     return [ '"', this.escape(s), '"' ].join("");
191 };
192 
193 AjxJsonSerializer.prototype.escape = function(s) {
194     return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
195 };