| /******************************************************************************* |
| * Copyright (c) 2002, 2013 Object Factory Inc. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Object Factory Inc. - Initial implementation |
| * IBM Corporation - Fix for bug 40951 |
| *******************************************************************************/ |
| package org.eclipse.ant.internal.ui.dtd.schema; |
| |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| |
| import org.eclipse.ant.internal.ui.dtd.IModel; |
| import org.eclipse.ant.internal.ui.dtd.ISchema; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.ext.DeclHandler; |
| |
| import com.ibm.icu.text.MessageFormat; |
| |
| /** |
| * SchemaFactory is a SAX DeclHandler that converts DTD ELEMENT and ATTLIST declarations to schema form on the fly. The only two methods available to |
| * external users of SchemaFactory are its constructor and <code>getSchema()</code>. The latter returns the schema built by this process and should |
| * not be called until the XML parser to which this handler is attached has finished parsing. |
| * |
| * @author Bob Foster |
| */ |
| public class SchemaFactory implements DeclHandler { |
| // used for parsing models |
| private char[] fBuf; |
| private int fLen; |
| private int fPos; |
| private Element fElement; |
| |
| private Schema fSchema; |
| private static HashSet<String> fTypes = new HashSet<>(); |
| private Exception fErrorException; |
| static { |
| fTypes.add("CDATA"); //$NON-NLS-1$ |
| fTypes.add("ID"); //$NON-NLS-1$ |
| fTypes.add("IDREF"); //$NON-NLS-1$ |
| fTypes.add("IDREFS"); //$NON-NLS-1$ |
| fTypes.add("NMTOKEN"); //$NON-NLS-1$ |
| fTypes.add("NMTOKENS"); //$NON-NLS-1$ |
| fTypes.add("ENTITY"); //$NON-NLS-1$ |
| fTypes.add("ENTITIES"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Constructor. |
| */ |
| public SchemaFactory() { |
| fSchema = new Schema(); |
| } |
| |
| /** |
| * @return ISchema produced from the DeclHandler. The schema is always correct, though it may be incomplete if the parse was interrupted due to |
| * validation or well-formed errors. |
| */ |
| public ISchema getSchema() { |
| fSchema.setErrorException(fErrorException); |
| return fSchema; |
| } |
| |
| @Override |
| public void attributeDecl(String eName, String aName, String type, String valueDefault, String value) { |
| Element element = getElement(eName); |
| Attribute attr = (Attribute) element.getAttributes().get(aName); |
| if (attr == null) { |
| attr = new Attribute(aName, element); |
| element.addAttribute(attr); |
| |
| String[] enumeration = null; |
| if (fTypes.contains(type)) |
| attr.setType(type); |
| else if (type.startsWith("NOTATION")) //$NON-NLS-1$ |
| enumeration = parseValues(type.substring("NOTATION".length() + 1), ','); //$NON-NLS-1$ |
| else { |
| type = stripSurroundingParentheses(type); |
| enumeration = parseValues(type, '|'); |
| } |
| attr.setEnum(enumeration); |
| |
| attr.setRequired(valueDefault == null || !valueDefault.equals("#IMPLIED")); //$NON-NLS-1$ |
| attr.setFixed(valueDefault != null && valueDefault.equals("#FIXED")); //$NON-NLS-1$ |
| attr.setDefault(value); |
| } |
| } |
| |
| /** |
| * Strips the surrounding parentheses from <code>aString</code>. |
| * <P> |
| * i.e.: {@code (true|false) -> true|false } |
| */ |
| private String stripSurroundingParentheses(String aString) { |
| if (aString.startsWith("(")) { //$NON-NLS-1$ |
| aString = aString.substring(1); |
| } |
| if (aString.endsWith(")")) { //$NON-NLS-1$ |
| aString = aString.substring(0, aString.length() - 1); |
| } |
| return aString; |
| } |
| |
| /** |
| * @param eName |
| * Element name |
| * @return Element from schema or new element. Either way the element is in the schema upon return. |
| */ |
| private Element getElement(String eName) { |
| Element element = (Element) fSchema.getElement(eName); |
| if (element == null) { |
| element = new Element(eName); |
| fSchema.addElement(element); |
| } |
| return element; |
| } |
| |
| private String[] parseValues(String type, char separator) { |
| int start = 0, pos, len = type.length(); |
| LinkedList<String> values = new LinkedList<>(); |
| while (start < len) { |
| pos = type.indexOf(separator, start); |
| if (pos < 0) |
| pos = len; |
| String term = type.substring(start, pos); |
| start = pos + 1; |
| values.add(term); |
| } |
| return values.toArray(new String[values.size()]); |
| } |
| |
| @Override |
| public void elementDecl(String name, String model) throws SAXException { |
| Element element = getElement(name); |
| if (!element.isUndefined()) { |
| // if the element has already been defined, this is an error |
| throw new SAXException(MessageFormat.format(AntDTDSchemaMessages.SchemaFactory_Doubly_defined, new Object[] { name })); |
| } |
| |
| fElement = element; |
| if (model.equals("ANY")) { //$NON-NLS-1$ |
| element.setAny(true); |
| } else if (model.equals("EMPTY")) { //$NON-NLS-1$ |
| element.setEmpty(true); |
| } else if (model.equals("(#PCDATA)")) {//$NON-NLS-1$ |
| element.setText(true); |
| } else { |
| element.setContentModel(parseModel(model)); |
| } |
| } |
| |
| /** |
| * Convert model string to IModel. The <code>fElement</code> variable is an implicit argument to this method, and it sets <code>fBuf</code>, |
| * <code>fPos</code> and <code>fLen</code> for use by other parser methods. |
| * |
| * @param model |
| * String from DTD, with parameter entities replaced. |
| * @return IModel |
| * @throws SAXException |
| * if syntax error detected in model. This is a validation error. Since the DTD is usually not read unless the parser is validating, |
| * we may not ever be handed a bad content model, but we need to check them, just the same. |
| */ |
| private IModel parseModel(String model) throws SAXException { |
| fBuf = model.toCharArray(); |
| fLen = fBuf.length; |
| if (fBuf[0] != '(') { |
| throw new SAXException(MessageFormat.format(AntDTDSchemaMessages.SchemaFactory_Start_with_left_parenthesis, new Object[] { |
| fElement.getName() })); |
| } |
| |
| boolean ortext = model.startsWith("(#PCDATA|"); //$NON-NLS-1$ |
| if (ortext) { |
| fPos = 8; // "(#PCDATA".length() |
| } else { |
| fPos = 0; |
| } |
| IModel emodel = scanExpr(); |
| return emodel; |
| } |
| |
| /** |
| * Scan a parenthesized expression starting from the left parenthesis or leftmost operator. |
| * |
| * @return IModel |
| */ |
| private IModel scanExpr() throws SAXException { |
| // skip opening ( or | |
| fPos++; |
| return scanExpr(scanElement()); |
| } |
| |
| /** |
| * Scan a parenthesized expression with the first term in hand. |
| * |
| * @param term |
| * The first operand in the expression, pre-scanned. |
| * @return IModel |
| * @throws SAXException |
| * if errors are detected in the model. |
| */ |
| private IModel scanExpr(IModel term) throws SAXException { |
| checkLen(); |
| if (fBuf[fPos] != ')') { |
| char op = fBuf[fPos]; |
| if (op != '|' && op != ',') { |
| throw new SAXException(MessageFormat.format(AntDTDSchemaMessages.SchemaFactory_Expecting_operator_or_right_parenthesis, new Object[] { |
| fElement.getName(), String.valueOf(fBuf) })); |
| } |
| Model model = new Model(op == '|' ? IModel.CHOICE : IModel.SEQUENCE); |
| model.addModel(term); |
| term = model; |
| |
| while (fBuf[fPos] == op) { |
| fPos++; |
| IModel next = scanElement(); |
| model.addModel(next); |
| } |
| if (fBuf[fPos] != ')') { |
| throw new SAXException(MessageFormat.format(AntDTDSchemaMessages.SchemaFactory_Expecting_operator_or_right_parenthesis, new Object[] { |
| fElement.getName(), String.valueOf(fBuf) })); |
| } |
| fPos++; |
| } |
| return term; |
| } |
| |
| /** |
| * Scan an element name or a parenthesized sub-expression. |
| * |
| * @return IModel |
| * @throws SAXException |
| */ |
| private IModel scanElement() throws SAXException { |
| checkLen(); |
| if (fBuf[fPos] == '(') |
| return scanExpr(); |
| StringBuilder sb = new StringBuilder(); |
| while (fBuf[fPos] != '|' && fBuf[fPos] != ',' && fBuf[fPos] != ')' && fBuf[fPos] != '*' && fBuf[fPos] != '+' && fBuf[fPos] != '?') { |
| sb.append(fBuf[fPos++]); |
| checkLen(); |
| } |
| String name = sb.toString(); |
| Element element = getElement(name); |
| Model model = new Model(IModel.LEAF); |
| model.setLeaf(element); |
| return model; |
| } |
| |
| private void checkLen() throws SAXException { |
| if (fPos == fLen) { |
| throw new SAXException(MessageFormat.format(AntDTDSchemaMessages.SchemaFactory_Unexpected_end, new Object[] { fElement.getName(), |
| String.valueOf(fBuf) })); |
| } |
| } |
| |
| @Override |
| public void externalEntityDecl(String name, String publicId, String systemId) { |
| // do nothing |
| } |
| |
| @Override |
| public void internalEntityDecl(String name, String value) { |
| // do nothing |
| } |
| |
| public void setErrorException(Exception e) { |
| fErrorException = e; |
| } |
| } |