1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package edu.internet2.middleware.grouperInstaller.util;
21
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24
25
26
27
28
29
30 public class XmlIndenter {
31
32
33 private String xml;
34
35
36 private int startTagIndex;
37
38
39 private int endTagIndex;
40
41
42 private int currentNumberOfIndents;
43
44
45 private String currentTagName;
46
47
48 private StringBuilder result;
49
50
51
52
53
54 public String result() {
55 try {
56 this.indent();
57 } catch (RuntimeException re) {
58 throw new RuntimeException("Problem here: " + this, re);
59 }
60 if (this.xml == null) {
61 return null;
62 }
63 return GrouperInstallerUtils.trim(this.result.toString());
64 }
65
66
67
68
69 private void indent() {
70 if (this.xml == null) {
71 return;
72 }
73 this.result = new StringBuilder();
74 this.startTagIndex = -1;
75 this.endTagIndex = -1;
76 this.currentTagName = null;
77 this.currentNumberOfIndents = 0;
78
79
80
81
82
83
84
85
86
87
88
89
90 while(true) {
91 this.startTagIndex = findStartTagIndex();
92 if (this.startTagIndex == -1) {
93
94 if (this.endTagIndex != this.xml.length()-1) {
95 this.result.append(this.xml, this.endTagIndex+1, this.xml.length());
96 }
97 break;
98 }
99 this.endTagIndex = findEndTagIndex();
100
101
102 if (ignoreTag(this.xml, this.startTagIndex, this.endTagIndex)) {
103
104
105
106 this.printNewlineIndent(this.startTagIndex, this.endTagIndex+1);
107 continue;
108 }
109
110 this.currentTagName = findTagName();
111
112
113 if (selfClosedTag(this.xml, this.endTagIndex)) {
114
115
116 this.printNewlineIndent(this.startTagIndex, this.endTagIndex+1);
117 } else if (closeTag(this.xml, this.startTagIndex)) {
118
119 this.unindent();
120 this.currentNumberOfIndents--;
121
122 this.printNewlineIndent(this.startTagIndex, this.endTagIndex+1);
123
124 } else {
125 int nextTagStartIndex = findNextStartTagIndex(this.xml, this.endTagIndex+1);
126 int nextTagEndIndex = findNextEndTagIndex(this.xml, nextTagStartIndex+1);
127
128 String nextTagName = tagName(this.xml, nextTagStartIndex, nextTagEndIndex);
129 boolean isNextTagCloseTag = closeTag(this.xml, nextTagStartIndex);
130 if (!textTag(this.xml, this.endTagIndex, this.currentTagName, nextTagName, isNextTagCloseTag)) {
131 this.currentNumberOfIndents++;
132 this.printNewlineIndent(this.startTagIndex, this.endTagIndex+1);
133 } else {
134
135 this.printNewlineIndent(this.startTagIndex, nextTagEndIndex+1);
136
137 this.startTagIndex = nextTagEndIndex;
138 this.endTagIndex = nextTagEndIndex;
139 }
140 }
141 }
142 }
143
144
145
146
147
148
149
150
151 static boolean ignoreTag(String theXml, int theStartTagIndex, int theEndTagIndex) {
152 char firstChar = theXml.charAt(theStartTagIndex+1);
153 if (firstChar == '?' || firstChar == '!') {
154 return true;
155 }
156 return false;
157 }
158
159
160
161
162
163
164 private void printNewlineIndent(int start, int end) {
165
166 this.result.append(this.xml, start, end);
167 this.newlineIndent();
168
169 }
170
171
172
173
174 private void newlineIndent() {
175 this.result.append("\n").append(GrouperInstallerUtils.repeat(" ", this.currentNumberOfIndents));
176 }
177
178
179
180
181 private void unindent() {
182 for (int i=0;i<2;i++) {
183 if (this.result.charAt(this.result.length()-1) == ' ') {
184 this.result.deleteCharAt(this.result.length()-1);
185 }
186 }
187 }
188
189
190
191
192
193
194
195
196
197
198 static String tagName(String xml, int startTagIndex, int endTagIndex) {
199 endTagIndex = endTagIndex > startTagIndex ? endTagIndex : (xml.length()-1);
200 String tag = xml.substring(startTagIndex, endTagIndex+1);
201 Pattern tagPattern = Pattern.compile("^<[\\s/]*([a-zA-Z_\\-0-9:\\.]+).*$", Pattern.DOTALL);
202 Matcher matcher = tagPattern.matcher(tag);
203 if (!matcher.matches()) {
204 throw new RuntimeException("Cant match tag: '" + tag + "'");
205 }
206
207 String tagName = matcher.group(1);
208 return tagName;
209 }
210
211
212
213
214
215 private int findStartTagIndex() {
216 return findNextStartTagIndex(this.xml, this.endTagIndex+1);
217 }
218
219
220
221
222
223 private String findTagName() {
224 return tagName(this.xml, this.startTagIndex, this.endTagIndex);
225 }
226
227
228
229
230
231 private int findEndTagIndex() {
232 return findNextEndTagIndex(this.xml, this.startTagIndex+1);
233 }
234
235
236
237
238
239
240
241 static int findNextStartTagIndex(String xml, int startFrom) {
242 int length = xml.length();
243 for (int i= startFrom; i<length;i++) {
244 if (xml.charAt(i) == '<') {
245 return i;
246 }
247 }
248 return -1;
249 }
250
251
252
253
254
255
256
257 static int findNextEndTagIndex(String xml, int startFrom) {
258 int length = xml.length();
259 for (int i= startFrom; i<length;i++) {
260 if (xml.charAt(i) == '>') {
261 return i;
262 }
263 }
264 return -1;
265 }
266
267
268
269
270
271
272
273 static boolean selfClosedTag(String xml, int endTagIndex) {
274 for (int i=endTagIndex-1;i>=0;i--) {
275 char curChar = xml.charAt(i);
276
277 if (Character.isWhitespace(curChar)) {
278 continue;
279 }
280 if (curChar == '/') {
281 return true;
282 }
283 return false;
284 }
285
286 return false;
287 }
288
289
290
291
292
293
294
295 static boolean closeTag(String xml, int startTagIndex) {
296 for (int i=startTagIndex+1;i<xml.length();i++) {
297 char curChar = xml.charAt(i);
298
299 if (Character.isWhitespace(curChar)) {
300 continue;
301 }
302 if (curChar == '/') {
303 return true;
304 }
305 return false;
306 }
307
308 return false;
309 }
310
311
312
313
314
315
316
317
318
319
320
321 static boolean textTag(String xml, int endTagIndex, String tagName,
322 String nextTagName, boolean isNextCloseTag) {
323 if (GrouperInstallerUtils.equals(tagName, nextTagName) && isNextCloseTag) {
324 return true;
325 }
326 return false;
327 }
328
329
330
331
332
333 public XmlIndenter(String theXml) {
334 if (theXml != null) {
335 this.xml = GrouperInstallerUtils.trimToEmpty(theXml);
336 }
337 }
338
339 }