001: package com.sun.xml.xsom.impl.parser;
002:
003: import com.sun.xml.xsom.XSDeclaration;
004: import com.sun.xml.xsom.XmlString;
005: import com.sun.xml.xsom.XSSimpleType;
006: import com.sun.xml.xsom.impl.ForeignAttributesImpl;
007: import com.sun.xml.xsom.impl.SchemaImpl;
008: import com.sun.xml.xsom.impl.UName;
009: import com.sun.xml.xsom.impl.Const;
010: import com.sun.xml.xsom.impl.parser.state.NGCCRuntime;
011: import com.sun.xml.xsom.impl.parser.state.Schema;
012: import com.sun.xml.xsom.impl.util.Uri;
013: import com.sun.xml.xsom.parser.AnnotationParser;
014: import org.relaxng.datatype.ValidationContext;
015: import org.xml.sax.Attributes;
016: import org.xml.sax.EntityResolver;
017: import org.xml.sax.ErrorHandler;
018: import org.xml.sax.InputSource;
019: import org.xml.sax.Locator;
020: import org.xml.sax.SAXException;
021: import org.xml.sax.SAXParseException;
022: import org.xml.sax.helpers.LocatorImpl;
023:
024: import java.io.IOException;
025: import java.text.MessageFormat;
026: import java.util.Stack;
027:
028: /**
029: * NGCCRuntime extended with various utility methods for
030: * parsing XML Schema.
031: *
032: * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
033: */
034: public class NGCCRuntimeEx extends NGCCRuntime implements
035: PatcherManager {
036:
037: /** coordinator. */
038: public final ParserContext parser;
039:
040: /** The schema currently being parsed. */
041: public SchemaImpl currentSchema;
042:
043: /** The @finalDefault value of the current schema. */
044: public int finalDefault = 0;
045: /** The @blockDefault value of the current schema. */
046: public int blockDefault = 0;
047:
048: /**
049: * The @elementFormDefault value of the current schema.
050: * True if local elements are qualified by default.
051: */
052: public boolean elementFormDefault = false;
053:
054: /**
055: * The @attributeFormDefault value of the current schema.
056: * True if local attributes are qualified by default.
057: */
058: public boolean attributeFormDefault = false;
059:
060: /**
061: * True if the current schema is in a chameleon mode.
062: * This changes the way QNames are interpreted.
063: *
064: * Life is very miserable with XML Schema, as you see.
065: */
066: public boolean chameleonMode = false;
067:
068: /**
069: * URI that identifies the schema document.
070: * Maybe null if the system ID is not available.
071: */
072: private String documentSystemId;
073:
074: /**
075: * Keep the local name of elements encountered so far.
076: * This information is passed to AnnotationParser as
077: * context information
078: */
079: private final Stack<String> elementNames = new Stack<String>();
080:
081: /**
082: * Points to the schema document (the parser of it) that included/imported
083: * this schema.
084: */
085: private final NGCCRuntimeEx referer;
086:
087: /**
088: * Points to the {@link SchemaDocumentImpl} that represents the
089: * schema document being parsed.
090: */
091: public SchemaDocumentImpl document;
092:
093: NGCCRuntimeEx(ParserContext _parser) {
094: this (_parser, false, null);
095: }
096:
097: private NGCCRuntimeEx(ParserContext _parser, boolean chameleonMode,
098: NGCCRuntimeEx referer) {
099: this .parser = _parser;
100: this .chameleonMode = chameleonMode;
101: this .referer = referer;
102:
103: // set up the default namespace binding
104: currentContext = new Context("", "", null);
105: currentContext = new Context("xml",
106: "http://www.w3.org/XML/1998/namespace", currentContext);
107: }
108:
109: public void checkDoubleDefError(XSDeclaration c)
110: throws SAXException {
111: if (c == null || ignorableDuplicateComponent(c))
112: return;
113:
114: reportError(Messages.format(Messages.ERR_DOUBLE_DEFINITION, c
115: .getName()));
116: reportError(Messages
117: .format(Messages.ERR_DOUBLE_DEFINITION_ORIGINAL), c
118: .getLocator());
119: }
120:
121: public static boolean ignorableDuplicateComponent(XSDeclaration c) {
122: if (c.getTargetNamespace().equals(Const.schemaNamespace)) {
123: if (c instanceof XSSimpleType)
124: // hide artificial "double definitions" on simple types
125: return true;
126: if (c.isGlobal() && c.getName().equals("anyType"))
127: return true; // ditto for anyType
128: }
129: return false;
130: }
131:
132: /* registers a patcher that will run after all the parsing has finished. */
133: public void addPatcher(Patch patcher) {
134: parser.patcherManager.addPatcher(patcher);
135: }
136:
137: public void addErrorChecker(Patch patcher) {
138: parser.patcherManager.addErrorChecker(patcher);
139: }
140:
141: public void reportError(String msg, Locator loc)
142: throws SAXException {
143: parser.patcherManager.reportError(msg, loc);
144: }
145:
146: public void reportError(String msg) throws SAXException {
147: reportError(msg, getLocator());
148: }
149:
150: /**
151: * Resolves relative URI found in the document.
152: *
153: * @param namespaceURI
154: * passed to the entity resolver.
155: * @param relativeUri
156: * value of the schemaLocation attribute. Can be null.
157: *
158: * @return
159: * non-null if {@link EntityResolver} returned an {@link InputSource},
160: * or if the relativeUri parameter seems to be pointing to something.
161: * Otherwise it returns null, in which case import/include should be abandoned.
162: */
163: private InputSource resolveRelativeURL(String namespaceURI,
164: String relativeUri) throws SAXException {
165: try {
166: String baseUri = getLocator().getSystemId();
167: if (baseUri == null)
168: // if the base URI is not available, the document system ID is
169: // better than nothing.
170: baseUri = documentSystemId;
171:
172: String systemId = null;
173: if (relativeUri != null)
174: systemId = Uri.resolve(baseUri, relativeUri);
175:
176: EntityResolver er = parser.getEntityResolver();
177: if (er != null) {
178: InputSource is = er.resolveEntity(namespaceURI,
179: systemId);
180: if (is != null)
181: return is;
182: }
183:
184: if (systemId != null)
185: return new InputSource(systemId);
186: else
187: return null;
188: } catch (IOException e) {
189: SAXParseException se = new SAXParseException(
190: e.getMessage(), getLocator(), e);
191: parser.errorHandler.error(se);
192: return null;
193: }
194: }
195:
196: /** Includes the specified schema. */
197: public void includeSchema(String schemaLocation)
198: throws SAXException {
199: NGCCRuntimeEx runtime = new NGCCRuntimeEx(parser,
200: chameleonMode, this );
201: runtime.currentSchema = this .currentSchema;
202: runtime.blockDefault = this .blockDefault;
203: runtime.finalDefault = this .finalDefault;
204:
205: if (schemaLocation == null) {
206: SAXParseException e = new SAXParseException(Messages
207: .format(Messages.ERR_MISSING_SCHEMALOCATION),
208: getLocator());
209: parser.errorHandler.fatalError(e);
210: throw e;
211: }
212:
213: runtime.parseEntity(resolveRelativeURL(null, schemaLocation),
214: true, currentSchema.getTargetNamespace(), getLocator());
215: }
216:
217: /** Imports the specified schema. */
218: public void importSchema(String ns, String schemaLocation)
219: throws SAXException {
220: NGCCRuntimeEx newRuntime = new NGCCRuntimeEx(parser, false,
221: this );
222: InputSource source = resolveRelativeURL(ns, schemaLocation);
223: if (source != null)
224: newRuntime.parseEntity(source, false, ns, getLocator());
225: // if source == null,
226: // we can't locate this document. Let's just hope that
227: // we already have the schema components for this schema
228: // or we will receive them in the future.
229: }
230:
231: /**
232: * Called when a new document is being parsed and checks
233: * if the document has already been parsed before.
234: *
235: * <p>
236: * Used to avoid recursive inclusion. Note that the same
237: * document will be parsed multiple times if they are for different
238: * target namespaces.
239: *
240: * <h2>Document Graph Model</h2>
241: * <p>
242: * The challenge we are facing here is that you have a graph of
243: * documents that reference each other. Each document has an unique
244: * URI to identify themselves, and references are done by using those.
245: * The graph may contain cycles.
246: *
247: * <p>
248: * Our goal here is to parse all the documents in the graph, without
249: * parsing the same document twice. This method implements this check.
250: *
251: * <p>
252: * One complication is the chameleon schema; a document can be parsed
253: * multiple times if they are under different target namespaces.
254: *
255: * <p>
256: * Also, note that when you resolve relative URIs in the @schemaLocation,
257: * their base URI is *NOT* the URI of the document.
258: *
259: * @return true if the document has already been processed and thus
260: * needs to be skipped.
261: */
262: public boolean hasAlreadyBeenRead() {
263: if (documentSystemId != null) {
264: if (documentSystemId.startsWith("file:///"))
265: // change file:///abc to file:/abc
266: // JDK File.toURL method produces the latter, but according to RFC
267: // I don't think that's a valid URL. Since two different ways of
268: // producing URLs could produce those two different forms,
269: // we need to canonicalize one to the other.
270: documentSystemId = "file:/"
271: + documentSystemId.substring(8);
272: } else {
273: // if the system Id is not provided, we can't test the identity,
274: // so we have no choice but to read it.
275: // the newly created SchemaDocumentImpl will be unique one
276: }
277:
278: assert document == null;
279: document = new SchemaDocumentImpl(currentSchema,
280: documentSystemId);
281:
282: SchemaDocumentImpl existing = parser.parsedDocuments
283: .get(document);
284: if (existing == null) {
285: parser.parsedDocuments.put(document, document);
286: } else {
287: document = existing;
288: }
289:
290: assert document != null;
291:
292: if (referer != null) {
293: assert referer.document != null : "referer "
294: + referer.documentSystemId
295: + " has docIdentity==null";
296: referer.document.references.add(this .document);
297: this .document.referers.add(referer.document);
298: }
299:
300: return existing != null;
301: }
302:
303: /**
304: * Parses the specified entity.
305: *
306: * @param importLocation
307: * The source location of the import/include statement.
308: * Used for reporting errors.
309: */
310: public void parseEntity(InputSource source, boolean includeMode,
311: String expectedNamespace, Locator importLocation)
312: throws SAXException {
313:
314: documentSystemId = source.getSystemId();
315: // System.out.println("parsing "+baseUri);
316:
317: try {
318: Schema s = new Schema(this , includeMode, expectedNamespace);
319: setRootHandler(s);
320:
321: try {
322: parser.parser.parse(source, this , getErrorHandler(),
323: parser.getEntityResolver());
324: } catch (IOException e) {
325: SAXParseException se = new SAXParseException(e
326: .toString(), importLocation, e);
327: parser.errorHandler.fatalError(se);
328: throw se;
329: }
330: } catch (SAXException e) {
331: parser.setErrorFlag();
332: throw e;
333: }
334: }
335:
336: /**
337: * Creates a new instance of annotation parser.
338: */
339: public AnnotationParser createAnnotationParser() {
340: if (parser.getAnnotationParserFactory() == null)
341: return DefaultAnnotationParser.theInstance;
342: else
343: return parser.getAnnotationParserFactory().create();
344: }
345:
346: /**
347: * Gets the element name that contains the annotation element.
348: * This method works correctly only when called by the annotation handler.
349: */
350: public String getAnnotationContextElementName() {
351: return elementNames.get(elementNames.size() - 2);
352: }
353:
354: /** Creates a copy of the current locator object. */
355: public Locator copyLocator() {
356: return new LocatorImpl(getLocator());
357: }
358:
359: public ErrorHandler getErrorHandler() {
360: return parser.errorHandler;
361: }
362:
363: public void onEnterElementConsumed(String uri, String localName,
364: String qname, Attributes atts) throws SAXException {
365: super .onEnterElementConsumed(uri, localName, qname, atts);
366: elementNames.push(localName);
367: }
368:
369: public void onLeaveElementConsumed(String uri, String localName,
370: String qname) throws SAXException {
371: super .onLeaveElementConsumed(uri, localName, qname);
372: elementNames.pop();
373: }
374:
375: //
376: //
377: // ValidationContext implementation
378: //
379: //
380: // this object lives longer than the parser itself,
381: // so it's important for this object not to have any reference
382: // to the parser.
383: private static class Context implements ValidationContext {
384: Context(String _prefix, String _uri, Context _context) {
385: this .previous = _context;
386: this .prefix = _prefix;
387: this .uri = _uri;
388: }
389:
390: public String resolveNamespacePrefix(String p) {
391: if (p.equals(prefix))
392: return uri;
393: if (previous == null)
394: return null;
395: else
396: return previous.resolveNamespacePrefix(p);
397: }
398:
399: private final String prefix;
400: private final String uri;
401: private final Context previous;
402:
403: // XSDLib don't use those methods, so we cut a corner here.
404: public String getBaseUri() {
405: return null;
406: }
407:
408: public boolean isNotation(String arg0) {
409: return false;
410: }
411:
412: public boolean isUnparsedEntity(String arg0) {
413: return false;
414: }
415: }
416:
417: private Context currentContext = null;
418:
419: /** Returns an immutable snapshot of the current context. */
420: public ValidationContext createValidationContext() {
421: return currentContext;
422: }
423:
424: public XmlString createXmlString(String value) {
425: if (value == null)
426: return null;
427: else
428: return new XmlString(value, createValidationContext());
429: }
430:
431: public void startPrefixMapping(String prefix, String uri)
432: throws SAXException {
433: super .startPrefixMapping(prefix, uri);
434: currentContext = new Context(prefix, uri, currentContext);
435: }
436:
437: public void endPrefixMapping(String prefix) throws SAXException {
438: super .endPrefixMapping(prefix);
439: currentContext = currentContext.previous;
440: }
441:
442: //
443: //
444: // Utility functions
445: //
446: //
447:
448: /** Parses UName under the given context. */
449: public UName parseUName(String qname) throws SAXException {
450: int idx = qname.indexOf(':');
451: if (idx < 0) {
452: String uri = resolveNamespacePrefix("");
453:
454: // chamelon behavior. ugly...
455: if (uri.equals("") && chameleonMode)
456: uri = currentSchema.getTargetNamespace();
457:
458: // this is guaranteed to resolve
459: return new UName(uri, qname, qname);
460: } else {
461: String prefix = qname.substring(0, idx);
462: String uri = currentContext.resolveNamespacePrefix(prefix);
463: if (uri == null) {
464: // prefix failed to resolve.
465: reportError(Messages.format(
466: Messages.ERR_UNDEFINED_PREFIX, prefix));
467: uri = "undefined"; // replace with a dummy
468: }
469: return new UName(uri, qname.substring(idx + 1), qname);
470: }
471: }
472:
473: public boolean parseBoolean(String v) {
474: if (v == null)
475: return false;
476: v = v.trim();
477: return v.equals("true") || v.equals("1");
478: }
479:
480: protected void unexpectedX(String token) throws SAXException {
481: SAXParseException e = new SAXParseException(
482: MessageFormat
483: .format(
484: "Unexpected {0} appears at line {1} column {2}",
485: token, getLocator().getLineNumber(),
486: getLocator().getColumnNumber()),
487: getLocator());
488:
489: parser.errorHandler.fatalError(e);
490: throw e; // we will abort anyway
491: }
492:
493: public ForeignAttributesImpl parseForeignAttributes(
494: ForeignAttributesImpl next) {
495: ForeignAttributesImpl impl = new ForeignAttributesImpl(
496: createValidationContext(), copyLocator(), next);
497:
498: Attributes atts = getCurrentAttributes();
499: for (int i = 0; i < atts.getLength(); i++) {
500: if (atts.getURI(i).length() > 0) {
501: impl.addAttribute(atts.getURI(i), atts.getLocalName(i),
502: atts.getQName(i), atts.getType(i), atts
503: .getValue(i));
504: }
505: }
506:
507: return impl;
508: }
509:
510: public static final String XMLSchemaNSURI = "http://www.w3.org/2001/XMLSchema";
511: }
|