001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.xml;
014
015import java.io.*;
016import java.net.*;
017
018import org.apache.juneau.*;
019import org.apache.juneau.serializer.*;
020import org.apache.juneau.xml.annotation.*;
021
022/**
023 * Specialized writer for serializing XML.
024 *
025 * <ul class='notes'>
026 *    <li>
027 *       This class is not intended for external use.
028 * </ul>
029 */
030public class XmlWriter extends SerializerWriter {
031
032   private String defaultNsPrefix;
033   private boolean enableNs;
034
035   /**
036    * Constructor.
037    *
038    * @param out The wrapped writer.
039    * @param useWhitespace If <jk>true</jk> XML elements will be indented.
040    * @param maxIndent The maximum indentation level.
041    * @param trimStrings If <jk>true</jk>, strings should be trimmed before they're serialized.
042    * @param quoteChar The quote character to use for attributes.  Should be <js>'\''</js> or <js>'"'</js>.
043    * @param uriResolver The URI resolver for resolving URIs to absolute or root-relative form.
044    * @param enableNs Flag to indicate if XML namespaces are enabled.
045    * @param defaultNamespace The default namespace if XML namespaces are enabled.
046    */
047   public XmlWriter(Writer out, boolean useWhitespace, int maxIndent, boolean trimStrings, char quoteChar,
048         UriResolver uriResolver, boolean enableNs, Namespace defaultNamespace) {
049      super(out, useWhitespace, maxIndent, trimStrings, quoteChar, uriResolver);
050      this.enableNs = enableNs;
051      this.defaultNsPrefix = defaultNamespace == null ? null : defaultNamespace.name;
052   }
053
054   /**
055    * Writes an opening tag to the output:  <code><xt>&lt;ns:name</xt></code>
056    *
057    * @param ns The namespace.  Can be <jk>null</jk>.
058    * @param name The element name.
059    * @param needsEncoding If <jk>true</jk>, element name will be encoded.
060    * @return This object (for method chaining).
061    * @throws IOException If a problem occurred.
062    */
063   public XmlWriter oTag(String ns, String name, boolean needsEncoding) throws IOException {
064      append('<');
065      if (enableNs && ns != null && ! (ns.isEmpty() || ns.equals(defaultNsPrefix)))
066         append(ns).append(':');
067      if (needsEncoding)
068         XmlUtils.encodeElementName(out, name);
069      else
070         append(name);
071      return this;
072   }
073
074   /**
075    * Shortcut for <code>oTag(ns, name, <jk>false</jk>);</code>
076    *
077    * @param ns The namespace.  Can be <jk>null</jk>.
078    * @param name The element name.
079    * @return This object (for method chaining).
080    * @throws IOException If a problem occurred.
081    */
082   public XmlWriter oTag(String ns, String name) throws IOException {
083      return oTag(ns, name, false);
084   }
085
086   /**
087    * Shortcut for <code>oTag(<jk>null</jk>, name, <jk>false</jk>);</code>
088    *
089    * @param name The element name.
090    * @return This object (for method chaining).
091    * @throws IOException If a problem occurred.
092    */
093   public XmlWriter oTag(String name) throws IOException {
094      return oTag(null, name, false);
095   }
096
097   /**
098    * Shortcut for <c>i(indent).oTag(ns, name, needsEncoding);</c>
099    *
100    * @param indent The number of prefix tabs to add.
101    * @param ns The namespace.  Can be <jk>null</jk>.
102    * @param name The element name.
103    * @param needsEncoding If <jk>true</jk>, element name will be encoded.
104    * @return This object (for method chaining).
105    * @throws IOException If a problem occurred.
106    */
107   public XmlWriter oTag(int indent, String ns, String name, boolean needsEncoding) throws IOException {
108      return i(indent).oTag(ns, name, needsEncoding);
109   }
110
111   /**
112    * Shortcut for <code>i(indent).oTag(ns, name, <jk>false</jk>);</code>
113    *
114    * @param indent The number of prefix tabs to add.
115    * @param ns The namespace.  Can be <jk>null</jk>.
116    * @param name The element name.
117    * @return This object (for method chaining).
118    * @throws IOException If a problem occurred.
119    */
120   public XmlWriter oTag(int indent, String ns, String name) throws IOException {
121      return i(indent).oTag(ns, name, false);
122   }
123
124   /**
125    * Shortcut for <code>i(indent).oTag(<jk>null</jk>, name, <jk>false</jk>);</code>
126    *
127    * @param indent The number of prefix tabs to add.
128    * @param name The element name.
129    * @return This object (for method chaining).
130    * @throws IOException If a problem occurred.
131    */
132   public XmlWriter oTag(int indent, String name) throws IOException {
133      return i(indent).oTag(null, name, false);
134   }
135
136   /**
137    * Closes a tag.
138    *
139    * <p>
140    * Shortcut for <code>append(<js>'>'</js>);</code>
141    *
142    * @return This object (for method chaining).
143    * @throws IOException Thrown by underlying stream.
144    */
145   public XmlWriter cTag() throws IOException {
146      append('>');
147      return this;
148   }
149
150   /**
151    * Closes an empty tag.
152    *
153    * <p>
154    * Shortcut for <code>append(<js>'/'</js>).append(<js>'>'</js>);</code>
155    *
156    * @return This object (for method chaining).
157    * @throws IOException Thrown by underlying stream.
158    */
159   public XmlWriter ceTag() throws IOException {
160      append('/').append('>');
161      return this;
162   }
163
164   /**
165    * Writes a closed tag to the output:  <code><xt>&lt;ns:name/&gt;</xt></code>
166    *
167    * @param ns The namespace.  Can be <jk>null</jk>.
168    * @param name The element name.
169    * @param needsEncoding If <jk>true</jk>, element name will be encoded.
170    * @return This object (for method chaining).
171    * @throws IOException If a problem occurred.
172    */
173   public XmlWriter tag(String ns, String name, boolean needsEncoding) throws IOException {
174      append('<');
175      if (enableNs && ns != null && ! (ns.isEmpty() || ns.equals(defaultNsPrefix)))
176         append(ns).append(':');
177      if (needsEncoding)
178         XmlUtils.encodeElementName(out, name);
179      else
180         append(name);
181      return append('/').append('>');
182   }
183
184   /**
185    * Shortcut for <code>tag(ns, name, <jk>false</jk>);</code>
186    *
187    * @param ns The namespace.  Can be <jk>null</jk>.
188    * @param name The element name.
189    * @return This object (for method chaining).
190    * @throws IOException If a problem occurred.
191    */
192   public XmlWriter tag(String ns, String name) throws IOException {
193      return tag(ns, name, false);
194   }
195
196   /**
197    * Shortcut for <code>tag(<jk>null</jk>, name, <jk>false</jk>);</code>
198    *
199    * @param name The element name.
200    * @return This object (for method chaining).
201    * @throws IOException If a problem occurred.
202    */
203   public XmlWriter tag(String name) throws IOException {
204      return tag(null, name, false);
205   }
206
207   /**
208    * Shortcut for <code>i(indent).tag(<jk>null</jk>, name, <jk>false</jk>);</code>
209    *
210    * @param indent The number of prefix tabs to add.
211    * @param name The element name.
212    * @return This object (for method chaining).
213    * @throws IOException If a problem occurred.
214    */
215   public XmlWriter tag(int indent, String name) throws IOException {
216      return i(indent).tag(name);
217   }
218
219   /**
220    * Shortcut for <c>i(indent).tag(ns, name, needsEncoding);</c>
221    *
222    * @param indent The number of prefix tabs to add.
223    * @param ns The namespace.  Can be <jk>null</jk>.
224    * @param name The element name.
225    * @param needsEncoding If <jk>true</jk>, element name will be encoded.
226    * @return This object (for method chaining).
227    * @throws IOException If a problem occurred.
228    */
229   public XmlWriter tag(int indent, String ns, String name, boolean needsEncoding) throws IOException {
230      return i(indent).tag(ns, name, needsEncoding);
231   }
232
233   /**
234    * Shortcut for <code>i(indent).tag(ns, name, <jk>false</jk>);</code>
235    *
236    * @param indent The number of prefix tabs to add.
237    * @param ns The namespace.  Can be <jk>null</jk>.
238    * @param name The element name.
239    * @return This object (for method chaining).
240    * @throws IOException If a problem occurred.
241    */
242   public XmlWriter tag(int indent, String ns, String name) throws IOException {
243      return i(indent).tag(ns, name);
244   }
245
246
247   /**
248    * Writes a start tag to the output:  <code><xt>&lt;ns:name&gt;</xt></code>
249    *
250    * @param ns The namespace.  Can be <jk>null</jk>.
251    * @param name The element name.
252    * @param needsEncoding If <jk>true</jk>, element name will be encoded.
253    * @return This object (for method chaining).
254    * @throws IOException If a problem occurred.
255    */
256   public XmlWriter sTag(String ns, String name, boolean needsEncoding) throws IOException {
257      return oTag(ns, name, needsEncoding).append('>');
258   }
259
260   /**
261    * Shortcut for <code>sTag(ns, name, <jk>false</jk>);</code>
262    *
263    * @param ns The namespace.  Can be <jk>null</jk>.
264    * @param name The element name.
265    * @return This object (for method chaining).
266    * @throws IOException If a problem occurred.
267    */
268   public XmlWriter sTag(String ns, String name) throws IOException {
269      return sTag(ns, name, false);
270   }
271
272   /**
273    * Shortcut for <code>sTag(<jk>null</jk>, name, <jk>false</jk>);</code>
274    *
275    * @param name The element name.
276    * @return This object (for method chaining).
277    * @throws IOException If a problem occurred.
278    */
279   public XmlWriter sTag(String name) throws IOException {
280      return sTag(null, name);
281   }
282
283   /**
284    * Shortcut for <c>i(indent).sTag(ns, name, needsEncoding);</c>
285    *
286    * @param indent The number of prefix tabs to add.
287    * @param ns The namespace.  Can be <jk>null</jk>.
288    * @param name The element name.
289    * @param needsEncoding If <jk>true</jk>, element name will be encoded.
290    * @return This object (for method chaining).
291    * @throws IOException If a problem occurred.
292    */
293   public XmlWriter sTag(int indent, String ns, String name, boolean needsEncoding) throws IOException {
294      return i(indent).sTag(ns, name, needsEncoding);
295   }
296
297   /**
298    * Shortcut for <code>i(indent).sTag(ns, name, <jk>false</jk>);</code>
299    *
300    * @param indent The number of prefix tabs to add.
301    * @param ns The namespace.  Can be <jk>null</jk>.
302    * @param name The element name.
303    * @return This object (for method chaining).
304    * @throws IOException If a problem occurred.
305    */
306   public XmlWriter sTag(int indent, String ns, String name) throws IOException {
307      return i(indent).sTag(ns, name, false);
308   }
309
310   /**
311    * Shortcut for <code>i(indent).sTag(<jk>null</jk>, name, <jk>false</jk>);</code>
312    *
313    * @param indent The number of prefix tabs to add.
314    * @param name The element name.
315    * @return This object (for method chaining).
316    * @throws IOException If a problem occurred.
317    */
318   public XmlWriter sTag(int indent, String name) throws IOException {
319      return i(indent).sTag(null, name, false);
320   }
321
322
323   /**
324    * Writes an end tag to the output:  <code><xt>&lt;/ns:name&gt;</xt></code>
325    *
326    * @param ns The namespace.  Can be <jk>null</jk>.
327    * @param name The element name.
328    * @param needsEncoding If <jk>true</jk>, element name will be encoded.
329    * @return This object (for method chaining).
330    * @throws IOException If a problem occurred.
331    */
332   public XmlWriter eTag(String ns, String name, boolean needsEncoding) throws IOException {
333      append('<').append('/');
334      if (enableNs && ns != null && ! (ns.isEmpty() || ns.equals(defaultNsPrefix)))
335         append(ns).append(':');
336      if (needsEncoding)
337         XmlUtils.encodeElementName(out, name);
338      else
339         append(name);
340      return append('>');
341   }
342
343   /**
344    * Shortcut for <code>eTag(ns, name, <jk>false</jk>);</code>
345    *
346    * @param ns The namespace.  Can be <jk>null</jk>.
347    * @param name The element name.
348    * @return This object (for method chaining).
349    * @throws IOException If a problem occurred.
350    */
351   public XmlWriter eTag(String ns, String name) throws IOException {
352      return eTag(ns, name, false);
353   }
354
355   /**
356    * Shortcut for <code>eTag(<jk>null</jk>, name, <jk>false</jk>);</code>
357    *
358    * @param name The element name.
359    * @return This object (for method chaining).
360    * @throws IOException If a problem occurred.
361    */
362   public XmlWriter eTag(String name) throws IOException {
363      return eTag(null, name);
364   }
365
366   /**
367    * Shortcut for <c>i(indent).eTag(ns, name, needsEncoding);</c>
368    *
369    * @param indent The number of prefix tabs to add.
370    * @param ns The namespace.  Can be <jk>null</jk>.
371    * @param name The element name.
372    * @param needsEncoding If <jk>true</jk>, element name will be encoded.
373    * @return This object (for method chaining).
374    * @throws IOException If a problem occurred.
375    */
376   public XmlWriter eTag(int indent, String ns, String name, boolean needsEncoding) throws IOException {
377      return i(indent).eTag(ns, name, needsEncoding);
378   }
379
380   /**
381    * Shortcut for <code>i(indent).eTag(ns, name, <jk>false</jk>);</code>
382    *
383    * @param indent The number of prefix tabs to add.
384    * @param ns The namespace.  Can be <jk>null</jk>.
385    * @param name The element name.
386    * @return This object (for method chaining).
387    * @throws IOException If a problem occurred.
388    */
389   public XmlWriter eTag(int indent, String ns, String name) throws IOException {
390      return i(indent).eTag(ns, name, false);
391   }
392
393   /**
394    * Shortcut for <code>i(indent).eTag(<jk>null</jk>, name, <jk>false</jk>);</code>
395    *
396    * @param indent The number of prefix tabs to add.
397    * @param name The element name.
398    * @return This object (for method chaining).
399    * @throws IOException If a problem occurred.
400    */
401   public XmlWriter eTag(int indent, String name) throws IOException {
402      return i(indent).eTag(name);
403   }
404
405   /**
406    * Writes an attribute to the output:  <code><xa>ns:name</xa>=<xs>'value'</xs></code>
407    *
408    * @param ns The namespace.  Can be <jk>null</jk>.
409    * @param name The attribute name.
410    * @param value The attribute value.
411    * @param valNeedsEncoding If <jk>true</jk>, attribute name will be encoded.
412    * @return This object (for method chaining).
413    * @throws IOException If a problem occurred.
414    */
415   public XmlWriter attr(String ns, String name, Object value, boolean valNeedsEncoding) throws IOException {
416      return oAttr(ns, name).q().attrValue(value, valNeedsEncoding).q();
417   }
418
419   /**
420    * Shortcut for <code>attr(<jk>null</jk>, name, value, <jk>false</jk>);</code>
421    *
422    * @param name The attribute name.
423    * @param value The attribute value.
424    * @param valNeedsEncoding If <jk>true</jk>, attribute name will be encoded.
425    * @return This object (for method chaining).
426    * @throws IOException If a problem occurred.
427    */
428   public XmlWriter attr(String name, Object value, boolean valNeedsEncoding) throws IOException {
429      return attr(null, name, value, valNeedsEncoding);
430   }
431
432   /**
433    * Shortcut for <code>attr(ns, name, value, <jk>false</jk>);</code>
434    *
435    * @param ns The namespace.  Can be <jk>null</jk>.
436    * @param name The attribute name.
437    * @param value The attribute value.
438    * @return This object (for method chaining).
439    * @throws IOException If a problem occurred.
440    */
441   public XmlWriter attr(String ns, String name, Object value) throws IOException {
442      return oAttr(ns, name).q().attrValue(value, false).q();
443   }
444
445   /**
446    * Same as {@link #attr(String, String, Object)}, except pass in a {@link Namespace} object for the namespace.
447    *
448    * @param ns The namespace.  Can be <jk>null</jk>.
449    * @param name The attribute name.
450    * @param value The attribute value.
451    * @return This object (for method chaining).
452    * @throws IOException If a problem occurred.
453    */
454   public XmlWriter attr(Namespace ns, String name, Object value) throws IOException {
455      return oAttr(ns == null ? null : ns.name, name).q().attrValue(value, false).q();
456   }
457
458   /**
459    * Shortcut for <code>attr(<jk>null</jk>, name, value, <jk>false</jk>);</code>
460    *
461    * @param name The attribute name.
462    * @param value The attribute value.
463    * @return This object (for method chaining).
464    * @throws IOException If a problem occurred.
465    */
466   public XmlWriter attr(String name, Object value) throws IOException {
467      return attr((String)null, name, value);
468   }
469
470
471   /**
472    * Writes an open-ended attribute to the output:  <code><xa>ns:name</xa>=</code>
473    *
474    * @param ns The namespace.  Can be <jk>null</jk>.
475    * @param name The attribute name.
476    * @return This object (for method chaining).
477    * @throws IOException If a problem occurred.
478    */
479   public XmlWriter oAttr(String ns, String name) throws IOException {
480      append(' ');
481      if (enableNs && ns != null && ! (ns.isEmpty() || ns.equals(defaultNsPrefix)))
482         append(ns).append(':');
483      append(name).append('=');
484      return this;
485   }
486
487   /**
488    * Writes an open-ended attribute to the output:  <code><xa>ns:name</xa>=</code>
489    *
490    * @param ns The namespace.  Can be <jk>null</jk>.
491    * @param name The attribute name.
492    * @return This object (for method chaining).
493    * @throws IOException If a problem occurred.
494    */
495   public XmlWriter oAttr(Namespace ns, String name) throws IOException {
496      return oAttr(ns == null ? null : ns.name, name);
497   }
498
499   /**
500    * Writes an attribute with a URI value to the output:  <code><xa>ns:name</xa>=<xs>'uri-value'</xs></code>
501    *
502    * @param ns The namespace.  Can be <jk>null</jk>.
503    * @param name The attribute name.
504    * @param value The attribute value, convertible to a URI via <c>toString()</c>
505    * @return This object (for method chaining).
506    * @throws IOException If a problem occurred.
507    */
508   public XmlWriter attrUri(Namespace ns, String name, Object value) throws IOException {
509      return attr(ns, name, uriResolver.resolve(value));
510   }
511
512   /**
513    * Writes an attribute with a URI value to the output:  <code><xa>ns:name</xa>=<xs>'uri-value'</xs></code>
514    *
515    * @param ns The namespace.  Can be <jk>null</jk>.
516    * @param name The attribute name.
517    * @param value The attribute value, convertible to a URI via <c>toString()</c>
518    * @return This object (for method chaining).
519    * @throws IOException If a problem occurred.
520    */
521   public XmlWriter attrUri(String ns, String name, Object value) throws IOException {
522      return attr(ns, name, uriResolver.resolve(value), true);
523   }
524
525   /**
526    * Append an attribute with a URI value.
527    *
528    * @param name The attribute name.
529    * @param value The attribute value.  Can be any object whose <c>toString()</c> method returns a URI.
530    * @return This object (for method chaining);
531    * @throws IOException If a problem occurred.
532    */
533   public XmlWriter attrUri(String name, Object value) throws IOException {
534      return attrUri((String)null, name, value);
535   }
536
537   /**
538    * Shortcut for calling <code>text(o, <jk>false</jk>);</code>
539    *
540    * @param o The object being serialized.
541    * @return This object (for method chaining).
542    * @throws IOException If a problem occurred.
543    */
544   public XmlWriter text(Object o) throws IOException {
545      text(o, false);
546      return this;
547   }
548
549   /**
550    * Serializes and encodes the specified object as valid XML text.
551    *
552    * @param o The object being serialized.
553    * @param preserveWhitespace
554    *    If <jk>true</jk>, then we're serializing {@link XmlFormat#MIXED_PWS} or {@link XmlFormat#TEXT_PWS} content.
555    * @return This object (for method chaining).
556    * @throws IOException Thrown by underlying stream.
557    */
558   public XmlWriter text(Object o, boolean preserveWhitespace) throws IOException {
559      XmlUtils.encodeText(this, o, trimStrings, preserveWhitespace);
560      return this;
561   }
562
563   /**
564    * Same as {@link #text(Object)} but treats the value as a URL to resolved then serialized.
565    *
566    * @param o The object being serialized.
567    * @return This object (for method chaining).
568    * @throws IOException Thrown by underlying stream.
569    */
570   public XmlWriter textUri(Object o) throws IOException {
571      text(uriResolver.resolve(o), false);
572      return this;
573   }
574
575   private XmlWriter attrValue(Object o, boolean needsEncoding) throws IOException {
576      if (needsEncoding)
577         XmlUtils.encodeAttrValue(out, o, this.trimStrings);
578      else if (o instanceof URI || o instanceof URL)
579         append(uriResolver.resolve(o));
580      else
581         append(o);
582      return this;
583   }
584
585   @Override /* SerializerWriter */
586   public XmlWriter cr(int depth) throws IOException {
587      super.cr(depth);
588      return this;
589   }
590
591   @Override /* SerializerWriter */
592   public XmlWriter cre(int depth) throws IOException {
593      super.cre(depth);
594      return this;
595   }
596
597   @Override /* SerializerWriter */
598   public XmlWriter appendln(int indent, String text) throws IOException {
599      super.appendln(indent, text);
600      return this;
601   }
602
603   @Override /* SerializerWriter */
604   public XmlWriter appendln(String text) throws IOException {
605      super.appendln(text);
606      return this;
607   }
608
609   @Override /* SerializerWriter */
610   public XmlWriter append(int indent, String text) throws IOException {
611      super.append(indent, text);
612      return this;
613   }
614
615   @Override /* SerializerWriter */
616   public XmlWriter append(int indent, char c) throws IOException {
617      super.append(indent, c);
618      return this;
619   }
620
621   @Override /* SerializerWriter */
622   public XmlWriter s() throws IOException {
623      super.s();
624      return this;
625   }
626
627   @Override /* SerializerWriter */
628   public XmlWriter q() throws IOException {
629      super.q();
630      return this;
631   }
632
633   @Override /* SerializerWriter */
634   public XmlWriter i(int indent) throws IOException {
635      super.i(indent);
636      return this;
637   }
638
639   @Override /* SerializerWriter */
640   public XmlWriter ie(int indent) throws IOException {
641      super.ie(indent);
642      return this;
643   }
644
645   @Override /* SerializerWriter */
646   public XmlWriter nl(int indent) throws IOException {
647      super.nl(indent);
648      return this;
649   }
650
651   @Override /* SerializerWriter */
652   public XmlWriter append(Object text) throws IOException {
653      super.append(text);
654      return this;
655   }
656
657   @Override /* SerializerWriter */
658   public XmlWriter append(String text) throws IOException {
659      super.append(text);
660      return this;
661   }
662
663   @Override /* SerializerWriter */
664   public XmlWriter append(char c) throws IOException {
665      out.write(c);
666      return this;
667   }
668
669   @Override /* Object */
670   public String toString() {
671      return out.toString();
672   }
673}