1package com.google.polo.json;
2
3/*
4Copyright (c) 2008 JSON.org
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15
16The Software shall be used for Good, not Evil.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24SOFTWARE.
25*/
26
27import java.util.Iterator;
28
29
30/**
31 * This provides static methods to convert an XML text into a JSONArray or
32 * JSONObject, and to covert a JSONArray or JSONObject into an XML text using
33 * the JsonML transform.
34 * @author JSON.org
35 * @version 2008-11-20
36 */
37public class JSONML {
38
39    /**
40     * Parse XML values and store them in a JSONArray.
41     * @param x       The XMLTokener containing the source string.
42     * @param arrayForm true if array form, false if object form.
43     * @param ja      The JSONArray that is containing the current tag or null
44     *     if we are at the outermost level.
45     * @return A JSONArray if the value is the outermost tag, otherwise null.
46     * @throws JSONException
47     */
48    private static Object parse(XMLTokener x, boolean arrayForm,
49    		JSONArray ja) throws JSONException {
50        String     attribute;
51        char       c;
52        String	   closeTag = null;
53        int        i;
54        JSONArray  newja = null;
55        JSONObject newjo = null;
56        Object     token;
57        String	   tagName = null;
58
59// Test for and skip past these forms:
60//      <!-- ... -->
61//      <![  ... ]]>
62//      <!   ...   >
63//      <?   ...  ?>
64
65        while (true) {
66        	token = x.nextContent();
67    		if (token == XML.LT) {
68    			token = x.nextToken();
69    			if (token instanceof Character) {
70			        if (token == XML.SLASH) {
71
72// Close tag </
73
74			        	token = x.nextToken();
75			        	if (!(token instanceof String)) {
76			        		throw new JSONException(
77			        				"Expected a closing name instead of '" +
78			        				token + "'.");
79			        	}
80			            if (x.nextToken() != XML.GT) {
81			                throw x.syntaxError("Misshaped close tag");
82			            }
83			            return token;
84			        } else if (token == XML.BANG) {
85
86// <!
87
88			            c = x.next();
89			            if (c == '-') {
90			                if (x.next() == '-') {
91			                    x.skipPast("-->");
92			                }
93			                x.back();
94			            } else if (c == '[') {
95			                token = x.nextToken();
96			                if (token.equals("CDATA") && x.next() == '[') {
97			                	if (ja != null) {
98			                		ja.put(x.nextCDATA());
99			                	}
100			                } else {
101			                	throw x.syntaxError("Expected 'CDATA['");
102			                }
103			            } else {
104				            i = 1;
105				            do {
106				                token = x.nextMeta();
107				                if (token == null) {
108				                    throw x.syntaxError("Missing '>' after '<!'.");
109				                } else if (token == XML.LT) {
110				                    i += 1;
111				                } else if (token == XML.GT) {
112				                    i -= 1;
113				                }
114				            } while (i > 0);
115			            }
116			        } else if (token == XML.QUEST) {
117
118// <?
119
120			        	x.skipPast("?>");
121			        } else {
122			            throw x.syntaxError("Misshaped tag");
123			        }
124
125// Open tag <
126
127		        } else {
128		        	if (!(token instanceof String)) {
129			            throw x.syntaxError("Bad tagName '" + token + "'.");
130		        	}
131		        	tagName = (String)token;
132		            newja = new JSONArray();
133		            newjo = new JSONObject();
134		        	if (arrayForm) {
135			            newja.put(tagName);
136			            if (ja != null) {
137			            	ja.put(newja);
138			            }
139			        } else {
140		        		newjo.put("tagName", tagName);
141		        		if (ja != null) {
142			            	ja.put(newjo);
143			            }
144			        }
145		            token = null;
146		            for (;;) {
147		                if (token == null) {
148		                    token = x.nextToken();
149		                }
150		                if (token == null) {
151		                	throw x.syntaxError("Misshaped tag");
152		                }
153		                if (!(token instanceof String)) {
154		                	break;
155		                }
156
157//		              attribute = value
158
159	                    attribute = (String)token;
160			        	if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) {
161                            throw x.syntaxError("Reserved attribute.");
162			        	}
163	                    token = x.nextToken();
164	                    if (token == XML.EQ) {
165	                        token = x.nextToken();
166	                        if (!(token instanceof String)) {
167	                            throw x.syntaxError("Missing value");
168	                        }
169	                        newjo.accumulate(attribute, JSONObject.stringToValue((String)token));
170	                        token = null;
171	                    } else {
172	                    	newjo.accumulate(attribute, "");
173	                    }
174		            }
175                    if (arrayForm && newjo.length() > 0) {
176                    	newja.put(newjo);
177                    }
178
179// Empty tag <.../>
180
181	                if (token == XML.SLASH) {
182	                    if (x.nextToken() != XML.GT) {
183	                        throw x.syntaxError("Misshaped tag");
184	                    }
185	                    if (ja == null) {
186	                    	if (arrayForm) {
187	                    		return newja;
188	                    	} else {
189	                    		return newjo;
190	                    	}
191	                    }
192
193// Content, between <...> and </...>
194
195	                } else {
196	                	if (token != XML.GT) {
197	                		throw x.syntaxError("Misshaped tag");
198	                	}
199	                	closeTag = (String)parse(x, arrayForm, newja);
200	                	if (closeTag != null) {
201		                	if (!closeTag.equals(tagName)) {
202		                		throw x.syntaxError("Mismatched '" + tagName +
203		                				"' and '" + closeTag + "'");
204					        }
205		                	tagName = null;
206		            		if (!arrayForm && newja.length() > 0) {
207		            			newjo.put("childNodes", newja);
208		            		}
209		                	if (ja == null) {
210		                    	if (arrayForm) {
211		                    		return newja;
212		                    	} else {
213		                    		return newjo;
214		                    	}
215		                	}
216	                	}
217                	}
218	            }
219		    } else {
220		    	if (ja != null) {
221		    		ja.put(token instanceof String ?
222		    				JSONObject.stringToValue((String)token) : token);
223		    	}
224		    }
225        }
226    }
227
228
229    /**
230     * Convert a well-formed (but not necessarily valid) XML string into a
231     * JSONArray using the JsonML transform. Each XML tag is represented as
232     * a JSONArray in which the first element is the tag name. If the tag has
233     * attributes, then the second element will be JSONObject containing the
234     * name/value pairs. If the tag contains children, then strings and
235     * JSONArrays will represent the child tags.
236     * Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
237     * @param string The source string.
238     * @return A JSONArray containing the structured data from the XML string.
239     * @throws JSONException
240     */
241    public static JSONArray toJSONArray(String string) throws JSONException {
242    	return toJSONArray(new XMLTokener(string));
243    }
244
245
246    /**
247     * Convert a well-formed (but not necessarily valid) XML string into a
248     * JSONArray using the JsonML transform. Each XML tag is represented as
249     * a JSONArray in which the first element is the tag name. If the tag has
250     * attributes, then the second element will be JSONObject containing the
251     * name/value pairs. If the tag contains children, then strings and
252     * JSONArrays will represent the child content and tags.
253     * Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
254     * @param x An XMLTokener.
255     * @return A JSONArray containing the structured data from the XML string.
256     * @throws JSONException
257     */
258    public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
259    	return (JSONArray)parse(x, true, null);
260    }
261
262
263
264    /**
265     * Convert a well-formed (but not necessarily valid) XML string into a
266     * JSONObject using the JsonML transform. Each XML tag is represented as
267     * a JSONObject with a "tagName" property. If the tag has attributes, then
268     * the attributes will be in the JSONObject as properties. If the tag
269     * contains children, the object will have a "childNodes" property which
270     * will be an array of strings and JsonML JSONObjects.
271
272     * Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
273     * @param x An XMLTokener of the XML source text.
274     * @return A JSONObject containing the structured data from the XML string.
275     * @throws JSONException
276     */
277    public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
278       	return (JSONObject)parse(x, false, null);
279    }
280    /**
281     * Convert a well-formed (but not necessarily valid) XML string into a
282     * JSONObject using the JsonML transform. Each XML tag is represented as
283     * a JSONObject with a "tagName" property. If the tag has attributes, then
284     * the attributes will be in the JSONObject as properties. If the tag
285     * contains children, the object will have a "childNodes" property which
286     * will be an array of strings and JsonML JSONObjects.
287
288     * Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
289     * @param string The XML source text.
290     * @return A JSONObject containing the structured data from the XML string.
291     * @throws JSONException
292     */
293    public static JSONObject toJSONObject(String string) throws JSONException {
294    	return toJSONObject(new XMLTokener(string));
295    }
296
297
298    /**
299     * Reverse the JSONML transformation, making an XML text from a JSONArray.
300     * @param ja A JSONArray.
301     * @return An XML string.
302     * @throws JSONException
303     */
304    public static String toString(JSONArray ja) throws JSONException {
305    	Object		 e;
306    	int			 i;
307    	JSONObject   jo;
308    	String       k;
309	    Iterator     keys;
310	    int			 length;
311    	StringBuffer sb = new StringBuffer();
312	    String       tagName;
313	    String       v;
314
315// Emit <tagName
316
317    	tagName = ja.getString(0);
318		XML.noSpace(tagName);
319		tagName = XML.escape(tagName);
320		sb.append('<');
321		sb.append(tagName);
322
323		e = ja.opt(1);
324		if (e instanceof JSONObject) {
325			i = 2;
326			jo = (JSONObject)e;
327
328// Emit the attributes
329
330	        keys = jo.keys();
331	        while (keys.hasNext()) {
332	            k = keys.next().toString();
333            	XML.noSpace(k);
334	            v = jo.optString(k);
335	            if (v != null) {
336		            sb.append(' ');
337		            sb.append(XML.escape(k));
338		            sb.append('=');
339		            sb.append('"');
340		            sb.append(XML.escape(v));
341		            sb.append('"');
342	            }
343	        }
344		} else {
345			i = 1;
346		}
347
348//Emit content in body
349
350		length = ja.length();
351		if (i >= length) {
352	        sb.append('/');
353	        sb.append('>');
354		} else {
355	        sb.append('>');
356			do {
357			    e = ja.get(i);
358			    i += 1;
359			    if (e != null) {
360			    	if (e instanceof String) {
361			    		sb.append(XML.escape(e.toString()));
362					} else if (e instanceof JSONObject) {
363						sb.append(toString((JSONObject)e));
364					} else if (e instanceof JSONArray) {
365						sb.append(toString((JSONArray)e));
366					}
367			    }
368			} while (i < length);
369			sb.append('<');
370	        sb.append('/');
371			sb.append(tagName);
372	        sb.append('>');
373	    }
374        return sb.toString();
375    }
376
377    /**
378     * Reverse the JSONML transformation, making an XML text from a JSONObject.
379     * The JSONObject must contain a "tagName" property. If it has children,
380     * then it must have a "childNodes" property containing an array of objects.
381     * The other properties are attributes with string values.
382     * @param jo A JSONObject.
383     * @return An XML string.
384     * @throws JSONException
385     */
386	public static String toString(JSONObject jo) throws JSONException {
387	    StringBuffer sb = new StringBuffer();
388	    Object		 e;
389	    int          i;
390	    JSONArray    ja;
391	    String       k;
392	    Iterator     keys;
393	    int          len;
394	    String       tagName;
395	    String       v;
396
397//Emit <tagName
398
399		tagName = jo.optString("tagName");
400		if (tagName == null) {
401			return XML.escape(jo.toString());
402		}
403		XML.noSpace(tagName);
404		tagName = XML.escape(tagName);
405		sb.append('<');
406		sb.append(tagName);
407
408//Emit the attributes
409
410        keys = jo.keys();
411        while (keys.hasNext()) {
412            k = keys.next().toString();
413            if (!k.equals("tagName") && !k.equals("childNodes")) {
414            	XML.noSpace(k);
415	            v = jo.optString(k);
416	            if (v != null) {
417		            sb.append(' ');
418		            sb.append(XML.escape(k));
419		            sb.append('=');
420		            sb.append('"');
421		            sb.append(XML.escape(v));
422		            sb.append('"');
423	            }
424            }
425        }
426
427//Emit content in body
428
429		ja = jo.optJSONArray("childNodes");
430		if (ja == null) {
431	        sb.append('/');
432	        sb.append('>');
433		} else {
434	        sb.append('>');
435			len = ja.length();
436			for (i = 0; i < len; i += 1) {
437			    e = ja.get(i);
438			    if (e != null) {
439			    	if (e instanceof String) {
440			    		sb.append(XML.escape(e.toString()));
441					} else if (e instanceof JSONObject) {
442						sb.append(toString((JSONObject)e));
443					} else if (e instanceof JSONArray) {
444						sb.append(toString((JSONArray)e));
445					}
446			    }
447			}
448			sb.append('<');
449	        sb.append('/');
450			sb.append(tagName);
451	        sb.append('>');
452	    }
453        return sb.toString();
454    }
455}