001/* 002 * Copyright 2001-2004 The Apache Software Foundation. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Bogdan Stefanescu 018 */ 019package org.nuxeo.apidoc.introspection; 020 021import java.io.IOException; 022import java.io.Writer; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Calendar; 026import java.util.Date; 027import java.util.TimeZone; 028 029import javax.xml.namespace.QName; 030 031/** 032 * This file contains code from org.apache.commons.betwixt.XMLUtils 033 */ 034public class XMLWriter { 035 036 protected static final String CRLF = System.getProperty("line.separator"); 037 038 protected int indent; 039 040 protected Writer writer; 041 042 protected String crlf; 043 044 protected boolean emitHeader = true; 045 046 protected String encoding; 047 048 protected ArrayList<String> globalNsMap; 049 050 protected Element element; // current element 051 052 protected int depth = -1; 053 054 public XMLWriter(Writer writer) { 055 this(writer, 0); 056 } 057 058 public XMLWriter(Writer writer, int indent) { 059 this(writer, indent, CRLF); 060 } 061 062 public XMLWriter(Writer writer, int indent, String crlf) { 063 this.writer = writer; 064 this.indent = indent; 065 this.crlf = crlf; 066 } 067 068 public void setEncoding(String encoding) { 069 this.encoding = encoding; 070 } 071 072 public String getEncoding() { 073 return encoding; 074 } 075 076 public void putXmlns(String uri) { 077 putXmlns("", uri); 078 } 079 080 public void putXmlns(String prefix, String uri) { 081 if (globalNsMap == null) { 082 globalNsMap = new ArrayList<>(); 083 } 084 globalNsMap.add(uri); 085 globalNsMap.add(prefix); 086 } 087 088 public String getXmlNs(String uri) { 089 if (globalNsMap != null) { 090 for (int i = 0, len = globalNsMap.size(); i < len; i += 2) { 091 if (uri.equals(globalNsMap.get(i))) { 092 return globalNsMap.get(i + 1); 093 } 094 } 095 } 096 return null; 097 } 098 099 public void setIndent(int indent) { 100 this.indent = indent; 101 } 102 103 public int getIndent() { 104 return indent; 105 } 106 107 public void setCRLF(String crlf) { 108 this.crlf = crlf; 109 } 110 111 public String getCRLF() { 112 return crlf; 113 } 114 115 public void setWriter(Writer writer) { 116 this.writer = writer; 117 } 118 119 public Writer getWriter() { 120 return writer; 121 } 122 123 public void setEmitHeader(boolean emitHeader) { 124 this.emitHeader = emitHeader; 125 } 126 127 public boolean isEmitHeader() { 128 return emitHeader; 129 } 130 131 public void flush() throws IOException { 132 writer.flush(); 133 } 134 135 public void close() throws IOException { 136 writer.close(); 137 } 138 139 protected void done() throws IOException { 140 writer.flush(); 141 // TODO check for errors 142 } 143 144 public XMLWriter write(String text) throws IOException { 145 writer.write(text); 146 return this; 147 } 148 149 public void indent(String text) throws IOException { 150 if (indent > 0) { 151 writer.write(crlf); 152 char[] buf = new char[depth * indent]; 153 Arrays.fill(buf, ' '); 154 writer.write(buf); 155 } 156 writer.write(text); 157 } 158 159 public XMLWriter element(String name) throws IOException { 160 if (element != null && !element.isContainer) { // a non closed sibling - 161 // close it 162 pop(); 163 writer.write("/>"); 164 } 165 indent("<"); 166 writer.write(name); 167 if (element == null) { // the first element - write any global ns 168 if (globalNsMap != null) { 169 for (int i = 0, len = globalNsMap.size(); i < len; i += 2) { 170 String prefix = globalNsMap.get(i + 1); 171 String uri = globalNsMap.get(i); 172 writer.write(" xmlns"); 173 if (prefix != null && prefix.length() > 0) { 174 writer.write(":"); 175 writer.write(prefix); 176 } 177 writer.write("=\""); 178 writer.write(uri); 179 writer.write("\""); 180 } 181 } 182 } 183 push(name); // push myself to the stack 184 return this; 185 } 186 187 public XMLWriter start() throws IOException { 188 depth++; 189 if (element == null) { // the root 190 if (emitHeader) { 191 if (encoding != null) { 192 writer.write("<?xml version=\"1.0\" encoding=" + encoding + "?>"); 193 } else { 194 writer.write("<?xml version=\"1.0\"?>"); 195 } 196 writer.write(crlf); 197 } 198 } else { 199 element.isContainer = true; 200 writer.write(">"); 201 } 202 return this; 203 } 204 205 public XMLWriter end() throws IOException { 206 depth--; 207 if (element == null) { 208 done(); 209 } else { 210 if (!element.isContainer) { // a child element - close it 211 pop(); 212 writer.write("/>"); 213 } 214 Element myself = pop(); // close myself 215 indent("</"); 216 writer.write(myself.name); 217 writer.write(">"); 218 } 219 return this; 220 } 221 222 public XMLWriter content(String text) throws IOException { 223 start(); 224 depth--; 225 writer.write(text); 226 Element elem = pop(); // close myself 227 writer.write("</"); 228 writer.write(elem.name); 229 writer.write(">"); 230 return this; 231 } 232 233 public XMLWriter econtent(String text) throws IOException { 234 return content(escapeBodyValue(text)); 235 } 236 237 public XMLWriter content(boolean value) throws IOException { 238 return content(value ? "true" : "false"); 239 } 240 241 public XMLWriter content(Date value) throws IOException { 242 return content(DateTimeFormat.abderaFormat(value)); 243 } 244 245 public XMLWriter text(String text) throws IOException { 246 indent(text); 247 return this; 248 } 249 250 public XMLWriter etext(String text) throws IOException { 251 return text(escapeBodyValue(text)); 252 } 253 254 public XMLWriter attr(String name, Object value) throws IOException { 255 writer.write(" "); 256 writer.write(name); 257 writer.write("=\""); 258 writer.write(value.toString()); 259 writer.write("\""); 260 return this; 261 } 262 263 public XMLWriter eattr(String name, Object value) throws IOException { 264 return attr(name, escapeAttributeValue(value)); 265 } 266 267 public XMLWriter xmlns(String value) throws IOException { 268 attr("xmlns", value); 269 element.putXmlns("", value); 270 return this; 271 } 272 273 public XMLWriter xmlns(String name, String value) throws IOException { 274 attr("xmlns:" + name, value); 275 element.putXmlns(name, value); 276 return this; 277 } 278 279 public XMLWriter attr(String name) throws IOException { 280 writer.write(" "); 281 writer.write(name); 282 writer.write("=\""); 283 return this; 284 } 285 286 public XMLWriter string(String value) throws IOException { 287 writer.write(value); 288 writer.write("\""); 289 return this; 290 } 291 292 public XMLWriter object(Object value) throws IOException { 293 writer.write(value.toString()); 294 writer.write("\""); 295 return this; 296 } 297 298 public XMLWriter date(Date value) throws IOException { 299 writer.write(format(value)); 300 writer.write("\""); 301 return this; 302 } 303 304 public XMLWriter integer(long value) throws IOException { 305 writer.write(Long.toString(value)); 306 writer.write("\""); 307 return this; 308 } 309 310 public XMLWriter number(double value) throws IOException { 311 writer.write(Double.toString(value)); 312 writer.write("\""); 313 return this; 314 } 315 316 public XMLWriter bool(String name, boolean value) throws IOException { 317 attr(name, value ? "true" : "false"); 318 return this; 319 } 320 321 public XMLWriter integer(String name, long value) throws IOException { 322 return attr(name, Long.toString(value)); 323 } 324 325 public XMLWriter number(String name, double value) throws IOException { 326 return attr(name, Double.toString(value)); 327 } 328 329 public XMLWriter date(String name, Date value) throws IOException { 330 return attr(name, value.toString()); 331 } 332 333 public String resolve(QName name) { 334 String prefix = null; 335 String uri = name.getNamespaceURI(); 336 if (element != null) { 337 prefix = element.getXmlNs(uri); 338 if (prefix == null) { 339 prefix = getXmlNs(uri); 340 } 341 } else { 342 prefix = getXmlNs(uri); 343 } 344 if (prefix == null) { 345 return name.toString(); 346 } 347 if (prefix.length() == 0) { 348 return name.getLocalPart(); 349 } 350 return prefix + ":" + name.getLocalPart(); 351 } 352 353 public XMLWriter element(QName name) throws IOException { 354 return element(resolve(name)); 355 } 356 357 public XMLWriter attr(QName name, Object value) throws IOException { 358 return attr(resolve(name), value); 359 } 360 361 public XMLWriter eattr(QName name, Object value) throws IOException { 362 return eattr(resolve(name), value); 363 } 364 365 public XMLWriter attr(QName name) throws IOException { 366 return attr(resolve(name)); 367 } 368 369 public XMLWriter bool(QName name, boolean value) throws IOException { 370 return bool(resolve(name), value); 371 } 372 373 public XMLWriter integer(QName name, long value) throws IOException { 374 return integer(resolve(name), value); 375 } 376 377 public XMLWriter number(QName name, double value) throws IOException { 378 return number(resolve(name), value); 379 } 380 381 public XMLWriter date(QName name, Date value) throws IOException { 382 return date(resolve(name), value); 383 } 384 385 public static String format(Date date) { 386 StringBuilder sb = new StringBuilder(); 387 Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); 388 c.setTime(date); 389 sb.append(c.get(Calendar.YEAR)); 390 sb.append('-'); 391 int f = c.get(Calendar.MONTH); 392 if (f < 9) { 393 sb.append('0'); 394 } 395 sb.append(f + 1); 396 sb.append('-'); 397 f = c.get(Calendar.DATE); 398 if (f < 10) { 399 sb.append('0'); 400 } 401 sb.append(f); 402 sb.append('T'); 403 f = c.get(Calendar.HOUR_OF_DAY); 404 if (f < 10) { 405 sb.append('0'); 406 } 407 sb.append(f); 408 sb.append(':'); 409 f = c.get(Calendar.MINUTE); 410 if (f < 10) { 411 sb.append('0'); 412 } 413 sb.append(f); 414 sb.append(':'); 415 f = c.get(Calendar.SECOND); 416 if (f < 10) { 417 sb.append('0'); 418 } 419 sb.append(f); 420 sb.append('.'); 421 f = c.get(Calendar.MILLISECOND); 422 if (f < 100) { 423 sb.append('0'); 424 } 425 if (f < 10) { 426 sb.append('0'); 427 } 428 sb.append(f); 429 sb.append('Z'); 430 return sb.toString(); 431 } 432 433 public static final String LESS_THAN_ENTITY = "<"; 434 435 public static final String GREATER_THAN_ENTITY = ">"; 436 437 public static final String AMPERSAND_ENTITY = "&"; 438 439 public static final String APOSTROPHE_ENTITY = "'"; 440 441 public static final String QUOTE_ENTITY = """; 442 443 /** 444 * <p> 445 * Escape the <code>toString</code> of the given object. For use as body 446 * text. 447 * </p> 448 * 449 * @param value 450 * escape <code>value.toString()</code> 451 * @return text with escaped delimiters 452 */ 453 public static final String escapeBodyValue(Object value) { 454 StringBuffer buffer = new StringBuffer(value.toString()); 455 for (int i = 0, size = buffer.length(); i < size; i++) { 456 switch (buffer.charAt(i)) { 457 case '<': 458 buffer.replace(i, i + 1, LESS_THAN_ENTITY); 459 size += 3; 460 i += 3; 461 break; 462 case '>': 463 buffer.replace(i, i + 1, GREATER_THAN_ENTITY); 464 size += 3; 465 i += 3; 466 break; 467 case '&': 468 buffer.replace(i, i + 1, AMPERSAND_ENTITY); 469 size += 4; 470 i += 4; 471 break; 472 } 473 } 474 return buffer.toString(); 475 } 476 477 /** 478 * <p> 479 * Escape the <code>toString</code> of the given object. For use in an 480 * attribute value. 481 * </p> 482 * 483 * @param value 484 * escape <code>value.toString()</code> 485 * @return text with characters restricted (for use in attributes) escaped 486 */ 487 public static final String escapeAttributeValue(Object value) { 488 StringBuffer buffer = new StringBuffer(value.toString()); 489 for (int i = 0, size = buffer.length(); i < size; i++) { 490 switch (buffer.charAt(i)) { 491 case '<': 492 buffer.replace(i, i + 1, LESS_THAN_ENTITY); 493 size += 3; 494 i += 3; 495 break; 496 case '>': 497 buffer.replace(i, i + 1, GREATER_THAN_ENTITY); 498 size += 3; 499 i += 3; 500 break; 501 case '&': 502 buffer.replace(i, i + 1, AMPERSAND_ENTITY); 503 size += 4; 504 i += 4; 505 break; 506 case '\'': 507 buffer.replace(i, i + 1, APOSTROPHE_ENTITY); 508 size += 5; 509 i += 5; 510 break; 511 case '\"': 512 buffer.replace(i, i + 1, QUOTE_ENTITY); 513 size += 5; 514 i += 5; 515 break; 516 } 517 } 518 return buffer.toString(); 519 } 520 521 Element push(String name) { 522 element = new Element(name); 523 return element; 524 } 525 526 Element pop() { 527 Element el = element; 528 if (el != null) { 529 element = el.parent; 530 } 531 return el; 532 } 533 534 class Element { 535 final String name; 536 537 final Element parent; 538 539 ArrayList<String> nsMap; 540 541 boolean isContainer; 542 543 Element(String name) { 544 this.name = name; 545 this.parent = element; 546 } 547 548 void putXmlns(String prefix, String uri) { 549 if (nsMap == null) { 550 nsMap = new ArrayList<>(); 551 } 552 nsMap.add(uri); 553 nsMap.add(prefix); 554 } 555 556 String getXmlNs(String uri) { 557 if (nsMap != null) { 558 for (int i = 0, len = nsMap.size(); i < len; i += 2) { 559 if (uri.equals(nsMap.get(i))) { 560 return nsMap.get(i + 1); 561 } 562 } 563 } 564 if (parent != null) { 565 return parent.getXmlNs(uri); 566 } 567 return null; 568 } 569 } 570 571}