/*
 * Copyright 2009 - 2010 Kevin Ackley (kackley@gwi.net)
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <limits>
#include <xercesc/sax2/Attributes.hpp>
#include <xercesc/sax2/XMLReaderFactory.hpp>

#include <xercesc/util/BinInputStream.hpp>
#include <xercesc/util/TransService.hpp>

#include "CheckedFile.h"
#include "E57FormatImpl.h"
#include "E57XmlParser.h"
#include "ImageFileImpl.h"

using namespace e57;
using namespace std;
using namespace XERCES_CPP_NAMESPACE;


// define convenient constants for the attribute names
static const XMLCh att_minimum[] = {
    chLatin_m, chLatin_i, chLatin_n, chLatin_i, chLatin_m, chLatin_u,
    chLatin_m, chNull
};
static const XMLCh att_maximum[] = {
    chLatin_m, chLatin_a, chLatin_x, chLatin_i, chLatin_m, chLatin_u,
    chLatin_m, chNull
};
static const XMLCh att_scale[] = {
    chLatin_s, chLatin_c, chLatin_a, chLatin_l, chLatin_e, chNull
};
static const XMLCh att_offset[] = {
    chLatin_o, chLatin_f, chLatin_f, chLatin_s, chLatin_e, chLatin_t, chNull
};
static const XMLCh att_precision[] = {
    chLatin_p, chLatin_r, chLatin_e, chLatin_c, chLatin_i, chLatin_s,
    chLatin_i, chLatin_o, chLatin_n, chNull
};
static const XMLCh att_allowHeterogeneousChildren[] = {
    chLatin_a, chLatin_l, chLatin_l, chLatin_o, chLatin_w, chLatin_H,
    chLatin_e, chLatin_t, chLatin_e, chLatin_r, chLatin_o, chLatin_g,
    chLatin_e, chLatin_n, chLatin_e, chLatin_o, chLatin_u, chLatin_s,
    chLatin_C, chLatin_h, chLatin_i, chLatin_l, chLatin_d, chLatin_r,
    chLatin_e, chLatin_n, chNull
};
static const XMLCh att_fileOffset[] = {
    chLatin_f, chLatin_i, chLatin_l, chLatin_e, chLatin_O, chLatin_f,
    chLatin_f, chLatin_s, chLatin_e, chLatin_t, chNull
};

static const XMLCh att_type[] = {
    chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull
};
static const XMLCh att_length[] = {
    chLatin_l, chLatin_e, chLatin_n, chLatin_g, chLatin_t, chLatin_h,
    chNull
};
static const XMLCh att_recordCount[] = {
    chLatin_r, chLatin_e, chLatin_c, chLatin_o, chLatin_r, chLatin_d,
    chLatin_C, chLatin_o, chLatin_u, chLatin_n, chLatin_t, chNull
};

inline int64_t  convertStrToLL( const std::string &inStr )
{
#if defined(_MSC_VER)
   return _atoi64( inStr.c_str() );
#elif defined(__GNUC__)
   return strtoll( inStr.c_str(), nullptr, 10 );
#else
#  error "Need to define string to long long conversion for this compiler"
#endif
}

//=============================================================================
// E57FileInputStream

class E57FileInputStream : public BinInputStream
{
public :
    E57FileInputStream(CheckedFile* cf, uint64_t logicalStart, uint64_t logicalLength);
    ~E57FileInputStream() override = default;

    E57FileInputStream(const E57FileInputStream&) = delete;
    E57FileInputStream& operator=(const E57FileInputStream&) = delete;

    XMLFilePos      curPos() const override {return(logicalPosition_);}
    XMLSize_t       readBytes(XMLByte* const toFill, const XMLSize_t maxToRead) override;
    const XMLCh*    getContentType() const override { return nullptr; }

private :
    //??? lifetime of cf_ must be longer than this object!
    CheckedFile*    cf_;
    uint64_t        logicalStart_;
    uint64_t        logicalLength_;
    uint64_t        logicalPosition_;
};

E57FileInputStream::E57FileInputStream(CheckedFile* cf, uint64_t logicalStart, uint64_t logicalLength)
: cf_(cf),
  logicalStart_(logicalStart),
  logicalLength_(logicalLength),
  logicalPosition_(logicalStart)
{
}

XMLSize_t E57FileInputStream::readBytes(       XMLByte* const  toFill
                                       , const XMLSize_t       maxToRead)
{
    if (logicalPosition_ > logicalStart_ + logicalLength_)
        return(0);

    int64_t available = logicalStart_ + logicalLength_ - logicalPosition_;
    if (available <= 0)
        return(0);

    /// size_t and XMLSize_t should be compatible, should get compiler warning here if not
    size_t maxToRead_size = maxToRead;

    /// Be careful if size_t is smaller than int64_t
    size_t available_size;
    if (sizeof(size_t) >= sizeof(int64_t))
        /// size_t is at least as big as int64_t
        available_size = static_cast<size_t>(available);
    else {
        /// size_t is smaller than int64_t, Calc max that size_t can hold
        const int64_t size_max = (std::numeric_limits<size_t>::max)();

        /// read smaller of size_max, available
        ///??? redo
        if (size_max < available)
            available_size = static_cast<size_t>(size_max);
        else
            available_size = static_cast<size_t>(available);
    }

    size_t readCount = min(maxToRead_size, available_size);

    cf_->seek(logicalPosition_);
    cf_->read(reinterpret_cast<char*>(toFill), readCount);  //??? cast ok?
    logicalPosition_ += readCount;
    return(readCount);
}

//=============================================================================
// E57XmlFileInputSource

E57XmlFileInputSource::E57XmlFileInputSource(CheckedFile* cf, uint64_t logicalStart, uint64_t logicalLength)
: InputSource("E57File", XMLPlatformUtils::fgMemoryManager),  //??? what if want to use our own memory manager?, what bufid is good?
  cf_(cf),
  logicalStart_(logicalStart),
  logicalLength_(logicalLength)
{
}

BinInputStream* E57XmlFileInputSource::makeStream() const
{
    return new E57FileInputStream(cf_, logicalStart_, logicalLength_);
}

//=============================================================================
// E57XmlParser::ParseInfo

E57XmlParser::ParseInfo::ParseInfo()
: nodeType(static_cast<NodeType>(0)),
  minimum(0),
  maximum(0),
  scale(0),
  offset(0),
  precision(static_cast<FloatPrecision>(0)),
  floatMinimum(0),
  floatMaximum(0),
  fileOffset(0),
  length(0),
  allowHeterogeneousChildren(false),
  recordCount(0)
{
}

void E57XmlParser::ParseInfo::dump(int indent, ostream& os) const
{
    os << space(indent) << "nodeType:       " << nodeType << endl;
    os << space(indent) << "minimum:        " << minimum << endl;
    os << space(indent) << "maximum:        " << maximum << endl;
    os << space(indent) << "scale:          " << scale << endl;
    os << space(indent) << "offset:         " << offset << endl;
    os << space(indent) << "precision:      " << precision << endl;
    os << space(indent) << "floatMinimum:   " << floatMinimum << endl;
    os << space(indent) << "floatMaximum:   " << floatMaximum << endl;
    os << space(indent) << "fileOffset:     " << fileOffset << endl;
    os << space(indent) << "length:         " << length << endl;
    os << space(indent) << "allowHeterogeneousChildren: " << allowHeterogeneousChildren << endl;
    os << space(indent) << "recordCount:    " << recordCount << endl;
    if (container_ni)
        os << space(indent) << "container_ni:   <defined>" << endl;
    else
        os << space(indent) << "container_ni:   <null>" << endl;
    os << space(indent) << "childText:      \"" << childText << "\"" << endl;
}


//=============================================================================
// E57XmlParser

E57XmlParser::E57XmlParser(ImageFileImplSharedPtr imf)
: imf_(imf),
  xmlReader( nullptr )
{
}

E57XmlParser::~E57XmlParser()
{
   delete xmlReader;

   xmlReader = nullptr;

   XMLPlatformUtils::Terminate();
}

void E57XmlParser::init()
{
   // Initialize the XML4C2 system
   try {
      XMLPlatformUtils::Initialize();
   } catch (const XMLException& ex) {
      /// Turn parser exception into E57Exception
      throw E57_EXCEPTION2(E57_ERROR_XML_PARSER_INIT, "parserMessage=" + ustring(XMLString::transcode(ex.getMessage())));
   }

   xmlReader = XMLReaderFactory::createXMLReader(); //??? auto_ptr?

   if ( xmlReader == nullptr )
   {
      throw E57_EXCEPTION2( E57_ERROR_XML_PARSER_INIT, "could not create the xml reader" );
   }

   //??? check these are right
   xmlReader->setFeature(XMLUni::fgSAX2CoreValidation,        true);
   xmlReader->setFeature(XMLUni::fgXercesDynamic,             true);
   xmlReader->setFeature(XMLUni::fgSAX2CoreNameSpaces,        true);
   xmlReader->setFeature(XMLUni::fgXercesSchema,              true);
   xmlReader->setFeature(XMLUni::fgXercesSchemaFullChecking,  true);
   xmlReader->setFeature(XMLUni::fgSAX2CoreNameSpacePrefixes, true);

   xmlReader->setContentHandler( this );
   xmlReader->setErrorHandler( this );
}

void E57XmlParser::parse( InputSource &inputSource )
{
   xmlReader->parse( inputSource );
}

void E57XmlParser::startElement(const   XMLCh* const    uri,
                                const   XMLCh* const    localName,
                                const   XMLCh* const    qName,
                                const   Attributes&     attributes)
{
#ifdef E57_MAX_VERBOSE
    cout << "startElement" << endl;
    cout << space(2) << "URI:       " << toUString(uri) << endl;
    cout << space(2) << "localName: " << toUString(localName) << endl;
    cout << space(2) << "qName:     " << toUString(qName) << endl;

    for (size_t i = 0; i < attributes.getLength(); i++) {
        cout << space(2) << "Attribute[" << i << "]" << endl;
        cout << space(4) << "URI:       " << toUString(attributes.getURI(i)) << endl;
        cout << space(4) << "localName: " << toUString(attributes.getLocalName(i)) << endl;
        cout << space(4) << "qName:     " << toUString(attributes.getQName(i)) << endl;
        cout << space(4) << "value:     " << toUString(attributes.getValue(i)) << endl;
    }
#endif
    /// Get Type attribute
    ustring node_type = lookupAttribute(attributes, att_type);

    //??? check to make sure not in primitive type (can only nest inside compound types).

    ParseInfo pi;

    if (node_type == "Integer") {
#ifdef E57_MAX_VERBOSE
        cout << "got a Integer" << endl;
#endif
        //??? check validity of numeric strings
        pi.nodeType = E57_INTEGER;

        if (isAttributeDefined(attributes, att_minimum)) {
            ustring minimum_str = lookupAttribute(attributes, att_minimum);

            pi.minimum = convertStrToLL( minimum_str );
        } else {
            /// Not defined defined in XML, so defaults to E57_INT64_MIN
            pi.minimum = E57_INT64_MIN;
        }

        if (isAttributeDefined(attributes, att_maximum)) {
            ustring maximum_str   = lookupAttribute(attributes, att_maximum);

            pi.maximum = convertStrToLL( maximum_str );
        } else {
            /// Not defined defined in XML, so defaults to E57_INT64_MAX
            pi.maximum = E57_INT64_MAX;
        }

        /// Push info so far onto stack
        stack_.push(pi);
    } else if (node_type == "ScaledInteger") {
#ifdef E57_MAX_VERBOSE
        cout << "got a ScaledInteger" << endl;
#endif
        pi.nodeType = E57_SCALED_INTEGER;

        //??? check validity of numeric strings
        if (isAttributeDefined(attributes, att_minimum)) {
            ustring minimum_str = lookupAttribute(attributes, att_minimum);

            pi.minimum = convertStrToLL( minimum_str );
        } else {
            /// Not defined defined in XML, so defaults to E57_INT64_MIN
            pi.minimum = E57_INT64_MIN;
        }

        if (isAttributeDefined(attributes, att_maximum)) {
            ustring maximum_str   = lookupAttribute(attributes, att_maximum);

            pi.maximum = convertStrToLL( maximum_str );
        } else {
            /// Not defined defined in XML, so defaults to E57_INT64_MAX
            pi.maximum = E57_INT64_MAX;
        }

        if (isAttributeDefined(attributes, att_scale)) {
            ustring scale_str = lookupAttribute(attributes, att_scale);
            pi.scale = atof(scale_str.c_str());  //??? use exact rounding library
        } else {
            /// Not defined defined in XML, so defaults to 1.0
            pi.scale = 1.0;
        }

        if (isAttributeDefined(attributes, att_offset)) {
            ustring offset_str = lookupAttribute(attributes, att_offset);
            pi.offset = atof(offset_str.c_str());  //??? use exact rounding library
        } else {
            /// Not defined defined in XML, so defaults to 0.0
            pi.offset = 0.0;
        }

        /// Push info so far onto stack
        stack_.push(pi);
    } else if (node_type == "Float") {
#ifdef E57_MAX_VERBOSE
        cout << "got a Float" << endl;
#endif
        pi.nodeType = E57_FLOAT;

        if (isAttributeDefined(attributes, att_precision)) {
            ustring precision_str = lookupAttribute(attributes, att_precision);
            if (precision_str == "single")
                pi.precision = E57_SINGLE;
            else if (precision_str == "double")
                pi.precision = E57_DOUBLE;
            else {
                throw E57_EXCEPTION2(E57_ERROR_BAD_XML_FORMAT,
                                     "precisionString=" + precision_str
                                     + " fileName=" + imf_->fileName()
                                     + " uri=" + toUString(uri)
                                     + " localName=" + toUString(localName)
                                     + " qName=" + toUString(qName));
            }
        } else {
            /// Not defined defined in XML, so defaults to double
            pi.precision = E57_DOUBLE;
        }

        if (isAttributeDefined(attributes, att_minimum)) {
            ustring minimum_str = lookupAttribute(attributes, att_minimum);
            pi.floatMinimum = atof(minimum_str.c_str());  //??? use exact rounding library
        } else {
            /// Not defined defined in XML, so defaults to E57_FLOAT_MIN or E57_DOUBLE_MIN
            if (pi.precision == E57_SINGLE)
                pi.floatMinimum = E57_FLOAT_MIN;
            else
                pi.floatMinimum = E57_DOUBLE_MIN;
        }

        if (isAttributeDefined(attributes, att_maximum)) {
            ustring maximum_str = lookupAttribute(attributes, att_maximum);
            pi.floatMaximum = atof(maximum_str.c_str());  //??? use exact rounding library
        } else {
            /// Not defined defined in XML, so defaults to FLOAT_MAX or DOUBLE_MAX
            if (pi.precision == E57_SINGLE)
                pi.floatMaximum = E57_FLOAT_MAX;
            else
                pi.floatMaximum = E57_DOUBLE_MAX;
        }

        /// Push info so far onto stack
        stack_.push(pi);
    } else if (node_type == "String") {
#ifdef E57_MAX_VERBOSE
        cout << "got a String" << endl;
#endif
        pi.nodeType = E57_STRING;

        /// Push info so far onto stack
        stack_.push(pi);
    } else if (node_type == "Blob") {
#ifdef E57_MAX_VERBOSE
        cout << "got a Blob" << endl;
#endif
        pi.nodeType = E57_BLOB;

        //??? check validity of numeric strings

        /// fileOffset is required to be defined
        ustring fileOffset_str = lookupAttribute(attributes, att_fileOffset);

        pi.fileOffset = convertStrToLL( fileOffset_str );

        /// length is required to be defined
        ustring length_str = lookupAttribute(attributes, att_length);

        pi.length = convertStrToLL( length_str );

        /// Push info so far onto stack
        stack_.push(pi);
    } else if (node_type == "Structure") {
#ifdef E57_MAX_VERBOSE
        cout << "got a Structure" << endl;
#endif
        pi.nodeType = E57_STRUCTURE;

        /// Read name space decls, if e57Root element
        if (toUString(localName) == "e57Root") {
            /// Search attributes for namespace declarations (only allowed in E57Root structure)
            bool gotDefault = false;
            for (size_t i = 0; i < attributes.getLength(); i++) {
                /// Check if declaring the default namespace
                if (toUString(attributes.getQName(i)) == "xmlns") {
#ifdef E57_VERBOSE
                    cout << "declared default namespace, URI=" << toUString(attributes.getValue(i)) << endl;
#endif
                    imf_->extensionsAdd("", toUString(attributes.getValue(i)));
                    gotDefault = true;
                }

                /// Check if declaring a namespace
                if (toUString(attributes.getURI(i)) == "http://www.w3.org/2000/xmlns/") {
#ifdef E57_VERBOSE
                    cout << "declared extension, prefix=" << toUString(attributes.getLocalName(i))
                         << " URI=" << toUString(attributes.getValue(i)) << endl;
#endif
                    imf_->extensionsAdd(toUString(attributes.getLocalName(i)), toUString(attributes.getValue(i)));
                }
            }

            /// If didn't declare a default namespace, have error
            if (!gotDefault) {
                throw E57_EXCEPTION2(E57_ERROR_BAD_XML_FORMAT,
                                     "fileName=" + imf_->fileName()
                                     + " uri=" + toUString(uri)
                                     + " localName=" + toUString(localName)
                                     + " qName=" + toUString(qName));
            }
        }

        /// Create container now, so can hold children
        shared_ptr<StructureNodeImpl> s_ni(new StructureNodeImpl(imf_));
        pi.container_ni = s_ni;

        /// After have Structure, check again if E57Root, if so mark attached so all children will be attached when added
        if (toUString(localName) == "e57Root")
            s_ni->setAttachedRecursive();

        /// Push info so far onto stack
        stack_.push(pi);
    } else if (node_type == "Vector") {
#ifdef E57_MAX_VERBOSE
        cout << "got a Vector" << endl;
#endif
        pi.nodeType = E57_VECTOR;

        if (isAttributeDefined(attributes, att_allowHeterogeneousChildren)) {
            ustring allowHetero_str = lookupAttribute(attributes, att_allowHeterogeneousChildren);

            int64_t i64 = convertStrToLL( allowHetero_str );

            if (i64 == 0)
                pi.allowHeterogeneousChildren = false;
            else if (i64 == 1)
                pi.allowHeterogeneousChildren = true;
            else {
                throw E57_EXCEPTION2(E57_ERROR_BAD_XML_FORMAT,
                                     "allowHeterogeneousChildren=" + toString(i64)
                                     + "fileName=" + imf_->fileName()
                                     + " uri=" + toUString(uri)
                                     + " localName=" + toUString(localName)
                                     + " qName=" + toUString(qName));
            }
        } else {
            /// Not defined defined in XML, so defaults to false
            pi.allowHeterogeneousChildren = false;
        }

        /// Create container now, so can hold children
        shared_ptr<VectorNodeImpl> v_ni(new VectorNodeImpl(imf_, pi.allowHeterogeneousChildren));
        pi.container_ni = v_ni;

        /// Push info so far onto stack
        stack_.push(pi);
    } else if (node_type == "CompressedVector") {
#ifdef E57_MAX_VERBOSE
        cout << "got a CompressedVector" << endl;
#endif
        pi.nodeType = E57_COMPRESSED_VECTOR;

        /// fileOffset is required to be defined
        ustring fileOffset_str = lookupAttribute(attributes, att_fileOffset);

        pi.fileOffset = convertStrToLL( fileOffset_str );

        /// recordCount is required to be defined
        ustring recordCount_str = lookupAttribute(attributes, att_recordCount);

        pi.recordCount = convertStrToLL( recordCount_str );

        /// Create container now, so can hold children
        shared_ptr<CompressedVectorNodeImpl> cv_ni(new CompressedVectorNodeImpl(imf_));
        cv_ni->setRecordCount(pi.recordCount);
        cv_ni->setBinarySectionLogicalStart(imf_->file_->physicalToLogical(pi.fileOffset));  //??? what if file_ is NULL?
        pi.container_ni = cv_ni;

        /// Push info so far onto stack
        stack_.push(pi);
    } else {
        throw E57_EXCEPTION2(E57_ERROR_BAD_XML_FORMAT,
                             "nodeType=" + node_type
                             + " fileName=" + imf_->fileName()
                             + " uri=" + toUString(uri)
                             + " localName=" + toUString(localName)
                             + " qName=" + toUString(qName));
    }
#ifdef E57_MAX_VERBOSE
    pi.dump(4);
#endif
}

void E57XmlParser::endElement(const XMLCh* const uri,
                              const XMLCh* const localName,
                              const XMLCh* const qName)
{
#ifdef E57_MAX_VERBOSE
    cout << "endElement" << endl;
#endif

    /// Pop the node that just ended
    ParseInfo pi = stack_.top();  //??? really want to make a copy here?
    stack_.pop();
#ifdef E57_MAX_VERBOSE
    pi.dump(4);
#endif

    /// We should now have all the info we need to create the node
    NodeImplSharedPtr current_ni;

    switch (pi.nodeType)
    {
        case E57_STRUCTURE:
        case E57_VECTOR:
            current_ni = pi.container_ni;
            break;
        case E57_COMPRESSED_VECTOR: {
            /// Verify that both prototype and codecs child elements were defined ???
            current_ni = pi.container_ni;
            } break;
        case E57_INTEGER: {
            /// Convert child text (if any) to value, else default to 0.0
            int64_t intValue;
            if (pi.childText.length() > 0) {
                intValue = convertStrToLL( pi.childText );
            } else
                intValue = 0;
            shared_ptr<IntegerNodeImpl> i_ni(new IntegerNodeImpl(imf_, intValue, pi.minimum, pi.maximum));
            current_ni = i_ni;
            } break;
        case E57_SCALED_INTEGER: {
            /// Convert child text (if any) to value, else default to 0.0
            int64_t intValue;
            if (pi.childText.length() > 0) {
                intValue = convertStrToLL( pi.childText );
            } else
                intValue = 0;
            shared_ptr<ScaledIntegerNodeImpl> si_ni(new ScaledIntegerNodeImpl(imf_, intValue, pi.minimum, pi.maximum, pi.scale, pi.offset));
            current_ni = si_ni;
            } break;
        case E57_FLOAT: {
            /// Convert child text (if any) to value, else default to 0.0
            double floatValue;
            if (pi.childText.length() > 0)
                floatValue = atof(pi.childText.c_str());
            else
                floatValue = 0.0;
            shared_ptr<FloatNodeImpl> f_ni(new FloatNodeImpl(imf_, floatValue, pi.precision, pi.floatMinimum, pi.floatMaximum));
            current_ni = f_ni;
            } break;
        case E57_STRING: {
            shared_ptr<StringNodeImpl> s_ni(new StringNodeImpl(imf_, pi.childText));
            current_ni = s_ni;
            } break;
        case E57_BLOB: {
            shared_ptr<BlobNodeImpl> b_ni(new BlobNodeImpl(imf_, pi.fileOffset, pi.length));
            current_ni = b_ni;
            } break;
        default:
            throw E57_EXCEPTION2(E57_ERROR_INTERNAL,
                                 "nodeType=" + toString(pi.nodeType)
                                 + " fileName=" + imf_->fileName()
                                 + " uri=" + toUString(uri)
                                 + " localName=" + toUString(localName)
                                 + " qName=" + toUString(qName));
    }
#ifdef E57_MAX_VERBOSE
    current_ni->dump(4);
#endif

    /// If first node in file ended, we are all done
    if (stack_.empty()) {
        /// Top level should be Structure
        if (current_ni->type() != E57_STRUCTURE) {
            throw E57_EXCEPTION2(E57_ERROR_BAD_XML_FORMAT,
                                 "currentType=" + toString(current_ni->type())
                                 + " fileName=" + imf_->fileName()
                                 + " uri=" + toUString(uri)
                                 + " localName=" + toUString(localName)
                                 + " qName=" + toUString(qName));
        }
        imf_->root_ = dynamic_pointer_cast<StructureNodeImpl>(current_ni);
        return;
    }

    /// Get next level up node (when entered function), which should be a container.
    NodeImplSharedPtr parent_ni = stack_.top().container_ni;

    if (!parent_ni) {
        throw E57_EXCEPTION2(E57_ERROR_BAD_XML_FORMAT,
                             "fileName=" + imf_->fileName()
                             + " uri=" + toUString(uri)
                             + " localName=" + toUString(localName)
                             + " qName=" + toUString(qName));
    }

    /// Add current node into parent at top of stack
    switch (parent_ni->type()) {
        case E57_STRUCTURE: {
            shared_ptr<StructureNodeImpl> struct_ni = dynamic_pointer_cast<StructureNodeImpl>(parent_ni);

            /// Add named child to structure
            struct_ni->set(toUString(qName), current_ni);
            } break;
        case E57_VECTOR: {
            shared_ptr<VectorNodeImpl> vector_ni = dynamic_pointer_cast<VectorNodeImpl>(parent_ni);

            /// Add unnamed child to vector
            vector_ni->append(current_ni);
            } break;
        case E57_COMPRESSED_VECTOR: {
            shared_ptr<CompressedVectorNodeImpl> cv_ni = dynamic_pointer_cast<CompressedVectorNodeImpl>(parent_ni);
            ustring uQName = toUString(qName);

            /// n can be either prototype or codecs
            if (uQName == "prototype")
                cv_ni->setPrototype(current_ni);
            else if (uQName == "codecs") {
                if (current_ni->type() != E57_VECTOR) {
                    throw E57_EXCEPTION2(E57_ERROR_BAD_XML_FORMAT,
                                         "currentType=" + toString(current_ni->type())
                                         + " fileName=" + imf_->fileName()
                                         + " uri=" + toUString(uri)
                                         + " localName=" + toUString(localName)
                                         + " qName=" + toUString(qName));
                }
                shared_ptr<VectorNodeImpl> vi = dynamic_pointer_cast<VectorNodeImpl>(current_ni);

                /// Check VectorNode is hetero
                if (!vi->allowHeteroChildren()) {
                    throw E57_EXCEPTION2(E57_ERROR_BAD_XML_FORMAT,
                                         "currentType=" + toString(current_ni->type())
                                         + " fileName=" + imf_->fileName()
                                         + " uri=" + toUString(uri)
                                         + " localName=" + toUString(localName)
                                         + " qName=" + toUString(qName));
                }

                cv_ni->setCodecs(vi);
            } else {
                /// Found unknown XML child element of CompressedVector, not prototype or codecs
                throw E57_EXCEPTION2(E57_ERROR_BAD_XML_FORMAT,
                                     + "fileName=" + imf_->fileName()
                                     + " uri=" + toUString(uri)
                                     + " localName=" + toUString(localName)
                                     + " qName=" + toUString(qName));
            }
        } break;
        default:
            /// Have bad XML nesting, parent should have been a container.
            throw E57_EXCEPTION2(E57_ERROR_BAD_XML_FORMAT,
                                 "parentType=" + toString(parent_ni->type())
                                 + " fileName=" + imf_->fileName()
                                 + " uri=" + toUString(uri)
                                 + " localName=" + toUString(localName)
                                 + " qName=" + toUString(qName));
    }
}

void E57XmlParser::characters(const   XMLCh* const chars,
                              const   XMLSize_t    length)
{
//??? use length to make ustring
#ifdef E57_MAX_VERBOSE
    cout << "characters, chars=\"" << toUString(chars) << "\" length=" << length << endl;
#endif
    /// Get active element
    ParseInfo& pi = stack_.top();

    /// Check if child text is allowed for current E57 element type
    switch (pi.nodeType) {
        case E57_STRUCTURE:
        case E57_VECTOR:
        case E57_COMPRESSED_VECTOR:
        case E57_BLOB: {
            /// If characters aren't whitespace, have an error, else ignore
            ustring s = toUString(chars);
            if (s.find_first_not_of(" \t\n\r") != string::npos)
                throw E57_EXCEPTION2(E57_ERROR_BAD_XML_FORMAT, "chars=" + toUString(chars));
            } break;
        default:
            /// Append to any previous characters
            pi.childText += toUString(chars);
    }
}

void E57XmlParser::error(const SAXParseException& ex)
{
    throw E57_EXCEPTION2(E57_ERROR_XML_PARSER,
                         "systemId=" + ustring(XMLString::transcode(ex.getSystemId()))
                         + " xmlLine=" + toString(ex.getLineNumber())
                         + " xmlColumn=" + toString(ex.getColumnNumber())
                         + " parserMessage=" + ustring(XMLString::transcode(ex.getMessage())));
}

void E57XmlParser::fatalError(const SAXParseException& ex)
{
    throw E57_EXCEPTION2(E57_ERROR_XML_PARSER,
                         "systemId=" + ustring(XMLString::transcode(ex.getSystemId()))
                         + " xmlLine=" + toString(ex.getLineNumber())
                         + " xmlColumn=" + toString(ex.getColumnNumber())
                         + " parserMessage=" + ustring(XMLString::transcode(ex.getMessage())));
}

void E57XmlParser::warning(const SAXParseException& ex)
{
    /// Don't take any action on warning from parser, just report
    cerr << "**** XML parser warning: " << ustring(XMLString::transcode(ex.getMessage())) << endl;
    cerr << "  Debug info:" << endl;
    cerr << "    systemId=" << XMLString::transcode(ex.getSystemId()) << endl;
    cerr << ",   xmlLine="     << ex.getLineNumber() << endl;
    cerr << ",   xmlColumn="   << ex.getColumnNumber() << endl;
}

ustring E57XmlParser::toUString(const XMLCh* const xml_str)
{
    ustring u_str;
    if (xml_str && *xml_str) {
        TranscodeToStr UTF8Transcoder(xml_str, "UTF-8");
        u_str  = ustring(reinterpret_cast<const char*>(UTF8Transcoder.str()));
    }
    return(u_str);
}

ustring E57XmlParser::lookupAttribute(const Attributes& attributes, const XMLCh* attribute_name)
{
    XMLSize_t attr_index;
    if (!attributes.getIndex(attribute_name, attr_index))
        throw E57_EXCEPTION2(E57_ERROR_BAD_XML_FORMAT, "attributeName=" + toUString(attribute_name));
    return(toUString(attributes.getValue(attr_index)));
}

bool E57XmlParser::isAttributeDefined(const Attributes& attributes, const XMLCh* attribute_name)
{
    XMLSize_t attr_index;
    return(attributes.getIndex(attribute_name, attr_index));
}
