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