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