001    package org.apache.tapestry.json;
002    
003    /*
004     Copyright (c) 2002 JSON.org
005    
006     Permission is hereby granted, free of charge, to any person obtaining a copy
007     of this software and associated documentation files (the "Software"), to deal
008     in the Software without restriction, including without limitation the rights
009     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010     copies of the Software, and to permit persons to whom the Software is
011     furnished to do so, subject to the following conditions:
012    
013     The above copyright notice and this permission notice shall be included in all
014     copies or substantial portions of the Software.
015    
016     The Software shall be used for Good, not Evil.
017    
018     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
021     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
023     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
024     SOFTWARE.
025     */
026    
027    import java.text.ParseException;
028    
029    /**
030     * This provides static methods to convert comma delimited text into a
031     * JSONArray, and to covert a JSONArray into comma delimited text. Comma
032     * delimited text is a very popular format for data interchange. It is
033     * understood by most database, spreadsheet, and organizer programs.
034     * <p>
035     * Each row of text represents a row in a table or a data record. Each row ends
036     * with a NEWLINE character. Each row contains one or more values. Values are
037     * separated by commas. A value can contain any character except for comma,
038     * unless is is wrapped in single quotes or double quotes.
039     * <p>
040     * The first row usually contains the names of the columns.
041     * <p>
042     * A comma delimited list can be converted into a JSONArray of JSONObjects. The
043     * names for the elements in the JSONObjects can be taken from the names in the
044     * first row.
045     */
046    public final class CDL
047    {
048    
049        /* defeat instantiation */
050        private CDL()
051        {
052        }
053        
054        /**
055         * Get the next value. The value can be wrapped in quotes. The value can be
056         * empty.
057         * 
058         * @param x
059         *            A JSONTokener of the source text.
060         * @return The value string, or null if empty.
061         * @throws java.text.ParseException
062         *             if the quoted string is badly formed.
063         */
064        private static String getValue(JSONTokener x)
065            throws java.text.ParseException
066        {
067            char c;
068            do
069            {
070                c = x.next();
071            } while(c <= ' ' && c != 0);
072            switch(c)
073            {
074            case 0:
075                return null;
076            case '"':
077            case '\'':
078                return x.nextString(c);
079            case ',':
080                x.back();
081                return "";
082            default:
083                x.back();
084                return x.nextTo(',');
085            }
086        }
087    
088        /**
089         * Produce a JSONArray of strings from a row of comma delimited values.
090         * 
091         * @param x
092         *            A JSONTokener of the source text.
093         * @return A JSONArray of strings.
094         * @throws ParseException
095         */
096        public static JSONArray rowToJSONArray(JSONTokener x)
097            throws ParseException
098        {
099            JSONArray ja = new JSONArray();
100            while(true)
101            {
102                String value = getValue(x);
103                if (value == null) { return null; }
104                ja.put(value);
105                while(true)
106                {
107                    char c = x.next();
108                    if (c == ',')
109                    {
110                        break;
111                    }
112                    if (c != ' ')
113                    {
114                        if (c == '\n' || c == '\r' || c == 0) { return ja; }
115                        throw x.syntaxError("Bad character '" + c + "' (" + (int) c
116                                + ").");
117                    }
118                }
119            }
120        }
121    
122        /**
123         * Produce a JSONObject from a row of comma delimited text, using a parallel
124         * JSONArray of strings to provides the names of the elements.
125         * 
126         * @param names
127         *            A JSONArray of names. This is commonly obtained from the first
128         *            row of a comma delimited text file using the rowToJSONArray
129         *            method.
130         * @param x
131         *            A JSONTokener of the source text.
132         * @return A JSONObject combining the names and values.
133         * @throws ParseException
134         */
135        public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x)
136            throws ParseException
137        {
138            JSONArray ja = rowToJSONArray(x);
139            return ja != null ? ja.toJSONObject(names) : null;
140        }
141    
142        /**
143         * Produce a JSONArray of JSONObjects from a comma delimited text string,
144         * using the first row as a source of names.
145         * 
146         * @param string
147         *            The comma delimited text.
148         * @return A JSONArray of JSONObjects.
149         * @throws ParseException
150         */
151        public static JSONArray toJSONArray(String string)
152            throws ParseException
153        {
154            return toJSONArray(new JSONTokener(string));
155        }
156    
157        /**
158         * Produce a JSONArray of JSONObjects from a comma delimited text string,
159         * using the first row as a source of names.
160         * 
161         * @param x
162         *            The JSONTokener containing the comma delimited text.
163         * @return A JSONArray of JSONObjects.
164         * @throws ParseException
165         */
166        public static JSONArray toJSONArray(JSONTokener x)
167            throws ParseException
168        {
169            return toJSONArray(rowToJSONArray(x), x);
170        }
171    
172        /**
173         * Produce a JSONArray of JSONObjects from a comma delimited text string
174         * using a supplied JSONArray as the source of element names.
175         * 
176         * @param names
177         *            A JSONArray of strings.
178         * @param string
179         *            The comma delimited text.
180         * @return A JSONArray of JSONObjects.
181         * @throws ParseException
182         */
183        public static JSONArray toJSONArray(JSONArray names, String string)
184            throws ParseException
185        {
186            return toJSONArray(names, new JSONTokener(string));
187        }
188    
189        /**
190         * Produce a JSONArray of JSONObjects from a comma delimited text string
191         * using a supplied JSONArray as the source of element names.
192         * 
193         * @param names
194         *            A JSONArray of strings.
195         * @param x
196         *            A JSONTokener of the source text.
197         * @return A JSONArray of JSONObjects.
198         * @throws java.text.ParseException
199         */
200        public static JSONArray toJSONArray(JSONArray names, JSONTokener x)
201            throws java.text.ParseException
202        {
203            if (names == null || names.length() == 0) { return null; }
204            JSONArray ja = new JSONArray();
205            while(true)
206            {
207                JSONObject jo = rowToJSONObject(names, x);
208                if (jo == null)
209                {
210                    break;
211                }
212                ja.put(jo);
213            }
214            if (ja.length() == 0) { return null; }
215            return ja;
216        }
217    
218        /**
219         * Produce a comma delimited text row from a JSONArray. Values containing
220         * the comma character will be quoted.
221         * 
222         * @param ja
223         *            A JSONArray of strings.
224         * @return A string ending in NEWLINE.
225         */
226        public static String rowToString(JSONArray ja)
227        {
228            StringBuffer sb = new StringBuffer();
229            for(int i = 0; i < ja.length(); i += 1)
230            {
231                if (i > 0)
232                {
233                    sb.append(',');
234                }
235                Object o = ja.opt(i);
236                if (o != null)
237                {
238                    String s = o.toString();
239                    if (s.indexOf(',') >= 0)
240                    {
241                        if (s.indexOf('"') >= 0)
242                        {
243                            sb.append('\'');
244                            sb.append(s);
245                            sb.append('\'');
246                        }
247                        else
248                        {
249                            sb.append('"');
250                            sb.append(s);
251                            sb.append('"');
252                        }
253                    }
254                    else
255                    {
256                        sb.append(s);
257                    }
258                }
259            }
260            sb.append('\n');
261            return sb.toString();
262    
263        }
264    
265        /**
266         * Produce a comma delimited text from a JSONArray of JSONObjects. The first
267         * row will be a list of names obtained by inspecting the first JSONObject.
268         * 
269         * @param ja
270         *            A JSONArray of JSONObjects.
271         * @return A comma delimited text.
272         */
273        public static String toString(JSONArray ja)
274        {
275            JSONObject jo = ja.optJSONObject(0);
276            if (jo != null)
277            {
278                JSONArray names = jo.names();
279                if (names != null) { return rowToString(names)
280                        + toString(names, ja); }
281            }
282            return null;
283        }
284    
285        /**
286         * Produce a comma delimited text from a JSONArray of JSONObjects using a
287         * provided list of names. The list of names is not included in the output.
288         * 
289         * @param names
290         *            A JSONArray of strings.
291         * @param ja
292         *            A JSONArray of JSONObjects.
293         * @return A comma delimited text.
294         */
295        public static String toString(JSONArray names, JSONArray ja)
296        {
297            if (names == null || names.length() == 0) { return null; }
298            StringBuffer sb = new StringBuffer();
299            for(int i = 0; i < ja.length(); i += 1)
300            {
301                JSONObject jo = ja.optJSONObject(i);
302                if (jo != null)
303                {
304                    sb.append(rowToString(jo.toJSONArray(names)));
305                }
306            }
307            return sb.toString();
308        }
309    }