KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*******************************************************************************
2  * Copyright (c) 2004, 2005 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 73411, 84342
11  *******************************************************************************/

12 package org.eclipse.ant.internal.ui.editor.formatter;
13
14 import java.text.CharacterIterator JavaDoc;
15 import java.text.StringCharacterIterator JavaDoc;
16 import java.util.ArrayList JavaDoc;
17 import java.util.Arrays JavaDoc;
18 import java.util.List JavaDoc;
19
20 public class XmlTagFormatter {
21
22     protected static class AttributePair {
23
24         private String JavaDoc fAttribute;
25         private String JavaDoc fValue;
26         private char fQuote;
27
28         public AttributePair(String JavaDoc attribute, String JavaDoc value, char attributeQuote) {
29             fAttribute = attribute;
30             fValue = value;
31             fQuote= attributeQuote;
32         }
33
34         public String JavaDoc getAttribute() {
35             return fAttribute;
36         }
37
38         public String JavaDoc getValue() {
39             return fValue;
40         }
41         
42         public char getQuote() {
43             return fQuote;
44         }
45     }
46
47     protected static class ParseException extends Exception JavaDoc {
48         
49         private static final long serialVersionUID = 1L;
50
51         public ParseException(String JavaDoc message) {
52             super(message);
53         }
54     }
55
56     protected static class Tag {
57
58         private List JavaDoc fAttributes = new ArrayList JavaDoc();
59
60         private boolean fClosed;
61
62         private String JavaDoc fElementName;
63
64         public void addAttribute(String JavaDoc attribute, String JavaDoc value, char quote) {
65             fAttributes.add(new AttributePair(attribute, value, quote));
66         }
67
68         public int attributeCount() {
69             return fAttributes.size();
70         }
71
72         public AttributePair getAttributePair(int i) {
73             return (AttributePair) fAttributes.get(i);
74         }
75
76         public String JavaDoc getElementName() {
77             return this.fElementName;
78         }
79
80         public boolean isClosed() {
81             return fClosed;
82         }
83
84         public int minimumLength() {
85             int length = 2; // for the < >
86
if (this.isClosed()) length++; // if we need to add an />
87
length += this.getElementName().length();
88             if (this.attributeCount() > 0 || this.isClosed()) length++;
89             for (int i = 0; i < this.attributeCount(); i++) {
90                 AttributePair attributePair = this.getAttributePair(i);
91                 length += attributePair.getAttribute().length();
92                 length += attributePair.getValue().length();
93                 length += 4; // equals sign, quote characters & trailing space
94
}
95             if (this.attributeCount() > 0 && !this.isClosed()) length--;
96             return length;
97         }
98
99         public void setAttributes(List JavaDoc attributePair) {
100             fAttributes.clear();
101             fAttributes.addAll(attributePair);
102         }
103
104         public void setClosed(boolean closed) {
105             fClosed = closed;
106         }
107
108         public void setElementName(String JavaDoc elementName) {
109             fElementName = elementName;
110         }
111
112         public String JavaDoc toString() {
113             StringBuffer JavaDoc sb = new StringBuffer JavaDoc(500);
114             sb.append('<');
115             sb.append(this.getElementName());
116             if (this.attributeCount() > 0 || this.isClosed()) sb.append(' ');
117
118             for (int i = 0; i < this.attributeCount(); i++) {
119                 AttributePair attributePair = this.getAttributePair(i);
120                 sb.append(attributePair.getAttribute());
121                 sb.append('=');
122                 sb.append(attributePair.getQuote());
123                 sb.append(attributePair.getValue());
124                 sb.append(attributePair.getQuote());
125                 if (this.isClosed() || i != this.attributeCount() - 1)
126                         sb.append(' ');
127             }
128             if (this.isClosed()) sb.append('/');
129             sb.append('>');
130             return sb.toString();
131         }
132     }
133
134     protected static class TagFormatter {
135
136         private int countChar(char searchChar, String JavaDoc inTargetString) {
137             StringCharacterIterator JavaDoc iter = new StringCharacterIterator JavaDoc(
138                     inTargetString);
139             int i = 0;
140             if (iter.first() == searchChar) i++;
141             while (iter.getIndex() < iter.getEndIndex()) {
142                 if (iter.next() == searchChar) {
143                     i++;
144                 }
145             }
146             return i;
147         }
148
149         public String JavaDoc format(Tag tag, FormattingPreferences prefs, String JavaDoc indent, String JavaDoc lineDelimiter) {
150             if (prefs.wrapLongTags()
151                     && lineRequiresWrap(indent + tag.toString(), prefs
152                             .getMaximumLineWidth(), prefs.getTabWidth())) {
153                 return wrapTag(tag, prefs, indent, lineDelimiter);
154             }
155             return tag.toString();
156         }
157
158         protected boolean lineRequiresWrap(String JavaDoc line, int lineWidth, int tabWidth) {
159             return tabExpandedLineWidth(line, tabWidth) > lineWidth;
160         }
161
162         /**
163          * @param line
164          * the line in which spaces are to be expanded
165          * @param tabWidth
166          * number of spaces to substitute for a tab
167          * @return length of the line with tabs expanded to spaces
168          */

169         protected int tabExpandedLineWidth(String JavaDoc line, int tabWidth) {
170             int tabCount = countChar('\t', line);
171             return (line.length() - tabCount) + (tabCount * tabWidth);
172         }
173
174         protected String JavaDoc wrapTag(Tag tag, FormattingPreferences prefs, String JavaDoc indent, String JavaDoc lineDelimiter) {
175             StringBuffer JavaDoc sb = new StringBuffer JavaDoc(1024);
176             sb.append('<');
177             sb.append(tag.getElementName());
178             sb.append(' ');
179
180             if (tag.attributeCount() > 0) {
181                 AttributePair pair= tag.getAttributePair(0);
182                 sb.append(pair.getAttribute());
183                 sb.append('=');
184                 sb.append(pair.getQuote());
185                 sb.append(tag.getAttributePair(0).getValue());
186                 sb.append(pair.getQuote());
187             }
188
189             if (tag.attributeCount() > 1) {
190                 char[] extraIndent = new char[tag.getElementName().length() + 2];
191                 Arrays.fill(extraIndent, ' ');
192                 for (int i = 1; i < tag.attributeCount(); i++) {
193                     AttributePair pair= tag.getAttributePair(i);
194                     sb.append(lineDelimiter);
195                     sb.append(indent);
196                     sb.append(extraIndent);
197                     sb.append(pair.getAttribute());
198                     sb.append('=');
199                     sb.append(pair.getQuote());
200                     sb.append(pair.getValue());
201                     sb.append(pair.getQuote());
202                 }
203             }
204
205             if (prefs.alignElementCloseChar()) {
206                 sb.append(lineDelimiter);
207                 sb.append(indent);
208             } else if (tag.isClosed()) {
209                 sb.append(' ');
210             }
211
212             if (tag.isClosed()) sb.append('/');
213             sb.append('>');
214             return sb.toString();
215         }
216     }
217
218     // if object creation is an issue, use static methods or a flyweight
219
// pattern
220
protected static class TagParser {
221
222         private String JavaDoc fElementName;
223
224         private String JavaDoc fParseText;
225
226         protected List JavaDoc getAttibutes(String JavaDoc elementText)
227                 throws ParseException {
228
229             class Mode {
230                 private int mode;
231                 public void setAttributeNameSearching() {mode = 0;}
232                 public void setAttributeNameFound() {mode = 1;}
233                 public void setAttributeValueSearching() {mode = 2;}
234                 public void setAttributeValueFound() {mode = 3;}
235                 public void setFinished() {mode = 4;}
236                 public boolean isAttributeNameSearching() {return mode == 0;}
237                 public boolean isAttributeNameFound() {return mode == 1;}
238                 public boolean isAttributeValueSearching() {return mode == 2;}
239                 public boolean isAttributeValueFound() {return mode == 3;}
240                 public boolean isFinished() {return mode == 4;}
241             }
242
243             List JavaDoc attributePairs = new ArrayList JavaDoc();
244
245             CharacterIterator JavaDoc iter = new StringCharacterIterator JavaDoc(elementText
246                     .substring(getElementName(elementText).length() + 2));
247
248             // state for finding attributes
249
Mode mode = new Mode();
250             mode.setAttributeNameSearching();
251             char attributeQuote = '"';
252             StringBuffer JavaDoc currentAttributeName = null;
253             StringBuffer JavaDoc currentAttributeValue = null;
254
255             char c = iter.first();
256             while (iter.getIndex() < iter.getEndIndex()) {
257                 
258                 switch (c) {
259                 
260                 case '"':
261                 case '\'':
262
263                     if (mode.isAttributeValueSearching()) {
264
265                         // start of an attribute value
266
attributeQuote = c;
267                         mode.setAttributeValueFound();
268                         currentAttributeValue = new StringBuffer JavaDoc(1024);
269
270                     } else if (mode.isAttributeValueFound()
271                             && attributeQuote == c) {
272
273                         // we've completed a pair!
274
AttributePair pair = new AttributePair(
275                                 currentAttributeName.toString(),
276                                 currentAttributeValue.toString(), attributeQuote);
277
278                         attributePairs.add(pair);
279
280                         // start looking for another attribute
281
mode.setAttributeNameSearching();
282
283                     } else if (mode.isAttributeValueFound()
284                             && attributeQuote != c) {
285
286                         // this quote character is part of the attribute value
287
currentAttributeValue.append(c);
288
289                     } else {
290                         // this is no place for a quote!
291
throw new ParseException("Unexpected '" + c //$NON-NLS-1$
292
+ "' when parsing:\n\t" + elementText); //$NON-NLS-1$
293
}
294                     break;
295
296                 case '=':
297                     
298                     if (mode.isAttributeValueFound()) {
299
300                         // this character is part of the attribute value
301
currentAttributeValue.append(c);
302
303                     } else if (mode.isAttributeNameFound()) {
304
305                         // end of the name, now start looking for the value
306
mode.setAttributeValueSearching();
307                         
308                     } else {
309                         // this is no place for an equals sign!
310
throw new ParseException("Unexpected '" + c //$NON-NLS-1$
311
+ "' when parsing:\n\t" + elementText); //$NON-NLS-1$
312
}
313                     break;
314
315                 case '/':
316                 case '>':
317                     if (mode.isAttributeValueFound()) {
318                         // attribute values are CDATA, add it all
319
currentAttributeValue.append(c);
320                     } else if (mode.isAttributeNameSearching()) {
321                         mode.setFinished();
322                     } else if (mode.isFinished()){
323                         // consume the remaining characters
324
} else {
325                         // we aren't ready to be done!
326
throw new ParseException("Unexpected '" + c //$NON-NLS-1$
327
+ "' when parsing:\n\t" + elementText); //$NON-NLS-1$
328
}
329                     break;
330
331                 default:
332
333                     if (mode.isAttributeValueFound()) {
334                         // attribute values are CDATA, add it all
335
currentAttributeValue.append(c);
336
337                     } else if (mode.isFinished()) {
338                         if (!Character.isWhitespace(c)) {
339                                 throw new ParseException("Unexpected '" + c //$NON-NLS-1$
340
+ "' when parsing:\n\t" + elementText); //$NON-NLS-1$
341
}
342                     } else {
343                         if (!Character.isWhitespace(c)) {
344                             if (mode.isAttributeNameSearching()) {
345                                 // we found the start of an attribute name
346
mode.setAttributeNameFound();
347                                 currentAttributeName = new StringBuffer JavaDoc(255);
348                                 currentAttributeName.append(c);
349                             } else if (mode.isAttributeNameFound()) {
350                                 currentAttributeName.append(c);
351                             }
352                         }
353                     }
354                     break;
355                 }
356                 
357                 c = iter.next();
358             }
359             if (!mode.isFinished()) {
360                 throw new ParseException("Element did not complete normally."); //$NON-NLS-1$
361
}
362             return attributePairs;
363         }
364
365         /**
366          * @param tagText
367          * text of an XML tag
368          * @return extracted XML element name
369          */

370         protected String JavaDoc getElementName(String JavaDoc tagText) throws ParseException {
371             if (!tagText.equals(this.fParseText) || this.fElementName == null) {
372                 int endOfTag = tagEnd(tagText);
373                 if ((tagText.length() > 2) && (endOfTag > 1)) {
374                     this.fParseText = tagText;
375                     this.fElementName = tagText.substring(1, endOfTag);
376                 } else {
377                     throw new ParseException("No element name for the tag:\n\t" //$NON-NLS-1$
378
+ tagText);
379                 }
380             }
381             return fElementName;
382         }
383
384         protected boolean isClosed(String JavaDoc tagText) {
385             return tagText.charAt(tagText.lastIndexOf('>') - 1) == '/';
386         }
387
388         /**
389          * @param tagText
390          * @return a fully populated tag
391          */

392         public Tag parse(String JavaDoc tagText) throws ParseException {
393             Tag tag = new Tag();
394             tag.setElementName(getElementName(tagText));
395             tag.setAttributes(getAttibutes(tagText));
396             tag.setClosed(isClosed(tagText));
397             return tag;
398         }
399
400         private int tagEnd(String JavaDoc text) {
401             // This is admittedly a little loose, but we don't want the
402
// formatter to be too strict...
403
// http://www.w3.org/TR/2000/REC-xml-20001006#NT-Name
404
for (int i = 1; i < text.length(); i++) {
405                 char c = text.charAt(i);
406                 if (!Character.isLetterOrDigit(c) && c != ':' && c != '.'
407                         && c != '-' && c != '_') { return i; }
408             }
409             return -1;
410         }
411     }
412
413     public static String JavaDoc format(String JavaDoc tagText, FormattingPreferences prefs, String JavaDoc indent, String JavaDoc lineDelimiter) {
414
415         Tag tag;
416         if (tagText.startsWith("</") || tagText.startsWith("<%") //$NON-NLS-1$ //$NON-NLS-2$
417
|| tagText.startsWith("<?") || tagText.startsWith("<[")) { //$NON-NLS-1$ //$NON-NLS-2$
418
return tagText;
419         }
420         try {
421             tag = new TagParser().parse(tagText);
422         } catch (ParseException e) {
423             // if we can't parse the tag, give up and leave the text as is.
424
return tagText;
425         }
426         return new TagFormatter().format(tag, prefs, indent, lineDelimiter);
427     }
428 }
Popular Tags