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