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