1
2
3
4
5
6 package org.repoweb.xml;
7 import java.util.regex.Matcher;
8 import java.util.regex.Pattern;
9 /***
10 * Simple XML Beautifier. This XML Beautifier doesn't need
11 * to have a valid xml file, since it doesn't use any XML API.
12 */
13 class XmlBeautifier {
14 public String beautify(String xml) {
15 if (null == xml) {
16 return null;
17 }
18 XmlHandler handler = new XmlHandler();
19 XmlParser parser = new XmlParser(handler);
20 parser.parse(xml);
21 return handler.result();
22 }
23
24 /***
25 * Beautify XML.
26 */
27 private static class XmlHandler {
28 private StringBuffer _buffer = new StringBuffer();
29 private final String _newLine = System.getProperty("line.separator");
30 private int _numberOfIndent = 0;
31 private String _oneIndent = " ";
32 private boolean _hadText = false;
33 private Pattern _whitespacePattern = Pattern.compile("//s*");
34
35 public void startElement(String raw) {
36 indentIfNeeded();
37 addIndent();
38 _buffer.append(raw);
39 }
40
41
42 public void endElement(String raw) {
43 removeIndent();
44 indentIfNeeded();
45 _buffer.append(raw);
46 }
47
48
49 public void characters(String raw) {
50 Matcher matcher = _whitespacePattern.matcher(raw);
51 if (!matcher.matches()) {
52 _buffer.append(raw.trim());
53 _hadText = true;
54 }
55 }
56
57
58 public void comment(String raw) {
59 _buffer.append(_newLine);
60 indent();
61 _buffer.append(raw);
62 }
63
64
65 public String result() {
66 return _buffer.toString();
67 }
68
69
70 private void indent() {
71 for (int i = 0; i < _numberOfIndent; i++) {
72 _buffer.append(_oneIndent);
73 }
74 }
75
76
77 private void indentIfNeeded() {
78 if (_hadText) {
79 _hadText = false;
80 }
81 else {
82 if (_buffer.length() != 0) {
83 _buffer.append(_newLine);
84 }
85 indent();
86 }
87 }
88
89
90 private void addIndent() {
91 _numberOfIndent++;
92 }
93
94
95 private void removeIndent() {
96 _numberOfIndent--;
97 _numberOfIndent = (_numberOfIndent < 0) ? 0 : _numberOfIndent;
98 }
99 }
100
101 /***
102 * Parse XML string into event.
103 */
104 private static class XmlParser {
105 private static final int IN_TAG = 0;
106 private static final int IN_CLOSING_TAG = 1;
107 private static final int CONTENT = 2;
108 private static final int IN_COMMENT = 3;
109 private final XmlHandler _handler;
110 private int _first = 0;
111 private int _last = 0;
112 private int _state = IN_TAG;
113 private int _prevTagState = IN_TAG;
114
115 public XmlParser(XmlHandler handler) {
116 _handler = handler;
117 }
118
119 public void parse(final String xml) {
120 char prevCh = ' ';
121 for (int i = 0; i < xml.length(); i++) {
122 char ch = xml.charAt(i);
123 _prevTagState = _state;
124
125 if (_state == IN_COMMENT) {
126 if (ch == '>' && prevCh == '-') {
127 _state = CONTENT;
128 }
129 }
130 else if (ch == '<') {
131 _state = IN_TAG;
132 }
133 else if (ch == '>') {
134 _state = CONTENT;
135 }
136 else if (_state == IN_TAG && ch == '!') {
137 _state = IN_COMMENT;
138 }
139 else if (_state == IN_TAG && ch == '/') {
140 _state = IN_CLOSING_TAG;
141 }
142
143 prevCh = ch;
144 if (_state != _prevTagState
145 && !(_prevTagState == IN_TAG
146 && _state == IN_CLOSING_TAG)
147 && !(_prevTagState == IN_TAG
148 && _state == IN_COMMENT)) {
149
150
151 _last = i;
152 handleStateChange(xml);
153 _first = _last;
154 }
155 }
156 }
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173 private void handleStateChange(final String xml) {
174 switch (_prevTagState) {
175 case IN_TAG:
176 _handler.startElement(xml.substring(_first, _last + 1));
177 break;
178 case IN_CLOSING_TAG:
179 _handler.endElement(xml.substring(_first, _last + 1));
180 break;
181 case IN_COMMENT:
182 _handler.comment(xml.substring(_first, _last + 1));
183 break;
184 case CONTENT:
185 _handler.characters(xml.substring(_first + 1, _last));
186 break;
187 default:
188 throw new IllegalStateException("Unknown State " + _prevTagState);
189 }
190 }
191 }
192 }