namespace Xml {

    // NOTE: this doesn't honor FAIL_SILENTLY and I'm leaving it that way.
    // Eventaully there'll be try-catch support and it'll throw an exception which will negate the need of FAIL_SILENTLY.
    class XmlNamespacedElement {
        field name;
        field alias;
        field xmlns;
        field children = [];
        field type = NodeType.ELEMENT;

        // Attributes must be a list instead of a lookup dictionary, since names can now collide.
        // Lookup keys with alias:name are semantically meaningless and namespace:name are cumbersome.
        field attributes = [];

        constructor(xmlnsLookupChain, element) {
            elementXmlnsLookup = xmlnsLookupChain[-1];

            attributes = element.attributes;
            children = element.children;

            // Check to see if there are any new xmlns definitions in this element.
            attributeNames = attributes.keys();
            pushNewLookup = false;
            for (attrName : attributeNames) {
                if (attrName == 'xmlns' || attrName.startsWith('xmlns:')) {
                    pushNewLookup = true;
                    break;
                }
            }

            // If there are, push a new alias lookup to the chain that's a copy of the preivous one.
            // If not, just push a duplicate reference to the old one.
            xmlnsLookupChain.add(pushNewLookup ? elementXmlnsLookup.clone() : elementXmlnsLookup);
            elementXmlnsLookup = xmlnsLookupChain[-1];

            if (pushNewLookup) {
                // Go through all the attributes.
                // If you find an xmlns, add it to the lookup.
                // If it's just a normal attribute, add it to this list.
                normalAttrs = [];
                for (attrName : attributeNames) {
                    if (attrName == 'xmlns') {
                        elementXmlnsLookup[''] = attributes[attrName];
                    } else if (attrName.startsWith('xmlns:')) {
                        alias = attrName['xmlns:'.length:];
                        ns = attributes[attrName];
                        elementXmlnsLookup[alias] = ns;
                    } else {
                        normalAttrs.add(attrName);
                    }
                }
            } else {
                // If there were no xmlns declarations, then that means all the attributes are normal attribute values.
                normalAttrs = attributeNames;
            }

            // Go through all normal attributes, resolve their namespace, and add them to the attribute list.
            for (normalAttr : normalAttrs) {
                colonIndex = normalAttr.indexOf(':');

                if (colonIndex != -1) {
                    // If there is a colon, the lookup value is before the colon and the attribute name is after
                    alias = normalAttr[:colonIndex];
                    attrName = normalAttr[colonIndex + 1:];
                } else {
                    // If there is no colon, then the whole attribute is the attribute name and the alias
                    // is denoted by the empty string, which will point to the most recent parent xmlns="..." definition.
                    alias = '';
                    attrName = normalAttr;
                }
                ns = elementXmlnsLookup.get(alias);
                if (ns == null) {
                    if (alias == '') Core.fail("Default xmlns alias not found. '" + normalAttr + "' not defined.");
                    Core.fail("Unknown alias: '" + alias + "'. Cannot evaluate '" + normalAttr + "'.");
                }
                this.attributes.add(new XmlNamespacedAttribute(attrName, alias, ns, attributes[normalAttr]));
            }

            // Recurse into children.
            for (child : children) {
                if (child.type == NodeType.ELEMENT) {
                    // pass along the current lookup chain.
                    this.children.add(new XmlNamespacedElement(xmlnsLookupChain, child));
                } else {
                    // text and comment elements pass through untouched.
                    this.children.add(child);
                }
            }

            // Figure out the name, aias, and namespace of this element
            name = element.name;
            colonIndex = name.indexOf(':');
            if (colonIndex == -1) {
                alias = '';
            } else {
                alias = name[:colonIndex];
                name = name[colonIndex + 1:];
            }
            ns = elementXmlnsLookup.get(alias);
            if (ns == null) {
                if (alias == '') Core.fail("Default xmlns alias not found. '" + name + "' not defined.");
                Core.fail("Unknown alias: '" + alias + "'. Cannot evaluate '" + element.name + "'.");
            }
            this.name = name;
            this.alias = alias;
            this.xmlns = ns;

            // Pop the lookup we pushed at the beginning.
            xmlnsLookupChain.pop();
        }
    }
}
