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