/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2020-2021 Roberto Fernandez Bautista <roberto.fer.bau@gmail.com>
 * Copyright (C) 2020-2021 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file cadstar_archive_parser.cpp
 * @brief Helper functions and common defines between schematic and PCB Archive files
 */
#include <wx/filename.h>
#include <wx/log.h>
#include <wx/xml/xml.h>

#include <dsnlexer.h>
#include <geometry/shape_poly_set.h>
#include <plugins/cadstar/cadstar_archive_parser.h>
#include <eda_item.h>
#include <eda_text.h>
#include <macros.h>
#include <progress_reporter.h>
#include <string_utils.h>
#include <trigo.h>

// Ratio derived from CADSTAR default font. See doxygen comment in cadstar_archive_parser.h
const double CADSTAR_ARCHIVE_PARSER::TXT_HEIGHT_RATIO = ( 24.0 - 5.0 ) / 24.0;

// Cadstar fields and their KiCad equivalent
const std::map<CADSTAR_ARCHIVE_PARSER::TEXT_FIELD_NAME, wxString>
        CADSTAR_ARCHIVE_PARSER::CADSTAR_TO_KICAD_FIELDS =
                {   { TEXT_FIELD_NAME::DESIGN_TITLE, wxT( "DESIGN_TITLE" ) },
                    { TEXT_FIELD_NAME::SHORT_JOBNAME, wxT( "SHORT_JOBNAME" ) },
                    { TEXT_FIELD_NAME::LONG_JOBNAME, wxT( "LONG_JOBNAME" ) },
                    { TEXT_FIELD_NAME::NUM_OF_SHEETS, wxT( "##" ) },
                    { TEXT_FIELD_NAME::SHEET_NUMBER, wxT( "#" ) },
                    { TEXT_FIELD_NAME::SHEET_NAME, wxT( "SHEETNAME" ) },
                    { TEXT_FIELD_NAME::VARIANT_NAME, wxT( "VARIANT_NAME" ) },
                    { TEXT_FIELD_NAME::VARIANT_DESCRIPTION, wxT( "VARIANT_DESCRIPTION" ) },
                    { TEXT_FIELD_NAME::REG_USER, wxT( "REG_USER" ) },
                    { TEXT_FIELD_NAME::COMPANY_NAME, wxT( "COMPANY_NAME" ) },
                    { TEXT_FIELD_NAME::CURRENT_USER, wxT( "CURRENT_USER" ) },
                    { TEXT_FIELD_NAME::DATE, wxT( "DATE" ) },
                    { TEXT_FIELD_NAME::TIME, wxT( "TIME" ) },
                    { TEXT_FIELD_NAME::MACHINE_NAME, wxT( "MACHINE_NAME" ) } };


void CADSTAR_ARCHIVE_PARSER::FORMAT::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "FORMAT" ) );

    Type    = GetXmlAttributeIDString( aNode, 0 );
    SomeInt = GetXmlAttributeIDLong( aNode, 1 );
    Version = GetXmlAttributeIDLong( aNode, 2 );
}


void CADSTAR_ARCHIVE_PARSER::TIMESTAMP::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "TIMESTAMP" ) );

    if( !GetXmlAttributeIDString( aNode, 0 ).ToLong( &Year )
            || !GetXmlAttributeIDString( aNode, 1 ).ToLong( &Month )
            || !GetXmlAttributeIDString( aNode, 2 ).ToLong( &Day )
            || !GetXmlAttributeIDString( aNode, 3 ).ToLong( &Hour )
            || !GetXmlAttributeIDString( aNode, 4 ).ToLong( &Minute )
            || !GetXmlAttributeIDString( aNode, 5 ).ToLong( &Second ) )
        THROW_PARSING_IO_ERROR( wxT( "TIMESTAMP" ), wxString::Format( "HEADER" ) );
}


void CADSTAR_ARCHIVE_PARSER::HEADER::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "HEADER" ) );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString nodeName = cNode->GetName();

        if( nodeName == wxT( "FORMAT" ) )
        {
            Format.Parse( cNode, aContext );
        }
        else if( nodeName == wxT( "JOBFILE" ) )
        {
            JobFile = GetXmlAttributeIDString( cNode, 0 );
        }
        else if( nodeName == wxT( "JOBTITLE" ) )
        {
            JobTitle = GetXmlAttributeIDString( cNode, 0 );
        }
        else if( nodeName == wxT( "GENERATOR" ) )
        {
            Generator = GetXmlAttributeIDString( cNode, 0 );
        }
        else if( nodeName == wxT( "RESOLUTION" ) )
        {
            XNODE* subNode = cNode->GetChildren();

            if( ( subNode->GetName() == wxT( "METRIC" ) )
                    && ( GetXmlAttributeIDString( subNode, 0 ) == wxT( "HUNDREDTH" ) )
                    && ( GetXmlAttributeIDString( subNode, 1 ) == wxT( "MICRON" ) ) )
            {
                Resolution = RESOLUTION::HUNDREDTH_MICRON;
            }
            else
            {
                // TODO Need to find out if there are other possible resolutions. Logically
                // there must be other base units that could be used, such as "IMPERIAL INCH"
                // or "METRIC MM" but so far none of settings in CADSTAR generated a different
                // output resolution to "HUNDREDTH MICRON"
                THROW_UNKNOWN_NODE_IO_ERROR( subNode->GetName(), wxT( "HEADER->RESOLUTION" ) );
            }
        }
        else if( nodeName == wxT( "TIMESTAMP" ) )
        {
            Timestamp.Parse( cNode, aContext );
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNode->GetName(), wxT( "HEADER" ) );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::VARIANT::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "VMASTER" ) || aNode->GetName() == wxT( "VARIANT" ) );

    ID = GetXmlAttributeIDString( aNode, 0 );

    if( aNode->GetName() == wxT( "VMASTER" ) )
    {
        Name        = GetXmlAttributeIDString( aNode, 1 );
        Description = GetXmlAttributeIDString( aNode, 2 );
    }
    else
    {
        ParentID    = GetXmlAttributeIDString( aNode, 1 );
        Name        = GetXmlAttributeIDString( aNode, 2 );
        Description = GetXmlAttributeIDString( aNode, 3 );
    }
}


void CADSTAR_ARCHIVE_PARSER::VARIANT_HIERARCHY::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "VHIERARCHY" ) );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        if( cNode->GetName() == wxT( "VMASTER" ) || cNode->GetName() == wxT( "VARIANT" ) )
        {
            VARIANT variant;
            variant.Parse( cNode, aContext );
            Variants.insert( std::make_pair( variant.ID, variant ) );
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNode->GetName(), cNode->GetName() );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::LINECODE::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "LINECODE" ) );

    ID   = GetXmlAttributeIDString( aNode, 0 );
    Name = GetXmlAttributeIDString( aNode, 1 );

    if( !GetXmlAttributeIDString( aNode, 2 ).ToLong( &Width ) )
        THROW_PARSING_IO_ERROR( wxT( "Line Width" ), wxString::Format( "LINECODE -> %s", Name ) );

    XNODE* cNode = aNode->GetChildren();

    if( !cNode || cNode->GetName() != wxT( "STYLE" ) )
        THROW_UNKNOWN_NODE_IO_ERROR( cNode->GetName(), wxString::Format( "LINECODE -> %s", Name ) );

    wxString styleStr = GetXmlAttributeIDString( cNode, 0 );

    if( styleStr == wxT( "SOLID" ) )
    {
        Style = LINESTYLE::SOLID;
    }
    else if( styleStr == wxT( "DASH" ) )
    {
        Style = LINESTYLE::DASH;
    }
    else if( styleStr == wxT( "DASHDOT" ) )
    {
        Style = LINESTYLE::DASHDOT;
    }
    else if( styleStr == wxT( "DASHDOTDOT" ) )
    {
        Style = LINESTYLE::DASHDOTDOT;
    }
    else if( styleStr == wxT( "DOT" ) )
    {
        Style = LINESTYLE::DOT;
    }
    else
    {
        THROW_UNKNOWN_PARAMETER_IO_ERROR( wxString::Format( "STYLE %s", styleStr ),
                wxString::Format( "LINECODE -> %s", Name ) );
    }
}


void CADSTAR_ARCHIVE_PARSER::HATCH::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "HATCH" ) );

    Step      = GetXmlAttributeIDLong( aNode, 0 );
    LineWidth = GetXmlAttributeIDLong( aNode, 2 );

    XNODE* cNode = aNode->GetChildren();

    if( !cNode || cNode->GetName() != wxT( "ORIENT" ) )
        THROW_MISSING_NODE_IO_ERROR( wxT( "ORIENT" ), wxT( "HATCH" ) );

    OrientAngle = GetXmlAttributeIDLong( cNode, 0 );
}


void CADSTAR_ARCHIVE_PARSER::HATCHCODE::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "HATCHCODE" ) );

    ID   = GetXmlAttributeIDString( aNode, 0 );
    Name = GetXmlAttributeIDString( aNode, 1 );

    XNODE*   cNode    = aNode->GetChildren();
    wxString location = wxString::Format( "HATCHCODE -> %s", Name );

    for( ; cNode; cNode = cNode->GetNext() )
    {
        if( cNode->GetName() != wxT( "HATCH" ) )
            THROW_UNKNOWN_NODE_IO_ERROR( cNode->GetName(), location );

        HATCH hatch;
        hatch.Parse( cNode, aContext );
        Hatches.push_back( hatch );
    }
}


void CADSTAR_ARCHIVE_PARSER::FONT::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "FONT" ) );

    Name      = GetXmlAttributeIDString( aNode, 0 );
    Modifier1 = GetXmlAttributeIDLong( aNode, 1 );
    Modifier2 = GetXmlAttributeIDLong( aNode, 2 );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "ITALIC" ) )
            Italic = true;
        else if( cNodeName == wxT( "KERNING" ) )
            KerningPairs = true;
        else
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
    }
}


void CADSTAR_ARCHIVE_PARSER::TEXTCODE::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "TEXTCODE" ) );

    ID   = GetXmlAttributeIDString( aNode, 0 );
    Name = GetXmlAttributeIDString( aNode, 1 );

    LineWidth = GetXmlAttributeIDLong( aNode, 2 );
    Height    = GetXmlAttributeIDLong( aNode, 3 );
    Width     = GetXmlAttributeIDLong( aNode, 4 );

    XNODE* cNode = aNode->GetChildren();

    if( cNode )
    {
        if( cNode->GetName() == wxT( "FONT" ) )
            Font.Parse( cNode, aContext );
        else
            THROW_UNKNOWN_NODE_IO_ERROR( cNode->GetName(), aNode->GetName() );
    }
}


void CADSTAR_ARCHIVE_PARSER::ROUTEREASSIGN::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "ROUTEREASSIGN" ) );

    LayerID      = GetXmlAttributeIDString( aNode, 0 );
    OptimalWidth = GetXmlAttributeIDLong( aNode, 1, false );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "NECKWIDTH" ) )
            NeckedWidth = GetXmlAttributeIDLong( cNode, 0 );
        else if( cNodeName == wxT( "SROUTEWIDTH" ) )
            OptimalWidth = GetXmlAttributeIDLong( cNode, 0 );
        else if( cNodeName == wxT( "MINWIDTH" ) )
            MinWidth = GetXmlAttributeIDLong( cNode, 0 );
        else if( cNodeName == wxT( "MAXWIDTH" ) )
            MaxWidth = GetXmlAttributeIDLong( cNode, 0 );
        else
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
    }
}


void CADSTAR_ARCHIVE_PARSER::ROUTECODE::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "ROUTECODE" ) );

    ID           = GetXmlAttributeIDString( aNode, 0 );
    Name         = GetXmlAttributeIDString( aNode, 1 );
    OptimalWidth = GetXmlAttributeIDLong( aNode, 2, false );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "NECKWIDTH" ) )
        {
            NeckedWidth = GetXmlAttributeIDLong( cNode, 0 );
        }
        else if( cNodeName == wxT( "SROUTEWIDTH" ) )
        {
            OptimalWidth = GetXmlAttributeIDLong( cNode, 0 );
        }
        else if( cNodeName == wxT( "MINWIDTH" ) )
        {
            MinWidth = GetXmlAttributeIDLong( cNode, 0 );
        }
        else if( cNodeName == wxT( "MAXWIDTH" ) )
        {
            MaxWidth = GetXmlAttributeIDLong( cNode, 0 );
        }
        else if( cNodeName == wxT( "ROUTEREASSIGN" ) )
        {
            ROUTEREASSIGN routereassign;
            routereassign.Parse( cNode, aContext );
            RouteReassigns.push_back( routereassign );
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
        }
    }
}


double CADSTAR_ARCHIVE_PARSER::EVALUE::GetDouble()
{
    return Base * std::pow( 10.0, Exponent );
}


void CADSTAR_ARCHIVE_PARSER::EVALUE::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "E" ) );

    if( ( !GetXmlAttributeIDString( aNode, 0 ).ToLong( &Base ) )
            || ( !GetXmlAttributeIDString( aNode, 1 ).ToLong( &Exponent ) ) )
    {
        THROW_PARSING_IO_ERROR( wxT( "Base and Exponent" ),
                wxString::Format(
                        "%s->%s", aNode->GetParent()->GetName(), aNode->GetParent()->GetName() ) );
    }
}


void CADSTAR_ARCHIVE_PARSER::POINT::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "PT" ) );

    x = GetXmlAttributeIDLong( aNode, 0 );
    y = GetXmlAttributeIDLong( aNode, 1 );
}


void CADSTAR_ARCHIVE_PARSER::LONGPOINT::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "PT" ) );

    x = GetXmlAttributeIDLong( aNode, 0 );
    y = GetXmlAttributeIDLong( aNode, 1 );
}


bool CADSTAR_ARCHIVE_PARSER::VERTEX::IsVertex( XNODE* aNode )
{
    wxString aNodeName = aNode->GetName();

    if( aNodeName == wxT( "PT" ) || aNodeName == wxT( "ACWARC" ) || aNodeName == wxT( "CWARC" )
            || aNodeName == wxT( "CWSEMI" ) || aNodeName == wxT( "ACWSEMI" ) )
    {
        return true;
    }
    else
    {
        return false;
    }
}


void CADSTAR_ARCHIVE_PARSER::VERTEX::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( IsVertex( aNode ) );

    wxString aNodeName = aNode->GetName();

    if( aNodeName == wxT( "PT" ) )
    {
        Type     = VERTEX_TYPE::POINT;
        Center.x = UNDEFINED_VALUE;
        Center.y = UNDEFINED_VALUE;
        End.Parse( aNode, aContext );
    }
    else if( aNodeName == wxT( "ACWARC" ) || aNodeName == wxT( "CWARC" ) )
    {
        if( aNodeName == wxT( "ACWARC" ) )
            Type = VERTEX_TYPE::ANTICLOCKWISE_ARC;
        else
            Type = VERTEX_TYPE::CLOCKWISE_ARC;

        std::vector<POINT> pts = ParseAllChildPoints( aNode, aContext, true, 2 );

        Center = pts[0];
        End    = pts[1];
    }
    else if( aNodeName == wxT( "ACWSEMI" ) || aNodeName == wxT( "CWSEMI" ) )
    {
        if( aNodeName == wxT( "ACWSEMI" ) )
            Type = VERTEX_TYPE::ANTICLOCKWISE_SEMICIRCLE;
        else
            Type = VERTEX_TYPE::CLOCKWISE_SEMICIRCLE;

        Center.x = UNDEFINED_VALUE;
        Center.y = UNDEFINED_VALUE;

        std::vector<POINT> pts = ParseAllChildPoints( aNode, aContext, true, 1 );

        End = pts[0];
    }
    else
    {
        wxASSERT_MSG( true, wxT( "Unknown VERTEX type" ) );
    }
}


void CADSTAR_ARCHIVE_PARSER::VERTEX::AppendToChain( SHAPE_LINE_CHAIN* aChainToAppendTo,
        const std::function<VECTOR2I( const VECTOR2I& )> aCadstarToKicadPointCallback,
        double aAccuracy ) const
{
    if( Type == VERTEX_TYPE::POINT )
    {
        aChainToAppendTo->Append( aCadstarToKicadPointCallback( End ) );
        return;
    }

    wxCHECK_MSG( aChainToAppendTo->PointCount() > 0, /*void*/,
                 "Can't append an arc to vertex to an empty chain" );

    aChainToAppendTo->Append( BuildArc( aChainToAppendTo->GetPoint( -1 ), aCadstarToKicadPointCallback),
                              aAccuracy );
}


SHAPE_ARC CADSTAR_ARCHIVE_PARSER::VERTEX::BuildArc( const VECTOR2I& aPrevPoint,
        const std::function<VECTOR2I( const VECTOR2I& )> aCadstarToKicadPointCallback ) const
{
    wxCHECK_MSG( Type != VERTEX_TYPE::POINT, SHAPE_ARC(),
                 "Can't build an arc for a straight segment!" );

    VECTOR2I startPoint = aPrevPoint;
    VECTOR2I endPoint = aCadstarToKicadPointCallback( End );
    VECTOR2I centerPoint;

    if( Type == VERTEX_TYPE::ANTICLOCKWISE_SEMICIRCLE || Type == VERTEX_TYPE::CLOCKWISE_SEMICIRCLE )
        centerPoint = ( startPoint / 2 ) + ( endPoint / 2 );
    else
        centerPoint = aCadstarToKicadPointCallback( Center );

    bool clockwise = Type == VERTEX_TYPE::CLOCKWISE_ARC
                  || Type == VERTEX_TYPE::CLOCKWISE_SEMICIRCLE;

    // A bit of a hack to figure out if we need to invert clockwise due to the transform
    VECTOR2I transform = aCadstarToKicadPointCallback( { 500, 500 } )
                       - aCadstarToKicadPointCallback( { 0, 0 } );

    if( ( transform.x > 0 && transform.y < 0 ) || ( transform.x < 0 && transform.y > 0 ) )
        clockwise = !clockwise;

    SHAPE_ARC arc;

    return arc.ConstructFromStartEndCenter( startPoint, endPoint, centerPoint, clockwise );
}


void CADSTAR_ARCHIVE_PARSER::CUTOUT::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "CUTOUT" ) );

    Vertices = ParseAllChildVertices( aNode, aContext, true );
}


bool CADSTAR_ARCHIVE_PARSER::SHAPE::IsShape( XNODE* aNode )
{
    wxString aNodeName = aNode->GetName();

    if( aNodeName == wxT( "OPENSHAPE" ) || aNodeName == wxT( "OUTLINE" )
            || aNodeName == wxT( "SOLID" ) || aNodeName == wxT( "HATCHED" ) )
    {
        return true;
    }
    else
    {
        return false;
    }
}


void CADSTAR_ARCHIVE_PARSER::SHAPE::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( IsShape( aNode ) );

    wxString aNodeName = aNode->GetName();

    if( aNodeName == wxT( "OPENSHAPE" ) )
    {
        Type     = SHAPE_TYPE::OPENSHAPE;
        Vertices = ParseAllChildVertices( aNode, aContext, true );
        Cutouts.clear();
        HatchCodeID = wxEmptyString;
    }
    else if( aNodeName == wxT( "OUTLINE" ) )
    {
        Type        = SHAPE_TYPE::OUTLINE;
        Vertices    = ParseAllChildVertices( aNode, aContext, false );
        Cutouts     = ParseAllChildCutouts( aNode, aContext, false );
        HatchCodeID = wxEmptyString;
    }
    else if( aNodeName == wxT( "SOLID" ) )
    {
        Type        = SHAPE_TYPE::SOLID;
        Vertices    = ParseAllChildVertices( aNode, aContext, false );
        Cutouts     = ParseAllChildCutouts( aNode, aContext, false );
        HatchCodeID = wxEmptyString;
    }
    else if( aNodeName == wxT( "HATCHED" ) )
    {
        Type        = SHAPE_TYPE::HATCHED;
        Vertices    = ParseAllChildVertices( aNode, aContext, false );
        Cutouts     = ParseAllChildCutouts( aNode, aContext, false );
        HatchCodeID = GetXmlAttributeIDString( aNode, 0 );
    }
    else
    {
        wxASSERT_MSG( true, wxT( "Unknown SHAPE type" ) );
    }
}

SHAPE_LINE_CHAIN CADSTAR_ARCHIVE_PARSER::SHAPE::OutlineAsChain(
        const std::function<VECTOR2I( const VECTOR2I& )> aCadstarToKicadPointCallback,
        double aAccuracy  ) const
{
    SHAPE_LINE_CHAIN outline;

    if( Vertices.size() == 0 )
        return outline;

    for( const auto& vertex : Vertices )
        vertex.AppendToChain( &outline, aCadstarToKicadPointCallback, aAccuracy );

    if( Type != SHAPE_TYPE::OPENSHAPE )
    {
        outline.SetClosed( true );

        // Append after closing, to ensre first and last point remain the same
        Vertices.at( 0 ).AppendToChain( &outline, aCadstarToKicadPointCallback, aAccuracy );
    }

    return outline;
}


SHAPE_POLY_SET CADSTAR_ARCHIVE_PARSER::SHAPE::ConvertToPolySet(
        const std::function<VECTOR2I( const VECTOR2I& )> aCadstarToKicadPointCallback,
        double aAccuracy  ) const
{
    SHAPE_POLY_SET polyset;

    wxCHECK( Type != SHAPE_TYPE::OPENSHAPE, polyset ); // We shouldn't convert openshapes to polyset!

    polyset.AddOutline( OutlineAsChain( aCadstarToKicadPointCallback, aAccuracy ) );

    for( const auto& cutout : Cutouts )
    {
        SHAPE_LINE_CHAIN hole;

        if( cutout.Vertices.size() == 0 )
            continue;

        for( const auto& cutoutVertex : cutout.Vertices )
            cutoutVertex.AppendToChain( &hole, aCadstarToKicadPointCallback, aAccuracy );

        hole.SetClosed( true );

        // Append after closing, to ensre first and last point remain the same
        cutout.Vertices.at( 0 ).AppendToChain( &hole, aCadstarToKicadPointCallback, aAccuracy );

        polyset.AddHole( hole );
    }

    return polyset;
}


CADSTAR_ARCHIVE_PARSER::UNITS CADSTAR_ARCHIVE_PARSER::ParseUnits( XNODE* aNode )
{
    wxASSERT( aNode->GetName() == wxT( "UNITS" ) );

    wxString unit = GetXmlAttributeIDString( aNode, 0 );

    if( unit == wxT( "CENTIMETER" ) )
        return UNITS::CENTIMETER;
    else if( unit == wxT( "INCH" ) )
        return UNITS::INCH;
    else if( unit == wxT( "METER" ) )
        return UNITS::METER;
    else if( unit == wxT( "MICROMETRE" ) )
        return UNITS::MICROMETRE;
    else if( unit == wxT( "MM" ) )
        return UNITS::MM;
    else if( unit == wxT( "THOU" ) )
        return UNITS::THOU;
    else if( unit == wxT( "DESIGN" ) )
        return UNITS::DESIGN;
    else
        THROW_UNKNOWN_PARAMETER_IO_ERROR( unit, wxT( "UNITS" ) );

    return UNITS();
}


CADSTAR_ARCHIVE_PARSER::ANGUNITS CADSTAR_ARCHIVE_PARSER::ParseAngunits( XNODE* aNode )
{
    wxASSERT( aNode->GetName() == wxT( "ANGUNITS" ) );

    wxString angUnitStr = GetXmlAttributeIDString( aNode, 0 );

    if( angUnitStr == wxT( "DEGREES" ) )
        return ANGUNITS::DEGREES;
    else if( angUnitStr == wxT( "RADIANS" ) )
        return ANGUNITS::RADIANS;
    else
        THROW_UNKNOWN_PARAMETER_IO_ERROR( angUnitStr, aNode->GetName() );

    return ANGUNITS();
}


bool CADSTAR_ARCHIVE_PARSER::GRID::IsGrid( XNODE* aNode )
{
    wxString aNodeName = aNode->GetName();

    if( aNodeName == wxT( "FRACTIONALGRID" ) || aNodeName == wxT( "STEPGRID" ) )
        return true;
    else
        return false;
}


void CADSTAR_ARCHIVE_PARSER::GRID::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( IsGrid( aNode ) );

    wxString aNodeName = aNode->GetName();

    if( aNodeName == wxT( "FRACTIONALGRID" ) )
        Type = GRID_TYPE::FRACTIONALGRID;
    else if( aNodeName == wxT( "STEPGRID" ) )
        Type = GRID_TYPE::STEPGRID;
    else
        wxASSERT_MSG( true, wxT( "Unknown Grid Type" ) );

    Name   = GetXmlAttributeIDString( aNode, 0 );
    Param1 = GetXmlAttributeIDLong( aNode, 1 );
    Param2 = GetXmlAttributeIDLong( aNode, 2 );
}


void CADSTAR_ARCHIVE_PARSER::GRIDS::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "GRIDS" ) );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "WORKINGGRID" ) )
        {
            XNODE* workingGridNode = cNode->GetChildren();

            if( !GRID::IsGrid( workingGridNode ) )
            {
                THROW_UNKNOWN_NODE_IO_ERROR(
                        workingGridNode->GetName(), wxT( "GRIDS -> WORKINGGRID" ) );
            }
            else
            {
                WorkingGrid.Parse( workingGridNode, aContext );
            }
        }
        else if( cNodeName == wxT( "SCREENGRID" ) )
        {
            XNODE* screenGridNode = cNode->GetChildren();

            if( !GRID::IsGrid( screenGridNode ) )
            {
                THROW_UNKNOWN_NODE_IO_ERROR(
                        screenGridNode->GetName(), wxT( "GRIDS -> SCREENGRID" ) );
            }
            else
            {
                ScreenGrid.Parse( screenGridNode, aContext );
            }
        }
        else if( GRID::IsGrid( cNode ) )
        {
            GRID userGrid;
            userGrid.Parse( cNode, aContext );
            UserGrids.push_back( userGrid );
        }
    }
}


bool CADSTAR_ARCHIVE_PARSER::SETTINGS::ParseSubNode( XNODE* aChildNode, PARSER_CONTEXT* aContext )
{
    wxString cNodeName = aChildNode->GetName();

    if( cNodeName == wxT( "UNITS" ) )
    {
        Units = ParseUnits( aChildNode );
    }
    else if( cNodeName == wxT( "UNITSPRECISION" ) )
    {
        UnitDisplPrecision = GetXmlAttributeIDLong( aChildNode, 0 );
    }
    else if( cNodeName == wxT( "INTERLINEGAP" ) )
    {
        InterlineGap = GetXmlAttributeIDLong( aChildNode, 0 );
    }
    else if( cNodeName == wxT( "BARLINEGAP" ) )
    {
        BarlineGap = GetXmlAttributeIDLong( aChildNode, 0 );
    }
    else if( cNodeName == wxT( "ALLOWBARTEXT" ) )
    {
        AllowBarredText = true;
    }
    else if( cNodeName == wxT( "ANGULARPRECISION" ) )
    {
        AngularPrecision = GetXmlAttributeIDLong( aChildNode, 0 );
    }
    else if( cNodeName == wxT( "DESIGNORIGIN" ) )
    {
        DesignOrigin.Parse( aChildNode->GetChildren(), aContext );
    }
    else if( cNodeName == wxT( "DESIGNAREA" ) )
    {
        std::vector<POINT> pts = ParseAllChildPoints( aChildNode, aContext, true, 2 );
        DesignArea             = std::make_pair( pts[0], pts[1] );
    }
    else if( cNodeName == wxT( "DESIGNREF" ) )
    {
        DesignOrigin.Parse( aChildNode->GetChildren(), aContext );
    }
    else if( cNodeName == wxT( "DESIGNLIMIT" ) )
    {
        DesignLimit.Parse( aChildNode->GetChildren(), aContext );
    }
    else
    {
        return false;
    }

    return true;
}


void CADSTAR_ARCHIVE_PARSER::SETTINGS::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "SETTINGS" ) );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( ParseSubNode( cNode, aContext ) )
            continue;
        else
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, wxT( "SETTINGS" ) );
    }
}


wxString CADSTAR_ARCHIVE_PARSER::ParseTextFields( const wxString& aTextString,
                                                  PARSER_CONTEXT* aContext )
{
    static const std::map<TEXT_FIELD_NAME, wxString> txtTokens =
    {
        { TEXT_FIELD_NAME::DESIGN_TITLE,        wxT( "DESIGN TITLE" ) },
        { TEXT_FIELD_NAME::SHORT_JOBNAME,       wxT( "SHORT_JOBNAME" ) },
        { TEXT_FIELD_NAME::LONG_JOBNAME,        wxT( "LONG_JOBNAME" ) },
        { TEXT_FIELD_NAME::NUM_OF_SHEETS,       wxT( "NUM_OF_SHEETS" ) },
        { TEXT_FIELD_NAME::SHEET_NUMBER,        wxT( "SHEET_NUMBER" ) },
        { TEXT_FIELD_NAME::SHEET_NAME,          wxT( "SHEET_NAME" ) },
        { TEXT_FIELD_NAME::VARIANT_NAME,        wxT( "VARIANT_NAME" ) },
        { TEXT_FIELD_NAME::VARIANT_DESCRIPTION, wxT( "VARIANT_DESCRIPTION" ) },
        { TEXT_FIELD_NAME::REG_USER,            wxT( "REG_USER" ) },
        { TEXT_FIELD_NAME::COMPANY_NAME,        wxT( "COMPANY_NAME" ) },
        { TEXT_FIELD_NAME::CURRENT_USER,        wxT( "CURRENT_USER" ) },
        { TEXT_FIELD_NAME::DATE,                wxT( "DATE" ) },
        { TEXT_FIELD_NAME::TIME,                wxT( "TIME" ) },
        { TEXT_FIELD_NAME::MACHINE_NAME,        wxT( "MACHINE_NAME" ) },
        { TEXT_FIELD_NAME::FROM_FILE,           wxT( "FROM_FILE" ) },
        { TEXT_FIELD_NAME::DISTANCE,            wxT( "DISTANCE" ) },
        { TEXT_FIELD_NAME::UNITS_SHORT,         wxT( "UNITS SHORT" ) },
        { TEXT_FIELD_NAME::UNITS_ABBREV,        wxT( "UNITS ABBREV" ) },
        { TEXT_FIELD_NAME::UNITS_FULL,          wxT( "UNITS FULL" ) },
        { TEXT_FIELD_NAME::HYPERLINK,           wxT( "HYPERLINK" ) }
    };

    wxString remainingStr = aTextString;
    wxString returnStr;

    while( remainingStr.size() > 0 )
    {
        //Find the start token
        size_t startpos = remainingStr.Find( wxT( "<@" ) );

        if( static_cast<int>( startpos ) == wxNOT_FOUND )
        {
            // No more fields to parse, add to return string
            returnStr += remainingStr;
            break;
        }

        if( startpos > 0 )
            returnStr += remainingStr.SubString( 0, startpos - 1 );

        if( ( startpos + 2 ) >= remainingStr.size() )
            break;

        remainingStr = remainingStr.Mid( startpos + 2 );

        //Find the expected token for the field
        TEXT_FIELD_NAME foundField = TEXT_FIELD_NAME::NONE;

        for( std::pair<TEXT_FIELD_NAME, wxString> txtF : txtTokens )
        {
            if( remainingStr.StartsWith( txtF.second ) )
            {
                foundField = txtF.first;
                break;
            }
        }

        if( foundField == TEXT_FIELD_NAME::NONE )
        {
            // Not a valid field, lets keep looking
            returnStr += wxT( "<@" );
            continue;
        }

        //Now lets find the end token
        size_t endpos = remainingStr.Find( wxT( "@>" ) );

        if( static_cast<int>( endpos ) == wxNOT_FOUND )
        {
            // The field we found isn't valid as it doesn't have a termination
            // Lets append the whole thing as plain text
            returnStr += wxT( "<@" ) + remainingStr;
            break;
        }

        size_t   valueStart = txtTokens.at( foundField ).size();
        wxString fieldValue = remainingStr.SubString( valueStart, endpos - 1 );
        wxString address;

        if( foundField == TEXT_FIELD_NAME::FROM_FILE || foundField == TEXT_FIELD_NAME::HYPERLINK )
        {
            // The first character should always be a double quotation mark
            wxASSERT_MSG( fieldValue.at( 0 ) == '"', "Expected '\"' as the first character" );

            size_t splitPos = fieldValue.find_first_of( '"', 1 );
            address         = fieldValue.SubString( 1, splitPos - 1 );

            if( foundField == TEXT_FIELD_NAME::HYPERLINK )
            {
                // Assume the last two characters are "@>"
                wxASSERT_MSG( remainingStr.EndsWith( wxT( "@>" ) ),
                              "Expected '@>' at the end of a hyperlink" );

                fieldValue = remainingStr.SubString( valueStart + splitPos + 1,
                                                     remainingStr.Length() - 3 );

                remainingStr = wxEmptyString;
            }
            else
            {
                fieldValue = fieldValue.Mid( splitPos + 1 );
            }
        }

        switch( foundField )
        {
        case TEXT_FIELD_NAME::DESIGN_TITLE:
        case TEXT_FIELD_NAME::SHORT_JOBNAME:
        case TEXT_FIELD_NAME::LONG_JOBNAME:
        case TEXT_FIELD_NAME::VARIANT_NAME:
        case TEXT_FIELD_NAME::VARIANT_DESCRIPTION:
        case TEXT_FIELD_NAME::REG_USER:
        case TEXT_FIELD_NAME::COMPANY_NAME:
        case TEXT_FIELD_NAME::CURRENT_USER:
        case TEXT_FIELD_NAME::DATE:
        case TEXT_FIELD_NAME::TIME:
        case TEXT_FIELD_NAME::MACHINE_NAME:

            if( aContext->TextFieldToValuesMap.find( foundField )
                    != aContext->TextFieldToValuesMap.end() )
            {
                aContext->InconsistentTextFields.insert( foundField );
            }
            else
            {
                aContext->TextFieldToValuesMap.insert( { foundField, fieldValue } );
            }

            KI_FALLTHROUGH;

        case TEXT_FIELD_NAME::NUM_OF_SHEETS:
        case TEXT_FIELD_NAME::SHEET_NUMBER:
        case TEXT_FIELD_NAME::SHEET_NAME:
            returnStr += wxT( "${" ) + CADSTAR_TO_KICAD_FIELDS.at( foundField ) + wxT( "}" );
            break;

        case TEXT_FIELD_NAME::DISTANCE:
        case TEXT_FIELD_NAME::UNITS_SHORT:
        case TEXT_FIELD_NAME::UNITS_ABBREV:
        case TEXT_FIELD_NAME::UNITS_FULL:
            // Just flatten the text for distances
            returnStr += fieldValue;
            break;

        case TEXT_FIELD_NAME::FROM_FILE:
        {
            wxFileName fn( address );
            wxString   fieldFmt = wxT( "FROM_FILE_%s_%s" );
            wxString   fieldName = wxString::Format( fieldFmt, fn.GetName(), fn.GetExt() );

            int version = 1;

            while(
                aContext->FilenamesToTextMap.find( fieldName )
                            != aContext->FilenamesToTextMap.end()
                    && aContext->FilenamesToTextMap.at( fieldName ) != fieldValue )
            {
                fieldName = wxString::Format( fieldFmt, fn.GetName(), fn.GetExt() )
                            + wxString::Format( wxT( "_%d" ), version++ );
            }

            aContext->FilenamesToTextMap[fieldName] = fieldValue;
            returnStr += wxT( "${" ) + fieldName + wxT( "}" );
        }
        break;

        case TEXT_FIELD_NAME::HYPERLINK:
        {
            aContext->TextToHyperlinksMap[fieldValue] = address;
            returnStr += ParseTextFields( fieldValue, aContext );
        }
        break;

        case TEXT_FIELD_NAME::NONE:
            wxFAIL_MSG( "We should have already covered this scenario above" );
            break;
        }


        if( ( endpos + 2 ) >= remainingStr.size() )
            break;

        remainingStr = remainingStr.Mid( endpos + 2 );
    }

    return returnStr;
}


CADSTAR_ARCHIVE_PARSER::ALIGNMENT CADSTAR_ARCHIVE_PARSER::ParseAlignment( XNODE* aNode )
{
    wxASSERT( aNode->GetName() == wxT( "ALIGN" ) );

    wxString alignmentStr = GetXmlAttributeIDString( aNode, 0 );

    if( alignmentStr == wxT( "BOTTOMCENTER" ) )
        return ALIGNMENT::BOTTOMCENTER;
    else if( alignmentStr == wxT( "BOTTOMLEFT" ) )
        return ALIGNMENT::BOTTOMLEFT;
    else if( alignmentStr == wxT( "BOTTOMRIGHT" ) )
        return ALIGNMENT::BOTTOMRIGHT;
    else if( alignmentStr == wxT( "CENTERCENTER" ) )
        return ALIGNMENT::CENTERCENTER;
    else if( alignmentStr == wxT( "CENTERLEFT" ) )
        return ALIGNMENT::CENTERLEFT;
    else if( alignmentStr == wxT( "CENTERRIGHT" ) )
        return ALIGNMENT::CENTERRIGHT;
    else if( alignmentStr == wxT( "TOPCENTER" ) )
        return ALIGNMENT::TOPCENTER;
    else if( alignmentStr == wxT( "TOPLEFT" ) )
        return ALIGNMENT::TOPLEFT;
    else if( alignmentStr == wxT( "TOPRIGHT" ) )
        return ALIGNMENT::TOPRIGHT;
    else
        THROW_UNKNOWN_PARAMETER_IO_ERROR( alignmentStr, wxT( "ALIGN" ) );

    //shouldn't be here but avoids compiler warning
    return ALIGNMENT::NO_ALIGNMENT;
}


CADSTAR_ARCHIVE_PARSER::JUSTIFICATION CADSTAR_ARCHIVE_PARSER::ParseJustification( XNODE* aNode )
{
    wxASSERT( aNode->GetName() == wxT( "JUSTIFICATION" ) );

    wxString justificationStr = GetXmlAttributeIDString( aNode, 0 );

    if( justificationStr == wxT( "LEFT" ) )
        return JUSTIFICATION::LEFT;
    else if( justificationStr == wxT( "RIGHT" ) )
        return JUSTIFICATION::RIGHT;
    else if( justificationStr == wxT( "CENTER" ) )
        return JUSTIFICATION::CENTER;
    else
        THROW_UNKNOWN_PARAMETER_IO_ERROR( justificationStr, wxT( "JUSTIFICATION" ) );

    return JUSTIFICATION::LEFT;
}


CADSTAR_ARCHIVE_PARSER::READABILITY CADSTAR_ARCHIVE_PARSER::ParseReadability( XNODE* aNode )
{
    wxASSERT( aNode->GetName() == wxT( "READABILITY" ) );

    wxString readabilityStr = GetXmlAttributeIDString( aNode, 0 );

    if( readabilityStr == wxT( "BOTTOM_TO_TOP" ) )
        return READABILITY::BOTTOM_TO_TOP;
    else if( readabilityStr == wxT( "TOP_TO_BOTTOM" ) )
        return READABILITY::TOP_TO_BOTTOM;
    else
        THROW_UNKNOWN_PARAMETER_IO_ERROR( readabilityStr, wxT( "READABILITY" ) );

    return READABILITY::BOTTOM_TO_TOP;
}


void CADSTAR_ARCHIVE_PARSER::ATTRIBUTE_LOCATION::ParseIdentifiers( XNODE* aNode,
                                                                   PARSER_CONTEXT* aContext )
{
    TextCodeID = GetXmlAttributeIDString( aNode, 0 );
    LayerID    = GetXmlAttributeIDString( aNode, 1 );
}


bool CADSTAR_ARCHIVE_PARSER::ATTRIBUTE_LOCATION::ParseSubNode( XNODE* aChildNode,
                                                               PARSER_CONTEXT* aContext )
{
    wxString cNodeName = aChildNode->GetName();

    if( cNodeName == wxT( "PT" ) )
        Position.Parse( aChildNode, aContext );
    else if( cNodeName == wxT( "ORIENT" ) )
        OrientAngle = GetXmlAttributeIDLong( aChildNode, 0 );
    else if( cNodeName == wxT( "MIRROR" ) )
        Mirror = true;
    else if( cNodeName == wxT( "FIX" ) )
        Fixed = true;
    else if( cNodeName == wxT( "ALIGN" ) )
        Alignment = ParseAlignment( aChildNode );
    else if( cNodeName == wxT( "JUSTIFICATION" ) )
        Justification = ParseJustification( aChildNode );
    else
        return false;

    return true;
}


void CADSTAR_ARCHIVE_PARSER::ATTRIBUTE_LOCATION::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "ATTRLOC" ) );

    ParseIdentifiers( aNode, aContext );

    //Parse child nodes
    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        if( ParseSubNode( cNode, aContext ) )
            continue;
        else
            THROW_UNKNOWN_NODE_IO_ERROR( cNode->GetName(), wxT( "ATTRLOC" ) );
    }

    if( !Position.IsFullySpecified() )
        THROW_MISSING_NODE_IO_ERROR( wxT( "PT" ), wxT( "ATTRLOC" ) );
}


void CADSTAR_ARCHIVE_PARSER::ATTRNAME::COLUMNORDER::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "COLUMNORDER" ) );

    ID    = GetXmlAttributeIDLong( aNode, 0 );
    Order = GetXmlAttributeIDLong( aNode, 1 );

    CheckNoChildNodes( aNode );
}


void CADSTAR_ARCHIVE_PARSER::ATTRNAME::COLUMNWIDTH::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "COLUMNWIDTH" ) );

    ID    = GetXmlAttributeIDLong( aNode, 0 );
    Width = GetXmlAttributeIDLong( aNode, 1 );

    CheckNoChildNodes( aNode );
}


void CADSTAR_ARCHIVE_PARSER::ATTRNAME::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "ATTRNAME" ) );

    ID   = GetXmlAttributeIDString( aNode, 0 );
    Name = GetXmlAttributeIDString( aNode, 1 );

    XNODE*   cNode    = aNode->GetChildren();
    wxString location = wxString::Format( "ATTRNAME -> %s", Name );

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "ATTROWNER" ) )
        {
            wxString attOwnerVal = GetXmlAttributeIDString( cNode, 0 );

            if( attOwnerVal == wxT( "ALL_ITEMS" ) )
                AttributeOwner = ATTROWNER::ALL_ITEMS;
            else if( attOwnerVal == wxT( "AREA" ) )
                AttributeOwner = ATTROWNER::AREA;
            else if( attOwnerVal == wxT( "BOARD" ) )
                AttributeOwner = ATTROWNER::BOARD;
            else if( attOwnerVal == wxT( "COMPONENT" ) )
                AttributeOwner = ATTROWNER::COMPONENT;
            else if( attOwnerVal == wxT( "CONNECTION" ) )
                AttributeOwner = ATTROWNER::CONNECTION;
            else if( attOwnerVal == wxT( "COPPER" ) )
                AttributeOwner = ATTROWNER::COPPER;
            else if( attOwnerVal == wxT( "DOCSYMBOL" ) )
                AttributeOwner = ATTROWNER::DOCSYMBOL;
            else if( attOwnerVal == wxT( "FIGURE" ) )
                AttributeOwner = ATTROWNER::FIGURE;
            else if( attOwnerVal == wxT( "NET" ) )
                AttributeOwner = ATTROWNER::NET;
            else if( attOwnerVal == wxT( "NETCLASS" ) )
                AttributeOwner = ATTROWNER::NETCLASS;
            else if( attOwnerVal == wxT( "PART" ) )
                AttributeOwner = ATTROWNER::PART;
            else if( attOwnerVal == wxT( "PART_DEFINITION" ) )
                AttributeOwner = ATTROWNER::PART_DEFINITION;
            else if( attOwnerVal == wxT( "PIN" ) )
                AttributeOwner = ATTROWNER::PIN;
            else if( attOwnerVal == wxT( "SIGNALREF" ) )
                AttributeOwner = ATTROWNER::SIGNALREF;
            else if( attOwnerVal == wxT( "SYMBOL" ) )
                AttributeOwner = ATTROWNER::SYMBOL;
            else if( attOwnerVal == wxT( "SYMDEF" ) )
                AttributeOwner = ATTROWNER::SYMDEF;
            else if( attOwnerVal == wxT( "TEMPLATE" ) )
                AttributeOwner = ATTROWNER::TEMPLATE;
            else if( attOwnerVal == wxT( "TESTPOINT" ) )
                AttributeOwner = ATTROWNER::TESTPOINT;
            else
                THROW_UNKNOWN_PARAMETER_IO_ERROR( attOwnerVal, location );
        }
        else if( cNodeName == wxT( "ATTRUSAGE" ) )
        {
            wxString attUsageVal = GetXmlAttributeIDString( cNode, 0 );

            if( attUsageVal == wxT( "BOTH" ) )
                AttributeUsage = ATTRUSAGE::BOTH;
            else if( attUsageVal == wxT( "COMPONENT" ) )
                AttributeUsage = ATTRUSAGE::COMPONENT;
            else if( attUsageVal == wxT( "PART_DEFINITION" ) )
                AttributeUsage = ATTRUSAGE::PART_DEFINITION;
            else if( attUsageVal == wxT( "PART_LIBRARY" ) )
                AttributeUsage = ATTRUSAGE::PART_LIBRARY;
            else if( attUsageVal == wxT( "SYMBOL" ) )
                AttributeUsage = ATTRUSAGE::SYMBOL;
            else
                THROW_UNKNOWN_PARAMETER_IO_ERROR( attUsageVal, location );
        }
        else if( cNodeName == wxT( "NOTRANSFER" ) )
        {
            NoTransfer = true;
        }
        else if( cNodeName == wxT( "COLUMNORDER" ) )
        {
            COLUMNORDER cOrder;
            cOrder.Parse( cNode, aContext );
            ColumnOrders.push_back( cOrder );
        }
        else if( cNodeName == wxT( "COLUMNWIDTH" ) )
        {
            COLUMNWIDTH cWidth;
            cWidth.Parse( cNode, aContext );
            ColumnWidths.push_back( cWidth );
        }
        else if( cNodeName == wxT( "COLUMNINVISIBLE" ) )
        {
            ColumnInvisible = true;
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, location );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::ATTRIBUTE_VALUE::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "ATTR" ) );

    AttributeID = GetXmlAttributeIDString( aNode, 0 );
    Value       = GetXmlAttributeIDString( aNode, 1 );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        if( cNode->GetName() == wxT( "READONLY" ) )
        {
            ReadOnly = true;
        }
        else if( cNode->GetName() == wxT( "ATTRLOC" ) )
        {
            AttributeLocation.Parse( cNode, aContext );
            HasLocation = true;
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNode->GetName(), wxT( "ATTR" ) );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::TEXT_LOCATION::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "TEXTLOC" ) );

    wxString attributeStr     = GetXmlAttributeIDString( aNode, 0 );
    bool     attributeIDisSet = false;

    if( attributeStr == wxT( "PART_NAME" ) )
    {
        AttributeID      = PART_NAME_ATTRID;
        attributeIDisSet = true;
    }
    else if( attributeStr == wxT( "COMP_NAME" ) )
    {
        AttributeID      = COMPONENT_NAME_ATTRID;
        attributeIDisSet = true;
    }
    else if( attributeStr == wxT( "COMP_NAME2" ) )
    {
        AttributeID      = COMPONENT_NAME_2_ATTRID;
        attributeIDisSet = true;
    }
    else if( attributeStr == wxT( "SYMBOL_NAME" ) )
    {
        AttributeID      = SYMBOL_NAME_ATTRID;
        attributeIDisSet = true;
    }
    else if( attributeStr == wxT( "LINK_ORIGIN" ) )
    {
        AttributeID      = LINK_ORIGIN_ATTRID;
        attributeIDisSet = true;
    }
    else if( attributeStr == wxT( "SIGNALNAME_ORIGIN" ) )
    {
        AttributeID      = SIGNALNAME_ORIGIN_ATTRID;
        attributeIDisSet = true;
    }
    else if( attributeStr == wxT( "ATTRREF" ) )
    {
        //We will initialise when we parse all child nodes
        attributeIDisSet = false;
    }
    else
    {
        THROW_UNKNOWN_PARAMETER_IO_ERROR( attributeStr, wxT( "TEXTLOC" ) );
    }

    TextCodeID = GetXmlAttributeIDString( aNode, 1 );
    LayerID    = GetXmlAttributeIDString( aNode, 2, false );

    //Parse child nodes
    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( ParseSubNode( cNode, aContext ) )
        {
            continue;
        }
        else if( !attributeIDisSet && cNodeName == wxT( "ATTRREF" ) )
        {
            AttributeID      = GetXmlAttributeIDString( cNode, 0 );
            attributeIDisSet = true;
        }
        else if( cNodeName == wxT( "ORIENT" ) )
        {
            OrientAngle = GetXmlAttributeIDLong( cNode, 0 );
        }
        else if( cNodeName == wxT( "MIRROR" ) )
        {
            Mirror = true;
        }
        else if( cNodeName == wxT( "FIX" ) )
        {
            Fixed = true;
        }
        else if( cNodeName == wxT( "ALIGN" ) )
        {
            Alignment = ParseAlignment( cNode );
        }
        else if( cNodeName == wxT( "JUSTIFICATION" ) )
        {
            Justification = ParseJustification( cNode );
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, wxT( "TEXTLOC" ) );
        }
    }

    if( !Position.IsFullySpecified() )
        THROW_MISSING_NODE_IO_ERROR( wxT( "PT" ), wxT( "TEXTLOC" ) );
}


void CADSTAR_ARCHIVE_PARSER::CADSTAR_NETCLASS::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "NETCLASS" ) );

    ID   = GetXmlAttributeIDString( aNode, 0 );
    Name = GetXmlAttributeIDString( aNode, 1 );

    XNODE*   cNode    = aNode->GetChildren();
    wxString location = wxString::Format( "NETCLASS -> %s", Name );

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "ATTR" ) )
        {
            ATTRIBUTE_VALUE attribute_val;
            attribute_val.Parse( cNode, aContext );
            Attributes.push_back( attribute_val );
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, location );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::SPCCLASSNAME::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "SPCCLASSNAME" ) );

    ID   = GetXmlAttributeIDString( aNode, 0 );
    Name = GetXmlAttributeIDString( aNode, 1 );
}


bool CADSTAR_ARCHIVE_PARSER::CODEDEFS::ParseSubNode( XNODE* aChildNode, PARSER_CONTEXT* aContext )
{
    wxString nodeName = aChildNode->GetName();

    if( nodeName == wxT( "LINECODE" ) )
    {
        LINECODE linecode;
        linecode.Parse( aChildNode, aContext );
        LineCodes.insert( std::make_pair( linecode.ID, linecode ) );
    }
    else if( nodeName == wxT( "HATCHCODE" ) )
    {
        HATCHCODE hatchcode;
        hatchcode.Parse( aChildNode, aContext );
        HatchCodes.insert( std::make_pair( hatchcode.ID, hatchcode ) );
    }
    else if( nodeName == wxT( "TEXTCODE" ) )
    {
        TEXTCODE textcode;
        textcode.Parse( aChildNode, aContext );
        TextCodes.insert( std::make_pair( textcode.ID, textcode ) );
    }
    else if( nodeName == wxT( "ROUTECODE" ) )
    {
        ROUTECODE routecode;
        routecode.Parse( aChildNode, aContext );
        RouteCodes.insert( std::make_pair( routecode.ID, routecode ) );
    }
    else if( nodeName == wxT( "ATTRNAME" ) )
    {
        ATTRNAME attrname;
        attrname.Parse( aChildNode, aContext );
        AttributeNames.insert( std::make_pair( attrname.ID, attrname ) );
    }
    else if( nodeName == wxT( "NETCLASS" ) )
    {
        CADSTAR_NETCLASS netclass;
        netclass.Parse( aChildNode, aContext );
        NetClasses.insert( std::make_pair( netclass.ID, netclass ) );
    }
    else if( nodeName == wxT( "SPCCLASSNAME" ) )
    {
        SPCCLASSNAME spcclassname;
        spcclassname.Parse( aChildNode, aContext );
        SpacingClassNames.insert( std::make_pair( spcclassname.ID, spcclassname ) );
    }
    else
    {
        return false;
    }

    return true;
}


CADSTAR_ARCHIVE_PARSER::SWAP_RULE CADSTAR_ARCHIVE_PARSER::ParseSwapRule( XNODE* aNode )
{
    wxASSERT( aNode->GetName() == wxT( "SWAPRULE" ) );

    SWAP_RULE retval;
    wxString  swapRuleStr = GetXmlAttributeIDString( aNode, 0 );

    if( swapRuleStr == wxT( "NO_SWAP" ) )
        retval = SWAP_RULE::NO_SWAP;
    else if( swapRuleStr == wxT( "USE_SWAP_LAYER" ) )
        retval = SWAP_RULE::USE_SWAP_LAYER;
    else
        THROW_UNKNOWN_PARAMETER_IO_ERROR( swapRuleStr, wxT( "SWAPRULE" ) );

    return retval;
}


void CADSTAR_ARCHIVE_PARSER::REUSEBLOCK::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "REUSEBLOCK" ) );

    ID       = GetXmlAttributeIDString( aNode, 0 );
    Name     = GetXmlAttributeIDString( aNode, 1 );
    FileName = GetXmlAttributeIDString( aNode, 2 );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "MIRROR" ) )
            Mirror = true;
        else if( cNodeName == wxT( "ORIENT" ) )
            OrientAngle = GetXmlAttributeIDLong( cNode, 0 );
        else
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, wxT( "REUSEBLOCK" ) );
    }
}


bool CADSTAR_ARCHIVE_PARSER::REUSEBLOCKREF::IsEmpty()
{
    return ReuseBlockID == wxEmptyString && ItemReference == wxEmptyString;
}


void CADSTAR_ARCHIVE_PARSER::REUSEBLOCKREF::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "REUSEBLOCKREF" ) );

    ReuseBlockID  = GetXmlAttributeIDString( aNode, 0 );
    ItemReference = GetXmlAttributeIDString( aNode, 1 );

    CheckNoChildNodes( aNode );
}


void CADSTAR_ARCHIVE_PARSER::GROUP::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "GROUP" ) );

    ID   = GetXmlAttributeIDString( aNode, 0 );
    Name = GetXmlAttributeIDString( aNode, 1 );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "FIX" ) )
            Fixed = true;
        else if( cNodeName == wxT( "TRANSFER" ) )
            Transfer = true;
        else if( cNodeName == wxT( "GROUPREF" ) )
            GroupID = GetXmlAttributeIDString( cNode, 0 );
        else if( cNodeName == wxT( "REUSEBLOCKREF" ) )
            ReuseBlockRef.Parse( cNode, aContext );
        else
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, wxT( "GROUP" ) );
    }
}


void CADSTAR_ARCHIVE_PARSER::FIGURE::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "FIGURE" ) );

    ID         = GetXmlAttributeIDString( aNode, 0 );
    LineCodeID = GetXmlAttributeIDString( aNode, 1 );
    LayerID    = GetXmlAttributeIDString( aNode, 2 );

    XNODE*   cNode              = aNode->GetChildren();
    bool     shapeIsInitialised = false; // Stop more than one Shape Object
    wxString location           = wxString::Format( "Figure %s", ID );

    if( !cNode )
        THROW_MISSING_NODE_IO_ERROR( wxT( "Shape" ), location );

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( !shapeIsInitialised && Shape.IsShape( cNode ) )
        {
            Shape.Parse( cNode, aContext );
            shapeIsInitialised = true;
        }
        else if( cNodeName == wxT( "SWAPRULE" ) )
        {
            SwapRule = ParseSwapRule( cNode );
        }
        else if( cNodeName == wxT( "FIX" ) )
        {
            Fixed = true;
        }
        else if( cNodeName == wxT( "GROUPREF" ) )
        {

            GroupID = GetXmlAttributeIDString( cNode, 0 );
        }
        else if( cNodeName == wxT( "REUSEBLOCKREF" ) )
        {
            ReuseBlockRef.Parse( cNode, aContext );
        }
        else if( cNodeName == wxT( "ATTR" ) )
        {
            ATTRIBUTE_VALUE attr;
            attr.Parse( cNode, aContext );
            AttributeValues.insert( std::make_pair( attr.AttributeID, attr ) );
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, location );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::TEXT::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    Parse( aNode, aContext, true );
}


void CADSTAR_ARCHIVE_PARSER::TEXT::Parse( XNODE* aNode, PARSER_CONTEXT* aContext,
                                          bool aParseFields )
{
    wxASSERT( aNode->GetName() == wxT( "TEXT" ) );

    ID = GetXmlAttributeIDString( aNode, 0 );
    Text = GetXmlAttributeIDString( aNode, 1 );

    if( aParseFields )
        Text = ParseTextFields( Text, aContext );

    TextCodeID = GetXmlAttributeIDString( aNode, 2 );
    LayerID    = GetXmlAttributeIDString( aNode, 3 );

    XNODE* cNode = aNode->GetChildren();

    if( !cNode )
        THROW_MISSING_NODE_IO_ERROR( wxT( "PT" ), wxT( "TEXT" ) );

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "PT" ) )
            Position.Parse( cNode, aContext );
        else if( cNodeName == wxT( "ORIENT" ) )
            OrientAngle = GetXmlAttributeIDLong( cNode, 0 );
        else if( cNodeName == wxT( "MIRROR" ) )
            Mirror = true;
        else if( cNodeName == wxT( "FIX" ) )
            Fixed = true;
        else if( cNodeName == wxT( "SWAPRULE" ) )
            SwapRule = ParseSwapRule( cNode );
        else if( cNodeName == wxT( "ALIGN" ) )
            Alignment = ParseAlignment( cNode );
        else if( cNodeName == wxT( "JUSTIFICATION" ) )
            Justification = ParseJustification( cNode );
        else if( cNodeName == wxT( "GROUPREF" ) )
            GroupID = GetXmlAttributeIDString( cNode, 0 );
        else if( cNodeName == wxT( "REUSEBLOCKREF" ) )
            ReuseBlockRef.Parse( cNode, aContext );
        else
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, wxT( "TEXT" ) );
    }
}


void CADSTAR_ARCHIVE_PARSER::SYMDEF::ParseIdentifiers( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "SYMDEF" ) );

    ID            = GetXmlAttributeIDString( aNode, 0 );
    ReferenceName = GetXmlAttributeIDString( aNode, 1 );
    Alternate     = GetXmlAttributeIDString( aNode, 2 );
}


bool CADSTAR_ARCHIVE_PARSER::SYMDEF::ParseSubNode( XNODE* aChildNode, PARSER_CONTEXT* aContext )
{
    wxString cNodeName = aChildNode->GetName();

    if( cNodeName == wxT( "PT" ) )
    {
        Origin.Parse( aChildNode, aContext );
    }
    else if( cNodeName == wxT( "STUB" ) )
    {
        Stub = true;
    }
    else if( cNodeName == wxT( "VERSION" ) )
    {
        Version = GetXmlAttributeIDLong( aChildNode, 0 );
    }
    else if( cNodeName == wxT( "FIGURE" ) )
    {
        FIGURE figure;
        figure.Parse( aChildNode, aContext );
        Figures.insert( std::make_pair( figure.ID, figure ) );
    }
    else if( cNodeName == wxT( "TEXT" ) )
    {
        TEXT txt;
        txt.Parse( aChildNode, aContext );
        Texts.insert( std::make_pair( txt.ID, txt ) );
    }
    else if( cNodeName == wxT( "TEXTLOC" ) )
    {
        TEXT_LOCATION textloc;
        textloc.Parse( aChildNode, aContext );
        TextLocations.insert( std::make_pair( textloc.AttributeID, textloc ) );
    }
    else if( cNodeName == wxT( "ATTR" ) )
    {
        ATTRIBUTE_VALUE attrVal;
        attrVal.Parse( aChildNode, aContext );
        AttributeValues.insert( std::make_pair( attrVal.AttributeID, attrVal ) );
    }
    else
    {
        return false;
    }

    return true;
}


void CADSTAR_ARCHIVE_PARSER::PART::DEFINITION::GATE::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "GATEDEFINITION" ) );

    ID        = GetXmlAttributeIDString( aNode, 0 );
    Name      = GetXmlAttributeIDString( aNode, 1 );
    Alternate = GetXmlAttributeIDString( aNode, 2 );
    PinCount  = GetXmlAttributeIDLong( aNode, 3 );

    CheckNoChildNodes( aNode );
}


CADSTAR_ARCHIVE_PARSER::PART::PIN_TYPE CADSTAR_ARCHIVE_PARSER::PART::GetPinType( XNODE* aNode )
{
    wxASSERT( aNode->GetName() == wxT( "PINTYPE" ) );

    wxString pinTypeStr = GetXmlAttributeIDString( aNode, 0 );

    std::map<wxString, PIN_TYPE> pinTypeMap = { { wxT( "INPUT" ), PIN_TYPE::INPUT },
        { wxT( "OUTPUT_OR" ), PIN_TYPE::OUTPUT_OR },
        { wxT( "OUTPUT_NOT_OR" ), PIN_TYPE::OUTPUT_NOT_OR },
        { wxT( "OUTPUT_NOT_NORM_OR" ), PIN_TYPE::OUTPUT_NOT_NORM_OR },
        { wxT( "POWER" ), PIN_TYPE::POWER }, { wxT( "GROUND" ), PIN_TYPE::GROUND },
        { wxT( "TRISTATE_BIDIR" ), PIN_TYPE::TRISTATE_BIDIR },
        { wxT( "TRISTATE_INPUT" ), PIN_TYPE::TRISTATE_INPUT },
        { wxT( "TRISTATE_DRIVER" ), PIN_TYPE::TRISTATE_DRIVER } };

    if( pinTypeMap.find( pinTypeStr ) == pinTypeMap.end() )
        THROW_UNKNOWN_PARAMETER_IO_ERROR( pinTypeStr, aNode->GetName() );

    return pinTypeMap[pinTypeStr];
}


void CADSTAR_ARCHIVE_PARSER::PART::DEFINITION::PIN::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "PARTDEFINITIONPIN" ) );

    ID = GetXmlAttributeIDLong( aNode, 0 );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "PINNAME" ) )
        {
            Name = GetXmlAttributeIDString( cNode, 0 );
        }
        else if( cNodeName == wxT( "PINLABEL" ) )
        {
            Label = GetXmlAttributeIDString( cNode, 0 );
        }
        else if( cNodeName == wxT( "PINSIGNAL" ) )
        {
            Signal = GetXmlAttributeIDString( cNode, 0 );
        }
        else if( cNodeName == wxT( "PINTERM" ) )
        {
            TerminalGate = GetXmlAttributeIDString( cNode, 0 );
            TerminalPin  = GetXmlAttributeIDLong( cNode, 1 );
        }
        else if( cNodeName == wxT( "PINTYPE" ) )
        {
            Type = GetPinType( cNode );
        }
        else if( cNodeName == wxT( "PINLOAD" ) )
        {
            Load = GetXmlAttributeIDLong( cNode, 0 );
        }
        else if( cNodeName == wxT( "PINPOSITION" ) )
        {
            Position = (POSITION) GetXmlAttributeIDLong( cNode, 0 );
        }
        else if( cNodeName == wxT( "PINIDENTIFIER" ) )
        {
            Identifier = GetXmlAttributeIDString( cNode, 0 );
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::PART::PART_PIN::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "PARTPIN" ) );

    ID = GetXmlAttributeIDLong( aNode, 0 );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "PINNAME" ) )
            Name = GetXmlAttributeIDString( cNode, 0 );
        else if( cNodeName == wxT( "PINTYPE" ) )
            Type = GetPinType( cNode );
        else if( cNodeName == wxT( "PINIDENTIFIER" ) )
            Identifier = GetXmlAttributeIDString( cNode, 0 );
        else
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
    }
}


void CADSTAR_ARCHIVE_PARSER::PART::DEFINITION::PIN_EQUIVALENCE::Parse( XNODE* aNode,
                                                                       PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "PINEQUIVALENCE" ) );

    wxXmlAttribute* xmlAttribute = aNode->GetAttributes();

    for( ; xmlAttribute; xmlAttribute = xmlAttribute->GetNext() )
    {
        if( !IsValidAttribute( xmlAttribute ) )
            continue;

        long pinId;

        if( !xmlAttribute->GetValue().ToLong( &pinId ) )
            THROW_UNKNOWN_PARAMETER_IO_ERROR( xmlAttribute->GetValue(), aNode->GetName() );

        PinIDs.push_back( (PART_DEFINITION_PIN_ID) pinId );
    }

    CheckNoChildNodes( aNode );
}


void CADSTAR_ARCHIVE_PARSER::PART::DEFINITION::SWAP_GATE::Parse( XNODE* aNode,
                                                                 PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "SWAPGATE" ) );

    wxXmlAttribute* xmlAttribute = aNode->GetAttributes();

    for( ; xmlAttribute; xmlAttribute = xmlAttribute->GetNext() )
    {
        if( !IsValidAttribute( xmlAttribute ) )
            continue;

        long pinId;

        if( !xmlAttribute->GetValue().ToLong( &pinId ) )
            THROW_UNKNOWN_PARAMETER_IO_ERROR( xmlAttribute->GetValue(), aNode->GetName() );

        PinIDs.push_back( (PART_DEFINITION_PIN_ID) pinId );
    }

    CheckNoChildNodes( aNode );
}


void CADSTAR_ARCHIVE_PARSER::PART::DEFINITION::SWAP_GROUP::Parse( XNODE* aNode,
                                                                  PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "SWAPGROUP" ) );

    GateName = GetXmlAttributeIDString( aNode, 0 );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "EXTERNAL" ) )
        {
            External = true;
        }
        else if( cNodeName == wxT( "SWAPGATE" ) )
        {
            SWAP_GATE swapGate;
            swapGate.Parse( cNode, aContext );
            SwapGates.push_back( swapGate );
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::PART::DEFINITION::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "PARTDEFINITION" ) );

    Name = GetXmlAttributeIDString( aNode, 0 );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "HIDEPINNAMES" ) )
        {
            HidePinNames = true;
        }
        else if( cNodeName == wxT( "MAXPIN" ) )
        {
            MaxPinCount = GetXmlAttributeIDLong( cNode, 0 );
        }
        else if( cNodeName == wxT( "GATEDEFINITION" ) )
        {
            GATE gate;
            gate.Parse( cNode, aContext );
            GateSymbols.insert( std::make_pair( gate.ID, gate ) );
        }
        else if( cNodeName == wxT( "PARTDEFINITIONPIN" ) )
        {
            PIN pin;
            pin.Parse( cNode, aContext );
            Pins.insert( std::make_pair( pin.ID, pin ) );
        }
        else if( cNodeName == wxT( "ATTR" ) )
        {
            ATTRIBUTE_VALUE attr;
            attr.Parse( cNode, aContext );
            AttributeValues.insert( std::make_pair( attr.AttributeID, attr ) );
        }
        else if( cNodeName == wxT( "PINEQUIVALENCE" ) )
        {
            PIN_EQUIVALENCE pinEq;
            pinEq.Parse( cNode, aContext );
            PinEquivalences.push_back( pinEq );
        }
        else if( cNodeName == wxT( "SWAPGROUP" ) )
        {
            SWAP_GROUP swapGroup;
            swapGroup.Parse( cNode, aContext );
            SwapGroups.push_back( swapGroup );
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::PART::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "PART" ) );

    ID   = GetXmlAttributeIDString( aNode, 0 );
    Name = GetXmlAttributeIDString( aNode, 1 );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "VERSION" ) )
        {
            Version = GetXmlAttributeIDLong( cNode, 0 );
        }
        else if( cNodeName == wxT( "HIDEPINNAMES" ) )
        {
            HidePinNames = true;
        }
        else if( cNodeName == wxT( "PARTDEFINITION" ) )
        {
            Definition.Parse( cNode, aContext );
        }
        else if( cNodeName == wxT( "PARTPIN" ) )
        {
            PART_PIN pin;
            pin.Parse( cNode, aContext );
            PartPins.insert( std::make_pair( pin.ID, pin ) );
        }
        else if( cNodeName == wxT( "ATTR" ) )
        {
            ATTRIBUTE_VALUE attr;
            attr.Parse( cNode, aContext );
            AttributeValues.insert( std::make_pair( attr.AttributeID, attr ) );
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::PARTS::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "PARTS" ) );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "PART" ) )
        {
            PART part;
            part.Parse( cNode, aContext );
            PartDefinitions.insert( std::make_pair( part.ID, part ) );
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
        }

        aContext->CheckPointCallback();
    }
}


void CADSTAR_ARCHIVE_PARSER::NET::JUNCTION::ParseIdentifiers( XNODE* aNode,
                                                              PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "JPT" ) );

    ID      = GetXmlAttributeIDString( aNode, 0 );
    LayerID = GetXmlAttributeIDString( aNode, 1 );
}


bool CADSTAR_ARCHIVE_PARSER::NET::JUNCTION::ParseSubNode( XNODE* aChildNode,
                                                          PARSER_CONTEXT* aContext )
{
    wxString cNodeName = aChildNode->GetName();

    if( cNodeName == wxT( "PT" ) )
        Location.Parse( aChildNode, aContext );
    else if( cNodeName == wxT( "FIX" ) )
        Fixed = true;
    else if( cNodeName == wxT( "GROUPREF" ) )
        GroupID = GetXmlAttributeIDString( aChildNode, 0 );
    else if( cNodeName == wxT( "REUSEBLOCKREF" ) )
        ReuseBlockRef.Parse( aChildNode, aContext );
    else
        return false;

    return true;
}


void CADSTAR_ARCHIVE_PARSER::NET::JUNCTION::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    ParseIdentifiers( aNode, aContext );
    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        if( ParseSubNode( cNode, aContext ) )
            continue;
        else
            THROW_UNKNOWN_NODE_IO_ERROR( cNode->GetName(), aNode->GetName() );
    }
}


void CADSTAR_ARCHIVE_PARSER::NET::CONNECTION::ParseIdentifiers( XNODE* aNode,
                                                                PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "CONN" ) );

    StartNode   = GetXmlAttributeIDString( aNode, 0 );
    EndNode     = GetXmlAttributeIDString( aNode, 1 );
    RouteCodeID = GetXmlAttributeIDString( aNode, 2 );
}


bool CADSTAR_ARCHIVE_PARSER::NET::CONNECTION::ParseSubNode( XNODE* aChildNode,
                                                            PARSER_CONTEXT* aContext )
{
    wxString cNodeName = aChildNode->GetName();

    if( cNodeName == wxT( "FIX" ) )
    {
        Fixed = true;
    }
    else if( cNodeName == wxT( "HIDDEN" ) )
    {
        Hidden = true;
    }
    else if( cNodeName == wxT( "GROUPREF" ) )
    {
        GroupID = GetXmlAttributeIDString( aChildNode, 0 );
    }
    else if( cNodeName == wxT( "REUSEBLOCKREF" ) )
    {
        ReuseBlockRef.Parse( aChildNode, aContext );
    }
    else if( cNodeName == wxT( "ATTR" ) )
    {
        ATTRIBUTE_VALUE attrVal;
        attrVal.Parse( aChildNode, aContext );
        AttributeValues.insert( std::make_pair( attrVal.AttributeID, attrVal ) );
    }
    else
    {
        return false;
    }

    return true;
}


void CADSTAR_ARCHIVE_PARSER::NET::ParseIdentifiers( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "NET" ) );

    ID = GetXmlAttributeIDString( aNode, 0 );
}


bool CADSTAR_ARCHIVE_PARSER::NET::ParseSubNode( XNODE* aChildNode, PARSER_CONTEXT* aContext )
{
    wxString cNodeName = aChildNode->GetName();

    if( cNodeName == wxT( "NETCODE" ) )
    {
        RouteCodeID = GetXmlAttributeIDString( aChildNode, 0 );
    }
    else if( cNodeName == wxT( "SIGNAME" ) )
    {
        Name = GetXmlAttributeIDString( aChildNode, 0 );
    }
    else if( cNodeName == wxT( "SIGNUM" ) )
    {
        SignalNum = GetXmlAttributeIDLong( aChildNode, 0 );
    }
    else if( cNodeName == wxT( "HIGHLIT" ) )
    {
        Highlight = true;
    }
    else if( cNodeName == wxT( "JPT" ) )
    {
        JUNCTION jpt;
        jpt.Parse( aChildNode, aContext );
        Junctions.insert( std::make_pair( jpt.ID, jpt ) );
    }
    else if( cNodeName == wxT( "NETCLASSREF" ) )
    {
        NetClassID = GetXmlAttributeIDString( aChildNode, 0 );
    }
    else if( cNodeName == wxT( "SPACINGCLASS" ) )
    {
        SpacingClassID = GetXmlAttributeIDString( aChildNode, 0 );
    }
    else if( cNodeName == wxT( "ATTR" ) )
    {
        ATTRIBUTE_VALUE attrVal;
        attrVal.Parse( aChildNode, aContext );
        AttributeValues.insert( std::make_pair( attrVal.AttributeID, attrVal ) );
    }
    else
    {
        return false;
    }

    return true;
}


void CADSTAR_ARCHIVE_PARSER::DOCUMENTATION_SYMBOL::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "DOCSYMBOL" ) );

    ID       = GetXmlAttributeIDString( aNode, 0 );
    SymdefID = GetXmlAttributeIDString( aNode, 1 );
    LayerID  = GetXmlAttributeIDString( aNode, 2 );

    XNODE* cNode        = aNode->GetChildren();
    bool   originParsed = false;

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( !originParsed && cNodeName == wxT( "PT" ) )
        {
            Origin.Parse( cNode, aContext );
            originParsed = true;
        }
        else if( cNodeName == wxT( "GROUPREF" ) )
        {
            GroupID = GetXmlAttributeIDString( cNode, 0 );
        }
        else if( cNodeName == wxT( "REUSEBLOCKREF" ) )
        {
            ReuseBlockRef.Parse( cNode, aContext );
        }
        else if( cNodeName == wxT( "FIX" ) )
        {
            Fixed = true;
        }
        else if( cNodeName == wxT( "MIRROR" ) )
        {
            Mirror = true;
        }
        else if( cNodeName == wxT( "READABILITY" ) )
        {
            Readability = ParseReadability( cNode );
        }
        else if( cNodeName == wxT( "ORIENT" ) )
        {
            OrientAngle = GetXmlAttributeIDLong( cNode, 0 );
        }
        else if( cNodeName == wxT( "ATTR" ) )
        {
            ATTRIBUTE_VALUE attr;
            attr.Parse( cNode, aContext );
            AttributeValues.insert( std::make_pair( attr.AttributeID, attr ) );
        }
        else if( cNodeName == wxT( "SCALE" ) )
        {
            ScaleRatioNumerator   = GetXmlAttributeIDLong( cNode, 0 );
            ScaleRatioDenominator = GetXmlAttributeIDLong( cNode, 1 );
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
        }
    }

    if( !originParsed )
        THROW_MISSING_PARAMETER_IO_ERROR( wxT( "PT" ), aNode->GetName() );
}


void CADSTAR_ARCHIVE_PARSER::DFLTSETTINGS::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "DFLTSETTINGS" ) );

    Color = GetXmlAttributeIDString( aNode, 0 );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "INVISIBLE" ) )
        {
            IsVisible = false;
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::ATTRCOL::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "ATTRCOL" ) );

    AttributeID = GetXmlAttributeIDString( aNode, 0 );
    Color = GetXmlAttributeIDString( aNode, 1 );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "INVISIBLE" ) )
        {
            IsVisible = false;
        }
        else if( cNodeName == wxT( "NOTPICKABLE" ) )
        {
            IsPickable = false;
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::ATTRCOLORS::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "ATTRCOLORS" ) );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "DFLTSETTINGS" ) )
        {
            DefaultSettings.Parse( cNode, aContext );
        }
        else if( cNodeName == wxT( "ATTRCOL" ) )
        {
            ATTRCOL attrcol;
            attrcol.Parse( cNode, aContext );
            AttributeColors.insert( { attrcol.AttributeID, attrcol } );
        }
        else if( cNodeName == wxT( "INVISIBLE" ) )
        {
            IsVisible = false;
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::PARTNAMECOL::Parse( XNODE* aNode, PARSER_CONTEXT* aContext )
{
    wxASSERT( aNode->GetName() == wxT( "PARTNAMECOL" ) );

    Color = GetXmlAttributeIDString( aNode, 0 );

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        wxString cNodeName = cNode->GetName();

        if( cNodeName == wxT( "INVISIBLE" ) )
        {
            IsVisible = false;
        }
        else if( cNodeName == wxT( "NOTPICKABLE" ) )
        {
            IsPickable = false;
        }
        else
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNodeName, aNode->GetName() );
        }
    }
}


void CADSTAR_ARCHIVE_PARSER::InsertAttributeAtEnd( XNODE* aNode, wxString aValue )
{
    wxString result;
    int      numAttributes = 0;

    if( aNode->GetAttribute( wxT( "numAttributes" ), &result ) )
    {
        numAttributes = wxAtoi( result );
        aNode->DeleteAttribute( wxT( "numAttributes" ) );
        ++numAttributes;
    }

    aNode->AddAttribute( wxT( "numAttributes" ), wxString::Format( wxT( "%i" ), numAttributes ) );

    wxString paramName = wxT( "attr" );
    paramName << numAttributes;

    aNode->AddAttribute( paramName, aValue );
}


XNODE* CADSTAR_ARCHIVE_PARSER::LoadArchiveFile( const wxString& aFileName,
                                                const wxString& aFileTypeIdentifier, PROGRESS_REPORTER* aProgressReporter )
{
    KEYWORD   emptyKeywords[1] = {};
    XNODE*    rootNode = nullptr;
    XNODE*    cNode = nullptr;
    XNODE*    iNode = nullptr;
    int       tok;
    bool      cadstarFileCheckDone = false;
    wxString  str;
    wxCSConv  win1252( wxT( "windows-1252" ) );
    wxMBConv* conv = &win1252; // Initial testing suggests file encoding to be Windows-1252
                               // More samples required.

    // Open the file and get the file size
    FILE* fp = wxFopen( aFileName, wxT( "rt" ) );

    if( !fp )
        THROW_IO_ERROR( wxString::Format( _( "Cannot open file '%s'" ), aFileName ) );

    fseek( fp, 0L, SEEK_END );
    long fileSize = ftell( fp );
    rewind( fp );

    DSNLEXER lexer( emptyKeywords, 0, nullptr,  fp, aFileName );

    auto currentProgress =  [&]() -> double
                            {
                                return static_cast<double>( ftell( fp ) ) / fileSize;
                            };

    double previousReportedProgress = -1.0;

    while( ( tok = lexer.NextTok() ) != DSN_EOF )
    {
        if( aProgressReporter && ( currentProgress() - previousReportedProgress ) > 0.01 )
        {
            if( !aProgressReporter->KeepRefreshing() )
            {
                delete rootNode;
                THROW_IO_ERROR( _( "File import cancelled by user." ) );
            }

            aProgressReporter->SetCurrentProgress( currentProgress() );
            previousReportedProgress = currentProgress();
        }

        if( tok == DSN_RIGHT )
        {
            cNode = iNode;
            if( cNode )
            {
                iNode = cNode->GetParent();
            }
            else
            {
                //too many closing brackets
                delete rootNode;
                THROW_IO_ERROR( _( "The selected file is not valid or might be corrupt!" ) );
            }
        }
        else if( tok == DSN_LEFT )
        {
            tok   = lexer.NextTok();
            str   = wxString( lexer.CurText(), *conv );
            cNode = new XNODE( wxXML_ELEMENT_NODE, str );

            if( !rootNode )
                rootNode = cNode;

            if( iNode )
            {
                //we will add it as attribute as well as child node
                InsertAttributeAtEnd( iNode, str );
                iNode->AddChild( cNode );
            }
            else if( !cadstarFileCheckDone )
            {
                if( cNode->GetName() != aFileTypeIdentifier )
                {
                    delete rootNode;
                    THROW_IO_ERROR( _( "The selected file is not valid or might be corrupt!" ) );
                }

                cadstarFileCheckDone = true;
            }

            iNode = cNode;
        }
        else if( iNode )
        {
            str = wxString( lexer.CurText(), *conv );
            //Insert even if string is empty
            InsertAttributeAtEnd( iNode, str );
        }
        else
        {
            //not enough closing brackets
            delete rootNode;
            THROW_IO_ERROR( _( "The selected file is not valid or might be corrupt!" ) );
        }
    }

    // Not enough closing brackets
    if( iNode != nullptr )
    {
        delete rootNode;
        THROW_IO_ERROR( _( "The selected file is not valid or might be corrupt!" ) );
    }

    // Throw if no data was parsed
    if( rootNode )
    {
        return rootNode;
    }
    else
    {
        delete rootNode;
        THROW_IO_ERROR( _( "The selected file is not valid or might be corrupt!" ) );
    }

    return nullptr;
}


bool CADSTAR_ARCHIVE_PARSER::IsValidAttribute( wxXmlAttribute* aAttribute )
{
    return aAttribute->GetName() != wxT( "numAttributes" );
}


wxString CADSTAR_ARCHIVE_PARSER::GetXmlAttributeIDString( XNODE* aNode, unsigned int aID,
                                                          bool aIsRequired )
{
    wxString attrName, retVal;
    attrName = "attr";
    attrName << aID;

    if( !aNode->GetAttribute( attrName, &retVal ) )
    {
        if( aIsRequired )
            THROW_MISSING_PARAMETER_IO_ERROR( std::to_string( aID ), aNode->GetName() );
        else
            return wxEmptyString;
    }

    return retVal;
}


long CADSTAR_ARCHIVE_PARSER::GetXmlAttributeIDLong( XNODE* aNode, unsigned int aID,
                                                    bool aIsRequired )
{
    long retVal;
    bool success = GetXmlAttributeIDString( aNode, aID, aIsRequired ).ToLong( &retVal );

    if( !success )
    {
        if( aIsRequired )
            THROW_PARSING_IO_ERROR( std::to_string( aID ), aNode->GetName() );
        else
            return UNDEFINED_VALUE;
    }

    return retVal;
}


void CADSTAR_ARCHIVE_PARSER::CheckNoChildNodes( XNODE* aNode )
{
    if( aNode && aNode->GetChildren() )
        THROW_UNKNOWN_NODE_IO_ERROR( aNode->GetChildren()->GetName(), aNode->GetName() );
}


void CADSTAR_ARCHIVE_PARSER::CheckNoNextNodes( XNODE* aNode )
{
    if( aNode && aNode->GetNext() )
        THROW_UNKNOWN_NODE_IO_ERROR( aNode->GetNext()->GetName(), aNode->GetParent()->GetName() );
}


void CADSTAR_ARCHIVE_PARSER::ParseChildEValue( XNODE* aNode, PARSER_CONTEXT* aContext,
                                               EVALUE& aValueToParse )
{
    if( aNode->GetChildren()->GetName() == wxT( "E" ) )
        aValueToParse.Parse( aNode->GetChildren(), aContext );
    else
        THROW_UNKNOWN_NODE_IO_ERROR( aNode->GetChildren()->GetName(), aNode->GetName() );
}


std::vector<CADSTAR_ARCHIVE_PARSER::POINT> CADSTAR_ARCHIVE_PARSER::ParseAllChildPoints(
        XNODE* aNode, PARSER_CONTEXT* aContext, bool aTestAllChildNodes, int aExpectedNumPoints )
{
    std::vector<POINT> retVal;

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        if( cNode->GetName() == wxT( "PT" ) )
        {
            POINT pt;
            //TODO try.. catch + throw again with more detailed error information
            pt.Parse( cNode, aContext );
            retVal.push_back( pt );
        }
        else if( aTestAllChildNodes )
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNode->GetName(), aNode->GetName() );
        }
    }

    if( aExpectedNumPoints != UNDEFINED_VALUE
            && retVal.size() != static_cast<size_t>( aExpectedNumPoints ) )
    {
        THROW_IO_ERROR( wxString::Format(
                _( "Unexpected number of points in '%s'. Found %d but expected %d." ),
                aNode->GetName(), retVal.size(), aExpectedNumPoints ) );
    }

    return retVal;
}


std::vector<CADSTAR_ARCHIVE_PARSER::VERTEX> CADSTAR_ARCHIVE_PARSER::ParseAllChildVertices(
        XNODE* aNode, PARSER_CONTEXT* aContext, bool aTestAllChildNodes )
{
    std::vector<VERTEX> retVal;

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        if( VERTEX::IsVertex( cNode ) )
        {
            VERTEX vertex;
            //TODO try.. catch + throw again with more detailed error information
            vertex.Parse( cNode, aContext );
            retVal.push_back( vertex );
        }
        else if( aTestAllChildNodes )
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNode->GetName(), aNode->GetName() );
        }
    }

    return retVal;
}


std::vector<CADSTAR_ARCHIVE_PARSER::CUTOUT> CADSTAR_ARCHIVE_PARSER::ParseAllChildCutouts(
        XNODE* aNode, PARSER_CONTEXT* aContext, bool aTestAllChildNodes )
{
    std::vector<CUTOUT> retVal;

    XNODE* cNode = aNode->GetChildren();

    for( ; cNode; cNode = cNode->GetNext() )
    {
        if( cNode->GetName() == wxT( "CUTOUT" ) )
        {
            CUTOUT cutout;
            //TODO try.. catch + throw again with more detailed error information
            cutout.Parse( cNode, aContext );
            retVal.push_back( cutout );
        }
        else if( aTestAllChildNodes )
        {
            THROW_UNKNOWN_NODE_IO_ERROR( cNode->GetName(), aNode->GetName() );
        }
    }

    return retVal;
}


long CADSTAR_ARCHIVE_PARSER::GetNumberOfChildNodes( XNODE* aNode )
{
    XNODE* childNodes = aNode->GetChildren();
    long   retval = 0;

    for( ; childNodes; childNodes = childNodes->GetNext() )
        retval++;

    return retval;
}


long CADSTAR_ARCHIVE_PARSER::GetNumberOfStepsForReporting( XNODE* aRootNode, std::vector<wxString> aSubNodeChildrenToCount )
{
    XNODE* level1Node = aRootNode->GetChildren();
    long   retval = 0;

    for( ; level1Node; level1Node = level1Node->GetNext() )
    {
        for( wxString childNodeName : aSubNodeChildrenToCount )
        {
            if( level1Node->GetName() == childNodeName )
                retval += GetNumberOfChildNodes( level1Node );
        }

        retval++;
    }

    return retval;
}


wxString CADSTAR_ARCHIVE_PARSER::HandleTextOverbar( wxString aCadstarString )
{
    wxString escapedText = aCadstarString;

    escapedText.Replace( wxT( "'" ), wxT( "~" ) );

    return ConvertToNewOverbarNotation( escapedText );
}


void CADSTAR_ARCHIVE_PARSER::FixTextPositionNoAlignment( EDA_TEXT* aKiCadTextItem )
{
    if( !aKiCadTextItem->GetText().IsEmpty() )
    {
        VECTOR2I positionOffset( 0, aKiCadTextItem->GetInterline() );
        RotatePoint( positionOffset, aKiCadTextItem->GetTextAngle() );

        EDA_ITEM* textEdaItem = dynamic_cast<EDA_ITEM*>( aKiCadTextItem );

        if( textEdaItem &&
            ( textEdaItem->Type() == LIB_TEXT_T || textEdaItem->Type() == LIB_FIELD_T ) )
        {
            // Y coordinate increases upwards in the symbol editor
            positionOffset.y = -positionOffset.y;
        }

        //Count num of additional lines
        wxString text = aKiCadTextItem->GetText();
        int      numExtraLines = text.Replace( "\n", "\n" );
        numExtraLines -= text.at( text.size() - 1 ) == '\n'; // Ignore new line character at end
        positionOffset.x *= numExtraLines;
        positionOffset.y *= numExtraLines;

        aKiCadTextItem->Offset( positionOffset );
    }
}

void CADSTAR_ARCHIVE_PARSER::checkPoint()
{
    if( m_progressReporter )
    {
        m_progressReporter->AdvanceProgress();

        if( !m_progressReporter->KeepRefreshing() )
            THROW_IO_ERROR( _( "File import cancelled by user." ) );
    }
}

