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