| /* |
| * 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; |
| } |
| |
| } |
| } |