kicad-source/common/eagle_parser.cpp
Alejandro García Montoro 9cf934ef17 Moves Eagle XML parser to common, replacing boost with wx.
All E'STRUCTS' are moved to common except for ERULES (which is
specific to pcbnew and needs its internal units), still in
pcbnew/eagle_plugin.{h,cpp}

In order to get rid of another boost dependency, this also changes
the parsing of the XML from Boost.PropertyTree to wxXml.

To replace boost::optional, an OPTIONAL_XML_ATTRIBUTE class has
been implemented. This could be replaced with std::optional when
C++17 is ready.
2017-05-04 15:29:45 +02:00

650 lines
21 KiB
C++

/*
* 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-2016 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 <eagle_parser.h>
#include <wx/xml/xml.h>
#include <wx/string.h>
#include <wx/filename.h>
#include <convert_to_biu.h>
using std::string;
// Template specializations below parse wxString to the used types:
// - string
// - double
// - int
// - bool
// - EROT
template<>
string Convert<string>( wxString aValue )
{
return aValue.ToStdString();
}
template <>
double Convert<double>( wxString aValue )
{
double value;
if( aValue.ToDouble( &value ) )
return value;
else
throw XML_PARSER_ERROR( "Conversion to double failed. Original value: '" +
aValue.ToStdString() + "'." );
}
template <>
int Convert<int>( wxString aValue )
{
if( aValue.IsEmpty() )
throw XML_PARSER_ERROR( "Conversion to int failed. Original value is empty." );
return wxAtoi( aValue );
}
template <>
bool Convert<bool>( 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>( 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'
NULL );
return value;
}
/**
* Function parseRequiredAttribute
* parsese the aAttribute of the XML node 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, string aAttribute )
{
wxString value;
if( aNode->GetAttribute( aAttribute, &value ) )
return Convert<T>( value );
else
throw XML_PARSER_ERROR( "The required attribute " + aAttribute + " is missing." );
}
/**
* Function parseOptionalAttribute
* parses the aAttribute of the XML node 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, string aAttribute )
{
return OPTIONAL_XML_ATTRIBUTE<T>( aNode->GetAttribute( aAttribute ) );
}
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<double>( aWire, "x1" );
y1 = parseRequiredAttribute<double>( aWire, "y1" );
x2 = parseRequiredAttribute<double>( aWire, "x2" );
y2 = parseRequiredAttribute<double>( aWire, "y2" );
width = parseRequiredAttribute<double>( aWire, "width" );
layer = parseRequiredAttribute<int>( aWire, "layer" );
curve = parseOptionalAttribute<double>( aWire, "curve" );
opt_string s = parseOptionalAttribute<string>( 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<string>( aWire, "cap" );
if( s == "round" )
cap = EWIRE::ROUND;
else if( s == "flat" )
cap = EWIRE::FLAT;
}
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<double>( aVia, "x" );
y = parseRequiredAttribute<double>( aVia, "y" );
string ext = parseRequiredAttribute<string>( aVia, "extent" );
sscanf( ext.c_str(), "%d-%d", &layer_front_most, &layer_back_most );
drill = parseRequiredAttribute<double>( aVia, "drill" );
diam = parseOptionalAttribute<double>( aVia, "diameter" );
shape = parseOptionalAttribute<string>( 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<double>( aCircle, "x" );
y = parseRequiredAttribute<double>( aCircle, "y" );
radius = parseRequiredAttribute<double>( aCircle, "radius" );
width = parseRequiredAttribute<double>( 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<double>( aRect, "x1" );
y1 = parseRequiredAttribute<double>( aRect, "y1" );
x2 = parseRequiredAttribute<double>( aRect, "x2" );
y2 = parseRequiredAttribute<double>( 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<string>( aTree, "name" );
value = parseOptionalAttribute<string>( aTree, "value" );
x = parseOptionalAttribute<double>( aTree, "x" );
y = parseOptionalAttribute<double>( aTree, "y" );
size = parseOptionalAttribute<double>( aTree, "size" );
// KiCad cannot currently put a TEXTE_MODULE on a different layer than the MODULE
// Eagle can it seems.
layer = parseOptionalAttribute<int>( aTree, "layer" );
ratio = parseOptionalAttribute<double>( aTree, "ratio" );
rot = parseOptionalAttribute<EROT>( aTree, "rot" );
opt_string stemp = parseOptionalAttribute<string>( aTree, "display" );
// (off | value | name | both)
if( stemp == "off" )
display = EATTR::Off;
else if( stemp == "value" )
display = EATTR::VALUE;
else if( stemp == "name" )
display = EATTR::NAME;
else if( stemp == "both" )
display = EATTR::BOTH;
}
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
layer %Layer; #REQUIRED
dtype %DimensionType; "parallel"
>
*/
x1 = parseRequiredAttribute<double>( aDimension, "x1" );
y1 = parseRequiredAttribute<double>( aDimension, "y1" );
x2 = parseRequiredAttribute<double>( aDimension, "x2" );
y2 = parseRequiredAttribute<double>( aDimension, "y2" );
x3 = parseRequiredAttribute<double>( aDimension, "x3" );
y3 = parseRequiredAttribute<double>( aDimension, "y3" );
layer = parseRequiredAttribute<int>( aDimension, "layer" );
opt_string dimType = parseOptionalAttribute<string>( aDimension, "dtype" );
if( !dimType )
{
// default type is parallel
}
}
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<double>( aText, "x" );
y = parseRequiredAttribute<double>( aText, "y" );
size = parseRequiredAttribute<double>( aText, "size" );
layer = parseRequiredAttribute<int>( aText, "layer" );
font = parseOptionalAttribute<string>( aText, "font" );
ratio = parseOptionalAttribute<double>( aText, "ratio" );
rot = parseOptionalAttribute<EROT>( aText, "rot" );
opt_string stemp = parseOptionalAttribute<string>( aText, "align" );
// (bottom-left | bottom-center | bottom-right | center-left |
// center | center-right | top-left | top-center | top-right)
if( stemp == "center" )
align = ETEXT::CENTER;
else if( stemp == "center-right" )
align = ETEXT::CENTER_RIGHT;
else if( stemp == "top-left" )
align = ETEXT::TOP_LEFT;
else if( stemp == "top-center" )
align = ETEXT::TOP_CENTER;
else if( stemp == "top-right" )
align = ETEXT::TOP_RIGHT;
else if( stemp == "bottom-left" )
align = ETEXT::BOTTOM_LEFT;
else if( stemp == "bottom-center" )
align = ETEXT::BOTTOM_CENTER;
else if( stemp == "bottom-right" )
align = ETEXT::BOTTOM_RIGHT;
else if( stemp == "center-left" )
align = ETEXT::CENTER_LEFT;
}
EPAD::EPAD( wxXmlNode* 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
name = parseRequiredAttribute<string>( aPad, "name" );
x = parseRequiredAttribute<double>( aPad, "x" );
y = parseRequiredAttribute<double>( aPad, "y" );
drill = parseRequiredAttribute<double>( aPad, "drill" );
// Optional attributes
diameter = parseOptionalAttribute<double>( aPad, "diameter" );
opt_string s = parseOptionalAttribute<string>( 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;
rot = parseOptionalAttribute<EROT>( aPad, "rot" );
stop = parseOptionalAttribute<bool>( aPad, "stop" );
thermals = parseOptionalAttribute<bool>( aPad, "thermals" );
first = parseOptionalAttribute<bool>( aPad, "first" );
}
ESMD::ESMD( wxXmlNode* 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
name = parseRequiredAttribute<string>( aSMD, "name" );
x = parseRequiredAttribute<double>( aSMD, "x" );
y = parseRequiredAttribute<double>( aSMD, "y" );
dx = parseRequiredAttribute<double>( aSMD, "dx" );
dy = parseRequiredAttribute<double>( aSMD, "dy" );
layer = parseRequiredAttribute<int>( aSMD, "layer" );
roundness = parseOptionalAttribute<int>( aSMD, "roundness" );
rot = parseOptionalAttribute<EROT>( aSMD, "rot" );
thermals = parseOptionalAttribute<bool>( aSMD, "thermals" );
stop = parseOptionalAttribute<bool>( aSMD, "stop" );
thermals = parseOptionalAttribute<bool>( aSMD, "thermals" );
cream = parseOptionalAttribute<bool>( aSMD, "cream" );
}
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<double>( aVertex, "x" );
y = parseRequiredAttribute<double>( aVertex, "y" );
}
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<double>( aPolygon, "width" );
layer = parseRequiredAttribute<int>( aPolygon, "layer" );
spacing = parseOptionalAttribute<double>( aPolygon, "spacing" );
isolate = parseOptionalAttribute<double>( aPolygon, "isolate" );
opt_string s = parseOptionalAttribute<string>( 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<double>( aHole, "x" );
y = parseRequiredAttribute<double>( aHole, "y" );
drill = parseRequiredAttribute<double>( 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<string>( aElement, "name" );
library = parseRequiredAttribute<string>( aElement, "library" );
value = parseRequiredAttribute<string>( aElement, "value" );
package = parseRequiredAttribute<string>( aElement, "package" );
ReplaceIllegalFileNameChars( &package );
x = parseRequiredAttribute<double>( aElement, "x" );
y = parseRequiredAttribute<double>( 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<string>( aLayer, "name" );
color = parseRequiredAttribute<int>( aLayer, "color" );
fill = 1; // Temporary value.
visible = parseOptionalAttribute<bool>( aLayer, "visible" );
active = parseOptionalAttribute<bool>( aLayer, "active" );
}
NODE_MAP MapChildren( wxXmlNode* currentNode )
{
// Map node_name -> node_pointer
NODE_MAP nodesMap;
// Loop through all children mapping them in nodesMap
currentNode = currentNode->GetChildren();
while( currentNode )
{
// Create a new pair in the map
// key: current node name
// value: current node pointer
nodesMap[currentNode->GetName().ToStdString()] = currentNode;
// Get next child
currentNode = currentNode->GetNext();
}
return nodesMap;
}
string makeKey( const string& aFirst, const string& aSecond )
{
string key = aFirst + '\x02' + aSecond;
return key;
}
unsigned long timeStamp( wxXmlNode* aTree )
{
// in this case from a unique tree memory location
return (unsigned long)(void*) aTree;
}
wxPoint kicad_arc_center( wxPoint start, wxPoint end, double angle )
{
// Eagle give us start and end.
// S_ARC wants start to give the center, and end to give the start.
double dx = end.x - start.x, dy = end.y - start.y;
wxPoint mid = (start + end) / 2;
double dlen = sqrt( dx*dx + dy*dy );
double dist = dlen / ( 2 * tan( DEG2RAD( angle ) / 2 ) );
wxPoint center(
mid.x + dist * ( dy / dlen ),
mid.y - dist * ( dx / dlen )
);
return center;
}