001/*
002 * Copyright 2001-2004 The Apache Software Foundation.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Bogdan Stefanescu
018 */
019package org.nuxeo.apidoc.introspection;
020
021import java.io.IOException;
022import java.io.Writer;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Calendar;
026import java.util.Date;
027import java.util.TimeZone;
028
029import javax.xml.namespace.QName;
030
031/**
032 * This file contains code from org.apache.commons.betwixt.XMLUtils
033 */
034public class XMLWriter {
035
036    protected static final String CRLF = System.getProperty("line.separator");
037
038    protected int indent;
039
040    protected Writer writer;
041
042    protected String crlf;
043
044    protected boolean emitHeader = true;
045
046    protected String encoding;
047
048    protected ArrayList<String> globalNsMap;
049
050    protected Element element; // current element
051
052    protected int depth = -1;
053
054    public XMLWriter(Writer writer) {
055        this(writer, 0);
056    }
057
058    public XMLWriter(Writer writer, int indent) {
059        this(writer, indent, CRLF);
060    }
061
062    public XMLWriter(Writer writer, int indent, String crlf) {
063        this.writer = writer;
064        this.indent = indent;
065        this.crlf = crlf;
066    }
067
068    public void setEncoding(String encoding) {
069        this.encoding = encoding;
070    }
071
072    public String getEncoding() {
073        return encoding;
074    }
075
076    public void putXmlns(String uri) {
077        putXmlns("", uri);
078    }
079
080    public void putXmlns(String prefix, String uri) {
081        if (globalNsMap == null) {
082            globalNsMap = new ArrayList<>();
083        }
084        globalNsMap.add(uri);
085        globalNsMap.add(prefix);
086    }
087
088    public String getXmlNs(String uri) {
089        if (globalNsMap != null) {
090            for (int i = 0, len = globalNsMap.size(); i < len; i += 2) {
091                if (uri.equals(globalNsMap.get(i))) {
092                    return globalNsMap.get(i + 1);
093                }
094            }
095        }
096        return null;
097    }
098
099    public void setIndent(int indent) {
100        this.indent = indent;
101    }
102
103    public int getIndent() {
104        return indent;
105    }
106
107    public void setCRLF(String crlf) {
108        this.crlf = crlf;
109    }
110
111    public String getCRLF() {
112        return crlf;
113    }
114
115    public void setWriter(Writer writer) {
116        this.writer = writer;
117    }
118
119    public Writer getWriter() {
120        return writer;
121    }
122
123    public void setEmitHeader(boolean emitHeader) {
124        this.emitHeader = emitHeader;
125    }
126
127    public boolean isEmitHeader() {
128        return emitHeader;
129    }
130
131    public void flush() throws IOException {
132        writer.flush();
133    }
134
135    public void close() throws IOException {
136        writer.close();
137    }
138
139    protected void done() throws IOException {
140        writer.flush();
141        // TODO check for errors
142    }
143
144    public XMLWriter write(String text) throws IOException {
145        writer.write(text);
146        return this;
147    }
148
149    public void indent(String text) throws IOException {
150        if (indent > 0) {
151            writer.write(crlf);
152            char[] buf = new char[depth * indent];
153            Arrays.fill(buf, ' ');
154            writer.write(buf);
155        }
156        writer.write(text);
157    }
158
159    public XMLWriter element(String name) throws IOException {
160        if (element != null && !element.isContainer) { // a non closed sibling -
161                                                       // close it
162            pop();
163            writer.write("/>");
164        }
165        indent("<");
166        writer.write(name);
167        if (element == null) { // the first element - write any global ns
168            if (globalNsMap != null) {
169                for (int i = 0, len = globalNsMap.size(); i < len; i += 2) {
170                    String prefix = globalNsMap.get(i + 1);
171                    String uri = globalNsMap.get(i);
172                    writer.write(" xmlns");
173                    if (prefix != null && prefix.length() > 0) {
174                        writer.write(":");
175                        writer.write(prefix);
176                    }
177                    writer.write("=\"");
178                    writer.write(uri);
179                    writer.write("\"");
180                }
181            }
182        }
183        push(name); // push myself to the stack
184        return this;
185    }
186
187    public XMLWriter start() throws IOException {
188        depth++;
189        if (element == null) { // the root
190            if (emitHeader) {
191                if (encoding != null) {
192                    writer.write("<?xml version=\"1.0\" encoding=" + encoding + "?>");
193                } else {
194                    writer.write("<?xml version=\"1.0\"?>");
195                }
196                writer.write(crlf);
197            }
198        } else {
199            element.isContainer = true;
200            writer.write(">");
201        }
202        return this;
203    }
204
205    public XMLWriter end() throws IOException {
206        depth--;
207        if (element == null) {
208            done();
209        } else {
210            if (!element.isContainer) { // a child element - close it
211                pop();
212                writer.write("/>");
213            }
214            Element myself = pop(); // close myself
215            indent("</");
216            writer.write(myself.name);
217            writer.write(">");
218        }
219        return this;
220    }
221
222    public XMLWriter content(String text) throws IOException {
223        start();
224        depth--;
225        writer.write(text);
226        Element elem = pop(); // close myself
227        writer.write("</");
228        writer.write(elem.name);
229        writer.write(">");
230        return this;
231    }
232
233    public XMLWriter econtent(String text) throws IOException {
234        return content(escapeBodyValue(text));
235    }
236
237    public XMLWriter content(boolean value) throws IOException {
238        return content(value ? "true" : "false");
239    }
240
241    public XMLWriter content(Date value) throws IOException {
242        return content(DateTimeFormat.abderaFormat(value));
243    }
244
245    public XMLWriter text(String text) throws IOException {
246        indent(text);
247        return this;
248    }
249
250    public XMLWriter etext(String text) throws IOException {
251        return text(escapeBodyValue(text));
252    }
253
254    public XMLWriter attr(String name, Object value) throws IOException {
255        writer.write(" ");
256        writer.write(name);
257        writer.write("=\"");
258        writer.write(value.toString());
259        writer.write("\"");
260        return this;
261    }
262
263    public XMLWriter eattr(String name, Object value) throws IOException {
264        return attr(name, escapeAttributeValue(value));
265    }
266
267    public XMLWriter xmlns(String value) throws IOException {
268        attr("xmlns", value);
269        element.putXmlns("", value);
270        return this;
271    }
272
273    public XMLWriter xmlns(String name, String value) throws IOException {
274        attr("xmlns:" + name, value);
275        element.putXmlns(name, value);
276        return this;
277    }
278
279    public XMLWriter attr(String name) throws IOException {
280        writer.write(" ");
281        writer.write(name);
282        writer.write("=\"");
283        return this;
284    }
285
286    public XMLWriter string(String value) throws IOException {
287        writer.write(value);
288        writer.write("\"");
289        return this;
290    }
291
292    public XMLWriter object(Object value) throws IOException {
293        writer.write(value.toString());
294        writer.write("\"");
295        return this;
296    }
297
298    public XMLWriter date(Date value) throws IOException {
299        writer.write(format(value));
300        writer.write("\"");
301        return this;
302    }
303
304    public XMLWriter integer(long value) throws IOException {
305        writer.write(Long.toString(value));
306        writer.write("\"");
307        return this;
308    }
309
310    public XMLWriter number(double value) throws IOException {
311        writer.write(Double.toString(value));
312        writer.write("\"");
313        return this;
314    }
315
316    public XMLWriter bool(String name, boolean value) throws IOException {
317        attr(name, value ? "true" : "false");
318        return this;
319    }
320
321    public XMLWriter integer(String name, long value) throws IOException {
322        return attr(name, Long.toString(value));
323    }
324
325    public XMLWriter number(String name, double value) throws IOException {
326        return attr(name, Double.toString(value));
327    }
328
329    public XMLWriter date(String name, Date value) throws IOException {
330        return attr(name, value.toString());
331    }
332
333    public String resolve(QName name) {
334        String prefix = null;
335        String uri = name.getNamespaceURI();
336        if (element != null) {
337            prefix = element.getXmlNs(uri);
338            if (prefix == null) {
339                prefix = getXmlNs(uri);
340            }
341        } else {
342            prefix = getXmlNs(uri);
343        }
344        if (prefix == null) {
345            return name.toString();
346        }
347        if (prefix.length() == 0) {
348            return name.getLocalPart();
349        }
350        return prefix + ":" + name.getLocalPart();
351    }
352
353    public XMLWriter element(QName name) throws IOException {
354        return element(resolve(name));
355    }
356
357    public XMLWriter attr(QName name, Object value) throws IOException {
358        return attr(resolve(name), value);
359    }
360
361    public XMLWriter eattr(QName name, Object value) throws IOException {
362        return eattr(resolve(name), value);
363    }
364
365    public XMLWriter attr(QName name) throws IOException {
366        return attr(resolve(name));
367    }
368
369    public XMLWriter bool(QName name, boolean value) throws IOException {
370        return bool(resolve(name), value);
371    }
372
373    public XMLWriter integer(QName name, long value) throws IOException {
374        return integer(resolve(name), value);
375    }
376
377    public XMLWriter number(QName name, double value) throws IOException {
378        return number(resolve(name), value);
379    }
380
381    public XMLWriter date(QName name, Date value) throws IOException {
382        return date(resolve(name), value);
383    }
384
385    public static String format(Date date) {
386        StringBuilder sb = new StringBuilder();
387        Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
388        c.setTime(date);
389        sb.append(c.get(Calendar.YEAR));
390        sb.append('-');
391        int f = c.get(Calendar.MONTH);
392        if (f < 9) {
393            sb.append('0');
394        }
395        sb.append(f + 1);
396        sb.append('-');
397        f = c.get(Calendar.DATE);
398        if (f < 10) {
399            sb.append('0');
400        }
401        sb.append(f);
402        sb.append('T');
403        f = c.get(Calendar.HOUR_OF_DAY);
404        if (f < 10) {
405            sb.append('0');
406        }
407        sb.append(f);
408        sb.append(':');
409        f = c.get(Calendar.MINUTE);
410        if (f < 10) {
411            sb.append('0');
412        }
413        sb.append(f);
414        sb.append(':');
415        f = c.get(Calendar.SECOND);
416        if (f < 10) {
417            sb.append('0');
418        }
419        sb.append(f);
420        sb.append('.');
421        f = c.get(Calendar.MILLISECOND);
422        if (f < 100) {
423            sb.append('0');
424        }
425        if (f < 10) {
426            sb.append('0');
427        }
428        sb.append(f);
429        sb.append('Z');
430        return sb.toString();
431    }
432
433    public static final String LESS_THAN_ENTITY = "&lt;";
434
435    public static final String GREATER_THAN_ENTITY = "&gt;";
436
437    public static final String AMPERSAND_ENTITY = "&amp;";
438
439    public static final String APOSTROPHE_ENTITY = "&apos;";
440
441    public static final String QUOTE_ENTITY = "&quot;";
442
443    /**
444     * <p>
445     * Escape the <code>toString</code> of the given object. For use as body
446     * text.
447     * </p>
448     *
449     * @param value
450     *            escape <code>value.toString()</code>
451     * @return text with escaped delimiters
452     */
453    public static final String escapeBodyValue(Object value) {
454        StringBuffer buffer = new StringBuffer(value.toString());
455        for (int i = 0, size = buffer.length(); i < size; i++) {
456            switch (buffer.charAt(i)) {
457            case '<':
458                buffer.replace(i, i + 1, LESS_THAN_ENTITY);
459                size += 3;
460                i += 3;
461                break;
462            case '>':
463                buffer.replace(i, i + 1, GREATER_THAN_ENTITY);
464                size += 3;
465                i += 3;
466                break;
467            case '&':
468                buffer.replace(i, i + 1, AMPERSAND_ENTITY);
469                size += 4;
470                i += 4;
471                break;
472            }
473        }
474        return buffer.toString();
475    }
476
477    /**
478     * <p>
479     * Escape the <code>toString</code> of the given object. For use in an
480     * attribute value.
481     * </p>
482     *
483     * @param value
484     *            escape <code>value.toString()</code>
485     * @return text with characters restricted (for use in attributes) escaped
486     */
487    public static final String escapeAttributeValue(Object value) {
488        StringBuffer buffer = new StringBuffer(value.toString());
489        for (int i = 0, size = buffer.length(); i < size; i++) {
490            switch (buffer.charAt(i)) {
491            case '<':
492                buffer.replace(i, i + 1, LESS_THAN_ENTITY);
493                size += 3;
494                i += 3;
495                break;
496            case '>':
497                buffer.replace(i, i + 1, GREATER_THAN_ENTITY);
498                size += 3;
499                i += 3;
500                break;
501            case '&':
502                buffer.replace(i, i + 1, AMPERSAND_ENTITY);
503                size += 4;
504                i += 4;
505                break;
506            case '\'':
507                buffer.replace(i, i + 1, APOSTROPHE_ENTITY);
508                size += 5;
509                i += 5;
510                break;
511            case '\"':
512                buffer.replace(i, i + 1, QUOTE_ENTITY);
513                size += 5;
514                i += 5;
515                break;
516            }
517        }
518        return buffer.toString();
519    }
520
521    Element push(String name) {
522        element = new Element(name);
523        return element;
524    }
525
526    Element pop() {
527        Element el = element;
528        if (el != null) {
529            element = el.parent;
530        }
531        return el;
532    }
533
534    class Element {
535        final String name;
536
537        final Element parent;
538
539        ArrayList<String> nsMap;
540
541        boolean isContainer;
542
543        Element(String name) {
544            this.name = name;
545            this.parent = element;
546        }
547
548        void putXmlns(String prefix, String uri) {
549            if (nsMap == null) {
550                nsMap = new ArrayList<>();
551            }
552            nsMap.add(uri);
553            nsMap.add(prefix);
554        }
555
556        String getXmlNs(String uri) {
557            if (nsMap != null) {
558                for (int i = 0, len = nsMap.size(); i < len; i += 2) {
559                    if (uri.equals(nsMap.get(i))) {
560                        return nsMap.get(i + 1);
561                    }
562                }
563            }
564            if (parent != null) {
565                return parent.getXmlNs(uri);
566            }
567            return null;
568        }
569    }
570
571}