/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
 * Copyright (C) 2012-2023 KiCad Developers, see AUTHORS.txt for contributors.
 * Copyright (C) 2017 CERN.
 *
 * @author Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#include <io/eagle/eagle_parser.h>

#include <string_utils.h>
#include <richio.h>
#include <wx/log.h>
#include <wx/regex.h>

#include <functional>
#include <cstdio>

constexpr auto DEFAULT_ALIGNMENT = ETEXT::BOTTOM_LEFT;


wxString escapeName( const wxString& aNetName )
{
    wxString ret( aNetName );

    ret.Replace( "!", "~" );

    return ConvertToNewOverbarNotation( ret );
}


wxString interpretText( const wxString& aText )
{
    wxString token = aText;

    if( substituteVariable( &token ) )
        return token;

    wxString text;
    bool sectionOpen = false;

    for( wxString::size_type i = 0; i < aText.size(); i++ )
    {
        // Interpret escaped characters
        if( aText[ i ] == '\\' )
        {
            if( i + 1 != aText.size() )
                text.Append( aText[ i + 1 ] );

            i++;
            continue;
        }

        // Escape ~ for KiCAD
        if( aText[i] == '~' )
        {
            text.Append( '~' );
            text.Append( '~' );
            continue;
        }

        if( aText[ i ] == '!' )
        {
            if( sectionOpen )
            {
                text.Append( '~' );
                sectionOpen = false;
                continue;
            }

            static wxString escapeChars( wxT( " )]}'\"" ) );

            if( i + 1 != aText.size() && escapeChars.Find( aText[i + 1] ) == wxNOT_FOUND )
            {
                sectionOpen = true;
                text.Append( '~' );
            }
            else
            {
                text.Append( aText[ i ] );
            }

            continue;
        }

        if( aText[i] == ',' && sectionOpen )
        {
            text.Append( '~' );
            sectionOpen = false;
        }

        text.Append( aText[ i ] );
    }

    return text;
}


bool substituteVariable( wxString* aText )
{
    if( aText->StartsWith( '>' ) && aText->AfterFirst( ' ' ).IsEmpty() )
    {
        wxString token = aText->Upper();

        if     ( token == wxT( ">NAME" ) )             *aText = wxT( "${REFERENCE}" );
        else if( token == wxT( ">VALUE" ) )            *aText = wxT( "${VALUE}" );
        else if( token == wxT( ">PART" ) )             *aText = wxT( "${REFERENCE}" );
        else if( token == wxT( ">GATE" ) )             *aText = wxT( "${UNIT}" );
        else if( token == wxT( ">MODULE" ) )           *aText = wxT( "${FOOTPRINT_NAME}" );
        else if( token == wxT( ">SHEETNR" ) )          *aText = wxT( "${#}" );
        else if( token == wxT( ">SHEETS" ) )           *aText = wxT( "${##}" );
        else if( token == wxT( ">SHEET" ) )            *aText = wxT( "${#}/${##}" );
        else if( token == wxT( ">SHEETNR_TOTAL" ) )    *aText = wxT( "${#}" );
        else if( token == wxT( ">SHEETS_TOTAL" ) )     *aText = wxT( "${##}" );
        else if( token == wxT( ">SHEET_TOTAL" ) )      *aText = wxT( "${#}/${##}" );
        else if( token == wxT( ">SHEET_HEADLINE" ) )   *aText = wxT( "${SHEETNAME}" );
        else if( token == wxT( ">ASSEMBLY_VARIANT" ) ) *aText = wxT( "${ASSEMBLY_VARIANT}" );
        else if( token == wxT( ">DRAWING_NAME" ) )     *aText = wxT( "${PROJECTNAME}" );
        else if( token == wxT( ">LAST_DATE_TIME" ) )   *aText = wxT( "${CURRENT_DATE}" );
        else if( token == wxT( ">PLOT_DATE_TIME" ) )   *aText = wxT( "${CURRENT_DATE}" );
        else *aText = wxString::Format( wxS( "${%s}" ), aText->Mid( 1 ).Trim() );

        return true;
    }

    return false;
}


wxString convertDescription( wxString aDescr )
{
    aDescr.Replace( wxS( "\n" ), wxS( " " ) );
    aDescr.Replace( wxS( "\r" ), wxEmptyString );

    wxRegEx( wxS( "<a\\s+(?:[^>]*?\\s+)?href=\"([^\"]*)\"[^>]*>" ) )
            .ReplaceAll( &aDescr, wxS( "\\1 " ) );

    aDescr.Replace( wxS( "<p>" ), wxS( "\n\n" ) );
    aDescr.Replace( wxS( "</p>" ), wxS( "\n\n" ) );

    aDescr.Replace( wxS( "<br>" ), wxS( "\n" ) );
    aDescr.Replace( wxS( "<ul>" ), wxS( "\n" ) );
    aDescr.Replace( wxS( "</ul>" ), wxS( "\n\n" ) );
    aDescr.Replace( wxS( "<li></li>" ), wxS( "\n" ) );
    aDescr.Replace( wxS( "<li>" ), wxS( "\n \u2022 " ) ); // Bullet point

    aDescr = RemoveHTMLTags( aDescr );

    wxRegEx( wxS( "\n +" ) ).ReplaceAll( &aDescr, wxS( "\n" ) );
    wxRegEx( wxS( " +\n" ) ).ReplaceAll( &aDescr, wxS( "\n" ) );

    wxRegEx( wxS( "\n{3,}" ) ).ReplaceAll( &aDescr, wxS( "\n\n" ) );
    wxRegEx( wxS( "^\n+" ) ).ReplaceAll( &aDescr, wxEmptyString );
    wxRegEx( wxS( "\n+$" ) ).ReplaceAll( &aDescr, wxEmptyString );

    return aDescr;
}


template<> template<>
OPTIONAL_XML_ATTRIBUTE<wxString>::OPTIONAL_XML_ATTRIBUTE( wxString aData )
{
    m_isAvailable = !aData.IsEmpty();

    if( m_isAvailable )
        Set( aData );
}


ECOORD::ECOORD( const wxString& aValue, enum ECOORD::EAGLE_UNIT aUnit )
{
    // This array is used to adjust the fraction part value basing on the number of digits
    // in the fraction.
    constexpr int DIVIDERS[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
    constexpr unsigned int DIVIDERS_MAX_IDX = sizeof( DIVIDERS ) / sizeof( DIVIDERS[0] ) - 1;

    int integer, fraction, pre_fraction, post_fraction;

    // The following check is needed to handle correctly negative fractions where the integer
    // part == 0.
    bool negative = ( aValue[0] == '-' );

    // %n is used to find out how many digits contains the fraction part, e.g. 0.001 contains 3
    // digits.
    int ret = sscanf( aValue.c_str(), "%d.%n%d%n", &integer, &pre_fraction, &fraction,
                      &post_fraction );

    if( ret == 0 )
        throw XML_PARSER_ERROR( "Invalid coordinate" );

    // process the integer part
    value = ConvertToNm( integer, aUnit );

    // process the fraction part
    if( ret == 2 )
    {
        int digits = post_fraction - pre_fraction;

        // adjust the number of digits if necessary as we cannot handle anything smaller than
        // nanometers (rounding).
        if( (unsigned) digits > DIVIDERS_MAX_IDX )
        {
            int diff = digits - DIVIDERS_MAX_IDX;
            digits = DIVIDERS_MAX_IDX;
            fraction /= DIVIDERS[diff];
        }

        int frac_value = ConvertToNm( fraction, aUnit ) / DIVIDERS[digits];

        // keep the sign in mind
        value = negative ? value - frac_value : value + frac_value;
    }
}


long long int ECOORD::ConvertToNm( int aValue, enum EAGLE_UNIT aUnit )
{
    long long int ret;

    switch( aUnit )
    {
        default:
        case EU_NM:    ret = aValue; break;
        case EU_MM:    ret = (long long) aValue * 1000000; break;
        case EU_INCH:  ret = (long long) aValue * 25400000; break;
        case EU_MIL:   ret = (long long) aValue * 25400; break;
    }

    if( ( ret > 0 ) != ( aValue > 0 ) )
        wxLogError( _( "Invalid size %lld: too large" ), aValue );

    return ret;
}


// Template specializations below parse wxString to the used types:
//      - wxString (preferred)
//      - string
//      - double
//      - int
//      - bool
//      - EROT
//      - ECOORD

template <>
wxString Convert<wxString>( const wxString& aValue )
{
    return aValue;
}


template <>
std::string Convert<std::string>( const wxString& aValue )
{
    return std::string( aValue.ToUTF8() );
}


template <>
double Convert<double>( const wxString& aValue )
{
    double value;

    if( aValue.ToCDouble( &value ) )
        return value;
    else
        throw XML_PARSER_ERROR( "Conversion to double failed. Original value: '" +
                                aValue.ToStdString() + "'." );
}


template <>
int Convert<int>( const wxString& aValue )
{
    if( aValue.IsEmpty() )
        throw XML_PARSER_ERROR( "Conversion to int failed. Original value is empty." );

    return wxAtoi( aValue );
}


template <>
bool Convert<bool>( const wxString& aValue )
{
    if( aValue != "yes" && aValue != "no" )
        throw XML_PARSER_ERROR( "Conversion to bool failed. Original value, '" +
                                aValue.ToStdString() +
                                "', is neither 'yes' nor 'no'." );

    return aValue == "yes";
}


/// parse an Eagle XML "rot" field.  Unfortunately the DTD seems not to explain
/// this format very well.  [S][M]R<degrees>.   Examples: "R90", "MR180", "SR180"
template<>
EROT Convert<EROT>( const wxString& aRot )
{
    EROT value;

    value.spin    = aRot.find( 'S' ) != aRot.npos;
    value.mirror  = aRot.find( 'M' ) != aRot.npos;
    value.degrees = strtod( aRot.c_str()
                            + 1                        // skip leading 'R'
                            + int( value.spin )       // skip optional leading 'S'
                            + int( value.mirror ),    // skip optional leading 'M'
                            nullptr );

    return value;
}


template<>
ECOORD Convert<ECOORD>( const wxString& aCoord )
{
    // Eagle uses millimeters as the default unit
    return ECOORD( aCoord, ECOORD::EAGLE_UNIT::EU_MM );
}


/**
 * Parse \a aAttribute of the XML node \a aNode.
 *
 * @param  aNode      is the node whose attribute will be parsed.
 * @param  aAttribute is the attribute that will be parsed.
 * @throw  XML_PARSER_ERROR - exception thrown if the required attribute is missing
 * @return T - the attributed parsed as the specified type.
 */
template<typename T>
T parseRequiredAttribute( wxXmlNode* aNode, const wxString& aAttribute )
{
    wxString value;

    if( aNode->GetAttribute( aAttribute, &value ) )
        return Convert<T>( value );
    else
        throw XML_PARSER_ERROR( "The required attribute " + aAttribute + " is missing." );
}


/**
 * Parse option \a aAttribute of the XML node \a aNode.
 *
 * @param  aNode      is the node whose attribute will be parsed.
 * @param  aAttribute is the attribute that will be parsed.
 * @return OPTIONAL_XML_ATTRIBUTE<T> - an optional XML attribute, parsed as the specified type if
 *                                     found.
 */
template<typename T>
OPTIONAL_XML_ATTRIBUTE<T> parseOptionalAttribute( wxXmlNode* aNode, const wxString& aAttribute )
{
    return OPTIONAL_XML_ATTRIBUTE<T>( aNode->GetAttribute( aAttribute ) );
}


NODE_MAP MapChildren( wxXmlNode* aCurrentNode )
{
    // Map node_name -> node_pointer
    NODE_MAP nodesMap;

    // Loop through all children mapping them in nodesMap
    if( aCurrentNode )
        aCurrentNode = aCurrentNode->GetChildren();

    while( aCurrentNode )
    {
        // Create a new pair in the map
        //      key: current node name
        //      value: current node pointer
        nodesMap[aCurrentNode->GetName()] = aCurrentNode;

        // Get next child
        aCurrentNode = aCurrentNode->GetNext();
    }

    return nodesMap;
}


VECTOR2I ConvertArcCenter( const VECTOR2I& aStart, const VECTOR2I& aEnd, double aAngle )
{
    // Eagle give us start and end.
    // S_ARC wants start to give the center, and end to give the start.
    double dx = aEnd.x - aStart.x, dy = aEnd.y - aStart.y;
    VECTOR2I mid = ( aStart + aEnd ) / 2;

    double dlen = sqrt( dx*dx + dy*dy );

    if( !std::isnormal( dlen ) || !std::isnormal( aAngle ) )
    {
        THROW_IO_ERROR( wxString::Format( _( "Invalid Arc with radius %f and angle %f" ),
                                          dlen,
                                          aAngle ) );
    }

    double dist = dlen / ( 2 * tan( DEG2RAD( aAngle ) / 2 ) );

    VECTOR2I center(
        mid.x + dist * ( dy / dlen ),
        mid.y - dist * ( dx / dlen )
    );

    return center;
}


static int parseAlignment( const wxString& aAlignment )
{
    // (bottom-left | bottom-center | bottom-right | center-left |
    // center | center-right | top-left | top-center | top-right)
    if( aAlignment == "center" )
        return ETEXT::CENTER;
    else if( aAlignment == "center-right" )
        return ETEXT::CENTER_RIGHT;
    else if( aAlignment == "top-left" )
        return ETEXT::TOP_LEFT;
    else if( aAlignment == "top-center" )
        return ETEXT::TOP_CENTER;
    else if( aAlignment == "top-right" )
        return ETEXT::TOP_RIGHT;
    else if( aAlignment == "bottom-left" )
        return ETEXT::BOTTOM_LEFT;
    else if( aAlignment == "bottom-center" )
        return ETEXT::BOTTOM_CENTER;
    else if( aAlignment == "bottom-right" )
        return ETEXT::BOTTOM_RIGHT;
    else if( aAlignment == "center-left" )
        return ETEXT::CENTER_LEFT;

    return DEFAULT_ALIGNMENT;
}


EWIRE::EWIRE( wxXmlNode* aWire )
{
    /*
        <!ELEMENT wire EMPTY>
        <!ATTLIST wire
            x1            %Coord;        #REQUIRED
            y1            %Coord;        #REQUIRED
            x2            %Coord;        #REQUIRED
            y2            %Coord;        #REQUIRED
            width         %Dimension;    #REQUIRED
            layer         %Layer;        #REQUIRED
            extent        %Extent;       #IMPLIED  -- only applicable for airwires --
            style         %WireStyle;    "continuous"
            curve         %WireCurve;    "0"
            cap           %WireCap;      "round"   -- only applicable if 'curve' is not zero --
        >
    */

    x1    = parseRequiredAttribute<ECOORD>( aWire, "x1" );
    y1    = parseRequiredAttribute<ECOORD>( aWire, "y1" );
    x2    = parseRequiredAttribute<ECOORD>( aWire, "x2" );
    y2    = parseRequiredAttribute<ECOORD>( aWire, "y2" );
    width = parseRequiredAttribute<ECOORD>( aWire, "width" );
    layer = parseRequiredAttribute<int>( aWire, "layer" );
    curve = parseOptionalAttribute<double>( aWire, "curve" );

    opt_wxString s = parseOptionalAttribute<wxString>( aWire, "style" );

    if( s == "continuous" )
        style = EWIRE::CONTINUOUS;
    else if( s == "longdash" )
        style = EWIRE::LONGDASH;
    else if( s == "shortdash" )
        style = EWIRE::SHORTDASH;
    else if( s == "dashdot" )
        style = EWIRE::DASHDOT;

    s = parseOptionalAttribute<wxString>( aWire, "cap" );

    if( s == "round" )
        cap = EWIRE::ROUND;
    else if( s == "flat" )
        cap = EWIRE::FLAT;
}


EJUNCTION::EJUNCTION( wxXmlNode* aJunction )
{
    /*
    <!ELEMENT junction EMPTY>
    <!ATTLIST junction
          x             %Coord;        #REQUIRED
          y             %Coord;        #REQUIRED
          >
    */

    x    = parseRequiredAttribute<ECOORD>( aJunction, "x" );
    y    = parseRequiredAttribute<ECOORD>( aJunction, "y" );
}


ELABEL::ELABEL( wxXmlNode* aLabel, const wxString& aNetName )
{
    /*
    <!ELEMENT label EMPTY>
    <!ATTLIST label
          x             %Coord;        #REQUIRED
          y             %Coord;        #REQUIRED
          size          %Dimension;    #REQUIRED
          layer         %Layer;        #REQUIRED
          font          %TextFont;     "proportional"
          ratio         %Int;          "8"
          rot           %Rotation;     "R0"
          xref          %Bool;         "no"
          >
    */

    x    = parseRequiredAttribute<ECOORD>( aLabel, "x" );
    y    = parseRequiredAttribute<ECOORD>( aLabel, "y" );
    size = parseRequiredAttribute<ECOORD>( aLabel, "size" );
    layer = parseRequiredAttribute<int>( aLabel, "layer" );
    rot   = parseOptionalAttribute<EROT>( aLabel, "rot" );
    xref  = parseOptionalAttribute<wxString>( aLabel, "xref" );
    netname = aNetName;
}


EVIA::EVIA( wxXmlNode* aVia )
{
    /*
    <!ELEMENT via EMPTY>
    <!ATTLIST via
          x             %Coord;        #REQUIRED
          y             %Coord;        #REQUIRED
          extent        %Extent;       #REQUIRED
          drill         %Dimension;    #REQUIRED
          diameter      %Dimension;    "0"
          shape         %ViaShape;     "round"
          alwaysstop    %Bool;         "no"
          >
    */

    x = parseRequiredAttribute<ECOORD>( aVia, "x" );
    y = parseRequiredAttribute<ECOORD>( aVia, "y" );

    wxString ext = parseRequiredAttribute<wxString>( aVia, "extent" );
    sscanf( ext.c_str(), "%d-%d", &layer_front_most, &layer_back_most );

    drill = parseRequiredAttribute<ECOORD>( aVia, "drill" );
    diam  = parseOptionalAttribute<ECOORD>( aVia, "diameter" );
    shape = parseOptionalAttribute<wxString>( aVia, "shape" );
}


ECIRCLE::ECIRCLE( wxXmlNode* aCircle )
{
    /*
    <!ELEMENT circle EMPTY>
    <!ATTLIST circle
          x             %Coord;        #REQUIRED
          y             %Coord;        #REQUIRED
          radius        %Coord;        #REQUIRED
          width         %Dimension;    #REQUIRED
          layer         %Layer;        #REQUIRED
          >
    */

    x      = parseRequiredAttribute<ECOORD>( aCircle, "x" );
    y      = parseRequiredAttribute<ECOORD>( aCircle, "y" );
    radius = parseRequiredAttribute<ECOORD>( aCircle, "radius" );
    width  = parseRequiredAttribute<ECOORD>( aCircle, "width" );
    layer  = parseRequiredAttribute<int>( aCircle, "layer" );
}


ERECT::ERECT( wxXmlNode* aRect )
{
    /*
    <!ELEMENT rectangle EMPTY>
    <!ATTLIST rectangle
          x1            %Coord;        #REQUIRED
          y1            %Coord;        #REQUIRED
          x2            %Coord;        #REQUIRED
          y2            %Coord;        #REQUIRED
          layer         %Layer;        #REQUIRED
          rot           %Rotation;     "R0"
          >
    */

    x1    = parseRequiredAttribute<ECOORD>( aRect, "x1" );
    y1    = parseRequiredAttribute<ECOORD>( aRect, "y1" );
    x2    = parseRequiredAttribute<ECOORD>( aRect, "x2" );
    y2    = parseRequiredAttribute<ECOORD>( aRect, "y2" );
    layer = parseRequiredAttribute<int>( aRect, "layer" );
    rot   = parseOptionalAttribute<EROT>( aRect, "rot" );
}


EATTR::EATTR( wxXmlNode* aTree )
{
    /*
    <!ELEMENT attribute EMPTY>
    <!ATTLIST attribute
        name          %String;       #REQUIRED
        value         %String;       #IMPLIED
        x             %Coord;        #IMPLIED
        y             %Coord;        #IMPLIED
        size          %Dimension;    #IMPLIED
        layer         %Layer;        #IMPLIED
        font          %TextFont;     #IMPLIED
        ratio         %Int;          #IMPLIED
        rot           %Rotation;     "R0"
        display       %AttributeDisplay; "value" -- only in <element> or <instance> context --
        constant      %Bool;         "no"     -- only in <device> context --
        >
    */

    name  = parseRequiredAttribute<wxString>( aTree, "name" );
    value = parseOptionalAttribute<wxString>( aTree, "value" );

    x     = parseOptionalAttribute<ECOORD>( aTree, "x" );
    y     = parseOptionalAttribute<ECOORD>( aTree, "y" );
    size  = parseOptionalAttribute<ECOORD>( aTree, "size" );

    layer = parseOptionalAttribute<int>( aTree, "layer" );
    ratio = parseOptionalAttribute<double>( aTree, "ratio" );
    rot   = parseOptionalAttribute<EROT>( aTree, "rot" );

    opt_wxString stemp = parseOptionalAttribute<wxString>( aTree, "display" );

    // (off | value | name | both)
    if( stemp == "off" )
        display = EATTR::Off;
    else if( stemp == "name" )
        display = EATTR::NAME;
    else if( stemp == "both" )
        display = EATTR::BOTH;
    else // "value" is the default
        display = EATTR::VALUE;

    stemp = parseOptionalAttribute<wxString>( aTree, "align" );

    align = stemp ? parseAlignment( *stemp ) : DEFAULT_ALIGNMENT;
}


EDIMENSION::EDIMENSION( wxXmlNode* aDimension )
{
    /*
    <!ELEMENT dimension EMPTY>
    <!ATTLIST dimension
          x1            %Coord;        #REQUIRED
          y1            %Coord;        #REQUIRED
          x2            %Coord;        #REQUIRED
          y2            %Coord;        #REQUIRED
          x3            %Coord;        #REQUIRED
          y3            %Coord;        #REQUIRED
          textsize      %Coord;
          layer         %Layer;        #REQUIRED
          dtype         %DimensionType; "parallel"
          >
    */

    x1       = parseRequiredAttribute<ECOORD>( aDimension, wxT( "x1" ) );
    y1       = parseRequiredAttribute<ECOORD>( aDimension, wxT( "y1" ) );
    x2       = parseRequiredAttribute<ECOORD>( aDimension, wxT( "x2" ) );
    y2       = parseRequiredAttribute<ECOORD>( aDimension, wxT( "y2" ) );
    x3       = parseRequiredAttribute<ECOORD>( aDimension, wxT( "x3" ) );
    y3       = parseRequiredAttribute<ECOORD>( aDimension, wxT( "y3" ) );
    textsize = parseOptionalAttribute<ECOORD>( aDimension, wxT( "textsize" ) );
    layer    = parseRequiredAttribute<int>( aDimension, wxT( "layer" ) );
    dimensionType = parseOptionalAttribute<wxString>( aDimension, wxT( "dtype" ) );
}


ETEXT::ETEXT( wxXmlNode* aText )
{
    /*
    <!ELEMENT text (#PCDATA)>
    <!ATTLIST text
          x             %Coord;        #REQUIRED
          y             %Coord;        #REQUIRED
          size          %Dimension;    #REQUIRED
          layer         %Layer;        #REQUIRED
          font          %TextFont;     "proportional"
          ratio         %Int;          "8"
          rot           %Rotation;     "R0"
          align         %Align;        "bottom-left"
          >
    */

    text  = aText->GetNodeContent();
    x     = parseRequiredAttribute<ECOORD>( aText, "x" );
    y     = parseRequiredAttribute<ECOORD>( aText, "y" );
    size  = parseRequiredAttribute<ECOORD>( aText, "size" );
    layer = parseRequiredAttribute<int>( aText, "layer" );

    font  = parseOptionalAttribute<wxString>( aText, "font" );
    ratio = parseOptionalAttribute<double>( aText, "ratio" );
    rot   = parseOptionalAttribute<EROT>( aText, "rot" );

    opt_wxString stemp = parseOptionalAttribute<wxString>( aText, "align" );

    align = stemp ? parseAlignment( *stemp ) : DEFAULT_ALIGNMENT;
}


VECTOR2I ETEXT::ConvertSize() const
{
    VECTOR2I textsize;

    if( font )
    {
        const wxString& fontName = font.CGet();

        if( fontName == "vector" )
        {
            textsize = VECTOR2I( size.ToSchUnits(), size.ToSchUnits() );
        }
        else if( fontName == "fixed" )
        {
            textsize = VECTOR2I( size.ToSchUnits(), size.ToSchUnits() * 0.80 );
        }
        else
        {
            textsize = VECTOR2I( size.ToSchUnits(), size.ToSchUnits() );
        }
    }
    else
    {
        textsize = VECTOR2I( size.ToSchUnits() * 0.85, size.ToSchUnits() );
    }

    return textsize;
}


EFRAME::EFRAME( wxXmlNode* aFrameNode )
{
    /*
     * <!ELEMENT frame EMPTY>
     * <!ATTLIST frame
     *          x1            %Coord;       #REQUIRED
     *          y1            %Coord;       #REQUIRED
     *          x2            %Coord;       #REQUIRED
     *          y2            %Coord;       #REQUIRED
     *          columns       %Int;         #REQUIRED
     *          rows          %Int;         #REQUIRED
     *          layer         %Layer;       #REQUIRED
     *          border-left   %Bool;        "yes"
     *          border-top    %Bool;        "yes"
     *          border-right  %Bool;        "yes"
     *          border-bottom %Bool;        "yes"
     *          >
     */
    border_left = true;
    border_top = true;
    border_right = true;
    border_bottom = true;

    x1 = parseRequiredAttribute<ECOORD>( aFrameNode, "x1" );
    y1 = parseRequiredAttribute<ECOORD>( aFrameNode, "y1" );
    x2 = parseRequiredAttribute<ECOORD>( aFrameNode, "x2" );
    y2 = parseRequiredAttribute<ECOORD>( aFrameNode, "y2" );
    columns = parseRequiredAttribute<int>( aFrameNode, "columns" );
    rows = parseRequiredAttribute<int>( aFrameNode, "rows" );
    layer = parseRequiredAttribute<int>( aFrameNode, "layer" );
    border_left = parseOptionalAttribute<bool>( aFrameNode, "border-left" );
    border_top = parseOptionalAttribute<bool>( aFrameNode, "border-top" );
    border_right = parseOptionalAttribute<bool>( aFrameNode, "border-right" );
    border_bottom = parseOptionalAttribute<bool>( aFrameNode, "border-bottom" );
}


EPAD_COMMON::EPAD_COMMON( wxXmlNode* aPad )
{
    // #REQUIRED says DTD, throw exception if not found
    name      = parseRequiredAttribute<wxString>( aPad, "name" );
    x         = parseRequiredAttribute<ECOORD>( aPad, "x" );
    y         = parseRequiredAttribute<ECOORD>( aPad, "y" );
    rot      = parseOptionalAttribute<EROT>( aPad, "rot" );
    stop     = parseOptionalAttribute<bool>( aPad, "stop" );
    thermals = parseOptionalAttribute<bool>( aPad, "thermals" );
}


EPAD::EPAD( wxXmlNode* aPad )
    : EPAD_COMMON( aPad )
{
    /*
    <!ELEMENT pad EMPTY>
    <!ATTLIST pad
          name          %String;       #REQUIRED
          x             %Coord;        #REQUIRED
          y             %Coord;        #REQUIRED
          drill         %Dimension;    #REQUIRED
          diameter      %Dimension;    "0"
          shape         %PadShape;     "round"
          rot           %Rotation;     "R0"
          stop          %Bool;         "yes"
          thermals      %Bool;         "yes"
          first         %Bool;         "no"
          >
    */

    // #REQUIRED says DTD, throw exception if not found
    drill        = parseRequiredAttribute<ECOORD>( aPad, "drill" );

    // Optional attributes
    diameter     = parseOptionalAttribute<ECOORD>( aPad, "diameter" );

    opt_wxString s = parseOptionalAttribute<wxString>( aPad, "shape" );

    // (square | round | octagon | long | offset)
    if( s == "square" )
        shape = EPAD::SQUARE;
    else if( s == "round" )
        shape = EPAD::ROUND;
    else if( s == "octagon" )
        shape = EPAD::OCTAGON;
    else if( s == "long" )
        shape = EPAD::LONG;
    else if( s == "offset" )
        shape = EPAD::OFFSET;

    first    = parseOptionalAttribute<bool>( aPad, "first" );
}


ESMD::ESMD( wxXmlNode* aSMD )
    : EPAD_COMMON( aSMD )
{
    /*
    <!ATTLIST smd
          name          %String;       #REQUIRED
          x             %Coord;        #REQUIRED
          y             %Coord;        #REQUIRED
          dx            %Dimension;    #REQUIRED
          dy            %Dimension;    #REQUIRED
          layer         %Layer;        #REQUIRED
          roundness     %Int;          "0"
          rot           %Rotation;     "R0"
          stop          %Bool;         "yes"
          thermals      %Bool;         "yes"
          cream         %Bool;         "yes"
          >
    */

    // DTD #REQUIRED, throw exception if not found
    dx        = parseRequiredAttribute<ECOORD>( aSMD, "dx" );
    dy        = parseRequiredAttribute<ECOORD>( aSMD, "dy" );
    layer     = parseRequiredAttribute<int>( aSMD, "layer" );

    roundness = parseOptionalAttribute<int>( aSMD, "roundness" );
    cream     = parseOptionalAttribute<bool>( aSMD, "cream" );
}


EPIN::EPIN( wxXmlNode* aPin )
{
    /*
    <!ELEMENT pin EMPTY>
    <!ATTLIST pin
              name          %String;       #REQUIRED
              x             %Coord;        #REQUIRED
              y             %Coord;        #REQUIRED
              visible       %PinVisible;   "both"
              length        %PinLength;    "long"
              direction     %PinDirection; "io"
              function      %PinFunction;  "none"
              swaplevel     %Int;          "0"
              rot           %Rotation;     "R0"
              >
    */

    // DTD #REQUIRED, throw exception if not found
    name      = parseRequiredAttribute<wxString>( aPin, "name" );
    x         = parseRequiredAttribute<ECOORD>( aPin, "x" );
    y         = parseRequiredAttribute<ECOORD>( aPin, "y" );

    visible   = parseOptionalAttribute<wxString>( aPin, "visible" );
    length    = parseOptionalAttribute<wxString>( aPin, "length" );
    direction = parseOptionalAttribute<wxString>( aPin, "direction" );
    function  = parseOptionalAttribute<wxString>( aPin, "function" );
    swaplevel = parseOptionalAttribute<int>( aPin, "swaplevel" );
    rot       = parseOptionalAttribute<EROT>( aPin, "rot" );
}


EVERTEX::EVERTEX( wxXmlNode* aVertex )
{
    /*
    <!ELEMENT vertex EMPTY>
    <!ATTLIST vertex
          x             %Coord;        #REQUIRED
          y             %Coord;        #REQUIRED
          curve         %WireCurve;    "0" -- the curvature from this vertex to the next one --
          >
    */

    x = parseRequiredAttribute<ECOORD>( aVertex, "x" );
    y = parseRequiredAttribute<ECOORD>( aVertex, "y" );
    curve = parseOptionalAttribute<double>( aVertex, "curve" );
}


EPOLYGON::EPOLYGON( wxXmlNode* aPolygon )
{
    /*
    <!ATTLIST polygon
          width         %Dimension;    #REQUIRED
          layer         %Layer;        #REQUIRED
          spacing       %Dimension;    #IMPLIED
          pour          %PolygonPour;  "solid"
          isolate       %Dimension;    #IMPLIED -- only in <signal> or <package> context --
          orphans       %Bool;         "no"  -- only in <signal> context --
          thermals      %Bool;         "yes" -- only in <signal> context --
          rank          %Int;          "0"   -- 1..6 in <signal> context, 0 or 7 in
                                                <package> context --
          >
    */

    width        = parseRequiredAttribute<ECOORD>( aPolygon, "width" );
    layer        = parseRequiredAttribute<int>( aPolygon, "layer" );

    spacing      = parseOptionalAttribute<ECOORD>( aPolygon, "spacing" );
    isolate      = parseOptionalAttribute<ECOORD>( aPolygon, "isolate" );
    opt_wxString s = parseOptionalAttribute<wxString>( aPolygon, "pour" );

    // default pour to solid fill
    pour = EPOLYGON::SOLID;

    // (solid | hatch | cutout)
    if( s == "hatch" )
        pour = EPOLYGON::HATCH;
    else if( s == "cutout" )
        pour = EPOLYGON::CUTOUT;

    orphans  = parseOptionalAttribute<bool>( aPolygon, "orphans" );
    thermals = parseOptionalAttribute<bool>( aPolygon, "thermals" );
    rank     = parseOptionalAttribute<int>( aPolygon, "rank" );
}


EHOLE::EHOLE( wxXmlNode* aHole )
{
    /*
    <!ELEMENT hole EMPTY>
    <!ATTLIST hole
          x             %Coord;        #REQUIRED
          y             %Coord;        #REQUIRED
          drill         %Dimension;    #REQUIRED
          >
    */

    // #REQUIRED:
    x     = parseRequiredAttribute<ECOORD>( aHole, "x" );
    y     = parseRequiredAttribute<ECOORD>( aHole, "y" );
    drill = parseRequiredAttribute<ECOORD>( aHole, "drill" );
}


EELEMENT::EELEMENT( wxXmlNode* aElement )
{
    /*
    <!ELEMENT element (attribute*, variant*)>
    <!ATTLIST element
          name          %String;       #REQUIRED
          library       %String;       #REQUIRED
          package       %String;       #REQUIRED
          value         %String;       #REQUIRED
          x             %Coord;        #REQUIRED
          y             %Coord;        #REQUIRED
          locked        %Bool;         "no"
          smashed       %Bool;         "no"
          rot           %Rotation;     "R0"
          >
    */

    // #REQUIRED
    name    = parseRequiredAttribute<wxString>( aElement, "name" );
    library = parseRequiredAttribute<wxString>( aElement, "library" );
    value   = parseRequiredAttribute<wxString>( aElement, "value" );
    std::string p = parseRequiredAttribute<std::string>( aElement, "package" );
    ReplaceIllegalFileNameChars( &p, '_' );
    package = wxString::FromUTF8( p.c_str() );

    x       = parseRequiredAttribute<ECOORD>( aElement, "x" );
    y       = parseRequiredAttribute<ECOORD>( aElement, "y" );

    // optional
    locked  = parseOptionalAttribute<bool>( aElement, "locked" );
    smashed = parseOptionalAttribute<bool>( aElement, "smashed" );
    rot     = parseOptionalAttribute<EROT>( aElement, "rot" );
}


ELAYER::ELAYER( wxXmlNode* aLayer )
{
    /*
    <!ELEMENT layer EMPTY>
    <!ATTLIST layer
          number        %Layer;        #REQUIRED
          name          %String;       #REQUIRED
          color         %Int;          #REQUIRED
          fill          %Int;          #REQUIRED
          visible       %Bool;         "yes"
          active        %Bool;         "yes"
          >
    */

    number  = parseRequiredAttribute<int>( aLayer, "number" );
    name    = parseRequiredAttribute<wxString>( aLayer, "name" );
    color   = parseRequiredAttribute<int>( aLayer, "color" );
    fill    = 1;    // Temporary value.
    visible = parseOptionalAttribute<bool>( aLayer, "visible" );
    active  = parseOptionalAttribute<bool>( aLayer, "active" );
}


EPART::EPART( wxXmlNode* aPart )
{
    /*
     *  <!ELEMENT part (attribute*, variant*)>
     *  <!ATTLIST part
     *  name          %String;       #REQUIRED
     *  library       %String;       #REQUIRED
     *  deviceset     %String;       #REQUIRED
     *  device        %String;       #REQUIRED
     *  technology    %String;       ""
     *  value         %String;       #IMPLIED
     *  >
     */
    // #REQUIRED
    name = parseRequiredAttribute<wxString>( aPart, "name" );
    library = parseRequiredAttribute<wxString>( aPart, "library" );
    deviceset = parseRequiredAttribute<wxString>( aPart, "deviceset" );
    device = parseRequiredAttribute<wxString>( aPart, "device" );
    technology = parseOptionalAttribute<wxString>( aPart, "technology" );
    value = parseOptionalAttribute<wxString>( aPart, "value" );

    for( wxXmlNode* child = aPart->GetChildren(); child; child = child->GetNext() )
    {
        if( child->GetName() == "attribute" )
        {
            std::string aname, avalue;

            for( wxXmlAttribute* x = child->GetAttributes(); x; x = x->GetNext() )
            {
                if( x->GetName() == "name" )
                    aname = x->GetValue();
                else if( x->GetName() == "value" )
                    avalue = x->GetValue();
            }

            if( aname.size() && avalue.size() )
                attribute[aname] = avalue;
        }
        else if( child->GetName() == "variant" )
        {
            std::string aname, avalue;

            for( wxXmlAttribute* x = child->GetAttributes(); x; x = x->GetNext() )
            {
                if( x->GetName() == "name" )
                    aname = x->GetValue();
                else if( x->GetName() == "value" )
                    avalue = x->GetValue();
            }

            if( aname.size() && avalue.size() )
                variant[aname] = avalue;
        }
    }
}


EINSTANCE::EINSTANCE( wxXmlNode* aInstance )
{
    /*
     *  <!ELEMENT instance (attribute)*>
     *  <!ATTLIST instance
     *     part          %String;       #REQUIRED
     *     gate          %String;       #REQUIRED
     *     x             %Coord;        #REQUIRED
     *     y             %Coord;        #REQUIRED
     *     smashed       %Bool;         "no"
     *     rot           %Rotation;     "R0"
     *     >
     */
    part    = parseRequiredAttribute<wxString>( aInstance, "part" );
    gate    = parseRequiredAttribute<wxString>( aInstance, "gate" );

    x   = parseRequiredAttribute<ECOORD>( aInstance, "x" );
    y   = parseRequiredAttribute<ECOORD>( aInstance, "y" );

    // optional
    smashed = parseOptionalAttribute<bool>( aInstance, "smashed" );
    rot = parseOptionalAttribute<EROT>( aInstance, "rot" );
}


EGATE::EGATE( wxXmlNode* aGate )
{
    /*
     *   <!ELEMENT gate EMPTY>
     *   <!ATTLIST gate
     *   name          %String;       #REQUIRED
     *   symbol        %String;       #REQUIRED
     *   x             %Coord;        #REQUIRED
     *   y             %Coord;        #REQUIRED
     *   addlevel      %GateAddLevel; "next"
     *   swaplevel     %Int;          "0"
     *   >
     */

    name = parseRequiredAttribute<wxString>( aGate, "name" );
    symbol = parseRequiredAttribute<wxString>( aGate, "symbol" );

    x   = parseRequiredAttribute<ECOORD>( aGate, "x" );
    y   = parseRequiredAttribute<ECOORD>( aGate, "y" );

    opt_wxString stemp = parseOptionalAttribute<wxString>( aGate, "addlevel" );

    // (off | value | name | both)
    if( stemp == "must" )
        addlevel = EGATE::MUST;
    else if( stemp == "can" )
        addlevel = EGATE::CAN;
    else if( stemp == "next" )
        addlevel = EGATE::NEXT;
    else if( stemp == "request" )
        addlevel = EGATE::REQUEST;
    else if( stemp == "always" )
        addlevel = EGATE::ALWAYS;
    else
        addlevel = EGATE::NEXT;
}


ECONNECT::ECONNECT( wxXmlNode* aConnect )
{
    /*
     *  <!ELEMENT connect EMPTY>
     *  <!ATTLIST connect
     *         gate          %String;       #REQUIRED
     *         pin           %String;       #REQUIRED
     *         pad           %String;       #REQUIRED
     *         route         %ContactRoute; "all"
     *         >
     */
    gate =  parseRequiredAttribute<wxString>( aConnect, "gate" );
    pin =  parseRequiredAttribute<wxString>( aConnect, "pin" );
    pad = parseRequiredAttribute<wxString>( aConnect, "pad" );
}


EDEVICE::EDEVICE( wxXmlNode* aDevice )
{
    /*
        <!ELEMENT device (connects?, technologies?)>
        <!ATTLIST device
                name          %String;       ""
                package       %String;       #IMPLIED
                >
     */
    name = parseRequiredAttribute<wxString>( aDevice, "name" );
    opt_wxString pack = parseOptionalAttribute<wxString>( aDevice, "package" );

    if( pack )
    {
        std::string p( pack->c_str() );
        ReplaceIllegalFileNameChars( &p, '_' );
        package.Set( wxString::FromUTF8( p.c_str() ) );
    }

    NODE_MAP   aDeviceChildren = MapChildren( aDevice );
    wxXmlNode* connectNode = getChildrenNodes( aDeviceChildren, "connects" );

    while( connectNode )
    {
        connects.emplace_back( connectNode );
        connectNode = connectNode->GetNext();
    }
}


EDEVICE_SET::EDEVICE_SET( wxXmlNode* aDeviceSet )
{
    /*
      <!ELEMENT deviceset (description?, gates, devices)>
      <!ATTLIST deviceset
              name          %String;       #REQUIRED
              prefix        %String;       ""
              uservalue     %Bool;         "no"
              >
     */

    name = parseRequiredAttribute<wxString>( aDeviceSet, "name" );
    prefix = parseOptionalAttribute<wxString>( aDeviceSet, "prefix" );
    uservalue = parseOptionalAttribute<bool>( aDeviceSet, "uservalue" );
}


ECLASS::ECLASS( wxXmlNode* aClass )
{
    number = parseRequiredAttribute<wxString>( aClass, "number" );
    name   = parseRequiredAttribute<wxString>( aClass, "name" );

    for( wxXmlNode* child = aClass->GetChildren(); child; child = child->GetNext() )
    {
        if( child->GetName() == "clearance" )
        {
            wxString to = parseRequiredAttribute<wxString>( child, "class" );
            ECOORD value = parseRequiredAttribute<ECOORD>( child, "value" );

            clearanceMap[to] = value;
        }
    }
}
