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