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