1 23 24 package org.enhydra.xml.io; 25 26 import java.io.IOException ; 27 import java.io.StringWriter ; 28 import java.io.Writer ; 29 30 import org.enhydra.xml.dom.DOMAccess; 31 import org.enhydra.xml.dom.DOMError; 32 import org.enhydra.xml.dom.DOMOps; 33 import org.enhydra.xml.dom.DOMTraversal; 34 import org.enhydra.xml.xmlc.XMLObject; 35 import org.enhydra.xml.xmlc.XMLObjectLink; 36 import org.w3c.dom.Attr ; 37 import org.w3c.dom.Comment ; 38 import org.w3c.dom.Document ; 39 import org.w3c.dom.DocumentType ; 40 import org.w3c.dom.Element ; 41 import org.w3c.dom.Entity ; 42 import org.w3c.dom.EntityReference ; 43 import org.w3c.dom.Node ; 44 import org.w3c.dom.Notation ; 45 import org.w3c.dom.Text ; 46 import org.w3c.dom.html.HTMLDocument; 47 48 49 53 abstract class BaseDOMFormatter implements Formatter, DOMTraversal.Handler { 54 56 59 protected static final int MAX_ENTITY_QUICK_CHECK_CHAR = 0x7f; 60 61 64 protected static final char ATTR_QUOTE_CHAR = '"'; 65 66 69 protected static final String ATTR_QUOTE_CHAR_ENTITY_REF = "#34"; 70 71 74 protected final OutputOptions fOptions; 75 76 79 protected Writer fOut; 80 81 84 protected final DOMTraversal fTraverser; 85 86 89 private final boolean fOmitAttributeCharEntityRefs; 90 91 94 protected final boolean fPreFormatMode; 95 96 102 protected final boolean fUsePreFormattedText; 103 104 109 protected final boolean fUsePreFormattedAttrText; 110 111 116 protected final boolean fUsePreFormattedElements; 117 118 121 private int fPreFormattedTextCount; 122 123 126 private int fDynamicFormattedTextCount; 127 128 131 protected int fPreFormattedElementCount; 132 133 137 protected int fDynamicFormattedElementCount; 138 139 142 private boolean fProcessingAttr; 143 144 147 protected final boolean fPrettyPrinting; 148 149 152 private final int fIndentSize; 153 154 157 private final String fEncoding; 158 159 162 private final int fMaxCharacterValue; 163 164 167 private final CharacterSet fCharSet; 168 169 173 private final URLRewriter fURLRewriter; 174 175 178 protected final Document fDocument; 179 180 183 protected final DocumentType fDocType; 184 185 188 protected final String fPublicId; 189 190 193 protected final String fSystemId; 194 195 199 private final DocumentInfo fDocInfo; 200 201 205 private final boolean[] fEntityQuickCheck; 206 207 210 private static final String fNewLine; 211 212 217 private static String fIndentSource 218 = " "; 219 220 223 static { 224 fNewLine = System.getProperty("line.separator"); 225 if (fNewLine == null) { 226 throw new XMLIOError("System property line.separator not found"); 227 } 228 } 229 230 236 private static OutputOptions checkUsePreformatting(Document doc, 237 String defaultEncoding, 238 CharacterSet charSet) { 239 if (!(doc instanceof PreFormattedTextDocument)) { 241 return null; 242 } 243 244 OutputOptions pfOptions = ((PreFormattedTextDocument)doc).getPreFormatOutputOptions(); 246 if (pfOptions == null) { 247 return null; 248 } 249 250 String preFormatEncoding = pfOptions.getMIMEEncoding(); 253 if (preFormatEncoding == null) { 254 preFormatEncoding = defaultEncoding; 255 } 256 Encodings encodings = Encodings.getEncodings(); 257 CharacterSet preFormatCharSet 258 = encodings.getCharacterSet(preFormatEncoding); 259 if (!charSet.isCompatible(preFormatCharSet)) { 260 return null; } 262 return pfOptions; 263 } 264 265 269 private static boolean checkUsePreformattedText(OutputOptions options, 270 OutputOptions pfOptions) { 271 return (pfOptions.getIndentSize() == options.getIndentSize()) 273 && (pfOptions.getPrettyPrinting() == options.getPrettyPrinting()); 274 } 275 276 280 private static boolean checkUsePreformattedAttrText(OutputOptions options, 281 OutputOptions pfOptions) { 282 return (pfOptions.getOmitAttributeCharEntityRefs() == options.getOmitAttributeCharEntityRefs()); 283 } 284 285 289 private static boolean checkUsePreformattedElements(OutputOptions options, 290 OutputOptions pfOptions, 291 URLRewriter urlRewriter) { 292 return (pfOptions.getDropHtmlSpanIds() == options.getDropHtmlSpanIds()) 293 && (pfOptions.getOmitAttributeCharEntityRefs() == options.getOmitAttributeCharEntityRefs()) 294 && (urlRewriter == null); 295 } 296 297 300 private static DocumentInfo findDocumentInfo(Document document) { 301 if (document instanceof DocumentInfo) { 302 return (DocumentInfo)document; 303 } else if (document instanceof XMLObjectLink) { 304 return (DocumentInfo)((XMLObjectLink)document).getXMLObject(); 306 } else if (document instanceof HTMLDocument) { 307 return new DocumentInfo () { 308 public boolean isURLAttribute(Element element, 309 String attrName) { 310 return org.enhydra.xml.xmlc.dom.HTMLDomFactoryMethods.isURLAttribute(element, attrName); 311 } 312 313 }; 314 } else { 315 return null; 316 } 317 } 318 319 324 private static String getEncoding(Document document, 325 OutputOptions outputOptions, 326 String defaultEncoding) { 327 String encoding = outputOptions.getMIMEEncoding(); 328 if (encoding == null) { 329 if (document instanceof XMLObject) { 331 encoding = ((XMLObject)document).getEncoding(); 332 } else if (document instanceof XMLObjectLink) { 333 encoding = ((XMLObjectLink)document).getXMLObject().getEncoding(); 334 } 335 if (encoding == null) { 336 encoding = defaultEncoding; 338 } 339 } 340 return encoding; 341 } 342 343 356 protected BaseDOMFormatter(Node node, 357 OutputOptions outputOptions, 358 boolean forPreFormatting, 359 String defaultEncoding, 360 boolean[] entityQuickCheck) { 361 fDocument = DOMOps.getDocument(node); 362 fDocType = DOMAccess.accessDocumentType(fDocument); 363 fOptions = outputOptions; 364 fTraverser = DOMTraversal.getTraverser(this, 0, node); 365 366 String publicId = fOptions.getPublicId(); 368 String systemId = fOptions.getSystemId(); 369 if (fDocType != null) { 370 if (fDocType.getPublicId() != null) { 371 publicId = fDocType.getPublicId(); 372 } 373 if (fDocType.getSystemId() != null) { 374 systemId = fDocType.getSystemId(); 375 } 376 } 377 fPublicId = publicId; 378 fSystemId = systemId; 379 380 fEncoding = getEncoding(fDocument, outputOptions, defaultEncoding); 382 Encodings encodings = Encodings.getEncodings(); 383 fMaxCharacterValue = encodings.getMaxCharacterValue(fEncoding); 384 fCharSet = encodings.getCharacterSet(fEncoding); 385 386 fDocInfo = findDocumentInfo(fDocument); 388 fURLRewriter = (fDocInfo != null) ? fOptions.getURLRewriter() : null; 389 390 fIndentSize = fOptions.getIndentSize(); 392 fPrettyPrinting = (fIndentSize > 0) && fOptions.getPrettyPrinting(); 393 fOmitAttributeCharEntityRefs = fOptions.getOmitAttributeCharEntityRefs(); 394 395 OutputOptions pfOptions 397 = checkUsePreformatting(fDocument, defaultEncoding, fCharSet); 398 399 if ((pfOptions != null) && (!forPreFormatting)) { 400 fUsePreFormattedText = checkUsePreformattedText(fOptions, pfOptions); 401 fUsePreFormattedAttrText = checkUsePreformattedAttrText(fOptions, pfOptions); 402 fUsePreFormattedElements = checkUsePreformattedElements(fOptions, pfOptions, 403 fURLRewriter); 404 } else { 405 fUsePreFormattedText = false; 406 fUsePreFormattedAttrText = false; 407 fUsePreFormattedElements = false; 408 } 409 fPreFormatMode = forPreFormatting; 410 411 fEntityQuickCheck = entityQuickCheck; 413 if (!fOptions.getUseAposEntity()) 414 fEntityQuickCheck['\''] = false; 415 416 if (forPreFormatting) { 418 fOut = new StringWriter (4096); } 420 } 421 422 425 public final String getMIMEEncoding() { 426 return fEncoding; 427 } 428 429 432 public boolean usedPreFormattedText() { 433 return fUsePreFormattedText; 434 } 435 436 439 public final int getPreFormattedTextCount() { 440 return fPreFormattedTextCount; 441 } 442 443 447 public final int getDynamicFormattedTextCount() { 448 return fDynamicFormattedTextCount; 449 } 450 451 454 public final boolean usedPreFormattedElements() { 455 return fUsePreFormattedElements; 456 } 457 458 461 public final int getPreFormattedElementCount() { 462 return fPreFormattedElementCount; 463 } 464 465 469 public final int getDynamicFormattedElementCount() { 470 return fDynamicFormattedElementCount; 471 } 472 473 476 protected final void writeln() throws IOException { 477 fOut.write(fNewLine); 478 } 479 480 484 private static void ensureIndentSource(int numChars) { 485 while (fIndentSource.length() < numChars) { 487 fIndentSource += fIndentSource; 488 } 489 } 490 491 494 protected final void printIndent() throws IOException { 495 if (fPrettyPrinting) { 496 int indent = (fTraverser.getDepth() - 1) * fIndentSize; 497 ensureIndentSource(indent); 498 fOut.write(fIndentSource, 0, indent); 499 } } 501 502 508 abstract protected String getCharacterEntity(char textChar); 509 510 514 private void writeCharacter(char textChar) throws IOException { 515 String entity; 518 if (fProcessingAttr && fOmitAttributeCharEntityRefs) { 519 entity = (textChar == ATTR_QUOTE_CHAR) ? ATTR_QUOTE_CHAR_ENTITY_REF : null; 520 } else { 521 entity = getCharacterEntity(textChar); 522 } 523 524 if (entity != null) { 527 fOut.write('&'); 528 fOut.write(entity); 529 fOut.write(';'); 530 } else if (!fCharSet.isValid(textChar)) { 531 fOut.write("&#"); 532 fOut.write(Integer.toString(textChar)); 533 fOut.write(';'); 534 } else { 535 fOut.write(textChar); 536 } 537 } 538 539 543 protected final void writeText(String text) throws IOException { 544 if (text == null) return; 545 int len = text.length(); 546 char ch; 547 548 for (int idx = 0; idx < len; idx++) { 553 ch = text.charAt(idx); 554 if ((ch <= fMaxCharacterValue) 555 && (ch <= MAX_ENTITY_QUICK_CHECK_CHAR) 556 && (!fEntityQuickCheck[ch])) { 557 fOut.write(ch); } else { 559 writeCharacter(ch); 560 } 561 } 562 } 563 564 568 public final void handleEntity(Entity entity) { 569 throw new XMLIOError("Unexpected call to handleEntity"); 570 } 571 572 576 public final void handleNotation(Notation notation) { 577 throw new XMLIOError("Unexpected call to handleNotation"); 578 } 579 580 584 public final void handleEntityReference(EntityReference entityRef) throws IOException { 585 fOut.write('&'); 586 fOut.write(entityRef.getNodeName()); 587 fOut.write(';'); 588 } 589 590 594 public final void handleComment(Comment comment) throws IOException { 595 fOut.write("<!--"); 596 fOut.write(comment.getData()); 597 fOut.write("-->"); 598 } 599 600 604 public void handleText(Text text) throws IOException { 605 String preformattedData = null; 606 if (fProcessingAttr) { 607 if (fUsePreFormattedAttrText && (text instanceof PreFormattedText)) { 608 preformattedData = ((PreFormattedText)text).getPreFormattedText(); 609 } 610 } else { 611 if (fUsePreFormattedText && (text instanceof PreFormattedText)) { 612 preformattedData = ((PreFormattedText)text).getPreFormattedText(); 613 } 614 } 615 616 if (preformattedData != null) { 617 fOut.write(preformattedData); 619 fPreFormattedTextCount++; 620 } else { 621 writeText(text.getData()); 623 fDynamicFormattedTextCount++; 624 } 625 } 626 627 631 protected final void writeAttributeValue(Attr attr) throws IOException { 632 fProcessingAttr = true; 633 634 fOut.write('='); 635 fOut.write(ATTR_QUOTE_CHAR); 636 637 if ((fURLRewriter != null) 639 && fDocInfo.isURLAttribute(attr.getOwnerElement(), 640 attr.getName())) { 641 String value = fURLRewriter.rewriteURL(attr.getValue()); 642 writeText(value); 643 } else { 644 fTraverser.processChildren(attr); 645 } 646 fOut.write(ATTR_QUOTE_CHAR); 647 648 fProcessingAttr = false; 651 } 652 653 658 abstract protected void writeOpenTag(Element element, 659 String tagName, 660 boolean hasChildren) throws IOException ; 661 662 665 private String preFormatElement(Element element) throws IOException { 666 writeOpenTag(element, element.getTagName(), element.hasChildNodes()); 667 return ((StringWriter )fOut).getBuffer().toString(); 668 } 669 670 673 private boolean isAttributeChild(Node node) { 674 if (node == null) { 675 return false; 676 } 677 switch (node.getNodeType()) { 678 case Node.ELEMENT_NODE: 679 return false; 680 case Node.ATTRIBUTE_NODE: 681 return true; 682 default: 683 return isAttributeChild(node.getParentNode()); 684 } 685 } 686 687 691 private String preFormatText(Text text) throws IOException { 692 fProcessingAttr = isAttributeChild(text); 693 try { 694 handleText(text); 695 } finally { 696 fProcessingAttr = false; 697 } 698 return ((StringWriter )fOut).getBuffer().toString(); 699 } 700 701 704 public final String preFormatNode(Node node) { 705 fProcessingAttr = false; 706 try { 707 ((StringWriter )fOut).getBuffer().setLength(0); 709 switch (node.getNodeType()) { 710 case Node.ELEMENT_NODE: 711 return preFormatElement((Element )node); 712 case Node.TEXT_NODE: 713 return preFormatText((Text )node); 714 } 715 return null; 716 } catch (IOException except) { 717 throw new XMLIOError(except); 719 } 720 } 721 722 726 public final void write(Node node, 727 Writer writer) throws IOException { 728 try { 729 fOut = writer; 730 fProcessingAttr = false; 731 fTraverser.traverse(node); 732 } catch (DOMError error) { 739 Throwable cause = error.getCause(); 741 if (cause instanceof IOException ) { 742 throw (IOException )cause; 743 } else { 744 throw error; 745 } 746 } 747 } 748 } 749 | Popular Tags |