Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/port80/eclipse/xml/editors/XMLFormatter.java


1   package com.port80.eclipse.xml.editors;
2   
3   import java.io.IOException;
4   import java.io.StringReader;
5   import java.util.Stack;
6   
7   import org.apache.xerces.impl.XMLDocumentScannerImpl;
8   import org.apache.xerces.parsers.SAXParser;
9   import org.apache.xerces.parsers.StandardParserConfiguration;
10  import org.apache.xerces.xni.XNIException;
11  import org.apache.xerces.xni.parser.XMLComponent;
12  import org.apache.xerces.xni.parser.XMLDocumentScanner;
13  import org.eclipse.jface.preference.IPreferenceStore;
14  import org.eclipse.jface.text.source.ISourceViewer;
15  import org.eclipse.ui.IEditorInput;
16  import org.eclipse.ui.texteditor.ITextEditor;
17  import org.xml.sax.Attributes;
18  import org.xml.sax.InputSource;
19  import org.xml.sax.Locator;
20  import org.xml.sax.SAXException;
21  import org.xml.sax.SAXParseException;
22  import org.xml.sax.ext.DeclHandler;
23  import org.xml.sax.ext.LexicalHandler;
24  import org.xml.sax.helpers.AttributesImpl;
25  import org.xml.sax.helpers.DefaultHandler;
26  
27  import com.port80.eclipse.editors.EditorsPlugin;
28  import com.port80.eclipse.editors.IConstants;
29  import com.port80.util.Msg;
30  import com.port80.util.text.TextUtil;
31  import com.port80.util.text.XmlUtil;
32  
33  /**
34   * @author chrisl
35   */
36  public class XMLFormatter {
37  
38    ////////////////////////////////////////////////////////////////////////
39  
40    private static final String NAME = "XMLFormatter";
41    private static final boolean DEBUG = false;
42    private static final boolean TRACE = false;
43  
44    ////////////////////////////////////////////////////////////////////////
45  
46    private XMLEditorConfiguration fConfig;
47    private ISourceViewer fViewer;
48    private IPreferenceStore fPreferences;
49  
50    ////////////////////////////////////////////////////////////////////////
51  
52    /**
53     * Constructor for XMLFormatter.
54     */
55    public XMLFormatter(XMLEditorConfiguration cf, ISourceViewer viewer) {
56      fConfig = cf;
57      fViewer = viewer;
58    }
59  
60    ////////////////////////////////////////////////////////////////////////
61  
62    /** 
63     * Formats the String <code>sourceString</code>,
64     * and returns a string containing the formatted version.
65     * 
66     * @param string the string to format
67     * @param indentationLevel the initial indentation level, used 
68     *      to shift left/right the entire source fragment. An initial indentation
69     *      level of zero has no effect.
70     * @param positions an array of positions to map. These are
71     *      character-based source positions inside the original source,
72     *     for which corresponding positions in the formatted source will
73     *     be computed (so as to relocate elements associated with the original
74     *     source). It updates the positions array with updated positions.
75     *     If set to <code>null</code>, then no positions are mapped.
76     * @param lineSeparator the line separator to use in formatted source,
77     *     if set to <code>null</code>, then the platform default one will be used.
78     * @return the formatted output string.
79     */
80    //  public String domFormat(
81    //    String string,
82    //    int indentationLevel,
83    //    int[] positions,
84    //    String line_separator,
85    //    boolean compact) {
86    //    IEditorInput input = fConfig.getEditor().getEditorInput();
87    //    //
88    //    EditorsPlugin.deleteProblemMarkers(input, IConstants.FormatActionProblemMarker);
89    //    Document xmldoc = Util.customParse(fConfig.getEditor(), (SourceViewer) fViewer, false);
90    //    StringBuffer ret = new StringBuffer();
91    //    xmlProlog(ret, string, line_separator);
92    //    if (format(xmldoc, ret))
93    //      return ret.toString();
94    //    return string;
95    //
96    //  }
97    //
98    //  private boolean format(Document node, StringBuffer ret) {
99    //    return false;
100   //  }
101 
102   private void xmlProlog(StringBuffer ret, String input, String line_separator) {
103     xmlDeclaration(ret, input);
104     ret.append(line_separator);
105   }
106 
107   /*
108    * Extract the xml declaration from the input string.
109    * //FIXME: How to get this from Xerces. There are no handler method for the xml declaration.
110    */
111   private int xmlDeclaration(StringBuffer ret, String input) {
112     int start = TextUtil.skipWhitespaces(input, 0);
113     if (input.startsWith("<?xml", start)) {
114       char c;
115       int end = start + 5;
116       for (int len = input.length(); end < len; ++end) {
117         c = input.charAt(end);
118         if (c == '?' && end + 1 < len && input.charAt(end + 1) == '>') {
119           ret.append(input.substring(start, end + 2));
120           return end + 2;
121         } else if (c == '<') {
122           break;
123         }
124       }
125       // This should not happend, but just in case there are malformed declaration.
126       ret.append(input.substring(start, end));
127       return end;
128     }
129     ret.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
130     return start;
131   }
132 
133   ////////////////////////////////////////////////////////////////////////
134 
135   /**
136    * Format routine with custom SAXParser.
137    * 
138    * @param string
139    * @param indentationLevel
140    * @param positions
141    * @param line_separator
142    * @param compact
143    * @return String
144    */
145   public String format(
146     String string,
147     int indentationLevel,
148     int[] positions,
149     String line_separator,
150     boolean compact) {
151     ITextEditor editor = fConfig.getEditor();
152     IEditorInput input = editor.getEditorInput();
153     EditorsPlugin.deleteProblemMarkers(input, IConstants.FormatActionProblemMarker);
154     StringBuffer ret = new StringBuffer();
155     xmlProlog(ret, string, line_separator);
156     SAXParser reader;
157     XMLContentHandler handler = new XMLContentHandler(ret, line_separator, compact, fConfig, fViewer);
158     reader = new SAXParser(new CustomXmlParserConfiguration((CustomHandler) handler));
159     try {
160       //      reader.setFeature("http://xml.org/sax/features/string-interning", true); //$NON-NLS-1$
161       reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
162       reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
163       reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
164       reader.setFeature("http://xml.org/sax/features/validation", false);
165       reader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
166       // Doesn't help to ignore entity not defined error.
167       // reader.setEntityResolver(null);
168       //      reader.setFeature("http://apache.org/xml/features/dom/create-entity-ref-nodes",false);
169       //      reader.setFeature("http://apache.org/xml/features/continue-after-fatal-error", true);
170     } catch (SAXException e) {
171       EditorsPlugin.error("Error setting features", null, input);
172       // In case support for this feature is removed
173 
174     }
175     reader.setContentHandler(handler);
176     reader.setErrorHandler(handler);
177     try {
178       reader.setProperty("http://xml.org/sax/properties/lexical-handler", handler);
179       reader.setProperty("http://xml.org/sax/properties/declaration-handler", handler);
180       // reader.setProperty("http://apache.org/xml/properties/internal/entity-manager", new CustomEntityManager());
181     } catch (Exception e) {
182       EditorsPlugin.error("Error initializing XML SAXParser", e, input);
183       return string;
184     }
185     try {
186       reader.parse(new InputSource(new StringReader(string)));
187     } catch (SAXParseException e) {
188       Util.reportError("XMLFormatter", e, editor, fViewer);
189       return string;
190     } catch (SAXException e) {
191       Util.reportError("XMLFormatter", e, editor, fViewer);
192       return string;
193     } catch (IOException e) {
194       EditorsPlugin.error("XMLFormatter: Read error", e, input);
195       return string;
196     } catch (Exception e) {
197       EditorsPlugin.error("XMLFormatter: Parse error", e, input);
198       return string;
199     }
200     if (ret.charAt(ret.length() - 1) != '\n')
201       ret.append('\n');
202     return ret.toString();
203   }
204 
205   ////////////////////////////////////////////////////////////////////////
206 
207   /**
208    * An XMLContentHanlder that format XML source while parsing.
209    */
210   static class XMLContentHandler extends DefaultHandler implements LexicalHandler, DeclHandler, CustomHandler {
211 
212     ////////////////////////////////////////////////////////////////////////
213 
214     XMLEditorConfiguration fConfig;
215     ISourceViewer fViewer;
216     //
217     StringBuffer fResult;
218     String fLineSep;
219     Locator fLocator;
220     //
221     StringBuffer fText;
222     Stack fTagStack;
223     int fLineWidth;
224     int fTabWidth;
225     String fTab;
226     boolean isNewLine;
227     boolean isCompact;
228     //
229     int fLine;
230     int fColumn;
231     boolean isEmpty;
232     boolean isPreserveSpace;
233     boolean hasInternalDTD;
234 
235     /** 
236      * Flag to ignore SAX ContentHandler calls when content is handled by LexicalHandler
237      * (eg. between StartEntity and EndEntity).
238      */
239     boolean isIgnore, isCData;
240 
241     ////////////////////////////////////////////////////////////////////////
242 
243     public XMLContentHandler(
244       StringBuffer ret,
245       String linesep,
246       boolean compact,
247       XMLEditorConfiguration cf,
248       ISourceViewer viewer) {
249       //
250       fConfig = cf;
251       fViewer = viewer;
252       //
253       fResult = ret;
254       fLineSep = (linesep == null) ? "\n" : linesep;
255       fText = new StringBuffer();
256       fTagStack = new Stack();
257       fLineWidth = cf.getLineWidth(viewer);
258       fTabWidth = cf.getTabWidth(viewer);
259       fTab = cf.getTab(viewer);
260       isCompact = compact;
261       isNewLine = true;
262       fLine = 1;
263     }
264 
265     public void reportError(String message, int line, int column) {
266       EditorsPlugin.error(message, null, line, column, fConfig.getEditor().getEditorInput(), fViewer);
267     }
268 
269     ////////////////////////////////////////////////////////////////////////
270 
271     /**
272      * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(Locator)
273      */
274     public void setDocumentLocator(Locator locator) {
275       fLocator = locator;
276     }
277 
278     /**
279      * @see org.xml.sax.ContentHandler#startDocument()
280      */
281     public void startDocument() throws SAXException {
282       if (TRACE)
283         Msg.println("startDocument");
284     }
285 
286     /**
287      * @see org.xml.sax.ContentHandler#endDocument()
288      */
289     public void endDocument() throws SAXException {
290       // SAX do not pass the "\n" beyond the last end tag, so we have to add one here.
291       fText.append(fLineSep);
292       emitText();
293     }
294 
295     /**
296      * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
297      */
298     public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
299       throws SAXException {
300       if (TRACE)
301         Msg.println(
302           "startElement: "
303             + qName
304             + " (line="
305             + fLocator.getLineNumber()
306             + ", column="
307             + fLocator.getColumnNumber());
308       if (isIgnore || isCData)
309         return;
310       //
311       if (fTagStack.isEmpty()) {
312         if (!isNewLine)
313           startNewLine();
314       } else {
315         Element element = (Element) fTagStack.peek();
316         if (!element.isEmitted) {
317           emitStartTag(element, -1);
318           if (!isPreserveSpace && !isNewLine)
319             startNewLine();
320         }
321         emitText();
322       }
323       Attributes a = new AttributesImpl(atts);
324       fTagStack.push(new Element(qName, a, fLine, fTagStack));
325       isEmpty = true;
326     }
327 
328     /**
329      * @see org.xml.sax.ContentHandler#endElement(String, String, String)
330      */
331     public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
332       if (TRACE)
333         Msg.println(
334           "endElement: "
335             + qName
336             + " (line="
337             + fLocator.getLineNumber()
338             + ", column="
339             + fLocator.getColumnNumber());
340       if (isIgnore || isCData)
341         return;
342       //
343       int len = 0;
344       if (!fTagStack.empty()) {
345         Element element = (Element) fTagStack.pop();
346         if (!element.qName.equals(qName)) {
347           Msg.println("End tag not match: end=" + qName + ", start=" + element.qName);
348           fTagStack.push(element);
349         }
350         boolean oneliner = isCompact && !element.isEmitted;
351         if (!element.isEmitted)
352           len = emitStartTag(element, 0);
353         if (isEmpty) {
354           emitEndElement(namespaceURI, localName, element);
355         } else {
356           if (!element.isPreserveSpace)
357             trimText(fText);
358           oneliner
359             &= (len >= 0
360               && (len + fText.length() + qName.length() + 3) < fLineWidth
361               && fText.indexOf("\n") < 0);
362           if (oneliner) {
363             emitText(false, element.isPreserveSpace, 0);
364             emitEndElement(namespaceURI, localName, element);
365             if (!isPreserveSpace && !isNewLine)
366               startNewLine();
367           } else {
368             emitText(true, element.isPreserveSpace, 1);
369             emitEndElement(namespaceURI, localName, element);
370           }
371         }
372       } else {
373         Msg.println("End tag with no matching start tag: end=" + qName);
374         emitText();
375         emitEndElement(namespaceURI, localName, new Element(qName, null, -1, fTagStack));
376       }
377       peekPreserveSpace();
378       isEmpty = false;
379     }
380 
381     /**
382      * @see org.xml.sax.ContentHandler#characters(char[], int, int)
383      */
384     public void characters(char[] ch, int start, int length) throws SAXException {
385       if (TRACE)
386         Msg.println("characters: |" + String.valueOf(ch, start, length) + "|");
387       if (isIgnore)
388         return;
389       isEmpty = false;
390       fText.append(ch, start, length);
391     }
392 
393     /**
394      * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
395      */
396     public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
397       if (TRACE)
398         Msg.println("ignorableWhitespace: |" + ch.toString() + "|");
399       if (isIgnore)
400         return;
401       isEmpty = false;
402       fText.append(ch, start, length);
403     }
404 
405     /**
406      * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
407      */
408     public void processingInstruction(String target, String data) throws SAXException {
409       if (TRACE)
410         Msg.println("processingInstruction: |" + target + "|" + data + "|");
411       if (isIgnore || isCData)
412         return;
413       if (!fTagStack.empty()) {
414         Element element = (Element) fTagStack.peek();
415         if (!element.isEmitted) {
416           emitStartTag(element, -1);
417           if (!isPreserveSpace && !isNewLine)
418             startNewLine();
419         }
420       }
421       isEmpty = false;
422       emitPI(target, data);
423     }
424 
425     /**
426      * @see org.xml.sax.ContentHandler#skippedEntity(String)
427      */
428     public void skippedEntity(String name) throws SAXException {
429       System.err.println("skippedEntity: |" + name + "|");
430       isEmpty = false;
431       if (isIgnore || isCData)
432         return;
433     }
434 
435     // LexicalHandler //////////////////////////////////////////////////////
436     //
437 
438     /**
439      * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
440      */
441     public void comment(char[] ch, int start, int length) throws SAXException {
442       //FIXME: comment is valid in internal DTD.
443       // Hack to fix problem that Xerces do not call endDTD when load-external-DTD is disabled.
444       if (isIgnore)
445         return;
446       if (!fTagStack.empty()) {
447         Element element = (Element) fTagStack.peek();
448         if (!element.isEmitted) {
449           emitStartTag(element, -1);
450           if (!isPreserveSpace && !isNewLine)
451             startNewLine();
452         }
453       }
454       emitText();
455       if (!isPreserveSpace) {
456         if (!isNewLine)
457           startNewLine();
458         emitIndent();
459       }
460       fResult.append("<!--");
461       fText.append(ch, start, length);
462       emitComment();
463       if (isNewLine)
464         emitIndent();
465       fResult.append("-->");
466       if (!isPreserveSpace)
467         startNewLine();
468     }
469 
470     /**
471      * @see org.xml.sax.ext.LexicalHandler#startDTD(String, String, String)
472      */
473     public void startDTD(String name, String publicId, String systemId) throws SAXException {
474       if (DEBUG)
475         System.err.println(NAME + ".startDTD()");
476       // <!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
477       // "/usr/share/sgml/docbook/xml-dtd-4.1.2-1.0-8/docbookx.dtd" []>
478       if (!isNewLine)
479         startNewLine();
480       fResult.append("<!DOCTYPE " + name);
481       startNewLine();
482       if (publicId != null) {
483         emitIndent(1);
484         fResult.append("PUBLIC \"" + publicId + "\"");
485         startNewLine();
486       }
487       if (systemId != null) {
488         emitIndent(1);
489         if (publicId == null)
490           fResult.append("SYSTEM \"" + systemId + "\"");
491         else
492           fResult.append("\"" + systemId + "\"");
493       }
494     }
495 
496     /**
497      * @see org.xml.sax.ext.LexicalHandler#endDTD()
498      */
499     public void endDTD() throws SAXException {
500       if (DEBUG)
501         System.err.println(NAME + ".endDTD()");
502       flushDTD();
503     }
504 
505     /**
506      * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
507      */
508     public void startEntity(String name) throws SAXException {
509       fText.append("&" + name + ";");
510       isIgnore = true;
511     }
512 
513     /**
514      * @see org.xml.sax.ext.LexicalHandler#endEntity(String)
515      */
516     public void endEntity(String name) throws SAXException {
517       isIgnore = false;
518     }
519 
520     /**
521      * @see org.xml.sax.ext.LexicalHandler#startCDATA()
522      */
523     public void startCDATA() throws SAXException {
524       if (isIgnore)
525         return;
526       if (!fTagStack.empty()) {
527         Element element = (Element) fTagStack.peek();
528         if (!element.isEmitted) {
529           emitStartTag(element, -1);
530         }
531       }
532       emitText();
533       isCData = true;
534       isEmpty = false;
535     }
536 
537     /**
538      * @see org.xml.sax.ext.LexicalHandler#endCDATA()
539      */
540     public void endCDATA() throws SAXException {
541       if (isIgnore)
542         return;
543       if (!isPreserveSpace && !isNewLine)
544         startNewLine();
545       fResult.append("<![CDATA[");
546       emitText(true, true, 1);
547       fResult.append("]]>");
548       isCData = false;
549       if (!isPreserveSpace)
550         startNewLine();
551     }
552 
553     // DeclHandler ///////////////////////////////////////////////////////
554 
555     /**
556      * @see org.xml.sax.ext.DeclHandler#attributeDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)
557      */
558     public void attributeDecl(String eName, String aName, String type, String valueDefault, String value)
559       throws SAXException {
560       if (!isNewLine)
561         startNewLine();
562       emitIndent();
563       fResult.append("!ATTLIST");
564       if (eName != null)
565         fResult.append(" " + eName);
566       if (aName != null)
567         fResult.append(" " + aName);
568       if (type != null)
569         fResult.append(" " + type);
570       if (valueDefault != null)
571         fResult.append(" " + valueDefault);
572       if (value != null)
573         fResult.append(" " + value);
574       fResult.append(">");
575     }
576 
577     /**
578      * @see org.xml.sax.ext.DeclHandler#elementDecl(java.lang.String, java.lang.String)
579      */
580     public void elementDecl(String name, String model) throws SAXException {
581       if (!isNewLine)
582         startNewLine();
583       emitIndent();
584       fResult.append("<!ELEMENT " + name);
585       if (model != null)
586         fResult.append(model);
587       fResult.append(">");
588     }
589 
590     /**
591      * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(java.lang.String, java.lang.String, java.lang.String)
592      */
593     public void externalEntityDecl(String name, String publicId, String systemId) throws SAXException {
594       if (DEBUG)
595         System.err.println(
596           NAME
597             + ".externalEntityDecl(): name="
598             + name
599             + ", publicId="
600             + publicId
601             + ", systemId="
602             + systemId);
603       if (!isNewLine)
604         startNewLine();
605       emitIndent();
606       fResult.append("<!ENTITY " + name);
607       if (publicId != null)
608         fResult.append(" PUBLIC \"" + publicId + "\"");
609       if (systemId != null)
610         if (publicId == null)
611           fResult.append(" SYSTEM \"" + systemId + "\"");
612         else
613           fResult.append(" \"" + systemId + "\"");
614       fResult.append(">");
615     }
616 
617     /**
618      * @see org.xml.sax.ext.DeclHandler#internalEntityDecl(java.lang.String, java.lang.String)
619      */
620     public void internalEntityDecl(String name, String value) throws SAXException {
621       if (DEBUG)
622         System.err.println(NAME + ".internalEntityDecl(): name=" + name + ", value=" + value);
623       if (!isNewLine)
624         startNewLine();
625       emitIndent();
626       fResult.append("<!ENTITY " + name + " \"" + value + "\">");
627     }
628 
629     // CustomHandler interface /////////////////////////////////////////////
630 
631     public void startInternalDTD(boolean b) {
632       hasInternalDTD = b;
633       if (hasInternalDTD) {
634         fResult.append(" [");
635         Element e = new Element(null, null, fLine, fTagStack);
636         e.isEmitted = true;
637         fTagStack.push(e);
638       }
639       startNewLine();
640     }
641 
642     ////////////////////////////////////////////////////////////////////////
643 
644     private void startNewLine() {
645       fResult.append(fLineSep);
646       ++fLine;
647       fColumn = 0;
648       isNewLine = true;
649     }
650 
651     private void emitIndent() {
652       emitIndent(0);
653     }
654 
655     private void emitIndent(int delta) {
656       for (int i = 0; i < fTagStack.size() + delta; ++i) {
657         fResult.append(fTab);
658         fColumn += fTabWidth;
659       }
660       isNewLine = false;
661     }
662 
663     private void flushDTD() {
664       if (!isNewLine)
665         startNewLine();
666       if (hasInternalDTD) {
667         fTagStack.pop();
668         fResult.append(']');
669       }
670       fResult.append(">");
671       startNewLine();
672       startNewLine();
673     }
674 
675     private void emitText() {
676       boolean preserve = false;
677       if (!fTagStack.empty())
678         preserve = ((Element) fTagStack.peek()).isPreserveSpace;
679       if (!preserve)
680         trimText(fText);
681       emitText(true, preserve, 0);
682     }
683 
684     /**
685      * Wrap given text to the given linewidth.  Embedded line break in the text are treated as paragraph
686      * break and would becomes a blank line.
687      * 
688      * NOTE: XML parser always return 0x0a as line separator
689      * 
690      * @param linebreak False to suppress the linebreak before start of text.
691      * @param preserve True to preserve spaces in the text.
692      * @param delta Extra indent level.
693      */
694     private void emitText(boolean linebreak, boolean preserve, int delta) {
695       if (preserve) {
696         fResult.append(isCData ? fText.toString() : XmlUtil.escText(fText.toString()));
697         fText.setLength(0);
698         return;
699       }
700       if (fText.length() == 0)
701         return;
702       int start = 0;
703       int index = fText.indexOf("\n");
704       while (index >= 0) {
705         if (linebreak) {
706           if (!isNewLine)
707             startNewLine();
708           if (start < index)
709             emitIndent(delta);
710         } else
711           linebreak = true;
712         emitText(fText, start, index, delta);
713         startNewLine();
714         start = index + 1;
715         index = fText.indexOf("\n", start);
716       }
717       if (linebreak) {
718         if (!isNewLine)
719           startNewLine();
720         if (start < fText.length())
721           emitIndent(delta);
722       }
723       emitText(fText, start, fText.length(), delta);
724       fText.setLength(0);
725     }
726 
727     /**
728      * Emit the given range of text with line wrap.
729      * @param text
730      * @param start
731      * @param end
732      */
733     private void emitText(StringBuffer text, int start, int end, int delta) {
734       int s, e;
735       while (start < end) {
736         int w = fLineWidth - fColumn;
737         e = start + w;
738         if (e >= end) {
739           e = end;
740         } else {
741           for (--e; e >= start; --e) {
742             if (Character.isWhitespace(text.charAt(e)))
743               break;
744           }
745           ++e;
746         }
747         if (e <= start) {
748           reportError(
749             NAME + ".emitText(): problem in break up the text: text=" + text,
750             fLine,
751             fColumn);
752           e = end;
753         }
754         s = start;
755         start = e;
756         for (--e; e >= s; --e) {
757           if (!Character.isWhitespace(text.charAt(e))) {
758             fResult.append(XmlUtil.escText(text.substring(s, e + 1)));
759             break;
760           }
761         }
762         if (start < end) {
763           startNewLine();
764           emitIndent(delta);
765         }
766       }
767     }
768 
769     private void emitComment() {
770       int index = fText.indexOf("\n");
771       while (index >= 0) {
772         if (isNewLine) {
773           emitIndent(1);
774           index -= trimLeadingWhitespace(fText);
775         }
776         fResult.append(fText.substring(0, index));
777         startNewLine();
778         fText.delete(0, index + 1);
779         index = fText.indexOf("\n");
780       }
781       if (isNewLine)
782         trimLeadingWhitespace(fText);
783       if (fText.length() > 0) {
784         if (isNewLine) {
785           emitIndent(1);
786         }
787         fResult.append(fText.toString());
788         fColumn += fText.length();
789       }
790       fText.setLength(0);
791     }
792 
793     /** @return The text length of the element if it is all on a single line, else -1. */
794     private int emitStartTag(Element element, int delta) {
795       // Lookahead for text length of the start tag.
796       String s;
797       int len = 0;
798       element.isEmitted = true;
799       for (int i = 0, max = element.atts.getLength(); i < max; ++i) {
800         s = element.atts.getQName(i);
801         len += s.length() + 1;
802         s = XmlUtil.escAttrValue(element.atts.getValue(i));
803         if (s != null)
804           len += s.length() + 3;
805       }
806       if (!isCompact
807         || fTagStack.size() * fTabWidth + element.qName.length() + 2 + len > fLineWidth) {
808         emitMultiLineElement(element, delta);
809         return -1;
810       } else {
811         emitSingleLineElement(element, delta);
812         return fColumn;
813 
814       }
815     }
816 
817     private void emitSingleLineElement(Element element, int delta) {
818       if (!isPreserveSpace) {
819         if (!isNewLine)
820           startNewLine();
821         emitIndent(delta);
822       }
823       element.line = fLine;
824       isPreserveSpace = element.isPreserveSpace;
825       fResult.append("<" + element.qName);
826       fColumn += element.qName.length() + 1;
827       String s;
828       Attributes atts = element.atts;
829       for (int i = 0; i < atts.getLength(); ++i) {
830         s = atts.getQName(i);
831         fResult.append(" " + s);
832         fColumn += s.length() + 1;
833         s = XmlUtil.escAttrValue(atts.getValue(i));
834         if (s != null) {
835           fResult.append("=\"" + s + "\"");
836           fColumn += s.length() + 3;
837         }
838       }
839       fResult.append(">");
840       ++fColumn;
841       isNewLine = false;
842     }
843 
844     private void emitMultiLineElement(Element element, int delta) {
845       if (!isPreserveSpace) {
846         if (!isNewLine)
847           startNewLine();
848         emitIndent(delta);
849       }
850       element.line = fLine;
851       isPreserveSpace = element.isPreserveSpace;
852       fResult.append("<" + element.qName);
853       fColumn += element.qName.length() + 1;
854       String s;
855       Attributes atts = element.atts;
856       for (int i = 0; i < atts.getLength(); ++i) {
857         startNewLine();
858         emitIndent(delta + 1);
859         s = atts.getQName(i);
860         fResult.append(s);
861         fColumn += s.length();
862         s = XmlUtil.escAttrValue(atts.getValue(i));
863         if (s != null) {
864           fResult.append("=\"" + s + "\"");
865           fColumn += s.length() + 3;
866         }
867       }
868       fResult.append(">");
869       ++fColumn;
870       isNewLine = false;
871     }
872 
873     private void emitEndElement(String namespaceURI, String localName, Element element) {
874       if (isEmpty) {
875         fResult.insert(fResult.length() - 1, " /");
876         ++fColumn;
877         return;
878       }
879       // Put end tag on same line if it is on the same line as start tag and
880       // overall width <= line width.
881       String name = element.qName;
882       if (!isPreserveSpace
883         && !(fLine == element.line && (fColumn + name.length() + 3) <= fLineWidth)) {
884         if (!isNewLine)
885           startNewLine();
886         emitIndent();
887       }
888       fResult.append("</" + name + ">");
889       fColumn += name.length() + 3;
890       isNewLine = false;
891     }
892 
893     private void emitPI(String target, String data) {
894       emitText();
895       if (!isPreserveSpace) {
896         if (!isNewLine)
897           startNewLine();
898         emitIndent();
899       }
900       fResult.append("<?" + target + " " + data + "?>");
901       startNewLine();
902     }
903 
904     ////////////////////////////////////////////////////////////////////////
905 
906     /** @return Number of characters deleted.
907      */
908     private int trimLeadingWhitespace(StringBuffer buf) {
909       if (isPreserveSpace)
910         return 0;
911       int len = buf.length();
912       char c;
913       while (buf.length() > 0) {
914         c = buf.charAt(0);
915         if (c == ' ' || c == '\t') {
916           buf.deleteCharAt(0);
917         } else
918           break;
919       }
920       return len - buf.length();
921     }
922 
923     private boolean trimWhitespace(StringBuffer buf) {
924       if (isPreserveSpace)
925         return false;
926       char c;
927       int len;
928       boolean trimmed = false;
929       len = buf.length();
930       int index = buf.length() - 1;
931       while (index >= 0) {
932         c = buf.charAt(index);
933         if (c == ' ' || c == '\t') {
934           --index;
935           trimmed = true;
936         } else
937           break;
938       }
939       buf.setLength(index + 1);
940       len = buf.length();
941       while (len > 1) {
942         c = buf.charAt(0);
943         if (c == ' ' || c == '\t') {
944           buf.delete(0, 1);
945           --len;
946         } else
947           break;
948       }
949       return trimmed;
950     }
951 
952     /**
953      * Trim given StringBuffer such that all leading and trailing whitespaces are eliminated unless there
954      * are two or more continuous breaks.  In that case, a break would stay. Similarly all whitespaces
955      * inside the text are reduced to a single space unless there are multple line breaks. In that case,
956      * one line break would stay.  Lines without blank line between them is reduced to one continuous line
957      * (represent a paragraph).
958      */
959     private void trimText(StringBuffer text) {
960       int len = text.length();
961       int start = 0;
962       char c;
963       int linebreaks = 0;
964       boolean wasspace = true;
965       String s = text.toString();
966       text.setLength(0);
967       // Skip leading blanks.
968       for (; start < len; ++start) {
969         c = s.charAt(start);
970         if (c == '\n' || !Character.isWhitespace(c))
971           break;
972       }
973       for (; start < len; ++start) {
974         c = s.charAt(start);
975         if (c == '\n') {
976           ++linebreaks;
977           if (linebreaks == 1 && !wasspace) {
978             text.append(' ');
979           } else if (linebreaks == 2) {
980             for (int n = text.length();
981               n > 0 && Character.isWhitespace(text.charAt(n - 1));
982               --n)
983               text.setLength(n - 1);
984             text.append("\n");
985           }
986           // linebreak is counted as space, so that no space would be added after it.
987           wasspace = true;
988         } else if (Character.isWhitespace(c)) {
989           if (!wasspace)
990             text.append(' ');
991           wasspace = true;
992         } else {
993           wasspace = false;
994           linebreaks = 0;
995           text.append(c);
996         }
997       }
998       int end = text.length() - 1;
999       for (; end >= 0; --end) {
1000        c = text.charAt(end);
1001        if (c == '\n' || !Character.isWhitespace(c))
1002          break;
1003      }
1004      text.setLength(end + 1);
1005    }
1006
1007    private void peekPreserveSpace() {
1008      if (fTagStack.isEmpty())
1009        isPreserveSpace = false;
1010      else
1011        isPreserveSpace = ((Element) fTagStack.peek()).isPreserveSpace;
1012    }
1013
1014    private void printBuffer(String m, StringBuffer buf) {
1015      System.err.print(m);
1016      for (int i = 0; i < buf.length(); ++i) {
1017        System.err.print(" " + (int) buf.charAt(i));
1018      }
1019      System.err.println("");
1020    }
1021  }
1022
1023  static class Element {
1024    String qName;
1025    Attributes atts;
1026    int line;
1027    boolean isPreserveSpace;
1028    boolean isEmitted;
1029    public Element(String name, Attributes a, int ln, Stack tagstack) {
1030      qName = name;
1031      atts = a;
1032      line = ln;
1033      String s;
1034      isPreserveSpace = false;
1035      boolean hasPreserveSpace = false;
1036      if (atts != null) {
1037        for (int i = 0, max = atts.getLength(); i < max; ++i) {
1038          s = atts.getQName(i);
1039          if (s.equalsIgnoreCase("xml:space")) {
1040            s = atts.getValue(i);
1041            isPreserveSpace = s.equalsIgnoreCase("preserve");
1042            hasPreserveSpace = true;
1043            break;
1044          }
1045        }
1046      }
1047      if (!hasPreserveSpace && !tagstack.isEmpty()) {
1048        isPreserveSpace = ((Element) tagstack.peek()).isPreserveSpace;
1049      }
1050    }
1051  }
1052
1053  ////////////////////////////////////////////////////////////////////////
1054
1055  //  static class Scanner {
1056  //
1057  //    public static final int LBRACKET = 1;
1058  //    public static final int START_END_TAG = 2;
1059  //    public static final int START_PI = 3;
1060  //    public static final int END_EMPTY_TAG = 11;
1061  //    public static final int RBRACKET = 12;
1062  //    public static final int CHAR = 21;
1063  //    public static final int STRING = 22;
1064  //    public static final int IDENTIFIER=23;
1065  //    public static final int UNKNOWN = 99;
1066  //    public static final int EOF = -1;
1067  //
1068  //    char[] fSource;
1069  //    StringBuffer fTokenSource = new StringBuffer();
1070  //    int pos = 0;
1071  //
1072  //    public Scanner(String source) {
1073  //      fSource = source.toCharArray();
1074  //    }
1075  //
1076  //    public boolean hasMoreToken() {
1077  //      return pos < fSource.length;
1078  //    }
1079  //
1080  //    public int getNextToken() {
1081  //      char next;
1082  //      char c = getChar();
1083  //      switch (c) {
1084  //        case ' ':
1085  //        case '\t':
1086  //        case '\n':
1087  //        case '\r':
1088  //          return getSpace(c);
1089  //        case '<' :
1090  //          next = getChar();
1091  //          if (next == '!') {
1092  //            return START_PI;
1093  //          } else if (next == '/') {
1094  //            return START_END_TAG;
1095  //          } else {
1096  //            ungetChar();
1097  //            return LBRACKET;
1098  //          }
1099  //        case '/' :
1100  //          next = getChar();
1101  //          if (next == '>') {
1102  //            return END_EMPTY_TAG;
1103  //          } else {
1104  //            return CHAR;
1105  //          }
1106  //        case '>' :
1107  //          return RBRACKET;
1108  //        case '"' :
1109  //        case '\'' :
1110  //          if (getStringEnd(c) < 0)
1111  //            return EOF;
1112  //          else
1113  //            return STRING;
1114  //        case 0 :
1115  //          return EOF;
1116  //        default :
1117  //          if(Character.isDigit(c)) return getNumber(c);
1118  //          else if(Character.isLetter()
1119  //          return CHAR;
1120  //      }
1121  //    }
1122  //
1123  //    public String getCurrentTokenSource(int type) {
1124  //      switch (type) {
1125  //        case START_PI :
1126  //          return "<!";
1127  //        case LBRACKET :
1128  //          return "<";
1129  //        case START_END_TAG :
1130  //          return "</";
1131  //        case RBRACKET :
1132  //          return ">";
1133  //        case CHAR :
1134  //        default :
1135  //          return "" + fSource[pos - 1];
1136  //      }
1137  //    }
1138  //
1139  //    private char getChar() {
1140  //      if (pos >= fSource.length) {
1141  //        return 0;
1142  //      }
1143  //      return fSource[pos++];
1144  //    }
1145  //
1146  //    private void ungetChar() {
1147  //      --pos;
1148  //    }
1149  //
1150  //    private char peekChar() {
1151  //      return fSource[pos];
1152  //    }
1153  //
1154  //    private char prevChar() {
1155  //      return fSource[pos - 1];
1156  //    }
1157  //
1158  //    private int getStringEnd(char ch) {
1159  //      fTokenSource.setLength(0);
1160  //      while (pos < fSource.length) {
1161  //        char c = fSource[++pos];
1162  //        if (c == '\\') {
1163  //          // ignore escaped characters
1164  //          pos++;
1165  //        } else if (c == ch) {
1166  //          return pos;
1167  //        }
1168  //        fTokenSource.append(c);
1169  //      }
1170  //      if (fTokenSource.length() > 0)
1171  //        return pos - 1;
1172  //      else
1173  //        return -1;
1174  //    }
1175  //  }
1176
1177  ////////////////////////////////////////////////////////////////////////
1178
1179  interface CustomHandler {
1180    public void endDTD() throws SAXException;
1181    public void startInternalDTD(boolean yes);
1182  }
1183
1184  /**
1185   * Hack to fix missing endDTD() call.
1186   * 
1187   * @author chrisl
1188   */
1189  static class CustomXmlParserConfiguration extends StandardParserConfiguration {
1190    private CustomHandler fHandler;
1191    public CustomXmlParserConfiguration(CustomHandler handler) {
1192      super();
1193      if (handler != null) {
1194        // Recreate document scanner with given lexical handler.
1195        boolean b = fComponents.remove(fScanner);
1196        fHandler = handler;
1197        fScanner = createDocumentScanner();
1198        setProperty(DOCUMENT_SCANNER, fScanner);
1199        if (b && fScanner instanceof XMLComponent) {
1200          addComponent((XMLComponent) fScanner);
1201        }
1202      }
1203      boolean b = fComponents.remove(fEntityManager);
1204      fEntityManager = new CustomEntityManager();
1205      setProperty(ENTITY_MANAGER, fEntityManager);
1206      if (b && fEntityManager instanceof XMLComponent)
1207        addComponent(fEntityManager);
1208      //
1209      fErrorReporter.setDocumentLocator(fEntityManager.getEntityScanner());
1210    }
1211    protected XMLDocumentScanner createDocumentScanner() {
1212      return new CustomDocumentScanner(fHandler);
1213    }
1214
1215  }
1216
1217  static class CustomDocumentScanner extends XMLDocumentScannerImpl {
1218    private CustomHandler fHandler;
1219    private boolean fHasInternalDTD;
1220    public CustomDocumentScanner(CustomHandler handler) {
1221      super();
1222      fHandler = handler;
1223    }
1224
1225    /** Scans a doctype declaration. */
1226    protected boolean scanDoctypeDecl() throws IOException, XNIException {
1227      fHasInternalDTD = super.scanDoctypeDecl();
1228      fHasExternalDTD = fLoadExternalDTD && (fDoctypeSystemId != null);
1229      if (!fHasExternalDTD && !fHasInternalDTD && fHandler != null) {
1230        try {
1231          fHandler.endDTD();
1232        } catch (SAXException e) {
1233          e.printStackTrace();
1234          throw new XNIException(e);
1235        }
1236      }
1237      fHandler.startInternalDTD(fHasInternalDTD);
1238      return fHasInternalDTD;
1239    }
1240  }
1241
1242  ////////////////////////////////////////////////////////////////////////
1243
1244}