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>'->'</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>'->'</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></ns:name></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><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><ns:name></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><ns:name/></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}