blob: c6b326a8de5e4e55ce1a9b17d78be77ad17e151b [file] [log] [blame]
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* Copyright (C) 2004-2012
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.sun.xml.internal.rngom.parse.xml;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import com.sun.xml.internal.rngom.ast.builder.Annotations;
import com.sun.xml.internal.rngom.ast.builder.CommentList;
import com.sun.xml.internal.rngom.ast.builder.DataPatternBuilder;
import com.sun.xml.internal.rngom.ast.builder.Div;
import com.sun.xml.internal.rngom.ast.builder.ElementAnnotationBuilder;
import com.sun.xml.internal.rngom.ast.builder.Grammar;
import com.sun.xml.internal.rngom.ast.builder.GrammarSection;
import com.sun.xml.internal.rngom.ast.builder.Include;
import com.sun.xml.internal.rngom.ast.builder.IncludedGrammar;
import com.sun.xml.internal.rngom.ast.builder.NameClassBuilder;
import com.sun.xml.internal.rngom.ast.builder.SchemaBuilder;
import com.sun.xml.internal.rngom.ast.builder.Scope;
import com.sun.xml.internal.rngom.ast.om.Location;
import com.sun.xml.internal.rngom.ast.om.ParsedElementAnnotation;
import com.sun.xml.internal.rngom.ast.om.ParsedNameClass;
import com.sun.xml.internal.rngom.ast.om.ParsedPattern;
import com.sun.xml.internal.rngom.parse.Context;
import com.sun.xml.internal.rngom.parse.IllegalSchemaException;
import com.sun.xml.internal.rngom.parse.Parseable;
import com.sun.xml.internal.rngom.util.Localizer;
import com.sun.xml.internal.rngom.util.Uri;
import com.sun.xml.internal.rngom.xml.sax.AbstractLexicalHandler;
import com.sun.xml.internal.rngom.xml.sax.XmlBaseHandler;
import com.sun.xml.internal.rngom.xml.util.Naming;
import com.sun.xml.internal.rngom.xml.util.WellKnownNamespaces;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
class SchemaParser {
private static final String relaxngURIPrefix =
WellKnownNamespaces.RELAX_NG.substring(0, WellKnownNamespaces.RELAX_NG.lastIndexOf('/') + 1);
static final String relaxng10URI = WellKnownNamespaces.RELAX_NG;
private static final Localizer localizer = new Localizer(new Localizer(Parseable.class), SchemaParser.class);
private String relaxngURI;
private final XMLReader xr;
private final ErrorHandler eh;
private final SchemaBuilder schemaBuilder;
/**
* The value of the {@link SchemaBuilder#getNameClassBuilder()} for the
* {@link #schemaBuilder} object.
*/
private final NameClassBuilder nameClassBuilder;
private ParsedPattern startPattern;
private Locator locator;
private final XmlBaseHandler xmlBaseHandler = new XmlBaseHandler();
private final ContextImpl context = new ContextImpl();
private boolean hadError = false;
private Hashtable patternTable;
private Hashtable nameClassTable;
static class PrefixMapping {
final String prefix;
final String uri;
final PrefixMapping next;
PrefixMapping(String prefix, String uri, PrefixMapping next) {
this.prefix = prefix;
this.uri = uri;
this.next = next;
}
}
static abstract class AbstractContext extends DtdContext implements Context {
PrefixMapping prefixMapping;
AbstractContext() {
prefixMapping = new PrefixMapping("xml", WellKnownNamespaces.XML, null);
}
AbstractContext(AbstractContext context) {
super(context);
prefixMapping = context.prefixMapping;
}
public String resolveNamespacePrefix(String prefix) {
for (PrefixMapping p = prefixMapping; p != null; p = p.next) {
if (p.prefix.equals(prefix)) {
return p.uri;
}
}
return null;
}
public Enumeration prefixes() {
Vector v = new Vector();
for (PrefixMapping p = prefixMapping; p != null; p = p.next) {
if (!v.contains(p.prefix)) {
v.addElement(p.prefix);
}
}
return v.elements();
}
public Context copy() {
return new SavedContext(this);
}
}
static class SavedContext extends AbstractContext {
private final String baseUri;
SavedContext(AbstractContext context) {
super(context);
this.baseUri = context.getBaseUri();
}
public String getBaseUri() {
return baseUri;
}
}
class ContextImpl extends AbstractContext {
public String getBaseUri() {
return xmlBaseHandler.getBaseUri();
}
}
static interface CommentHandler {
void comment(String value);
}
abstract class Handler implements ContentHandler, CommentHandler {
CommentList comments;
CommentList getComments() {
CommentList tem = comments;
comments = null;
return tem;
}
public void comment(String value) {
if (comments == null) {
comments = schemaBuilder.makeCommentList();
}
comments.addComment(value, makeLocation());
}
public void processingInstruction(String target, String date) {
}
public void skippedEntity(String name) {
}
public void ignorableWhitespace(char[] ch, int start, int len) {
}
public void startDocument() {
}
public void endDocument() {
}
public void startPrefixMapping(String prefix, String uri) {
context.prefixMapping = new PrefixMapping(prefix, uri, context.prefixMapping);
}
public void endPrefixMapping(String prefix) {
context.prefixMapping = context.prefixMapping.next;
}
public void setDocumentLocator(Locator loc) {
locator = loc;
xmlBaseHandler.setLocator(loc);
}
}
abstract class State extends Handler {
State parent;
String nsInherit;
String ns;
String datatypeLibrary;
/**
* The current scope, or null if there's none.
*/
Scope scope;
Location startLocation;
Annotations annotations;
void set() {
xr.setContentHandler(this);
}
abstract State create();
abstract State createChildState(String localName) throws SAXException;
void setParent(State parent) {
this.parent = parent;
this.nsInherit = parent.getNs();
this.datatypeLibrary = parent.datatypeLibrary;
this.scope = parent.scope;
this.startLocation = makeLocation();
if (parent.comments != null) {
annotations = schemaBuilder.makeAnnotations(parent.comments, getContext());
parent.comments = null;
} else if (parent instanceof RootState) {
annotations = schemaBuilder.makeAnnotations(null, getContext());
}
}
String getNs() {
return ns == null ? nsInherit : ns;
}
boolean isRelaxNGElement(String uri) throws SAXException {
return uri.equals(relaxngURI);
}
public void startElement(String namespaceURI,
String localName,
String qName,
Attributes atts) throws SAXException {
xmlBaseHandler.startElement();
if (isRelaxNGElement(namespaceURI)) {
State state = createChildState(localName);
if (state == null) {
xr.setContentHandler(new Skipper(this));
return;
}
state.setParent(this);
state.set();
state.attributes(atts);
} else {
checkForeignElement();
ForeignElementHandler feh = new ForeignElementHandler(this, getComments());
feh.startElement(namespaceURI, localName, qName, atts);
xr.setContentHandler(feh);
}
}
public void endElement(String namespaceURI,
String localName,
String qName) throws SAXException {
xmlBaseHandler.endElement();
parent.set();
end();
}
void setName(String name) throws SAXException {
error("illegal_name_attribute");
}
void setOtherAttribute(String name, String value) throws SAXException {
error("illegal_attribute_ignored", name);
}
void endAttributes() throws SAXException {
}
void checkForeignElement() throws SAXException {
}
void attributes(Attributes atts) throws SAXException {
int len = atts.getLength();
for (int i = 0; i < len; i++) {
String uri = atts.getURI(i);
if (uri.length() == 0) {
String name = atts.getLocalName(i);
if (name.equals("name")) {
setName(atts.getValue(i).trim());
} else if (name.equals("ns")) {
ns = atts.getValue(i);
} else if (name.equals("datatypeLibrary")) {
datatypeLibrary = atts.getValue(i);
checkUri(datatypeLibrary);
if (!datatypeLibrary.equals("")
&& !Uri.isAbsolute(datatypeLibrary)) {
error("relative_datatype_library");
}
if (Uri.hasFragmentId(datatypeLibrary)) {
error("fragment_identifier_datatype_library");
}
datatypeLibrary = Uri.escapeDisallowedChars(datatypeLibrary);
} else {
setOtherAttribute(name, atts.getValue(i));
}
} else if (uri.equals(relaxngURI)) {
error("qualified_attribute", atts.getLocalName(i));
} else if (uri.equals(WellKnownNamespaces.XML)
&& atts.getLocalName(i).equals("base")) {
xmlBaseHandler.xmlBaseAttribute(atts.getValue(i));
} else {
if (annotations == null) {
annotations = schemaBuilder.makeAnnotations(null, getContext());
}
annotations.addAttribute(uri, atts.getLocalName(i), findPrefix(atts.getQName(i), uri),
atts.getValue(i), startLocation);
}
}
endAttributes();
}
abstract void end() throws SAXException;
void endChild(ParsedPattern pattern) {
// XXX cannot happen; throw exception
}
void endChild(ParsedNameClass nc) {
// XXX cannot happen; throw exception
}
@Override
public void startDocument() {
}
@Override
public void endDocument() {
if (comments != null && startPattern != null) {
startPattern = schemaBuilder.commentAfter(startPattern, comments);
comments = null;
}
}
public void characters(char[] ch, int start, int len) throws SAXException {
for (int i = 0; i < len; i++) {
switch (ch[start + i]) {
case ' ':
case '\r':
case '\n':
case '\t':
break;
default:
error("illegal_characters_ignored");
break;
}
}
}
boolean isPatternNamespaceURI(String s) {
return s.equals(relaxngURI);
}
void endForeignChild(ParsedElementAnnotation ea) {
if (annotations == null) {
annotations = schemaBuilder.makeAnnotations(null, getContext());
}
annotations.addElement(ea);
}
void mergeLeadingComments() {
if (comments != null) {
if (annotations == null) {
annotations = schemaBuilder.makeAnnotations(comments, getContext());
} else {
annotations.addLeadingComment(comments);
}
comments = null;
}
}
}
class ForeignElementHandler extends Handler {
final State nextState;
ElementAnnotationBuilder builder;
final Stack builderStack = new Stack();
StringBuffer textBuf;
Location textLoc;
ForeignElementHandler(State nextState, CommentList comments) {
this.nextState = nextState;
this.comments = comments;
}
public void startElement(String namespaceURI, String localName,
String qName, Attributes atts) {
flushText();
if (builder != null) {
builderStack.push(builder);
}
Location loc = makeLocation();
builder = schemaBuilder.makeElementAnnotationBuilder(namespaceURI,
localName,
findPrefix(qName, namespaceURI),
loc,
getComments(),
getContext());
int len = atts.getLength();
for (int i = 0; i < len; i++) {
String uri = atts.getURI(i);
builder.addAttribute(uri, atts.getLocalName(i), findPrefix(atts.getQName(i), uri),
atts.getValue(i), loc);
}
}
public void endElement(String namespaceURI, String localName,
String qName) {
flushText();
if (comments != null) {
builder.addComment(getComments());
}
ParsedElementAnnotation ea = builder.makeElementAnnotation();
if (builderStack.empty()) {
nextState.endForeignChild(ea);
nextState.set();
} else {
builder = (ElementAnnotationBuilder) builderStack.pop();
builder.addElement(ea);
}
}
public void characters(char ch[], int start, int length) {
if (textBuf == null) {
textBuf = new StringBuffer();
}
textBuf.append(ch, start, length);
if (textLoc == null) {
textLoc = makeLocation();
}
}
@Override
public void comment(String value) {
flushText();
super.comment(value);
}
void flushText() {
if (textBuf != null && textBuf.length() != 0) {
builder.addText(textBuf.toString(), textLoc, getComments());
textBuf.setLength(0);
}
textLoc = null;
}
}
static class Skipper extends DefaultHandler implements CommentHandler {
int level = 1;
final State nextState;
Skipper(State nextState) {
this.nextState = nextState;
}
@Override
public void startElement(String namespaceURI,
String localName,
String qName,
Attributes atts) throws SAXException {
++level;
}
@Override
public void endElement(String namespaceURI,
String localName,
String qName) throws SAXException {
if (--level == 0) {
nextState.set();
}
}
public void comment(String value) {
}
}
abstract class EmptyContentState extends State {
State createChildState(String localName) throws SAXException {
error("expected_empty", localName);
return null;
}
abstract ParsedPattern makePattern() throws SAXException;
void end() throws SAXException {
if (comments != null) {
if (annotations == null) {
annotations = schemaBuilder.makeAnnotations(null, getContext());
}
annotations.addComment(comments);
comments = null;
}
parent.endChild(makePattern());
}
}
static private final int INIT_CHILD_ALLOC = 5;
abstract class PatternContainerState extends State {
List<ParsedPattern> childPatterns;
State createChildState(String localName) throws SAXException {
State state = (State) patternTable.get(localName);
if (state == null) {
error("expected_pattern", localName);
return null;
}
return state.create();
}
ParsedPattern buildPattern(List<ParsedPattern> patterns, Location loc, Annotations anno) throws SAXException {
if (patterns.size() == 1 && anno == null) {
return patterns.get(0);
}
return schemaBuilder.makeGroup(patterns, loc, anno);
}
@Override
void endChild(ParsedPattern pattern) {
if (childPatterns == null) {
childPatterns = new ArrayList<ParsedPattern>(INIT_CHILD_ALLOC);
}
childPatterns.add(pattern);
}
@Override
void endForeignChild(ParsedElementAnnotation ea) {
// Harshit : Annotation handling should always be taken care of, irrespective of childPatterns being null or not.
super.endForeignChild(ea);
if (childPatterns != null) {
int idx = childPatterns.size() - 1;
childPatterns.set(idx, schemaBuilder.annotateAfter(childPatterns.get(idx), ea));
}
}
void end() throws SAXException {
if (childPatterns == null) {
error("missing_children");
endChild(schemaBuilder.makeErrorPattern());
}
if (comments != null) {
int idx = childPatterns.size() - 1;
childPatterns.set(idx, schemaBuilder.commentAfter(childPatterns.get(idx), comments));
comments = null;
}
sendPatternToParent(buildPattern(childPatterns, startLocation, annotations));
}
void sendPatternToParent(ParsedPattern p) {
parent.endChild(p);
}
}
class GroupState extends PatternContainerState {
State create() {
return new GroupState();
}
}
class ZeroOrMoreState extends PatternContainerState {
State create() {
return new ZeroOrMoreState();
}
@Override
ParsedPattern buildPattern(List<ParsedPattern> patterns, Location loc, Annotations anno) throws SAXException {
return schemaBuilder.makeZeroOrMore(super.buildPattern(patterns, loc, null), loc, anno);
}
}
class OneOrMoreState extends PatternContainerState {
State create() {
return new OneOrMoreState();
}
@Override
ParsedPattern buildPattern(List<ParsedPattern> patterns, Location loc, Annotations anno) throws SAXException {
return schemaBuilder.makeOneOrMore(super.buildPattern(patterns, loc, null), loc, anno);
}
}
class OptionalState extends PatternContainerState {
State create() {
return new OptionalState();
}
@Override
ParsedPattern buildPattern(List<ParsedPattern> patterns, Location loc, Annotations anno) throws SAXException {
return schemaBuilder.makeOptional(super.buildPattern(patterns, loc, null), loc, anno);
}
}
class ListState extends PatternContainerState {
State create() {
return new ListState();
}
@Override
ParsedPattern buildPattern(List<ParsedPattern> patterns, Location loc, Annotations anno) throws SAXException {
return schemaBuilder.makeList(super.buildPattern(patterns, loc, null), loc, anno);
}
}
class ChoiceState extends PatternContainerState {
State create() {
return new ChoiceState();
}
@Override
ParsedPattern buildPattern(List<ParsedPattern> patterns, Location loc, Annotations anno) throws SAXException {
return schemaBuilder.makeChoice(patterns, loc, anno);
}
}
class InterleaveState extends PatternContainerState {
State create() {
return new InterleaveState();
}
@Override
ParsedPattern buildPattern(List<ParsedPattern> patterns, Location loc, Annotations anno) {
return schemaBuilder.makeInterleave(patterns, loc, anno);
}
}
class MixedState extends PatternContainerState {
State create() {
return new MixedState();
}
@Override
ParsedPattern buildPattern(List<ParsedPattern> patterns, Location loc, Annotations anno) throws SAXException {
return schemaBuilder.makeMixed(super.buildPattern(patterns, loc, null), loc, anno);
}
}
static interface NameClassRef {
void setNameClass(ParsedNameClass nc);
}
class ElementState extends PatternContainerState implements NameClassRef {
ParsedNameClass nameClass;
boolean nameClassWasAttribute;
String name;
@Override
void setName(String name) {
this.name = name;
}
public void setNameClass(ParsedNameClass nc) {
nameClass = nc;
}
@Override
void endAttributes() throws SAXException {
if (name != null) {
nameClass = expandName(name, getNs(), null);
nameClassWasAttribute = true;
} else {
new NameClassChildState(this, this).set();
}
}
State create() {
return new ElementState();
}
@Override
ParsedPattern buildPattern(List<ParsedPattern> patterns, Location loc, Annotations anno) throws SAXException {
return schemaBuilder.makeElement(nameClass, super.buildPattern(patterns, loc, null), loc, anno);
}
@Override
void endForeignChild(ParsedElementAnnotation ea) {
if (nameClassWasAttribute || childPatterns != null || nameClass == null) {
super.endForeignChild(ea);
} else {
nameClass = nameClassBuilder.annotateAfter(nameClass, ea);
}
}
}
class RootState extends PatternContainerState {
IncludedGrammar grammar;
RootState() {
}
RootState(IncludedGrammar grammar, Scope scope, String ns) {
this.grammar = grammar;
this.scope = scope;
this.nsInherit = ns;
this.datatypeLibrary = "";
}
State create() {
return new RootState();
}
@Override
State createChildState(String localName) throws SAXException {
if (grammar == null) {
return super.createChildState(localName);
}
if (localName.equals("grammar")) {
return new MergeGrammarState(grammar);
}
error("expected_grammar", localName);
return null;
}
@Override
void checkForeignElement() throws SAXException {
error("root_bad_namespace_uri", WellKnownNamespaces.RELAX_NG);
}
@Override
void endChild(ParsedPattern pattern) {
startPattern = pattern;
}
@Override
boolean isRelaxNGElement(String uri) throws SAXException {
if (!uri.startsWith(relaxngURIPrefix)) {
return false;
}
if (!uri.equals(WellKnownNamespaces.RELAX_NG)) {
warning("wrong_uri_version",
WellKnownNamespaces.RELAX_NG.substring(relaxngURIPrefix.length()),
uri.substring(relaxngURIPrefix.length()));
}
relaxngURI = uri;
return true;
}
}
class NotAllowedState extends EmptyContentState {
State create() {
return new NotAllowedState();
}
ParsedPattern makePattern() {
return schemaBuilder.makeNotAllowed(startLocation, annotations);
}
}
class EmptyState extends EmptyContentState {
State create() {
return new EmptyState();
}
ParsedPattern makePattern() {
return schemaBuilder.makeEmpty(startLocation, annotations);
}
}
class TextState extends EmptyContentState {
State create() {
return new TextState();
}
ParsedPattern makePattern() {
return schemaBuilder.makeText(startLocation, annotations);
}
}
class ValueState extends EmptyContentState {
final StringBuffer buf = new StringBuffer();
String type;
State create() {
return new ValueState();
}
@Override
void setOtherAttribute(String name, String value) throws SAXException {
if (name.equals("type")) {
type = checkNCName(value.trim());
} else {
super.setOtherAttribute(name, value);
}
}
@Override
public void characters(char[] ch, int start, int len) {
buf.append(ch, start, len);
}
@Override
void checkForeignElement() throws SAXException {
error("value_contains_foreign_element");
}
ParsedPattern makePattern() throws SAXException {
if (type == null) {
return makePattern("", "token");
} else {
return makePattern(datatypeLibrary, type);
}
}
@Override
void end() throws SAXException {
mergeLeadingComments();
super.end();
}
ParsedPattern makePattern(String datatypeLibrary, String type) {
return schemaBuilder.makeValue(datatypeLibrary,
type,
buf.toString(),
getContext(),
getNs(),
startLocation,
annotations);
}
}
class DataState extends State {
String type;
ParsedPattern except = null;
DataPatternBuilder dpb = null;
State create() {
return new DataState();
}
State createChildState(String localName) throws SAXException {
if (localName.equals("param")) {
if (except != null) {
error("param_after_except");
}
return new ParamState(dpb);
}
if (localName.equals("except")) {
if (except != null) {
error("multiple_except");
}
return new ChoiceState();
}
error("expected_param_except", localName);
return null;
}
@Override
void setOtherAttribute(String name, String value) throws SAXException {
if (name.equals("type")) {
type = checkNCName(value.trim());
} else {
super.setOtherAttribute(name, value);
}
}
@Override
void endAttributes() throws SAXException {
if (type == null) {
error("missing_type_attribute");
} else {
dpb = schemaBuilder.makeDataPatternBuilder(datatypeLibrary, type, startLocation);
}
}
void end() throws SAXException {
ParsedPattern p;
if (dpb != null) {
if (except != null) {
p = dpb.makePattern(except, startLocation, annotations);
} else {
p = dpb.makePattern(startLocation, annotations);
}
} else {
p = schemaBuilder.makeErrorPattern();
}
// XXX need to capture comments
parent.endChild(p);
}
@Override
void endChild(ParsedPattern pattern) {
except = pattern;
}
}
class ParamState extends State {
private final StringBuffer buf = new StringBuffer();
private final DataPatternBuilder dpb;
private String name;
ParamState(DataPatternBuilder dpb) {
this.dpb = dpb;
}
State create() {
return new ParamState(null);
}
@Override
void setName(String name) throws SAXException {
this.name = checkNCName(name);
}
@Override
void endAttributes() throws SAXException {
if (name == null) {
error("missing_name_attribute");
}
}
State createChildState(String localName) throws SAXException {
error("expected_empty", localName);
return null;
}
@Override
public void characters(char[] ch, int start, int len) {
buf.append(ch, start, len);
}
@Override
void checkForeignElement() throws SAXException {
error("param_contains_foreign_element");
}
void end() throws SAXException {
if (name == null) {
return;
}
if (dpb == null) {
return;
}
mergeLeadingComments();
dpb.addParam(name, buf.toString(), getContext(), getNs(), startLocation, annotations);
}
}
class AttributeState extends PatternContainerState implements NameClassRef {
ParsedNameClass nameClass;
boolean nameClassWasAttribute;
String name;
State create() {
return new AttributeState();
}
@Override
void setName(String name) {
this.name = name;
}
public void setNameClass(ParsedNameClass nc) {
nameClass = nc;
}
@Override
void endAttributes() throws SAXException {
if (name != null) {
String nsUse;
if (ns != null) {
nsUse = ns;
} else {
nsUse = "";
}
nameClass = expandName(name, nsUse, null);
nameClassWasAttribute = true;
} else {
new NameClassChildState(this, this).set();
}
}
@Override
void endForeignChild(ParsedElementAnnotation ea) {
if (nameClassWasAttribute || childPatterns != null || nameClass == null) {
super.endForeignChild(ea);
} else {
nameClass = nameClassBuilder.annotateAfter(nameClass, ea);
}
}
@Override
void end() throws SAXException {
if (childPatterns == null) {
endChild(schemaBuilder.makeText(startLocation, null));
}
super.end();
}
@Override
ParsedPattern buildPattern(List<ParsedPattern> patterns, Location loc, Annotations anno) throws SAXException {
return schemaBuilder.makeAttribute(nameClass, super.buildPattern(patterns, loc, null), loc, anno);
}
@Override
State createChildState(String localName) throws SAXException {
State tem = super.createChildState(localName);
if (tem != null && childPatterns != null) {
error("attribute_multi_pattern");
}
return tem;
}
}
abstract class SinglePatternContainerState extends PatternContainerState {
@Override
State createChildState(String localName) throws SAXException {
if (childPatterns == null) {
return super.createChildState(localName);
}
error("too_many_children");
return null;
}
}
class GrammarSectionState extends State {
GrammarSection section;
GrammarSectionState() {
}
GrammarSectionState(GrammarSection section) {
this.section = section;
}
State create() {
return new GrammarSectionState(null);
}
State createChildState(String localName) throws SAXException {
if (localName.equals("define")) {
return new DefineState(section);
}
if (localName.equals("start")) {
return new StartState(section);
}
if (localName.equals("include")) {
Include include = section.makeInclude();
if (include != null) {
return new IncludeState(include);
}
}
if (localName.equals("div")) {
return new DivState(section.makeDiv());
}
error("expected_define", localName);
// XXX better errors
return null;
}
void end() throws SAXException {
if (comments != null) {
section.topLevelComment(comments);
comments = null;
}
}
@Override
void endForeignChild(ParsedElementAnnotation ea) {
section.topLevelAnnotation(ea);
}
}
class DivState extends GrammarSectionState {
final Div div;
DivState(Div div) {
super(div);
this.div = div;
}
@Override
void end() throws SAXException {
super.end();
div.endDiv(startLocation, annotations);
}
}
class IncludeState extends GrammarSectionState {
String href;
final Include include;
IncludeState(Include include) {
super(include);
this.include = include;
}
@Override
void setOtherAttribute(String name, String value) throws SAXException {
if (name.equals("href")) {
href = value;
checkUri(href);
} else {
super.setOtherAttribute(name, value);
}
}
@Override
void endAttributes() throws SAXException {
if (href == null) {
error("missing_href_attribute");
} else {
href = resolve(href);
}
}
@Override
void end() throws SAXException {
super.end();
if (href != null) {
try {
include.endInclude(parseable, href, getNs(), startLocation, annotations);
} catch (IllegalSchemaException e) {
}
}
}
}
class MergeGrammarState extends GrammarSectionState {
final IncludedGrammar grammar;
MergeGrammarState(IncludedGrammar grammar) {
super(grammar);
this.grammar = grammar;
}
@Override
void end() throws SAXException {
super.end();
parent.endChild(grammar.endIncludedGrammar(startLocation, annotations));
}
}
class GrammarState extends GrammarSectionState {
Grammar grammar;
@Override
void setParent(State parent) {
super.setParent(parent);
grammar = schemaBuilder.makeGrammar(scope);
section = grammar;
scope = grammar;
}
@Override
State create() {
return new GrammarState();
}
@Override
void end() throws SAXException {
super.end();
parent.endChild(grammar.endGrammar(startLocation, annotations));
}
}
class RefState extends EmptyContentState {
String name;
State create() {
return new RefState();
}
@Override
void endAttributes() throws SAXException {
if (name == null) {
error("missing_name_attribute");
}
}
@Override
void setName(String name) throws SAXException {
this.name = checkNCName(name);
}
ParsedPattern makePattern() throws SAXException {
if (name == null) {
return schemaBuilder.makeErrorPattern();
}
if (scope == null) {
error("ref_outside_grammar", name);
return schemaBuilder.makeErrorPattern();
} else {
return scope.makeRef(name, startLocation, annotations);
}
}
}
class ParentRefState extends RefState {
@Override
State create() {
return new ParentRefState();
}
@Override
ParsedPattern makePattern() throws SAXException {
if (name == null) {
return schemaBuilder.makeErrorPattern();
}
if (scope == null) {
error("parent_ref_outside_grammar", name);
return schemaBuilder.makeErrorPattern();
} else {
return scope.makeParentRef(name, startLocation, annotations);
}
}
}
class ExternalRefState extends EmptyContentState {
String href;
State create() {
return new ExternalRefState();
}
@Override
void setOtherAttribute(String name, String value) throws SAXException {
if (name.equals("href")) {
href = value;
checkUri(href);
} else {
super.setOtherAttribute(name, value);
}
}
@Override
void endAttributes() throws SAXException {
if (href == null) {
error("missing_href_attribute");
} else {
href = resolve(href);
}
}
ParsedPattern makePattern() {
if (href != null) {
try {
return schemaBuilder.makeExternalRef(parseable,
href,
getNs(),
scope,
startLocation,
annotations);
} catch (IllegalSchemaException e) {
}
}
return schemaBuilder.makeErrorPattern();
}
}
abstract class DefinitionState extends PatternContainerState {
GrammarSection.Combine combine = null;
final GrammarSection section;
DefinitionState(GrammarSection section) {
this.section = section;
}
@Override
void setOtherAttribute(String name, String value) throws SAXException {
if (name.equals("combine")) {
value = value.trim();
if (value.equals("choice")) {
combine = GrammarSection.COMBINE_CHOICE;
} else if (value.equals("interleave")) {
combine = GrammarSection.COMBINE_INTERLEAVE;
} else {
error("combine_attribute_bad_value", value);
}
} else {
super.setOtherAttribute(name, value);
}
}
@Override
ParsedPattern buildPattern(List<ParsedPattern> patterns, Location loc, Annotations anno) throws SAXException {
return super.buildPattern(patterns, loc, null);
}
}
class DefineState extends DefinitionState {
String name;
DefineState(GrammarSection section) {
super(section);
}
State create() {
return new DefineState(null);
}
@Override
void setName(String name) throws SAXException {
this.name = checkNCName(name);
}
@Override
void endAttributes() throws SAXException {
if (name == null) {
error("missing_name_attribute");
}
}
@Override
void sendPatternToParent(ParsedPattern p) {
if (name != null) {
section.define(name, combine, p, startLocation, annotations);
}
}
}
class StartState extends DefinitionState {
StartState(GrammarSection section) {
super(section);
}
State create() {
return new StartState(null);
}
@Override
void sendPatternToParent(ParsedPattern p) {
section.define(GrammarSection.START, combine, p, startLocation, annotations);
}
@Override
State createChildState(String localName) throws SAXException {
State tem = super.createChildState(localName);
if (tem != null && childPatterns != null) {
error("start_multi_pattern");
}
return tem;
}
}
abstract class NameClassContainerState extends State {
State createChildState(String localName) throws SAXException {
State state = (State) nameClassTable.get(localName);
if (state == null) {
error("expected_name_class", localName);
return null;
}
return state.create();
}
}
class NameClassChildState extends NameClassContainerState {
final State prevState;
final NameClassRef nameClassRef;
State create() {
return null;
}
NameClassChildState(State prevState, NameClassRef nameClassRef) {
this.prevState = prevState;
this.nameClassRef = nameClassRef;
setParent(prevState.parent);
this.ns = prevState.ns;
}
@Override
void endChild(ParsedNameClass nameClass) {
nameClassRef.setNameClass(nameClass);
prevState.set();
}
@Override
void endForeignChild(ParsedElementAnnotation ea) {
prevState.endForeignChild(ea);
}
void end() throws SAXException {
nameClassRef.setNameClass(nameClassBuilder.makeErrorNameClass());
error("missing_name_class");
prevState.set();
prevState.end();
}
}
abstract class NameClassBaseState extends State {
abstract ParsedNameClass makeNameClass() throws SAXException;
void end() throws SAXException {
parent.endChild(makeNameClass());
}
}
class NameState extends NameClassBaseState {
final StringBuffer buf = new StringBuffer();
State createChildState(String localName) throws SAXException {
error("expected_name", localName);
return null;
}
State create() {
return new NameState();
}
@Override
public void characters(char[] ch, int start, int len) {
buf.append(ch, start, len);
}
@Override
void checkForeignElement() throws SAXException {
error("name_contains_foreign_element");
}
ParsedNameClass makeNameClass() throws SAXException {
mergeLeadingComments();
return expandName(buf.toString().trim(), getNs(), annotations);
}
}
private static final int PATTERN_CONTEXT = 0;
private static final int ANY_NAME_CONTEXT = 1;
private static final int NS_NAME_CONTEXT = 2;
private SAXParseable parseable;
class AnyNameState extends NameClassBaseState {
ParsedNameClass except = null;
State create() {
return new AnyNameState();
}
State createChildState(String localName) throws SAXException {
if (localName.equals("except")) {
if (except != null) {
error("multiple_except");
}
return new NameClassChoiceState(getContext());
}
error("expected_except", localName);
return null;
}
int getContext() {
return ANY_NAME_CONTEXT;
}
ParsedNameClass makeNameClass() {
if (except == null) {
return makeNameClassNoExcept();
} else {
return makeNameClassExcept(except);
}
}
ParsedNameClass makeNameClassNoExcept() {
return nameClassBuilder.makeAnyName(startLocation, annotations);
}
ParsedNameClass makeNameClassExcept(ParsedNameClass except) {
return nameClassBuilder.makeAnyName(except, startLocation, annotations);
}
@Override
void endChild(ParsedNameClass nameClass) {
except = nameClass;
}
}
class NsNameState extends AnyNameState {
@Override
State create() {
return new NsNameState();
}
@Override
ParsedNameClass makeNameClassNoExcept() {
return nameClassBuilder.makeNsName(getNs(), null, null);
}
@Override
ParsedNameClass makeNameClassExcept(ParsedNameClass except) {
return nameClassBuilder.makeNsName(getNs(), except, null, null);
}
@Override
int getContext() {
return NS_NAME_CONTEXT;
}
}
class NameClassChoiceState extends NameClassContainerState {
private ParsedNameClass[] nameClasses;
private int nNameClasses;
private int context;
NameClassChoiceState() {
this.context = PATTERN_CONTEXT;
}
NameClassChoiceState(int context) {
this.context = context;
}
@Override
void setParent(State parent) {
super.setParent(parent);
if (parent instanceof NameClassChoiceState) {
this.context = ((NameClassChoiceState) parent).context;
}
}
State create() {
return new NameClassChoiceState();
}
@Override
State createChildState(String localName) throws SAXException {
if (localName.equals("anyName")) {
if (context >= ANY_NAME_CONTEXT) {
error(context == ANY_NAME_CONTEXT
? "any_name_except_contains_any_name"
: "ns_name_except_contains_any_name");
return null;
}
} else if (localName.equals("nsName")) {
if (context == NS_NAME_CONTEXT) {
error("ns_name_except_contains_ns_name");
return null;
}
}
return super.createChildState(localName);
}
@Override
void endChild(ParsedNameClass nc) {
if (nameClasses == null) {
nameClasses = new ParsedNameClass[INIT_CHILD_ALLOC];
} else if (nNameClasses >= nameClasses.length) {
ParsedNameClass[] newNameClasses = new ParsedNameClass[nameClasses.length * 2];
System.arraycopy(nameClasses, 0, newNameClasses, 0, nameClasses.length);
nameClasses = newNameClasses;
}
nameClasses[nNameClasses++] = nc;
}
@Override
void endForeignChild(ParsedElementAnnotation ea) {
if (nNameClasses == 0) {
super.endForeignChild(ea);
} else {
nameClasses[nNameClasses - 1] = nameClassBuilder.annotateAfter(nameClasses[nNameClasses - 1], ea);
}
}
void end() throws SAXException {
if (nNameClasses == 0) {
error("missing_name_class");
parent.endChild(nameClassBuilder.makeErrorNameClass());
return;
}
if (comments != null) {
nameClasses[nNameClasses - 1] = nameClassBuilder.commentAfter(nameClasses[nNameClasses - 1], comments);
comments = null;
}
parent.endChild(nameClassBuilder.makeChoice(Arrays.asList(nameClasses).subList(0, nNameClasses), startLocation, annotations));
}
}
private void initPatternTable() {
patternTable = new Hashtable();
patternTable.put("zeroOrMore", new ZeroOrMoreState());
patternTable.put("oneOrMore", new OneOrMoreState());
patternTable.put("optional", new OptionalState());
patternTable.put("list", new ListState());
patternTable.put("choice", new ChoiceState());
patternTable.put("interleave", new InterleaveState());
patternTable.put("group", new GroupState());
patternTable.put("mixed", new MixedState());
patternTable.put("element", new ElementState());
patternTable.put("attribute", new AttributeState());
patternTable.put("empty", new EmptyState());
patternTable.put("text", new TextState());
patternTable.put("value", new ValueState());
patternTable.put("data", new DataState());
patternTable.put("notAllowed", new NotAllowedState());
patternTable.put("grammar", new GrammarState());
patternTable.put("ref", new RefState());
patternTable.put("parentRef", new ParentRefState());
patternTable.put("externalRef", new ExternalRefState());
}
private void initNameClassTable() {
nameClassTable = new Hashtable();
nameClassTable.put("name", new NameState());
nameClassTable.put("anyName", new AnyNameState());
nameClassTable.put("nsName", new NsNameState());
nameClassTable.put("choice", new NameClassChoiceState());
}
public ParsedPattern getParsedPattern() throws IllegalSchemaException {
if (hadError) {
throw new IllegalSchemaException();
}
return startPattern;
}
private void error(String key) throws SAXException {
error(key, locator);
}
private void error(String key, String arg) throws SAXException {
error(key, arg, locator);
}
void error(String key, String arg1, String arg2) throws SAXException {
error(key, arg1, arg2, locator);
}
private void error(String key, Locator loc) throws SAXException {
error(new SAXParseException(localizer.message(key), loc));
}
private void error(String key, String arg, Locator loc) throws SAXException {
error(new SAXParseException(localizer.message(key, arg), loc));
}
private void error(String key, String arg1, String arg2, Locator loc)
throws SAXException {
error(new SAXParseException(localizer.message(key, arg1, arg2), loc));
}
private void error(SAXParseException e) throws SAXException {
hadError = true;
if (eh != null) {
eh.error(e);
}
}
void warning(String key) throws SAXException {
warning(key, locator);
}
private void warning(String key, String arg) throws SAXException {
warning(key, arg, locator);
}
private void warning(String key, String arg1, String arg2) throws SAXException {
warning(key, arg1, arg2, locator);
}
private void warning(String key, Locator loc) throws SAXException {
warning(new SAXParseException(localizer.message(key), loc));
}
private void warning(String key, String arg, Locator loc) throws SAXException {
warning(new SAXParseException(localizer.message(key, arg), loc));
}
private void warning(String key, String arg1, String arg2, Locator loc)
throws SAXException {
warning(new SAXParseException(localizer.message(key, arg1, arg2), loc));
}
private void warning(SAXParseException e) throws SAXException {
if (eh != null) {
eh.warning(e);
}
}
SchemaParser(SAXParseable parseable,
XMLReader xr,
ErrorHandler eh,
SchemaBuilder schemaBuilder,
IncludedGrammar grammar,
Scope scope,
String inheritedNs) throws SAXException {
this.parseable = parseable;
this.xr = xr;
this.eh = eh;
this.schemaBuilder = schemaBuilder;
this.nameClassBuilder = schemaBuilder.getNameClassBuilder();
if (eh != null) {
xr.setErrorHandler(eh);
}
xr.setDTDHandler(context);
if (schemaBuilder.usesComments()) {
try {
xr.setProperty("http://xml.org/sax/properties/lexical-handler", new LexicalHandlerImpl());
} catch (SAXNotRecognizedException e) {
warning("no_comment_support", xr.getClass().getName());
} catch (SAXNotSupportedException e) {
warning("no_comment_support", xr.getClass().getName());
}
}
initPatternTable();
initNameClassTable();
new RootState(grammar, scope, inheritedNs).set();
}
private Context getContext() {
return context;
}
class LexicalHandlerImpl extends AbstractLexicalHandler {
private boolean inDtd = false;
@Override
public void startDTD(String s, String s1, String s2) throws SAXException {
inDtd = true;
}
@Override
public void endDTD() throws SAXException {
inDtd = false;
}
@Override
public void comment(char[] chars, int start, int length) throws SAXException {
if (!inDtd) {
((CommentHandler) xr.getContentHandler()).comment(new String(chars, start, length));
}
}
}
private ParsedNameClass expandName(String name, String ns, Annotations anno) throws SAXException {
int ic = name.indexOf(':');
if (ic == -1) {
return nameClassBuilder.makeName(ns, checkNCName(name), null, null, anno);
}
String prefix = checkNCName(name.substring(0, ic));
String localName = checkNCName(name.substring(ic + 1));
for (PrefixMapping tem = context.prefixMapping; tem != null; tem = tem.next) {
if (tem.prefix.equals(prefix)) {
return nameClassBuilder.makeName(tem.uri, localName, prefix, null, anno);
}
}
error("undefined_prefix", prefix);
return nameClassBuilder.makeName("", localName, null, null, anno);
}
private String findPrefix(String qName, String uri) {
String prefix = null;
if (qName == null || qName.equals("")) {
for (PrefixMapping p = context.prefixMapping; p != null; p = p.next) {
if (p.uri.equals(uri)) {
prefix = p.prefix;
break;
}
}
} else {
int off = qName.indexOf(':');
if (off > 0) {
prefix = qName.substring(0, off);
}
}
return prefix;
}
private String checkNCName(String str) throws SAXException {
if (!Naming.isNcname(str)) {
error("invalid_ncname", str);
}
return str;
}
private String resolve(String systemId) throws SAXException {
if (Uri.hasFragmentId(systemId)) {
error("href_fragment_id");
}
systemId = Uri.escapeDisallowedChars(systemId);
return Uri.resolve(xmlBaseHandler.getBaseUri(), systemId);
}
private Location makeLocation() {
if (locator == null) {
return null;
}
return schemaBuilder.makeLocation(locator.getSystemId(),
locator.getLineNumber(),
locator.getColumnNumber());
}
private void checkUri(String s) throws SAXException {
if (!Uri.isValid(s)) {
error("invalid_uri", s);
}
}
}