KiCad PCB EDA Suite
altium_parser.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2019-2020 Thomas Pointhuber <thomas.pointhuber@gmx.at>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, you may find one here:
18  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19  * or you may search the http://www.gnu.org website for the version 2 license,
20  * or you may write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22  */
23 
24 #include "altium_parser.h"
25 #include "altium_parser_utils.h"
26 
27 #include <compoundfilereader.h>
28 #include <ki_exception.h>
29 #include <math/util.h>
30 #include <sstream>
31 #include <utf.h>
32 #include <wx/log.h>
33 #include <wx/translation.h>
34 
35 const CFB::COMPOUND_FILE_ENTRY* FindStream( const CFB::CompoundFileReader& aReader,
36  const char* aStreamName )
37 {
38  const CFB::COMPOUND_FILE_ENTRY* ret = nullptr;
39  aReader.EnumFiles( aReader.GetRootEntry(), -1,
40  [&]( const CFB::COMPOUND_FILE_ENTRY* aEntry, const CFB::utf16string& aU16dir,
41  int level ) -> void
42  {
43  if( aReader.IsStream( aEntry ) )
44  {
45  std::string name = UTF16ToUTF8( aEntry->name );
46  if( aU16dir.length() > 0 )
47  {
48  std::string dir = UTF16ToUTF8( aU16dir.c_str() );
49  if( strncmp( aStreamName, dir.c_str(), dir.length() ) == 0
50  && aStreamName[dir.length()] == '\\'
51  && strcmp( aStreamName + dir.length() + 1, name.c_str() ) == 0 )
52  {
53  ret = aEntry;
54  }
55  }
56  else
57  {
58  if( strcmp( aStreamName, name.c_str() ) == 0 )
59  {
60  ret = aEntry;
61  }
62  }
63  }
64  } );
65  return ret;
66 }
67 
68 
69 ALTIUM_PARSER::ALTIUM_PARSER( const CFB::CompoundFileReader& aReader,
70  const CFB::COMPOUND_FILE_ENTRY* aEntry )
71 {
72  m_subrecord_end = nullptr;
73  m_size = static_cast<size_t>( aEntry->size );
74  m_error = false;
75  m_content.reset( new char[m_size] );
76  m_pos = m_content.get();
77 
78  // read file into buffer
79  aReader.ReadFile( aEntry, 0, m_content.get(), m_size );
80 }
81 
82 
83 ALTIUM_PARSER::ALTIUM_PARSER( std::unique_ptr<char[]>& aContent, size_t aSize )
84 {
85  m_subrecord_end = nullptr;
86  m_size = aSize;
87  m_error = false;
88  m_content = std::move( aContent );
89  m_pos = m_content.get();
90 }
91 
92 
93 std::map<wxString, wxString> ALTIUM_PARSER::ReadProperties()
94 {
95  std::map<wxString, wxString> kv;
96 
97  uint32_t length = Read<uint32_t>();
98 
99  if( length > GetRemainingBytes() )
100  {
101  m_error = true;
102  return kv;
103  }
104 
105  if( length == 0 )
106  {
107  return kv;
108  }
109 
110  // There is one case by kliment where Board6 ends with "|NEARDISTANCE=1000mi".
111  // Both the 'l' and the null-byte are missing, which looks like Altium swallowed two bytes.
112  bool hasNullByte = m_pos[length - 1] == '\0';
113 
114  if( !hasNullByte )
115  {
116  wxLogError( _( "Missing null byte at end of property list. Imported data might be "
117  "malformed or missing." ) );
118  }
119 
120  // we use std::string because std::string can handle NULL-bytes
121  // wxString would end the string at the first NULL-byte
122  std::string str = std::string( m_pos, length - ( hasNullByte ? 1 : 0 ) );
123  m_pos += length;
124 
125  std::size_t token_end = 0;
126 
127  while( token_end < str.size() && token_end != std::string::npos )
128  {
129  std::size_t token_start = str.find( '|', token_end );
130  std::size_t token_equal = str.find( '=', token_start );
131  token_end = str.find( '|', token_start + 1 );
132 
133  if( token_equal >= token_end )
134  {
135  continue; // this looks like an error: skip the entry. Also matches on std::string::npos
136  }
137 
138  if( token_end == std::string::npos )
139  {
140  token_end = str.size() + 1; // this is the correct offset
141  }
142 
143  std::string keyS = str.substr( token_start + 1, token_equal - token_start - 1 );
144  std::string valueS = str.substr( token_equal + 1, token_end - token_equal - 1 );
145 
146  // convert the strings to wxStrings, since we use them everywhere
147  // value can have non-ASCII characters, so we convert them from LATIN1/ISO8859-1
148  wxString key( keyS.c_str(), wxConvISO8859_1 );
149  // Altium stores keys either in Upper, or in CamelCase. Lets unify it.
150  wxString canonicalKey = key.Trim( false ).Trim( true ).MakeUpper();
151  // If the key starts with '%UTF8%' we have to parse the value using UTF8
152  wxString value;
153 
154  if( canonicalKey.StartsWith( "%UTF8%" ) )
155  value = wxString( valueS.c_str(), wxConvUTF8 );
156  else
157  value = wxString( valueS.c_str(), wxConvISO8859_1 );
158 
159  // Breathless hack because I haven't a clue what the story is here (but this character
160  // appears in a lot of radial dimensions and is rendered by Altium as a space).
161  value.Replace( wxT( "ÿ" ), wxT( " " ) );
162 
163  if( canonicalKey == wxT( "DESIGNATOR" )
164  || canonicalKey == wxT( "NAME" )
165  || canonicalKey == wxT( "TEXT" ) )
166  {
167  value = AltiumPropertyToKiCadString( value.Trim() );
168  }
169 
170  kv.insert( { canonicalKey, value.Trim() } );
171  }
172 
173  return kv;
174 }
175 
176 
177 int32_t ALTIUM_PARSER::ConvertToKicadUnit( const double aValue )
178 {
179  const double int_limit = ( std::numeric_limits<int>::max() - 1 ) / 2.54;
180 
181  int32_t iu = KiROUND( Clamp<double>( -int_limit, aValue, int_limit ) * 2.54 );
182 
183  // Altium stores metric units up to 0.001mm (1000nm) in accuracy. This code fixes rounding
184  // errors. Because imperial units > 0.01mil are always even, this workaround should never
185  // trigger for them.
186  switch( iu % 1000 )
187  {
188  case 1:
189  case -999:
190  return iu - 1;
191  case 999:
192  case -1:
193  return iu + 1;
194  default:
195  return iu;
196  }
197 }
198 
199 
200 int ALTIUM_PARSER::ReadInt( const std::map<wxString, wxString>& aProps, const wxString& aKey,
201  int aDefault )
202 {
203  const std::map<wxString, wxString>::const_iterator& value = aProps.find( aKey );
204  return value == aProps.end() ? aDefault : wxAtoi( value->second );
205 }
206 
207 
208 double ALTIUM_PARSER::ReadDouble( const std::map<wxString, wxString>& aProps, const wxString& aKey,
209  double aDefault )
210 {
211  const std::map<wxString, wxString>::const_iterator& value = aProps.find( aKey );
212 
213  if( value == aProps.end() )
214  return aDefault;
215 
216  // Locale independent str -> double conversation
217  std::istringstream istr( (const char*) value->second.mb_str() );
218  istr.imbue( std::locale::classic() );
219 
220  double doubleValue;
221  istr >> doubleValue;
222  return doubleValue;
223 }
224 
225 
226 bool ALTIUM_PARSER::ReadBool( const std::map<wxString, wxString>& aProps, const wxString& aKey,
227  bool aDefault )
228 {
229  const std::map<wxString, wxString>::const_iterator& value = aProps.find( aKey );
230 
231  if( value == aProps.end() )
232  return aDefault;
233  else
234  return value->second == "T" || value->second == "TRUE";
235 }
236 
237 
238 int32_t ALTIUM_PARSER::ReadKicadUnit( const std::map<wxString, wxString>& aProps,
239  const wxString& aKey, const wxString& aDefault )
240 {
241  const wxString& value = ReadString( aProps, aKey, aDefault );
242 
243  wxString prefix;
244 
245  if( !value.EndsWith( "mil", &prefix ) )
246  {
247  wxLogError( _( "Unit '%s' does not end with 'mil'." ), value );
248  return 0;
249  }
250 
251  double mils;
252 
253  if( !prefix.ToCDouble( &mils ) )
254  {
255  wxLogError( _( "Cannot convert '%s' to double." ), prefix );
256  return 0;
257  }
258 
259  return ConvertToKicadUnit( mils * 10000 );
260 }
261 
262 
263 wxString ALTIUM_PARSER::ReadString( const std::map<wxString, wxString>& aProps,
264  const wxString& aKey, const wxString& aDefault )
265 {
266  const auto& utf8Value = aProps.find( wxString( "%UTF8%" ) + aKey );
267 
268  if( utf8Value != aProps.end() )
269  return utf8Value->second;
270 
271  const auto& value = aProps.find( aKey );
272 
273  if( value != aProps.end() )
274  return value->second;
275 
276  return aDefault;
277 }
#define kv
char * m_subrecord_end
size_t GetRemainingBytes() const
static double ReadDouble(const std::map< wxString, wxString > &aProps, const wxString &aKey, double aDefault)
const CFB::COMPOUND_FILE_ENTRY * FindStream(const CFB::CompoundFileReader &aReader, const char *aStreamName)
static int ReadInt(const std::map< wxString, wxString > &aProps, const wxString &aKey, int aDefault)
std::map< wxString, wxString > ReadProperties()
#define _(s)
static bool ReadBool(const std::map< wxString, wxString > &aProps, const wxString &aKey, bool aDefault)
int32_t ReadKicadUnit()
ALTIUM_PARSER(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
const char * name
Definition: DXF_plotter.cpp:56
wxString AltiumPropertyToKiCadString(const wxString &aString)
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:73
static wxString ReadString(const std::map< wxString, wxString > &aProps, const wxString &aKey, const wxString &aDefault)
std::unique_ptr< char[]> m_content
static int32_t ConvertToKicadUnit(const double aValue)