blob: 3d12a5c598c9c6f3f3e534d2ef2b94169b268422 [file] [log] [blame]
/*
* 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.netbeans.modules.xml.dtd.grammar;
import java.io.*;
import java.util.*;
import java.util.StringTokenizer;
import org.xml.sax.*;
import org.xml.sax.ext.*;
import org.xml.sax.helpers.*;
import org.openide.xml.*;
import org.openide.util.Lookup;
import org.netbeans.api.xml.parsers.SAXEntityParser;
import org.netbeans.api.xml.services.UserCatalog;
import org.netbeans.modules.xml.api.model.GrammarQuery;
/**
* Produces {@link DTDGrammar} from passed SAX declaration handler events.
*
* @author Petr Kuzel
* @author asgeir@dimonsoftware.com
*/
public class DTDParser {
static final String SAX_PROPERTY = "http://xml.org/sax/properties/"; //NOI18N
static final String DECL_HANDLER = "declaration-handler"; //NOI18N
/** If true, the InputSource parameter of the parse method is expected to be a
* DTD document, otherwise if is expected to be a XML document. */
private boolean dtdOnly;
/** Creates new DTDParser
* The InputSource parameter of the parse method should be a XML document
*/
public DTDParser() {
this(false);
}
/** Creates new DTDParser
* @param dtdOnly If true the InputSource parameter into the parse method
* should be a DTD document, otherwise it should be an XML
* document.
*/
public DTDParser(boolean dtdOnly) {
this.dtdOnly = dtdOnly;
}
public GrammarQuery parse(InputSource in) {
Handler handler = new Handler();
EntityResolverWrapper res = null;
try {
XMLReader parser = XMLUtil.createXMLReader(dtdOnly == false); // we do not want Crimson, it does not understand relative SYSTEM ids
parser.setContentHandler(handler);
parser.setErrorHandler(handler);
parser.setDTDHandler(handler);
UserCatalog catalog = UserCatalog.getDefault();
if(catalog != null) {
res = new EntityResolverWrapper(catalog.getEntityResolver());
};
if (res != null) {
parser.setEntityResolver(res);
}
parser.setProperty(SAX_PROPERTY + DECL_HANDLER, handler);
if (dtdOnly) {
new SAXEntityParser(parser, false).parse(in);
} else {
parser.parse(in);
}
throw new IllegalStateException("How we can get here?");
} catch (Stop stop) {
//OK
} catch (SAXException ex) {
if (Boolean.getBoolean("netbeans.debug.xml") || Boolean.getBoolean("netbeans.debug.exceptions")) { //NOI18N
ex.printStackTrace();
if (ex.getException() instanceof RuntimeException) {
ex.getException().printStackTrace(); //???
}
}
//error, but return what was parsed
} catch (IOException ex) {
if (Boolean.getBoolean("netbeans.debug.xml")) { // NOI18N
ex.printStackTrace();
}
//error, but return at least a partial result
}
DTDGrammar dtdGrammar = handler.getDTDGrammar();
dtdGrammar.setResolvedEntities(
res == null ? Collections.EMPTY_LIST : res.getResolvedSystemIds()
);
return dtdGrammar;
}
/**
* Actually create a grammar from callback information.
*/
private class Handler extends DefaultHandler implements DeclHandler {
private Map attrs, elements, models, enums, attrDefaults;
private Set<String> notations, entities, anys, emptyElements;
private DTDGrammar dtd;
Handler() {
attrs = new HashMap();
elements = new HashMap();
models = new HashMap();
notations = new TreeSet<>();
entities = new TreeSet<>();
anys = new HashSet();
enums = new HashMap();
attrDefaults = new HashMap();
emptyElements = new HashSet();
dtd = new DTDGrammar(elements, models, attrs, attrDefaults, enums, entities, notations, emptyElements);
}
/**
* Update value of ANY declared content models
*/
DTDGrammar getDTDGrammar() {
Iterator it = anys.iterator();
while (it.hasNext()) {
String name = (String) it.next();
elements.put(name, elements.keySet());
}
return dtd;
}
public void elementDecl(String name, String model) throws SAXException {
// special cases
if ("ANY".equals(model)) {
anys.add(name);
elements.put(name, Collections.EMPTY_SET); // see anys resolving
return;
} else if ("EMPTY".equals(model)) {
elements.put(name, Collections.EMPTY_SET);
emptyElements.add(name);
return;
} else if ("(#PCDATA)".equals(model)) {
elements.put(name, Collections.EMPTY_SET);
return;
}
// parse content model
StringTokenizer tokenizer = new StringTokenizer(model, " \t\n|,()?+*");
Set modelset = new TreeSet();
while (tokenizer.hasMoreTokens()) {
String next = tokenizer.nextToken().trim();
if ("#PCDATA".equals(next)) continue;
modelset.add(next);
}
elements.put(name, modelset);
models.put(name, model);
}
public void externalEntityDecl(String name, String publicId, String systemId) throws SAXException {
if (name.startsWith("%")) return; // NOI18N
entities.add(name);
}
public void attributeDecl(String eName, String aName, String type, String valueDefault, String value) throws SAXException {
Set set = (Set) attrs.get(eName);
if (set == null) {
set = new TreeSet();
attrs.put(eName, set);
}
set.add(aName);
// if enumeration type place into enumeration map new entry
if (type != null && type.startsWith("(")) {
StringTokenizer tokenizer = new StringTokenizer(type, "()|", false);
List tokens = new ArrayList(7);
while (tokenizer.hasMoreTokens()) {
tokens.add(tokenizer.nextToken());
}
enums.put(eName + " " + aName, tokens); // NOI18N
}
// store defaults
String key = eName + " " + aName; // NOI18N
attrDefaults.put(key, valueDefault);
}
public void internalEntityDecl(String name, String value) throws SAXException {
if (name.startsWith("%")) return; // NOI18N
entities.add(name);
}
public void notationDecl(String name, String publicId, String systemId) throws SAXException {
notations.add(name);
}
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
throw new Stop();
}
}
private class Stop extends SAXException {
private static final long serialVersionUID = -6466279601744402792L;
Stop() {
super("STOP"); //NOI18N
}
public Throwable fillInStackTrace() {
return this;
}
}
private class EntityResolverWrapper implements EntityResolver {
private EntityResolver resolver;
private ArrayList<String> resolvedSystemIds = new ArrayList(3);
public EntityResolverWrapper(EntityResolver resolver) {
this.resolver = resolver;
}
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
resolvedSystemIds.add(systemId);
return resolver.resolveEntity(publicId, systemId);
}
public List/*<String>*/ getResolvedSystemIds() {
return resolvedSystemIds;
}
}
}