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

import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.apache.xerces.dom.DocumentImpl;
import org.apache.xerces.impl.Constants;
import org.apache.xerces.impl.xs.SchemaSymbols;
import org.apache.xerces.util.DOMUtil;
import org.apache.xerces.util.NamespaceSupport;
import org.apache.xerces.util.XMLAttributesImpl;
import org.apache.xerces.util.XMLSymbols;
import org.apache.xerces.xni.Augmentations;
import org.apache.xerces.xni.NamespaceContext;
import org.apache.xerces.xni.QName;
import org.apache.xerces.xni.XMLAttributes;
import org.apache.xerces.xni.XMLDocumentHandler;
import org.apache.xerces.xni.XMLLocator;
import org.apache.xerces.xni.XMLResourceIdentifier;
import org.apache.xerces.xni.XMLString;
import org.apache.xerces.xni.XNIException;
import org.apache.xerces.xni.parser.XMLComponent;
import org.apache.xerces.xni.parser.XMLComponentManager;
import org.apache.xerces.xni.parser.XMLConfigurationException;
import org.apache.xerces.xni.parser.XMLDocumentFilter;
import org.apache.xerces.xni.parser.XMLDocumentSource;
import org.apache.xerces.xs.AttributePSVI;
import org.apache.xerces.xs.ElementPSVI;
import org.apache.xerces.xs.ItemPSVI;
import org.apache.xerces.xs.StringList;
import org.apache.xerces.xs.XSAnnotation;
import org.apache.xerces.xs.XSAttributeDeclaration;
import org.apache.xerces.xs.XSAttributeGroupDefinition;
import org.apache.xerces.xs.XSAttributeUse;
import org.apache.xerces.xs.XSComplexTypeDefinition;
import org.apache.xerces.xs.XSConstants;
import org.apache.xerces.xs.XSElementDeclaration;
import org.apache.xerces.xs.XSFacet;
import org.apache.xerces.xs.XSIDCDefinition;
import org.apache.xerces.xs.XSModel;
import org.apache.xerces.xs.XSModelGroup;
import org.apache.xerces.xs.XSModelGroupDefinition;
import org.apache.xerces.xs.XSMultiValueFacet;
import org.apache.xerces.xs.XSNamedMap;
import org.apache.xerces.xs.XSNamespaceItem;
import org.apache.xerces.xs.XSNamespaceItemList;
import org.apache.xerces.xs.XSNotationDeclaration;
import org.apache.xerces.xs.XSObject;
import org.apache.xerces.xs.XSObjectList;
import org.apache.xerces.xs.XSParticle;
import org.apache.xerces.xs.XSSimpleTypeDefinition;
import org.apache.xerces.xs.XSTypeDefinition;
import org.apache.xerces.xs.XSValue;
import org.apache.xerces.xs.XSWildcard;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;

/**
 * This class is a intersepts XNI events and serialized
 * XML infoset and Post Schema Validation Infoset.
 *
 * @author Arun  Yadav,Sun Miscrosystem.
 * @author Peter McCracken, IBM
 * @version $Id: PSVIWriter.java 1062647 2011-01-24 04:45:26Z mrglavas $
 */
public class PSVIWriter implements XMLComponent, XMLDocumentFilter {

    public static final String XERCES_PSVI_NS =
        "http://apache.org/xml/2001/PSVInfosetExtension";

    /** Feature id: augment Post-Schema-Validation-Infoset */
    protected static final String PSVINFOSET =
        Constants.XERCES_FEATURE_PREFIX + Constants.SCHEMA_AUGMENT_PSVI;

    /** Feature id: include ignorable whitespace. */
    protected static final String INCLUDE_IGNORABLE_WHITESPACE =
        "http://apache.org/xml/features/dom/include-ignorable-whitespace";

    /** Include ignorable whitespace. */
    protected boolean fIncludeIgnorableWhitespace;

    /** Recognized features. */
    private static final String[] RECOGNIZED_FEATURES =
        { INCLUDE_IGNORABLE_WHITESPACE, PSVINFOSET, };

    /** Feature defaults. */
    private static final Boolean[] FEATURE_DEFAULTS = { null, null, };

    /** Recognized properties. */
    private static final String[] RECOGNIZED_PROPERTIES = {
    };

    /** Property defaults. */
    private static final Object[] PROPERTY_DEFAULTS = {
    };

    /** PSVInfoset */
    protected boolean fPSVInfoset;

    /** Document handler. */
    protected XMLDocumentHandler fDocumentHandler;

    /** Document source */
    protected XMLDocumentSource fDocumentSource;

    /** The namespace context for the received event stream */
    protected NamespaceContext fNamespaceContext;

    /** The namespace context for the new event stream */
    protected NamespaceContext fPSVINamespaceContext;

    /** Document Location */
    protected XMLLocator fDocumentLocation;

    /** Attributes and Element Info  is  cached in stack */
    private Stack _elementState = new Stack();

    /** The number used for anonymous types */
    protected int fAnonNum;

    /** The number that stores the indent level */
    protected int fIndent;

    /** The map used to store IDs for types and elements */
    protected Map fIDMap;

    /** A list of ids for defined XSObjects */
    protected List fDefined;

    private char[] fIndentChars =
        { '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t' };

    private XMLString newLine = new XMLString(new char[] { '\n' }, 0, 1);

    public PSVIWriter() {
        /*
        System.out.println(
            "Generating Schema Information Set Contribution (PSVI) \n"
                + "which follow as a consequence of validation and/or assessment.");

        System.out.println("NOTE: Requires use of -s and -v");
        System.out.println("Output: generated in " + PSVI_OUTPUT);
        */
    } // <init>()

    public void reset(XMLComponentManager componentManager)
        throws XNIException {

        try {
            fPSVInfoset = componentManager.getFeature(PSVINFOSET);
        }
        catch (XMLConfigurationException e) {
            fPSVInfoset = false;
        }
        fIncludeIgnorableWhitespace =
            componentManager.getFeature(INCLUDE_IGNORABLE_WHITESPACE);

        fAnonNum = 1000;
        fIDMap = new HashMap();
        fDefined = new ArrayList();
        fIndent = 0;
        fPSVINamespaceContext = new NamespaceSupport();
    } // reset(XMLComponentManager)

    /**
     * Returns a list of feature identifiers that are recognized by
     * this component. This method may return null if no features
     * are recognized by this component.
     */
    public String[] getRecognizedFeatures() {
        return RECOGNIZED_FEATURES;
    } // getRecognizedFeatures():String[]

    /**
     * Sets the state of a feature. This method is called by the component
     * manager any time after reset when a feature changes state.
     * <p>
     * <strong>Note:</strong> Components should silently ignore features
     * that do not affect the operation of the component.
     *
     * @param featureId The feature identifier.
     * @param state     The state of the feature.
     *
     * @throws SAXNotRecognizedException The component should not throw
     *                                   this exception.
     * @throws SAXNotSupportedException The component should not throw
     *                                  this exception.
     */
    public void setFeature(String featureId, boolean state)
        throws XMLConfigurationException {
    } // setFeature(String,boolean)

    /**
     * Returns a list of property identifiers that are recognized by
     * this component. This method may return null if no properties
     * are recognized by this component.
     */
    public String[] getRecognizedProperties() {
        return RECOGNIZED_PROPERTIES;
    } // getRecognizedProperties():String[]

    /**
     * Sets the value of a property. This method is called by the component
     * manager any time after reset when a property changes value.
     * <p>
     * <strong>Note:</strong> Components should silently ignore properties
     * that do not affect the operation of the component.
     *
     * @param propertyId The property identifier.
     * @param value      The value of the property.
     *
     * @throws SAXNotRecognizedException The component should not throw
     *                                   this exception.
     * @throws SAXNotSupportedException The component should not throw
     *                                  this exception.
     */
    public void setProperty(String propertyId, Object value)
        throws XMLConfigurationException {

    } // setProperty(String,Object)

    /**
     * Returns the default state for a feature, or null if this
     * component does not want to report a default value for this
     * feature.
     *
     * @param featureId The feature identifier.
     *
     * @since Xerces 2.2.0
     */
    public Boolean getFeatureDefault(String featureId) {
        for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
            if (RECOGNIZED_FEATURES[i].equals(featureId)) {
                return FEATURE_DEFAULTS[i];
            }
        }
        return null;
    } // getFeatureDefault(String):Boolean

    /**
     * Returns the default state for a property, or null if this
     * component does not want to report a default value for this
     * property.
     *
     * @param propertyId The property identifier.
     *
     * @since Xerces 2.2.0
     */
    public Object getPropertyDefault(String propertyId) {
        for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
            if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
                return PROPERTY_DEFAULTS[i];
            }
        }
        return null;
    } // getPropertyDefault(String):Object

    //
    // XMLDocumentSource methods
    //

    /** Sets the document handler to receive information about the document. */
    public void setDocumentHandler(XMLDocumentHandler documentHandler) {
        fDocumentHandler = documentHandler;
    } // setDocumentHandler(XMLDocumentHandler)

    /** Returns the document handler */
    public XMLDocumentHandler getDocumentHandler() {
        return fDocumentHandler;
    } // setDocumentHandler(XMLDocumentHandler)

    //
    // XMLDocumentHandler methods
    //

    /** Sets the document source */
    public void setDocumentSource(XMLDocumentSource source) {
        fDocumentSource = source;
    } // setDocumentSource

    /** Returns the document source */
    public XMLDocumentSource getDocumentSource() {
        return fDocumentSource;
    } // getDocumentSource

    /**
     * This method notifies the start of an entity. General entities are just
     * specified by their name.
     * <p>
     * <strong>Note:</strong> This method is not called for entity references
     * appearing as part of attribute values.
     *
     * @param name     The name of the entity.
     * @param publicId The public identifier of the entity if the entity
     *                 is external, null otherwise.
     * @param systemId The system identifier of the entity if the entity
     *                 is external, null otherwise.
     * @param baseSystemId The base system identifier of the entity if
     *                     the entity is external, null otherwise.
     * @param encoding The auto-detected IANA encoding name of the entity
     *                 stream. This value will be null in those situations
     *                 where the entity encoding is not auto-detected (e.g.
     *                 internal entities or a document entity that is
     *                 parsed from a java.io.Reader).
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void startGeneralEntity(
        String name,
        XMLResourceIdentifier identifier,
        String encoding,
        Augmentations augs)
        throws XNIException {
    } // startEntity(String,String,String,String,String)

    /**
     * Notifies of the presence of a TextDecl line in an entity. If present,
     * this method will be called immediately following the startEntity call.
     * <p>
     * <strong>Note:</strong> This method will never be called for the
     * document entity; it is only called for external general entities
     * referenced in document content.
     * <p>
     * <strong>Note:</strong> This method is not called for entity references
     * appearing as part of attribute values.
     *
     * @param version  The XML version, or null if not specified.
     * @param encoding The IANA encoding name of the entity.
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void textDecl(String version, String encoding, Augmentations augs)
        throws XNIException {
    } // textDecl(String,String)

    /**
     * The start of the document.
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void startDocument(
        XMLLocator locator,
        String encoding,
        NamespaceContext namespaceContext,
        Augmentations augs)
        throws XNIException {
        fNamespaceContext = namespaceContext;
        fDocumentLocation = locator;

        fPSVINamespaceContext.declarePrefix(
            "xsi",
            "http://www.w3.org/2001/XMLSchema-instance");
        fPSVINamespaceContext.declarePrefix("psv", XERCES_PSVI_NS);
        fPSVINamespaceContext.declarePrefix(
            "",
            "http://www.w3.org/2001/05/XMLInfoset");

        if (fDocumentHandler == null)
            return;

        fDocumentHandler.startDocument(
            locator,
            "UTF-8",
            fPSVINamespaceContext,
            null);

        List attributes = new ArrayList();
        attributes.add("xmlns:xsi");
        attributes.add("http://www.w3.org/2001/XMLSchema-instance");
        attributes.add(XMLSymbols.fCDATASymbol);
        attributes.add("xmlns:psv");
        attributes.add(XERCES_PSVI_NS);
        attributes.add(XMLSymbols.fCDATASymbol);
        attributes.add("xmlns");
        attributes.add("http://www.w3.org/2001/05/XMLInfoset");
        attributes.add(XMLSymbols.fCDATASymbol);
        sendIndentedElement("document", attributes);
    } // startDocument(XMLLocator,String)

    /**
     * Notifies of the presence of an XMLDecl line in the document. If
     * present, this method will be called immediately following the
     * startDocument call.
     *
     * @param version    The XML version.
     * @param encoding   The IANA encoding name of the document, or null if
     *                   not specified.
     * @param standalone The standalone value, or null if not specified.
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void xmlDecl(
        String version,
        String encoding,
        String standalone,
        Augmentations augs)
        throws XNIException {
        if (fDocumentHandler == null)
            return;

        sendElementEvent("characterEncodingScheme", encoding);
        sendElementEvent("standalone", standalone);
        sendElementEvent("version", version);
    } // xmlDecl(String,String,String)

    /**
     * Notifies of the presence of the DOCTYPE line in the document.
     *
     * @param rootElement The name of the root element.
     * @param publicId    The public identifier if an external DTD or null
     *                    if the external DTD is specified using SYSTEM.
     * @param systemId    The system identifier if an external DTD, null
     *                    otherwise.
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void doctypeDecl(
        String rootElement,
        String publicId,
        String systemId,
        Augmentations augs)
        throws XNIException {
        if (fDocumentHandler == null)
            return;

        checkForChildren();
        sendIndentedElement("docTypeDeclaration");
        if (publicId != null)
            sendElementEvent("publicIdentifier", publicId);
        if (systemId != null)
            sendElementEvent("systemIdentifier", systemId);
        sendUnIndentedElement("docTypeDeclaration");
    } // doctypeDecl(String,String,String)

    /**
     * A comment.
     *
     * @param text The text in the comment.
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by application to signal an error.
     */
    public void comment(XMLString text, Augmentations augs)
        throws XNIException {
        if (fDocumentHandler == null)
            return;

        checkForChildren();
        sendIndentedElement("comment");
        sendElementEvent("content", text);
        sendUnIndentedElement("comment");
    } // comment(XMLString)

    /**
     * A processing instruction. Processing instructions consist of a
     * target name and, optionally, text data. The data is only meaningful
     * to the application.
     * <p>
     * Typically, a processing instruction's data will contain a series
     * of pseudo-attributes. These pseudo-attributes follow the form of
     * element attributes but are <strong>not</strong> parsed or presented
     * to the application as anything other than text. The application is
     * responsible for parsing the data.
     *
     * @param target The target.
     * @param data   The data or null if none specified.
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void processingInstruction(
        String target,
        XMLString data,
        Augmentations augs)
        throws XNIException {
        if (fDocumentHandler == null)
            return;

        checkForChildren();
        sendIndentedElement("processingInstruction");
        sendElementEvent("target", target);
        sendElementEvent("content", data);
        sendUnIndentedElement("processingInstruction");
    } // processingInstruction(String,XMLString)

    /**
     * Binds the namespaces. This method will handle calling the
     * document handler to start the prefix mappings.
     * <p>
     * <strong>Note:</strong> This method makes use of the
     * fAttributeQName variable. Any contents of the variable will
     * be destroyed. Caller should copy the values out of this
     * temporary variable before calling this method.
     *
     * @param element    The name of the element.
     * @param attributes The element attributes.
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void startElement(
        QName element,
        XMLAttributes attributes,
        Augmentations augs)
        throws XNIException {
        if (fDocumentHandler == null)
            return;

        checkForChildren();

        _elementState.push(new ElementState(true));

        sendIndentedElement("element");
        sendElementEvent("namespaceName", element.uri);
        sendElementEvent("localName", element.localpart);
        sendElementEvent("prefix", element.prefix);
        processAttributes(attributes);
        processInScopeNamespaces();
        sendElementEvent("baseURI", fDocumentLocation.getBaseSystemId());
        if (fPSVInfoset) {
            processPSVIStartElement(augs);
        }
    } // startElement(QName,XMLAttributes)

    /**
     * An empty element.
     *
     * @param element    The name of the element.
     * @param attributes The element attributes.
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void emptyElement(
        QName element,
        XMLAttributes attributes,
        Augmentations augs)
        throws XNIException {
        if (fDocumentHandler == null)
            return;

        checkForChildren();
        sendIndentedElement("element");
        sendElementEvent("namespaceName", element.uri);
        sendElementEvent("localName", element.localpart);
        sendElementEvent("prefix", element.prefix);
        processAttributes(attributes);
        processInScopeNamespaces();
        sendElementEvent("baseURI", fDocumentLocation.getBaseSystemId());
        if (fPSVInfoset) {
            processPSVIStartElement(augs);
        }
        sendEmptyElementEvent("children");
        if (fPSVInfoset) {
            processPSVIEndElement(augs);
        }
        sendUnIndentedElement("element");
    } // emptyElement(QName,XMLAttributes)

    /**
     * Character content.
     *
     * @param text The content.
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void characters(XMLString text, Augmentations augs)
        throws XNIException {
        if (fDocumentHandler == null)
            return;

        checkForChildren();
        sendIndentedElement("character");
        sendElementEvent("textContent", text);
        // detecting whitespace is not relevant here
        // this is only useful if characters are output individually
        sendUnIndentedElement("character");
    } // characters(XMLString)

    /**
     * Ignorable whitespace. For this method to be called, the document
     * source must have some way of determining that the text containing
     * only whitespace characters should be considered ignorable. For
     * example, the validator can determine if a length of whitespace
     * characters in the document are ignorable based on the element
     * content model.
     *
     * @param text The ignorable whitespace.
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void ignorableWhitespace(XMLString text, Augmentations augs)
        throws XNIException {
        if (fDocumentHandler == null)
            return;

        if (fIncludeIgnorableWhitespace) {
            this.characters(text, augs);
        }
    } // ignorableWhitespace(XMLString)

    /**
     * The end of an element.
     *
     * @param element The name of the element.
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void endElement(QName element, Augmentations augs)
        throws XNIException {
        if (fDocumentHandler == null)
            return;

        ElementState fElementState = (ElementState)_elementState.peek();
        if (fElementState.isEmpty) {
            sendEmptyElementEvent("children");
        }
        else {
            sendUnIndentedElement("children");
        }
        _elementState.pop();
        if (fPSVInfoset) {
            processPSVIStartElement(augs);
            processPSVIEndElement(augs);
        }
        sendUnIndentedElement("element");
    } // endElement(QName)

    /**
     * The start of a CDATA section.
     *
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void startCDATA(Augmentations augs) throws XNIException {
    } // startCDATA()

    /**
     * The end of a CDATA section.
     *
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void endCDATA(Augmentations augs) throws XNIException {
    } // endCDATA()

    /**
     * The end of the document.
     *
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void endDocument(Augmentations augs) throws XNIException {
        if (fDocumentHandler == null)
            return;

        sendUnIndentedElement("children");
        sendElementEvent("documentElement");
        // these aren't relevent for PSVI
        sendEmptyElementEvent("notations");
        sendEmptyElementEvent("unparsedEntities");

        sendElementEvent("baseURI", fDocumentLocation.getBaseSystemId());

        // do we ALWAYS process all declarations?  I think so - PJM
        // this isn't relevant to PSVI
        sendElementEvent("allDeclarationsProcessed", "true");
        sendUnIndentedElement("document");
        fDocumentHandler.endDocument(null);
    } // endDocument()

    /**
     * This method notifies the end of an entity. General entities are just
     * specified by their name.
     * <p>
     * <strong>Note:</strong> This method is not called for entity references
     * appearing as part of attribute values.
     *
     * @param name The name of the entity.
     * @param augs   Additional information that may include infoset augmentations
     *
     * @throws XNIException Thrown by handler to signal an error.
     */
    public void endGeneralEntity(String name, Augmentations augs)
        throws XNIException {
    } // endEntity(String)

    /**
     * Write an unordered set of attribute information items, one for each of
     * the attributes (specified or defaulted from the DTD) of this element.
     * Namespace declarations do not appear in this set. If the element has no
     * attributes, this set has no members.
     */
    private void processAttributes(XMLAttributes attributes) {
        boolean namespaceAttribute = false;
        boolean attrElement = false;

         int attrCount = attributes == null ? 0 : attributes.getLength();

        if (attrCount == 0) {
            sendEmptyElementEvent("attributes");
            sendEmptyElementEvent("namespaceAttributes");
            return;
        }

        for (int i = 0; i < attrCount; i++) {
            String localpart = attributes.getLocalName(i);
            String prefix = attributes.getPrefix(i);
            if (prefix.equals(XMLSymbols.PREFIX_XMLNS)
                || localpart.equals(XMLSymbols.PREFIX_XMLNS)) {
                namespaceAttribute = true;
                continue;
            }
            if (!attrElement)
                sendIndentedElement("attributes");

            sendIndentedElement("attribute");
            sendElementEvent("namespaceName", attributes.getURI(i));
            sendElementEvent("localName", attributes.getLocalName(i));
            sendElementEvent("prefix", attributes.getPrefix(i));
            sendElementEvent("normalizedValue", attributes.getValue(i));
            sendElementEvent(
                "specified",
                String.valueOf(attributes.isSpecified(i)));
            sendElementEvent("attributeType", attributes.getType(i));

            // this property isn't relevent to PSVI
            sendElementEvent("references");

            if (fPSVInfoset) {
                processPSVIAttribute(attributes.getAugmentations(i));
            }
            sendUnIndentedElement("attribute");
            attrElement = true;
        }
        if (attrElement) {
            sendUnIndentedElement("attributes");
        }
        else {
            sendEmptyElementEvent("attributes");
        }

        if (namespaceAttribute) {
            processNamespaceAttributes(attributes);
        }
        else {
            sendEmptyElementEvent("namespaceAttributes");
        }
    } //printAttributes

    /**
    * Write an unordered set of attribute information items, one for each of
    * the namespace declarations (specified or defaulted from the DTD) of this
    * element. A declaration of the form xmlns="", which undeclares the default
    * namespace, counts as a namespace declaration. By definition, all
    * namespace attributes (including those named xmlns, whose [prefix]
    * property has no value) have a namespace URI of
    * http://www.w3.org/2000/xmlns/. If the element has no namespace
    * declarations, this set has no members
    */
    private void processNamespaceAttributes(XMLAttributes attributes) {

        // we don't need to check for null, since that was checked for in processAttributes()
        int attrCount = attributes.getLength();

        sendIndentedElement("namespaceAttributes");
        for (int i = 0; i < attrCount; i++) {
            String localpart = attributes.getLocalName(i);
            String prefix = attributes.getPrefix(i);
            if (!(prefix.equals(XMLSymbols.PREFIX_XMLNS)
                || localpart.equals(XMLSymbols.PREFIX_XMLNS)))
                continue;
            sendIndentedElement("attribute");
            sendElementEvent("namespaceName", NamespaceContext.XMLNS_URI);
            sendElementEvent("localName", localpart);
            sendElementEvent("prefix", prefix);
            sendElementEvent("normalizedValue", attributes.getValue(i));
            sendElementEvent(
                "specified",
                String.valueOf(attributes.isSpecified(i)));
            sendElementEvent("attributeType", attributes.getType(i));
            // this property isn't relevent to PSVI
            sendElementEvent("references");
            if (fPSVInfoset) {
                processPSVIAttribute(attributes.getAugmentations(i));
            }
            sendUnIndentedElement("attribute");
        }
        sendUnIndentedElement("namespaceAttributes");

    } //printNamespacesAttributes()

    /**
     * Write an unordered set of namespace information items, one for each of the
     * namespaces in effect for this element. This set always contains an item
     * with the prefix xml which is implicitly bound to the namespace name
     * http://www.w3.org/XML/1998/namespace. It does not contain an item with the
     * prefix xmlns (used for declaring namespaces), since an application can
     * never encounter an element or attribute with that prefix. The set will
     * include namespace items corresponding to all of the members of
     * [namespace attributes], except for any representing a declaration of the
     * form xmlns="", which does not declare a namespace but rather undeclares
     * the default namespace
     */
    private void processInScopeNamespaces() {
        sendIndentedElement("inScopeNamespaces");
        sendIndentedElement("namespace");
        // print 'xml' binding
        sendElementEvent("prefix", "xml");
        sendElementEvent("namespaceName", NamespaceContext.XML_URI);
        sendUnIndentedElement("namespace");
        Enumeration prefixes = fNamespaceContext.getAllPrefixes();
        while (prefixes.hasMoreElements()) {
            sendIndentedElement("namespace");

            String prefix = (String)prefixes.nextElement();
            String uri = fNamespaceContext.getURI(prefix);
            sendElementEvent("prefix", prefix);
            sendElementEvent("namespaceName", uri);
            sendUnIndentedElement("namespace");

        }
        sendUnIndentedElement("inScopeNamespaces");
    } //printinScopeNamespaces()

    /* The following information will be available at the startElement call:
    * name, namespace, type, notation, validation context
    *
    * The following information will be available at the endElement call:
    * nil, specified, normalized value, member type, validity, error codes,
    * default
    */
    private void processPSVIStartElement(Augmentations augs) {
        if (augs == null)
            return;
        ElementPSVI elemPSVI =
            (ElementPSVI)augs.getItem(Constants.ELEMENT_PSVI);
        if (elemPSVI != null) {
            // Should we store the values till end element call? -- AY
            // I don't think so -- PJM
        }
    }

    /* The following information will be available at the startElement call:
    * name, namespace, type, notation, validation context
    *
    * The following information will be available at the endElement call:
    * nil, specified, normalized value, member type, validity, error codes,
    * default
    */
    private void processPSVIEndElement(Augmentations augs) {
        if (augs == null)
            return;
        ElementPSVI elemPSVI =
            (ElementPSVI)augs.getItem(Constants.ELEMENT_PSVI);
        if (elemPSVI != null) {

            processPSVISchemaInformation(elemPSVI);
            sendElementEvent(
                "psv:validationAttempted",
                this.translateValidationAttempted(
                    elemPSVI.getValidationAttempted()));
            // Would rather getValidationContext() return element info item.
            // This is non the same as XSV.
            sendElementEvent(
                "psv:validationContext",
                elemPSVI.getValidationContext());

            sendElementEvent(
                "psv:validity",
                this.translateValidity(elemPSVI.getValidity()));

            processPSVISchemaErrorCode(elemPSVI.getErrorCodes());
            sendElementEvent(
                "psv:schemaNormalizedValue",
                getSchemaNormalizedValue(elemPSVI));
            sendElementEvent(
                "psv:schemaSpecified",
                elemPSVI.getIsSchemaSpecified() ? "schema" : "infoset");
            sendElementEvent("psv:schemaDefault", elemPSVI.getSchemaDefault());

            processPSVITypeDefinitionRef(
                "psv:typeDefinition",
                elemPSVI.getTypeDefinition());
            processPSVITypeDefinitionRef(
                "psv:memberTypeDefinition",
                elemPSVI.getMemberTypeDefinition());
            // A value for nil is not necessary, since we output declaration, instead.
            // See http://www.w3.org/TR/xmlschema-1/#section-Element-Declaration-Information-Set-Contributions.
            sendElementEvent("psv:nil");

            sendIndentedElement("psv:declaration");
            processPSVIElementRef(
                "psv:elementDeclaration",
                elemPSVI.getElementDeclaration());
            sendUnIndentedElement("psv:declaration");
            processPSVIElementRef("psv:notation", elemPSVI.getNotation());
            // idref table does not have to be exposed, and is not exposed
            sendElementEvent("psv:idIdrefTable");
            // identity constraint table does not have to be exposed, and is not exposed
            sendElementEvent("psv:identityConstraintTable");
        }
    }

    private void processPSVIAttribute(Augmentations augs) {
        if (augs == null)
            return;
        AttributePSVI attrPSVI =
            (AttributePSVI)augs.getItem(Constants.ATTRIBUTE_PSVI);
        if (attrPSVI != null) {
            sendElementEvent(
                "psv:validationAttempted",
                this.translateValidationAttempted(
                    attrPSVI.getValidationAttempted()));
            // Would rather getValidationContext() return element info item.
            // This is not the same as XSV.
            sendElementEvent(
                "psv:validationContext",
                attrPSVI.getValidationContext());

            sendElementEvent(
                "psv:validity",
                this.translateValidity(attrPSVI.getValidity()));

            processPSVISchemaErrorCode(attrPSVI.getErrorCodes());
            sendElementEvent(
                "psv:schemaNormalizedValue",
                getSchemaNormalizedValue(attrPSVI));
            sendElementEvent(
                "psv:schemaSpecified",
                attrPSVI.getIsSchemaSpecified() ? "schema" : "infoset");
            sendElementEvent("psv:schemaDefault", attrPSVI.getSchemaDefault());

            processPSVITypeDefinitionRef(
                "psv:typeDefinition",
                attrPSVI.getTypeDefinition());
            processPSVITypeDefinitionRef(
                "psv:memberTypeDefinition",
                attrPSVI.getMemberTypeDefinition());

            if (attrPSVI.getAttributeDeclaration() == null) {
                sendElementEvent("psv:declaration");
            }
            else {
                sendIndentedElement("psv:declaration");
                processPSVIAttributeDeclarationRef(
                    attrPSVI.getAttributeDeclaration());
                sendUnIndentedElement("psv:declaration");
            }
        }
    }

    private void processPSVISchemaErrorCode(StringList errorCodes) {
        StringBuffer errorBuffer = new StringBuffer();
        if (errorCodes != null && errorCodes.getLength() > 0) {
            for (int i = 0; i < errorCodes.getLength() - 1; i++) {
                errorBuffer.append(errorCodes.item(i));
                errorBuffer.append(" ");
            }
            errorBuffer.append(errorCodes.item(errorCodes.getLength() - 1));
        }
        sendElementEvent("psv:schemaErrorCode", errorBuffer.toString());
    }

    private void processPSVISchemaInformation(ElementPSVI elemPSVI) {
        if (elemPSVI == null)
            return;
        XSModel schemaInfo = elemPSVI.getSchemaInformation();
        XSNamespaceItemList schemaNamespaces =
            schemaInfo == null ? null : schemaInfo.getNamespaceItems();
        if (schemaNamespaces == null || schemaNamespaces.getLength() == 0) {
            sendElementEvent("psv:schemaInformation");
        }
        else {
            sendIndentedElement("psv:schemaInformation");
            for (int i = 0; i < schemaNamespaces.getLength(); i++) {
                processPSVINamespaceItem(schemaNamespaces.item(i));
            }
            sendUnIndentedElement("psv:schemaInformation");
        }
    }

    private void processPSVINamespaceItem(XSNamespaceItem item) {
        if (item == null)
            return;

        String namespace = item.getSchemaNamespace();
        if (namespace != null && namespace.equals(Constants.NS_XMLSCHEMA)) {
            // we don't want to output information for schema for schemas
            return;
        }

        sendIndentedElement("psv:namespaceSchemaInformation");
        sendElementEvent("psv:schemaNamespace", namespace);

        // print out schema components
        processPSVISchemaComponents(item);

        // print out schema document information
        processPSVISchemaDocuments(item);

        // print out schema annotations
        processPSVISchemaAnnotations(item.getAnnotations());
        sendUnIndentedElement("psv:namespaceSchemaInformation");
    }

    private void processPSVISchemaDocuments(XSNamespaceItem item) {
        StringList locations =
            item == null ? null : item.getDocumentLocations();
        if (locations == null || locations.getLength() == 0) {
            sendEmptyElementEvent("psv:schemaDocuments");
            return;
        }
        sendIndentedElement("psv:schemaDocuments");
        for (int i = 0; i < locations.getLength(); i++) {
            sendIndentedElement("psv:schemaDocument");
            sendElementEvent("psv:documentLocation", locations.item(i));
            // It's supposed to point to a <document> element, and we're not really
            // dealing with those (except for the one at the root)
            sendElementEvent("psv:document");
            sendUnIndentedElement("psv:schemaDocument");
        }
        sendUnIndentedElement("psv:schemaDocuments");
    }

    private void processPSVISchemaComponents(XSNamespaceItem item) {
        if (item == null) {
            sendEmptyElementEvent("psv:schemaComponents");
            return;
        }

        // it we happen to not get any components, this will output a start tag
        // and a close tag, instead of an empty element tag.  This isn't a big
        // deal, though
        sendIndentedElement("psv:schemaComponents");

        // typeDefinitions
        XSNamedMap components = item.getComponents(XSConstants.TYPE_DEFINITION);
        for (int i = 0; i < components.getLength(); i++) {
            processPSVITypeDefinition((XSTypeDefinition)components.item(i));
        }
        // elementDeclarations
        components = item.getComponents(XSConstants.ELEMENT_DECLARATION);
        for (int i = 0; i < components.getLength(); i++) {
            processPSVIElementDeclaration(
                (XSElementDeclaration)components.item(i));
        }
        // attributeDeclarations
        components = item.getComponents(XSConstants.ATTRIBUTE_DECLARATION);
        for (int i = 0; i < components.getLength(); i++) {
            processPSVIAttributeDeclaration(
                (XSAttributeDeclaration)components.item(i));
        }
        // modelGroupDefinitions
        components = item.getComponents(XSConstants.MODEL_GROUP_DEFINITION);
        for (int i = 0; i < components.getLength(); i++) {
            processPSVIModelGroupDefinition(
                (XSModelGroupDefinition)components.item(i));
        }
        // attributeGroupDefinitions
        components = item.getComponents(XSConstants.ATTRIBUTE_GROUP);
        for (int i = 0; i < components.getLength(); i++) {
            processPSVIAttributeGroupDefinition(
                (XSAttributeGroupDefinition)components.item(i));
        }
        // notationDeclarations
        components = item.getComponents(XSConstants.NOTATION_DECLARATION);
        for (int i = 0; i < components.getLength(); i++) {
            processPSVINotationDeclaration(
                (XSNotationDeclaration)components.item(i));
        }
        sendUnIndentedElement("psv:schemaComponents");
    }

    private void processPSVITypeDefinition(XSTypeDefinition type) {
        if (type == null)
            return;
        if (type.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
            processPSVIComplexTypeDefinition((XSComplexTypeDefinition)type);
        }
        else if (type.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) {
            processPSVISimpleTypeDefinition((XSSimpleTypeDefinition)type);
        }
        else {
            throw new IllegalArgumentException(
                "Unknown type definition value: " + type.getType());
        }
    }

    private void processPSVIComplexTypeDefinition(XSComplexTypeDefinition type) {
        if (type == null)
            return;
        sendIndentedElementWithID("psv:complexTypeDefinition", type);
        sendElementEvent("psv:name", type.getName());
        sendElementEvent("psv:targetNamespace", type.getNamespace());
        processPSVITypeDefinitionOrRef(
            "psv:baseTypeDefinition",
            type.getBaseType());
        sendElementEvent(
            "psv:derivationMethod",
            this.translateDerivation(type.getDerivationMethod()));
        sendElementEvent("psv:final", this.translateBlockOrFinal(type.getFinal()));
        sendElementEvent("psv:abstract", String.valueOf(type.getAbstract()));
        processPSVIAttributeUses(type.getAttributeUses());
        processPSVIAttributeWildcard(type.getAttributeWildcard());
        sendIndentedElement("psv:contentType");
        sendElementEvent(
            "psv:variety",
            this.translateContentType(type.getContentType()));
        XSSimpleTypeDefinition simpleType = type.getSimpleType();
        if(simpleType == null || (!simpleType.getAnonymous() || fDefined.contains(this.getID(simpleType)))) {
            processPSVIElementRef("psv:simpleTypeDefinition", simpleType);
        }
        else {
            processPSVISimpleTypeDefinition(simpleType);
        }
        processPSVIParticle(type.getParticle());
        sendUnIndentedElement("psv:contentType");
        sendElementEvent(
            "psv:prohibitedSubstitutions",
            this.translateBlockOrFinal(type.getProhibitedSubstitutions()));
        processPSVIAnnotations(type.getAnnotations());
        sendUnIndentedElement("psv:complexTypeDefinition");
    }

    private void processPSVISimpleTypeDefinition(XSSimpleTypeDefinition type) {
        if (type == null) {
        	sendElementEvent("psv:simpleTypeDefinition");
            return;
        }
            
        sendIndentedElementWithID("psv:simpleTypeDefinition", type);
        sendElementEvent("psv:name", type.getName());
        sendElementEvent("psv:targetNamespace", type.getNamespace());
        processPSVITypeDefinitionOrRef(
            "psv:baseTypeDefinition",
            type.getBaseType());
        processPSVITypeDefinitionOrRef(
            "psv:primitiveTypeDefinition",
            type.getPrimitiveType());
        processPSVIFacets(type);

        sendIndentedElement("psv:fundamentalFacets");
        sendIndentedElement("psv:ordered");
        sendElementEvent("psv:value", this.translateOrdered(type.getOrdered()));
        sendUnIndentedElement("psv:ordered");
        sendIndentedElement("psv:bounded");
        sendElementEvent("psv:value", String.valueOf(type.getBounded()));
        sendUnIndentedElement("psv:bounded");
        sendIndentedElement("psv:cardinality");
        sendElementEvent("psv:value", String.valueOf(type.getFinite()));
        sendUnIndentedElement("psv:cardinality");
        sendIndentedElement("psv:numeric");
        sendElementEvent("psv:value", String.valueOf(type.getNumeric()));
        sendUnIndentedElement("psv:numeric");
        sendUnIndentedElement("psv:fundamentalFacets");

        sendElementEvent("psv:final", this.translateBlockOrFinal(type.getFinal()));
        sendElementEvent(
            "psv:variety",
            this.translateVariety(type.getVariety()));
        processPSVITypeDefinitionOrRef(
            "psv:itemTypeDefinition",
            type.getItemType());
        processPSVIMemberTypeDefinitions(type.getMemberTypes());
        processPSVIAnnotations(type.getAnnotations());
        sendUnIndentedElement("psv:simpleTypeDefinition");
    }

    private void processPSVIFacets(XSSimpleTypeDefinition type) {
        if (type == null)
            return;
        XSObjectList facets = type.getFacets();
        XSObjectList multiValueFacets = type.getMultiValueFacets();
        if ((facets == null || facets.getLength() == 0)
            && (multiValueFacets == null || multiValueFacets.getLength() == 0)) {
            sendElementEvent("psv:facets");
        }
        else {
            sendIndentedElement("psv:facets");
            if (facets != null) {
                for (int i = 0; i < facets.getLength(); i++) {
                    XSFacet facet = (XSFacet)facets.item(i);
                    String name = this.translateFacetKind(facet.getFacetKind());
                    sendIndentedElement("psv:" + name);
                    sendElementEvent("psv:value", facet.getLexicalFacetValue());
                    sendElementEvent(
                        "psv:fixed",
                        String.valueOf(facet.getFixed()));
                    processPSVIAnnotation(facet.getAnnotation());
                    sendUnIndentedElement("psv:" + name);
                }
            }
            if (multiValueFacets != null) {
                for (int i = 0; i < multiValueFacets.getLength(); i++) {
                    XSMultiValueFacet facet =
                        (XSMultiValueFacet)multiValueFacets.item(i);
                    String name = this.translateFacetKind(facet.getFacetKind());
                    sendIndentedElement("psv:" + name);
                    StringList values = facet.getLexicalFacetValues();
                    for (int j = 0; j < values.getLength(); j++) {
                        sendElementEvent("psv:value", values.item(j));
                    }
                    sendElementEvent("psv:fixed", "false");
                    processPSVIAnnotations(facet.getAnnotations());
                    sendUnIndentedElement("psv:" + name);
                }
            }
            sendUnIndentedElement("psv:facets");
        }
    }

    private void processPSVIMemberTypeDefinitions(XSObjectList memTypes) {
        if (memTypes == null || memTypes.getLength() == 0) {
            sendElementEvent("psv:memberTypeDefinitions");
        }
        else {
            sendIndentedElement("psv:memberTypeDefinitions");
            for (int i = 0; i < memTypes.getLength(); i++) {
                processPSVITypeDefinitionOrRef(
                    "psv:memberTypeDefinition",
                    (XSTypeDefinition)memTypes.item(i));
            }
            sendUnIndentedElement("psv:memberTypeDefinitions");
        }
    }

    /* It's possible that this method will send events for null annotations, i.e.
     * <psv:annotations>
     *     <psv:annotation xsi:nil="true"/>
     *     <psv:annotation>...</psv:annotation>
     * </psv:annotations>
     *
     * This is because of the way multi-value facet is implemented.  It represents
     * the annotation on each value of the facet, and if a value doesn't have one,
     * it's corresponding annotation is null.  Thus, it's possible for the first
     * annotation to be null, but the second one to be exist.
     *
     * An exception to this is if all of the annotations are null; then I output
     * <psv:annotations xsi:nil="true"/>
     */
    private void processPSVIAnnotations(XSObjectList annotations) {
        boolean empty = true;
        if (annotations != null && annotations.getLength() > 0) {
            for (int i = 0; i < annotations.getLength(); i++) {
                if (annotations.item(i) != null) {
                    empty = false;
                    break;
                }
            }
        }

        if (empty) {
            sendElementEvent("psv:annotations");
        }
        else {
            sendIndentedElement("psv:annotations");
            for (int i = 0; i < annotations.getLength(); i++) {
                processPSVIAnnotation((XSAnnotation)annotations.item(i));
            }
            sendUnIndentedElement("psv:annotations");
        }
    }

    private void processPSVISchemaAnnotations(XSObjectList annotations) {
        if (annotations == null || annotations.getLength() == 0) {
            sendElementEvent("psv:schemaAnnotations");
        }
        else {
            sendIndentedElement("psv:schemaAnnotations");
            for (int i = 0; i < annotations.getLength(); i++) {
                processPSVIAnnotation((XSAnnotation)annotations.item(i));
            }
            sendUnIndentedElement("psv:schemaAnnotations");
        }
    }

    private void processPSVIAttributeUses(XSObjectList uses) {
        if (uses == null || uses.getLength() == 0) {
            sendElementEvent("psv:attributeUses");
        }
        else {
            sendIndentedElement("psv:attributeUses");
            for (int i = 0; i < uses.getLength(); i++) {
                XSAttributeUse use = (XSAttributeUse)uses.item(i);
                sendIndentedElement("psv:attributeUse");
                sendElementEvent("psv:required", String.valueOf(use.getRequired()));
                processPSVIAttributeDeclarationOrRef(use.getAttrDeclaration());
                processPSVIValueConstraint(use.getConstraintType(), getConstraintValue(use));
                sendUnIndentedElement("psv:attributeUse");
            }
            sendUnIndentedElement("psv:attributeUses");
        }
    }

    private void processPSVIAttributeWildcard(XSWildcard wildcard) {
        if (wildcard == null) {
            sendElementEvent("psv:attributeWildcard");
        }
        else {
            sendIndentedElement("psv:attributeWildcard");
            processPSVIWildcard(wildcard);
            sendUnIndentedElement("psv:attributeWildcard");
        }
    }

    private void processPSVIWildcard(XSWildcard wildcard) {
        if (wildcard == null)
            return;
        sendIndentedElement("psv:wildcard");
        sendIndentedElement("psv:namespaceConstraint");
        sendElementEvent(
            "psv:variety",
            this.translateConstraintType(wildcard.getConstraintType()));

        StringBuffer constraintBuffer = new StringBuffer();
        StringList constraints = wildcard.getNsConstraintList();
        if (constraints != null && constraints.getLength() > 0) {
            for (int i = 0; i < constraints.getLength() - 1; i++) {
                constraintBuffer.append(constraints.item(i));
                constraintBuffer.append(" ");
            }
            constraintBuffer.append(
                constraints.item(constraints.getLength() - 1));
        }
        sendElementEvent("psv:namespaces", constraintBuffer.toString());

        sendUnIndentedElement("psv:namespaceConstraint");
        sendElementEvent(
            "psv:processContents",
            this.translateProcessContents(wildcard.getProcessContents()));
        processPSVIAnnotation(wildcard.getAnnotation());
        sendUnIndentedElement("psv:wildcard");
    }

    private void processPSVIAnnotation(XSAnnotation ann) {
        if (ann == null) {
            sendElementEvent("psv:annotation");
        }
        else {
            sendIndentedElement("psv:annotation");
            // We can't get all the information from DOM, but I've outputed what
            // we can get -- PJM
            Node dom = new DocumentImpl();
            ann.writeAnnotation(dom, XSAnnotation.W3C_DOM_DOCUMENT);

            // this child will be the annotation element
            Element annot = DOMUtil.getFirstChildElement(dom);

            processDOMElement(
                annot,
                SchemaSymbols.ELT_APPINFO,
                "psv:applicationInformation");
            processDOMElement(
                annot,
                SchemaSymbols.ELT_DOCUMENTATION,
                "psv:userInformation");
            processDOMAttributes(annot);
            sendUnIndentedElement("psv:annotation");
        }
    }

    private void processDOMElement(
        Node node,
        String elementName,
        String tagName) {
        if (node == null)
            return;
        boolean foundElem = false;
        for (Element child = DOMUtil.getFirstChildElement(node);
            child != null;
            child = DOMUtil.getNextSiblingElement(child)) {
            if (DOMUtil.getLocalName(child).equals(elementName)) {
                if (!foundElem) {
                    sendIndentedElement(tagName);
                    foundElem = true;
                }
                sendIndentedElement("element");
                sendElementEvent(
                    "namespaceName",
                    DOMUtil.getNamespaceURI(child));
                sendElementEvent("localName", DOMUtil.getLocalName(child));
                sendElementEvent("prefix", child.getPrefix());
                sendIndentedElement("children");
                sendIndentedElement("character");
                sendElementEvent("textContent", DOMUtil.getChildText(child));
                sendUnIndentedElement("character");
                sendUnIndentedElement("children");

                //Create XMLAttributes from DOM
                Attr[] atts = (Element) child == null ? null : DOMUtil.getAttrs((Element) child);
                XMLAttributes attrs = new XMLAttributesImpl();
                for (int i=0; i<atts.length; i++) {
                    Attr att = (Attr)atts[i];
                    attrs.addAttribute(
                            new QName(att.getPrefix(), att.getLocalName(), att.getName(), att.getNamespaceURI()),
                            "CDATA" ,att.getValue()
                            );
                }

                processAttributes(attrs);
                sendUnIndentedElement("element");
            }
        }
        if (foundElem) {
            sendUnIndentedElement(tagName);
        }
        else {
            sendEmptyElementEvent(tagName);
        }
    }

    private void processDOMAttributes(Element elem) {
        Attr[] atts = elem == null ? null : DOMUtil.getAttrs(elem);

        boolean namespaceAttribute = false;
        boolean attrElement = false;

        int attrCount = atts == null ? 0 : atts.length;

        if (attrCount == 0) {
            sendEmptyElementEvent("attributes");
            sendEmptyElementEvent("namespaceAttributes");
            return;
        }

        for (int i = 0; i < attrCount; i++) {
            Attr att = (Attr)atts[i];
            String localpart = DOMUtil.getLocalName(att);
            String prefix = att.getPrefix();
            if (localpart.equals(XMLSymbols.PREFIX_XMLNS)
                || prefix.equals(XMLSymbols.PREFIX_XMLNS)) {
                namespaceAttribute = true;
                continue;
            }
            if (!attrElement)
                sendIndentedElement("attributes");

            sendIndentedElement("attribute");
            sendElementEvent("namespaceName", DOMUtil.getNamespaceURI(att));
            sendElementEvent("localName", DOMUtil.getLocalName(att));
            sendElementEvent("prefix", att.getPrefix());
            sendElementEvent("normalizedValue", att.getValue());
            sendElementEvent(
                "specified",
                String.valueOf(att.getSpecified()));
            sendElementEvent("attributeType");

            // this property isn't relevent to PSVI
            sendElementEvent("references");

            sendUnIndentedElement("attribute");
            attrElement = true;
        }
        if (attrElement) {
            sendUnIndentedElement("attributes");
        }
        else {
            sendEmptyElementEvent("attributes");
        }

        if (namespaceAttribute) {
            sendIndentedElement("namespaceAttributes");
            for (int i = 0; i < attrCount; i++) {
                Attr att = (Attr)atts[i];
                String localpart = DOMUtil.getLocalName(att);
                String prefix = att.getPrefix();
                if (localpart.equals(XMLSymbols.PREFIX_XMLNS)
                    || prefix.equals(XMLSymbols.PREFIX_XMLNS)) {

                    sendIndentedElement("attribute");
                    sendElementEvent("namespaceName", DOMUtil.getNamespaceURI(att));
                    sendElementEvent("localName", DOMUtil.getLocalName(att));
                    sendElementEvent("prefix", att.getPrefix());
                    sendElementEvent("normalizedValue", att.getValue());
                    sendElementEvent(
                        "specified",
                        String.valueOf(att.getSpecified()));
                    sendElementEvent("attributeType");

                    // this property isn't relevent to PSVI
                    sendElementEvent("references");

                    sendUnIndentedElement("attribute");
                }
            }
            sendUnIndentedElement("namespaceAttributes");
        }
        else {
            sendEmptyElementEvent("namespaceAttributes");
        }
    }

    private void processPSVIElementDeclaration(XSElementDeclaration elem) {
        if (elem == null)
            return;
        sendIndentedElementWithID("psv:elementDeclaration", elem);
        sendElementEvent("psv:name", elem.getName());
        sendElementEvent("psv:targetNamespace", elem.getNamespace());
        processPSVITypeDefinitionOrRef(
            "psv:typeDefinition",
            elem.getTypeDefinition());
        processPSVIScope("psv:scope", elem.getEnclosingCTDefinition(), elem.getScope());
        processPSVIValueConstraint(elem.getConstraintType(), getConstraintValue(elem));
        sendElementEvent("psv:nillable", String.valueOf(elem.getNillable()));
        processPSVIIdentityConstraintDefinitions(elem.getIdentityConstraints());
        processPSVISubstitutionGroupAffiliation(elem);

        sendElementEvent(
            "psv:substitutionGroupExclusions",
            this.translateBlockOrFinal(elem.getSubstitutionGroupExclusions()));
        sendElementEvent(
            "psv:disallowedSubstitutions",
            this.translateBlockOrFinal(elem.getDisallowedSubstitutions()));
        sendElementEvent("psv:abstract", String.valueOf(elem.getAbstract()));
        processPSVIAnnotation(elem.getAnnotation());
        sendUnIndentedElement("psv:elementDeclaration");
    }

    private void processPSVIAttributeDeclaration(XSAttributeDeclaration attr) {
        if (attr == null)
            return;
        sendIndentedElementWithID("psv:attributeDeclaration", attr);
        sendElementEvent("psv:name", attr.getName());
        sendElementEvent("psv:targetNamespace", attr.getNamespace());
        processPSVITypeDefinitionOrRef(
            "psv:typeDefinition",
            attr.getTypeDefinition());
        processPSVIScope("psv:scope", attr.getEnclosingCTDefinition(), attr.getScope());
        processPSVIValueConstraint(attr.getConstraintType(), getConstraintValue(attr));
        processPSVIAnnotation(attr.getAnnotation());
        sendUnIndentedElement("psv:attributeDeclaration");
    }

    private void processPSVIAttributeGroupDefinition(XSAttributeGroupDefinition ag) {
        if (ag == null)
            return;
        sendIndentedElementWithID("psv:attributeGroupDefinition", ag);
        sendElementEvent("psv:name", ag.getName());
        sendElementEvent("psv:targetNamespace", ag.getNamespace());
        processPSVIAttributeUses(ag.getAttributeUses());
        processPSVIAttributeWildcard(ag.getAttributeWildcard());
        processPSVIAnnotation(ag.getAnnotation());
        sendUnIndentedElement("psv:attributeGroupDefinition");
    }

    private void processPSVIModelGroupDefinition(XSModelGroupDefinition mgd) {
        if (mgd == null) {
            sendElementEvent("psv:modelGroupDefinition");
        }
        else {
            sendIndentedElementWithID("psv:modelGroupDefinition", mgd);
            sendElementEvent("psv:name", mgd.getName());
            sendElementEvent("psv:targetNamespace", mgd.getNamespace());
            processPSVIModelGroup(mgd.getModelGroup());
            processPSVIAnnotation(mgd.getAnnotation());
            sendUnIndentedElement("psv:modelGroupDefinition");
        }
    }

    private void processPSVIModelGroup(XSModelGroup mg) {
        if (mg == null) {
            sendElementEvent("psv:modelGroup");
        }
        else {
            sendIndentedElement("psv:modelGroup");
            sendElementEvent(
                "psv:compositor",
                this.translateCompositor(mg.getCompositor()));
            processPSVIParticles(mg.getParticles());
            processPSVIAnnotation(mg.getAnnotation());
            sendUnIndentedElement("psv:modelGroup");
        }
    }

    private void processPSVINotationDeclaration(XSNotationDeclaration not) {
        if (not == null) {
            sendElementEvent("psv:notationDeclaration");
        }
        else {
            sendIndentedElementWithID("psv:notationDeclaration", not);
            sendElementEvent("psv:name", not.getName());
            sendElementEvent("psv:targetNamespace", not.getNamespace());
            sendElementEvent("systemIdentifier", not.getSystemId());
            sendElementEvent("publicIdentifier", not.getPublicId());
            processPSVIAnnotation(not.getAnnotation());
            sendUnIndentedElement("psv:notationDeclaration");
        }
    }

    private void processPSVIIdentityConstraintDefinitions(XSNamedMap constraints) {
        if (constraints == null || constraints.getLength() == 0) {
            sendElementEvent("psv:identityConstraintDefinitions");
        }
        else {
            sendIndentedElement("psv:identityConstraintDefinitions");
            for (int i = 0; i < constraints.getLength(); i++) {
                XSIDCDefinition constraint =
                    (XSIDCDefinition)constraints.item(i);
                sendIndentedElementWithID(
                    "psv:identityConstraintDefinition",
                    constraint);
                sendElementEvent("psv:name", constraint.getName());
                sendElementEvent(
                    "psv:targetNamespace",
                    constraint.getNamespace());
                sendElementEvent(
                    "psv:identityConstraintCategory",
                    this.translateCategory(constraint.getCategory()));
                sendIndentedElement("psv:selector");
                processPSVIXPath(constraint.getSelectorStr());
                sendUnIndentedElement("psv:selector");
                processPSVIFields(constraint.getFieldStrs());
                processPSVIElementRef(
                    "psv:referencedKey",
                    constraint.getRefKey());
                processPSVIAnnotations(constraint.getAnnotations());
                sendUnIndentedElement("psv:identityConstraintDefinition");
            }
            sendUnIndentedElement("psv:identityConstraintDefinitions");
        }
    }

    private void processPSVIFields(StringList fields) {
        if (fields == null || fields.getLength() == 0) {
            sendElementEvent("psv:fields");
        }
        else {
            sendIndentedElement("psv:fields");
            for (int i = 0; i < fields.getLength(); i++) {
                processPSVIXPath(fields.item(i));
            }
            sendUnIndentedElement("psv:fields");
        }
    }

    private void processPSVIXPath(String path) {
        sendIndentedElement("psv:xpath");
        sendElementEvent("psv:xpath", path);
        sendUnIndentedElement("psv:xpath");
    }

    private void processPSVIParticles(XSObjectList particles) {
        if (particles == null || particles.getLength() == 0) {
            sendElementEvent("psv:particles");
        }
        else {
            sendIndentedElement("psv:particles");
            for (int i = 0; i < particles.getLength(); i++) {
                processPSVIParticle((XSParticle)particles.item(i));
            }
            sendUnIndentedElement("psv:particles");
        }
    }

    private void processPSVIParticle(XSParticle part) {
        if (part == null) {
            sendElementEvent("psv:particle");
        }
        else {
            sendIndentedElement("psv:particle");
            sendElementEvent(
                "psv:minOccurs",
                String.valueOf(part.getMinOccurs()));
            sendElementEvent(
                "psv:maxOccurs",
                part.getMaxOccurs() == SchemaSymbols.OCCURRENCE_UNBOUNDED
                    ? "unbounded"
                    : String.valueOf(part.getMaxOccurs()));
            sendIndentedElement("psv:term");
            switch (part.getTerm().getType()) {
                case XSConstants.ELEMENT_DECLARATION :
                    processPSVIElementDeclarationOrRef(
                        (XSElementDeclaration)part.getTerm());
                    break;
                case XSConstants.MODEL_GROUP :
                    processPSVIModelGroup((XSModelGroup)part.getTerm());
                    break;
                case XSConstants.WILDCARD :
                    processPSVIWildcard((XSWildcard)part.getTerm());
                    break;
            }
            sendUnIndentedElement("psv:term");
            sendUnIndentedElement("psv:particle");
        }
    }

    private void processPSVIElementRef(String elementName, XSObject obj) {
        this.processPSVIElementRef(elementName, null, obj);
    }

    private void processPSVIElementRef(
        String elementName,
        List attributes,
        XSObject obj) {
        if (attributes == null) {
            attributes = new ArrayList();
        }
        String ref = this.getID(obj);
        if (ref != null) {
            attributes.add("ref");
            attributes.add(ref);
            attributes.add(XMLSymbols.fIDREFSymbol);
        }
        sendElementEvent(elementName, attributes, (XMLString) null);
    }

    private void processPSVIAttributeDeclarationOrRef(XSAttributeDeclaration att) {
        if (att == null)
            return;
        // for global attributes, and attributes that have already been printed,
        // we always want to print references
        if (att.getScope() == XSConstants.SCOPE_GLOBAL
            || fDefined.contains(this.getID(att))) {
            processPSVIAttributeDeclarationRef(att);
        }
        else {
            processPSVIAttributeDeclaration(att);
        }
    }

    private void processPSVIAttributeDeclarationRef(XSAttributeDeclaration att) {
        if (att == null)
            return;
        List attributes = new ArrayList();
        attributes.add("name");
        attributes.add(att.getName());
        attributes.add(XMLSymbols.fCDATASymbol);
        if (att.getNamespace() != null) {
            attributes.add("tns");
            attributes.add(att.getNamespace());
            attributes.add(XMLSymbols.fCDATASymbol);
        }
        processPSVIElementRef("psv:attributeDeclaration", attributes, att);
    }

    // always prints a reference
    private void processPSVITypeDefinitionRef(
        String enclose,
        XSTypeDefinition type) {
        if (type == null) {
        	sendElementEvent(enclose);
            return;
        }

        sendIndentedElement(enclose);
        if (type.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
            processPSVIElementRef("psv:complexTypeDefinition", type);
        }
        else if (type.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) {
            processPSVIElementRef("psv:simpleTypeDefinition", type);
        }
        else {
            throw new IllegalArgumentException(
                "Unknown type definition value: " + type.getTypeCategory());
        }
        sendUnIndentedElement(enclose);
    }

    // prints a reference if the type is anonymous and hasn't already been defined,
    // otherwise prints a definition
    private void processPSVITypeDefinitionOrRef(
        String enclose,
        XSTypeDefinition type) {
        if (type == null){
        	sendElementEvent(enclose);
            return;
        }

        // we'll check for anonymous types here, since they only occur in places where
        // a reference would be appropriate
        if (type.getAnonymous() && !fDefined.contains(this.getID(type))) {
            sendIndentedElement(enclose);
            processPSVITypeDefinition(type);
            sendUnIndentedElement(enclose);
        }
        else {
            processPSVITypeDefinitionRef(enclose, type);
        }
    }

    private void processPSVIElementDeclarationRef(XSElementDeclaration elem) {
        if (elem == null)
            return;
        processPSVIElementRef("psv:elementDeclaration", elem);
    }

    private void processPSVIElementDeclarationOrRef(XSElementDeclaration elem) {
        if (elem == null)
            return;
        // for global attributes, and attributes that have already been printed,
        // we always want to print references
        if (elem.getScope() == XSConstants.SCOPE_GLOBAL
            || fDefined.contains(this.getID(elem))) {
            processPSVIElementDeclarationRef(elem);
        }
        else {
            processPSVIElementDeclaration(elem);
        }
    }

    private void processPSVIScope(
        String enclose,
        XSComplexTypeDefinition enclosingCTD,
        short scope) {
        if (scope == XSConstants.SCOPE_ABSENT || scope == XSConstants.SCOPE_GLOBAL) {
            sendElementEvent(enclose, this.translateScope(scope));
        } else {  // XSConstants.SCOPE_LOCAL
            processPSVITypeDefinitionRef(enclose, enclosingCTD);
        }
    }

    private void processPSVIValueConstraint(
        short constraintType,
        String constraintValue) {
        if (constraintType == XSConstants.VC_NONE) {
            sendElementEvent("psv:valueConstraint");
        } else {
            sendIndentedElement("psv:valueConstraint");
            sendElementEvent("psv:variety", translateValueConstraintType(constraintType));
            sendElementEvent("psv:value", constraintValue);
            sendUnIndentedElement("psv:valueConstraint");
        }
    }

    private void processPSVISubstitutionGroupAffiliation(XSElementDeclaration elem) {
        if (elem.getSubstitutionGroupAffiliation() == null) {
            sendElementEvent("psv:substitutionGroupAffiliation");
        } else {
            sendIndentedElement("psv:substitutionGroupAffiliation");
            processPSVIElementRef("psv:elementDeclaration", elem.getSubstitutionGroupAffiliation());
//            XSElementDeclaration[] subgroups = elem.getSubstitutionGroupAffiliations();
//            for (int i=0; i<subgroups.length; i++) {
//                XSElementDeclaration subgroup = subgroups[i];
//                processPSVIElementRef("psv:elementDeclaration", subgroup);
//            }
            sendUnIndentedElement("psv:substitutionGroupAffiliation");
        }
    }

    /**
     * This method writes an empty element at the current indent level.
     *
     * @param tagname The name of the Element.
     *
     * @throws IOEXception
     */
    private void sendEmptyElementEvent(String tagname) {
        this.sendEmptyElementEvent(tagname, null);
    } //sendEmptyElementEvent

    private void sendEmptyElementEvent(String tagname, List attributes) {
        this.sendIndent();
        fDocumentHandler.emptyElement(
            createQName(tagname),
            createAttributes(attributes),
            null);
        this.sendNewLine();
    } //sendEmptyElementEvent

    /**
     * This method writes an empty element at the current indent level.
     *
     * @param tagname The name of the Element.
     *
     * @throws IOEXception
     */
    private void sendStartElementEvent(String tagname, List attributes) {
        fDocumentHandler.startElement(
            createQName(tagname),
            createAttributes(attributes),
            null);
    } //sendStartElementEvent

    /**
     * This method writes a closing tag at the current indent level.
     *
     * @param tagname The name of the Element.
     *
     * @throws IOEXception
     */
    private void sendEndElementEvent(String tagname) {
        fDocumentHandler.endElement(this.createQName(tagname), null);
    } //sendEndElementEvent

    /**
     * This method write the element at the current indent level and increase
     * the one level of indentation.
     *
     * @param The name of the Element.
     *
     * @throws IOException
     */
    private void sendIndentedElement(String tagName) {
        this.sendIndentedElement(tagName, null);
    } //sendIndentedElement

    private void sendIndentedElement(String tagName, List attributes) {
        this.sendIndent();
        this.sendStartElementEvent(tagName, attributes);
        this.sendNewLine();
        fIndent++;
    } //sendIndentedElement

    /**
     * This method write the element at the current indent level and decrease
     * one level of indentation.
     *
     * @param the name of the Element.
     *
     */
    private void sendUnIndentedElement(String tagName) {
        fIndent--;
        this.sendIndent();
        this.sendEndElementEvent(tagName);
        this.sendNewLine();
    } //sendUnIndentedElement

    /**
     * Write the Element Information Item for each element appearing in the XML
     * document. One of the element information items is the value of the
     * [document element] property of the document information item, corresponding
     * to the root of the element tree, and all other element information items
     * are accessible by recursively following its [children] property.
     *
     * @elementName  Name of the elment.
     * @elemmentValue  Value of the element
     */
    private void sendElementEvent(String elementName) {
        this.sendElementEvent(elementName, null, (XMLString) null);
    } //sendElementEvents

    private void sendElementEvent(String elementName, String elementValue) {
        this.sendElementEvent(elementName, null, elementValue);
    } //sendElementEvents

    private void sendElementEvent(String elementName, XMLString elementValue) {
        this.sendElementEvent(elementName, null, elementValue);
    } //sendElementEvents

    private void sendElementEvent(
        String elementName,
        List attributes,
        String elementValue) {
        XMLString text =
            elementValue == null
                ? null
                : new XMLString(
                    elementValue.toCharArray(),
                    0,
                    elementValue.length());
        this.sendElementEvent(elementName, attributes, text);
    }

    private void sendElementEvent(
        String elementName,
        List attributes,
        XMLString elementValue) {
        if (elementValue == null || elementValue.length == 0) {
            if (attributes == null) {
                attributes = new ArrayList();
            }
            attributes.add("xsi:nil");
            attributes.add("true");
            attributes.add(XMLSymbols.fCDATASymbol);
            this.sendEmptyElementEvent(elementName, attributes);
        }
        else {
            this.sendIndent();
            this.sendStartElementEvent(elementName, attributes);
            fDocumentHandler.characters(elementValue, null);
            this.sendEndElementEvent(elementName);
            this.sendNewLine();
        }
    } //sendElementEvents

    private void sendIndentedElementWithID(String elementName, XSObject obj) {
        String id = this.getID(obj);
        // since this method is called everytime we define something with an ID,
        // may as well mark the ID as defined here
        fDefined.add(id);
        List attributes = new ArrayList();
        attributes.add("id");
        attributes.add(id);
        attributes.add(XMLSymbols.fIDSymbol);
        sendIndentedElement(elementName, attributes);
    }

    private void sendIndent() {
        if (fIndent > fIndentChars.length) {
            fIndentChars = new char[fIndentChars.length * 2];
            for (int i = 0; i < fIndentChars.length; i++) {
                fIndentChars[i] = '\t';
            }
        }
        XMLString text = new XMLString(fIndentChars, 0, fIndent);
        fDocumentHandler.characters(text, null);
    }

    private void sendNewLine() {
        fDocumentHandler.characters(newLine, null);
    }

    private QName createQName(String rawname) {
        int index = rawname.indexOf(':');
        String prefix, localpart;
        if (index == -1) {
            prefix = "";
            localpart = rawname;
        }
        else {
            prefix = rawname.substring(0, index);
            localpart = rawname.substring(index + 1);
        }
        String uri = fPSVINamespaceContext.getURI(prefix);
        return new QName(prefix, localpart, rawname, uri);
    }

    private XMLAttributes createAttributes(List atts) {
        XMLAttributes attributes = new XMLAttributesImpl();
        if (atts != null) {
            for (int i = 0; i < atts.size(); i += 3) {
                String rawname = (String)atts.get(i);
                String value = (String)atts.get(i + 1);
                String type = (String)atts.get(i + 2);
                attributes.addAttribute(createQName(rawname), type, value);
            }
        }
        return attributes;
    }

    private String createID(XSObject obj) {
        String namespace = obj.getNamespace();
        String prefix = fNamespaceContext.getPrefix(obj.getNamespace());
        String name = obj.getName();
        String type = this.translateType(obj.getType());

        // must be anonymous
        if (name == null) {
            name = "anon_" + fAnonNum++;
        }
        // no namespace
        else if (namespace == null || namespace == XMLSymbols.EMPTY_STRING) {
            name = name + "." + fAnonNum++;
        }

        if (namespace == Constants.NS_XMLSCHEMA) {
            return name;
        }
        else {
            return (prefix == null ? "" : prefix + ".") + type + "." + name;
        }
    }

    private String getID(XSObject obj) {
        if (obj == null)
            return null;
        String id = (String)fIDMap.get(obj);
        if (id == null) {
            id = createID(obj);
            fIDMap.put(obj, id);
        }
        return id;
    }

    private String translateType(short type) {
        switch (type) {
            case XSConstants.TYPE_DEFINITION :
                return "type";
            case XSConstants.ANNOTATION :
                return "annot";
            case XSConstants.ATTRIBUTE_DECLARATION :
                return "attr";
            case XSConstants.ATTRIBUTE_GROUP :
                return "ag";
            case XSConstants.ATTRIBUTE_USE :
                return "au";
            case XSConstants.ELEMENT_DECLARATION :
                return "elt";
            case XSConstants.MODEL_GROUP_DEFINITION :
                return "mg";
            case XSConstants.NOTATION_DECLARATION :
                return "not";
            case XSConstants.IDENTITY_CONSTRAINT :
                return "idc";
            default :
                return "unknown";
        }
    }

    private String translateFacetKind(short kind) {
        switch (kind) {
            case XSSimpleTypeDefinition.FACET_WHITESPACE :
                return SchemaSymbols.ELT_WHITESPACE;
            case XSSimpleTypeDefinition.FACET_LENGTH :
                return SchemaSymbols.ELT_LENGTH;
            case XSSimpleTypeDefinition.FACET_MINLENGTH :
                return SchemaSymbols.ELT_MINLENGTH;
            case XSSimpleTypeDefinition.FACET_MAXLENGTH :
                return SchemaSymbols.ELT_MAXLENGTH;
            case XSSimpleTypeDefinition.FACET_TOTALDIGITS :
                return SchemaSymbols.ELT_TOTALDIGITS;
            case XSSimpleTypeDefinition.FACET_FRACTIONDIGITS :
                return SchemaSymbols.ELT_FRACTIONDIGITS;
            case XSSimpleTypeDefinition.FACET_PATTERN :
                return SchemaSymbols.ELT_PATTERN;
            case XSSimpleTypeDefinition.FACET_ENUMERATION :
                return SchemaSymbols.ELT_ENUMERATION;
            case XSSimpleTypeDefinition.FACET_MAXINCLUSIVE :
                return SchemaSymbols.ELT_MAXINCLUSIVE;
            case XSSimpleTypeDefinition.FACET_MAXEXCLUSIVE :
                return SchemaSymbols.ELT_MAXEXCLUSIVE;
            case XSSimpleTypeDefinition.FACET_MINEXCLUSIVE :
                return SchemaSymbols.ELT_MINEXCLUSIVE;
            case XSSimpleTypeDefinition.FACET_MININCLUSIVE :
                return SchemaSymbols.ELT_MININCLUSIVE;
            default :
                return "unknown";
        }
    }

    private String translateVariety(short var) {
        switch (var) {
            case XSSimpleTypeDefinition.VARIETY_LIST :
                return "list";
            case XSSimpleTypeDefinition.VARIETY_UNION :
                return "union";
            case XSSimpleTypeDefinition.VARIETY_ATOMIC :
                return "atomic";
            case XSSimpleTypeDefinition.VARIETY_ABSENT :
                return null;
            default :
                return "unknown";
        }
    }

    private String translateConstraintType(short type) {
        switch (type) {
            case XSWildcard.NSCONSTRAINT_ANY :
                return "any";
                // the spec says that when it's a list, the "type" shouldn't be there
            case XSWildcard.NSCONSTRAINT_LIST :
                return null;
            case XSWildcard.NSCONSTRAINT_NOT :
                return "not";
            default :
                return "unknown";
        }
    }

    private String translateValueConstraintType(short type) {
        switch (type) {
            case XSConstants.VC_DEFAULT :
                return "default";
            case XSConstants.VC_FIXED :
                return "fixed";
            default :
                return "unknown";
        }
    }

    private String translateBlockOrFinal(short val) {
        String ret = "";
        if ((val & XSConstants.DERIVATION_EXTENSION) != 0) {
            ret += SchemaSymbols.ATTVAL_EXTENSION;
        }
        if ((val & XSConstants.DERIVATION_LIST) != 0) {
            if (ret.length() != 0)
                ret += " ";
            ret += SchemaSymbols.ATTVAL_LIST;
        }
        if ((val & XSConstants.DERIVATION_RESTRICTION) != 0) {
            if (ret.length() != 0)
                ret += " ";
            ret += SchemaSymbols.ATTVAL_RESTRICTION;
        }
        if ((val & XSConstants.DERIVATION_UNION) != 0) {
            if (ret.length() != 0)
                ret += " ";
            ret += SchemaSymbols.ATTVAL_UNION;
        }
        if ((val & XSConstants.DERIVATION_SUBSTITUTION) != 0) {
            if (ret.length() != 0)
                ret += " ";
            ret += SchemaSymbols.ATTVAL_SUBSTITUTION;
        }
        return ret;
    }

    private String translateScope(short scope) {
        switch (scope) {
            case XSConstants.SCOPE_ABSENT :
                return null;
            case XSConstants.SCOPE_GLOBAL :
                return "global";
            case XSConstants.SCOPE_LOCAL :
                return "local";
            default :
                return "unknown";
        }
    }

    private String translateCompositor(short comp) {
        switch (comp) {
            case XSModelGroup.COMPOSITOR_SEQUENCE :
                return SchemaSymbols.ELT_SEQUENCE;
            case XSModelGroup.COMPOSITOR_CHOICE :
                return SchemaSymbols.ELT_CHOICE;
            case XSModelGroup.COMPOSITOR_ALL :
                return SchemaSymbols.ELT_ALL;
            default :
                return "unknown";
        }
    }

    private String translateContentType(short contentType) {
        switch (contentType) {
            case XSComplexTypeDefinition.CONTENTTYPE_ELEMENT :
                return "elementOnly";
            case XSComplexTypeDefinition.CONTENTTYPE_EMPTY :
                return "empty";
            case XSComplexTypeDefinition.CONTENTTYPE_MIXED :
                return "mixed";
            case XSComplexTypeDefinition.CONTENTTYPE_SIMPLE :
                return "simple";
            default :
                return "unknown";
        }
    }

    private String translateProcessContents(short process) {
        switch (process) {
            case XSWildcard.PC_LAX :
                return SchemaSymbols.ATTVAL_LAX;
            case XSWildcard.PC_SKIP :
                return SchemaSymbols.ATTVAL_SKIP;
            case XSWildcard.PC_STRICT :
                return SchemaSymbols.ATTVAL_STRICT;
            default :
                return "unknown";
        }
    }

    private String translateDerivation(short deriv) {
        switch (deriv) {
            case XSConstants.DERIVATION_EXTENSION :
                return SchemaSymbols.ELT_EXTENSION;
            case XSConstants.DERIVATION_LIST :
                return SchemaSymbols.ELT_LIST;
            case XSConstants.DERIVATION_RESTRICTION :
                return SchemaSymbols.ELT_RESTRICTION;
            case XSConstants.DERIVATION_SUBSTITUTION :
                return SchemaSymbols.ATTVAL_SUBSTITUTION;
            case XSConstants.DERIVATION_UNION :
                return SchemaSymbols.ELT_UNION;
            case XSConstants.DERIVATION_NONE :
                return null;
            default :
                return "unknown";
        }
    }

    private String translateCategory(short cat) {
        switch (cat) {
            case XSIDCDefinition.IC_KEY :
                return SchemaSymbols.ELT_KEY;
            case XSIDCDefinition.IC_KEYREF :
                return SchemaSymbols.ELT_KEYREF;
            case XSIDCDefinition.IC_UNIQUE :
                return SchemaSymbols.ELT_UNIQUE;
            default :
                return "unknown";
        }
    }

    private String translateOrdered(short ordered) {
        switch (ordered) {
            case XSSimpleTypeDefinition.ORDERED_FALSE :
                return "false";
            case XSSimpleTypeDefinition.ORDERED_PARTIAL :
                return "partial";
            case XSSimpleTypeDefinition.ORDERED_TOTAL :
                return "total";
            default :
                return "unknown";
        }
    }

    private String translateValidationAttempted(short val) {
        switch (val) {
            case ItemPSVI.VALIDATION_NONE :
                return "none";
            case ItemPSVI.VALIDATION_PARTIAL :
                return "partial";
            case ItemPSVI.VALIDATION_FULL :
                return "full";
            default :
                return "unknown";
        }
    }

    private String translateValidity(short val) {
        switch (val) {
            case ItemPSVI.VALIDITY_NOTKNOWN :
                return "notKnown";
            case ItemPSVI.VALIDITY_VALID :
                return "valid";
            case ItemPSVI.VALIDITY_INVALID :
                return "invalid";
            default :
                return "unknown";
        }
    }

    /**
     *  Check whether the calling event is  first in children list ,
     * if yes print the <children>.
     */
    private void checkForChildren() {
        if (!_elementState.empty()) {
            ElementState fElementState = (ElementState)_elementState.peek();
            if (fElementState.isEmpty == true) {
                sendIndentedElement("children");
                fElementState.isEmpty = false;
            }
        }
        else {
            sendIndentedElement("children");
            _elementState.push(new ElementState(false));
        }
    } //checkForChildren
    
    private String getConstraintValue(XSElementDeclaration elemDecl) {
        return getConstraintValue(elemDecl.getValueConstraintValue());
    } // getConstraintValue(XSElementDeclaration)
    
    private String getConstraintValue(XSAttributeDeclaration attrDecl) {
        return getConstraintValue(attrDecl.getValueConstraintValue());
    } // getConstraintValue(XSAttributeDeclaration)
    
    private String getConstraintValue(XSAttributeUse attrUse) {
        return getConstraintValue(attrUse.getValueConstraintValue());
    } // getConstraintValue(XSAttributeUse)
    
    private String getConstraintValue(XSValue vcValue) {
        return (vcValue != null) ? vcValue.getNormalizedValue() : null;
    } // getConstraintValue(XSValue)
    
    private String getSchemaNormalizedValue(ItemPSVI itemPSVI) {
        XSValue schemaValue = itemPSVI.getSchemaValue();
        return (schemaValue != null) ? schemaValue.getNormalizedValue() : null;
    } // getSchemaNormalizedValue(ItemPSVI)

    static final class ElementState {
        public boolean isEmpty;
        XMLAttributes fAttributes;

        public ElementState(XMLAttributes attributes) {
            fAttributes = attributes;
            isEmpty = true;
        }
        public ElementState(boolean value) {
            isEmpty = value;
        }
        public XMLAttributes getAttributes() {
            return fAttributes;
        }
        public void isEmpty(boolean value) {
            isEmpty = value;
        }
    } //class ElementState
} // class PSVIWriter
