1 package com.fasterxml.jackson.dataformat.xml;
2
3 import java.io.*;
4
5 import javax.xml.stream.*;
6
7 import org.codehaus.stax2.io.Stax2ByteArraySource;
8 import org.codehaus.stax2.io.Stax2CharArraySource;
9
10 import com.fasterxml.jackson.core.*;
11 import com.fasterxml.jackson.core.format.InputAccessor;
12 import com.fasterxml.jackson.core.format.MatchStrength;
13 import com.fasterxml.jackson.core.io.IOContext;
14 import com.fasterxml.jackson.core.util.VersionUtil;
15
16 import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser;
17 import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
18 import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
19
20 /**
21 * Factory used for constructing {@link FromXmlParser} and {@link ToXmlGenerator}
22 * instances.
23 *<p>
24 * Implements {@link JsonFactory} since interface for constructing XML backed
25 * parsers and generators is quite similar to dealing with JSON.
26
27 * @author Tatu Saloranta (tatu.saloranta@iki.fi)
28 */

29 public class XmlFactory extends JsonFactory
30 {
31     private static final long serialVersionUID = 1; // 2.6
32
33     /**
34      * Name used to identify XML format
35      * (and returned by {@link #getFormatName()}
36      */

37     public final static String FORMAT_NAME_XML = "XML";
38
39     /**
40      * Bitfield (set of flags) of all parser features that are enabled
41      * by default.
42      */

43     final static int DEFAULT_XML_PARSER_FEATURE_FLAGS = FromXmlParser.Feature.collectDefaults();
44
45     /**
46      * Bitfield (set of flags) of all generator features that are enabled
47      * by default.
48      */

49     final static int DEFAULT_XML_GENERATOR_FEATURE_FLAGS = ToXmlGenerator.Feature.collectDefaults();
50
51     /*
52     /**********************************************************
53     /* Configuration
54     /**********************************************************
55      */

56
57     protected int _xmlParserFeatures;
58
59     protected int _xmlGeneratorFeatures;
60
61     // non-final for setters (why are they needed again?)
62     protected transient XMLInputFactory _xmlInputFactory;
63
64     protected transient XMLOutputFactory _xmlOutputFactory;
65
66     protected String _cfgNameForTextElement;
67     
68     /*
69     /**********************************************************
70     /* Factory construction, configuration
71     /**********************************************************
72      */

73
74     /**
75      * Default constructor used to create factory instances.
76      * Creation of a factory instance is a light-weight operation,
77      * but it is still a good idea to reuse limited number of
78      * factory instances (and quite often just a single instance):
79      * factories are used as context for storing some reused
80      * processing objects (such as symbol tables parsers use)
81      * and this reuse only works within context of a single
82      * factory instance.
83      */

84     public XmlFactory() { this(nullnullnull); }
85
86     public XmlFactory(ObjectCodec oc) {
87         this(oc, nullnull);
88     }
89
90     public XmlFactory(XMLInputFactory xmlIn) {
91         this(null, xmlIn, null);
92     }
93     
94     public XmlFactory(XMLInputFactory xmlIn, XMLOutputFactory xmlOut) {
95         this(null, xmlIn, xmlOut);
96     }
97     
98     public XmlFactory(ObjectCodec oc, XMLInputFactory xmlIn, XMLOutputFactory xmlOut)
99     {
100         this(oc, DEFAULT_XML_PARSER_FEATURE_FLAGS, DEFAULT_XML_GENERATOR_FEATURE_FLAGS,
101                 xmlIn, xmlOut, null);
102     }
103
104     protected XmlFactory(ObjectCodec oc, int xpFeatures, int xgFeatures,
105             XMLInputFactory xmlIn, XMLOutputFactory xmlOut,
106             String nameForTextElem)
107     {
108         super(oc);
109         _xmlParserFeatures = xpFeatures;
110         _xmlGeneratorFeatures = xgFeatures;
111         _cfgNameForTextElement = nameForTextElem;
112         if (xmlIn == null) {
113             xmlIn = XMLInputFactory.newInstance();
114             // as per [dataformat-xml#190], disable external entity expansion by default
115             xmlIn.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
116             // and ditto wrt [dataformat-xml#211], SUPPORT_DTD
117             xmlIn.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
118         }
119         if (xmlOut == null) {
120             xmlOut = XMLOutputFactory.newInstance();
121         }
122         _initFactories(xmlIn, xmlOut);
123         _xmlInputFactory = xmlIn;
124         _xmlOutputFactory = xmlOut;
125     }
126
127     /**
128      * @since 2.2.1
129      */

130     protected XmlFactory(XmlFactory src, ObjectCodec oc)
131     {
132         super(src, oc);
133         _xmlParserFeatures = src._xmlParserFeatures;
134         _xmlGeneratorFeatures = src._xmlGeneratorFeatures;
135         _cfgNameForTextElement = src._cfgNameForTextElement;
136         _xmlInputFactory = src._xmlInputFactory;
137         _xmlOutputFactory = src._xmlOutputFactory;
138     }
139
140     /**
141      * Constructors used by {@link JsonFactoryBuilder} for instantiation.
142      *
143      * @since 2.9
144      */

145     protected XmlFactory(XmlFactoryBuilder b)
146     {
147         super(b, false);
148         _xmlParserFeatures = b.formatParserFeaturesMask();
149         _xmlGeneratorFeatures = b.formatGeneratorFeaturesMask();
150         _cfgNameForTextElement = b.nameForTextElement();
151         _xmlInputFactory = b.xmlInputFactory();
152         _xmlOutputFactory = b.xmlOutputFactory();
153         _initFactories(_xmlInputFactory, _xmlOutputFactory);
154     }
155
156     public static XmlFactoryBuilder builder() {
157         return new XmlFactoryBuilder();
158     }
159
160     @Override
161     public XmlFactoryBuilder rebuild() {
162         return new XmlFactoryBuilder(this);
163     }
164
165     protected void _initFactories(XMLInputFactory xmlIn, XMLOutputFactory xmlOut)
166     {
167         // [dataformat-xml#326]: Better ensure namespaces get built properly, so:
168         xmlOut.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
169         // and for parser, force coalescing as well (much simpler to use)
170         xmlIn.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
171     }
172
173     /**
174      * Note: compared to base implementation by {@link JsonFactory},
175      * here the copy will actually share underlying XML input and
176      * output factories, as there is no way to make copies of those.
177      */

178     @Override
179     public XmlFactory copy() {
180         _checkInvalidCopy(XmlFactory.class);
181         return new XmlFactory(thisnull);
182     }
183
184     @Override
185     public Version version() {
186         return PackageVersion.VERSION;
187     }
188
189     /*
190     /**********************************************************
191     /* Serializable overrides
192     /**********************************************************
193      */

194
195     /**
196      * Hiding place for JDK-serialization unthawed factories...
197      */

198     protected transient String _jdkXmlInFactory;
199
200     /**
201      * Hiding place for JDK-serialization unthawed factories...
202      */

203     protected transient String _jdkXmlOutFactory;
204
205     /**
206      * Method that we need to override to actually make restoration go
207      * through constructors etc.
208      */

209     @Override // since JsonFactory already implemented it
210     protected Object readResolve() {
211         if (_jdkXmlInFactory == null) {
212             throw new IllegalStateException("No XMLInputFactory class name read during JDK deserialization");
213         }
214         if (_jdkXmlOutFactory == null) {
215             throw new IllegalStateException("No XMLOutputFactory class name read during JDK deserialization");
216         }
217         final XMLInputFactory inf;
218         XMLOutputFactory outf;
219         try {
220             inf = (XMLInputFactory) Class.forName(_jdkXmlInFactory).getDeclaredConstructor().newInstance();
221             outf = (XMLOutputFactory) Class.forName(_jdkXmlOutFactory).getDeclaredConstructor().newInstance();
222         } catch (Exception e) {
223             throw new IllegalArgumentException(e);
224         }
225     return new XmlFactory(_objectCodec, _xmlParserFeatures, _xmlGeneratorFeatures,
226                   inf, outf, _cfgNameForTextElement);
227     }
228
229     /**
230      * In addition to default serialization, which mostly works, need
231      * to handle case of XML factories, hence override.
232      */

233     private void readObject(ObjectInputStream in)
234             throws IOException, ClassNotFoundException
235     {
236         in.defaultReadObject();
237         _jdkXmlInFactory = in.readUTF();
238         _jdkXmlOutFactory = in.readUTF();
239     }
240
241     /**
242      * In addition to default serialization, which mostly works, need
243      * to handle case of XML factories, hence override.
244      */

245     private void writeObject(ObjectOutputStream out) throws IOException {
246         out.defaultWriteObject();
247         out.writeUTF(_xmlInputFactory.getClass().getName());
248         out.writeUTF(_xmlOutputFactory.getClass().getName());
249     }
250     
251     /*
252     /**********************************************************
253     /* Configuration, XML-specific
254     /**********************************************************
255      */

256     
257     /**
258      * @since 2.1
259      */

260     public void setXMLTextElementName(String name) {
261         _cfgNameForTextElement = name;
262     }
263
264     /**
265      * @since 2.2
266      */

267     public String getXMLTextElementName() {
268         return _cfgNameForTextElement;
269     }
270     
271     /*
272     /**********************************************************
273     /* Configuration, XML, parser setting
274     /**********************************************************
275      */

276
277     /**
278      * Method for enabling or disabling specified XML parser feature.
279      */

280     public final XmlFactory configure(FromXmlParser.Feature f, boolean state)
281     {
282         if (state) {
283             enable(f);
284         } else {
285             disable(f);
286         }
287         return this;
288     }
289
290     /**
291      * Method for enabling specified XML parser feature.
292      */

293     public XmlFactory enable(FromXmlParser.Feature f) {
294         _xmlParserFeatures |= f.getMask();
295         return this;
296     }
297
298     /**
299      * Method for disabling specified XML parser feature.
300      */

301     public XmlFactory disable(FromXmlParser.Feature f) {
302         _xmlParserFeatures &= ~f.getMask();
303         return this;
304     }
305
306     /**
307      * Checked whether specified XML parser feature is enabled.
308      */

309     public final boolean isEnabled(FromXmlParser.Feature f) {
310         return (_xmlParserFeatures & f.getMask()) != 0;
311     }
312
313     @Override
314     public int getFormatParserFeatures() {
315         return _xmlParserFeatures;
316     }
317
318     @Override
319     public int getFormatGeneratorFeatures() {
320         return _xmlGeneratorFeatures;
321     }
322
323     /*
324     /******************************************************
325     /* Configuration, XML, generator settings
326     /******************************************************
327      */

328
329     /**
330      * Method for enabling or disabling specified XML generator feature.
331      */

332     public final XmlFactory configure(ToXmlGenerator.Feature f, boolean state) {
333         if (state) {
334             enable(f);
335         } else {
336             disable(f);
337         }
338         return this;
339     }
340
341     /**
342      * Method for enabling specified XML generator feature.
343      */

344     public XmlFactory enable(ToXmlGenerator.Feature f) {
345         _xmlGeneratorFeatures |= f.getMask();
346         return this;
347     }
348
349     /**
350      * Method for disabling specified XML generator feature.
351      */

352     public XmlFactory disable(ToXmlGenerator.Feature f) {
353         _xmlGeneratorFeatures &= ~f.getMask();
354         return this;
355     }
356
357     /**
358      * Check whether specified XML generator feature is enabled.
359      */

360     public final boolean isEnabled(ToXmlGenerator.Feature f) {
361         return (_xmlGeneratorFeatures & f.getMask()) != 0;
362     }
363
364     /*
365     /**********************************************************
366     /* Additional configuration
367     /**********************************************************
368      */

369
370     /** @since 2.4 */
371     public XMLInputFactory getXMLInputFactory() {
372         return _xmlInputFactory;
373     }
374
375     public void setXMLInputFactory(XMLInputFactory f) {
376         _xmlInputFactory = f;
377     }
378
379     /** @since 2.4 */
380     public XMLOutputFactory getXMLOutputFactory() {
381         return _xmlOutputFactory;
382     }
383     
384     public void setXMLOutputFactory(XMLOutputFactory f) {
385         _xmlOutputFactory = f;
386     }
387
388     /*
389     /**********************************************************
390     /* Format detection functionality
391     /**********************************************************
392      */

393
394     /**
395      * Method that returns short textual id identifying format
396      * this factory supports.
397      *<p>
398      * Note: sub-classes should override this method; default
399      * implementation will return null for all sub-classes
400      */

401     @Override
402     public String getFormatName() {
403         return FORMAT_NAME_XML;
404     }
405
406     @Override
407     public MatchStrength hasFormat(InputAccessor acc) throws IOException {
408         return hasXMLFormat(acc);
409     }
410
411     /**
412      * XML format does require support from custom {@link ObjectCodec}
413      * (that is, {@link XmlMapper}), so need to return true here.
414      * 
415      * @return True since XML format does require support from codec
416      */

417     @Override
418     public boolean requiresCustomCodec() { return true; }
419
420     /*
421     /**********************************************************
422     /* Capability introspection
423     /**********************************************************
424      */

425
426     /**
427      * As of 2.4, we do have actual capability for passing char arrays
428      * efficiently, but unfortunately
429      * have no working mechanism for recycling buffers. So we have to 
430      * admit that can not make efficient use.
431      */

432     @Override
433     public boolean canUseCharArrays() { return false; }
434
435     @Override // since 2.6
436     public Class<FromXmlParser.Feature> getFormatReadFeatureType() {
437         return FromXmlParser.Feature.class;
438     }
439
440     @Override // since 2.6
441     public Class<ToXmlGenerator.Feature> getFormatWriteFeatureType() {
442         return ToXmlGenerator.Feature.class;
443     }
444
445     /*
446     /**********************************************************
447     /* Overrides of public methods: parsing
448     /**********************************************************
449      */

450
451     /**
452      * Overridden just to prevent trying to optimize access via char array;
453      * while nice idea, problem is that we don't have proper hooks to ensure
454      * that temporary buffer gets recycled; so let's just use StringReader.
455      */

456     @SuppressWarnings("resource")
457     @Override
458     public JsonParser createParser(String content) throws IOException {
459         Reader r = new StringReader(content);
460         IOContext ctxt = _createContext(r, true);
461         if (_inputDecorator != null) {
462             r = _inputDecorator.decorate(ctxt, r);
463         }
464         return _createParser(r, ctxt);
465     }
466     
467     /*
468     /**********************************************************
469     /* Overrides of public methods: generation
470     /**********************************************************
471      */

472
473     @Override
474     public ToXmlGenerator createGenerator(OutputStream out) throws IOException {
475         return createGenerator(out, JsonEncoding.UTF8);
476     }
477     
478     @Override
479     public ToXmlGenerator createGenerator(OutputStream out, JsonEncoding enc) throws IOException
480     {
481         // false -> we won't manage the stream unless explicitly directed to
482         final IOContext ctxt = _createContext(out, false);
483         ctxt.setEncoding(enc);
484         return new ToXmlGenerator(ctxt,
485                 _generatorFeatures, _xmlGeneratorFeatures,
486                 _objectCodec, _createXmlWriter(ctxt, out));
487     }
488     
489     @Override
490     public ToXmlGenerator createGenerator(Writer out) throws IOException
491     {
492         final IOContext ctxt = _createContext(out, false);
493         return new ToXmlGenerator(ctxt,
494                 _generatorFeatures, _xmlGeneratorFeatures,
495                 _objectCodec, _createXmlWriter(ctxt, out));
496     }
497
498     @SuppressWarnings("resource")
499     @Override
500     public ToXmlGenerator createGenerator(File f, JsonEncoding enc) throws IOException
501     {
502         OutputStream out = new FileOutputStream(f);
503         // true -> yes, we have to manage the stream since we created it
504         final IOContext ctxt = _createContext(out, true);
505         ctxt.setEncoding(enc);
506         return new ToXmlGenerator(ctxt, _generatorFeatures, _xmlGeneratorFeatures,
507                 _objectCodec, _createXmlWriter(ctxt, out));
508     }
509
510     /*
511     /**********************************************************
512     /* Extended public API, mostly for XmlMapper
513     /**********************************************************
514      */

515
516     /**
517      * Factory method that wraps given {@link XMLStreamReader}, usually to allow
518      * partial data-binding.
519      * 
520      * @since 2.4
521      */

522     public FromXmlParser createParser(XMLStreamReader sr) throws IOException
523     {
524         // note: should NOT move parser if already pointing to START_ELEMENT
525         if (sr.getEventType() != XMLStreamConstants.START_ELEMENT) {
526             sr = _initializeXmlReader(sr);
527         }
528
529         // false -> not managed
530         FromXmlParser xp = new FromXmlParser(_createContext(sr, false),
531                 _parserFeatures, _xmlParserFeatures, _objectCodec, sr);
532         if (_cfgNameForTextElement != null) {
533             xp.setXMLTextElementName(_cfgNameForTextElement);
534         }
535         return xp;
536     }
537
538     /**
539      * Factory method that wraps given {@link XMLStreamWriter}, usually to allow
540      * incremental serialization to compose large output by serializing a sequence
541      * of individual objects.
542      *
543      * @since 2.4
544      */

545     public ToXmlGenerator createGenerator(XMLStreamWriter sw) throws IOException
546     {
547         sw = _initializeXmlWriter(sw);
548         IOContext ctxt = _createContext(sw, false);
549         return new ToXmlGenerator(ctxt, _generatorFeatures, _xmlGeneratorFeatures,
550                 _objectCodec, sw);
551     }
552
553     /*
554     /**********************************************************
555     /* Internal factory method overrides
556     /**********************************************************
557      */

558
559     @Override
560     protected FromXmlParser _createParser(InputStream in, IOContext ctxt) throws IOException
561     {
562         XMLStreamReader sr;
563         try {
564             sr = _xmlInputFactory.createXMLStreamReader(in);
565         } catch (XMLStreamException e) {
566             return StaxUtil.throwAsParseException(e, null);
567         }
568         sr = _initializeXmlReader(sr);
569         FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
570                 _objectCodec, sr);
571         if (_cfgNameForTextElement != null) {
572             xp.setXMLTextElementName(_cfgNameForTextElement);
573         }
574         return xp;
575     }
576
577     @Override
578     protected FromXmlParser _createParser(Reader r, IOContext ctxt) throws IOException
579     {
580         XMLStreamReader sr;
581         try {
582             sr = _xmlInputFactory.createXMLStreamReader(r);
583         } catch (XMLStreamException e) {
584             return StaxUtil.throwAsParseException(e, null);
585         }
586         sr = _initializeXmlReader(sr);
587         FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
588                 _objectCodec, sr);
589         if (_cfgNameForTextElement != null) {
590             xp.setXMLTextElementName(_cfgNameForTextElement);
591         }
592         return xp;
593     }
594
595     @Override
596     protected FromXmlParser _createParser(char[] data, int offset, int len, IOContext ctxt,
597             boolean recycleBuffer) throws IOException
598     {
599         // !!! TODO: add proper handling of 'recycleBuffer'; currently its handling
600         //    is always same as if 'false' was passed
601         XMLStreamReader sr;
602         try {
603             sr = _xmlInputFactory.createXMLStreamReader(new Stax2CharArraySource(data, offset, len));
604         } catch (XMLStreamException e) {
605             return StaxUtil.throwAsParseException(e, null);
606         }
607         sr = _initializeXmlReader(sr);
608         FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
609                 _objectCodec, sr);
610         if (_cfgNameForTextElement != null) {
611             xp.setXMLTextElementName(_cfgNameForTextElement);
612         }
613         return xp;
614     }
615
616     @Override
617     protected FromXmlParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException
618     {
619         XMLStreamReader sr;
620         try {
621             sr = _xmlInputFactory.createXMLStreamReader(new Stax2ByteArraySource(data, offset, len));
622         } catch (XMLStreamException e) {
623             return StaxUtil.throwAsParseException(e, null);
624         }
625         sr = _initializeXmlReader(sr);
626         FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
627                 _objectCodec, sr);
628         if (_cfgNameForTextElement != null) {
629             xp.setXMLTextElementName(_cfgNameForTextElement);
630         }
631         return xp;
632     }
633
634     @Override
635     protected JsonGenerator _createGenerator(Writer out, IOContext ctxt) throws IOException {
636         // this method should never get called here, so:
637         VersionUtil.throwInternal();
638         return null;
639     }
640
641     /*
642     /**********************************************************************
643     /* Internal factory methods, XML-specific
644     /**********************************************************************
645      */

646
647     protected XMLStreamWriter _createXmlWriter(IOContext ctxt, OutputStream out) throws IOException
648     {
649         XMLStreamWriter sw;
650         try {
651             sw = _xmlOutputFactory.createXMLStreamWriter(_decorate(ctxt, out), "UTF-8");
652         } catch (Exception e) {
653             throw new JsonGenerationException(e.getMessage(), e, null);
654         }
655         return _initializeXmlWriter(sw);
656     }
657
658     protected XMLStreamWriter _createXmlWriter(IOContext ctxt, Writer w) throws IOException
659     {
660         XMLStreamWriter sw;
661         try {
662             sw = _xmlOutputFactory.createXMLStreamWriter(_decorate(ctxt, w));
663         } catch (Exception e) {
664             throw new JsonGenerationException(e.getMessage(), e, null);
665         }
666         return _initializeXmlWriter(sw);
667     }
668
669     protected final XMLStreamWriter _initializeXmlWriter(XMLStreamWriter sw) throws IOException
670     {
671         // And just for Sun Stax parser (JDK default), seems that we better define default namespace
672         // (Woodstox doesn't care) -- otherwise it'll add unnecessary odd declaration
673         try {
674             sw.setDefaultNamespace("");
675         } catch (Exception e) {
676             throw new JsonGenerationException(e.getMessage(), e, null);
677         }
678         return sw;
679     }
680
681     protected final XMLStreamReader _initializeXmlReader(XMLStreamReader sr) throws IOException
682     {
683         try {
684             // for now, nothing to do... except let's find the root element
685             while (sr.next() != XMLStreamConstants.START_ELEMENT) {
686                 ;
687             }
688         // [dataformat-xml#350]: Xerces-backed impl throws non-XMLStreamException so:
689         } catch (Exception e) {
690             throw new JsonParseException(null, e.getMessage(), e);
691         }
692         return sr;
693     }
694
695     /*
696     /**********************************************************************
697     /* Internal methods, format auto-detection
698     /**********************************************************************
699      */

700
701     private final static byte UTF8_BOM_1 = (byte) 0xEF;
702     private final static byte UTF8_BOM_2 = (byte) 0xBB;
703     private final static byte UTF8_BOM_3 = (byte) 0xBF;
704
705     private final static byte BYTE_x = (byte) 'x';
706     private final static byte BYTE_m = (byte) 'm';
707     private final static byte BYTE_l = (byte) 'l';
708     private final static byte BYTE_D = (byte) 'D';
709
710     private final static byte BYTE_LT = (byte) '<';
711     private final static byte BYTE_QMARK = (byte) '?';
712     private final static byte BYTE_EXCL = (byte) '!';
713     private final static byte BYTE_HYPHEN = (byte) '-';
714     
715     /**
716      * Method that tries to figure out if content seems to be in some kind
717      * of XML format.
718      * Note that implementation here is not nearly as robust as what underlying
719      * Stax parser will do; the idea is to first support common encodings,
720      * then expand as needed (for example, it is not all that hard to support
721      * UTF-16; but it is some work and not needed quite yet)
722      */

723     public static MatchStrength hasXMLFormat(InputAccessor acc) throws IOException
724     {
725         /* Basically we just need to find "<!""<?" or "<NAME"... but ideally
726          * we would actually see the XML declaration
727          */

728         if (!acc.hasMoreBytes()) {
729             return MatchStrength.INCONCLUSIVE;
730         }
731         byte b = acc.nextByte();
732         // Very first thing, a UTF-8 BOM? (later improvements: other BOM's, heuristics)
733         if (b == UTF8_BOM_1) { // yes, looks like UTF-8 BOM
734             if (!acc.hasMoreBytes()) {
735                 return MatchStrength.INCONCLUSIVE;
736             }
737             if (acc.nextByte() != UTF8_BOM_2) {
738                 return MatchStrength.NO_MATCH;
739             }
740             if (!acc.hasMoreBytes()) {
741                 return MatchStrength.INCONCLUSIVE;
742             }
743             if (acc.nextByte() != UTF8_BOM_3) {
744                 return MatchStrength.NO_MATCH;
745             }
746             if (!acc.hasMoreBytes()) {
747                 return MatchStrength.INCONCLUSIVE;
748             }
749             b = acc.nextByte();
750         }
751         // otherwise: XML declaration?
752         boolean maybeXmlDecl = (b == BYTE_LT);
753         if (!maybeXmlDecl) {
754             int ch = skipSpace(acc, b);
755             if (ch < 0) {
756                 return MatchStrength.INCONCLUSIVE;
757             }
758             b = (byte) ch;
759             // If we did not get an LT, shouldn't be valid XML (minus encoding issues etc)
760            if (b != BYTE_LT) {
761                 return MatchStrength.NO_MATCH;
762             }
763         }
764         if (!acc.hasMoreBytes()) {
765             return MatchStrength.INCONCLUSIVE;
766         }
767         b = acc.nextByte();
768         // Couple of choices here
769         if (b == BYTE_QMARK) { // <?
770             b = acc.nextByte();
771             if (b == BYTE_x) {
772                 if (maybeXmlDecl) {
773                     if (acc.hasMoreBytes() && acc.nextByte() == BYTE_m) {
774                         if (acc.hasMoreBytes() && acc.nextByte() == BYTE_l) {
775                             return MatchStrength.FULL_MATCH;
776                         }
777                     }
778                 }
779                 // but even with just partial match, we ought to be fine
780                 return MatchStrength.SOLID_MATCH;
781             }
782             // Ok to start with some other char too; just not xml declaration
783             if (validXmlNameStartChar(acc, b)) {
784                 return MatchStrength.SOLID_MATCH;
785             }
786         } else if (b == BYTE_EXCL) {
787             /* must be <!-- comment --> or <!DOCTYPE ...>, since
788              * <![CDATA[ ]]> can NOT come outside of root
789              */

790             if (!acc.hasMoreBytes()) {
791                 return MatchStrength.INCONCLUSIVE;
792             }
793             b = acc.nextByte();
794             if (b == BYTE_HYPHEN) {
795                 if (!acc.hasMoreBytes()) {
796                     return MatchStrength.INCONCLUSIVE;
797                 }
798                 if (acc.nextByte() == BYTE_HYPHEN) {
799                     return MatchStrength.SOLID_MATCH;
800                 }
801             } else if (b == BYTE_D) {
802                 return tryMatch(acc, "OCTYPE", MatchStrength.SOLID_MATCH);
803             }
804         } else {
805             // maybe root element? Just needs to match first char.
806             if (validXmlNameStartChar(acc, b)) {
807                 return MatchStrength.SOLID_MATCH;
808             }
809         }
810         return MatchStrength.NO_MATCH;
811     }
812
813     private final static boolean validXmlNameStartChar(InputAccessor acc, byte b)
814         throws IOException
815     {
816         /* Can make it actual real XML check in future; for now we do just crude
817          * check for ASCII range
818          */

819         int ch = (int) b & 0xFF;
820         if (ch >= 'A') { // in theory, colon could be; in practice it should never be valid (wrt namespace)
821             // This is where we'd check for multi-byte UTF-8 chars (or whatever encoding is in use)...
822             return true;
823         }
824         return false;
825     }
826     
827     private final static MatchStrength tryMatch(InputAccessor acc, String matchStr, MatchStrength fullMatchStrength)
828         throws IOException
829     {
830         for (int i = 0, len = matchStr.length(); i < len; ++i) {
831             if (!acc.hasMoreBytes()) {
832                 return MatchStrength.INCONCLUSIVE;
833             }
834             if (acc.nextByte() != matchStr.charAt(i)) {
835                 return MatchStrength.NO_MATCH;
836             }
837         }
838         return fullMatchStrength;
839     }
840     
841     private final static int skipSpace(InputAccessor acc, byte b) throws IOException
842     {
843         while (true) {
844             int ch = (int) b & 0xFF;
845             if (!(ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t')) {
846                 return ch;
847             }
848             if (!acc.hasMoreBytes()) {
849                 return -1;
850             }
851             b = acc.nextByte();
852             ch = (int) b & 0xFF;
853         }
854     }
855
856     /*
857     /**********************************************************
858     /* Decorators, output
859     /**********************************************************
860      */

861
862     protected OutputStream _decorate(IOContext ioCtxt, OutputStream out) throws IOException
863     {
864         if (_outputDecorator != null) {
865             OutputStream out2 = _outputDecorator.decorate(ioCtxt, out);
866             if (out2 != null) {
867                 return out2;
868             }
869         }
870         return out;
871     }
872
873     protected Writer _decorate(IOContext ioCtxt, Writer out) throws IOException
874     {
875         if (_outputDecorator != null) {
876             Writer out2 = _outputDecorator.decorate(ioCtxt, out);
877             if (out2 != null) {
878                 return out2;
879             }
880         }
881         return out;
882     }
883 }
884