org.apache.xerces.impl.XMLDTDScannerImpl Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.xerces.impl;
import java.io.IOException;
import org.apache.xerces.impl.msg.XMLMessageFormatter;
import org.apache.xerces.util.SymbolTable;
import org.apache.xerces.util.XMLChar;
import org.apache.xerces.util.XMLStringBuffer;
import org.apache.xerces.util.XMLSymbols;
import org.apache.xerces.xni.Augmentations;
import org.apache.xerces.xni.XMLDTDContentModelHandler;
import org.apache.xerces.xni.XMLDTDHandler;
import org.apache.xerces.xni.XMLResourceIdentifier;
import org.apache.xerces.xni.XMLString;
import org.apache.xerces.xni.XNIException;
import org.apache.xerces.xni.parser.XMLComponent;
import org.apache.xerces.xni.parser.XMLComponentManager;
import org.apache.xerces.xni.parser.XMLConfigurationException;
import org.apache.xerces.xni.parser.XMLDTDScanner;
import org.apache.xerces.xni.parser.XMLInputSource;
/**
* This class is responsible for scanning the declarations found
* in the internal and external subsets of a DTD in an XML document.
* The scanner acts as the sources for the DTD information which is
* communicated to the DTD handlers.
*
* This component requires the following features and properties from the
* component manager that uses it:
*
* - http://xml.org/sax/features/validation
* - http://apache.org/xml/features/scanner/notify-char-refs
* - http://apache.org/xml/properties/internal/symbol-table
* - http://apache.org/xml/properties/internal/error-reporter
* - http://apache.org/xml/properties/internal/entity-manager
*
*
* @xerces.internal
*
* @author Arnaud Le Hors, IBM
* @author Andy Clark, IBM
* @author Glenn Marcy, IBM
* @author Eric Ye, IBM
*
* @version $Id: XMLDTDScannerImpl.java 965250 2010-07-18 16:04:58Z mrglavas $
*/
public class XMLDTDScannerImpl
extends XMLScanner
implements XMLDTDScanner, XMLComponent, XMLEntityHandler {
//
// Constants
//
// scanner states
/** Scanner state: end of input. */
protected static final int SCANNER_STATE_END_OF_INPUT = 0;
/** Scanner state: text declaration. */
protected static final int SCANNER_STATE_TEXT_DECL = 1;
/** Scanner state: markup declaration. */
protected static final int SCANNER_STATE_MARKUP_DECL = 2;
// recognized features and properties
/** Recognized features. */
private static final String[] RECOGNIZED_FEATURES = {
VALIDATION,
NOTIFY_CHAR_REFS,
};
/** Feature defaults. */
private static final Boolean[] FEATURE_DEFAULTS = {
null,
Boolean.FALSE,
};
/** Recognized properties. */
private static final String[] RECOGNIZED_PROPERTIES = {
SYMBOL_TABLE,
ERROR_REPORTER,
ENTITY_MANAGER,
};
/** Property defaults. */
private static final Object[] PROPERTY_DEFAULTS = {
null,
null,
null,
};
// debugging
/** Debug scanner state. */
private static final boolean DEBUG_SCANNER_STATE = false;
//
// Data
//
// handlers
/** DTD handler. */
protected XMLDTDHandler fDTDHandler;
/** DTD content model handler. */
protected XMLDTDContentModelHandler fDTDContentModelHandler;
// state
/** Scanner state. */
protected int fScannerState;
/** Standalone. */
protected boolean fStandalone;
/** Seen external DTD. */
protected boolean fSeenExternalDTD;
/** Seen a parameter entity reference. */
protected boolean fSeenPEReferences;
// private data
/** Start DTD called. */
private boolean fStartDTDCalled;
/**
* Stack of content operators (either '|' or ',') in children
* content.
*/
private int[] fContentStack = new int[5];
/** Size of content stack. */
private int fContentDepth;
/** Parameter entity stack to check well-formedness. */
private int[] fPEStack = new int[5];
/** Parameter entity stack to report start/end entity calls. */
private boolean[] fPEReport = new boolean[5];
/** Number of opened parameter entities. */
private int fPEDepth;
/** Markup depth. */
private int fMarkUpDepth;
/** Number of opened external entities. */
private int fExtEntityDepth;
/** Number of opened include sections. */
private int fIncludeSectDepth;
// temporary variables
/** Array of 3 strings. */
private final String[] fStrings = new String[3];
/** String. */
private final XMLString fString = new XMLString();
/** String buffer. */
private final XMLStringBuffer fStringBuffer = new XMLStringBuffer();
/** String buffer. */
private final XMLStringBuffer fStringBuffer2 = new XMLStringBuffer();
/** Literal text. */
private final XMLString fLiteral = new XMLString();
/** Literal text. */
private final XMLString fLiteral2 = new XMLString();
/** Enumeration values. */
private String[] fEnumeration = new String[5];
/** Enumeration values count. */
private int fEnumerationCount;
/** Ignore conditional section buffer. */
private final XMLStringBuffer fIgnoreConditionalBuffer = new XMLStringBuffer(128);
//
// Constructors
//
/** Default constructor. */
public XMLDTDScannerImpl() {} // ()
/** Constructor for he use of non-XMLComponentManagers. */
public XMLDTDScannerImpl(SymbolTable symbolTable,
XMLErrorReporter errorReporter, XMLEntityManager entityManager) {
fSymbolTable = symbolTable;
fErrorReporter = errorReporter;
fEntityManager = entityManager;
entityManager.setProperty(SYMBOL_TABLE, fSymbolTable);
}
//
// XMLDTDScanner methods
//
/**
* Sets the input source.
*
* @param inputSource The input source or null.
*
* @throws IOException Thrown on i/o error.
*/
public void setInputSource(XMLInputSource inputSource) throws IOException {
if (inputSource == null) {
// no system id was available
if (fDTDHandler != null) {
fDTDHandler.startDTD(null, null);
fDTDHandler.endDTD(null);
}
return;
}
fEntityManager.setEntityHandler(this);
fEntityManager.startDTDEntity(inputSource);
} // setInputSource(XMLInputSource)
/**
* Scans the external subset of the document.
*
* @param complete True if the scanner should scan the document
* completely, pushing all events to the registered
* document handler. A value of false indicates that
* that the scanner should only scan the next portion
* of the document and return. A scanner instance is
* permitted to completely scan a document if it does
* not support this "pull" scanning model.
*
* @return True if there is more to scan, false otherwise.
*/
public boolean scanDTDExternalSubset(boolean complete)
throws IOException, XNIException {
fEntityManager.setEntityHandler(this);
if (fScannerState == SCANNER_STATE_TEXT_DECL) {
fSeenExternalDTD = true;
boolean textDecl = scanTextDecl();
if (fScannerState == SCANNER_STATE_END_OF_INPUT) {
return false;
}
else {
// next state is markup decls regardless of whether there
// is a TextDecl or not
setScannerState(SCANNER_STATE_MARKUP_DECL);
if (textDecl && !complete) {
return true;
}
}
}
// keep dispatching "events"
do {
if (!scanDecls(complete)) {
return false;
}
} while (complete);
// return that there is more to scan
return true;
} // scanDTDExternalSubset(boolean):boolean
/**
* Scans the internal subset of the document.
*
* @param complete True if the scanner should scan the document
* completely, pushing all events to the registered
* document handler. A value of false indicates that
* that the scanner should only scan the next portion
* of the document and return. A scanner instance is
* permitted to completely scan a document if it does
* not support this "pull" scanning model.
* @param standalone True if the document was specified as standalone.
* This value is important for verifying certain
* well-formedness constraints.
* @param hasExternalSubset True if the document has an external DTD.
* This allows the scanner to properly notify
* the handler of the end of the DTD in the
* absence of an external subset.
*
* @return True if there is more to scan, false otherwise.
*/
public boolean scanDTDInternalSubset(boolean complete, boolean standalone,
boolean hasExternalSubset)
throws IOException, XNIException {
// reset entity scanner
fEntityScanner = fEntityManager.getEntityScanner();
fEntityManager.setEntityHandler(this);
fStandalone = standalone;
if (fScannerState == SCANNER_STATE_TEXT_DECL) {
// call handler
if (fDTDHandler != null) {
fDTDHandler.startDTD(fEntityScanner, null);
fStartDTDCalled = true;
}
// set starting state for internal subset
setScannerState(SCANNER_STATE_MARKUP_DECL);
}
// keep dispatching "events"
do {
if (!scanDecls(complete)) {
// call handler
if (fDTDHandler != null && hasExternalSubset == false) {
fDTDHandler.endDTD(null);
}
// we're done, set starting state for external subset
setScannerState(SCANNER_STATE_TEXT_DECL);
return false;
}
} while (complete);
// return that there is more to scan
return true;
} // scanDTDInternalSubset(boolean,boolean,boolean):boolean
//
// XMLComponent methods
//
/**
* reset
*
* @param componentManager
*/
public void reset(XMLComponentManager componentManager)
throws XMLConfigurationException {
super.reset(componentManager);
init();
} // reset(XMLComponentManager)
// this is made for something like XMLDTDLoader--XMLComponentManager-free operation...
public void reset() {
super.reset();
init();
}
/**
* Returns a list of feature identifiers that are recognized by
* this component. This method may return null if no features
* are recognized by this component.
*/
public String[] getRecognizedFeatures() {
return (String[])(RECOGNIZED_FEATURES.clone());
} // getRecognizedFeatures():String[]
/**
* Returns a list of property identifiers that are recognized by
* this component. This method may return null if no properties
* are recognized by this component.
*/
public String[] getRecognizedProperties() {
return (String[])(RECOGNIZED_PROPERTIES.clone());
} // getRecognizedProperties():String[]
/**
* Returns the default state for a feature, or null if this
* component does not want to report a default value for this
* feature.
*
* @param featureId The feature identifier.
*
* @since Xerces 2.2.0
*/
public Boolean getFeatureDefault(String featureId) {
for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
if (RECOGNIZED_FEATURES[i].equals(featureId)) {
return FEATURE_DEFAULTS[i];
}
}
return null;
} // getFeatureDefault(String):Boolean
/**
* Returns the default state for a property, or null if this
* component does not want to report a default value for this
* property.
*
* @param propertyId The property identifier.
*
* @since Xerces 2.2.0
*/
public Object getPropertyDefault(String propertyId) {
for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
return PROPERTY_DEFAULTS[i];
}
}
return null;
} // getPropertyDefault(String):Object
//
// XMLDTDSource methods
//
/**
* setDTDHandler
*
* @param dtdHandler
*/
public void setDTDHandler(XMLDTDHandler dtdHandler) {
fDTDHandler = dtdHandler;
} // setDTDHandler(XMLDTDHandler)
/**
* getDTDHandler
*
* @return the XMLDTDHandler
*/
public XMLDTDHandler getDTDHandler() {
return fDTDHandler;
} // getDTDHandler(): XMLDTDHandler
//
// XMLDTDContentModelSource methods
//
/**
* setDTDContentModelHandler
*
* @param dtdContentModelHandler
*/
public void setDTDContentModelHandler(XMLDTDContentModelHandler
dtdContentModelHandler) {
fDTDContentModelHandler = dtdContentModelHandler;
} // setDTDContentModelHandler
/**
* getDTDContentModelHandler
*
* @return XMLDTDContentModelHandler
*/
public XMLDTDContentModelHandler getDTDContentModelHandler() {
return fDTDContentModelHandler ;
} // setDTDContentModelHandler
//
// XMLEntityHandler methods
//
/**
* This method notifies of the start of an entity. The DTD has the
* pseudo-name of "[dtd]" parameter entity names start with '%'; and
* general entities are just specified by their name.
*
* @param name The name of the entity.
* @param identifier The resource identifier.
* @param encoding The auto-detected IANA encoding name of the entity
* stream. This value will be null in those situations
* where the entity encoding is not auto-detected (e.g.
* internal entities or a document entity that is
* parsed from a java.io.Reader).
* @param augs Additional information that may include infoset augmentations
*
* @throws XNIException Thrown by handler to signal an error.
*/
public void startEntity(String name,
XMLResourceIdentifier identifier,
String encoding, Augmentations augs) throws XNIException {
super.startEntity(name, identifier, encoding, augs);
boolean dtdEntity = name.equals("[dtd]");
if (dtdEntity) {
// call handler
if (fDTDHandler != null && !fStartDTDCalled ) {
fDTDHandler.startDTD(fEntityScanner, null);
}
if (fDTDHandler != null) {
fDTDHandler.startExternalSubset(identifier,null);
}
fEntityManager.startExternalSubset();
fExtEntityDepth++;
}
else if (name.charAt(0) == '%') {
pushPEStack(fMarkUpDepth, fReportEntity);
if (fEntityScanner.isExternal()) {
fExtEntityDepth++;
}
}
// call handler
if (fDTDHandler != null && !dtdEntity && fReportEntity) {
fDTDHandler.startParameterEntity(name, identifier, encoding, augs);
}
} // startEntity(String,XMLResourceIdentifier,String)
/**
* This method notifies the end of an entity. The DTD has the pseudo-name
* of "[dtd]" parameter entity names start with '%'; and general entities
* are just specified by their name.
*
* @param name The name of the entity.
* @param augs Additional information that may include infoset augmentations
*
* @throws XNIException Thrown by handler to signal an error.
*/
public void endEntity(String name, Augmentations augs)
throws XNIException {
super.endEntity(name, augs);
// if there is no data after the doctype
//
if (fScannerState == SCANNER_STATE_END_OF_INPUT)
return;
// Handle end of PE
boolean reportEntity = fReportEntity;
if (name.startsWith("%")) {
reportEntity = peekReportEntity();
// check well-formedness of the enity
int startMarkUpDepth = popPEStack();
// throw fatalError if this entity was incomplete and
// was a freestanding decl
if(startMarkUpDepth == 0 &&
startMarkUpDepth < fMarkUpDepth) {
fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
"ILL_FORMED_PARAMETER_ENTITY_WHEN_USED_IN_DECL",
new Object[]{ fEntityManager.fCurrentEntity.name},
XMLErrorReporter.SEVERITY_FATAL_ERROR);
}
if (startMarkUpDepth != fMarkUpDepth) {
reportEntity = false;
if (fValidation) {
// Proper nesting of parameter entities is a Validity Constraint
// and must not be enforced when validation is off
fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
"ImproperDeclarationNesting",
new Object[]{ name },
XMLErrorReporter.SEVERITY_ERROR);
}
}
if (fEntityScanner.isExternal()) {
fExtEntityDepth--;
}
// call handler
if (fDTDHandler != null && reportEntity) {
fDTDHandler.endParameterEntity(name, augs);
}
}
// end DTD
else if (name.equals("[dtd]")) {
if (fIncludeSectDepth != 0) {
reportFatalError("IncludeSectUnterminated", null);
}
fScannerState = SCANNER_STATE_END_OF_INPUT;
// call handler
fEntityManager.endExternalSubset();
if (fDTDHandler != null) {
fDTDHandler.endExternalSubset(null);
fDTDHandler.endDTD(null);
}
fExtEntityDepth--;
}
} // endEntity(String)
// helper methods
/**
* Sets the scanner state.
*
* @param state The new scanner state.
*/
protected final void setScannerState(int state) {
fScannerState = state;
if (DEBUG_SCANNER_STATE) {
System.out.print("### setScannerState: ");
System.out.print(getScannerStateName(state));
System.out.println();
}
} // setScannerState(int)
//
// Private methods
//
/** Returns the scanner state name. */
private static String getScannerStateName(int state) {
if (DEBUG_SCANNER_STATE) {
switch (state) {
case SCANNER_STATE_END_OF_INPUT: return "SCANNER_STATE_END_OF_INPUT";
case SCANNER_STATE_TEXT_DECL: return "SCANNER_STATE_TEXT_DECL";
case SCANNER_STATE_MARKUP_DECL: return "SCANNER_STATE_MARKUP_DECL";
}
}
return "??? ("+state+')';
} // getScannerStateName(int):String
protected final boolean scanningInternalSubset() {
return fExtEntityDepth == 0;
}
/**
* start a parameter entity dealing with the textdecl if there is any
*
* @param name The name of the parameter entity to start (without the '%')
* @param literal Whether this is happening within a literal
*/
protected void startPE(String name, boolean literal)
throws IOException, XNIException {
int depth = fPEDepth;
String pName = "%"+name;
if (!fSeenPEReferences) {
fSeenPEReferences = true;
fEntityManager.notifyHasPEReferences();
}
if (fValidation && !fEntityManager.isDeclaredEntity(pName)) {
fErrorReporter.reportError( XMLMessageFormatter.XML_DOMAIN,"EntityNotDeclared",
new Object[]{name}, XMLErrorReporter.SEVERITY_ERROR);
}
fEntityManager.startEntity(fSymbolTable.addSymbol(pName),
literal);
// if we actually got a new entity and it's external
// parse text decl if there is any
if (depth != fPEDepth && fEntityScanner.isExternal()) {
scanTextDecl();
}
}
/**
* Dispatch an XML "event".
*
* @return true if a TextDecl was scanned.
*
* @throws IOException Thrown on i/o error.
* @throws XNIException Thrown on parse error.
*
*/
protected final boolean scanTextDecl()
throws IOException, XNIException {
// scan XMLDecl
boolean textDecl = false;
if (fEntityScanner.skipString("starts with "xml". (e.g. xmlfoo)
*
* @param target The PI target
* @param data The string to fill in with the data
*/
protected final void scanPIData(String target, XMLString data)
throws IOException, XNIException {
super.scanPIData(target, data);
fMarkUpDepth--;
// call handler
if (fDTDHandler != null) {
fDTDHandler.processingInstruction(target, data, null);
}
} // scanPIData(String)
/**
* Scans a comment.
*
*
* [15] Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->'
*
*
* Note: Called after scanning past '<!--'
*/
protected final void scanComment() throws IOException, XNIException {
fReportEntity = false;
scanComment(fStringBuffer);
fMarkUpDepth--;
// call handler
if (fDTDHandler != null) {
fDTDHandler.comment(fStringBuffer, null);
}
fReportEntity = true;
} // scanComment()
/**
* Scans an element declaration
*
*
* [45] elementdecl ::= '<!ELEMENT' S Name S contentspec S? '>'
* [46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | children
*
*
* Note: Called after scanning past '<!ELEMENT'
*/
protected final void scanElementDecl() throws IOException, XNIException {
// spaces
fReportEntity = false;
if (!skipSeparator(true, !scanningInternalSubset())) {
reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ELEMENT_TYPE_IN_ELEMENTDECL",
null);
}
// element name
String name = fEntityScanner.scanName();
if (name == null) {
reportFatalError("MSG_ELEMENT_TYPE_REQUIRED_IN_ELEMENTDECL",
null);
}
// spaces
if (!skipSeparator(true, !scanningInternalSubset())) {
reportFatalError("MSG_SPACE_REQUIRED_BEFORE_CONTENTSPEC_IN_ELEMENTDECL",
new Object[]{name});
}
// content model
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.startContentModel(name, null);
}
String contentModel = null;
fReportEntity = true;
if (fEntityScanner.skipString("EMPTY")) {
contentModel = "EMPTY";
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.empty(null);
}
}
else if (fEntityScanner.skipString("ANY")) {
contentModel = "ANY";
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.any(null);
}
}
else {
if (!fEntityScanner.skipChar('(')) {
reportFatalError("MSG_OPEN_PAREN_OR_ELEMENT_TYPE_REQUIRED_IN_CHILDREN",
new Object[]{name});
}
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.startGroup(null);
}
fStringBuffer.clear();
fStringBuffer.append('(');
fMarkUpDepth++;
skipSeparator(false, !scanningInternalSubset());
// Mixed content model
if (fEntityScanner.skipString("#PCDATA")) {
scanMixed(name);
}
else { // children content
scanChildren(name);
}
contentModel = fStringBuffer.toString();
}
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.endContentModel(null);
}
fReportEntity = false;
skipSeparator(false, !scanningInternalSubset());
// end
if (!fEntityScanner.skipChar('>')) {
reportFatalError("ElementDeclUnterminated", new Object[]{name});
}
fReportEntity = true;
fMarkUpDepth--;
// call handler
if (fDTDHandler != null) {
fDTDHandler.elementDecl(name, contentModel, null);
}
} // scanElementDecl()
/**
* scan Mixed content model
* This assumes the content model has been parsed up to #PCDATA and
* can simply append to fStringBuffer.
*
* [51] Mixed ::= '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*'
* | '(' S? '#PCDATA' S? ')'
*
*
* @param elName The element type name this declaration is about.
*
* Note: Called after scanning past '(#PCDATA'.
*/
private final void scanMixed(String elName)
throws IOException, XNIException {
String childName = null;
fStringBuffer.append("#PCDATA");
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.pcdata(null);
}
skipSeparator(false, !scanningInternalSubset());
while (fEntityScanner.skipChar('|')) {
fStringBuffer.append('|');
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.separator(XMLDTDContentModelHandler.SEPARATOR_CHOICE,
null);
}
skipSeparator(false, !scanningInternalSubset());
childName = fEntityScanner.scanName();
if (childName == null) {
reportFatalError("MSG_ELEMENT_TYPE_REQUIRED_IN_MIXED_CONTENT",
new Object[]{elName});
}
fStringBuffer.append(childName);
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.element(childName, null);
}
skipSeparator(false, !scanningInternalSubset());
}
// The following check must be done in a single call (as opposed to one
// for ')' and then one for '*') to guarantee that callbacks are
// properly nested. We do not want to trigger endEntity too early in
// case we cross the boundary of an entity between the two characters.
if (fEntityScanner.skipString(")*")) {
fStringBuffer.append(")*");
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.endGroup(null);
fDTDContentModelHandler.occurrence(XMLDTDContentModelHandler.OCCURS_ZERO_OR_MORE,
null);
}
}
else if (childName != null) {
reportFatalError("MixedContentUnterminated",
new Object[]{elName});
}
else if (fEntityScanner.skipChar(')')){
fStringBuffer.append(')');
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.endGroup(null);
}
}
else {
reportFatalError("MSG_CLOSE_PAREN_REQUIRED_IN_CHILDREN",
new Object[]{elName});
}
fMarkUpDepth--;
// we are done
}
/**
* scan children content model
* This assumes it can simply append to fStringBuffer.
*
* [47] children ::= (choice | seq) ('?' | '*' | '+')?
* [48] cp ::= (Name | choice | seq) ('?' | '*' | '+')?
* [49] choice ::= '(' S? cp ( S? '|' S? cp )+ S? ')'
* [50] seq ::= '(' S? cp ( S? ',' S? cp )* S? ')'
*
*
* @param elName The element type name this declaration is about.
*
* Note: Called after scanning past the first open
* paranthesis.
*/
private final void scanChildren(String elName)
throws IOException, XNIException {
fContentDepth = 0;
pushContentStack(0);
int currentOp = 0;
int c;
while (true) {
if (fEntityScanner.skipChar('(')) {
fMarkUpDepth++;
fStringBuffer.append('(');
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.startGroup(null);
}
// push current op on stack and reset it
pushContentStack(currentOp);
currentOp = 0;
skipSeparator(false, !scanningInternalSubset());
continue;
}
skipSeparator(false, !scanningInternalSubset());
String childName = fEntityScanner.scanName();
if (childName == null) {
reportFatalError("MSG_OPEN_PAREN_OR_ELEMENT_TYPE_REQUIRED_IN_CHILDREN",
new Object[]{elName});
return;
}
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.element(childName, null);
}
fStringBuffer.append(childName);
c = fEntityScanner.peekChar();
if (c == '?' || c == '*' || c == '+') {
// call handler
if (fDTDContentModelHandler != null) {
short oc;
if (c == '?') {
oc = XMLDTDContentModelHandler.OCCURS_ZERO_OR_ONE;
}
else if (c == '*') {
oc = XMLDTDContentModelHandler.OCCURS_ZERO_OR_MORE;
}
else {
oc = XMLDTDContentModelHandler.OCCURS_ONE_OR_MORE;
}
fDTDContentModelHandler.occurrence(oc, null);
}
fEntityScanner.scanChar();
fStringBuffer.append((char)c);
}
while (true) {
skipSeparator(false, !scanningInternalSubset());
c = fEntityScanner.peekChar();
if (c == ',' && currentOp != '|') {
currentOp = c;
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.separator(XMLDTDContentModelHandler.SEPARATOR_SEQUENCE,
null);
}
fEntityScanner.scanChar();
fStringBuffer.append(',');
break;
}
else if (c == '|' && currentOp != ',') {
currentOp = c;
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.separator(XMLDTDContentModelHandler.SEPARATOR_CHOICE,
null);
}
fEntityScanner.scanChar();
fStringBuffer.append('|');
break;
}
else if (c != ')') {
reportFatalError("MSG_CLOSE_PAREN_REQUIRED_IN_CHILDREN",
new Object[]{elName});
}
// call handler
if (fDTDContentModelHandler != null) {
fDTDContentModelHandler.endGroup(null);
}
// restore previous op
currentOp = popContentStack();
short oc;
// The following checks must be done in a single call (as
// opposed to one for ')' and then one for '?', '*', and '+')
// to guarantee that callbacks are properly nested. We do not
// want to trigger endEntity too early in case we cross the
// boundary of an entity between the two characters.
if (fEntityScanner.skipString(")?")) {
fStringBuffer.append(")?");
// call handler
if (fDTDContentModelHandler != null) {
oc = XMLDTDContentModelHandler.OCCURS_ZERO_OR_ONE;
fDTDContentModelHandler.occurrence(oc, null);
}
}
else if (fEntityScanner.skipString(")+")) {
fStringBuffer.append(")+");
// call handler
if (fDTDContentModelHandler != null) {
oc = XMLDTDContentModelHandler.OCCURS_ONE_OR_MORE;
fDTDContentModelHandler.occurrence(oc, null);
}
}
else if (fEntityScanner.skipString(")*")) {
fStringBuffer.append(")*");
// call handler
if (fDTDContentModelHandler != null) {
oc = XMLDTDContentModelHandler.OCCURS_ZERO_OR_MORE;
fDTDContentModelHandler.occurrence(oc, null);
}
}
else {
// no occurrence specified
fEntityScanner.scanChar();
fStringBuffer.append(')');
}
fMarkUpDepth--;
if (fContentDepth == 0) {
return;
}
}
skipSeparator(false, !scanningInternalSubset());
}
}
/**
* Scans an attlist declaration
*
*
* [52] AttlistDecl ::= '<!ATTLIST' S Name AttDef* S? '>'
* [53] AttDef ::= S Name S AttType S DefaultDecl
*
*
* Note: Called after scanning past '<!ATTLIST'
*/
protected final void scanAttlistDecl() throws IOException, XNIException {
// spaces
fReportEntity = false;
if (!skipSeparator(true, !scanningInternalSubset())) {
reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ELEMENT_TYPE_IN_ATTLISTDECL",
null);
}
// element name
String elName = fEntityScanner.scanName();
if (elName == null) {
reportFatalError("MSG_ELEMENT_TYPE_REQUIRED_IN_ATTLISTDECL",
null);
}
// call handler
if (fDTDHandler != null) {
fDTDHandler.startAttlist(elName, null);
}
// spaces
if (!skipSeparator(true, !scanningInternalSubset())) {
// no space, is it the end yet?
if (fEntityScanner.skipChar('>')) {
// yes, stop here
// call handler
if (fDTDHandler != null) {
fDTDHandler.endAttlist(null);
}
fMarkUpDepth--;
return;
}
else {
reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ATTRIBUTE_NAME_IN_ATTDEF",
new Object[]{elName});
}
}
// definitions
while (!fEntityScanner.skipChar('>')) {
String name = fEntityScanner.scanName();
if (name == null) {
reportFatalError("AttNameRequiredInAttDef",
new Object[]{elName});
}
// spaces
if (!skipSeparator(true, !scanningInternalSubset())) {
reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ATTTYPE_IN_ATTDEF",
new Object[]{elName, name});
}
// type
String type = scanAttType(elName, name);
// spaces
if (!skipSeparator(true, !scanningInternalSubset())) {
reportFatalError("MSG_SPACE_REQUIRED_BEFORE_DEFAULTDECL_IN_ATTDEF",
new Object[]{elName, name});
}
// default decl
String defaultType = scanAttDefaultDecl(elName, name,
type,
fLiteral, fLiteral2);
// REVISIT: Should we do anything with the non-normalized
// default attribute value? -Ac
// yes--according to bug 5073. - neilg
// call handler
if (fDTDHandler != null) {
String[] enumeration = null;
if (fEnumerationCount != 0) {
enumeration = new String[fEnumerationCount];
System.arraycopy(fEnumeration, 0, enumeration,
0, fEnumerationCount);
}
// Determine whether the default value to be passed should be null.
// REVISIT: should probably check whether fLiteral.ch is null instead. LM.
if (defaultType!=null && (defaultType.equals("#REQUIRED") ||
defaultType.equals("#IMPLIED"))) {
fDTDHandler.attributeDecl(elName, name, type, enumeration,
defaultType, null, null, null);
}
else {
fDTDHandler.attributeDecl(elName, name, type, enumeration,
defaultType, fLiteral, fLiteral2, null);
}
}
skipSeparator(false, !scanningInternalSubset());
}
// call handler
if (fDTDHandler != null) {
fDTDHandler.endAttlist(null);
}
fMarkUpDepth--;
fReportEntity = true;
} // scanAttlistDecl()
/**
* Scans an attribute type definition
*
*
* [54] AttType ::= StringType | TokenizedType | EnumeratedType
* [55] StringType ::= 'CDATA'
* [56] TokenizedType ::= 'ID'
* | 'IDREF'
* | 'IDREFS'
* | 'ENTITY'
* | 'ENTITIES'
* | 'NMTOKEN'
* | 'NMTOKENS'
* [57] EnumeratedType ::= NotationType | Enumeration
* [58] NotationType ::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')'
* [59] Enumeration ::= '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')'
*
*
* Note: Called after scanning past '<!ATTLIST'
*
* @param elName The element type name this declaration is about.
* @param atName The attribute name this declaration is about.
*/
private final String scanAttType(String elName, String atName)
throws IOException, XNIException {
String type = null;
fEnumerationCount = 0;
/*
* Watchout: the order here is important: when a string happens to
* be a substring of another string, the longer one needs to be
* looked for first!!
*/
if (fEntityScanner.skipString("CDATA")) {
type = "CDATA";
}
else if (fEntityScanner.skipString("IDREFS")) {
type = "IDREFS";
}
else if (fEntityScanner.skipString("IDREF")) {
type = "IDREF";
}
else if (fEntityScanner.skipString("ID")) {
type = "ID";
}
else if (fEntityScanner.skipString("ENTITY")) {
type = "ENTITY";
}
else if (fEntityScanner.skipString("ENTITIES")) {
type = "ENTITIES";
}
else if (fEntityScanner.skipString("NMTOKENS")) {
type = "NMTOKENS";
}
else if (fEntityScanner.skipString("NMTOKEN")) {
type = "NMTOKEN";
}
else if (fEntityScanner.skipString("NOTATION")) {
type = "NOTATION";
// spaces
if (!skipSeparator(true, !scanningInternalSubset())) {
reportFatalError("MSG_SPACE_REQUIRED_AFTER_NOTATION_IN_NOTATIONTYPE",
new Object[]{elName, atName});
}
// open paren
int c = fEntityScanner.scanChar();
if (c != '(') {
reportFatalError("MSG_OPEN_PAREN_REQUIRED_IN_NOTATIONTYPE",
new Object[]{elName, atName});
}
fMarkUpDepth++;
do {
skipSeparator(false, !scanningInternalSubset());
String aName = fEntityScanner.scanName();
if (aName == null) {
reportFatalError("MSG_NAME_REQUIRED_IN_NOTATIONTYPE",
new Object[]{elName, atName});
c = skipInvalidEnumerationValue();
if (c == '|') {
continue;
}
break;
}
ensureEnumerationSize(fEnumerationCount + 1);
fEnumeration[fEnumerationCount++] = aName;
skipSeparator(false, !scanningInternalSubset());
c = fEntityScanner.scanChar();
} while (c == '|');
if (c != ')') {
reportFatalError("NotationTypeUnterminated",
new Object[]{elName, atName});
}
fMarkUpDepth--;
}
else { // Enumeration
type = "ENUMERATION";
// open paren
int c = fEntityScanner.scanChar();
if (c != '(') {
// "OPEN_PAREN_REQUIRED_BEFORE_ENUMERATION_IN_ATTRDECL",
reportFatalError("AttTypeRequiredInAttDef",
new Object[]{elName, atName});
}
fMarkUpDepth++;
do {
skipSeparator(false, !scanningInternalSubset());
String token = fEntityScanner.scanNmtoken();
if (token == null) {
reportFatalError("MSG_NMTOKEN_REQUIRED_IN_ENUMERATION",
new Object[]{elName, atName});
c = skipInvalidEnumerationValue();
if (c == '|') {
continue;
}
break;
}
ensureEnumerationSize(fEnumerationCount + 1);
fEnumeration[fEnumerationCount++] = token;
skipSeparator(false, !scanningInternalSubset());
c = fEntityScanner.scanChar();
} while (c == '|');
if (c != ')') {
reportFatalError("EnumerationUnterminated",
new Object[]{elName, atName});
}
fMarkUpDepth--;
}
return type;
} // scanAttType():String
/**
* Scans an attribute default declaration
*
*
* [60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED' | (('#FIXED' S)? AttValue)
*
*
* @param elName
* @param atName The name of the attribute being scanned.
* @param type
* @param defaultVal The string to fill in with the default value.
* @param nonNormalizedDefaultVal
*/
protected final String scanAttDefaultDecl(String elName, String atName,
String type,
XMLString defaultVal,
XMLString nonNormalizedDefaultVal)
throws IOException, XNIException {
String defaultType = null;
fString.clear();
defaultVal.clear();
if (fEntityScanner.skipString("#REQUIRED")) {
defaultType = "#REQUIRED";
}
else if (fEntityScanner.skipString("#IMPLIED")) {
defaultType = "#IMPLIED";
}
else {
if (fEntityScanner.skipString("#FIXED")) {
defaultType = "#FIXED";
// spaces
if (!skipSeparator(true, !scanningInternalSubset())) {
reportFatalError("MSG_SPACE_REQUIRED_AFTER_FIXED_IN_DEFAULTDECL",
new Object[]{elName, atName});
}
}
// AttValue
boolean isVC = !fStandalone && (fSeenExternalDTD || fSeenPEReferences);
scanAttributeValue(defaultVal, nonNormalizedDefaultVal, atName, isVC, elName);
}
return defaultType;
} // ScanAttDefaultDecl
/**
* Scans an entity declaration
*
*
* [70] EntityDecl ::= GEDecl | PEDecl
* [71] GEDecl ::= '<!ENTITY' S Name S EntityDef S? '>'
* [72] PEDecl ::= '<!ENTITY' S '%' S Name S PEDef S? '>'
* [73] EntityDef ::= EntityValue | (ExternalID NDataDecl?)
* [74] PEDef ::= EntityValue | ExternalID
* [75] ExternalID ::= 'SYSTEM' S SystemLiteral
* | 'PUBLIC' S PubidLiteral S SystemLiteral
* [76] NDataDecl ::= S 'NDATA' S Name
*
*
* Note: Called after scanning past '<!ENTITY'
*/
private final void scanEntityDecl() throws IOException, XNIException {
boolean isPEDecl = false;
boolean sawPERef = false;
fReportEntity = false;
if (fEntityScanner.skipSpaces()) {
if (!fEntityScanner.skipChar('%')) {
isPEDecl = false; //
}
else if (skipSeparator(true, !scanningInternalSubset())) {
//
isPEDecl = true;
}
else if (scanningInternalSubset()) {
reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ENTITY_NAME_IN_PEDECL",
null);
isPEDecl = true;
}
else if (fEntityScanner.peekChar() == '%') {
// is legal
skipSeparator(false, !scanningInternalSubset());
isPEDecl = true;
}
else {
sawPERef = true;
}
}
else if (scanningInternalSubset() || !fEntityScanner.skipChar('%')) {
// or
reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ENTITY_NAME_IN_ENTITYDECL",
null);
isPEDecl = false;
}
else if (fEntityScanner.skipSpaces()) {
//
reportFatalError("MSG_SPACE_REQUIRED_BEFORE_PERCENT_IN_PEDECL",
null);
isPEDecl = false;
}
else {
sawPERef = true;
}
if (sawPERef) {
while (true) {
String peName = fEntityScanner.scanName();
if (peName == null) {
reportFatalError("NameRequiredInPEReference", null);
}
else if (!fEntityScanner.skipChar(';')) {
reportFatalError("SemicolonRequiredInPEReference",
new Object[]{peName});
}
else {
startPE(peName, false);
}
fEntityScanner.skipSpaces();
if (!fEntityScanner.skipChar('%'))
break;
if (!isPEDecl) {
if (skipSeparator(true, !scanningInternalSubset())) {
isPEDecl = true;
break;
}
isPEDecl = fEntityScanner.skipChar('%');
}
}
}
// name
String name = null;
if(fNamespaces) {
name = fEntityScanner.scanNCName();
} else {
name = fEntityScanner.scanName();
}
if (name == null) {
reportFatalError("MSG_ENTITY_NAME_REQUIRED_IN_ENTITYDECL", null);
}
// spaces
if (!skipSeparator(true, !scanningInternalSubset())) {
if(fNamespaces && fEntityScanner.peekChar() == ':') {
fEntityScanner.scanChar();
XMLStringBuffer colonName = new XMLStringBuffer(name);
colonName.append(':');
String str = fEntityScanner.scanName();
if (str != null)
colonName.append(str);
reportFatalError("ColonNotLegalWithNS", new Object[] {colonName.toString()});
if (!skipSeparator(true, !scanningInternalSubset())) {
reportFatalError("MSG_SPACE_REQUIRED_AFTER_ENTITY_NAME_IN_ENTITYDECL",
new Object[]{name});
}
} else {
reportFatalError("MSG_SPACE_REQUIRED_AFTER_ENTITY_NAME_IN_ENTITYDECL",
new Object[]{name});
}
}
// external id
scanExternalID(fStrings, false);
String systemId = fStrings[0];
String publicId = fStrings[1];
String notation = null;
// NDATA
boolean sawSpace = skipSeparator(true, !scanningInternalSubset());
if (!isPEDecl && fEntityScanner.skipString("NDATA")) {
// check whether there was space before NDATA
if (!sawSpace) {
reportFatalError("MSG_SPACE_REQUIRED_BEFORE_NDATA_IN_UNPARSED_ENTITYDECL",
new Object[]{name});
}
// spaces
if (!skipSeparator(true, !scanningInternalSubset())) {
reportFatalError("MSG_SPACE_REQUIRED_BEFORE_NOTATION_NAME_IN_UNPARSED_ENTITYDECL",
new Object[]{name});
}
notation = fEntityScanner.scanName();
if (notation == null) {
reportFatalError("MSG_NOTATION_NAME_REQUIRED_FOR_UNPARSED_ENTITYDECL",
new Object[]{name});
}
}
// internal entity
if (systemId == null) {
scanEntityValue(fLiteral, fLiteral2);
// since we need it's value anyway, let's snag it so it doesn't get corrupted
// if a new load takes place before we store the entity values
fStringBuffer.clear();
fStringBuffer2.clear();
fStringBuffer.append(fLiteral.ch, fLiteral.offset, fLiteral.length);
fStringBuffer2.append(fLiteral2.ch, fLiteral2.offset, fLiteral2.length);
}
// skip possible trailing space
skipSeparator(false, !scanningInternalSubset());
// end
if (!fEntityScanner.skipChar('>')) {
reportFatalError("EntityDeclUnterminated", new Object[]{name});
}
fMarkUpDepth--;
// register entity and make callback
if (isPEDecl) {
name = "%" + name;
}
if (systemId != null) {
String baseSystemId = fEntityScanner.getBaseSystemId();
if (notation != null) {
fEntityManager.addUnparsedEntity(name, publicId, systemId, baseSystemId, notation);
}
else {
fEntityManager.addExternalEntity(name, publicId, systemId,
baseSystemId);
}
if (fDTDHandler != null) {
fResourceIdentifier.setValues(publicId, systemId, baseSystemId, XMLEntityManager.expandSystemId(systemId, baseSystemId, false));
if (notation != null) {
fDTDHandler.unparsedEntityDecl(name, fResourceIdentifier,
notation, null);
}
else {
fDTDHandler.externalEntityDecl(name, fResourceIdentifier, null);
}
}
}
else {
fEntityManager.addInternalEntity(name, fStringBuffer.toString());
if (fDTDHandler != null) {
fDTDHandler.internalEntityDecl(name, fStringBuffer, fStringBuffer2, null);
}
}
fReportEntity = true;
} // scanEntityDecl()
/**
* Scans an entity value.
*
* @param value The string to fill in with the value.
* @param nonNormalizedValue The string to fill in with the
* non-normalized value.
*
* Note: This method uses fString, fStringBuffer (through
* the use of scanCharReferenceValue), and fStringBuffer2, anything in them
* at the time of calling is lost.
*/
protected final void scanEntityValue(XMLString value,
XMLString nonNormalizedValue)
throws IOException, XNIException
{
int quote = fEntityScanner.scanChar();
if (quote != '\'' && quote != '"') {
reportFatalError("OpenQuoteMissingInDecl", null);
}
// store at which depth of entities we start
int entityDepth = fEntityDepth;
XMLString literal = fString;
XMLString literal2 = fString;
if (fEntityScanner.scanLiteral(quote, fString) != quote) {
fStringBuffer.clear();
fStringBuffer2.clear();
do {
fStringBuffer.append(fString);
fStringBuffer2.append(fString);
if (fEntityScanner.skipChar('&')) {
if (fEntityScanner.skipChar('#')) {
fStringBuffer2.append("");
scanCharReferenceValue(fStringBuffer, fStringBuffer2);
}
else {
fStringBuffer.append('&');
fStringBuffer2.append('&');
String eName = fEntityScanner.scanName();
if (eName == null) {
reportFatalError("NameRequiredInReference",
null);
}
else {
fStringBuffer.append(eName);
fStringBuffer2.append(eName);
}
if (!fEntityScanner.skipChar(';')) {
reportFatalError("SemicolonRequiredInReference",
new Object[]{eName});
}
else {
fStringBuffer.append(';');
fStringBuffer2.append(';');
}
}
}
else if (fEntityScanner.skipChar('%')) {
while (true) {
fStringBuffer2.append('%');
String peName = fEntityScanner.scanName();
if (peName == null) {
reportFatalError("NameRequiredInPEReference",
null);
}
else if (!fEntityScanner.skipChar(';')) {
reportFatalError("SemicolonRequiredInPEReference",
new Object[]{peName});
}
else {
if (scanningInternalSubset()) {
reportFatalError("PEReferenceWithinMarkup",
new Object[]{peName});
}
fStringBuffer2.append(peName);
fStringBuffer2.append(';');
}
startPE(peName, true);
// REVISIT: [Q] Why do we skip spaces here? -Ac
// REVISIT: This will make returning the non-
// normalized value harder. -Ac
fEntityScanner.skipSpaces();
if (!fEntityScanner.skipChar('%'))
break;
}
}
else {
int c = fEntityScanner.peekChar();
if (XMLChar.isHighSurrogate(c)) {
scanSurrogates(fStringBuffer2);
}
else if (isInvalidLiteral(c)) {
reportFatalError("InvalidCharInLiteral",
new Object[]{Integer.toHexString(c)});
fEntityScanner.scanChar();
}
// if it's not the delimiting quote or if it is but from a
// different entity than the one this literal started from,
// simply append the character to our buffer
else if (c != quote || entityDepth != fEntityDepth) {
fStringBuffer.append((char)c);
fStringBuffer2.append((char)c);
fEntityScanner.scanChar();
}
}
} while (fEntityScanner.scanLiteral(quote, fString) != quote);
fStringBuffer.append(fString);
fStringBuffer2.append(fString);
literal = fStringBuffer;
literal2 = fStringBuffer2;
}
value.setValues(literal);
nonNormalizedValue.setValues(literal2);
if (!fEntityScanner.skipChar(quote)) {
reportFatalError("CloseQuoteMissingInDecl", null);
}
} // scanEntityValue(XMLString,XMLString):void
/**
* Scans a notation declaration
*
*
* [82] NotationDecl ::= '<!NOTATION' S Name S (ExternalID|PublicID) S? '>'
* [83] PublicID ::= 'PUBLIC' S PubidLiteral
*
*
* Note: Called after scanning past '<!NOTATION'
*/
private final void scanNotationDecl() throws IOException, XNIException {
// spaces
fReportEntity = false;
if (!skipSeparator(true, !scanningInternalSubset())) {
reportFatalError("MSG_SPACE_REQUIRED_BEFORE_NOTATION_NAME_IN_NOTATIONDECL",
null);
}
// notation name
String name = null;
if(fNamespaces) {
name = fEntityScanner.scanNCName();
} else {
name = fEntityScanner.scanName();
}
if (name == null) {
reportFatalError("MSG_NOTATION_NAME_REQUIRED_IN_NOTATIONDECL",
null);
}
// spaces
if (!skipSeparator(true, !scanningInternalSubset())) {
// check for invalid ":"
if(fNamespaces && fEntityScanner.peekChar() == ':') {
fEntityScanner.scanChar();
XMLStringBuffer colonName = new XMLStringBuffer(name);
colonName.append(':');
colonName.append(fEntityScanner.scanName());
reportFatalError("ColonNotLegalWithNS", new Object[] {colonName.toString()});
skipSeparator(true, !scanningInternalSubset());
} else {
reportFatalError("MSG_SPACE_REQUIRED_AFTER_NOTATION_NAME_IN_NOTATIONDECL",
new Object[]{name});
}
}
// external id
scanExternalID(fStrings, true);
String systemId = fStrings[0];
String publicId = fStrings[1];
String baseSystemId = fEntityScanner.getBaseSystemId();
if (systemId == null && publicId == null) {
reportFatalError("ExternalIDorPublicIDRequired",
new Object[]{name});
}
// skip possible trailing space
skipSeparator(false, !scanningInternalSubset());
// end
if (!fEntityScanner.skipChar('>')) {
reportFatalError("NotationDeclUnterminated", new Object[]{name});
}
fMarkUpDepth--;
// call handler
if (fDTDHandler != null) {
fResourceIdentifier.setValues(publicId, systemId, baseSystemId, XMLEntityManager.expandSystemId(systemId, baseSystemId, false));
fDTDHandler.notationDecl(name, fResourceIdentifier, null);
}
fReportEntity = true;
} // scanNotationDecl()
/**
* Scans a conditional section. If it's a section to ignore the whole
* section gets scanned through and this method only returns after the
* closing bracket has been found. When it's an include section though, it
* returns to let the main loop take care of scanning it. In that case the
* end of the section if handled by the main loop (scanDecls).
*
*
* [61] conditionalSect ::= includeSect | ignoreSect
* [62] includeSect ::= '<![' S? 'INCLUDE' S? '[' extSubsetDecl ']]>'
* [63] ignoreSect ::= '<![' S? 'IGNORE' S? '[' ignoreSectContents* ']]>'
* [64] ignoreSectContents ::= Ignore ('<![' ignoreSectContents ']]>' Ignore)*
* [65] Ignore ::= Char* - (Char* ('<![' | ']]>') Char*)
*
*
* Note: Called after scanning past '<![' */
private final void scanConditionalSect(int currPEDepth)
throws IOException, XNIException {
fReportEntity = false;
skipSeparator(false, !scanningInternalSubset());
if (fEntityScanner.skipString("INCLUDE")) {
skipSeparator(false, !scanningInternalSubset());
if(currPEDepth != fPEDepth && fValidation) {
fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
"INVALID_PE_IN_CONDITIONAL",
new Object[]{ fEntityManager.fCurrentEntity.name},
XMLErrorReporter.SEVERITY_ERROR);
}
// call handler
if (!fEntityScanner.skipChar('[')) {
reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
}
if (fDTDHandler != null) {
fDTDHandler.startConditional(XMLDTDHandler.CONDITIONAL_INCLUDE,
null);
}
fIncludeSectDepth++;
// just stop there and go back to the main loop
fReportEntity = true;
}
else if (fEntityScanner.skipString("IGNORE")) {
skipSeparator(false, !scanningInternalSubset());
if(currPEDepth != fPEDepth && fValidation) {
fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
"INVALID_PE_IN_CONDITIONAL",
new Object[]{ fEntityManager.fCurrentEntity.name},
XMLErrorReporter.SEVERITY_ERROR);
}
// call handler
if (fDTDHandler != null) {
fDTDHandler.startConditional(XMLDTDHandler.CONDITIONAL_IGNORE,
null);
}
if (!fEntityScanner.skipChar('[')) {
reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
}
fReportEntity = true;
int initialDepth = ++fIncludeSectDepth;
if (fDTDHandler != null) {
fIgnoreConditionalBuffer.clear();
}
while (true) {
if (fEntityScanner.skipChar('<')) {
if (fDTDHandler != null) {
fIgnoreConditionalBuffer.append('<');
}
//
// These tests are split so that we handle cases like
// '<', etc.
//
if (fEntityScanner.skipChar(']')) {
if (fDTDHandler != null) {
fIgnoreConditionalBuffer.append(']');
}
while (fEntityScanner.skipChar(']')) {
/* empty loop body */
if (fDTDHandler != null) {
fIgnoreConditionalBuffer.append(']');
}
}
if (fEntityScanner.skipChar('>')) {
if (fIncludeSectDepth-- == initialDepth) {
fMarkUpDepth--;
// call handler
if (fDTDHandler != null) {
fLiteral.setValues(fIgnoreConditionalBuffer.ch, 0,
fIgnoreConditionalBuffer.length - 2);
fDTDHandler.ignoredCharacters(fLiteral, null);
fDTDHandler.endConditional(null);
}
return;
} else if(fDTDHandler != null) {
fIgnoreConditionalBuffer.append('>');
}
}
}
}
else {
int c = fEntityScanner.scanChar();
if (fScannerState == SCANNER_STATE_END_OF_INPUT) {
reportFatalError("IgnoreSectUnterminated", null);
return;
}
if (fDTDHandler != null) {
fIgnoreConditionalBuffer.append((char)c);
}
}
}
}
else {
reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
}
} // scanConditionalSect()
/**
* Dispatch an XML "event".
*
* @param complete True if this method is intended to scan
* and dispatch as much as possible.
*
* @return True if there is more to scan.
*
* @throws IOException Thrown on i/o error.
* @throws XNIException Thrown on parse error.
*
*/
protected final boolean scanDecls(boolean complete)
throws IOException, XNIException {
skipSeparator(false, true);
boolean again = true;
while (again && fScannerState == SCANNER_STATE_MARKUP_DECL) {
again = complete;
if (fEntityScanner.skipChar('<')) {
fMarkUpDepth++;
if (fEntityScanner.skipChar('?')) {
scanPI();
}
else if (fEntityScanner.skipChar('!')) {
if (fEntityScanner.skipChar('-')) {
if (!fEntityScanner.skipChar('-')) {
reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD",
null);
} else {
scanComment();
}
}
else if (fEntityScanner.skipString("ELEMENT")) {
scanElementDecl();
}
else if (fEntityScanner.skipString("ATTLIST")) {
scanAttlistDecl();
}
else if (fEntityScanner.skipString("ENTITY")) {
scanEntityDecl();
}
else if (fEntityScanner.skipString("NOTATION")) {
scanNotationDecl();
}
else if (fEntityScanner.skipChar('[') &&
!scanningInternalSubset()) {
scanConditionalSect(fPEDepth);
}
else {
fMarkUpDepth--;
reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD",
null);
}
}
else {
fMarkUpDepth--;
reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
}
}
else if (fIncludeSectDepth > 0 && fEntityScanner.skipChar(']')) {
// end of conditional section?
if (!fEntityScanner.skipChar(']')
|| !fEntityScanner.skipChar('>')) {
reportFatalError("IncludeSectUnterminated", null);
}
// call handler
if (fDTDHandler != null) {
fDTDHandler.endConditional(null);
}
// decreaseMarkupDepth();
fIncludeSectDepth--;
fMarkUpDepth--;
}
else if (scanningInternalSubset() &&
fEntityScanner.peekChar() == ']') {
// this is the end of the internal subset, let's stop here
return false;
}
else if (fEntityScanner.skipSpaces()) {
// simply skip
}
else {
reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
// Skip the part in error
int ch;
do {
// Ignore the current character
fEntityScanner.scanChar();
// Skip any separators
skipSeparator(false, true);
// Keeping getting the next character,
// until it's one of the expected ones
ch = fEntityScanner.peekChar();
} while (ch != '<' && ch != ']' && !XMLChar.isSpace(ch));
}
skipSeparator(false, true);
}
return fScannerState != SCANNER_STATE_END_OF_INPUT;
}
/**
* Skip separator. This is typically just whitespace but it can also be one
* or more parameter entity references.
*
* If there are some it "expands them" by calling the corresponding entity
* from the entity manager.
*
* This is recursive and will process has many refs as possible.
*
* @param spaceRequired Specify whether some leading whitespace should be
* found
* @param lookForPERefs Specify whether parameter entity references should
* be looked for
* @return True if any leading whitespace was found or the end of a
* parameter entity was crossed.
*/
private boolean skipSeparator(boolean spaceRequired, boolean lookForPERefs)
throws IOException, XNIException
{
int depth = fPEDepth;
boolean sawSpace = fEntityScanner.skipSpaces();
if (!lookForPERefs || !fEntityScanner.skipChar('%')) {
return !spaceRequired || sawSpace || (depth != fPEDepth);
}
while (true) {
String name = fEntityScanner.scanName();
if (name == null) {
reportFatalError("NameRequiredInPEReference", null);
}
else if (!fEntityScanner.skipChar(';')) {
reportFatalError("SemicolonRequiredInPEReference",
new Object[]{name});
}
startPE(name, false);
fEntityScanner.skipSpaces();
if (!fEntityScanner.skipChar('%'))
return true;
}
}
/*
* Element Children Content Stack
*/
private final void pushContentStack(int c) {
if (fContentStack.length == fContentDepth) {
int[] newStack = new int[fContentDepth * 2];
System.arraycopy(fContentStack, 0, newStack, 0, fContentDepth);
fContentStack = newStack;
}
fContentStack[fContentDepth++] = c;
}
private final int popContentStack() {
return fContentStack[--fContentDepth];
}
/*
* Parameter Entity Stack
*/
private final void pushPEStack(int depth, boolean report) {
if (fPEStack.length == fPEDepth) {
int[] newIntStack = new int[fPEDepth * 2];
System.arraycopy(fPEStack, 0, newIntStack, 0, fPEDepth);
fPEStack = newIntStack;
// report end/start calls
boolean[] newBooleanStack = new boolean[fPEDepth * 2];
System.arraycopy(fPEReport, 0, newBooleanStack, 0, fPEDepth);
fPEReport = newBooleanStack;
}
fPEReport[fPEDepth] = report;
fPEStack[fPEDepth++] = depth;
}
/** pop the stack */
private final int popPEStack() {
return fPEStack[--fPEDepth];
}
/** look at the top of the stack */
private final boolean peekReportEntity() {
return fPEReport[fPEDepth-1];
}
/*
* Utility method
*/
private final void ensureEnumerationSize(int size) {
if (fEnumeration.length == size) {
String[] newEnum = new String[size * 2];
System.arraycopy(fEnumeration, 0, newEnum, 0, size);
fEnumeration = newEnum;
}
}
// private methods
private void init() {
// reset state related data
fStartDTDCalled = false;
fExtEntityDepth = 0;
fIncludeSectDepth = 0;
fMarkUpDepth = 0;
fPEDepth = 0;
fStandalone = false;
fSeenExternalDTD = false;
fSeenPEReferences = false;
// set starting state
setScannerState(SCANNER_STATE_TEXT_DECL);
}
private int skipInvalidEnumerationValue() throws IOException {
int c;
do {
c = fEntityScanner.scanChar();
}
while (c != '|' && c != ')');
ensureEnumerationSize(fEnumerationCount + 1);
fEnumeration[fEnumerationCount++] = XMLSymbols.EMPTY_STRING;
return c;
}
} // class XMLDTDScannerImpl