KiCad PCB EDA Suite
Loading...
Searching...
No Matches
easyedapro_import_utils.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) 2023 Alex Shvartzkop <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
22#include "easyedapro_parser.h"
23
25
26#include <ki_exception.h>
27#include <string_utils.h>
28#include <json_common.h>
30
31#include <wx/log.h>
32#include <wx/stream.h>
33#include <wx/zipstrm.h>
34#include <wx/wfstream.h>
35#include <wx/mstream.h>
36#include <wx/txtstrm.h>
37
38
39wxString EASYEDAPRO::ShortenLibName( wxString aProjectName )
40{
41 wxString shortenedName = aProjectName;
42 shortenedName.Replace( wxS( "ProProject_" ), wxS( "" ) );
43 shortenedName.Replace( wxS( "ProDocument_" ), wxS( "" ) );
44 shortenedName = shortenedName.substr( 0, 10 );
45
46 return LIB_ID::FixIllegalChars( shortenedName + wxS( "-easyedapro" ), true );
47}
48
49
50LIB_ID EASYEDAPRO::ToKiCadLibID( const wxString& aLibName, const wxString& aLibReference )
51{
52 wxString libName = LIB_ID::FixIllegalChars( aLibName, true );
53 wxString libReference = EscapeString( aLibReference, CTX_LIBID );
54
55 wxString key = !libName.empty() ? ( libName + ':' + libReference ) : libReference;
56
57 LIB_ID libId;
58 libId.Parse( key, true );
59
60 return libId;
61}
62
63
64std::vector<IMPORT_PROJECT_DESC>
65EASYEDAPRO::ProjectToSelectorDialog( const nlohmann::json& aProject, bool aPcbOnly, bool aSchOnly )
66{
67 std::vector<IMPORT_PROJECT_DESC> result;
68
69 std::map<wxString, EASYEDAPRO::PRJ_SCHEMATIC> prjSchematics = aProject.at( "schematics" );
70 std::map<wxString, EASYEDAPRO::PRJ_BOARD> prjBoards = aProject.at( "boards" );
71
72 std::map<wxString, wxString> prjPcbNames;
73 std::map<wxString, nlohmann::json> prjPcbs = aProject.at( "pcbs" );
74
75 for( const auto& [pcbUuid, pcbJsonEntry] : prjPcbs )
76 {
77 if( pcbJsonEntry.is_string() )
78 prjPcbNames.emplace( pcbUuid, pcbJsonEntry );
79 else if( pcbJsonEntry.is_object() )
80 prjPcbNames.emplace( pcbUuid, pcbJsonEntry.at( "title" ) );
81 }
82
83 for( const auto& [prjName, board] : prjBoards )
84 {
86 desc.ComboName = desc.ComboId = prjName;
87 desc.PCBId = board.pcb;
88 desc.SchematicId = board.schematic;
89
90 auto pcbNameIt = prjPcbNames.find( desc.PCBId );
91 if( pcbNameIt != prjPcbNames.end() )
92 {
93 desc.PCBName = pcbNameIt->second;
94
95 if( desc.PCBName.empty() )
96 desc.PCBName = pcbNameIt->first;
97
98 prjPcbNames.erase( pcbNameIt );
99 }
100
101 auto schIt = prjSchematics.find( desc.SchematicId );
102 if( schIt != prjSchematics.end() )
103 {
104 desc.SchematicName = schIt->second.name;
105
106 if( desc.SchematicName.empty() )
107 desc.SchematicName = schIt->first;
108
109 prjSchematics.erase( schIt );
110 }
111
112 result.emplace_back( desc );
113 }
114
115 if( !aSchOnly )
116 {
117 for( const auto& [pcbId, pcbName] : prjPcbNames )
118 {
120 desc.PCBId = pcbId;
121 desc.PCBName = pcbName;
122
123 if( desc.PCBName.empty() )
124 desc.PCBName = pcbId;
125
126 result.emplace_back( desc );
127 }
128 }
129
130 if( !aPcbOnly )
131 {
132 for( const auto& [schId, schData] : prjSchematics )
133 {
135 desc.SchematicId = schId;
136 desc.SchematicName = schData.name;
137
138 if( desc.SchematicName.empty() )
139 desc.SchematicName = schId;
140
141 result.emplace_back( desc );
142 }
143 }
144
145 return result;
146}
147
148
149nlohmann::json EASYEDAPRO::FindJsonFile( const wxString& aZipFileName,
150 const std::set<wxString>& aFileNames )
151{
152 std::shared_ptr<wxZipEntry> entry;
153 wxFFileInputStream in( aZipFileName );
154 wxZipInputStream zip( in );
155
156 while( entry.reset( zip.GetNextEntry() ), entry.get() != NULL )
157 {
158 wxString name = entry->GetName();
159
160 try
161 {
162 if( aFileNames.find( name ) != aFileNames.end() )
163 {
164 wxMemoryOutputStream memos;
165 memos << zip;
166 wxStreamBuffer* buf = memos.GetOutputStreamBuffer();
167
168 wxString str =
169 wxString::FromUTF8( (char*) buf->GetBufferStart(), buf->GetBufferSize() );
170
171 return nlohmann::json::parse( str );
172 }
173 }
174 catch( nlohmann::json::exception& e )
175 {
177 wxString::Format( _( "JSON error reading '%s': %s" ), name, e.what() ) );
178 }
179 catch( std::exception& e )
180 {
181 THROW_IO_ERROR( wxString::Format( _( "Error reading '%s': %s" ), name, e.what() ) );
182 }
183 }
184
185 return nlohmann::json{};
186}
187
188
189nlohmann::json EASYEDAPRO::ReadProjectOrDeviceFile( const wxString& aZipFileName )
190{
191 static const std::set<wxString> c_files = { wxS( "project.json" ), wxS( "device.json" ),
192 wxS( "footprint.json" ), wxS( "symbol.json" ) };
193
194 nlohmann::json j = FindJsonFile( aZipFileName, c_files );
195
196 if( !j.is_null() )
197 return j;
198
199 THROW_IO_ERROR( wxString::Format(
200 _( "'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro "
201 "project or library file. Cannot find project.json or device.json." ),
202 aZipFileName ) );
203}
204
205
207 const wxString& aFileName,
208 std::function<bool( const wxString&, const wxString&, wxInputStream& )> aCallback )
209{
210 std::shared_ptr<wxZipEntry> entry;
211 wxFFileInputStream in( aFileName );
212 wxZipInputStream zip( in );
213
214 if( !zip.IsOk() )
215 {
216 THROW_IO_ERROR( wxString::Format( _( "Cannot read ZIP archive '%s'" ), aFileName ) );
217 }
218
219 while( entry.reset( zip.GetNextEntry() ), entry.get() != NULL )
220 {
221 wxString name = entry->GetName();
222 wxString baseName = name.AfterLast( '\\' ).AfterLast( '/' ).BeforeFirst( '.' );
223
224 try
225 {
226 if( aCallback( name, baseName, zip ) )
227 break;
228 }
229 catch( nlohmann::json::exception& e )
230 {
232 wxString::Format( _( "JSON error reading '%s': %s" ), name, e.what() ) );
233 }
234 catch( std::exception& e )
235 {
236 THROW_IO_ERROR( wxString::Format( _( "Error reading '%s': %s" ), name, e.what() ) );
237 }
238 }
239}
240
241
242std::vector<nlohmann::json> EASYEDAPRO::ParseJsonLines( wxInputStream& aInput,
243 const wxString& aSource )
244{
245 wxTextInputStream txt( aInput, wxS( " " ), wxConvUTF8 );
246
247 int currentLine = 1;
248
249 std::vector<nlohmann::json> lines;
250 while( aInput.CanRead() )
251 {
252 try
253 {
254 wxString line = txt.ReadLine();
255
256 if( !line.IsEmpty() )
257 {
258 nlohmann::json js = nlohmann::json::parse( line );
259 lines.emplace_back( js );
260 }
261 else
262 {
263 lines.emplace_back( nlohmann::json() );
264 }
265 }
266 catch( nlohmann::json::exception& e )
267 {
268 wxLogWarning( wxString::Format( _( "Cannot parse JSON line %d in '%s': %s" ),
269 currentLine, aSource, e.what() ) );
270 }
271
272 currentLine++;
273 }
274
275 return lines;
276}
277
278
279std::vector<std::vector<nlohmann::json>>
280EASYEDAPRO::ParseJsonLinesWithSeparation( wxInputStream& aInput, const wxString& aSource )
281{
282 wxTextInputStream txt( aInput, wxS( " " ), wxConvUTF8 );
283
284 int currentLine = 1;
285
286 std::vector<std::vector<nlohmann::json>> lineBlocks;
287 lineBlocks.emplace_back();
288
289 while( aInput.CanRead() )
290 {
291 try
292 {
293 wxString line = txt.ReadLine();
294
295 if( !line.IsEmpty() )
296 {
297 nlohmann::json js = nlohmann::json::parse( line );
298 lineBlocks.back().emplace_back( js );
299 }
300 else
301 {
302 lineBlocks.emplace_back();
303 }
304 }
305 catch( nlohmann::json::exception& e )
306 {
307 wxLogWarning( wxString::Format( _( "Cannot parse JSON line %d in '%s': %s" ),
308 currentLine, aSource, e.what() ) );
309 }
310
311 currentLine++;
312 }
313
314 return lineBlocks;
315}
316
317
318std::map<wxString, wxString>
319EASYEDAPRO::AnyMapToStringMap( const std::map<wxString, nlohmann::json>& aInput )
320{
321 std::map<wxString, wxString> stringMap;
322
323 for( auto& [key, value] : aInput )
324 {
325 if( value.is_string() )
326 stringMap[key] = value.get<wxString>();
327 else if( value.is_number() )
328 stringMap[key] = wxString::FromCDouble( value.get<double>() );
329 }
330
331 return stringMap;
332}
const char * name
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:48
static UTF8 FixIllegalChars(const UTF8 &aLibItemName, bool aLib)
Replace illegal LIB_ID item name characters with underscores '_'.
Definition lib_id.cpp:188
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
LIB_ID ToKiCadLibID(const wxString &aLibName, const wxString &aLibReference)
void IterateZipFiles(const wxString &aFileName, std::function< bool(const wxString &, const wxString &, wxInputStream &)> aCallback)
std::vector< nlohmann::json > ParseJsonLines(wxInputStream &aInput, const wxString &aSource)
std::vector< std::vector< nlohmann::json > > ParseJsonLinesWithSeparation(wxInputStream &aInput, const wxString &aSource)
Multiple document types (e.g.
nlohmann::json FindJsonFile(const wxString &aZipFileName, const std::set< wxString > &aFileNames)
std::vector< IMPORT_PROJECT_DESC > ProjectToSelectorDialog(const nlohmann::json &aProject, bool aPcbOnly=false, bool aSchOnly=false)
nlohmann::json ReadProjectOrDeviceFile(const wxString &aZipFileName)
wxString ShortenLibName(wxString aProjectName)
std::map< wxString, wxString > AnyMapToStringMap(const std::map< wxString, nlohmann::json > &aInput)
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
@ CTX_LIBID
Describes how non-KiCad boards and schematics should be imported as KiCad projects.
wxString result
Test unit parsing edge cases and error handling.