1 package net.sf.saxon.event; 2 import net.sf.saxon.charcode.UnicodeCharacterSet; 3 import net.sf.saxon.om.XMLChar; 4 import net.sf.saxon.om.FastStringBuffer; 5 import net.sf.saxon.trans.DynamicError; 6 import net.sf.saxon.trans.XPathException; 7 import net.sf.saxon.tinytree.CharSlice; 8 9 import javax.xml.transform.OutputKeys ; 10 import javax.xml.transform.TransformerException ; 11 import javax.xml.transform.TransformerFactory ; 12 import javax.xml.transform.Templates ; 13 import javax.xml.transform.stream.StreamResult ; 14 import javax.xml.transform.stream.StreamSource ; 15 import java.util.Stack ; 16 import java.io.CharArrayWriter ; 17 import java.io.File ; 18 19 23 24 public class XMLEmitter extends Emitter 25 { 26 protected boolean empty = true; 27 protected boolean openStartTag = false; 28 protected boolean declarationIsWritten = false; 29 protected int elementCode; 30 31 protected boolean preferHex = false; 32 protected boolean undeclareNamespaces = false; 33 private boolean warningIssued = false; 34 35 39 protected Stack elementStack = new Stack (); 40 41 45 private String [] nameLookup = new String [2048]; 46 47 private boolean indenting = false; 48 private int indentSpaces = 3; 49 private String indentChars = "\n "; 50 private int totalAttributeLength = 0; 51 52 53 static boolean[] specialInText; static boolean[] specialInAtt; 57 static { 58 specialInText = new boolean[128]; 59 for (int i=0; i<=15; i++) specialInText[i] = true; for (int i=16; i<=127; i++) specialInText[i] = false; 61 specialInText[(char)0] = true; 62 specialInText['\n'] = false; 64 specialInText['\t'] = false; 65 specialInText['\r'] = true; 66 specialInText['<'] = true; 67 specialInText['>'] = true; 68 specialInText['&'] = true; 69 70 specialInAtt = new boolean[128]; 71 for (int i=0; i<=15; i++) specialInAtt[i] = true; for (int i=16; i<=127; i++) specialInAtt[i] = false; 73 specialInAtt[(char)0] = true; 74 specialInAtt['\r'] = true; 76 specialInAtt['\n'] = true; 77 specialInAtt['\t'] = true; 78 specialInAtt['<'] = true; 79 specialInAtt['>'] = true; 80 specialInAtt['&'] = true; 81 specialInAtt['\"'] = true; 82 } 83 84 88 89 public void open() throws XPathException {} 90 91 95 96 public void startDocument(int properties) throws XPathException {} 97 98 101 102 public void endDocument() throws XPathException { 103 if (!elementStack.isEmpty()) { 104 throw new IllegalStateException ("Attempt to end document in serializer when elements are unclosed"); 105 } 106 } 107 108 113 114 protected void openDocument () throws XPathException 115 { 116 if (writer==null) { 117 makeWriter(); 118 } 119 if (characterSet==null) { 120 characterSet = UnicodeCharacterSet.getInstance(); 121 } 122 writeDeclaration(); 123 String rep = outputProperties.getProperty(SaxonOutputKeys.CHARACTER_REPRESENTATION); 124 if (rep!=null) { 125 preferHex = (rep.trim().equalsIgnoreCase("hex")); 126 } 127 rep = outputProperties.getProperty(SaxonOutputKeys.UNDECLARE_PREFIXES); 128 if (rep!=null) { 129 undeclareNamespaces = (rep.trim().equalsIgnoreCase("yes")); 130 } 131 } 132 133 136 137 public void writeDeclaration() throws XPathException { 138 if (declarationIsWritten) return; 139 declarationIsWritten = true; 140 try { 141 indenting = "yes".equals(outputProperties.getProperty(OutputKeys.INDENT)); 142 String s = outputProperties.getProperty(SaxonOutputKeys.INDENT_SPACES); 143 if (s!=null) { 144 try { 145 indentSpaces = Integer.parseInt(s.trim()); 146 } catch (NumberFormatException err) {} 147 } 148 149 String byteOrderMark = outputProperties.getProperty(SaxonOutputKeys.BYTE_ORDER_MARK); 150 151 if ("yes".equals(byteOrderMark) && 152 "UTF-8".equalsIgnoreCase(outputProperties.getProperty(OutputKeys.ENCODING))) { 153 writer.write('\uFEFF'); 155 } 156 157 String omit = outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION); 158 if (omit==null) { 159 omit = "no"; 160 } 161 162 String version = outputProperties.getProperty(OutputKeys.VERSION); 163 if (version==null) { 164 version = "1.0"; 165 } 166 167 String encoding = outputProperties.getProperty(OutputKeys.ENCODING); 168 if (encoding==null || encoding.equalsIgnoreCase("utf8")) { 169 encoding = "UTF-8"; 170 } 171 172 String standalone = outputProperties.getProperty(OutputKeys.STANDALONE); 173 if ("omit".equals(standalone)) { 174 standalone = null; 175 } 176 177 if (omit.equals("no")) { 178 writer.write("<?xml version=\"" + version + "\" " + "encoding=\"" + encoding + '\"' + 179 (standalone != null ? " standalone=\"" + standalone + '\"' : "") + "?>"); 180 } 183 } catch (java.io.IOException err) { 184 throw new DynamicError(err); 185 } 186 } 187 188 191 192 protected void writeDocType(String type, String systemId, String publicId) throws XPathException { 193 try { 194 if (declarationIsWritten && !indenting) { 195 writer.write("\n"); 197 } 198 writer.write("<!DOCTYPE " + type + '\n'); 199 if (systemId!=null && publicId==null) { 200 writer.write(" SYSTEM \"" + systemId + "\">\n"); 201 } else if (systemId==null && publicId!=null) { writer.write(" PUBLIC \"" + publicId + "\">\n"); 203 } else { 204 writer.write(" PUBLIC \"" + publicId + "\" \"" + systemId + "\">\n"); 205 } 206 } catch (java.io.IOException err) { 207 throw new DynamicError(err); 208 } 209 } 210 211 214 215 public void close() throws XPathException 216 { 217 try { 218 if (writer != null) { 219 writer.flush(); 220 } 221 } catch (java.io.IOException err) { 222 throw new DynamicError(err); 223 } 224 } 225 226 229 230 public void startElement (int nameCode, int typeCode, int locationId, int properties) throws XPathException 231 { 232 if (empty) { 233 openDocument(); 234 } 235 String displayName = null; 236 237 if (nameCode < 2048) { 239 displayName = nameLookup[nameCode]; 240 } 241 242 if (displayName == null) { 244 displayName = namePool.getDisplayName(nameCode); 245 if (nameCode < 2048) { 246 nameLookup[nameCode] = displayName; 247 } 248 int badchar = testCharacters(displayName); 249 if (badchar!=0) { 250 DynamicError err = new DynamicError("Element name contains a character (decimal + " + 251 badchar + ") not available in the selected encoding"); 252 err.setErrorCode("SERE0008"); 253 throw err; 254 } 255 } 256 257 elementStack.push(displayName); 258 elementCode = nameCode; 259 260 try { 261 if (empty) { 262 String systemId = outputProperties.getProperty(OutputKeys.DOCTYPE_SYSTEM); 263 String publicId = outputProperties.getProperty(OutputKeys.DOCTYPE_PUBLIC); 264 if (systemId!=null) { 265 writeDocType(displayName, systemId, publicId); 266 } 267 empty = false; 268 } 269 if (openStartTag) { 270 closeStartTag(); 271 } 272 writer.write('<'); 273 writer.write(displayName); 274 openStartTag = true; 275 totalAttributeLength = 0; 276 277 } catch (java.io.IOException err) { 278 throw new DynamicError(err); 279 } 280 } 281 282 public void namespace(int namespaceCode, int properties) throws XPathException { 283 try { 284 String nsprefix = namePool.getPrefixFromNamespaceCode(namespaceCode); 285 String nsuri = namePool.getURIFromNamespaceCode(namespaceCode); 286 287 int len = nsuri.length() + nsprefix.length() + 8; 288 String sep = " "; 289 if (indenting && (totalAttributeLength + len) > 80 && totalAttributeLength != 0) { 290 sep = getAttributeIndentString(); 291 } 292 totalAttributeLength += len; 293 294 if (nsprefix.equals("")) { 295 writer.write(sep); 296 writeAttribute(elementCode, "xmlns", nsuri, 0); 297 } else if (nsprefix.equals("xml")) { 298 return; 299 } else { 300 int badchar = testCharacters(nsprefix); 301 if (badchar!=0) { 302 DynamicError err = new DynamicError("Namespace prefix contains a character (decimal + " + 303 badchar + ") not available in the selected encoding"); 304 err.setErrorCode("SERE0008"); 305 throw err; 306 } 307 if (undeclareNamespaces || !nsuri.equals("")) { 308 writer.write(sep); 309 writeAttribute(elementCode, "xmlns:" + nsprefix, nsuri, 0); 310 } 311 } 312 } catch (java.io.IOException err) { 313 throw new DynamicError(err); 314 } 315 } 316 317 public void attribute(int nameCode, int typeCode, CharSequence value, int locationId, int properties) 318 throws XPathException { 319 String displayName = null; 320 321 if (nameCode < 2048) { 323 displayName = nameLookup[nameCode]; 324 } 325 326 if (displayName == null) { 328 displayName = namePool.getDisplayName(nameCode); 329 if (nameCode < 2048) { 330 nameLookup[nameCode] = displayName; 331 } 332 int badchar = testCharacters(displayName); 333 if (badchar!=0) { 334 DynamicError err = new DynamicError("Attribute name contains a character (decimal + " + 335 badchar + ") not available in the selected encoding"); 336 err.setErrorCode("SERE0008"); 337 throw err; 338 } 339 } 340 341 int len = displayName.length() + value.length() + 4; 342 String sep = " "; 343 if (indenting && (totalAttributeLength + len) > 80 && totalAttributeLength != 0) { 344 sep = getAttributeIndentString(); 345 } 346 totalAttributeLength += len; 347 348 try { 349 writer.write(sep); 350 writeAttribute( 351 elementCode, 352 displayName, 353 value, 354 properties ); 355 356 } catch (java.io.IOException err) { 357 throw new DynamicError(err); 358 } 359 } 360 361 private String getAttributeIndentString() { 362 int indent = (elementStack.size()-1) * indentSpaces + ((String )elementStack.peek()).length() + 3; 363 while (indent >= indentChars.length()) { 364 indentChars += " "; 365 } 366 return indentChars.substring(0, indent); 367 } 368 369 public void startContent() throws XPathException { 370 } 372 373 public void closeStartTag() throws XPathException { 374 try { 375 if (openStartTag) { 376 writer.write('>'); 377 openStartTag = false; 378 } 379 } catch (java.io.IOException err) { 380 throw new DynamicError(err); 381 } 382 } 383 384 387 388 protected String emptyElementTagCloser(String displayName, int nameCode) { 389 return "/>"; 390 } 391 392 401 402 protected void writeAttribute(int elCode, String attname, CharSequence value, int properties) throws XPathException { 403 try { 404 String val = value.toString(); 405 writer.write(attname); 406 if ((properties & ReceiverOptions.NO_SPECIAL_CHARS) != 0) { 407 writer.write('='); 408 writer.write('"'); 409 writer.write(val); 410 writer.write('"'); 411 } else if ((properties & ReceiverOptions.USE_NULL_MARKERS) != 0) { 412 writer.write('='); 415 char delimiter = (val.indexOf('"') >= 0 ? '\'' : '"'); 416 writer.write(delimiter); 417 writeEscape(value, true); 418 writer.write(delimiter); 419 } else { 420 writer.write("=\""); 421 writeEscape(value, true); 422 writer.write('\"'); 423 } 424 } catch (java.io.IOException err) { 425 throw new DynamicError(err); 426 } 427 } 428 429 430 435 436 protected int testCharacters(CharSequence chars) throws XPathException { 437 for (int i=0; i<chars.length(); i++) { 438 char c = chars.charAt(i); 439 if (c > 127) { 440 if (XMLChar.isHighSurrogate(c)) { 441 int cc = XMLChar.supplemental(c, chars.charAt(++i)); 442 if (!characterSet.inCharset(cc)) { 443 return cc; 444 } 445 } else if (!characterSet.inCharset(c)) { 446 return c; 447 } 448 } 449 } 450 return 0; 451 } 452 453 456 457 public void endElement () throws XPathException 458 { 459 String displayName = (String )elementStack.pop(); 460 try { 461 if (openStartTag) { 462 writer.write(emptyElementTagCloser(displayName, elementCode)); 463 openStartTag = false; 464 } else { 465 writer.write("</"); 466 writer.write(displayName); 467 writer.write('>'); 468 } 469 } catch (java.io.IOException err) { 470 throw new DynamicError(err); 471 } 472 } 473 474 477 478 public void characters (CharSequence chars, int locationId, int properties) throws XPathException 479 { 480 if (empty) { 481 openDocument(); 482 } 483 try { 484 if (openStartTag) { 485 closeStartTag(); 486 } 487 488 if ((properties & ReceiverOptions.NO_SPECIAL_CHARS) != 0) { 489 writeCharSequence(chars); 490 } else if ((properties & ReceiverOptions.DISABLE_ESCAPING) == 0) { 491 writeEscape(chars, false); 492 } else { 493 if (testCharacters(chars) == 0) { 495 writeCharSequence(chars); 496 } else { 497 if (!warningIssued) { 500 try { 501 getPipelineConfiguration().getErrorListener().warning( 502 new TransformerException ("disable-output-escaping is ignored for characters " + 503 "not available in the chosen encoding")); 504 } catch (TransformerException e) { 505 throw DynamicError.makeDynamicError(e); 506 } 507 warningIssued = true; 508 } 509 writeEscape(chars, false); 510 } 511 } 512 } catch (java.io.IOException err) { 513 throw new DynamicError(err); 514 } 515 } 516 517 520 521 public void writeCharSequence(CharSequence s) throws java.io.IOException { 522 if (s instanceof String ) { 523 writer.write((String )s); 524 } else if (s instanceof CharSlice) { 525 ((CharSlice)s).write(writer); 526 } else if (s instanceof FastStringBuffer) { 527 ((FastStringBuffer)s).write(writer); 528 } else { 529 writer.write(s.toString()); 530 } 531 } 532 533 534 537 538 public void processingInstruction (String target, CharSequence data, int locationId, int properties) 539 throws XPathException { 540 if (empty) { 541 openDocument(); 542 } 543 try { 544 if (openStartTag) { 545 closeStartTag(); 546 } 547 writer.write("<?" + target + (data.length()>0 ? ' ' + data.toString() : "") + "?>"); 548 } catch (java.io.IOException err) { 549 throw new DynamicError(err); 550 } 551 } 552 553 560 561 protected void writeEscape(final CharSequence chars, final boolean inAttribute) 562 throws java.io.IOException , XPathException { 563 int segstart = 0; 564 boolean disabled = false; 565 final boolean[] specialChars = (inAttribute ? specialInAtt : specialInText); 566 567 while (segstart < chars.length()) { 568 int i = segstart; 569 570 while (i < chars.length()) { 572 final char c = chars.charAt(i); 573 if (c < 127) { 574 if (specialChars[c]) { 575 break; 576 } else { 577 i++; 578 } 579 } else if (c < 160) { 580 break; 581 } else if (XMLChar.isHighSurrogate(c)) { 582 break; 583 } else if (!characterSet.inCharset(c)) { 584 break; 585 } else { 586 i++; 587 } 588 } 589 590 if (i >= chars.length()) { 592 if (segstart == 0) { 593 writeCharSequence(chars); 594 } else { 595 writeCharSequence(chars.subSequence(segstart, i)); 596 } 597 return; 598 } 599 600 if (i > segstart) { 602 writeCharSequence(chars.subSequence(segstart, i)); 603 } 604 605 final char c = chars.charAt(i); 607 if (c==0) { 608 disabled = !disabled; 610 } else if (disabled) { 611 writer.write(c); 612 } else if (c>=127 && c<160) { 613 outputCharacterReference(c); 615 } else if (c>=160) { 616 if (XMLChar.isHighSurrogate(c)) { 617 char d = chars.charAt(++i); 618 int charval = XMLChar.supplemental(c, d); 619 if (characterSet.inCharset(charval)) { 620 writer.write(c); 621 writer.write(d); 622 } else { 623 outputCharacterReference(charval); 624 } 625 } else { 626 outputCharacterReference(c); 628 } 629 630 } else { 631 632 634 if (c=='<') { 635 writer.write("<"); 636 } else if (c=='>') { 637 writer.write(">"); 638 } else if (c=='&') { 639 writer.write("&"); 640 } else if (c=='\"') { 641 writer.write("""); 642 } else if (c=='\n') { 643 writer.write("
"); 644 } else if (c=='\r') { 645 writer.write("
"); 646 } else if (c=='\t') { 647 writer.write("	"); 648 } 649 } 650 segstart = ++i; 651 } 652 } 653 654 657 658 private char[] charref = new char[10]; 659 protected void outputCharacterReference(int charval) throws java.io.IOException { 660 if (preferHex) { 661 int o = 0; 662 charref[o++]='&'; 663 charref[o++]='#'; 664 charref[o++]='x'; 665 String code = Integer.toHexString(charval); 666 int len = code.length(); 667 for (int k=0; k<len; k++) { 668 charref[o++]=code.charAt(k); 669 } 670 charref[o++]=';'; 671 writer.write(charref, 0, o); 672 } else { 673 int o = 0; 674 charref[o++]='&'; 675 charref[o++]='#'; 676 String code = Integer.toString(charval); 677 int len = code.length(); 678 for (int k=0; k<len; k++) { 679 charref[o++]=code.charAt(k); 680 } 681 charref[o++]=';'; 682 writer.write(charref, 0, o); 683 } 684 } 685 686 689 690 public void comment (CharSequence chars, int locationId, int properties) throws XPathException 691 { 692 if (empty) { 693 openDocument(); 694 } 695 try { 696 if (openStartTag) { 697 closeStartTag(); 698 } 699 writer.write("<!--"); 700 writer.write(chars.toString()); 701 writer.write("-->"); 702 } catch (java.io.IOException err) { 703 throw new DynamicError(err); 704 } 705 } 706 707 public static void main(String [] params) throws Exception { 708 StreamResult iStreamResult = new StreamResult (new CharArrayWriter ()); 709 XMLEmitter iResult = new XMLEmitter(); 710 iResult.setStreamResult(iStreamResult); 711 712 StreamSource iSource = new StreamSource (new File ("c:\\temp\\test.xml")); 713 714 System.setProperty("javax.xml.transform.TransformerFactory", 715 "net.sf.saxon.TransformerFactoryImpl"); 716 TransformerFactory iTfactory = TransformerFactory.newInstance(); 717 Templates iTemplates = iTfactory.newTemplates( 718 new StreamSource (new File ("c:\\temp\\test.xsl"))); 719 iTemplates.newTransformer().transform(iSource, iResult); 720 721 } 722 723 724 } 725 726 | Popular Tags |