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