KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ant > internal > ui > editor > formatter > XmlDocumentFormatter


1 /*******************************************************************************
2  * Copyright (c) 2004, 2006s John-Mason P. Shackelford and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * John-Mason P. Shackelford - initial API and implementation
10  * IBM Corporation - bug fixes
11  *******************************************************************************/

12
13 package org.eclipse.ant.internal.ui.editor.formatter;
14
15 import java.io.IOException JavaDoc;
16 import java.io.Reader JavaDoc;
17 import java.io.StringReader JavaDoc;
18
19 import org.eclipse.ant.internal.ui.AntUIPlugin;
20 import org.eclipse.ant.internal.ui.preferences.AntEditorPreferenceConstants;
21 import org.eclipse.core.runtime.Assert;
22 import org.eclipse.jface.preference.IPreferenceStore;
23 import org.eclipse.jface.text.BadLocationException;
24 import org.eclipse.jface.text.IDocument;
25 import org.eclipse.jface.text.IRegion;
26
27 public class XmlDocumentFormatter {
28
29     private static class CommentReader extends TagReader {
30
31         private boolean complete = false;
32
33         protected void clear() {
34             this.complete = false;
35         }
36
37         public String JavaDoc getStartOfTag() {
38             return "<!--"; //$NON-NLS-1$
39
}
40
41         protected String JavaDoc readTag() throws IOException JavaDoc {
42             int intChar;
43             char c;
44             StringBuffer JavaDoc node = new StringBuffer JavaDoc();
45
46             while (!complete && (intChar = reader.read()) != -1) {
47                 c = (char) intChar;
48
49                 node.append(c);
50
51                 if (c == '>' && node.toString().endsWith("-->")) { //$NON-NLS-1$
52
complete = true;
53                 }
54             }
55             return node.toString();
56         }
57     }
58
59     private static class DoctypeDeclarationReader extends TagReader {
60
61         private boolean complete = false;
62
63         protected void clear() {
64             this.complete = false;
65         }
66
67         public String JavaDoc getStartOfTag() {
68             return "<!"; //$NON-NLS-1$
69
}
70
71         protected String JavaDoc readTag() throws IOException JavaDoc {
72             int intChar;
73             char c;
74             StringBuffer JavaDoc node = new StringBuffer JavaDoc();
75
76             while (!complete && (intChar = reader.read()) != -1) {
77                 c = (char) intChar;
78
79                 node.append(c);
80
81                 if (c == '>') {
82                     complete = true;
83                 }
84             }
85             return node.toString();
86         }
87
88     }
89
90     private static class ProcessingInstructionReader extends TagReader {
91
92         private boolean complete = false;
93
94         protected void clear() {
95             this.complete = false;
96         }
97
98         public String JavaDoc getStartOfTag() {
99             return "<?"; //$NON-NLS-1$
100
}
101
102         protected String JavaDoc readTag() throws IOException JavaDoc {
103             int intChar;
104             char c;
105             StringBuffer JavaDoc node = new StringBuffer JavaDoc();
106
107             while (!complete && (intChar = reader.read()) != -1) {
108                 c = (char) intChar;
109
110                 node.append(c);
111
112                 if (c == '>' && node.toString().endsWith("?>")) { //$NON-NLS-1$
113
complete = true;
114                 }
115             }
116             return node.toString();
117         }
118     }
119
120     private static abstract class TagReader {
121
122         protected Reader JavaDoc reader;
123
124         private String JavaDoc tagText;
125
126         protected abstract void clear();
127
128         public int getPostTagDepthModifier() {
129             return 0;
130         }
131
132         public int getPreTagDepthModifier() {
133             return 0;
134         }
135
136         abstract public String JavaDoc getStartOfTag();
137
138         public String JavaDoc getTagText() {
139             return this.tagText;
140         }
141
142         public boolean isTextNode() {
143             return false;
144         }
145
146         protected abstract String JavaDoc readTag() throws IOException JavaDoc;
147
148         public boolean requiresInitialIndent() {
149             return true;
150         }
151
152         public void setReader(Reader JavaDoc reader) throws IOException JavaDoc {
153             this.reader = reader;
154             this.clear();
155             this.tagText = readTag();
156         }
157
158         public boolean startsOnNewline() {
159             return true;
160         }
161     }
162
163     private static class TagReaderFactory {
164
165         // Warning: the order of the Array is important!
166
private static TagReader[] tagReaders = new TagReader[]{new CommentReader(),
167                                                                 new DoctypeDeclarationReader(),
168                                                                 new ProcessingInstructionReader(),
169                                                                 new XmlElementReader()};
170
171         private static TagReader textNodeReader = new TextReader();
172
173         public static TagReader createTagReaderFor(Reader JavaDoc reader)
174                                                                  throws IOException JavaDoc {
175
176             char[] buf = new char[10];
177             reader.mark(10);
178             reader.read(buf, 0, 10);
179             reader.reset();
180
181             String JavaDoc startOfTag = String.valueOf(buf);
182
183             for (int i = 0; i < tagReaders.length; i++) {
184                 if (startOfTag.startsWith(tagReaders[i].getStartOfTag())) {
185                     tagReaders[i].setReader(reader);
186                     return tagReaders[i];
187                 }
188             }
189             // else
190
textNodeReader.setReader(reader);
191             return textNodeReader;
192         }
193     }
194
195     private static class TextReader extends TagReader {
196
197         private boolean complete;
198
199         private boolean isTextNode;
200
201         protected void clear() {
202             this.complete = false;
203         }
204
205         /* (non-Javadoc)
206          * @see org.eclipse.ant.internal.ui.editor.formatter.XmlDocumentFormatter.TagReader#getStartOfTag()
207          */

208         public String JavaDoc getStartOfTag() {
209             return ""; //$NON-NLS-1$
210
}
211
212         /* (non-Javadoc)
213          * @see org.eclipse.ant.internal.ui.editor.formatter.XmlDocumentFormatter.TagReader#isTextNode()
214          */

215         public boolean isTextNode() {
216             return this.isTextNode;
217         }
218
219         protected String JavaDoc readTag() throws IOException JavaDoc {
220
221             StringBuffer JavaDoc node = new StringBuffer JavaDoc();
222
223             while (!complete) {
224
225                 reader.mark(1);
226                 int intChar = reader.read();
227                 if (intChar == -1) break;
228
229                 char c = (char) intChar;
230                 if (c == '<') {
231                     reader.reset();
232                     complete = true;
233                 } else {
234                     node.append(c);
235                 }
236             }
237
238             // if this text node is just whitespace
239
// remove it, except for the newlines.
240
if (node.length() < 1) {
241                 this.isTextNode = false;
242
243             } else if (node.toString().trim().length() == 0) {
244                 String JavaDoc whitespace = node.toString();
245                 node = new StringBuffer JavaDoc();
246                 for (int i = 0; i < whitespace.length(); i++) {
247                     char whitespaceCharacter = whitespace.charAt(i);
248                     if (whitespaceCharacter == '\n' || whitespaceCharacter == '\r') {
249                         node.append(whitespaceCharacter);
250                     }
251                 }
252                 this.isTextNode = false;
253
254             } else {
255                 this.isTextNode = true;
256             }
257             return node.toString();
258         }
259
260         /* (non-Javadoc)
261          * @see org.eclipse.ant.internal.ui.editor.formatter.XmlDocumentFormatter.TagReader#requiresInitialIndent()
262          */

263         public boolean requiresInitialIndent() {
264             return false;
265         }
266
267         /* (non-Javadoc)
268          * @see org.eclipse.ant.internal.ui.editor.formatter.XmlDocumentFormatter.TagReader#startsOnNewline()
269          */

270         public boolean startsOnNewline() {
271             return false;
272         }
273     }
274
275     private static class XmlElementReader extends TagReader {
276
277         private boolean complete = false;
278
279         protected void clear() {
280             this.complete = false;
281         }
282
283         public int getPostTagDepthModifier() {
284             if (getTagText().endsWith("/>") || getTagText().endsWith("/ >")) { //$NON-NLS-1$ //$NON-NLS-2$
285
return 0;
286             } else if (getTagText().startsWith("</")) { //$NON-NLS-1$
287
return 0;
288             } else {
289                 return +1;
290             }
291         }
292
293         public int getPreTagDepthModifier() {
294             if (getTagText().startsWith("</")) { //$NON-NLS-1$
295
return -1;
296             }
297             return 0;
298         }
299
300         public String JavaDoc getStartOfTag() {
301             return "<"; //$NON-NLS-1$
302
}
303
304         protected String JavaDoc readTag() throws IOException JavaDoc {
305
306             StringBuffer JavaDoc node = new StringBuffer JavaDoc();
307
308             boolean insideQuote = false;
309             int intChar;
310
311             while (!complete && (intChar = reader.read()) != -1) {
312                 char c = (char) intChar;
313
314                 node.append(c);
315                 // TODO logic incorrectly assumes that " is quote character
316
// when it could also be '
317
if (c == '"') {
318                     insideQuote = !insideQuote;
319                 }
320                 if (c == '>' && !insideQuote) {
321                     complete = true;
322                 }
323             }
324             return node.toString();
325         }
326     }
327
328     private int depth;
329     private StringBuffer JavaDoc formattedXml;
330     private boolean lastNodeWasText;
331     private String JavaDoc fDefaultLineDelimiter;
332
333     public XmlDocumentFormatter() {
334         super();
335         depth= -1;
336     }
337
338     private void copyNode(Reader JavaDoc reader, StringBuffer JavaDoc out, FormattingPreferences prefs) throws IOException JavaDoc {
339
340         TagReader tag = TagReaderFactory.createTagReaderFor(reader);
341
342         depth = depth + tag.getPreTagDepthModifier();
343
344         if (!lastNodeWasText) {
345
346             if (tag.startsOnNewline() && !hasNewlineAlready(out)) {
347                 out.append(fDefaultLineDelimiter);
348             }
349
350             if (tag.requiresInitialIndent()) {
351                 out.append(indent(prefs.getCanonicalIndent()));
352             }
353         }
354
355         out.append(tag.getTagText());
356
357         depth = depth + tag.getPostTagDepthModifier();
358
359         lastNodeWasText = tag.isTextNode();
360
361     }
362     
363     /**
364      * Returns the indent of the given string.
365      *
366      * @param line the text line
367      * @param tabWidth the width of the '\t' character.
368      */

369     public static int computeIndent(String JavaDoc line, int tabWidth) {
370         int result= 0;
371         int blanks= 0;
372         int size= line.length();
373         for (int i= 0; i < size; i++) {
374             char c= line.charAt(i);
375             if (c == '\t') {
376                 result++;
377                 blanks= 0;
378             } else if (isIndentChar(c)) {
379                 blanks++;
380                 if (blanks == tabWidth) {
381                     result++;
382                     blanks= 0;
383                 }
384             } else {
385                 return result;
386             }
387         }
388         return result;
389     }
390     
391     /**
392      * Indent char is a space char but not a line delimiters.
393      * <code>== Character.isWhitespace(ch) && ch != '\n' && ch != '\r'</code>
394      */

395     public static boolean isIndentChar(char ch) {
396         return Character.isWhitespace(ch) && !isLineDelimiterChar(ch);
397     }
398     
399     /**
400      * Line delimiter chars are '\n' and '\r'.
401      */

402     public static boolean isLineDelimiterChar(char ch) {
403         return ch == '\n' || ch == '\r';
404     }
405     
406     public String JavaDoc format(String JavaDoc documentText, FormattingPreferences prefs) {
407
408         Assert.isNotNull(documentText);
409         Assert.isNotNull(prefs);
410
411         Reader JavaDoc reader = new StringReader JavaDoc(documentText);
412         formattedXml = new StringBuffer JavaDoc();
413
414         if (depth == -1) {
415             depth = 0;
416         }
417         lastNodeWasText = false;
418         try {
419             while (true) {
420                 reader.mark(1);
421                 int intChar = reader.read();
422                 reader.reset();
423
424                 if (intChar != -1) {
425                     copyNode(reader, formattedXml, prefs);
426                 } else {
427                     break;
428                 }
429             }
430             reader.close();
431         } catch (IOException JavaDoc e) {
432            AntUIPlugin.log(e);
433         }
434         return formattedXml.toString();
435     }
436
437     private boolean hasNewlineAlready(StringBuffer JavaDoc out) {
438         return out.lastIndexOf("\n") == formattedXml.length() - 1 //$NON-NLS-1$
439
|| out.lastIndexOf("\r") == formattedXml.length() - 1; //$NON-NLS-1$
440
}
441
442     private String JavaDoc indent(String JavaDoc canonicalIndent) {
443         StringBuffer JavaDoc indent = new StringBuffer JavaDoc(30);
444         for (int i = 0; i < depth; i++) {
445             indent.append(canonicalIndent);
446         }
447         return indent.toString();
448     }
449     
450     public void setInitialIndent(int indent) {
451         depth= indent;
452     }
453    
454     /**
455      * Returns the indentation of the line at <code>offset</code> as a
456      * <code>StringBuffer</code>. If the offset is not valid, the empty string
457      * is returned.
458      *
459      * @param offset the offset in the document
460      * @return the indentation (leading whitespace) of the line in which
461      * <code>offset</code> is located
462      */

463     public static StringBuffer JavaDoc getLeadingWhitespace(int offset, IDocument document) {
464         StringBuffer JavaDoc indent= new StringBuffer JavaDoc();
465         try {
466             IRegion line= document.getLineInformationOfOffset(offset);
467             int lineOffset= line.getOffset();
468             int nonWS= findEndOfWhiteSpace(document, lineOffset, lineOffset + line.getLength());
469             indent.append(document.get(lineOffset, nonWS - lineOffset));
470             return indent;
471         } catch (BadLocationException e) {
472             return indent;
473         }
474     }
475     
476     /**
477      * Returns the first offset greater than <code>offset</code> and smaller than
478      * <code>end</code> whose character is not a space or tab character. If no such
479      * offset is found, <code>end</code> is returned.
480      *
481      * @param document the document to search in
482      * @param offset the offset at which searching start
483      * @param end the offset at which searching stops
484      * @return the offset in the specifed range whose character is not a space or tab
485      * @exception BadLocationException if position is an invalid range in the given document
486      */

487     public static int findEndOfWhiteSpace(IDocument document, int offset, int end) throws BadLocationException {
488         while (offset < end) {
489             char c= document.getChar(offset);
490             if (c != ' ' && c != '\t') {
491                 return offset;
492             }
493             offset++;
494         }
495         return end;
496     }
497     
498     /**
499      * Creates a string that represents one indent (can be
500      * spaces or tabs..)
501      *
502      * @return one indentation
503      */

504     public static StringBuffer JavaDoc createIndent() {
505         StringBuffer JavaDoc oneIndent= new StringBuffer JavaDoc();
506         IPreferenceStore pluginPrefs= AntUIPlugin.getDefault().getPreferenceStore();
507         pluginPrefs.getBoolean(AntEditorPreferenceConstants.FORMATTER_TAB_CHAR);
508         
509         if (!pluginPrefs.getBoolean(AntEditorPreferenceConstants.FORMATTER_TAB_CHAR)) {
510             int tabLen= pluginPrefs.getInt(AntEditorPreferenceConstants.FORMATTER_TAB_SIZE);
511             for (int i= 0; i < tabLen; i++) {
512                 oneIndent.append(' ');
513             }
514         } else {
515             oneIndent.append('\t'); // default
516
}
517         
518         return oneIndent;
519     }
520
521     public void setDefaultLineDelimiter(String JavaDoc defaultLineDelimiter) {
522        fDefaultLineDelimiter= defaultLineDelimiter;
523         
524     }
525 }
Popular Tags