/**
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright
 *    statements and notices.  Redistributions must also contain a
 *    copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the
 *    above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote
 *    products derived from this Software without prior written
 *    permission of Intalio, Inc.  For written permission,
 *    please contact info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab"
 *    nor may "Exolab" appear in their names without prior written
 *    permission of Intalio, Inc. Exolab is a registered
 *    trademark of Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project
 *    (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 2000 (C) Intalio Inc. All Rights Reserved.
 *
 * $Id: Converter.java 8519 2010-01-28 20:46:33Z wguttmn $
 */

package org.exolab.castor.xml.dtd;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.exolab.castor.xml.dtd.parser.DTDInitialParser;
import org.exolab.castor.xml.dtd.parser.DTDParser;
import org.exolab.castor.xml.dtd.parser.InputCharStream;
import org.exolab.castor.xml.dtd.parser.ParseException;
import org.exolab.castor.xml.dtd.parser.TokenMgrError;
import org.exolab.castor.xml.schema.Annotation;
import org.exolab.castor.xml.schema.AttributeDecl;
import org.exolab.castor.xml.schema.ComplexType;
import org.exolab.castor.xml.schema.ContentType;
import org.exolab.castor.xml.schema.Documentation;
import org.exolab.castor.xml.schema.ElementDecl;
import org.exolab.castor.xml.schema.Facet;
import org.exolab.castor.xml.schema.FacetFactory;
import org.exolab.castor.xml.schema.Group;
import org.exolab.castor.xml.schema.Order;
import org.exolab.castor.xml.schema.Particle;
import org.exolab.castor.xml.schema.Schema;
import org.exolab.castor.xml.schema.SchemaException;
import org.exolab.castor.xml.schema.SimpleType;
import org.exolab.castor.xml.schema.SimpleTypesFactory;
import org.exolab.castor.xml.schema.Wildcard;
import org.exolab.castor.xml.schema.writer.SchemaWriter;
import org.xml.sax.SAXException;

/**
 * Class containing methods to parse and convert XML DTD
 * documents to corresponding XML Schema documents. Also contains simple command
 * line interface to read an XML DTD file and create corresponding XML Schema
 * object.
 * 
 * @author <a href="mailto:totok@intalio.com">Alexander Totok</a>
 * @version $Revision: 8519 $ $Date: 2006-04-14 04:14:43 -0600 (Fri, 14 Apr
 *          2006) $
 */
public class Converter {
	
	private static final Log log = LogFactory.getLog(Converter.class);

	public static final String NAME_SPACE_PREFIX_KEY = "nameSpacePrefixKey";

	public static final String NAME_SPACE_KEY = "nameSpaceKey";

	public static final String DEFAULT_NAME_SPACE_PREFIX = "tns";

	public static final String DEFAULT_NAME_SPACE = "generated.castor.org";

	/**
	 * Simple command line interface to read an XML DTD file and create
	 * corresponding XML Schema file. Usage:
	 * 
	 * <pre>
	 * java org.exolab.castor.xml.dtd.Converter  dtd_file xsd_file [character_encoding]
	 *    [-tns=[TNS_PREFIX:]NAMESPACE_URI]
	 *    [-xmlns=[TNS_PREFIX:]NAMESPACE_URI]*
	 * 
	 * dtd_file: name of the input DTD file
	 * xsd_file: name of the output Schema file
	 * character_encoding: name of the character encoding,
	 *            if not specified, ASCII is chosen
	 * </pre>
	 * 
	 * Help message is provided.
	 * 
	 * @throws DTDException
	 *             if the input DTD document is malformed.
	 * @throws SchemaException
	 *             if Schema object can not be created.
	 * @throws SAXException
	 *             if an error occured during marshalling of schema object
	 *             constructed from the DTD document.
	 */
	public static void main(String args[]) throws IOException, DTDException,
			SchemaException, SAXException {
		
		StringBuffer header = new StringBuffer();
		header.append("\n");
		header.append("Converts a DTD to an XML schema.\n\n");
		header.append("   <DTD>: Name of the input DTD file.\n");
		header.append("   <XSD>: Name of the output XML schema file.\n");
		header.append("\n");
		header.append("Options:");
		
		Options options = new Options();
		
		Option targetNamespace = OptionBuilder
			.withDescription("target namespace of the XML schema generated")
			.isRequired(false)
			.withLongOpt("targetNamespace")
			.hasArg()
			.withArgName("[prefix:]uri")
			.create("tns");

		Option xmlns = OptionBuilder
		.withDescription("xml namespace declarations")
		.isRequired(false)
		.hasArgs()
		.withValueSeparator(',')
		.withArgName("[[prefix:]uri]*")
		.create("xmlns");

		options.addOption(targetNamespace);
		options.addOption(xmlns);
		options.addOption("h", "help", false, "prints usage information");
		options.addOption("e", "encoding", false, "character encoding");
		
		CommandLineParser parser = new GnuParser();
		CommandLine line = null;
	    try {
			line = parser.parse(options, args);
	    } catch (org.apache.commons.cli.ParseException e) {
	    	System.err.println( "Parsing failed.  Reason: " + e.getMessage() );
		}

		if (args.length < 2 || line.hasOption("help")) {
			HelpFormatter formatter = new HelpFormatter();
			formatter.printHelp("org.exolab.castor.xml.dtd.Converter <DTD> <XSD>", 
					header.toString(),
					options,
					"");
			return;
		}
		
		String encoding = "US-ASCII";
		String targetNameSpace = DEFAULT_NAME_SPACE;
		Map<String, String> nameSpaceMap = new HashMap<String, String>();

		if (line.hasOption("tns")) {
			log.info("Found option -tns ...");
			Map<String, String> nameSpaceMapTemp = parseNamespace(line.getOptionValue("tns"));
			targetNameSpace = nameSpaceMapTemp.get(NAME_SPACE_KEY);
			nameSpaceMap.put(nameSpaceMapTemp.get(NAME_SPACE_PREFIX_KEY), targetNameSpace);
		}

		if (line.hasOption("xmlns")) {
			log.info("Found option -xmlns ...");
			Map<String, String> nameSpaceMapTemp = parseNamespace(line.getOptionValue("xmlns"));
			nameSpaceMap.put(nameSpaceMapTemp.get(NAME_SPACE_PREFIX_KEY), 
					nameSpaceMapTemp.get(NAME_SPACE_KEY));
		}

		if (line.hasOption("encoding")) {
			log.info("Found option -encoding ...");
			String encodingValue = line.getOptionValue("encoding");
			if (encodingValue.equalsIgnoreCase("ascii")
					|| args[2].equalsIgnoreCase("us-ascii")) {
				encoding = "US-ASCII";
			} else if (encodingValue.equalsIgnoreCase("utf-8")) {
				encoding = "UTF-8";
			} else if (encodingValue.equalsIgnoreCase("utf-16")) {
				encoding = "UTF-16";
			} else {
				encoding = encodingValue.toUpperCase();
			}
		}

		String inFile = args[0];
		String outFile = args[1];

		Converter convertor = new Converter();
		convertor.process(inFile, outFile, encoding, targetNameSpace, nameSpaceMap);
	}

	/**
	 * @param dtdFile Location of the DTD file.
	 * @param schemaFile Location of the XML schema file.
	 * @param encoding (Optional) encoding.
	 * @param targetNamespace (Optional) target name space.
	 * @param namespaces (Optional) XML name spaces.
	 * @throws SchemaException
	 * @throws DTDException
	 * @throws IOException
	 * @throws SAXException
	 */
	public void process(final String dtdFile, final String schemaFile, final String encoding, 
			final String targetNamespace, 
			final Map<String, String> namespaces) throws SchemaException, DTDException, IOException, SAXException {

		// instantiate input byte stream, associated with the input file
		FileInputStream inputStream = new FileInputStream(dtdFile);
		
		// instantiate char reader from input file byte stream
		InputStreamReader reader = new InputStreamReader(inputStream, encoding);
		
		// instantiate output byte stream, associated with the output file
		FileOutputStream outputStream = new FileOutputStream(schemaFile);
		
		// instantiate char writer from output file byte stream
		OutputStreamWriter writer = new OutputStreamWriter(outputStream, encoding);
		
		process (reader, writer, encoding, targetNamespace, namespaces);
	}
	
	/**
	 * @param in Location of the DTD file.
	 * @param out Location of the XML schema file.
	 * @param encoding (Optional) encoding.
	 * @param targetNamespace (Optional) target name space.
	 * @param namespaces (Optional) XML name spaces.
	 * @throws SchemaException
	 * @throws DTDException
	 * @throws IOException
	 * @throws SAXException
	 */
	public void process(final Reader in, final Writer out, final String encoding, 
			final String targetNamespace, 
			final Map<String, String> namespaces) throws SchemaException, DTDException, IOException, SAXException {

		// -- convert DTD to Schema
		convertDTDtoSchema(in, out, targetNamespace, namespaces);

		in.close();
		out.close();
	}

	protected static Map<String, String> parseNamespace(final String nameSpaceArg) {

		Map<String, String> nameSpaceMap = new HashMap<String, String>();

		if ("".equals(nameSpaceArg.substring("-tns=".length()))) {
			throw new RuntimeException("name space argument is emty, Spaces after '='? ");
		}

		String[] tnsToken = nameSpaceArg.substring("-tns=".length()).split(":",	2);

		if (isNameSpacePrefix(tnsToken[0])) {
			nameSpaceMap.put(NAME_SPACE_PREFIX_KEY, tnsToken[0]);
			nameSpaceMap.put(NAME_SPACE_KEY, tnsToken[1]);
		} else {
			nameSpaceMap.put(NAME_SPACE_KEY, nameSpaceArg.substring("-tns=".length()));
			nameSpaceMap.put(NAME_SPACE_PREFIX_KEY, "tns");
		} // -- namespace

		return nameSpaceMap;

	}

	protected static boolean isNameSpacePrefix(String nameSpacePrefix2test) {

		if (!nameSpacePrefix2test.matches("[a-z]*")) {
			return false;
		}

		if (nameSpacePrefix2test.matches("https?")) {
			return false;
		}

		return true;

	}

	/**
	 * Convert DTD document to corresponding XML Schema document.
	 * 
	 * @param reader
	 *            reader of the input DTD document.
	 * @param writer
	 *            writer to the output Schema document.
	 * @param targetNameSpaceMap
	 *            (Optional) XML target name space for the XML schema. 
	 * @param nameSpaceMap
	 *            (Optional) XML name space declarations for the XML schema. 
	 * @throws DTDException
	 *             if the DTD document is syntactically or semantically not
	 *             correct.
	 * @throws SchemaException
	 *             if Schema object can not be created.
	 * @throws IOException
	 *             if there is an I/O problem with the <tt>reader</tt> or
	 *             <tt>writer</tt>.
	 * @throws SAXException
	 *             if an error occured during schema object marshalling.
	 */
	public void convertDTDtoSchema(Reader reader, Writer writer,
			String targetNameSpace, Map<String, String> nameSpaceMap)
			throws DTDException, SchemaException, IOException, SAXException {

		// -- parse text of DTD document
		DTDdocument dtd = parseDTD(reader);

		// -- convert DTD document object into its corresponding Schema object
		Schema schema = convertDTDObjectToSchemaObject(dtd, targetNameSpace,
				nameSpaceMap);

		// -- marshal Schema object into its corresponding XML document
		marshalSchema(schema, writer);

	} // -- convertDTDtoSchema

	/**
	 * Convert DTD document to corresponding XML Schema document.
	 * 
	 * @param reader
	 *            reader of the input DTD document.
	 * @param writer
	 *            writer to the output Schema document.
	 *
	 * @throws DTDException
	 *             if the DTD document is syntactically or semanticly not
	 *             correct.
	 * @throws SchemaException
	 *             if Schema object can not be created.
	 * @throws IOException
	 *             if there is an I/O problem with the <tt>reader</tt> or
	 *             <tt>writer</tt>.
	 * @throws SAXException
	 *             if an error occured during schema object marshalling.
	 */
	public void convertDTDtoSchema(Reader reader, Writer writer)
			throws DTDException, SchemaException, IOException, SAXException {

		String targetNameSpace = DEFAULT_NAME_SPACE;
		Map<String, String> nameSpaceMap = new HashMap<String, String>();
		
		convertDTDtoSchema(reader, writer, targetNameSpace, nameSpaceMap);
	}

	/**
	 * Parses text of a DTD document and returns corresponding DTD document
	 * object. It is left to constructor of the <tt>reader</tt> to set up
	 * character encoding correctly. This means that method <u><font
	 * color="blue">read</font></u> of the <tt>reader</tt> is used to get next
	 * character, assuming it returns appropriate values.
	 * 
	 * @param reader
	 *            input char stream reader. It is recommended to use class
	 *            {@link java.io.InputStreamReader java.io.InputStreamReader} as
	 *            a <tt>reader</tt>, which allows to set desired character
	 *            encoding.
	 * @return DTD document object corresponding to the input text of a DTD
	 *         document.
	 * @throws DTDException
	 *             if the DTD document is syntactically or semanticly not
	 *             correct.
	 */
	public DTDdocument parseDTD(Reader reader) throws DTDException {
		try {

			InputCharStream charStream;

			// -- instantiate char stream for initial parser from the input
			// reader
			charStream = new InputCharStream(reader);

			// -- instantiate initial parser
			DTDInitialParser initialParser = new DTDInitialParser(charStream);

			// -- get result of initial parsing - DTD document with parameter
			// -- entity references expanded
			String intermedResult = initialParser.Input();

			// -- construct StringReader from the intermediate result of parsing
			StringReader strReader = new StringReader(intermedResult);

			// -- instantiate char stream for main parser
			charStream = new InputCharStream(strReader);

			// -- instantiate main parser
			DTDParser parser = new DTDParser(charStream);

			// -- parse intermediate result by the main parser
			// -- and get corresponding DTD document oblect
			DTDdocument dtd = parser.Input();

			strReader.close();

			// -- return DTD document object
			return dtd;
		} catch (TokenMgrError tme) {
			String msg = tme.getMessage();
			throw new DTDException("TokenMgrError"
					+ (msg == null ? "" : ": " + msg));
		} catch (ParseException pe) {
			String msg = pe.getMessage();
			throw new DTDException("ParseException"
					+ (msg == null ? "" : ": " + msg));
		}
	} // -- parseDTD

	/**
	 * Convert DTD document object to corresponding Schema object.
	 * 
	 * @param dtd
	 *            input XML DTD document object.
	 * @param nameSpacePrefix
	 * 
	 * @param nameSpace
	 * 
	 * @return corresponding XML Schema object.
	 * @throws DTDException
	 *             if the input DTD document is malformed.
	 * @throws SchemaException
	 *             if Schema object can not be created.
	 */
	public Schema convertDTDObjectToSchemaObject(DTDdocument dtd,
			String targetNamespace, Map nameSpaceMap) throws DTDException,
			SchemaException {

		Schema schema = new Schema();

		String name = dtd.getName();
		if (name != null && !name.equals("")) {
			schema.setId(name);
		}

		schema.setTargetNamespace(targetNamespace);

		for (Iterator<String> namespaces = nameSpaceMap.keySet().iterator(); namespaces
				.hasNext();) {
			String xmlns = namespaces.next();
			schema.addNamespace(xmlns, (String) nameSpaceMap.get(xmlns));
		}

		// convert Notation declarations
		Enumeration dtdNotations = dtd.getNotations();

		while (dtdNotations.hasMoreElements()) {
			dtdNotations.nextElement();
			// do nothing for now as the Castor Schema object model does not
			// support Notation declarations
		} // -- convert Notations declarations

		// convert General Entity declarations.
		// XML Schema does not provide facilities analogous to General Entity
		// declarations in XML DTD, so we convert each General Entity
		// declaration
		// to Documentation subelement of XML Schema document annotaion.
		Enumeration dtdGeneralEntities = dtd.getGeneralEntities();
		if (dtdGeneralEntities.hasMoreElements()) {
			GeneralEntity ge;
			Annotation annotation = new Annotation();
			Documentation documentation;
			String text;

			while (dtdGeneralEntities.hasMoreElements()) {
				ge = (GeneralEntity) dtdGeneralEntities.nextElement();
				documentation = new Documentation();

				text = "General Entity Declaration";
				documentation.add(text);
				documentation.add(ge);
				annotation.addDocumentation(documentation);
			}

			schema.addAnnotation(annotation);
		}
		// -- convert General Entity declarations

		// convert Element declarations
		Enumeration<Element> dtdElements = dtd.getElements();
		Element dtdElement; // DTD Element declaration
		ElementDecl schemaElement; // Schema Element declaration

		while (dtdElements.hasMoreElements()) {
			dtdElement = dtdElements.nextElement();
			schemaElement = convertDTDElementToSchemaElement(dtdElement, schema);
			schema.addElementDecl(schemaElement);
		} // -- convert Element declarations

		return schema;

	} // -- convertDTDObjectToSchemaObject

	/**
	 * Convert DTD Element declaration to Schema Element Declaration.
	 * 
	 * @param dtdElement
	 *            DTD Element declaration.
	 * @param schema
	 *            Schema owning Element declaration.
	 * @throws DTDException
	 *             if the input DTD Element Declaration is malformed.
	 * @throws SchemaException
	 *             if unable to construct return
	 *             {@link org.exolab.castor.xml.schema.ElementDecl ElementDecl}
	 *             object from the input DTD
	 *             {@link org.exolab.castor.xml.dtd.Element Element} object.
	 * @return corresponding Schema Element declaration.
	 */
	public ElementDecl convertDTDElementToSchemaElement(
			Element dtdElement, Schema schema) throws DTDException,
			SchemaException {

		String name = dtdElement.getName();
		if (name == null || name.equals("")) {
			String err = "DTD to Schema converter: a DTD element has no name.";
			throw new DTDException(err);
		}
		ElementDecl schemaElement = new ElementDecl(schema, name);

		// start converting content of the element
		ComplexType complexType = schema.createComplexType();
		ContentType contentType = null;
		Group group = null; // auxiliary
		Iterator mixedChildrenIterator = null; // auxiliary
		String elementRef = null; // auxiliary
		ElementDecl elem = null; // auxiliary

		if (dtdElement.isEmptyContent()) {

			// mixed="false"
			contentType = ContentType.elemOnly;
			// -- mixed="false"

		} else if (dtdElement.isAnyContent()) {

			// mixed="true"
			contentType = ContentType.mixed;
			// -- mixed="true"

			group = new Group();
			group.setOrder(Order.sequence);
			group.setMinOccurs(0);
			group.setMaxOccurs(-1);
			Wildcard any = new Wildcard(group);
			group.addWildcard(any);
			complexType.addGroup(group);

		} else if (dtdElement.isElemOnlyContent()) {

			// mixed="false"
			contentType = ContentType.elemOnly;
			// -- mixed="false"

			ContentParticle dtdContent = dtdElement.getContent();
			if (dtdContent == null) { // content is not specified
				String err = "DTD to Schema converter: element \""
						+ dtdElement.getName();
				err += "\" has no content.";
				throw new DTDException(err);
			}

			Particle content = null;
			try {
				content = convertContentParticle(dtdContent, schema);
			} catch (DTDException e) {
				String err = "DTD to Schema converter: content of DTD element \""
						+ dtdElement.getName();
				err += "\", represented by a Content Particle, is malformed.";
				throw new DTDException(err);
			}

			if (content instanceof ElementDecl) {
				group = new Group();
				group.setOrder(Order.sequence);
				group.addElementDecl((ElementDecl) content);
				complexType.addGroup(group);
			} else {
				complexType.addGroup((Group) content);
			}

		} else if (dtdElement.isMixedContent()) {

			// mixed="true"
			contentType = ContentType.mixed;
			// -- mixed="true"

			mixedChildrenIterator = dtdElement.getMixedContentChildren();
			if ((mixedChildrenIterator != null)
					&& (mixedChildrenIterator.hasNext())) {
				group = new Group();
				group.setOrder(Order.choice);
				group.setMinOccurs(0);
				group.setMaxOccurs(-1);
				while (mixedChildrenIterator.hasNext()) {
					elementRef = (String) mixedChildrenIterator.next();
					elem = new ElementDecl(schema);
					elem.setReferenceName(elementRef);
					group.addElementDecl(elem);
				}
				complexType.addGroup(group);
			}

		} else { // the type of the element has not been specified
			String err = "DTD to Schema converter: content type of DTD element \""
					+ dtdElement.getName();
			err += "\" has not been specified.";
			throw new DTDException(err);
		}
		complexType.setContentType(contentType);
		// finish converting content of the element

		// start attributes convertion
		Enumeration dtdAttributes = dtdElement.getAttributes();
		Attribute dtdAttribute;
		AttributeDecl schemaAttribute;

		while (dtdAttributes.hasMoreElements()) {
			dtdAttribute = (Attribute) dtdAttributes.nextElement();
			schemaAttribute = convertAttribute(dtdAttribute, schema);
			complexType.addAttributeDecl(schemaAttribute);
		}
		// end attributes convertion

		schemaElement.setType(complexType);
		return schemaElement;

	} // -- convertDTDElementToSchemaElement

	/**
	 * Method to convert {@link org.exolab.castor.xml.dtd.ContentParticle
	 * ContentParticle} object, used to implement element content in the DTD
	 * object model, to the corresponding object in the Schema object model:
	 * either {@link org.exolab.castor.xml.schema.Group Group} or
	 * {@link org.exolab.castor.xml.schema.ElementDecl ElementDecl}.
	 * 
	 * @param dtdContent
	 *            input {@link org.exolab.castor.xml.dtd.ContentParticle
	 *            ContentParticle} object.
	 * @return object returned is an instance of either
	 *         {@link org.exolab.castor.xml.schema.Group Group} class or
	 *         {@link org.exolab.castor.xml.schema.ElementDecl ElementDecl}
	 *         class.
	 * @throws DTDException
	 *             if the input ContentParticle is malformed.
	 * @throws SchemaException
	 *             if unable to construct return content object from a given
	 *             ContentParticle
	 */
	public Particle convertContentParticle(ContentParticle dtdContent,
			Schema schema) throws DTDException, SchemaException {

		Particle returnValue;

		if (dtdContent.isReferenceType()) {

			ElementDecl elem = new ElementDecl(schema);
			elem.setReferenceName(dtdContent.getReference());
			returnValue = elem;

		} else if (dtdContent.isSeqType() || dtdContent.isChoiceType()) {

			Group group = new Group();
			if (dtdContent.isSeqType())
				group.setOrder(Order.sequence);
			else
				group.setOrder(Order.choice);

			Enumeration<ContentParticle> children = dtdContent.getChildren();
			ContentParticle child;
			Particle contentParticle;

			while (children.hasMoreElements()) {
				child = children.nextElement();
				contentParticle = convertContentParticle(child, schema);

				if (contentParticle instanceof ElementDecl) {
					group.addElementDecl((ElementDecl) contentParticle);
				} else {
					group.addGroup((Group) contentParticle);
				}
			}

			returnValue = group;

		} else { // -- type of input DTD Content Particle is not specified
			throw new DTDException();
		}

		if (dtdContent.isOneOccurance()) {
			returnValue.setMinOccurs(1);
			returnValue.setMaxOccurs(1);
		} else if (dtdContent.isOneOrMoreOccurances()) {
			returnValue.setMinOccurs(1);
			returnValue.setMaxOccurs(-1);
		} else if (dtdContent.isZeroOrMoreOccurances()) {
			returnValue.setMinOccurs(0);
			returnValue.setMaxOccurs(-1);
		} else if (dtdContent.isZeroOrOneOccurance()) {
			returnValue.setMinOccurs(0);
			returnValue.setMaxOccurs(1);
		} else {
			// a content particle always has "one occurance" default
			// occurance specification
		}

		return returnValue;

	} // -- convertContentParticle

	/**
	 * Convert DTD Attribute declaration to Schema Attribute Declaration.
	 * 
	 * @param dtdAttribute
	 *            DTD Attribute declaration.
	 * @param schema
	 *            Schema owning Element of this Attribute.
	 * @throws DTDException
	 *             if the input DTD Attribute Declaration is malformed.
	 * @return corresponding Schema Attribute declaration.
	 */
	public AttributeDecl convertAttribute(Attribute dtdAttribute,
			Schema schema) throws DTDException {

		AttributeDecl schemaAttribute = new AttributeDecl(schema, dtdAttribute
				.getName());

		SimpleType type = null;

		if (dtdAttribute.isStringType()) {
			type = schema.getSimpleType(schema
					.getBuiltInTypeName(SimpleTypesFactory.STRING_TYPE));
		} else if (dtdAttribute.isIDType()) {
			type = schema.getSimpleType(schema
					.getBuiltInTypeName(SimpleTypesFactory.ID_TYPE));
		} else if (dtdAttribute.isIDREFType()) {
			type = schema.getSimpleType(schema
					.getBuiltInTypeName(SimpleTypesFactory.IDREF_TYPE));
		} else if (dtdAttribute.isIDREFSType()) {
			type = schema.getSimpleType(schema
					.getBuiltInTypeName(SimpleTypesFactory.IDREFS_TYPE));
		} else if (dtdAttribute.isENTITYType()) {
			type = schema.getSimpleType(schema
					.getBuiltInTypeName(SimpleTypesFactory.ENTITY_TYPE));
		} else if (dtdAttribute.isENTITIESType()) {
			type = schema.getSimpleType(schema
					.getBuiltInTypeName(SimpleTypesFactory.ENTITIES_TYPE));
		} else if (dtdAttribute.isNMTOKENType()) {
			type = schema.getSimpleType(schema
					.getBuiltInTypeName(SimpleTypesFactory.NMTOKEN_TYPE));
		} else if (dtdAttribute.isNMTOKENSType()) {
			type = schema.getSimpleType(schema
					.getBuiltInTypeName(SimpleTypesFactory.NMTOKENS_TYPE));
		} else if (dtdAttribute.isNOTATIONType()) {
			type = schema.createSimpleType(null, schema
					.getBuiltInTypeName(SimpleTypesFactory.NOTATION_TYPE),
					"restriction");
			Iterator<String> values = dtdAttribute.getValues();
			FacetFactory facetFactory = FacetFactory.getInstance();
			while (values.hasNext()) {
				Facet facet = facetFactory.createFacet(Facet.ENUMERATION,
						values.next());
				facet.setOwningType(type);
				type.addFacet(facet);
			}

		} else if (dtdAttribute.isEnumerationType()) {
			type = schema.createSimpleType(null, schema
					.getBuiltInTypeName(SimpleTypesFactory.NMTOKEN_TYPE),
					"restriction");
			Iterator<String> values = dtdAttribute.getValues();
			FacetFactory facetFactory = FacetFactory.getInstance();
			while (values.hasNext()) {
				Facet facet = facetFactory.createFacet(Facet.ENUMERATION,
						values.next());
				facet.setOwningType(type);
				type.addFacet(facet);
			}
		} else {
			String err = "DTD to Schema converter: DTD attribute \""
					+ dtdAttribute.getName();
			err += "\" has unspecified type.";
			throw new DTDException(err);
		}

		schemaAttribute.setSimpleType(type);

		if (dtdAttribute.isREQUIRED()) {
			schemaAttribute.setUse(AttributeDecl.USE_REQUIRED);
		} else if (dtdAttribute.isIMPLIED()) {
			schemaAttribute.setUse(AttributeDecl.USE_OPTIONAL);
		} else if (dtdAttribute.isFIXED()) {
			// 
		} else { // DTD attribute is of "DEFAULT" type
			schemaAttribute.setDefaultValue(dtdAttribute.getDefaultValue());
		}

		return schemaAttribute;
	} // -- convertAttribute

	/**
	 * Marshals XML Schema to output char stream.
	 * 
	 * @param schema
	 *            XML Schema object to marshal.
	 * @param writer
	 *            output char stream to marshal Schema to.
	 * @throws IOException
	 *             if there is an I/O problem with the <tt>writer</tt>.
	 * @throws SAXException
	 *             if an error occured during <tt>schema</tt> marshalling.
	 */
	public void marshalSchema(Schema schema, Writer writer)
			throws IOException, SAXException {

		SchemaWriter sw = new SchemaWriter(writer);
		sw.write(schema);
	} // -- marshalSchema

} // -- Converter
