source: SRUServer/trunk/src/main/java/eu/clarin/sru/server/SRUXMLStreamWriter.java @ 7269

Last change on this file since 7269 was 7269, checked in by Oliver Schonefeld, 2 years ago
  • Update copyright
  • Property svn:eol-style set to native
File size: 15.7 KB
RevLine 
[1881]1/**
[7269]2 * This software is copyright (c) 2011-2022 by
3 *  - Leibniz-Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
[1881]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 *
[7269]12 * @copyright Leibniz-Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
[1881]13 *
14 * @license http://www.gnu.org/licenses/gpl-3.0.txt
15 *  GNU General Public License v3
16 */
17package eu.clarin.sru.server;
18
19import java.io.IOException;
20import java.io.OutputStream;
21import java.io.OutputStreamWriter;
22import java.io.StringReader;
23import java.io.Writer;
[1955]24import java.util.ArrayDeque;
25import java.util.Deque;
[1881]26
27import javax.xml.namespace.NamespaceContext;
28import javax.xml.parsers.SAXParser;
29import javax.xml.parsers.SAXParserFactory;
30import javax.xml.stream.XMLOutputFactory;
31import javax.xml.stream.XMLStreamException;
32import javax.xml.stream.XMLStreamWriter;
33
34import org.xml.sax.Attributes;
35import org.xml.sax.InputSource;
36import org.xml.sax.SAXException;
37import org.xml.sax.helpers.DefaultHandler;
38import org.z3950.zing.cql.CQLNode;
39
40final class SRUXMLStreamWriter implements XMLStreamWriter {
[1955]41    private enum IndentingState {
42        SEEN_NOTHING,
43        SEEN_ELEMENT,
[2103]44        SEEN_DATA
[1955]45    }
[1881]46    private static final SAXParserFactory factory;
[6816]47    private final SRURecordXmlEscaping recordEscaping;
[1881]48    private final Writer writer;
49    private final XMLStreamWriter xmlwriter;
[1955]50    private int indent = -1;
51    private int depth = 0;
52    private Deque<IndentingState> stateStack;
53    private IndentingState state;
[1881]54    private boolean writingRecord = false;
55
[1956]56
[6816]57    SRUXMLStreamWriter(OutputStream stream,
58            XMLOutputFactory factory,
59            SRURecordXmlEscaping recordXmlEscaping,
60            int indent) throws IOException, XMLStreamException {
61        this.recordEscaping = recordXmlEscaping;
[1881]62        this.writer = new OutputStreamWriter(stream,
[1889]63                SRUServer.RESPONSE_ENCODING) {
[1881]64            @Override
65            public void write(int c) throws IOException {
[6816]66                if (writingRecord && (recordEscaping == SRURecordXmlEscaping.STRING)) {
[1881]67                    /*
68                     * NOTE: need to write single characters here, because
69                     * super.write(String) will call us again, and we would
70                     * create and endless loop here until stack blows up ...
71                     */
72                    switch (c) {
73                    case '<':
74                        super.write('&');
75                        super.write('l');
76                        super.write('t');
77                        super.write(';');
78                        return;
79                    case '>':
80                        super.write('&');
81                        super.write('g');
82                        super.write('t');
83                        super.write(';');
84                        return;
85                    case '&':
86                        super.write('&');
87                        super.write('a');
88                        super.write('m');
89                        super.write('p');
90                        super.write(';');
91                        return;
92                    default:
93                        /* $FALL-THROUGH$ */
94                    }
95                }
96                super.write(c);
97            }
98
99            @Override
100            public void write(char[] c, int off, int len) throws IOException {
[6816]101                if (writingRecord && (recordEscaping == SRURecordXmlEscaping.STRING)) {
[1881]102                    for (int i = off; i < len; i++) {
103                        this.write(c[i]);
104                    }
105                } else {
106                    super.write(c, off, len);
107                }
108            }
109
110            @Override
111            public void write(String s, int off, int len) throws IOException {
[6816]112                if (writingRecord && (recordEscaping == SRURecordXmlEscaping.STRING)) {
[1881]113                    for (int i = off; i < len; i++) {
114                        this.write(s.charAt(i));
115                    }
116                } else {
117                    super.write(s, off, len);
118                }
119            }
120        };
121
122        /*
123         * It is believed, that XMLWriterFactories, once configured,
124         * are thread-save ...
125         */
126        this.xmlwriter = factory.createXMLStreamWriter(this.writer);
[1956]127
[1957]128        if (indent > 0) {
[1955]129            this.indent = indent;
130            this.state = IndentingState.SEEN_NOTHING;
131            this.stateStack = new ArrayDeque<IndentingState>(16);
132        }
[1881]133    }
134
[1956]135
[1881]136    @Override
137    public void writeStartElement(String localName) throws XMLStreamException {
[1955]138        if (indent > 0) {
139            onStartElement();
140        }
[1881]141        xmlwriter.writeStartElement(localName);
142    }
143
[1956]144
[1881]145    @Override
146    public void writeStartElement(String namespaceURI, String localName)
147            throws XMLStreamException {
[1955]148        if (indent > 0) {
149            onStartElement();
150        }
[1881]151        xmlwriter.writeStartElement(namespaceURI, localName);
152    }
153
[1956]154
[1881]155    @Override
156    public void writeStartElement(String prefix, String localName,
157            String namespaceURI) throws XMLStreamException {
[1955]158        if (indent > 0) {
159            onStartElement();
160        }
[1881]161        xmlwriter.writeStartElement(prefix, localName, namespaceURI);
162    }
163
[1956]164
[1881]165    @Override
166    public void writeEmptyElement(String namespaceURI, String localName)
167            throws XMLStreamException {
[1955]168        if (indent > 0) {
169            onEmptyElement();
170        }
[1881]171        xmlwriter.writeEmptyElement(namespaceURI, localName);
172    }
173
[1956]174
[1881]175    @Override
176    public void writeEmptyElement(String prefix, String localName,
177            String namespaceURI) throws XMLStreamException {
[1955]178        if (indent > 0) {
179            onEmptyElement();
180        }
[1881]181        xmlwriter.writeEmptyElement(prefix, localName, namespaceURI);
182    }
183
[1956]184
[1881]185    @Override
186    public void writeEmptyElement(String localName) throws XMLStreamException {
[1955]187        if (indent > 0) {
188            onEmptyElement();
189        }
[1881]190        xmlwriter.writeEmptyElement(localName);
191    }
192
[1956]193
[1881]194    @Override
195    public void writeEndElement() throws XMLStreamException {
[1955]196        if (indent > 0) {
197            onEndElement();
198        }
[1881]199        xmlwriter.writeEndElement();
200    }
201
[1956]202
[1881]203    @Override
204    public void writeEndDocument() throws XMLStreamException {
205        xmlwriter.writeEndDocument();
206    }
207
[1956]208
[1881]209    @Override
210    public void close() throws XMLStreamException {
211        xmlwriter.close();
212    }
213
[1956]214
[1881]215    @Override
216    public void flush() throws XMLStreamException {
217        xmlwriter.flush();
218    }
219
[1956]220
[1881]221    @Override
222    public void writeAttribute(String localName, String value)
223            throws XMLStreamException {
224        xmlwriter.writeAttribute(localName, value);
225    }
226
[1956]227
[1881]228    @Override
229    public void writeAttribute(String prefix, String namespaceURI,
230            String localName, String value) throws XMLStreamException {
231        xmlwriter.writeAttribute(prefix, namespaceURI, localName, value);
232    }
233
[1956]234
[1881]235    @Override
236    public void writeAttribute(String namespaceURI, String localName,
237            String value) throws XMLStreamException {
238        xmlwriter.writeAttribute(namespaceURI, localName, value);
239    }
240
[1956]241
[1881]242    @Override
243    public void writeNamespace(String prefix, String namespaceURI)
244            throws XMLStreamException {
245        xmlwriter.writeNamespace(prefix, namespaceURI);
246    }
247
[1956]248
[1881]249    @Override
250    public void writeDefaultNamespace(String namespaceURI)
251            throws XMLStreamException {
252        xmlwriter.writeDefaultNamespace(namespaceURI);
253    }
254
[1956]255
[1881]256    @Override
257    public void writeComment(String data) throws XMLStreamException {
258        xmlwriter.writeComment(data);
259    }
260
[1956]261
[1881]262    @Override
263    public void writeProcessingInstruction(String target)
264            throws XMLStreamException {
265        xmlwriter.writeProcessingInstruction(target);
266    }
267
[1956]268
[1881]269    @Override
270    public void writeProcessingInstruction(String target, String data)
271            throws XMLStreamException {
272        xmlwriter.writeProcessingInstruction(target, data);
273    }
274
[1956]275
[1881]276    @Override
277    public void writeCData(String data) throws XMLStreamException {
[1955]278        if (indent > 0) {
279            state = IndentingState.SEEN_DATA;
280        }
[1881]281        xmlwriter.writeCData(data);
282    }
283
[1956]284
[1881]285    @Override
286    public void writeDTD(String dtd) throws XMLStreamException {
287        xmlwriter.writeDTD(dtd);
288    }
289
[1956]290
[1881]291    @Override
292    public void writeEntityRef(String name) throws XMLStreamException {
293        xmlwriter.writeEntityRef(name);
294    }
295
[1956]296
[1881]297    @Override
298    public void writeStartDocument() throws XMLStreamException {
299        xmlwriter.writeStartDocument();
[1955]300        if (indent > 0) {
301            xmlwriter.writeCharacters("\n");
302        }
[1881]303    }
304
[1956]305
[1881]306    @Override
307    public void writeStartDocument(String version) throws XMLStreamException {
308        xmlwriter.writeStartDocument(version);
[1955]309        if (indent > 0) {
310            xmlwriter.writeCharacters("\n");
311        }
[1881]312    }
313
[1956]314
[1881]315    @Override
316    public void writeStartDocument(String encoding, String version)
317            throws XMLStreamException {
318        xmlwriter.writeStartDocument(encoding, version);
[1955]319        if (indent > 0) {
320            xmlwriter.writeCharacters("\n");
321        }
[1881]322    }
323
[1956]324
[1881]325    @Override
326    public void writeCharacters(String text) throws XMLStreamException {
[1955]327        if (indent > 0) {
328            state = IndentingState.SEEN_DATA;
329        }
[1881]330        xmlwriter.writeCharacters(text);
331    }
332
[1956]333
[1881]334    @Override
335    public void writeCharacters(char[] text, int start, int len)
336            throws XMLStreamException {
[1955]337        if (indent > 0) {
338            state = IndentingState.SEEN_DATA;
339        }
[1881]340        xmlwriter.writeCharacters(text, start, len);
341    }
342
[1956]343
[1881]344    @Override
345    public String getPrefix(String uri) throws XMLStreamException {
346        return xmlwriter.getPrefix(uri);
347    }
348
[1956]349
[1881]350    @Override
351    public void setPrefix(String prefix, String uri) throws XMLStreamException {
352        xmlwriter.setPrefix(prefix, uri);
353    }
354
[1956]355
[1881]356    @Override
357    public void setDefaultNamespace(String uri) throws XMLStreamException {
358        xmlwriter.setDefaultNamespace(uri);
359    }
360
[1956]361
[1881]362    @Override
363    public void setNamespaceContext(NamespaceContext context)
364            throws XMLStreamException {
365        xmlwriter.setNamespaceContext(context);
366    }
367
[1956]368
[1881]369    @Override
370    public NamespaceContext getNamespaceContext() {
371        return xmlwriter.getNamespaceContext();
372    }
373
[1956]374
[1881]375    @Override
376    public Object getProperty(String name) throws IllegalArgumentException {
377        return xmlwriter.getProperty(name);
378    }
379
380
381    public Writer getWriter() {
382        return writer;
383    }
384
385
386    public void startRecord() throws XMLStreamException {
387        if (writingRecord) {
388            throw new IllegalStateException("was already writing record");
389        }
390        xmlwriter.flush();
391        /*
392         *  abuse writeCharacters to force writer to close finish
393         *  any pending start or end elements
394         */
395        xmlwriter.writeCharacters("");
396        writingRecord = true;
397    }
398
[1956]399
[1881]400    public void endRecord() throws XMLStreamException {
401        if (!writingRecord) {
402            throw new IllegalStateException("was not writing record");
403        }
404        /*
405         *  abuse writeCharacters to force writer to close finish
406         *  any pending start or end elements
407         */
408        xmlwriter.writeCharacters("");
409        xmlwriter.flush();
410        writingRecord = false;
411    }
412
413
[2199]414    public void writeXCQL(CQLNode query, final boolean searchRetrieveMode)
415            throws XMLStreamException {
[1881]416        /*
417         * HACK: Parsing the XCQL to serialize is wasting resources.
418         * Alternative would be to serialize to XCQL from CQLNode, but
419         * I'm not yet enthusiastic on writing the serializer myself.
420         */
421        try {
422            SAXParser parser = factory.newSAXParser();
423            InputSource input =
[2729]424                    new InputSource(new StringReader(query.toXCQL()));
[1881]425            parser.parse(input, new DefaultHandler() {
426                @Override
427                public void startElement(String uri, String localName,
428                        String qName, Attributes attributes)
[1956]429                                throws SAXException {
[2199]430                    if (!searchRetrieveMode && qName.equals("searchClause")) {
431                        return;
432                    }
[1881]433                    try {
[1955]434                        SRUXMLStreamWriter.this.writeStartElement(qName);
[1881]435                        for (int i = 0; i < attributes.getLength(); i++) {
[1955]436                            SRUXMLStreamWriter.this.writeAttribute(attributes.getQName(i),
[1881]437                                    attributes.getValue(i));
438                        }
439                    } catch (XMLStreamException e) {
440                        throw new SAXException(e);
441                    }
442                }
443
444                @Override
445                public void endElement(String uri, String localName,
446                        String qName) throws SAXException {
[2199]447                    if (!searchRetrieveMode && qName.equals("searchClause")) {
448                        return;
449                    }
[1881]450                    try {
[1955]451                        SRUXMLStreamWriter.this.writeEndElement();
[1881]452                    } catch (XMLStreamException e) {
453                        throw new SAXException(e);
454                    }
455                }
456
457                @Override
458                public void characters(char[] text, int start, int length)
459                        throws SAXException {
460                    try {
461                        boolean isWhitespaceOnly = true;
462                        for (int i = start; i < start + length; i++) {
463                            if (!Character.isWhitespace(text[i])) {
464                                isWhitespaceOnly = false;
465                                break;
466                            }
467                        }
468                        if (!isWhitespaceOnly) {
[1955]469                            SRUXMLStreamWriter.this.writeCharacters(text, start, length);
[1881]470                        }
471                    } catch (XMLStreamException e) {
472                        throw new SAXException(e);
473                    }
474                }
475            });
476        } catch (Exception e) {
477            throw new XMLStreamException("cannot write XCQL", e);
478        }
479    }
480
481
[1955]482    private void onStartElement() throws XMLStreamException {
[6816]483        if (!(writingRecord && (recordEscaping == SRURecordXmlEscaping.STRING))) {
[1955]484            stateStack.push(IndentingState.SEEN_ELEMENT);
485            state = IndentingState.SEEN_NOTHING;
486            if (depth > 0) {
487                xmlwriter.writeCharacters("\n");
488            }
489            doIndent();
490            depth++;
491        }
492    }
493
494
495    private void onEndElement() throws XMLStreamException {
[6816]496        if (!(writingRecord && (recordEscaping == SRURecordXmlEscaping.STRING))) {
[1955]497            depth--;
498            if (state == IndentingState.SEEN_ELEMENT) {
499                xmlwriter.writeCharacters("\n");
500                doIndent();
501            }
502            state = stateStack.pop();
503        }
504    }
505
[1956]506
[1955]507    private void onEmptyElement() throws XMLStreamException {
[6816]508        if (!(writingRecord && (recordEscaping == SRURecordXmlEscaping.STRING))) {
[1955]509            state = IndentingState.SEEN_ELEMENT;
510            if (depth > 0) {
511                xmlwriter.writeCharacters("\n");
512            }
513            doIndent();
514        }
515    }
516
517
518    private void doIndent() throws XMLStreamException {
519        if (depth > 0) {
520            for (int i = 0; i < (depth * indent); i++) {
521                xmlwriter.writeCharacters(" ");
522            }
523        }
524    }
525
526
[1881]527    static {
528        factory = SAXParserFactory.newInstance();
529        factory.setNamespaceAware(false);
530        factory.setValidating(false);
531        factory.setXIncludeAware(false);
532    }
533
534} // class SRUXMLStreamWriter
Note: See TracBrowser for help on using the repository browser.