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