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"
24
26
27#include <algorithm>
28#include <core/map_helpers.h>
29#include <ki_exception.h>
30#include <string_utils.h>
31#include <vector>
32#include <json_common.h>
34
35#include <wx/log.h>
36#include <wx/stream.h>
37#include <wx/zipstrm.h>
38#include <wx/wfstream.h>
39#include <wx/mstream.h>
40#include <wx/txtstrm.h>
41#include <trace_helpers.h>
42
43
44namespace
45{
46
47// clang-format off
48static const std::vector<wxString> c_deviceAttributesWhitelist = { wxS( "Value" ),
49 wxS( "Datasheet" ),
50 wxS( "Manufacturer Part" ),
51 wxS( "Manufacturer" ),
52 wxS( "BOM_Manufacturer Part" ),
53 wxS( "BOM_Manufacturer" ),
54 wxS( "Supplier Part" ),
55 wxS( "Supplier" ),
56 wxS( "BOM_Supplier Part" ),
57 wxS( "BOM_Supplier" ),
58 wxS( "LCSC Part Name" ) };
59// clang-format on
60
61
62static std::string ToStdString( const wxString& aStr )
63{
64 return std::string( aStr.ToUTF8() );
65}
66
67
68static wxString ToWxString( const std::string& aStr )
69{
70 return wxString::FromUTF8( aStr.c_str() );
71}
72
73
74static nlohmann::json EmptyV3ProjectIndex()
75{
76 nlohmann::json project = nlohmann::json::object();
77 project["schematics"] = nlohmann::json::object();
78 project["boards"] = nlohmann::json::object();
79 project["pcbs"] = nlohmann::json::object();
80 project["symbols"] = nlohmann::json::object();
81 project["footprints"] = nlohmann::json::object();
82 project["devices"] = nlohmann::json::object();
83
84 return project;
85}
86
87
88static const nlohmann::json* FindMetaRow( const EASYEDAPRO::V3_DOC_RAW& aDoc )
89{
90 for( const EASYEDAPRO::V3_ROW& row : aDoc.rows )
91 {
92 if( row.type == wxS( "META" ) )
93 return &row.inner;
94 }
95
96 return nullptr;
97}
98
99
100static wxString MetaGetString( const EASYEDAPRO::V3_DOC_RAW& aDoc, const char* aKey,
101 const wxString& aDefault = wxEmptyString )
102{
103 if( const nlohmann::json* meta = FindMetaRow( aDoc ) )
104 return EASYEDAPRO::V3GetString( *meta, aKey, aDefault );
105
106 return aDefault;
107}
108
109
110static int MetaGetInt( const EASYEDAPRO::V3_DOC_RAW& aDoc, const char* aKey, int aDefault = 0 )
111{
112 if( const nlohmann::json* meta = FindMetaRow( aDoc ) )
113 return EASYEDAPRO::V3GetInt( *meta, aKey, aDefault );
114
115 return aDefault;
116}
117
118
119static nlohmann::json MetaGetValue( const EASYEDAPRO::V3_DOC_RAW& aDoc, const char* aKey,
120 const nlohmann::json& aDefault )
121{
122 if( const nlohmann::json* meta = FindMetaRow( aDoc ) )
123 {
124 auto it = meta->find( aKey );
125
126 if( it != meta->end() )
127 return *it;
128 }
129
130 return aDefault;
131}
132
133} // namespace
134
135
136wxString EASYEDAPRO::ShortenLibName( wxString aProjectName )
137{
138 wxString shortenedName = aProjectName;
139 shortenedName.Replace( wxS( "ProProject_" ), wxS( "" ) );
140 shortenedName.Replace( wxS( "ProDocument_" ), wxS( "" ) );
141 shortenedName = shortenedName.substr( 0, 10 );
142
143 return LIB_ID::FixIllegalChars( shortenedName + wxS( "-easyedapro" ), true );
144}
145
146
147LIB_ID EASYEDAPRO::ToKiCadLibID( const wxString& aLibName, const wxString& aLibReference )
148{
149 wxString libName = LIB_ID::FixIllegalChars( aLibName, true );
150 wxString libReference = EscapeString( aLibReference, CTX_LIBID );
151
152 wxString key = !libName.empty() ? ( libName + ':' + libReference ) : libReference;
153
154 LIB_ID libId;
155 libId.Parse( key, true );
156
157 return libId;
158}
159
160
161std::vector<IMPORT_PROJECT_DESC>
162EASYEDAPRO::ProjectToSelectorDialog( const nlohmann::json& aProject, bool aPcbOnly, bool aSchOnly )
163{
164 std::vector<IMPORT_PROJECT_DESC> result;
165
166 std::map<wxString, EASYEDAPRO::PRJ_SCHEMATIC> prjSchematics = aProject.at( "schematics" );
167 std::map<wxString, EASYEDAPRO::PRJ_BOARD> prjBoards = aProject.at( "boards" );
168
169 std::map<wxString, wxString> prjPcbNames;
170 std::map<wxString, nlohmann::json> prjPcbs = aProject.at( "pcbs" );
171
172 for( const auto& [pcbUuid, pcbJsonEntry] : prjPcbs )
173 {
174 if( pcbJsonEntry.is_string() )
175 prjPcbNames.emplace( pcbUuid, pcbJsonEntry );
176 else if( pcbJsonEntry.is_object() )
177 prjPcbNames.emplace( pcbUuid, pcbJsonEntry.at( "title" ) );
178 }
179
180 for( const auto& [prjName, board] : prjBoards )
181 {
183 desc.ComboName = desc.ComboId = prjName;
184 desc.PCBId = board.pcb;
185 desc.SchematicId = board.schematic;
186
187 auto pcbNameIt = prjPcbNames.find( desc.PCBId );
188 if( pcbNameIt != prjPcbNames.end() )
189 {
190 desc.PCBName = pcbNameIt->second;
191
192 if( desc.PCBName.empty() )
193 desc.PCBName = pcbNameIt->first;
194
195 prjPcbNames.erase( pcbNameIt );
196 }
197
198 auto schIt = prjSchematics.find( desc.SchematicId );
199 if( schIt != prjSchematics.end() )
200 {
201 desc.SchematicName = schIt->second.name;
202
203 if( desc.SchematicName.empty() )
204 desc.SchematicName = schIt->first;
205
206 prjSchematics.erase( schIt );
207 }
208
209 result.emplace_back( desc );
210 }
211
212 if( !aSchOnly )
213 {
214 for( const auto& [pcbId, pcbName] : prjPcbNames )
215 {
217 desc.PCBId = pcbId;
218 desc.PCBName = pcbName;
219
220 if( desc.PCBName.empty() )
221 desc.PCBName = pcbId;
222
223 result.emplace_back( desc );
224 }
225 }
226
227 if( !aPcbOnly )
228 {
229 for( const auto& [schId, schData] : prjSchematics )
230 {
232 desc.SchematicId = schId;
233 desc.SchematicName = schData.name;
234
235 if( desc.SchematicName.empty() )
236 desc.SchematicName = schId;
237
238 result.emplace_back( desc );
239 }
240 }
241
242 return result;
243}
244
245
246nlohmann::json EASYEDAPRO::FindJsonFile( const wxString& aZipFileName,
247 const std::set<wxString>& aFileNames )
248{
249 std::shared_ptr<wxZipEntry> entry;
250 wxFFileInputStream in( aZipFileName );
251 wxZipInputStream zip( in );
252
253 while( entry.reset( zip.GetNextEntry() ), entry.get() )
254 {
255 wxString name = entry->GetName();
256
257 try
258 {
259 if( aFileNames.find( name ) != aFileNames.end() )
260 {
261 wxMemoryOutputStream memos;
262 memos << zip;
263 wxStreamBuffer* buf = memos.GetOutputStreamBuffer();
264
265 wxString str = wxString::FromUTF8( (char*) buf->GetBufferStart(), buf->GetBufferSize() );
266
267 return nlohmann::json::parse( str );
268 }
269 }
270 catch( nlohmann::json::exception& e )
271 {
272 THROW_IO_ERRORF( _( "JSON error reading '%s': %s" ), name, e.what() );
273 }
274 catch( std::exception& e )
275 {
276 THROW_IO_ERRORF( _( "Error reading '%s': %s" ), name, e.what() );
277 }
278 }
279
280 return nlohmann::json{};
281}
282
283
284nlohmann::json EASYEDAPRO::ReadProjectOrDeviceFile( const wxString& aZipFileName )
285{
286 static const std::set<wxString> c_files = { wxS( "project.json" ), wxS( "device.json" ),
287 wxS( "footprint.json" ), wxS( "symbol.json" ) };
288
289 nlohmann::json j = FindJsonFile( aZipFileName, c_files );
290
291 if( !j.is_null() )
292 return j;
293
294 THROW_IO_ERRORF( _( "'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro project or library file. "
295 "Cannot find project.json or device.json." ), aZipFileName );
296}
297
298
300 const wxString& aFileName,
301 std::function<bool( const wxString&, const wxString&, wxInputStream& )> aCallback )
302{
303 std::shared_ptr<wxZipEntry> entry;
304 wxFFileInputStream in( aFileName );
305 wxZipInputStream zip( in );
306
307 if( !zip.IsOk() )
308 THROW_IO_ERRORF( _( "Cannot read ZIP archive '%s'" ), aFileName );
309
310 while( entry.reset( zip.GetNextEntry() ), entry.get() )
311 {
312 wxString name = entry->GetName();
313 wxString baseName = name.AfterLast( '\\' ).AfterLast( '/' ).BeforeFirst( '.' );
314
315 try
316 {
317 if( aCallback( name, baseName, zip ) )
318 break;
319 }
320 catch( nlohmann::json::exception& e )
321 {
322 THROW_IO_ERRORF( _( "JSON error reading '%s': %s" ), name, e.what() );
323 }
324 catch( std::exception& e )
325 {
326 THROW_IO_ERRORF( _( "Error reading '%s': %s" ), name, e.what() );
327 }
328 }
329}
330
331
332std::vector<nlohmann::json> EASYEDAPRO::ParseJsonLines( wxInputStream& aInput, const wxString& aSource )
333{
334 // wxTextInputStream reads a byte at a time, so an unbuffered zip stream inflates per character
335 wxBufferedInputStream buffered( aInput, 64 * 1024 );
336 wxTextInputStream txt( buffered, wxS( " " ), wxConvUTF8 );
337
338 int currentLine = 1;
339
340 std::vector<nlohmann::json> lines;
341 while( buffered.CanRead() )
342 {
343 try
344 {
345 wxString line = txt.ReadLine();
346
347 if( !line.IsEmpty() )
348 {
349 nlohmann::json js = nlohmann::json::parse( line );
350 lines.emplace_back( js );
351 }
352 else
353 {
354 lines.emplace_back( nlohmann::json() );
355 }
356 }
357 catch( nlohmann::json::exception& e )
358 {
359 wxLogTrace( traceEasyEdaIo, wxT( "Cannot parse JSON line %d in '%s': %s" ),
360 currentLine, aSource, e.what() );
361 }
362
363 currentLine++;
364 }
365
366 return lines;
367}
368
369
370std::vector<std::vector<nlohmann::json>>
371EASYEDAPRO::ParseJsonLinesWithSeparation( wxInputStream& aInput, const wxString& aSource )
372{
373 wxBufferedInputStream buffered( aInput, 64 * 1024 );
374 wxTextInputStream txt( buffered, wxS( " " ), wxConvUTF8 );
375
376 int currentLine = 1;
377
378 std::vector<std::vector<nlohmann::json>> lineBlocks;
379 lineBlocks.emplace_back();
380
381 while( buffered.CanRead() )
382 {
383 try
384 {
385 wxString line = txt.ReadLine();
386
387 if( !line.IsEmpty() )
388 {
389 nlohmann::json js = nlohmann::json::parse( line );
390 lineBlocks.back().emplace_back( js );
391 }
392 else
393 {
394 lineBlocks.emplace_back();
395 }
396 }
397 catch( nlohmann::json::exception& e )
398 {
399 wxLogTrace( traceEasyEdaIo, wxT( "Cannot parse JSON line %d in '%s': %s" ),
400 currentLine, aSource, e.what() );
401 }
402
403 currentLine++;
404 }
405
406 return lineBlocks;
407}
408
409
410std::map<wxString, wxString>
411EASYEDAPRO::AnyMapToStringMap( const std::map<wxString, nlohmann::json>& aInput )
412{
413 std::map<wxString, wxString> stringMap;
414
415 for( auto& [key, value] : aInput )
416 {
417 if( value.is_string() )
418 stringMap[key] = value.get<wxString>();
419 else if( value.is_number() )
420 stringMap[key] = wxString::FromCDouble( value.get<double>() );
421 }
422
423 return stringMap;
424}
425
426
427nlohmann::json EASYEDAPRO::BuildV3ProjectIndexFromRawDocs( const V3_DOC_PARSER& aParser, bool aIncludeLibraryMetadata )
428{
429 nlohmann::json project = EmptyV3ProjectIndex();
430
431 struct SHEET_INFO
432 {
433 wxString uuid;
434 wxString name;
435 int zIndex = 0;
436 int order = 0;
437 };
438
439 std::map<wxString, std::vector<SHEET_INFO>> sheetsBySch;
440 int pageOrder = 0;
441
442 for( const auto& [uuid, pageDoc] : aParser.GetRawDocs( wxS( "SCH_PAGE" ) ) )
443 {
444 wxString schematic = MetaGetString( pageDoc, "schematic" );
445
446 if( schematic.empty() )
447 schematic = V3GetString( pageDoc.head, "schematic" );
448
449 if( schematic.empty() )
450 continue;
451
452 SHEET_INFO info;
453 info.uuid = uuid;
454 info.name = MetaGetString( pageDoc, "title", uuid );
455 info.zIndex = MetaGetInt( pageDoc, "zIndex", 0 );
456 info.order = pageOrder++;
457
458 sheetsBySch[schematic].push_back( std::move( info ) );
459 }
460
461 std::map<wxString, wxString> schematicsByBoard;
462
463 for( const auto& [uuid, schDoc] : aParser.GetRawDocs( wxS( "SCH" ) ) )
464 {
465 nlohmann::json sch = nlohmann::json::object();
466 sch["name"] = ToStdString( MetaGetString( schDoc, "title", uuid ) );
467 sch["sheets"] = nlohmann::json::array();
468
469 auto pagesIt = sheetsBySch.find( uuid );
470
471 if( pagesIt != sheetsBySch.end() )
472 {
473 auto& pages = pagesIt->second;
474
475 std::sort( pages.begin(), pages.end(),
476 []( const SHEET_INFO& aLeft, const SHEET_INFO& aRight )
477 {
478 if( aLeft.zIndex != aRight.zIndex )
479 return aLeft.zIndex < aRight.zIndex;
480
481 return aLeft.order < aRight.order;
482 } );
483
484 int sheetId = 1;
485
486 for( const SHEET_INFO& page : pages )
487 {
488 sch["sheets"].push_back( nlohmann::json::object( { { "id", sheetId++ },
489 { "name", ToStdString( page.name ) },
490 { "uuid", ToStdString( page.uuid ) } } ) );
491 }
492 }
493
494 project["schematics"][ToStdString( uuid )] = std::move( sch );
495
496 wxString board = MetaGetString( schDoc, "board" );
497
498 if( !board.empty() )
499 schematicsByBoard[board] = uuid;
500 }
501
502 std::map<wxString, wxString> boardTitles;
503
504 for( const auto& [uuid, boardDoc] : aParser.GetRawDocs( wxS( "BOARD" ) ) )
505 boardTitles[uuid] = MetaGetString( boardDoc, "title", uuid );
506
507 std::map<wxString, wxString> pcbsByBoard;
508
509 for( const auto& [uuid, pcbDoc] : aParser.GetRawDocs( wxS( "PCB" ) ) )
510 {
511 nlohmann::json pcb = nlohmann::json::object();
512 pcb["title"] = ToStdString( MetaGetString( pcbDoc, "title", uuid ) );
513
514 project["pcbs"][ToStdString( uuid )] = std::move( pcb );
515
516 wxString board = MetaGetString( pcbDoc, "board" );
517
518 if( !board.empty() )
519 pcbsByBoard[board] = uuid;
520 }
521
522 std::set<wxString> allBoardRefs;
523
524 for( const auto& [boardRef, schUuid] : schematicsByBoard )
525 allBoardRefs.insert( boardRef );
526
527 for( const auto& [boardRef, pcbUuid] : pcbsByBoard )
528 allBoardRefs.insert( boardRef );
529
530 for( const wxString& boardRef : allBoardRefs )
531 {
532 wxString boardName = boardRef;
533
534 if( auto it = boardTitles.find( boardRef ); it != boardTitles.end() )
535 boardName = it->second;
536
537 if( boardName.empty() )
538 boardName = boardRef;
539
540 nlohmann::json board = nlohmann::json::object();
541 auto schIt = schematicsByBoard.find( boardRef );
542 auto pcbIt = pcbsByBoard.find( boardRef );
543
544 board["schematic"] = schIt != schematicsByBoard.end() ? ToStdString( schIt->second ) : "";
545 board["pcb"] = pcbIt != pcbsByBoard.end() ? ToStdString( pcbIt->second ) : "";
546
547 project["boards"][ToStdString( boardName )] = std::move( board );
548 }
549
550 if( project["boards"].empty() && project["pcbs"].is_object() && project["pcbs"].size() == 1
551 && project["schematics"].is_object() && project["schematics"].size() == 1 )
552 {
553 auto pcbIt = project["pcbs"].begin();
554 auto schIt = project["schematics"].begin();
555
556 wxString pcbId = wxString::FromUTF8( pcbIt.key() );
557 wxString schId = wxString::FromUTF8( schIt.key() );
558 wxString boardName = wxString::FromUTF8( pcbIt.value().value( "title", pcbIt.key() ) );
559
560 if( boardName.empty() )
561 boardName = schId;
562
563 nlohmann::json board = nlohmann::json::object();
564 board["schematic"] = ToStdString( schId );
565 board["pcb"] = ToStdString( pcbId );
566
567 project["boards"][ToStdString( boardName )] = std::move( board );
568 }
569
570 if( !aIncludeLibraryMetadata )
571 return project;
572
573 for( const auto& [uuid, symDoc] : aParser.GetRawDocs( wxS( "SYMBOL" ) ) )
574 {
575 nlohmann::json sym = nlohmann::json::object();
576 sym["source"] = ToStdString( MetaGetString( symDoc, "source" ) );
577 sym["description"] = ToStdString( MetaGetString( symDoc, "description" ) );
578 sym["title"] = ToStdString( MetaGetString( symDoc, "title", uuid ) );
579 sym["display_title"] = sym["title"];
580 sym["version"] = "3";
581 sym["type"] = MetaGetInt( symDoc, "docType", static_cast<int>( SYMBOL_TYPE::NORMAL ) );
582 sym["tags"] = MetaGetValue( symDoc, "tags", nlohmann::json::object() );
583
584 project["symbols"][ToStdString( uuid )] = std::move( sym );
585 }
586
587 for( const auto& [uuid, fpDoc] : aParser.GetRawDocs( wxS( "FOOTPRINT" ) ) )
588 {
589 nlohmann::json fp = nlohmann::json::object();
590 fp["source"] = ToStdString( MetaGetString( fpDoc, "source" ) );
591 fp["description"] = ToStdString( MetaGetString( fpDoc, "description" ) );
592 fp["title"] = ToStdString( MetaGetString( fpDoc, "title", uuid ) );
593 fp["display_title"] = fp["title"];
594 fp["version"] = "3";
595 fp["type"] = static_cast<int>( FOOTPRINT_TYPE::NORMAL );
596 fp["tags"] = MetaGetValue( fpDoc, "tags", nlohmann::json::object() );
597
598 project["footprints"][ToStdString( uuid )] = std::move( fp );
599 }
600
601 for( const auto& [uuid, deviceDoc] : aParser.GetRawDocs( wxS( "DEVICE" ) ) )
602 {
603 nlohmann::json dev = nlohmann::json::object();
604 dev["source"] = ToStdString( MetaGetString( deviceDoc, "source" ) );
605 dev["description"] = ToStdString( MetaGetString( deviceDoc, "description" ) );
606 dev["title"] = ToStdString( MetaGetString( deviceDoc, "title", uuid ) );
607 dev["version"] = "3";
608 dev["tags"] = MetaGetValue( deviceDoc, "tags", nlohmann::json::object() );
609 dev["attributes"] = MetaGetValue( deviceDoc, "attributes", nlohmann::json::object() );
610
611 project["devices"][ToStdString( uuid )] = std::move( dev );
612 }
613
614 return project;
615}
616
617
618std::map<wxString, EASYEDAPRO::BLOB> EASYEDAPRO::BuildV3BlobMap( const V3_DOC_PARSER& aParser )
619{
620 std::map<wxString, BLOB> blobs;
621
622 for( const auto& [blobDocUuid, rawDoc] : aParser.GetRawDocs( wxS( "BLOB" ) ) )
623 {
624 for( const V3_ROW& row : rawDoc.rows )
625 {
626 if( row.type != wxS( "BLOB" ) )
627 continue;
628
629 try
630 {
631 BLOB blob;
632 blob.objectId = V3GetString( row.outer, "id" );
633 blob.url = V3GetString( row.inner, "content" );
634 blobs[blob.objectId] = blob;
635 }
636 catch( nlohmann::json::exception& e )
637 {
638 wxLogTrace( traceEasyEdaIo, wxT( "EasyEDA Pro v3 blob in '%s' was skipped due to parse error: %s" ),
639 blobDocUuid, e.what() );
640 }
641 }
642 }
643
644 return blobs;
645}
646
647
648wxString EASYEDAPRO::GetV3LibraryItemTitle( const nlohmann::json& aMetadata, const wxString& aUuid )
649{
650 wxString title = EASYEDAPRO::V3GetString( aMetadata, "display_title" );
651
652 if( title.empty() )
653 title = EASYEDAPRO::V3GetString( aMetadata, "title" );
654
655 if( title.empty() )
656 title = aUuid;
657
658 return title;
659}
660
661
662wxString EASYEDAPRO::KeywordsFromV3Tags( const nlohmann::json& aTags )
663{
664 if( !aTags.is_object() )
665 return {};
666
667 wxString keywords;
668
669 auto appendTagName = [&]( const char* aKey )
670 {
671 if( !aTags.contains( aKey ) || !aTags.at( aKey ).is_object() )
672 return;
673
674 wxString name = V3GetString( aTags.at( aKey ), "name" );
675
676 if( name.empty() )
677 return;
678
679 if( !keywords.empty() )
680 keywords += wxS( " " );
681
682 keywords += name;
683 };
684
685 appendTagName( "parent_tag" );
686 appendTagName( "child_tag" );
687
688 return keywords;
689}
690
691
692wxString EASYEDAPRO::ResolveDeviceFieldVariables( const wxString& aInput,
693 const std::map<wxString, wxString>& aDeviceAttributes )
694{
695 wxString inputText = aInput;
696 wxString resolvedText;
697 int variableCount = 0;
698
699 // Resolve variables: ={Variable1}text{Variable2}
700 do
701 {
702 if( !inputText.StartsWith( wxS( "={" ) ) )
703 return inputText;
704
705 resolvedText.Clear();
706 variableCount = 0;
707
708 for( size_t i = 1; i < inputText.size(); )
709 {
710 wxUniChar c = inputText[i++];
711
712 if( c == '{' )
713 {
714 wxString varName;
715 bool endFound = false;
716
717 while( i < inputText.size() )
718 {
719 c = inputText[i++];
720
721 if( c == '}' )
722 {
723 endFound = true;
724 break;
725 }
726
727 varName << c;
728 }
729
730 if( !endFound )
731 return inputText;
732
733 wxString varValue =
734 get_def( aDeviceAttributes, varName, wxString::Format( wxS( "{%s!}" ), varName ) );
735
736 resolvedText << varValue;
737 variableCount++;
738 }
739 else
740 {
741 resolvedText << c;
742 }
743 }
744
745 inputText = resolvedText;
746 } while( variableCount > 0 );
747
748 return resolvedText;
749}
750
751
752wxString EASYEDAPRO::NormalizeEasyEDAText( wxString aText )
753{
754 // ℃ -> °C
755 aText.Replace( wxS( "\u2103" ), wxS( "\u00B0C" ), true );
756 return aText;
757}
758
759
760wxString EASYEDAPRO::MakeUniqueLibName( std::set<wxString>& aUsedNames, const wxString& aName,
761 const wxString& aFallback )
762{
763 wxString uniqueBase = aName.empty() ? aFallback : aName;
764 wxString uniqueName = uniqueBase;
765 int suffix = 2;
766
767 while( aUsedNames.contains( uniqueName ) )
768 uniqueName = uniqueBase + wxString::Format( wxS( "_%d" ), suffix++ );
769
770 aUsedNames.insert( uniqueName );
771 return uniqueName;
772}
773
774
775std::map<wxString, wxString> EASYEDAPRO::BuildV3DeviceLibNames( nlohmann::json& aProject,
776 const std::map<wxString, PRJ_DEVICE>& aDevices,
777 std::set<wxString>* aUsedNames )
778{
779 std::map<wxString, wxString> deviceLibNames;
780 std::set<wxString> localUsedNames;
781 std::set<wxString>& usedLibNames = aUsedNames ? *aUsedNames : localUsedNames;
782 nlohmann::json deviceLibNamesJson = nlohmann::json::object();
783
784 for( const auto& [devUuid, device] : aDevices )
785 {
786 wxString libNameForDevice = MakeUniqueLibName( usedLibNames, device.title, devUuid );
787 deviceLibNames[devUuid] = libNameForDevice;
788 deviceLibNamesJson[std::string( devUuid.ToUTF8() )] = std::string( libNameForDevice.ToUTF8() );
789 }
790
791 aProject["device_lib_names"] = std::move( deviceLibNamesJson );
792 return deviceLibNames;
793}
794
795
796wxString EASYEDAPRO::LookupV3DeviceLibName( const nlohmann::json& aProject, const wxString& aDeviceUuid )
797{
798 if( aDeviceUuid.empty() || !aProject.contains( "device_lib_names" )
799 || !aProject.at( "device_lib_names" ).is_object() )
800 {
801 return {};
802 }
803
804 std::string deviceIdUtf8 = std::string( aDeviceUuid.ToUTF8() );
805 const auto& names = aProject.at( "device_lib_names" );
806
807 if( names.contains( deviceIdUtf8 ) && names.at( deviceIdUtf8 ).is_string() )
808 return names.at( deviceIdUtf8 ).get<wxString>();
809
810 return {};
811}
812
813
815 const wxString& aDeviceUuid )
816{
817 V3_DEVICE_DATA data;
818
819 if( aDeviceUuid.empty() || !aProject.contains( "devices" ) || !aProject.at( "devices" ).is_object() )
820 return data;
821
822 std::string deviceIdUtf8 = std::string( aDeviceUuid.ToUTF8() );
823
824 if( !aProject.at( "devices" ).contains( deviceIdUtf8 ) )
825 return data;
826
827 const nlohmann::json& dev = aProject.at( "devices" ).at( deviceIdUtf8 );
828
829 data.found = true;
830 data.description = V3GetString( dev, "description" );
831
832 if( dev.contains( "attributes" ) && dev.at( "attributes" ).is_object() )
833 {
834 for( const auto& [key, value] : dev.at( "attributes" ).items() )
835 data.attributes[wxString::FromUTF8( key )] = V3JsonToString( value );
836 }
837
838 return data;
839}
840
841
842wxString EASYEDAPRO::ResolveV3DeviceValueText( const std::map<wxString, wxString>& aDeviceAttributes )
843{
844 wxString valueText = get_def( aDeviceAttributes, wxS( "Value" ), wxEmptyString );
845
846 if( valueText.empty() )
847 valueText = get_def( aDeviceAttributes, wxS( "Name" ), wxEmptyString );
848
849 return NormalizeEasyEDAText( ResolveDeviceFieldVariables( valueText, aDeviceAttributes ) );
850}
851
852
854 const std::map<wxString, wxString>& aDeviceAttributes, bool aIncludeValue,
855 const std::function<void( const wxString& aKey, const wxString& aValue )>& aCallback )
856{
857 for( const wxString& attrKey : c_deviceAttributesWhitelist )
858 {
859 if( !aIncludeValue && attrKey == wxS( "Value" ) )
860 continue;
861
862 auto valOpt = get_opt( aDeviceAttributes, attrKey );
863
864 if( !valOpt || valOpt->empty() )
865 continue;
866
867 aCallback( attrKey,
868 NormalizeEasyEDAText( ResolveDeviceFieldVariables( *valOpt, aDeviceAttributes ) ) );
869 }
870}
871
872
873template <typename Map>
874static wxString MakeUniqueV3LibraryName( const Map& aItems, const wxString& aName, const wxString& aFallback )
875{
876 wxString uniqueBase = aName.empty() ? aFallback : aName;
877 wxString uniqueName = uniqueBase;
878
879 int suffix = 2;
880 while( aItems.contains( uniqueName ) )
881 uniqueName = uniqueBase + wxString::Format( wxS( "_%d" ), suffix++ );
882
883 return uniqueName;
884}
885
886
887std::map<wxString, EASYEDAPRO::V3_SYMBOL_LIB_ITEM> EASYEDAPRO::BuildV3SymbolLibraryMap( const V3_DOC_PARSER& aParser )
888{
889 std::map<wxString, V3_SYMBOL_LIB_ITEM> items;
890 const nlohmann::json& index = aParser.GetLibraryIndex();
891
892 const bool hasDevices = index.is_object() && index.contains( "devices" ) && index.at( "devices" ).is_object()
893 && !index.at( "devices" ).empty();
894
895 if( hasDevices )
896 {
897 try
898 {
899 for( const auto& [devUuidKey, deviceJson] : index.at( "devices" ).items() )
900 {
901 if( !deviceJson.is_object() )
902 continue;
903
904 PRJ_DEVICE device = deviceJson;
905 auto symbolIt = device.attributes.find( wxS( "Symbol" ) );
906
907 if( symbolIt == device.attributes.end() || symbolIt->second.empty() )
908 continue;
909
910 if( !aParser.FindRawDoc( wxS( "SYMBOL" ), symbolIt->second ) )
911 continue;
912
913 wxString deviceUuid = V3GetString( deviceJson, "uuid", ToWxString( devUuidKey ) );
914 wxString name = device.title.empty() ? GetV3LibraryItemTitle( deviceJson, deviceUuid ) : device.title;
916 item.symbolUuid = symbolIt->second;
917 item.device = std::move( device );
918 item.hasDevice = true;
919
920 items.emplace( MakeUniqueV3LibraryName( items, name, deviceUuid ), std::move( item ) );
921 }
922 }
923 catch( nlohmann::json::exception& e )
924 {
925 wxLogTrace( traceEasyEdaIo, wxT( "Failed to parse EasyEDA Pro v3 device metadata: %s" ), e.what() );
926 return {};
927 }
928
929 return items;
930 }
931
932 for( const auto& [name, symbolUuid] : BuildV3LibraryItemMap( aParser, "symbols", wxS( "SYMBOL" ) ) )
933 {
935 item.symbolUuid = symbolUuid;
936 items.emplace( name, std::move( item ) );
937 }
938
939 return items;
940}
941
942
943std::map<wxString, wxString> EASYEDAPRO::BuildV3LibraryItemMap( const V3_DOC_PARSER& aParser, const char* aIndexKey,
944 const wxString& aDocType )
945{
946 std::map<wxString, wxString> titlesByUuid;
947 const nlohmann::json& index = aParser.GetLibraryIndex();
948
949 if( index.is_object() && index.contains( aIndexKey ) && index.at( aIndexKey ).is_object() )
950 {
951 for( const auto& [uuidKey, metadata] : index.at( aIndexKey ).items() )
952 {
953 wxString uuid = V3GetString( metadata, "uuid", ToWxString( uuidKey ) );
954 titlesByUuid[uuid] = GetV3LibraryItemTitle( metadata, uuid );
955 }
956 }
957
958 std::map<wxString, wxString> nameToUuid;
959
960 for( const auto& [uuid, rawDoc] : aParser.GetRawDocs( aDocType ) )
961 {
962 (void) rawDoc;
963
964 wxString name = uuid;
965
966 if( auto it = titlesByUuid.find( uuid ); it != titlesByUuid.end() )
967 name = it->second;
968
969 nameToUuid[MakeUniqueV3LibraryName( nameToUuid, name, uuid )] = uuid;
970 }
971
972 return nameToUuid;
973}
int index
const char * name
Parses EasyEDA Pro v3 .epro2 archives.
const V3_DOC_RAW * FindRawDoc(const wxString &aDocType, const wxString &aUuid) const
const std::map< wxString, V3_DOC_RAW > & GetRawDocs(const wxString &aDocType) const
const nlohmann::json & GetLibraryIndex() const
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:65
static UTF8 FixIllegalChars(const UTF8 &aLibItemName, bool aLib)
Replace illegal LIB_ID item name characters with underscores '_'.
Definition lib_id.cpp:205
static bool empty(const wxTextEntryBase *aCtrl)
static wxString MakeUniqueV3LibraryName(const Map &aItems, const wxString &aName, const wxString &aFallback)
static std::string ToStdString(const wxString &aStr)
#define _(s)
const wxChar *const traceEasyEdaIo
#define THROW_IO_ERRORF(msg,...)
wxString get_def(const std::map< wxString, wxString > &aMap, const char *aKey, const char *aDefval="")
Definition map_helpers.h:60
std::optional< V > get_opt(const std::map< wxString, V > &aMap, const wxString &aKey)
Definition map_helpers.h:30
nlohmann::json BuildV3ProjectIndexFromRawDocs(const V3_DOC_PARSER &aParser, bool aIncludeLibraryMetadata=true)
Build a minimal legacy-style project index from parsed v3 raw documents.
LIB_ID ToKiCadLibID(const wxString &aLibName, const wxString &aLibReference)
wxString MakeUniqueLibName(std::set< wxString > &aUsedNames, const wxString &aName, const wxString &aFallback)
Allocate a unique library item name, recording it in aUsedNames.
wxString KeywordsFromV3Tags(const nlohmann::json &aTags)
Build KiCad keywords from a v3 tags object (parent_tag / child_tag name fields).
wxString GetV3LibraryItemTitle(const nlohmann::json &aMetadata, const wxString &aUuid)
void ForEachImportedDeviceField(const std::map< wxString, wxString > &aDeviceAttributes, bool aIncludeValue, const std::function< void(const wxString &aKey, const wxString &aValue)> &aCallback)
Invoke aCallback for each non-empty whitelisted Device field (resolved + normalized).
wxString ResolveDeviceFieldVariables(const wxString &aInput, const std::map< wxString, wxString > &aDeviceAttributes)
Resolve EasyEDA ={Var} / ={A}text{B} field expressions against device attributes.
void IterateZipFiles(const wxString &aFileName, std::function< bool(const wxString &, const wxString &, wxInputStream &)> aCallback)
std::vector< nlohmann::json > ParseJsonLines(wxInputStream &aInput, const wxString &aSource)
wxString NormalizeEasyEDAText(wxString aText)
Replace EasyEDA temperature glyph (℃) with °C.
V3_DEVICE_DATA GetV3DeviceData(const nlohmann::json &aProject, const wxString &aDeviceUuid)
std::vector< std::vector< nlohmann::json > > ParseJsonLinesWithSeparation(wxInputStream &aInput, const wxString &aSource)
Multiple document types (e.g.
wxString LookupV3DeviceLibName(const nlohmann::json &aProject, const wxString &aDeviceUuid)
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 V3JsonToString(const nlohmann::json &aValue, const wxString &aDefault)
wxString ResolveV3DeviceValueText(const std::map< wxString, wxString > &aDeviceAttributes)
Preferred Value text: Value attribute, else Name, with variables resolved.
int V3GetInt(const nlohmann::json &aObj, const char *aKey, int aDefault)
std::map< wxString, V3_SYMBOL_LIB_ITEM > BuildV3SymbolLibraryMap(const V3_DOC_PARSER &aParser)
std::map< wxString, wxString > BuildV3LibraryItemMap(const V3_DOC_PARSER &aParser, const char *aIndexKey, const wxString &aDocType)
std::map< wxString, BLOB > BuildV3BlobMap(const V3_DOC_PARSER &aParser)
wxString V3GetString(const nlohmann::json &aObj, const char *aKey, const wxString &aDefault)
std::map< wxString, wxString > BuildV3DeviceLibNames(nlohmann::json &aProject, const std::map< wxString, PRJ_DEVICE > &aDevices, std::set< wxString > *aUsedNames=nullptr)
Stable project-lib item names keyed by Device UUID (one entry per Device).
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
std::map< wxString, wxString > attributes
std::map< wxString, wxString > attributes
Raw parsed document from an EasyEDA Pro v3 .epru stream.
std::vector< V3_ROW > rows
One parsed row from an EasyEDA Pro v3 .epru document stream.
Build the schematic-library name map for a v3 .elibz2.
Describes how non-KiCad boards and schematics should be imported as KiCad projects.
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.