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