37#include <wx/filename.h>
39#include <wx/mstream.h>
40#include <wx/txtstrm.h>
41#include <wx/wfstream.h>
42#include <wx/zipstrm.h>
51 return std::string( aStr.ToUTF8() );
58wxString
V3JsonToString(
const nlohmann::json& aValue,
const wxString& aDefault )
60 if( aValue.is_string() )
61 return aValue.get<wxString>();
63 if( aValue.is_number_integer() )
64 return wxString::Format(
"%lld",
static_cast<long long>( aValue.get<int64_t>() ) );
66 if( aValue.is_number_unsigned() )
67 return wxString::Format(
"%llu",
static_cast<unsigned long long>( aValue.get<uint64_t>() ) );
69 if( aValue.is_number_float() )
70 return wxString::FromDouble( aValue.get<
double>() );
72 if( aValue.is_boolean() )
73 return aValue.get<
bool>() ? wxS(
"true" ) : wxS(
"false" );
79wxString
V3GetString(
const nlohmann::json& aObj,
const char* aKey,
const wxString& aDefault )
81 if( !aObj.is_object() )
84 auto it = aObj.find( aKey );
86 if( it == aObj.end() )
93wxString
V3GetString(
const nlohmann::json& aObj,
const wxString& aKey,
const wxString& aDefault )
99double V3GetDouble(
const nlohmann::json& aObj,
const char* aKey,
double aDefault )
101 if( !aObj.is_object() )
104 auto it = aObj.find( aKey );
106 if( it == aObj.end() )
109 if( it->is_number() )
110 return it->get<
double>();
112 if( it->is_string() )
116 if( it->get<wxString>().ToCDouble( &value ) )
124int V3GetInt(
const nlohmann::json& aObj,
const char* aKey,
int aDefault )
126 if( !aObj.is_object() )
129 auto it = aObj.find( aKey );
131 if( it == aObj.end() )
134 if( it->is_number_integer() )
135 return it->get<
int>();
137 if( it->is_number_float() )
138 return static_cast<int>( std::lround( it->get<
double>() ) );
140 if( it->is_boolean() )
141 return it->get<
bool>() ? 1 : 0;
143 if( it->is_string() )
147 if( it->get<wxString>().ToLong( &value ) )
148 return static_cast<int>( value );
155bool V3GetBool(
const nlohmann::json& aObj,
const char* aKey,
bool aDefault )
157 if( !aObj.is_object() )
160 auto it = aObj.find( aKey );
162 if( it == aObj.end() )
165 if( it->is_boolean() )
166 return it->get<
bool>();
168 if( it->is_number() )
169 return it->get<
double>() != 0.0;
171 if( it->is_string() )
173 wxString s = it->get<wxString>();
175 if( s.CmpNoCase( wxS(
"true" ) ) == 0 )
178 if( s.CmpNoCase( wxS(
"false" ) ) == 0 )
183 if( s.ToLong( &value ) )
193 if( !aObj.is_object() )
196 auto it = aObj.find( aKey );
198 return it == aObj.end() || it->is_null();
205 return nlohmann::json();
209 return nlohmann::json::parse(
ToStdString( aId ) );
213 return nlohmann::json();
224static inline wxString GetString(
const nlohmann::json& aObj,
const char* aKey,
225 const wxString& aDefault = wxEmptyString )
230static inline int GetInt(
const nlohmann::json& aObj,
const char* aKey,
int aDefault = 0 )
236static wxString ParseDocType(
const nlohmann::json& aHead )
238 return GetString( aHead,
"docType" );
242static wxString ParseDocUuid(
const nlohmann::json& aHead )
244 return GetString( aHead,
"uuid" );
248static void ParseEpruStream( wxInputStream& aInput,
const wxString& aSource,
249 std::vector<V3_DOC_RAW>& aDocs )
251 wxTextInputStream txt( aInput, wxS(
" " ), wxConvUTF8 );
254 std::optional<V3_DOC_RAW> currentDoc;
256 auto flushDoc = [&]()
260 aDocs.push_back( std::move( *currentDoc ) );
265 while( aInput.CanRead() )
267 wxString rawLine = txt.ReadLine();
269 if( rawLine.empty() )
275 if( rawLine.Last() ==
'|' )
276 rawLine.RemoveLast();
278 int sepPos = rawLine.Find( wxS(
"||" ) );
286 wxString outerStr = rawLine.Left( sepPos );
287 wxString innerStr = rawLine.Mid( sepPos + 2 );
291 nlohmann::json outer = nlohmann::json::parse(
ToStdString( outerStr ) );
292 nlohmann::json inner;
294 if( !innerStr.empty() )
295 inner = nlohmann::json::parse(
ToStdString( innerStr ) );
297 wxString type = GetString( outer,
"type" );
299 if( type == wxS(
"DOCHEAD" ) )
305 doc.
docType = ParseDocType( inner );
306 doc.
uuid = ParseDocUuid( inner );
310 THROW_IO_ERROR( wxString::Format(
_(
"Invalid DOCHEAD in '%s' at line %d" ),
311 aSource, currentLine ) );
314 currentDoc = std::move( doc );
321 THROW_IO_ERROR( wxString::Format(
_(
"Expected DOCHEAD in '%s' at line %d" ),
322 aSource, currentLine ) );
326 row.
outer = std::move( outer );
327 row.
inner = std::move( inner );
329 row.
id = GetString( row.
outer,
"id" );
332 wxString dedupId = row.
id;
334 if( dedupId.empty() )
335 dedupId = wxString::Format(
"__line_%d", currentLine );
337 auto it = currentDoc->rowById.find( dedupId );
339 if( it == currentDoc->rowById.end() )
341 currentDoc->rowById[dedupId] = currentDoc->rows.size();
342 currentDoc->rows.emplace_back( std::move( row ) );
344 else if( row.
ticket >= currentDoc->rows[it->second].ticket )
346 currentDoc->rows[it->second] = std::move( row );
349 catch( nlohmann::json::exception& e )
351 THROW_IO_ERROR( wxString::Format(
_(
"JSON error in '%s' at line %d: %s" ),
352 aSource, currentLine, e.what() ) );
362struct V3_LIBRARY_CONTENTS
364 bool hasIndex =
false;
365 bool hasElibu =
false;
366 std::set<wxString> docTypes;
370static bool HasValidV3LibraryContents(
const V3_LIBRARY_CONTENTS& aContents,
const wxString& aRequiredDocType )
372 if( !aContents.hasIndex || !aContents.hasElibu )
375 if( aRequiredDocType.empty() )
378 return aContents.docTypes.contains( aRequiredDocType );
382static void ScanEpruDocTypes( wxInputStream& aInput,
const wxString& aSource, std::set<wxString>& aDocTypes )
384 wxTextInputStream txt( aInput, wxS(
" " ), wxConvUTF8 );
387 while( aInput.CanRead() )
389 wxString rawLine = txt.ReadLine();
391 if( rawLine.empty() )
397 if( rawLine.Last() ==
'|' )
398 rawLine.RemoveLast();
400 int sepPos = rawLine.Find( wxS(
"||" ) );
408 wxString outerStr = rawLine.Left( sepPos );
409 wxString innerStr = rawLine.Mid( sepPos + 2 );
413 nlohmann::json outer = nlohmann::json::parse(
ToStdString( outerStr ) );
415 if( GetString( outer,
"type" ) == wxS(
"DOCHEAD" ) && !innerStr.empty() )
417 nlohmann::json inner = nlohmann::json::parse(
ToStdString( innerStr ) );
418 wxString docType = ParseDocType( inner );
420 if( !docType.empty() )
421 aDocTypes.insert( docType );
424 catch( nlohmann::json::exception& e )
427 wxString::Format(
_(
"JSON error in '%s' at line %d: %s" ), aSource, currentLine, e.what() ) );
435static nlohmann::json ReadJsonEntry( wxInputStream& aInput,
const wxString& aEntryName )
437 wxMemoryOutputStream mem;
440 wxStreamBuffer* buf = mem.GetOutputStreamBuffer();
441 std::string jsonStr(
static_cast<const char*
>( buf->GetBufferStart() ), buf->GetBufferSize() );
445 return nlohmann::json::parse( jsonStr );
447 catch( nlohmann::json::exception& e )
449 THROW_IO_ERROR( wxString::Format(
_(
"JSON error reading '%s': %s" ), aEntryName, e.what() ) );
454static void MergeLibraryIndex( nlohmann::json& aTarget, nlohmann::json&& aIndex )
456 if( !aIndex.is_object() )
459 for(
auto& [key, value] : aIndex.items() )
461 if( aTarget.contains( key ) && aTarget[key].is_object() && value.is_object() )
463 for(
auto& [itemKey, itemValue] : value.items() )
464 aTarget[key][itemKey] = std::move( itemValue );
466 else if( !aTarget.contains( key ) || !value.is_object() || !value.empty() )
468 aTarget[key] = std::move( value );
474static V3_LIBRARY_CONTENTS ScanV3LibraryArchive(
const wxString& aFileName,
const wxString& aRequiredDocType )
476 V3_LIBRARY_CONTENTS contents;
478 std::shared_ptr<wxZipEntry> entry;
479 wxFFileInputStream in( aFileName );
480 wxZipInputStream
zip( in );
483 THROW_IO_ERROR( wxString::Format(
_(
"Cannot read ZIP archive '%s'" ), aFileName ) );
485 while( entry.reset(
zip.GetNextEntry() ), entry.get() != NULL )
487 wxString entryName = entry->GetName();
488 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' ).Lower();
490 if( baseName == wxS(
"symbol2.json" ) || baseName == wxS(
"footprint2.json" )
491 || baseName == wxS(
"device2.json" ) )
493 contents.hasIndex =
true;
495 else if( baseName.EndsWith( wxS(
".elibu" ) ) )
497 contents.hasElibu =
true;
499 if( !aRequiredDocType.empty() )
500 ScanEpruDocTypes(
zip, entryName, contents.docTypes );
503 if( HasValidV3LibraryContents( contents, aRequiredDocType ) )
511static std::vector<V3_DOC_RAW> ParseV3DocsFromArchive(
const wxString& aFileName, nlohmann::json& aProject2 )
513 std::vector<V3_DOC_RAW> docs;
515 bool hasProject2 =
false;
516 bool hasEpru =
false;
518 std::shared_ptr<wxZipEntry> entry;
519 wxFFileInputStream in( aFileName );
520 wxZipInputStream
zip( in );
523 THROW_IO_ERROR( wxString::Format(
_(
"Cannot read ZIP archive '%s'" ), aFileName ) );
525 while( entry.reset(
zip.GetNextEntry() ), entry.get() != NULL )
527 wxString entryName = entry->GetName();
528 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' );
530 if( baseName.CmpNoCase( wxS(
"project2.json" ) ) == 0 )
532 aProject2 = ReadJsonEntry(
zip, entryName );
535 else if( baseName.Lower().EndsWith( wxS(
".epru" ) ) )
538 ParseEpruStream(
zip, entryName, docs );
542 if( !hasProject2 || !hasEpru )
544 THROW_IO_ERROR( wxString::Format(
_(
"'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro v3 project. "
545 "Cannot find project2.json and .epru documents." ),
553static std::vector<V3_DOC_RAW> ParseV3DocsFromLibraryArchive(
const wxString& aFileName, nlohmann::json& aLibraryIndex )
555 std::vector<V3_DOC_RAW> docs;
556 V3_LIBRARY_CONTENTS contents;
558 std::shared_ptr<wxZipEntry> entry;
559 wxFFileInputStream in( aFileName );
560 wxZipInputStream
zip( in );
563 THROW_IO_ERROR( wxString::Format(
_(
"Cannot read ZIP archive '%s'" ), aFileName ) );
565 aLibraryIndex = nlohmann::json::object();
567 while( entry.reset(
zip.GetNextEntry() ), entry.get() != NULL )
569 wxString entryName = entry->GetName();
570 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' );
571 wxString lowerBaseName = baseName.Lower();
573 if( lowerBaseName == wxS(
"symbol2.json" ) || lowerBaseName == wxS(
"footprint2.json" )
574 || lowerBaseName == wxS(
"device2.json" ) )
576 nlohmann::json
index = ReadJsonEntry(
zip, entryName );
578 MergeLibraryIndex( aLibraryIndex, std::move(
index ) );
580 contents.hasIndex =
true;
582 else if( lowerBaseName.EndsWith( wxS(
".elibu" ) ) )
584 contents.hasElibu =
true;
585 ParseEpruStream(
zip, entryName, docs );
588 contents.docTypes.insert( doc.
docType );
592 if( !HasValidV3LibraryContents( contents, wxEmptyString ) )
594 THROW_IO_ERROR( wxString::Format(
_(
"'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro v3 library. "
595 "Cannot find symbol2.json, footprint2.json or device2.json and .elibu "
618 wxFileName fn( aFileName );
619 wxString ext = fn.GetExt().Lower();
621 if( ext != wxS(
"epro2" ) && ext != wxS(
"zip" ) )
626 std::shared_ptr<wxZipEntry> entry;
627 wxFFileInputStream in( aFileName );
628 wxZipInputStream
zip( in );
633 bool hasProject2 =
false;
634 bool hasEpru =
false;
636 while( entry.reset(
zip.GetNextEntry() ), entry.get() != NULL )
638 wxString entryName = entry->GetName();
639 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' );
641 if( baseName.CmpNoCase( wxS(
"project2.json" ) ) == 0 )
643 else if( baseName.Lower().EndsWith( wxS(
".epru" ) ) )
646 if( hasProject2 && hasEpru )
661 wxFileName fn( aFileName );
663 if( fn.GetExt().Lower() != wxS(
"elibz2" ) )
668 V3_LIBRARY_CONTENTS contents = ScanV3LibraryArchive( aFileName, aRequiredDocType );
669 return HasValidV3LibraryContents( contents, aRequiredDocType );
689 m_rawDocs[raw.docType][raw.uuid] = std::move( raw );
704 m_rawDocs[raw.docType][raw.uuid] = std::move( raw );
710 static const std::map<wxString, V3_DOC_RAW>
empty;
723 auto typeIt =
m_rawDocs.find( aDocType );
728 auto docIt = typeIt->second.find( aUuid );
730 if( docIt == typeIt->second.end() )
733 return &docIt->second;
void Load()
Load all v3 docs from the archive.
const V3_DOC_RAW * FindRawDoc(const wxString &aDocType, const wxString &aUuid) const
const std::map< wxString, V3_DOC_RAW > & GetRawDocs(const wxString &aDocType) const
nlohmann::json m_project2
static bool IsV3Library(const wxString &aFileName, const wxString &aRequiredDocType=wxEmptyString)
Return true if aFileName looks like an EasyEDA Pro v3 library archive.
std::map< wxString, std::map< wxString, V3_DOC_RAW > > m_rawDocs
nlohmann::json m_libraryIndex
V3_DOC_PARSER(const wxString &aFileName)
static bool IsV3Archive(const wxString &aFileName)
Return true if aFileName looks like an EasyEDA Pro v3 archive.
void LoadLibrary()
Load all v3 docs from a library archive.
static bool empty(const wxTextEntryBase *aCtrl)
static std::string ToStdString(const wxString &aStr)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
bool V3GetBool(const nlohmann::json &aObj, const char *aKey, bool aDefault)
nlohmann::json V3ParseIdArray(const wxString &aId)
bool V3IsNullOrMissing(const nlohmann::json &aObj, const char *aKey)
double V3GetDouble(const nlohmann::json &aObj, const char *aKey, double aDefault)
wxString V3JsonToString(const nlohmann::json &aValue, const wxString &aDefault)
int V3GetInt(const nlohmann::json &aObj, const char *aKey, int aDefault)
wxString V3GetString(const nlohmann::json &aObj, const char *aKey, const wxString &aDefault)
Raw parsed document from an EasyEDA Pro v3 .epru stream.
One parsed row from an EasyEDA Pro v3 .epru document stream.