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 <[email protected]>
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 <numeric>
31#include <sstream>
32#include <utf.h>
33#include <wx/log.h>
34#include <wx/translation.h>
35
36
37// Helper for debug logging
38std::string FormatPath( const std::vector<std::string>& aVectorPath )
39{
40 return std::accumulate( aVectorPath.cbegin(), aVectorPath.cend(), std::string(),
41 []( const std::string& ss, const std::string& s )
42 {
43 return ss.empty() ? s : ss + '\\' + s;
44 } );
45}
46
47
49{
50 // Open file
51 FILE* fp = wxFopen( aFilePath, "rb" );
52
53 if( fp == nullptr )
54 {
55 THROW_IO_ERROR( wxString::Format( _( "Cannot open file '%s'." ), aFilePath ) );
56 }
57
58 fseek( fp, 0, SEEK_END );
59 long len = ftell( fp );
60
61 if( len < 0 )
62 {
63 fclose( fp );
64 THROW_IO_ERROR( _( "Error reading file: cannot determine length." ) );
65 }
66
67 // Read into buffer (TODO: add support for memory-mapped files to avoid this copy!)
68 m_buffer.resize( len );
69
70 fseek( fp, 0, SEEK_SET );
71
72 size_t bytesRead = fread( m_buffer.data(), sizeof( unsigned char ), len, fp );
73 fclose( fp );
74
75 if( static_cast<size_t>( len ) != bytesRead )
76 {
77 THROW_IO_ERROR( _( "Error reading file." ) );
78 }
79
80 try
81 {
82 m_reader = std::make_unique<CFB::CompoundFileReader>( m_buffer.data(), m_buffer.size() );
83 }
84 catch( CFB::CFBException& exception )
85 {
86 THROW_IO_ERROR( exception.what() );
87 }
88}
89
90
91static const CFB::COMPOUND_FILE_ENTRY*
92FindStreamSingleLevel( const CFB::CompoundFileReader& aReader,
93 const CFB::COMPOUND_FILE_ENTRY* aEntry, const std::string aName,
94 const bool aIsStream )
95{
96 const CFB::COMPOUND_FILE_ENTRY* ret = nullptr;
97
98 aReader.EnumFiles( aEntry, 1,
99 [&]( const CFB::COMPOUND_FILE_ENTRY* entry, const CFB::utf16string& dir,
100 int level ) -> void
101 {
102 if( aReader.IsStream( entry ) == aIsStream )
103 {
104 std::string name = UTF16ToUTF8( entry->name );
105 if( name == aName.c_str() )
106 {
107 ret = entry;
108 }
109 }
110 } );
111 return ret;
112}
113
114
115const CFB::COMPOUND_FILE_ENTRY*
116ALTIUM_COMPOUND_FILE::FindStream( const std::vector<std::string>& aStreamPath ) const
117{
118 if( !m_reader )
119 {
120 return nullptr;
121 }
122
123 const CFB::COMPOUND_FILE_ENTRY* currentDirEntry = m_reader->GetRootEntry();
124
125 auto it = aStreamPath.cbegin();
126 while( currentDirEntry != nullptr )
127 {
128 const std::string& name = *it;
129
130 if( ++it == aStreamPath.cend() )
131 {
132 return FindStreamSingleLevel( *m_reader.get(), currentDirEntry, name, true );
133 }
134 else
135 {
136 currentDirEntry =
137 FindStreamSingleLevel( *m_reader.get(), currentDirEntry, name, false );
138 }
139 }
140
141 return nullptr;
142}
143
144
146 const CFB::COMPOUND_FILE_ENTRY* aEntry )
147{
148 m_subrecord_end = nullptr;
149 m_size = static_cast<size_t>( aEntry->size );
150 m_error = false;
151 m_content.reset( new char[m_size] );
152 m_pos = m_content.get();
153
154 // read file into buffer
155 aFile.GetCompoundFileReader().ReadFile( aEntry, 0, m_content.get(), m_size );
156}
157
158
159ALTIUM_PARSER::ALTIUM_PARSER( std::unique_ptr<char[]>& aContent, size_t aSize )
160{
161 m_subrecord_end = nullptr;
162 m_size = aSize;
163 m_error = false;
164 m_content = std::move( aContent );
165 m_pos = m_content.get();
166}
167
168
169std::map<wxString, wxString> ALTIUM_PARSER::ReadProperties()
170{
171 std::map<wxString, wxString> kv;
172
173 uint32_t length = Read<uint32_t>();
174
175 if( length > GetRemainingBytes() )
176 {
177 m_error = true;
178 return kv;
179 }
180
181 if( length == 0 )
182 {
183 return kv;
184 }
185
186 // There is one case by kliment where Board6 ends with "|NEARDISTANCE=1000mi".
187 // Both the 'l' and the null-byte are missing, which looks like Altium swallowed two bytes.
188 bool hasNullByte = m_pos[length - 1] == '\0';
189
190 if( !hasNullByte )
191 {
192 wxLogError( _( "Missing null byte at end of property list. Imported data might be "
193 "malformed or missing." ) );
194 }
195
196 // we use std::string because std::string can handle NULL-bytes
197 // wxString would end the string at the first NULL-byte
198 std::string str = std::string( m_pos, length - ( hasNullByte ? 1 : 0 ) );
199 m_pos += length;
200
201 std::size_t token_end = 0;
202
203 while( token_end < str.size() && token_end != std::string::npos )
204 {
205 std::size_t token_start = str.find( '|', token_end );
206 std::size_t token_equal = str.find( '=', token_start );
207 token_end = str.find( '|', token_start + 1 );
208
209 if( token_equal >= token_end )
210 {
211 continue; // this looks like an error: skip the entry. Also matches on std::string::npos
212 }
213
214 if( token_end == std::string::npos )
215 {
216 token_end = str.size() + 1; // this is the correct offset
217 }
218
219 std::string keyS = str.substr( token_start + 1, token_equal - token_start - 1 );
220 std::string valueS = str.substr( token_equal + 1, token_end - token_equal - 1 );
221
222 // convert the strings to wxStrings, since we use them everywhere
223 // value can have non-ASCII characters, so we convert them from LATIN1/ISO8859-1
224 wxString key( keyS.c_str(), wxConvISO8859_1 );
225 // Altium stores keys either in Upper, or in CamelCase. Lets unify it.
226 wxString canonicalKey = key.Trim( false ).Trim( true ).MakeUpper();
227 // If the key starts with '%UTF8%' we have to parse the value using UTF8
228 wxString value;
229
230 if( canonicalKey.StartsWith( "%UTF8%" ) )
231 value = wxString( valueS.c_str(), wxConvUTF8 );
232 else
233 value = wxString( valueS.c_str(), wxConvISO8859_1 );
234
235 // Breathless hack because I haven't a clue what the story is here (but this character
236 // appears in a lot of radial dimensions and is rendered by Altium as a space).
237 value.Replace( wxT( "ÿ" ), wxT( " " ) );
238
239 if( canonicalKey == wxT( "DESIGNATOR" )
240 || canonicalKey == wxT( "NAME" )
241 || canonicalKey == wxT( "TEXT" ) )
242 {
243 value = AltiumPropertyToKiCadString( value.Trim() );
244 }
245
246 kv.insert( { canonicalKey, value.Trim() } );
247 }
248
249 return kv;
250}
251
252
253int32_t ALTIUM_PARSER::ConvertToKicadUnit( const double aValue )
254{
255 constexpr double int_limit = ( std::numeric_limits<int>::max() - 10 ) / 2.54;
256
257 int32_t iu = KiROUND( Clamp<double>( -int_limit, aValue, int_limit ) * 2.54 );
258
259 // Altium's internal precision is 0.1uinch. KiCad's is 1nm. Round to nearest 10nm to clean
260 // up most rounding errors. This allows lossless conversion of increments of 0.05mils and
261 // 0.01um.
262 return KiROUND( (double) iu / 10.0 ) * 10;
263}
264
265
266int ALTIUM_PARSER::ReadInt( const std::map<wxString, wxString>& aProps, const wxString& aKey,
267 int aDefault )
268{
269 const std::map<wxString, wxString>::const_iterator& value = aProps.find( aKey );
270 return value == aProps.end() ? aDefault : wxAtoi( value->second );
271}
272
273
274double ALTIUM_PARSER::ReadDouble( const std::map<wxString, wxString>& aProps, const wxString& aKey,
275 double aDefault )
276{
277 const std::map<wxString, wxString>::const_iterator& value = aProps.find( aKey );
278
279 if( value == aProps.end() )
280 return aDefault;
281
282 // Locale independent str -> double conversation
283 std::istringstream istr( (const char*) value->second.mb_str() );
284 istr.imbue( std::locale::classic() );
285
286 double doubleValue;
287 istr >> doubleValue;
288 return doubleValue;
289}
290
291
292bool ALTIUM_PARSER::ReadBool( const std::map<wxString, wxString>& aProps, const wxString& aKey,
293 bool aDefault )
294{
295 const std::map<wxString, wxString>::const_iterator& value = aProps.find( aKey );
296
297 if( value == aProps.end() )
298 return aDefault;
299 else
300 return value->second == "T" || value->second == "TRUE";
301}
302
303
304int32_t ALTIUM_PARSER::ReadKicadUnit( const std::map<wxString, wxString>& aProps,
305 const wxString& aKey, const wxString& aDefault )
306{
307 const wxString& value = ReadString( aProps, aKey, aDefault );
308
309 wxString prefix;
310
311 if( !value.EndsWith( "mil", &prefix ) )
312 {
313 wxLogError( _( "Unit '%s' does not end with 'mil'." ), value );
314 return 0;
315 }
316
317 double mils;
318
319 if( !prefix.ToCDouble( &mils ) )
320 {
321 wxLogError( _( "Cannot convert '%s' to double." ), prefix );
322 return 0;
323 }
324
325 return ConvertToKicadUnit( mils * 10000 );
326}
327
328
329wxString ALTIUM_PARSER::ReadString( const std::map<wxString, wxString>& aProps,
330 const wxString& aKey, const wxString& aDefault )
331{
332 const auto& utf8Value = aProps.find( wxString( "%UTF8%" ) + aKey );
333
334 if( utf8Value != aProps.end() )
335 return utf8Value->second;
336
337 const auto& value = aProps.find( aKey );
338
339 if( value != aProps.end() )
340 return value->second;
341
342 return aDefault;
343}
const char * name
Definition: DXF_plotter.cpp:56
std::string FormatPath(const std::vector< std::string > &aVectorPath)
Helper for debug logging (vector -> string)
static const CFB::COMPOUND_FILE_ENTRY * FindStreamSingleLevel(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry, const std::string aName, const bool aIsStream)
wxString AltiumPropertyToKiCadString(const wxString &aString)
const CFB::CompoundFileReader & GetCompoundFileReader() const
Definition: altium_parser.h:64
std::vector< char > m_buffer
Definition: altium_parser.h:70
std::unique_ptr< CFB::CompoundFileReader > m_reader
Definition: altium_parser.h:69
ALTIUM_COMPOUND_FILE(const wxString &aFilePath)
Open a CFB file.
const CFB::COMPOUND_FILE_ENTRY * FindStream(const std::vector< std::string > &aStreamPath) const
static int ReadInt(const std::map< wxString, wxString > &aProps, const wxString &aKey, int aDefault)
size_t GetRemainingBytes() const
std::map< wxString, wxString > ReadProperties()
static wxString ReadString(const std::map< wxString, wxString > &aProps, const wxString &aKey, const wxString &aDefault)
std::unique_ptr< char[]> m_content
static double ReadDouble(const std::map< wxString, wxString > &aProps, const wxString &aKey, double aDefault)
static int32_t ConvertToKicadUnit(const double aValue)
int32_t ReadKicadUnit()
ALTIUM_PARSER(const ALTIUM_COMPOUND_FILE &aFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
static bool ReadBool(const std::map< wxString, wxString > &aProps, const wxString &aKey, bool aDefault)
char * m_subrecord_end
#define _(s)
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:38
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
#define kv
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:85