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