source: SRUClient/trunk/src/main/java/eu/clarin/sru/client/SRUXMLStreamReader.java @ 7272

Last change on this file since 7272 was 7272, checked in by Oliver Schonefeld, 2 years ago
  • update copyright
  • Property svn:eol-style set to native
File size: 24.5 KB
Line 
1/**
2 * This software is copyright (c) 2012-2022 by
3 *  - Leibniz-Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
4 * This is free software. You can redistribute it
5 * and/or modify it under the terms described in
6 * the GNU General Public License v3 of which you
7 * should have received a copy. Otherwise you can download
8 * it from
9 *
10 *   http://www.gnu.org/licenses/gpl-3.0.txt
11 *
12 * @copyright Leibniz-Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
13 *
14 * @license http://www.gnu.org/licenses/gpl-3.0.txt
15 *  GNU General Public License v3
16 */
17package eu.clarin.sru.client;
18
19import java.io.FilterInputStream;
20import java.io.IOException;
21import java.io.InputStream;
22
23import javax.xml.namespace.NamespaceContext;
24import javax.xml.namespace.QName;
25import javax.xml.stream.Location;
26import javax.xml.stream.XMLInputFactory;
27import javax.xml.stream.XMLStreamConstants;
28import javax.xml.stream.XMLStreamException;
29import javax.xml.stream.XMLStreamReader;
30import javax.xml.stream.XMLStreamWriter;
31
32import org.codehaus.stax2.XMLInputFactory2;
33import org.codehaus.stax2.XMLStreamReader2;
34
35import com.ctc.wstx.api.WstxInputProperties;
36
37
38class SRUXMLStreamReader implements XMLStreamReader {
39    private static final class CountingInputStream extends FilterInputStream {
40        protected long count = 0;
41
42        private CountingInputStream(InputStream stream) {
43            super(stream);
44        }
45
46        @Override
47        public int read() throws IOException {
48            final int value = super.read();
49            if (value != -1) {
50                count++;
51            }
52            return value;
53        }
54
55        @Override
56        public int read(byte[] buffer, int offset, int length)
57                throws IOException {
58            final int result = super.read(buffer, offset, length);
59            if (result >= 0) {
60                count += result;
61            }
62            return result;
63        }
64
65        @Override
66        public int read(byte[] buffer) throws IOException {
67            return this.read(buffer, 0, buffer.length);
68        }
69
70        @Override
71        public long skip(long n) throws IOException {
72            final long result = super.skip(n);
73            if (result > 0) {
74                count += result;
75            }
76            return result;
77        }
78    } // class CountingInputStream
79    private static final XMLInputFactory2 factory;
80    private final InputStream stream;
81    private final XMLStreamReader2 reader;
82
83    SRUXMLStreamReader(InputStream in, boolean wrap) throws XMLStreamException {
84        this.stream = wrap ? new CountingInputStream(in) : in;
85        this.reader =
86                (XMLStreamReader2) factory.createXMLStreamReader(stream);
87    }
88
89
90    @Override
91    public void close() throws XMLStreamException {
92        reader.close();
93    }
94
95
96    @Override
97    public int getAttributeCount() {
98        return reader.getAttributeCount();
99    }
100
101
102    @Override
103    public String getAttributeLocalName(int index) {
104        return reader.getAttributeLocalName(index);
105    }
106
107
108    @Override
109    public QName getAttributeName(int index) {
110        return reader.getAttributeName(index);
111    }
112
113
114    @Override
115    public String getAttributeNamespace(int index) {
116        return reader.getAttributeNamespace(index);
117    }
118
119
120    @Override
121    public String getAttributePrefix(int index) {
122        return reader.getAttributePrefix(index);
123    }
124
125
126    @Override
127    public String getAttributeType(int index) {
128        return reader.getAttributeType(index);
129    }
130
131
132    @Override
133    public String getAttributeValue(int index) {
134        return reader.getAttributeValue(index);
135    }
136
137
138    @Override
139    public String getAttributeValue(String namespaceURI, String localName) {
140        return reader.getAttributeValue(namespaceURI, localName);
141    }
142
143
144    @Override
145    public String getCharacterEncodingScheme() {
146        return reader.getCharacterEncodingScheme();
147    }
148
149
150    @Override
151    public String getElementText() throws XMLStreamException {
152        return reader.getElementText();
153    }
154
155
156    @Override
157    public String getEncoding() {
158        return reader.getEncoding();
159    }
160
161
162    @Override
163    public int getEventType() {
164        return reader.getEventType();
165    }
166
167
168    @Override
169    public String getLocalName() {
170        return reader.getLocalName();
171    }
172
173
174    @Override
175    public Location getLocation() {
176        return reader.getLocation();
177    }
178
179
180    @Override
181    public QName getName() {
182        return reader.getName();
183    }
184
185
186    @Override
187    public NamespaceContext getNamespaceContext() {
188        return reader.getNamespaceContext();
189    }
190
191
192    @Override
193    public int getNamespaceCount() {
194        return reader.getNamespaceCount();
195    }
196
197
198    @Override
199    public String getNamespacePrefix(int index) {
200        return reader.getNamespacePrefix(index);
201    }
202
203
204    @Override
205    public String getNamespaceURI() {
206        return reader.getNamespaceURI();
207    }
208
209
210    @Override
211    public String getNamespaceURI(String prefix) {
212        return reader.getNamespaceURI(prefix);
213    }
214
215
216    @Override
217    public String getNamespaceURI(int index) {
218        return reader.getNamespaceURI(index);
219    }
220
221
222    @Override
223    public String getPIData() {
224        return reader.getPIData();
225    }
226
227
228    @Override
229    public String getPITarget() {
230        return reader.getPITarget();
231    }
232
233
234    @Override
235    public String getPrefix() {
236        return reader.getPrefix();
237    }
238
239
240    @Override
241    public Object getProperty(String name) throws IllegalArgumentException {
242        return reader.getProperty(name);
243    }
244
245
246    @Override
247    public String getText() {
248        return reader.getText();
249    }
250
251
252    @Override
253    public char[] getTextCharacters() {
254        return reader.getTextCharacters();
255    }
256
257
258    @Override
259    public int getTextCharacters(int sourceStart, char[] target,
260            int targetStart, int length) throws XMLStreamException {
261        return reader.getTextCharacters(sourceStart, target, targetStart, length);
262    }
263
264
265    @Override
266    public int getTextLength() {
267        return reader.getTextLength();
268    }
269
270
271    @Override
272    public int getTextStart() {
273        return reader.getTextStart();
274    }
275
276
277    @Override
278    public String getVersion() {
279        return reader.getVersion();
280    }
281
282
283    @Override
284    public boolean hasName() {
285        return reader.hasName();
286    }
287
288
289    @Override
290    public boolean hasNext() throws XMLStreamException {
291        return reader.hasNext();
292    }
293
294
295    @Override
296    public boolean hasText() {
297        return reader.hasText();
298    }
299
300
301    @Override
302    public boolean isAttributeSpecified(int index) {
303        return reader.isAttributeSpecified(index);
304    }
305
306
307    @Override
308    public boolean isCharacters() {
309        return reader.isCharacters();
310    }
311
312
313    @Override
314    public boolean isEndElement() {
315        return reader.isEndElement();
316    }
317
318
319    @Override
320    public boolean isStandalone() {
321        return reader.isStandalone();
322    }
323
324
325    @Override
326    public boolean isStartElement() {
327        return reader.isStartElement();
328    }
329
330
331    @Override
332    public boolean isWhiteSpace() {
333        return reader.isWhiteSpace();
334    }
335
336
337    @Override
338    public int next() throws XMLStreamException {
339        return reader.next();
340    }
341
342
343    @Override
344    public int nextTag() throws XMLStreamException {
345        return reader.nextTag();
346    }
347
348
349    @Override
350    public void require(int type, String namespaceURI, String localName)
351            throws XMLStreamException {
352        reader.require(type, namespaceURI, localName);
353    }
354
355
356    @Override
357    public boolean standaloneSet() {
358        return reader.standaloneSet();
359    }
360
361
362    void closeCompletly() {
363        try {
364            reader.close();
365        } catch (XMLStreamException e) {
366            /* IGNORE */
367        }
368        try {
369            stream.close();
370        } catch (IOException e) {
371            /* IGNORE */
372        }
373    }
374
375
376    long getByteCount() {
377        if (stream instanceof CountingInputStream) {
378            return ((CountingInputStream) stream).count;
379        } else {
380            return -1;
381        }
382    }
383
384
385    boolean readStart(String namespaceURI, String localName, boolean required)
386            throws XMLStreamException {
387        return readStart(namespaceURI, localName, required, false);
388    }
389
390
391    boolean readStart(String namespaceURI, String localName, boolean required,
392            boolean attributes) throws XMLStreamException {
393        // System.err.println("readStart (" + localName + ", required = " +
394        // required + ") @ " + toReadable(reader));
395        if (!reader.isEndElement()) {
396            while (reader.hasNext()) {
397                // System.err.println("  LOOP: " + dumpState());
398                if (reader.isWhiteSpace()) {
399                    reader.next();
400                    continue;
401                }
402                if (reader.isStartElement()) {
403                    if (namespaceURI.equals(reader.getNamespaceURI()) &&
404                            localName.equals(reader.getLocalName())) {
405                        // System.err.print("--> found ");
406                        if (!attributes) {
407                            // System.err.print("and consumed ");
408                            reader.next(); // skip to next event
409                        }
410                        // System.err.println("@ " + toReadable(reader));
411                        return true;
412                    }
413                    break;
414                }
415                if (reader.isCharacters() || reader.isEndElement()) {
416                    break;
417                }
418                reader.next();
419            } // while
420        }
421        if (required) {
422            // System.err.println("--> error, not found @ " +
423            // toReadable(reader));
424            String what;
425            if (reader.isStartElement() || reader.isEndElement() ||
426                    reader.isEmptyElement()) {
427                what = "'" + reader.getName().toString() + "'";
428            } else {
429                what = "some character data";
430            }
431            throw new XMLStreamException("expected element '" +
432                    new QName(namespaceURI, localName) + "', but found " +
433                    what, reader.getLocation());
434        }
435        // System.err.println("--> not found @ " + toReadable(reader));
436        return false;
437    }
438
439
440    void readEnd(String namespaceURI, String localName)
441            throws XMLStreamException {
442        readEnd(namespaceURI, localName, false);
443    }
444
445
446    void readEnd(String namespaceURI, String localName, boolean skipContent)
447            throws XMLStreamException {
448        // System.err.println("readEnd (" + localName + ") @ " + dumpState() +
449        // ", skipContent = " + skipContent);
450        int level = 1;
451        while (reader.hasNext()) {
452            // System.err.println("  LOOP " + dumpState() + " [" +
453            // level + "]");
454            if (reader.isWhiteSpace()) {
455                reader.next();
456                continue;
457            }
458            if (skipContent) {
459                if (reader.isCharacters()) {
460                    reader.next();
461                    continue;
462                }
463                if (reader.isStartElement()) {
464                    if (!(namespaceURI.equals(reader.getNamespaceURI()) &&
465                            localName.equals(reader.getLocalName()))) {
466                        level++;
467                    }
468                    reader.next();
469                    continue;
470                }
471            }
472            if (reader.isEndElement()) {
473                level--;
474                // System.err.println("   @END-TAG: " + dumpState() + " [" +
475                // level + "]");
476                if (level == 0) {
477                    if (namespaceURI.equals(reader.getNamespaceURI()) &&
478                            localName.equals(reader.getLocalName())) {
479                        reader.next(); // consume tag
480                        break;
481                    } else {
482                        String what;
483                        if (reader.isStartElement() || reader.isEndElement() ||
484                                reader.isEmptyElement()) {
485                            what = "'" + reader.getName().toString() + "'";
486                        } else {
487                            what = "some character data";
488                        }
489                        throw new XMLStreamException("expected end tag for '" +
490                                new QName(namespaceURI, localName) +
491                                "', but found " + what, reader.getLocation());
492                    }
493                }
494            }
495            reader.next();
496        }
497        // System.err.println("--> ok @ " + dumpState());
498    }
499
500
501    boolean peekStart(String namespaceURI, String localName)
502            throws XMLStreamException {
503        if (!reader.isEndElement()) {
504            while (reader.hasNext()) {
505                // System.err.println("  LOOP: " + dumpState());
506                if (reader.isWhiteSpace()) {
507                    reader.next();
508                    continue;
509                }
510                if (reader.isStartElement()) {
511                    if (namespaceURI.equals(reader.getNamespaceURI()) &&
512                            localName.equals(reader.getLocalName())) {
513                        return true;
514                    } else {
515                        return false;
516                    }
517                }
518                if (reader.isCharacters() || reader.isEndElement()) {
519                    break;
520                }
521                reader.next();
522            } // while
523        }
524        return false;
525    }
526
527
528    String readContent(String namespaceURI, String localName, boolean required)
529            throws XMLStreamException {
530        return readContent(namespaceURI, localName, required, true);
531    }
532
533
534    String readContent(String namespaceURI, String localName, boolean required,
535            boolean contentRequired) throws XMLStreamException {
536        String result = null;
537        if (readStart(namespaceURI, localName, required)) {
538            try {
539                result = readString(contentRequired);
540                if (!contentRequired && (result == null)) {
541                    result = "";
542                }
543            } catch (XMLStreamException e) {
544                StringBuilder sb = new StringBuilder();
545                sb.append("element '");
546                if (namespaceURI != null) {
547                    sb.append('{').append(namespaceURI).append('}');
548                }
549                sb.append(localName).append("' may not be empty");
550                throw new XMLStreamException(sb.toString(), e.getLocation());
551            }
552            readEnd(namespaceURI, localName);
553        }
554        return result;
555    }
556
557
558    int readContent(String namespaceURI, String localName, boolean required,
559            int defaultValue) throws XMLStreamException {
560        if (readStart(namespaceURI, localName, required)) {
561            String s = readString(true);
562            try {
563                readEnd(namespaceURI, localName);
564            } catch (XMLStreamException e) {
565                StringBuilder sb = new StringBuilder();
566                sb.append("element '");
567                if (namespaceURI != null) {
568                    sb.append('{').append(namespaceURI).append('}');
569                }
570                sb.append(localName).append("' may not be empty");
571                throw new XMLStreamException(sb.toString(), e.getLocation());
572            }
573            try {
574                return Integer.parseInt(s);
575            } catch (NumberFormatException e) {
576                StringBuilder sb = new StringBuilder();
577                sb.append("element '");
578                if (namespaceURI != null) {
579                    sb.append('{').append(namespaceURI).append('}');
580                }
581                sb.append(" was expected to be of type xs:integer; ");
582                sb.append("incompatible value was: ").append(s);
583                throw new XMLStreamException(sb.toString(),
584                        reader.getLocation(), e);
585            }
586        }
587        return defaultValue;
588    }
589
590
591    String readString(boolean required) throws XMLStreamException {
592        // System.err.println("readString @ " + toReadable(reader));
593        StringBuilder sb = new StringBuilder();
594        while (reader.isCharacters()) {
595            String s = reader.getText();
596            if (s != null) {
597                sb.append(s);
598            }
599            reader.next();
600        } // while
601        String s = null;
602        if (sb.length() > 0) {
603            s = sb.toString().trim();
604        }
605        if (required && ((s == null) || s.isEmpty())) {
606            throw new XMLStreamException("expected character content "
607                    + "at position ", reader.getLocation());
608        }
609        // System.err.println("--> ok @ " + toReadable(reader));
610        return s;
611    }
612
613
614    String readAttributeValue(String namespaceURI, String localName)
615            throws XMLStreamException {
616        if (!reader.isStartElement()) {
617            throw new XMLStreamException("not at a start elment event",
618                    reader.getLocation());
619        }
620        String attr = reader.getAttributeValue(namespaceURI, localName);
621        if (attr != null) {
622            attr = attr.trim().intern();
623        }
624        return attr;
625    }
626
627
628    String readNamespaceURI() throws XMLStreamException {
629        if (!reader.isStartElement()) {
630            throw new XMLStreamException("not at a start elment event",
631                    reader.getLocation());
632        }
633        return reader.getNamespaceURI();
634    }
635
636
637    String peekElementLocalName() throws XMLStreamException {
638        if (!reader.isStartElement()) {
639            throw new XMLStreamException("not at a start elment event",
640                    reader.getLocation());
641        }
642        return reader.getLocalName();
643    }
644
645
646    void consumeStart() throws XMLStreamException {
647        if (!reader.isStartElement()) {
648            throw new XMLStreamException("not at a start elment event",
649                    reader.getLocation());
650        }
651        reader.next();
652    }
653
654
655    void consumeWhitespace() throws XMLStreamException {
656        while (reader.isWhiteSpace() && reader.hasNext()) {
657            reader.next();
658            continue;
659        }
660    }
661
662
663    void copyTo(XMLStreamWriter writer) throws XMLStreamException {
664        final int depth = reader.getDepth();
665        do {
666            copyEvent(reader, writer);
667            reader.next();
668        } while (reader.getDepth() >= depth);
669    }
670
671
672    String dumpState() {
673        StringBuilder sb = new StringBuilder();
674        switch (reader.getEventType()) {
675        case XMLStreamConstants.START_DOCUMENT:
676            return "START_DOC";
677        case XMLStreamConstants.END_DOCUMENT:
678            return "END_DOC";
679        case XMLStreamConstants.START_ELEMENT:
680            sb.append("START[");
681            sb.append(reader.getNamespaceURI());
682            sb.append(",");
683            sb.append(reader.getLocalName());
684            sb.append("]");
685            break;
686        case XMLStreamConstants.END_ELEMENT:
687            sb.append("END[");
688            sb.append(reader.getNamespaceURI());
689            sb.append(",");
690            sb.append(reader.getLocalName());
691            sb.append("]");
692            break;
693        case XMLStreamConstants.CHARACTERS:
694            sb.append("CHARACTERS[\"");
695            sb.append(reader.getText().replace("\n", "\\n")
696                    .replace("\r", "\\r").replace("\t", "\\t"));
697            sb.append("\", isWhitespace = ");
698            sb.append(reader.isWhiteSpace());
699            sb.append("]");
700            break;
701        case XMLStreamConstants.CDATA:
702            sb.append("CDATA[\"");
703            sb.append(reader.getText().replace("\n", "\\n")
704                    .replace("\r", "\\r").replace("\t", "\\t"));
705            sb.append("\", isWhitespace = ");
706            sb.append(reader.isWhiteSpace());
707            sb.append("]");
708            break;
709        default:
710            sb.append(Integer.toString(reader.getEventType()));
711        }
712        return sb.toString();
713    }
714
715
716    private static void copyEvent(XMLStreamReader from, XMLStreamWriter to)
717            throws XMLStreamException {
718        switch (from.getEventType()) {
719        case XMLStreamConstants.START_DOCUMENT:
720            {
721                String version = from.getVersion();
722                if (version == null || version.length() == 0) {
723                    to.writeStartDocument();
724                } else {
725                    to.writeStartDocument(from.getCharacterEncodingScheme(),
726                            from.getVersion());
727                }
728                to.writeCharacters("\n");
729            }
730            return;
731
732        case XMLStreamConstants.END_DOCUMENT:
733            to.writeCharacters("\n");
734            to.writeEndDocument();
735            return;
736
737        case XMLStreamConstants.START_ELEMENT:
738            copyStartElement(from, to);
739            return;
740
741        case XMLStreamConstants.END_ELEMENT:
742            to.writeEndElement();
743            return;
744
745        case XMLStreamConstants.SPACE:
746            to.writeCharacters(from.getTextCharacters(), from.getTextStart(),
747                    from.getTextLength());
748            return;
749
750        case XMLStreamConstants.CDATA:
751            to.writeCData(from.getText());
752            return;
753
754        case XMLStreamConstants.CHARACTERS:
755            to.writeCharacters(from.getTextCharacters(), from.getTextStart(),
756                    from.getTextLength());
757            return;
758
759        case XMLStreamConstants.COMMENT:
760            to.writeComment(from.getText());
761            return;
762
763        case XMLStreamConstants.PROCESSING_INSTRUCTION:
764            to.writeProcessingInstruction(from.getPITarget(), from.getPIData());
765            return;
766
767        case XMLStreamConstants.DTD:
768        case XMLStreamConstants.ENTITY_REFERENCE:
769        case XMLStreamConstants.ATTRIBUTE:
770        case XMLStreamConstants.NAMESPACE:
771        case XMLStreamConstants.ENTITY_DECLARATION:
772        case XMLStreamConstants.NOTATION_DECLARATION:
773            /* FALL_TROUGH */
774        }
775        throw new XMLStreamException("unsupported event type: " +
776                from.getEventType());
777    }
778
779
780    private static void copyStartElement(XMLStreamReader from,
781            XMLStreamWriter to) throws XMLStreamException {
782        final int nsCount = from.getNamespaceCount();
783        if (nsCount > 0) { // yup, got some...
784            for (int i = 0; i < nsCount; ++i) {
785                String pfx = from.getNamespacePrefix(i);
786                String uri = from.getNamespaceURI(i);
787                if ((pfx == null) || pfx.isEmpty()) { // default NS
788                    to.setDefaultNamespace(uri);
789                } else {
790                    to.setPrefix(pfx, uri);
791                }
792            }
793        }
794
795        final String prefix             = from.getPrefix();
796        final NamespaceContext from_ctx = from.getNamespaceContext();
797        final NamespaceContext to_ctx   = to.getNamespaceContext();
798        boolean repair_prefix_namespace = false;
799        if ((prefix != null) && (to_ctx.getNamespaceURI(prefix) == null)) {
800            repair_prefix_namespace = true;
801            to.setPrefix(prefix, from_ctx.getNamespaceURI(prefix));
802        }
803
804        to.writeStartElement(prefix, from.getLocalName(),
805                from.getNamespaceURI());
806
807        if (nsCount > 0) {
808            // write namespace declarations
809            for (int i = 0; i < nsCount; ++i) {
810                String pfx = from.getNamespacePrefix(i);
811                String uri    = from.getNamespaceURI(i);
812
813                if ((pfx == null) || pfx.isEmpty()) { // default NS
814                    to.writeDefaultNamespace(uri);
815                } else {
816                    to.writeNamespace(pfx, uri);
817                }
818            }
819        }
820        if (repair_prefix_namespace) {
821            to.writeNamespace(prefix, from_ctx.getNamespaceURI(prefix));
822        }
823
824        int attrCount = from.getAttributeCount();
825        if (attrCount > 0) {
826            for (int i = 0; i < attrCount; ++i) {
827                to.writeAttribute(from.getAttributePrefix(i),
828                        from.getAttributeNamespace(i),
829                        from.getAttributeLocalName(i),
830                        from.getAttributeValue(i));
831            }
832        }
833    }
834
835
836    static {
837        factory = (XMLInputFactory2) XMLInputFactory.newInstance();
838        // Stax settings
839        factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.TRUE);
840
841        // Stax2 settings
842        factory.setProperty(XMLInputFactory2.P_INTERN_NS_URIS, Boolean.TRUE);
843        factory.setProperty(XMLInputFactory2.P_LAZY_PARSING, Boolean.FALSE);
844
845        // Woodstox settings
846        factory.setProperty(WstxInputProperties.P_NORMALIZE_LFS, Boolean.TRUE);
847    }
848
849} // SRUXMLStreamReader
Note: See TracBrowser for help on using the repository browser.