1 package org.xmlpull.mxp1_serializer; 2 3 import java.io.IOException ; 4 import java.io.OutputStream ; 5 import java.io.OutputStreamWriter ; 6 import java.io.Writer ; 7 8 import org.xmlpull.v1.XmlSerializer; 9 10 26 public class MXSerializer implements XmlSerializer { 27 protected final static String XML_URI = "http://www.w3.org/XML/1998/namespace"; 28 protected final static String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; 29 private static final boolean TRACE_SIZING = false; 30 31 protected final String FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE = 32 "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator"; 33 protected final String FEATURE_NAMES_INTERNED = 34 "http://xmlpull.org/v1/doc/properties.html#names-interned"; 35 protected final String PROPERTY_SERIALIZER_INDENTATION = 36 "http://xmlpull.org/v1/doc/properties.html#serializer-indentation"; 37 protected final String PROPERTY_SERIALIZER_LINE_SEPARATOR = 38 "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator"; 39 40 protected boolean namesInterned; 42 protected boolean attributeUseApostrophe; 43 protected String indentationString = null; protected String lineSeparator = null; 46 protected Writer out; 47 48 protected int autoDeclaredPrefixes; 49 50 protected int depth = 0; 51 52 protected String elNamespace[] = new String [ 2 ]; 54 protected String elName[] = new String [ elNamespace.length ]; 55 protected int elNamespaceCount[] = new int[ elNamespace.length ]; 56 57 protected int namespaceEnd = 0; 59 protected String namespacePrefix[] = new String [ 8 ]; 60 protected String namespaceUri[] = new String [ namespacePrefix.length ]; 61 62 protected boolean finished; 63 protected boolean pastRoot; 64 protected boolean setPrefixCalled; 65 protected boolean startTagIncomplete; 66 67 protected static final String precomputedPrefixes[]; 68 static { 69 precomputedPrefixes = new String [10]; for (int i = 0; i < precomputedPrefixes.length; i++) 71 { 72 precomputedPrefixes[i] = ("n"+i).intern(); 73 } 74 } 75 76 protected void reset() { 77 out = null; 78 autoDeclaredPrefixes = 0; 79 depth = 0; 80 81 for (int i = 0; i < elNamespaceCount.length; i++) 83 { 84 elName[ i ] = null; 85 elNamespace[ i ] = null; 86 elNamespaceCount[ i ] = 2; 87 } 88 89 90 namespaceEnd = 0; 91 92 93 99 namespacePrefix[ namespaceEnd ] = "xmlns"; 102 namespaceUri[ namespaceEnd ] = XMLNS_URI; 103 ++namespaceEnd; 104 105 namespacePrefix[ namespaceEnd ] = "xml"; 106 namespaceUri[ namespaceEnd ] = XML_URI; 107 ++namespaceEnd; 108 109 finished = false; 110 pastRoot = false; 111 setPrefixCalled = false; 112 startTagIncomplete = false; 113 } 114 115 116 protected void ensureElementsCapacity() { 117 int elStackSize = elName.length; 118 int newSize = (depth >= 7 ? 2 * depth : 8) + 2; if(TRACE_SIZING) { 122 System.err.println("elStackSize "+elStackSize+" ==> "+newSize); 123 } 124 boolean needsCopying = elStackSize > 0; 125 String [] arr = null; 126 arr = new String [newSize]; 127 if(needsCopying) System.arraycopy(elName, 0, arr, 0, elStackSize); 128 elName = arr; 129 arr = new String [newSize]; 130 if(needsCopying) System.arraycopy(elNamespace, 0, arr, 0, elStackSize); 131 elNamespace = arr; 132 133 int[] iarr = new int[newSize]; 134 if(needsCopying) { 135 System.arraycopy(elNamespaceCount, 0, iarr, 0, elStackSize); 136 } else { 137 iarr[0] = 0; 139 } 140 elNamespaceCount = iarr; 141 } 142 143 protected void ensureNamespacesCapacity() { 147 int newSize = namespaceEnd > 7 ? 2 * namespaceEnd : 8; 150 if(TRACE_SIZING) { 151 System.err.println("namespaceSize "+namespacePrefix.length+" ==> "+newSize); 152 } 153 String [] newNamespacePrefix = new String [newSize]; 154 String [] newNamespaceUri = new String [newSize]; 155 if(namespacePrefix != null) { 156 System.arraycopy( 157 namespacePrefix, 0, newNamespacePrefix, 0, namespaceEnd); 158 System.arraycopy( 159 namespaceUri, 0, newNamespaceUri, 0, namespaceEnd); 160 } 161 namespacePrefix = newNamespacePrefix; 162 namespaceUri = newNamespaceUri; 163 164 } 177 178 179 public void setFeature(String name, 180 boolean state) throws IllegalArgumentException , IllegalStateException 181 { 182 if(name == null) { 183 throw new IllegalArgumentException ("feature name can not be null"); 184 } 185 if(FEATURE_NAMES_INTERNED.equals(name)) { 186 namesInterned = state; 187 } else if(FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE.equals(name)) { 188 attributeUseApostrophe = state; 189 } else { 190 throw new IllegalStateException ("unsupported feature "+name); 191 } 192 } 193 194 public boolean getFeature(String name) throws IllegalArgumentException 195 { 196 if(name == null) { 197 throw new IllegalArgumentException ("feature name can not be null"); 198 } 199 if(FEATURE_NAMES_INTERNED.equals(name)) { 200 return namesInterned; 201 } else if(FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE.equals(name)) { 202 return attributeUseApostrophe; 203 } else { 204 return false; 205 } 206 } 207 208 protected boolean writeLineSepartor; 209 protected boolean writeIndentation; 210 211 protected boolean seenTag; 212 protected int indentLevel; 213 214 protected boolean doIndent; 215 protected int offsetNewLine; 216 protected int indentationJump; 217 protected char[] indentationBuf; 218 protected int maxIndentLevel; 219 220 protected void rebuildIndentationBuf() { 221 final int maxIndent = 65; 222 int bufSize = 0; 223 offsetNewLine = 0; 224 if(writeLineSepartor) { 225 offsetNewLine = lineSeparator.length(); 226 bufSize += offsetNewLine; 227 } 228 maxIndentLevel = 0; 229 if(writeIndentation) { 230 indentationJump = indentationString.length(); 231 maxIndentLevel = maxIndent / indentationJump; 232 bufSize += maxIndentLevel * indentationJump; 233 } 234 if(indentationBuf == null || indentationBuf.length < bufSize) { 235 indentationBuf = new char[bufSize + 8]; 236 } 237 int bufPos = 0; 238 if(writeLineSepartor) { 239 for (int i = 0; i < lineSeparator.length(); i++) 240 { 241 indentationBuf[ bufPos++ ] = lineSeparator.charAt(i); 242 } 243 } 244 if(writeIndentation) { 245 for (int i = 0; i < maxIndentLevel; i++) 246 { 247 for (int j = 0; j < indentationString.length(); j++) 248 { 249 indentationBuf[ bufPos++ ] = indentationString.charAt(j); 250 } 251 } 252 } 253 } 254 255 protected void writeIndent() throws IOException { 257 int start = writeLineSepartor ? 0 : offsetNewLine; 258 int level = (indentLevel > maxIndentLevel) ? maxIndentLevel : indentLevel; 259 out.write( indentationBuf, start, (level * indentationJump) - start); 260 } 261 262 public void setProperty(String name, 263 Object value) throws IllegalArgumentException , IllegalStateException 264 { 265 if(name == null) { 266 throw new IllegalArgumentException ("property name can not be null"); 267 } 268 throw new IllegalStateException ("unsupported property "+name); 274 } 283 284 public Object getProperty(String name) throws IllegalArgumentException 285 { 286 if(name == null) { 287 throw new IllegalArgumentException ("property name can not be null"); 288 } 289 return null; 295 } 297 298 299 public void setOutput (Writer writer) 300 { 301 reset(); 302 out = writer; 303 } 304 305 public void setOutput (OutputStream os, String encoding) throws IOException 306 { 307 if(os == null) throw new IllegalArgumentException ("output stream can not be null"); 308 reset(); 309 if(encoding != null) { 310 out = new OutputStreamWriter (os, encoding); 311 } else { 312 out = new OutputStreamWriter (os); 313 } 314 } 315 316 public void startDocument (String encoding, Boolean standalone) throws IOException 317 { 318 out.write("<?xml version=\"1.0\""); 319 if(encoding != null) { 320 out.write(" encoding='"); 321 out.write(encoding); 322 out.write("'"); 323 } 324 if(standalone != null) { 325 if(standalone.booleanValue()) { 326 out.write(" standalone='yes'"); 327 } else { 328 out.write(" standalone='no'"); 329 } 330 } 331 out.write("?>"); 332 } 333 334 public void endDocument () throws IOException 335 { 336 while(depth > 0) { 338 endTag(elNamespace[ depth ], elName[ depth ]); 339 } 340 finished = pastRoot = startTagIncomplete = true; 343 out.flush(); 344 } 345 346 public void setPrefix (String prefix, String namespace) throws IOException 347 { 348 if(startTagIncomplete) closeStartTag(); 349 prefix = prefix.intern(); 352 353 int start = elNamespaceCount[ depth ]; 355 for (int i = start; i < namespaceEnd; i++) 356 { 357 if(prefix == namespacePrefix[ i ]) { 358 throw new IllegalStateException ("duplicated prefix "+printable(prefix)); 359 } 360 } 361 namespace = namespace.intern(); 362 363 if(namespaceEnd >= namespacePrefix.length) { 364 ensureNamespacesCapacity(); 365 } 366 namespacePrefix[ namespaceEnd ] = prefix; 367 namespaceUri[ namespaceEnd ] = namespace; 368 ++namespaceEnd; 369 setPrefixCalled = true; 370 } 371 372 protected String lookupOrDeclarePrefix( String namespace ) { 373 return getPrefix(namespace, true); 374 } 375 376 public String getPrefix (String namespace, boolean generatePrefix) 377 378 { 379 if(namesInterned) { 381 } else { 387 namespace = namespace.intern(); 389 } 390 for (int i = namespaceEnd - 1; i >= 0 ; --i) 392 { 393 if(namespace == namespaceUri[ i ]) { 394 String prefix = namespacePrefix[ i ]; 395 for (int p = namespaceEnd - 1; p > i ; --p) 397 { 398 if(prefix == namespacePrefix[ p ]) 399 continue; } 401 return prefix; 402 } 403 } 404 405 if(!generatePrefix) { 407 return null; 408 } 409 return generatePrefix(namespace); 410 } 411 412 private String generatePrefix(String namespace) { 413 while(true) { 415 ++autoDeclaredPrefixes; 416 String prefix = autoDeclaredPrefixes < precomputedPrefixes.length 418 ? precomputedPrefixes[autoDeclaredPrefixes] : ("n"+autoDeclaredPrefixes).intern(); 419 for (int i = namespaceEnd - 1; i >= 0 ; --i) 421 { 422 if(prefix == namespacePrefix[ i ]) { 423 continue; } 425 } 426 428 if(namespaceEnd >= namespacePrefix.length) { 429 ensureNamespacesCapacity(); 430 } 431 namespacePrefix[ namespaceEnd ] = prefix; 432 namespaceUri[ namespaceEnd ] = namespace; 433 ++namespaceEnd; 434 435 return prefix; 436 } 437 } 438 439 public int getDepth() 440 { 441 return depth; 442 } 443 444 public String getNamespace () 445 { 446 return elNamespace[depth]; 447 } 448 449 public String getName() 450 { 451 return elName[depth]; 452 } 453 454 public XmlSerializer startTag (String namespace, String name) throws IOException 455 { 456 if(startTagIncomplete) closeStartTag(); 457 setPrefixCalled = false; 458 startTagIncomplete = true; 459 ++depth; 460 if( (depth + 1) >= elName.length) { 461 ensureElementsCapacity(); 462 } 463 elNamespace[ depth ] = namespace; 465 elName[ depth ] = name; 467 out.write('<'); 468 if(namespace != null) { 469 if(namespace.length() > 0) { 470 String prefix = lookupOrDeclarePrefix( namespace ); 471 if(prefix.length() > 0) { 474 out.write(prefix); 475 out.write(':'); 476 } 477 } else { 478 for (int i = namespaceEnd - 1; i >= 0 ; --i) 480 { 481 if(namespacePrefix[ i ] == "") { 482 String uri = namespaceUri[ i ]; 483 if(uri == null) { 484 setPrefix("", ""); 486 } else if(uri.length() > 0) { 487 throw new IllegalStateException ( 488 "start tag can not be writtwen in empty default namespace "+ 489 "as default namespace is currently bound to '"+uri+"'"); 490 } 491 break; 492 } 493 } 494 } 495 496 } 497 out.write(name); 498 return this; 499 } 500 501 public XmlSerializer attribute (String namespace, String name, 502 String value) throws IOException 503 { 504 if(!startTagIncomplete) { 505 throw new IllegalArgumentException ("startTag() must be called before attribute()"); 506 } 507 out.write(' '); 509 if(namespace != null && namespace.length() > 0) { 511 namespace = namespace.intern(); 512 String prefix = lookupOrDeclarePrefix( namespace ); 513 if(prefix.length() == 0) { 515 prefix = generatePrefix(namespace); 517 } 518 out.write(prefix); 519 out.write(':'); 520 } 521 out.write(name); 523 out.write('='); 524 out.write( attributeUseApostrophe ? '\'' : '"'); 526 writeAttributeValue(value, out); 527 out.write( attributeUseApostrophe ? '\'' : '"'); 528 return this; 529 } 530 531 protected void closeStartTag() throws IOException { 532 if(finished) { 533 throw new IllegalArgumentException ("trying to write past already finished output"); 534 } 535 if(setPrefixCalled) { 536 throw new IllegalArgumentException ( 537 "startTag() must be called immediately after setPrefix()"); 538 } 539 if(!startTagIncomplete) { 540 throw new IllegalArgumentException ("trying to close start tag that is not declared"); 541 } 542 543 writeNamespaceDeclarations(); 545 out.write('>'); 546 elNamespaceCount[ depth ] = namespaceEnd; 547 startTagIncomplete = false; 548 } 549 550 private void writeNamespaceDeclarations() throws IOException 551 { 552 int start = elNamespaceCount[ depth - 1 ]; 553 for (int i = start; i < namespaceEnd; i++) 554 { 555 if(namespacePrefix[ i ] != "") { 556 out.write(" xmlns:"); 557 out.write(namespacePrefix[ i ]); 558 out.write('='); 559 } else { 560 out.write(" xmlns="); 561 } 562 out.write( attributeUseApostrophe ? '\'' : '"'); 563 564 writeAttributeValue(namespaceUri[ i ], out); 566 567 out.write( attributeUseApostrophe ? '\'' : '"'); 568 } 569 } 570 571 public XmlSerializer endTag (String namespace, String name) throws IOException 572 { 573 if(namespace != null) { 576 namespace = namespace.intern(); 577 } 578 if(namespace != elNamespace[ depth ]) 579 { 580 throw new IllegalArgumentException ( 581 "expected namespace "+printable(elNamespace[ depth ]) 582 +" and not "+printable(namespace)); 583 } 584 if((name != null && !name.equals(elName[ depth ])) 585 || name != elName[ depth ]) 586 { 587 throw new IllegalArgumentException ( 588 "expected element name "+printable(elName[ depth ])+" and not '"+printable(name)); 589 } 590 if(startTagIncomplete) { 591 writeNamespaceDeclarations(); 592 out.write("/>"); 593 } else { 594 out.write("</"); 595 if(namespace != null && namespace.length() > 0) { 596 String prefix = lookupOrDeclarePrefix( namespace ); 597 if(prefix.length() > 0) { 599 out.write(prefix); 600 out.write(':'); 601 } 602 } 603 out.write(name); 604 out.write('>'); 605 606 } 607 --depth; 608 namespaceEnd = elNamespaceCount[ depth ]; 609 startTagIncomplete = false; 610 return this; 611 } 612 613 public XmlSerializer text (String text) throws IOException 614 { 615 if(startTagIncomplete || setPrefixCalled) closeStartTag(); 617 writeElementContent(text, out); 618 return this; 619 } 620 621 public XmlSerializer text (char [] buf, int start, int len) throws IOException 622 { 623 if(startTagIncomplete || setPrefixCalled) closeStartTag(); 624 writeElementContent(buf, start, len, out); 625 return this; 626 } 627 628 public void cdsect (String text) throws IOException 629 { 630 if(startTagIncomplete || setPrefixCalled) closeStartTag(); 631 out.write("<![CDATA["); 632 out.write(text); out.write("]]>"); 634 } 635 636 public void entityRef (String text) throws IOException 637 { 638 if(startTagIncomplete || setPrefixCalled) closeStartTag(); 639 out.write('&'); 640 out.write(text); out.write(';'); 642 } 643 644 public void processingInstruction (String text) throws IOException 645 { 646 if(startTagIncomplete || setPrefixCalled) closeStartTag(); 647 out.write("<?"); 648 out.write(text); out.write("?>"); 650 } 651 652 public void comment (String text) throws IOException 653 { 654 if(startTagIncomplete || setPrefixCalled) closeStartTag(); 655 out.write("<!--"); 656 out.write(text); out.write("-->"); 658 } 659 660 public void docdecl (String text) throws IOException 661 { 662 if(startTagIncomplete || setPrefixCalled) closeStartTag(); 663 out.write("<!DOCTYPE"); 664 out.write(text); out.write(">"); 666 } 667 668 public void ignorableWhitespace (String text) throws IOException 669 { 670 if(startTagIncomplete || setPrefixCalled) closeStartTag(); 671 out.write(text); } 673 674 public void flush () throws IOException 675 { 676 if(startTagIncomplete) closeStartTag(); 677 out.flush(); 678 } 679 680 682 protected void writeAttributeValue(String value, Writer out) throws IOException 683 { 684 char quot = attributeUseApostrophe ? '\'' : '"'; 686 String quotEntity = attributeUseApostrophe ? "'" : """; 687 int posLt = value.indexOf('<'); 688 int posAmp = value.indexOf('&'); 689 int posQuot = value.indexOf(quot); 690 int pos = 0; 692 while(true) { 693 if(posLt == -1 && posAmp == -1 && posQuot == -1) { 694 if(pos > 0) { 695 out.write(value.substring(pos)); 696 } else { 697 out.write(value); } 699 break; 700 } else if(posQuot != -1 && (posAmp == -1 || (posAmp != -1 && posQuot < posAmp)) 702 && (posLt == -1 || (posLt != -1 && posQuot < posLt)) 703 ) 704 { 705 if(pos < posQuot) out.write(value.substring(pos, posQuot)); 706 out.write(quotEntity); 707 pos = posQuot + 1; 708 posQuot = value.indexOf(quot, pos); 709 } else if(posAmp != -1 710 && (posQuot == -1 || (posQuot != -1 && posAmp < posQuot)) 711 && (posLt == -1 || (posLt != -1 && posAmp < posLt)) 712 ) 713 { 714 if(pos < posAmp) out.write(value.substring(pos, posAmp)); 715 out.write("&"); 716 pos = posAmp + 1; 717 posAmp = value.indexOf('&', pos); 718 } else if(posLt != -1 719 && (posQuot == -1 || (posQuot != -1 && posLt < posQuot)) 720 && (posAmp == -1 || (posAmp != -1 && posLt < posAmp)) 721 ) 722 { 723 if(pos < posLt) out.write(value.substring(pos, posLt)); 724 out.write("<"); 725 pos = posLt + 1; 726 posLt = value.indexOf('<', pos); 727 } else { 728 throw new IllegalStateException ("wrong state #1 posLt="+posLt+" posAmp="+posAmp 729 +" posQuot="+posQuot+" for "+value); 730 } 731 } 732 } 733 734 protected void writeElementContent(String text, Writer out) throws IOException 735 { 736 int posLt = text.indexOf('<'); 739 int posAmp = text.indexOf('&'); 740 int pos = 0; 742 while(true) { 743 if(posLt == -1 && posAmp == -1) { out.write(text.substring(pos)); 745 break; 746 } else if(posLt == -1 747 || (posLt != -1 && posAmp != -1 && posAmp < posLt)) 748 { 749 if(pos < posAmp) out.write(text.substring(pos, posAmp)); 750 out.write("&"); 751 pos = posAmp + 1; 752 posAmp = text.indexOf('&', pos); 753 } else if(posAmp == -1 754 || (posLt != -1 && posAmp != -1 && posLt < posAmp)) 755 { 756 if(pos < posLt) out.write(text.substring(pos, posLt)); 757 out.write("<"); 758 pos = posLt + 1; 759 posLt = text.indexOf('<', pos); 760 } else { 761 throw new IllegalStateException ("wrong state posLt="+posLt 762 +" posAmp="+posAmp+" for "+text); 763 } 764 } 765 } 766 767 protected void writeElementContent(char[] buf, int off, int len, Writer out) throws IOException 768 { 769 int end = off + len; 771 int pos = off; 772 for (int i = off; i < end; i++) 773 { 774 char ch = buf[i]; 775 if(ch == '&') { 776 if(i > pos) { 777 out.write(buf, pos, i - pos); 778 } 779 out.write("&"); 780 pos = i + 1; 781 } else if(ch == '<') { 782 if(i > pos) { 783 out.write(buf, pos, i - pos); 784 } 785 out.write("<"); 786 pos = i + 1; 787 } 788 } 789 if(end > pos) { 790 out.write(buf, pos, end - pos); 791 } 792 } 793 794 795 protected static final String printable(String s) { 796 if(s == null) return "null"; 797 StringBuffer retval = new StringBuffer (s.length() + 16); 798 retval.append("'"); 799 char ch; 800 for (int i = 0; i < s.length(); i++) { 801 addPrintable(retval, s.charAt(i)); 802 } 803 retval.append("'"); 804 return retval.toString(); 805 } 806 807 protected static final String printable(char ch) { 808 StringBuffer retval = new StringBuffer (); 809 addPrintable(retval, ch); 810 return retval.toString(); 811 } 812 813 private static void addPrintable(StringBuffer retval, char ch) 814 { 815 switch (ch) 816 { 817 case '\b': 818 retval.append("\\b"); 819 break; 820 case '\t': 821 retval.append("\\t"); 822 break; 823 case '\n': 824 retval.append("\\n"); 825 break; 826 case '\f': 827 retval.append("\\f"); 828 break; 829 case '\r': 830 retval.append("\\r"); 831 break; 832 case '\"': 833 retval.append("\\\""); 834 break; 835 case '\'': 836 retval.append("\\\'"); 837 break; 838 case '\\': 839 retval.append("\\\\"); 840 break; 841 default: 842 if (ch < 0x20 || ch > 0x7e) { 843 String ss = "0000" + Integer.toString(ch, 16); 844 retval.append("\\u" + ss.substring(ss.length() - 4, ss.length())); 845 } else { 846 retval.append(ch); 847 } 848 } 849 } 850 851 } 852 853 | Popular Tags |