1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.model.transform.pull;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.Reader;
24 import java.util.ArrayDeque;
25 import java.util.Arrays;
26 import java.util.Deque;
27 import java.util.Objects;
28 import java.util.regex.Pattern;
29
30 import org.codehaus.plexus.util.xml.pull.XmlPullParser;
31 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
32
33
34
35
36
37
38
39 public class BufferingParser implements XmlPullParser {
40
41 private static final Pattern WHITESPACE_REGEX = Pattern.compile("[ \r\t\n]+");
42
43 protected XmlPullParser xmlPullParser;
44 protected Deque<Event> events;
45 protected Event current;
46 protected boolean bypass;
47
48 @SuppressWarnings("checkstyle:VisibilityModifier")
49 public static class Event {
50 public int event;
51 public String name;
52 public String prefix;
53 public String namespace;
54 public boolean empty;
55 public String text;
56 public Attribute[] attributes;
57 public Namespace[] namespaces;
58 public int columnNumber;
59 public int lineNumber;
60
61 public String positionDescription() {
62 return " " + TYPES[event] + " @" + lineNumber + ":" + columnNumber;
63 }
64
65 @Override
66 public String toString() {
67 switch (event) {
68 case START_DOCUMENT:
69 case END_DOCUMENT:
70 return "Event{event=" + TYPES[event] + "'}";
71 case PROCESSING_INSTRUCTION:
72 case TEXT:
73 case CDSECT:
74 case ENTITY_REF:
75 case COMMENT:
76 case IGNORABLE_WHITESPACE:
77 return "Event{event=" + TYPES[event] + ", text='" + text + "'}";
78 case START_TAG:
79 return "Event{" + "event=START_TAG"
80 + ", name='"
81 + name + '\'' + ", prefix='"
82 + prefix + '\'' + ", namespace='"
83 + namespace + '\'' + ", empty="
84 + empty + ", attributes="
85 + Arrays.toString(attributes) + ", namespaces="
86 + Arrays.toString(namespaces) + '}';
87 case END_TAG:
88 return "Event{" + "event=END_TAG"
89 + ", name='"
90 + name + '\'' + ", prefix='"
91 + prefix + '\'' + ", namespace='"
92 + namespace + '\'' + ", empty="
93 + empty + ", namespaces="
94 + Arrays.toString(namespaces) + '}';
95 default:
96 return "Event{" + "event="
97 + TYPES[event] + ", name='"
98 + name + '\'' + ", prefix='"
99 + prefix + '\'' + ", namespace='"
100 + namespace + '\'' + ", empty="
101 + empty + ", text='"
102 + text + '\'' + ", attributes="
103 + Arrays.toString(attributes) + ", namespaces="
104 + Arrays.toString(namespaces) + '}';
105 }
106 }
107 }
108
109 @SuppressWarnings("checkstyle:VisibilityModifier")
110 public static class Namespace {
111 public String prefix;
112 public String uri;
113 }
114
115 @SuppressWarnings("checkstyle:VisibilityModifier")
116 public static class Attribute {
117 public String name;
118 public String prefix;
119 public String namespace;
120 public String type;
121 public String value;
122 public boolean isDefault;
123 }
124
125 public BufferingParser(XmlPullParser xmlPullParser) {
126 this.xmlPullParser = xmlPullParser;
127 }
128
129 @Override
130 public void setFeature(String name, boolean state) throws XmlPullParserException {
131 xmlPullParser.setFeature(name, state);
132 }
133
134 @Override
135 public boolean getFeature(String name) {
136 return xmlPullParser.getFeature(name);
137 }
138
139 @Override
140 public void setProperty(String name, Object value) throws XmlPullParserException {
141 xmlPullParser.setProperty(name, value);
142 }
143
144 @Override
145 public Object getProperty(String name) {
146 return xmlPullParser.getProperty(name);
147 }
148
149 @Override
150 public void setInput(Reader in) throws XmlPullParserException {
151 xmlPullParser.setInput(in);
152 }
153
154 @Override
155 public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
156 xmlPullParser.setInput(inputStream, inputEncoding);
157 }
158
159 @Override
160 public String getInputEncoding() {
161 return xmlPullParser.getInputEncoding();
162 }
163
164 @Override
165 public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException {
166 xmlPullParser.defineEntityReplacementText(entityName, replacementText);
167 }
168
169 @Override
170 public int getNamespaceCount(int depth) throws XmlPullParserException {
171
172 return xmlPullParser.getNamespaceCount(depth);
173 }
174
175 @Override
176 public String getNamespacePrefix(int pos) throws XmlPullParserException {
177
178 return xmlPullParser.getNamespacePrefix(pos);
179 }
180
181 @Override
182 public String getNamespaceUri(int pos) throws XmlPullParserException {
183
184 return xmlPullParser.getNamespaceUri(pos);
185 }
186
187 @Override
188 public String getNamespace(String prefix) {
189
190 return xmlPullParser.getNamespace(prefix);
191 }
192
193 @Override
194 public int getDepth() {
195
196 return xmlPullParser.getDepth();
197 }
198
199 @Override
200 public String getPositionDescription() {
201 if (current != null) {
202 return current.positionDescription();
203 }
204 return xmlPullParser.getPositionDescription();
205 }
206
207 @Override
208 public int getLineNumber() {
209 if (current != null) {
210 return current.lineNumber;
211 }
212 return xmlPullParser.getLineNumber();
213 }
214
215 @Override
216 public int getColumnNumber() {
217 if (current != null) {
218 return current.columnNumber;
219 }
220 return xmlPullParser.getColumnNumber();
221 }
222
223 @Override
224 public boolean isWhitespace() throws XmlPullParserException {
225 if (current != null) {
226 if (current.event == TEXT || current.event == CDSECT) {
227 return WHITESPACE_REGEX.matcher(current.text).matches();
228 } else if (current.event == IGNORABLE_WHITESPACE) {
229 return true;
230 } else {
231 throw new XmlPullParserException("no content available to check for whitespaces");
232 }
233 }
234 return xmlPullParser.isWhitespace();
235 }
236
237 @Override
238 public String getText() {
239 return current != null ? current.text : xmlPullParser.getText();
240 }
241
242 @Override
243 public char[] getTextCharacters(int[] holderForStartAndLength) {
244 if (current != null) {
245 throw new IllegalStateException("Not supported during events replay");
246 }
247 return xmlPullParser.getTextCharacters(holderForStartAndLength);
248 }
249
250 @Override
251 public String getNamespace() {
252 return current != null ? current.namespace : xmlPullParser.getNamespace();
253 }
254
255 @Override
256 public String getName() {
257 return current != null ? current.name : xmlPullParser.getName();
258 }
259
260 @Override
261 public String getPrefix() {
262 return current != null ? current.prefix : xmlPullParser.getPrefix();
263 }
264
265 @Override
266 public boolean isEmptyElementTag() throws XmlPullParserException {
267 return current != null ? current.empty : xmlPullParser.isEmptyElementTag();
268 }
269
270 @Override
271 public int getAttributeCount() {
272 if (current != null) {
273 return current.attributes != null ? current.attributes.length : 0;
274 } else {
275 return xmlPullParser.getAttributeCount();
276 }
277 }
278
279 @Override
280 public String getAttributeNamespace(int index) {
281 if (current != null) {
282 return current.attributes[index].namespace;
283 } else {
284 return xmlPullParser.getAttributeNamespace(index);
285 }
286 }
287
288 @Override
289 public String getAttributeName(int index) {
290 if (current != null) {
291 return current.attributes[index].name;
292 } else {
293 return xmlPullParser.getAttributeName(index);
294 }
295 }
296
297 @Override
298 public String getAttributePrefix(int index) {
299 if (current != null) {
300 return current.attributes[index].prefix;
301 } else {
302 return xmlPullParser.getAttributePrefix(index);
303 }
304 }
305
306 @Override
307 public String getAttributeType(int index) {
308 if (current != null) {
309 return current.attributes[index].type;
310 } else {
311 return xmlPullParser.getAttributeType(index);
312 }
313 }
314
315 @Override
316 public boolean isAttributeDefault(int index) {
317 if (current != null) {
318 return current.attributes[index].isDefault;
319 } else {
320 return xmlPullParser.isAttributeDefault(index);
321 }
322 }
323
324 @Override
325 public String getAttributeValue(int index) {
326 if (current != null) {
327 return current.attributes[index].value;
328 } else {
329 return xmlPullParser.getAttributeValue(index);
330 }
331 }
332
333 @Override
334 public String getAttributeValue(String namespace, String name) {
335 if (current != null) {
336 if (current.attributes != null) {
337 for (Attribute attr : current.attributes) {
338 if (Objects.equals(namespace, attr.namespace) && Objects.equals(name, attr.name)) {
339 return attr.value;
340 }
341 }
342 }
343 return null;
344 } else {
345 return xmlPullParser.getAttributeValue(namespace, name);
346 }
347 }
348
349 @Override
350 public void require(int type, String namespace, String name) throws XmlPullParserException, IOException {
351 if (current != null) {
352 throw new IllegalStateException("Not supported during events replay");
353 }
354 xmlPullParser.require(type, namespace, name);
355 }
356
357 @Override
358 public int getEventType() throws XmlPullParserException {
359 return current != null ? current.event : xmlPullParser.getEventType();
360 }
361
362 @Override
363 public int next() throws XmlPullParserException, IOException {
364 while (true) {
365 if (events != null && !events.isEmpty()) {
366 current = events.removeFirst();
367 return current.event;
368 } else {
369 current = null;
370 }
371 if (getEventType() == END_DOCUMENT) {
372 throw new XmlPullParserException("already reached end of XML input", this, null);
373 }
374 int currentEvent = xmlPullParser.next();
375 if (bypass() || accept()) {
376 return currentEvent;
377 }
378 }
379 }
380
381 @Override
382 public int nextToken() throws XmlPullParserException, IOException {
383 while (true) {
384 if (events != null && !events.isEmpty()) {
385 current = events.removeFirst();
386 return current.event;
387 } else {
388 current = null;
389 }
390 if (getEventType() == END_DOCUMENT) {
391 throw new XmlPullParserException("already reached end of XML input", this, null);
392 }
393 int currentEvent = xmlPullParser.nextToken();
394 if (bypass() || accept()) {
395 return currentEvent;
396 }
397 }
398 }
399
400 @Override
401 public int nextTag() throws XmlPullParserException, IOException {
402 int eventType = next();
403 if (eventType == TEXT && isWhitespace()) {
404 eventType = next();
405 }
406 if (eventType != START_TAG && eventType != END_TAG) {
407 throw new XmlPullParserException("expected START_TAG or END_TAG not " + TYPES[getEventType()], this, null);
408 }
409 return eventType;
410 }
411
412 @Override
413 public String nextText() throws XmlPullParserException, IOException {
414 int eventType = getEventType();
415 if (eventType != START_TAG) {
416 throw new XmlPullParserException("parser must be on START_TAG to read next text", this, null);
417 }
418 eventType = next();
419 if (eventType == TEXT) {
420 final String result = getText();
421 eventType = next();
422 if (eventType != END_TAG) {
423 throw new XmlPullParserException(
424 "TEXT must be immediately followed by END_TAG and not " + TYPES[getEventType()], this, null);
425 }
426 return result;
427 } else if (eventType == END_TAG) {
428 return "";
429 } else {
430 throw new XmlPullParserException("parser must be on START_TAG or TEXT to read text", this, null);
431 }
432 }
433
434 protected Event bufferEvent() throws XmlPullParserException {
435 Event event = new Event();
436 XmlPullParser pp = xmlPullParser;
437 event.event = xmlPullParser.getEventType();
438 event.columnNumber = xmlPullParser.getColumnNumber();
439 event.lineNumber = xmlPullParser.getLineNumber();
440 switch (event.event) {
441 case START_DOCUMENT:
442 case END_DOCUMENT:
443 break;
444 case START_TAG:
445 event.name = pp.getName();
446 event.namespace = pp.getNamespace();
447 event.prefix = pp.getPrefix();
448 event.empty = pp.isEmptyElementTag();
449 event.text = pp.getText();
450 event.attributes = new Attribute[pp.getAttributeCount()];
451 for (int i = 0; i < pp.getAttributeCount(); i++) {
452 Attribute attr = new Attribute();
453 attr.name = pp.getAttributeName(i);
454 attr.namespace = pp.getAttributeNamespace(i);
455 attr.value = pp.getAttributeValue(i);
456 attr.type = pp.getAttributeType(i);
457 attr.isDefault = pp.isAttributeDefault(i);
458 event.attributes[i] = attr;
459 }
460 break;
461 case END_TAG:
462 event.name = pp.getName();
463 event.namespace = pp.getNamespace();
464 event.prefix = pp.getPrefix();
465 event.text = pp.getText();
466 break;
467 case TEXT:
468 case COMMENT:
469 case IGNORABLE_WHITESPACE:
470 case CDSECT:
471 case ENTITY_REF:
472 event.text = pp.getText();
473 break;
474 default:
475 break;
476 }
477 return event;
478 }
479
480 protected void pushEvent(Event event) {
481 if (events == null) {
482 events = new ArrayDeque<>();
483 }
484 events.add(event);
485 }
486
487 protected boolean accept() throws XmlPullParserException, IOException {
488 return true;
489 }
490
491 public void bypass(boolean bypass) {
492 if (bypass && events != null && !events.isEmpty()) {
493 throw new IllegalStateException("Can not disable filter while processing");
494 }
495 this.bypass = bypass;
496 }
497
498 public boolean bypass() {
499 return bypass || (xmlPullParser instanceof BufferingParser && ((BufferingParser) xmlPullParser).bypass());
500 }
501
502 protected static String nullSafeAppend(String originalValue, String charSegment) {
503 if (originalValue == null) {
504 return charSegment;
505 } else {
506 return originalValue + charSegment;
507 }
508 }
509 }