| /******************************************************************************* |
| * Copyright (c) 2002, 2005 Object Factory Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Object Factory Inc. - Initial implementation |
| * IBM Corporation - Fix for bug 40951 |
| *******************************************************************************/ |
| package org.eclipse.ant.internal.ui.dtd.schema; |
| |
| import com.ibm.icu.text.MessageFormat; |
| 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; |
| |
| /** |
| * 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 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; |
| } |
| |
| /** |
| * @see org.xml.sax.ext.DeclHandler#attributeDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String) |
| */ |
| 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.: (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 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 (String[]) values.toArray(new String[values.size()]); |
| } |
| |
| /** |
| * @see org.xml.sax.ext.DeclHandler#elementDecl(java.lang.String, java.lang.String) |
| */ |
| 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 String[]{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 String[]{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 String[]{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 String[]{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(); |
| StringBuffer sb = new StringBuffer(); |
| 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 String[]{fElement.getName(), |
| String.valueOf(fBuf)})); |
| } |
| } |
| |
| /** |
| * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(java.lang.String, java.lang.String, java.lang.String) |
| */ |
| public void externalEntityDecl(String name, String publicId, String systemId) { |
| } |
| |
| /** |
| * @see org.xml.sax.ext.DeclHandler#internalEntityDecl(java.lang.String, java.lang.String) |
| */ |
| public void internalEntityDecl(String name, String value) { |
| } |
| |
| public void setErrorException(Exception e) { |
| fErrorException = e; |
| } |
| } |