/**
 * 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$
 */

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.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
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$ $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 {

    StringBuilder header = new StringBuilder().append('\n')
        .append("Converts a DTD to an XML schema.\n\n")
        .append("   <DTD>: Name of the input DTD file.\n")
        .append("   <XSD>: Name of the output XML schema file.\n").append('\n').append("Options:");

    Options options = new Options();

    Option targetNamespace =
        Option.builder("tns").desc("target namespace of the XML schema generated").required(false)
            .longOpt("targetNamespace").hasArg().argName("[prefix:]uri").build();

    Option xmlns = Option.builder("xmlns").desc("xml namespace declarations").required(false)
        .hasArgs().valueSeparator(',').argName("[[prefix:]uri]*").build();

    options.addOption(targetNamespace);
    options.addOption(xmlns);
    options.addOption("h", "help", false, "prints usage information");
    options.addOption("e", "encoding", false, "character encoding");

    CommandLineParser parser = new DefaultParser();
    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 {

    try {
      // -- convert DTD to Schema
      convertDTDtoSchema(in, out, targetNamespace, namespaces);
    } finally {
      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 {
      // -- instantiate char stream for initial parser from the input
      // reader
      InputCharStream 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
      try (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();

        // -- 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<String, String> nameSpaceMap) throws DTDException, SchemaException {

    Schema schema = new Schema();

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

    schema.setTargetNamespace(targetNamespace);

    for (Map.Entry<String, String> entry : nameSpaceMap.entrySet()) {
      schema.addNamespace(entry.getKey(), entry.getValue());
    }

    // convert Notation declarations
    Enumeration<Notation> 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<GeneralEntity> dtdGeneralEntities = dtd.getGeneralEntities();
    if (dtdGeneralEntities.hasMoreElements()) {
      Annotation annotation = new Annotation();

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

        String 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();
    while (dtdElements.hasMoreElements()) {
      Element dtdElement = dtdElements.nextElement();
      ElementDecl 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()
          + "\" has not been specified.";
      throw new DTDException(err);
    }
    complexType.setContentType(contentType);
    // finish converting content of the element

    // start attributes convertion
    Enumeration<Attribute> dtdAttributes = dtdElement.getAttributes();
    while (dtdAttributes.hasMoreElements()) {
      Attribute dtdAttribute = dtdAttributes.nextElement();
      AttributeDecl 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();
      while (children.hasMoreElements()) {
        ContentParticle child = children.nextElement();
        Particle 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()
          + "\" 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
