001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.quercus.lib.xml;
030:
031: import com.caucho.quercus.annotation.Optional;
032: import com.caucho.quercus.env.BooleanValue;
033: import com.caucho.quercus.env.Env;
034: import com.caucho.quercus.env.LongValue;
035: import com.caucho.quercus.env.NullValue;
036: import com.caucho.quercus.env.StringValue;
037: import com.caucho.quercus.env.Value;
038: import com.caucho.util.L10N;
039: import com.caucho.vfs.Path;
040:
041: import javax.xml.stream.*;
042: import java.io.IOException;
043: import java.util.HashMap;
044: import java.util.logging.Level;
045: import java.util.logging.Logger;
046:
047: public class XmlReader {
048: private static final Logger log = Logger.getLogger(XmlReader.class
049: .getName());
050: private static final L10N L = new L10N(XmlReader.class);
051:
052: private int _depth;
053: private int _lastNodeType;
054:
055: private int _currentNodeType;
056:
057: private boolean _hasAttribute;
058:
059: private XMLStreamReader _streamReader;
060:
061: private static final HashMap<Integer, Integer> _constConvertMap = new HashMap<Integer, Integer>();
062:
063: private HashMap<String, Integer> _startElements;
064:
065: public static final int NONE = 0;
066: public static final int ELEMENT = 1;
067: public static final int ATTRIBUTE = 2;
068: public static final int TEXT = 3;
069: public static final int CDATA = 4;
070: public static final int ENTITY_REF = 5;
071: public static final int ENTITY = 6;
072: public static final int PI = 7;
073: public static final int COMMENT = 8;
074: public static final int DOC = 9;
075: public static final int DOC_TYPE = 10;
076: public static final int DOC_FRAGMENT = 11;
077: public static final int NOTATION = 12;
078: public static final int WHITESPACE = 13;
079: public static final int SIGNIFICANT_WHITESPACE = 14;
080: public static final int END_ELEMENT = 15;
081: public static final int END_ENTITY = 16;
082: public static final int XML_DECLARATION = 17;
083:
084: public static final int LOADDTD = 1;
085: public static final int DEFAULTATTRS = 2;
086: public static final int VALIDATE = 3;
087: public static final int SUBST_ENTITIES = 4;
088:
089: /**
090: * Default constructor.
091: *
092: * XXX: Not completely sure what the passed in string(s) does.
093: *
094: * @param string not used
095: */
096: public XmlReader(@Optional
097: String[] string) {
098: _depth = 0;
099: _lastNodeType = -1;
100: _currentNodeType = XMLStreamConstants.START_DOCUMENT;
101:
102: _streamReader = null;
103:
104: _startElements = new HashMap<String, Integer>();
105:
106: _hasAttribute = false;
107: }
108:
109: /**
110: * Determines if the stream has been opened and produces a warning if not.
111: *
112: * @param env
113: * @param operation name of the operation being performed (i.e. read, etc.)
114: * @return true if the stream is open, false otherwise
115: */
116: private boolean streamIsOpen(Env env, String operation) {
117: if (!streamIsOpen()) {
118: env.warning(L.l("Load Data before trying to " + operation));
119:
120: return false;
121: }
122:
123: return true;
124: }
125:
126: /**
127: * Determines if the stream has been opened.
128: *
129: * @return true if the stream is open, false otherwise
130: */
131: private boolean streamIsOpen() {
132: return _streamReader != null;
133: }
134:
135: /**
136: * Returns the number of attributes of the current element.
137: *
138: * @return the count if it exists, otherwise null
139: */
140: public Value getAttributeCount() {
141: if (!streamIsOpen())
142: return NullValue.NULL;
143:
144: try {
145: if (_currentNodeType == XMLStreamConstants.CHARACTERS)
146: return LongValue.create(0);
147:
148: return LongValue.create(_streamReader.getAttributeCount());
149: } catch (IllegalStateException ex) {
150: log.log(Level.WARNING, ex.toString(), ex);
151:
152: return NullValue.NULL;
153: }
154: }
155:
156: /**
157: * Returns the base uniform resource locator of the current element.
158: *
159: * @return the URI, otherwise null
160: */
161: public Value getBaseURI() {
162: if (!streamIsOpen())
163: return NullValue.NULL;
164:
165: return StringValue.create(_streamReader.getLocation()
166: .getSystemId());
167: }
168:
169: /**
170: * Returns the depth of the current element.
171: *
172: * @return the depth if it exists, otherwise null
173: */
174: public Value getDepth() {
175: if (!streamIsOpen())
176: return NullValue.NULL;
177:
178: return LongValue.create(_depth);
179: }
180:
181: /**
182: * Determines whether this element has attributes.
183: *
184: * @return true if this element has attributes, false if not, otherwise null
185: */
186: public Value getHasAttributes() {
187: if (!streamIsOpen())
188: return NullValue.NULL;
189:
190: try {
191: if (_currentNodeType == XMLStreamConstants.CHARACTERS)
192: return BooleanValue.FALSE;
193:
194: return BooleanValue.create(_hasAttribute
195: || _streamReader.getAttributeCount() > 0);
196: } catch (IllegalStateException ex) {
197: log.log(Level.WARNING, ex.toString(), ex);
198:
199: return NullValue.NULL;
200: }
201: }
202:
203: /**
204: * Determines whether this element has content.
205: *
206: * @return true if this element has content, false if not, otherwise null
207: */
208: public Value getHasValue() {
209: if (!streamIsOpen())
210: return NullValue.NULL;
211:
212: return BooleanValue.create(_streamReader.hasText());
213: }
214:
215: /**
216: * Determines whether this element is default.
217: *
218: * @return true if this element is default, false if not, otherwise null
219: */
220: public Value getIsDefault() {
221: if (!streamIsOpen())
222: return NullValue.NULL;
223:
224: // XXX: StreamReaderImpl.isAttributeSpecified() only checks for
225: // attribute existence. This should be tested against the atttribute list
226: // but couldn't find anything like that in StreamReader.
227: return BooleanValue.FALSE;
228: }
229:
230: /**
231: * Determines whether this element is empty.
232: *
233: * @return true if this element is empty, false if not, otherwise null
234: */
235: public Value getIsEmptyElement() {
236: if (!streamIsOpen())
237: return NullValue.NULL;
238:
239: // The only case I found for isEmptyElement was for something
240: // like <element/>. Even something like <element></element> was
241: // not considered empty.
242: if (_currentNodeType == XMLStreamConstants.START_ELEMENT
243: && _streamReader.isEndElement())
244: return BooleanValue.TRUE;
245:
246: return BooleanValue.FALSE;
247: }
248:
249: /**
250: * Determines whether this element has attributes.
251: *
252: * @return true if this element has attributes, false if not, otherwise null
253: */
254: public Value getLocalName() {
255: if (!streamIsOpen())
256: return NullValue.NULL;
257:
258: String name = "";
259:
260: if (_currentNodeType == XMLStreamConstants.CHARACTERS)
261: name = "#text";
262: else if (_currentNodeType == XMLStreamConstants.COMMENT)
263: name = "#comment";
264: else
265: name = _streamReader.getLocalName();
266:
267: return StringValue.create(name);
268: }
269:
270: /**
271: * Returns the name of the current element.
272: *
273: * @return the name, otherwise null
274: */
275: public Value getName(Env env) {
276: if (!streamIsOpen())
277: return NullValue.NULL;
278:
279: try {
280: String name = "";
281:
282: // XXX: Next line should be "String prefix = _streamReader.getPrefix();"
283: // but there was a NullPointerException for XMLStreamReaderImpl._name.
284:
285: // php/4618
286: String prefix = _streamReader.getPrefix();
287:
288: if (_currentNodeType == XMLStreamConstants.CHARACTERS)
289: name = "#text";
290: else if (_currentNodeType == XMLStreamConstants.COMMENT)
291: name = "#comment";
292: else {
293: if (prefix == null)
294: name = _streamReader.getName().toString();
295: else
296: name = prefix + ":"
297: + _streamReader.getLocalName().toString();
298: }
299:
300: return StringValue.create(name);
301: } catch (IllegalStateException ex) {
302: log.log(Level.WARNING, ex.toString(), ex);
303:
304: return NullValue.NULL;
305: }
306: }
307:
308: /**
309: * Returns the namespace uniform resource locator of the current element.
310: *
311: * @return the namespace URI, otherwise null
312: */
313: public Value getNamespaceURI() {
314: if (!streamIsOpen())
315: return NullValue.NULL;
316:
317: return StringValue.create(_streamReader.getNamespaceURI());
318: }
319:
320: /**
321: * Returns the node type of the current element.
322: *
323: * @return the node type, otherwise null
324: */
325: public Value getNodeType() {
326: if (!streamIsOpen())
327: return NullValue.NULL;
328:
329: /*
330: Integer convertedInteger = _constConvertMap.get(_nextType);
331:
332: int convertedInt = convertedInteger.intValue();
333:
334: return LongValue.create(convertedInt);*/
335:
336: int convertedInt = SIGNIFICANT_WHITESPACE;
337:
338: if (!_streamReader.isWhiteSpace()) {
339: Integer convertedInteger = _constConvertMap
340: .get(_streamReader.getEventType());
341:
342: convertedInt = convertedInteger.intValue();
343: }
344:
345: return LongValue.create(convertedInt);
346: }
347:
348: /**
349: * Returns the prefix of the current element.
350: *
351: * @return the prefix, otherwise null
352: */
353: public Value getPrefix() {
354: if (!streamIsOpen())
355: return NullValue.NULL;
356:
357: return StringValue.create(_streamReader.getPrefix());
358: }
359:
360: /**
361: * Returns the value of the current element.
362: *
363: * @return the value, otherwise null
364: */
365: public Value getValue() {
366: if (!streamIsOpen())
367: return NullValue.NULL;
368:
369: if (_currentNodeType != XMLStreamConstants.END_ELEMENT)
370: return StringValue.create(_streamReader.getText());
371:
372: return StringValue.create(null);
373: }
374:
375: /**
376: * Returns the node type of the current element.
377: *
378: * @return the node type, otherwise null
379: */
380: public Value getXmlLang() {
381: if (!streamIsOpen())
382: return NullValue.NULL;
383:
384: // XXX: Defaulted for now.
385: return StringValue.create("");
386: }
387:
388: /**
389: * Closes the reader.
390: *
391: * @return true if success, false otherwise
392: */
393: public BooleanValue close() {
394: if (!streamIsOpen())
395: return BooleanValue.TRUE;
396:
397: try {
398: _streamReader.close();
399: } catch (XMLStreamException ex) {
400: log.log(Level.WARNING, ex.toString(), ex);
401:
402: return BooleanValue.FALSE;
403: }
404:
405: return BooleanValue.TRUE;
406: }
407:
408: /**
409: *
410: * @return
411: */
412: public Value expand() {
413: throw new UnsupportedOperationException(getClass().getName());
414: }
415:
416: /**
417: *
418: * @param name
419: * @return
420: */
421: public StringValue getAttribute(String name) {
422: throw new UnsupportedOperationException(getClass().getName());
423: }
424:
425: /**
426: *
427: * @param index
428: * @return
429: */
430: public StringValue getAttributeNo(int index) {
431: throw new UnsupportedOperationException(getClass().getName());
432: }
433:
434: /**
435: *
436: * @param localName
437: * @param namespaceURI
438: * @return
439: */
440: public StringValue getAttributeNS(String localName,
441: String namespaceURI) {
442: throw new UnsupportedOperationException(getClass().getName());
443: }
444:
445: /**
446: *
447: * @param property
448: * @return
449: */
450: public BooleanValue getParserProperty(int property) {
451: throw new UnsupportedOperationException(getClass().getName());
452: }
453:
454: /**
455: *
456: * @return
457: */
458: public BooleanValue isValid() {
459: throw new UnsupportedOperationException(getClass().getName());
460: }
461:
462: /**
463: *
464: * @param prefix
465: * @return
466: */
467: public BooleanValue lookupNamespace(String prefix) {
468: throw new UnsupportedOperationException(getClass().getName());
469: }
470:
471: /**
472: *
473: * @param name
474: * @return
475: */
476: public BooleanValue moveToAttribute(String name) {
477: throw new UnsupportedOperationException(getClass().getName());
478: }
479:
480: /**
481: *
482: * @param index
483: * @return
484: */
485: public BooleanValue moveToAttributeNo(int index) {
486: throw new UnsupportedOperationException(getClass().getName());
487: }
488:
489: /**
490: *
491: * @param localName
492: * @param namespaceURI
493: * @return
494: */
495: public BooleanValue moveToAttributeNs(String localName,
496: String namespaceURI) {
497: throw new UnsupportedOperationException(getClass().getName());
498: }
499:
500: /**
501: *
502: * @return
503: */
504: public BooleanValue moveToElement() {
505: throw new UnsupportedOperationException(getClass().getName());
506: }
507:
508: /**
509: *
510: * @return
511: */
512: public BooleanValue moveToFirstAttribute() {
513: throw new UnsupportedOperationException(getClass().getName());
514: }
515:
516: /**
517: *
518: * @return
519: */
520: public BooleanValue moveToNextAttribute() {
521: throw new UnsupportedOperationException(getClass().getName());
522: }
523:
524: /**
525: *
526: * @param localname
527: * @return
528: */
529: public BooleanValue next(@Optional
530: String localname) {
531: throw new UnsupportedOperationException(getClass().getName());
532: }
533:
534: /**
535: * Opens a stream using the uniform resource locator.
536: *
537: * @param uri uniform resource locator to open
538: * @return true if success, false otherwise
539: */
540: public BooleanValue open(Env env, Path path) {
541: try {
542: XMLInputFactory factory = XMLInputFactory.newInstance();
543:
544: _streamReader = factory.createXMLStreamReader(path
545: .getNativePath(), path.openRead());
546: } catch (XMLStreamException ex) {
547: log.log(Level.WARNING, ex.toString(), ex);
548:
549: env
550: .warning(L
551: .l(
552: "XML input file '{0}' cannot be opened for reading.",
553: path));
554:
555: return BooleanValue.FALSE;
556: } catch (IOException ex) {
557: log.log(Level.WARNING, ex.toString(), ex);
558:
559: env.warning(L.l("Unable to open source data"));
560:
561: return BooleanValue.FALSE;
562: }
563:
564: return BooleanValue.TRUE;
565: }
566:
567: /**
568: * Updates the depth.
569: *
570: */
571: private void updateDepth(Env env) {
572: if (_lastNodeType == XMLStreamConstants.START_ELEMENT
573: && !_streamReader.isEndElement())
574: _depth++;
575: else if ((_lastNodeType == XMLStreamConstants.CHARACTERS || _lastNodeType == XMLStreamConstants.COMMENT)
576: && _currentNodeType == XMLStreamConstants.END_ELEMENT)
577: _depth--;
578: }
579:
580: /**
581: * Maintains the _hasAttribute variable.
582: *
583: */
584: private void updateAttribute(Env env) {
585: _hasAttribute = false;
586:
587: String key = getName(env).toString() + _depth;
588:
589: if (_currentNodeType == XMLStreamConstants.START_ELEMENT
590: && _streamReader.getAttributeCount() > 0) {
591: _startElements.put(key, _depth);
592:
593: _hasAttribute = true;
594: }
595:
596: if (_currentNodeType == XMLStreamConstants.END_ELEMENT
597: && _startElements.containsKey(key)) {
598: _hasAttribute = true;
599:
600: _startElements.remove(key);
601: }
602: }
603:
604: /**
605: * Moves the cursor to the next node.
606: *
607: * @return true if success, false otherwise
608: */
609: public BooleanValue read(Env env) {
610: if (!streamIsOpen(env, "read"))
611: return BooleanValue.FALSE;
612:
613: try {
614: if (!_streamReader.hasNext())
615: return BooleanValue.FALSE;
616:
617: _lastNodeType = _currentNodeType;
618:
619: Value isEmptyElement = getIsEmptyElement();
620:
621: _currentNodeType = _streamReader.next();
622:
623: // php/4618
624: if (isEmptyElement.toBoolean())
625: return read(env);
626:
627: if (_currentNodeType == XMLStreamConstants.SPACE)
628: return read(env);
629:
630: if (_currentNodeType == XMLStreamConstants.END_DOCUMENT)
631: return BooleanValue.FALSE;
632:
633: updateDepth(env);
634:
635: updateAttribute(env);
636:
637: } catch (XMLStreamException ex) {
638: log.log(Level.WARNING, ex.toString(), ex);
639:
640: env.warning(L.l("Unable to read :" + ex.toString()));
641:
642: return BooleanValue.FALSE;
643: }
644:
645: return BooleanValue.TRUE;
646: }
647:
648: public LongValue getNextType() {
649: return LongValue.create(_currentNodeType);
650: }
651:
652: /**
653: *
654: * @param property
655: * @param value
656: * @return
657: */
658: public BooleanValue setParserProperty(int property, boolean value) {
659: throw new UnsupportedOperationException(getClass().getName());
660: }
661:
662: /**
663: *
664: * @param filename
665: * @return
666: */
667: public BooleanValue setRelaxNGSchema(String filename) {
668: throw new UnsupportedOperationException(getClass().getName());
669: }
670:
671: /**
672: *
673: * @param source
674: * @return
675: */
676: public BooleanValue setRelaxNGSchemaSource(String source) {
677: throw new UnsupportedOperationException(getClass().getName());
678: }
679:
680: /**
681: *
682: * @param source
683: * @return
684: */
685: public BooleanValue XML(String source) {
686: throw new UnsupportedOperationException(getClass().getName());
687: }
688:
689: static {
690: _constConvertMap.put(XMLStreamConstants.ATTRIBUTE, ATTRIBUTE);
691: _constConvertMap.put(XMLStreamConstants.CDATA, CDATA);
692: _constConvertMap.put(XMLStreamConstants.CHARACTERS, TEXT);
693: _constConvertMap.put(XMLStreamConstants.COMMENT, COMMENT);
694: _constConvertMap.put(XMLStreamConstants.END_ELEMENT,
695: END_ELEMENT);
696: /*
697: _constConvertMap.put(XMLStreamConstants.END_ENTITY,
698: END_ENTITY);
699: */
700: // XXX: XMLStreamConstants.ENTITY_DECLARATION is 17 in the BAE docs
701: // but is 15 in the Resin implementation.
702: _constConvertMap.put(XMLStreamConstants.ENTITY_DECLARATION,
703: ENTITY); // ENTITY used twice
704: _constConvertMap.put(XMLStreamConstants.ENTITY_REFERENCE,
705: ENTITY_REF);
706: _constConvertMap.put(XMLStreamConstants.NOTATION_DECLARATION,
707: NOTATION);
708: _constConvertMap.put(XMLStreamConstants.PROCESSING_INSTRUCTION,
709: PI);
710: _constConvertMap.put(XMLStreamConstants.SPACE, WHITESPACE);
711: _constConvertMap.put(XMLStreamConstants.START_ELEMENT, ELEMENT);
712: /*
713: _constConvertMap.put(XMLStreamConstants.START_ENTITY,
714: ENTITY);
715: */
716: // Following constants did not match
717: _constConvertMap.put(XMLStreamConstants.DTD, NONE);
718: _constConvertMap.put(XMLStreamConstants.END_DOCUMENT, NONE);
719: _constConvertMap.put(XMLStreamConstants.NAMESPACE, NONE);
720: _constConvertMap.put(XMLStreamConstants.START_DOCUMENT, NONE);
721: _constConvertMap.put(0, NONE); // Pre-Read
722: _constConvertMap.put(-1, NONE);
723: _constConvertMap.put(-1, DOC);
724: _constConvertMap.put(-1, DOC_TYPE);
725: _constConvertMap.put(-1, DOC_FRAGMENT);
726: _constConvertMap.put(-1, DOC_TYPE);
727: _constConvertMap.put(-1, XML_DECLARATION);
728: }
729: }
|