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 )
252 wxBufferedInputStream buffered( aInput, 64 * 1024 );
253 wxTextInputStream txt( buffered, wxS(
" " ), wxConvUTF8 );
256 std::optional<V3_DOC_RAW> currentDoc;
258 auto flushDoc = [&]()
266 std::vector<V3_ROW> keptRows;
267 keptRows.reserve( currentDoc->rows.size() );
269 for(
V3_ROW& row : currentDoc->rows )
271 if( !row.
inner.is_null() )
272 keptRows.push_back( std::move( row ) );
275 currentDoc->rows = std::move( keptRows );
276 currentDoc->rowById.clear();
278 for(
size_t i = 0; i < currentDoc->rows.size(); ++i )
280 if( !currentDoc->rows[i].id.empty() )
281 currentDoc->rowById[currentDoc->rows[i].id] = i;
284 aDocs.push_back( std::move( *currentDoc ) );
289 while( buffered.CanRead() )
291 wxString rawLine = txt.ReadLine();
293 if( rawLine.empty() )
299 if( rawLine.Last() ==
'|' )
300 rawLine.RemoveLast();
302 int sepPos = rawLine.Find( wxS(
"||" ) );
310 wxString outerStr = rawLine.Left( sepPos );
311 wxString innerStr = rawLine.Mid( sepPos + 2 );
315 nlohmann::json outer = nlohmann::json::parse(
ToStdString( outerStr ) );
316 nlohmann::json inner;
318 if( !innerStr.empty() )
319 inner = nlohmann::json::parse(
ToStdString( innerStr ) );
321 wxString type = GetString( outer,
"type" );
323 if( type == wxS(
"DOCHEAD" ) )
329 doc.
docType = ParseDocType( inner );
330 doc.
uuid = ParseDocUuid( inner );
333 THROW_IO_ERRORF(
_(
"Invalid DOCHEAD in '%s' at line %d" ), aSource, currentLine );
335 currentDoc = std::move( doc );
341 THROW_IO_ERRORF(
_(
"Expected DOCHEAD in '%s' at line %d" ), aSource, currentLine );
344 row.
outer = std::move( outer );
345 row.
inner = std::move( inner );
347 row.
id = GetString( row.
outer,
"id" );
350 wxString dedupId = row.
id;
352 if( dedupId.empty() )
353 dedupId = wxString::Format(
"__line_%d", currentLine );
355 auto it = currentDoc->rowById.find( dedupId );
357 if( it == currentDoc->rowById.end() )
359 currentDoc->rowById[dedupId] = currentDoc->rows.size();
360 currentDoc->rows.emplace_back( std::move( row ) );
362 else if( row.
ticket >= currentDoc->rows[it->second].ticket )
364 currentDoc->rows[it->second] = std::move( row );
367 catch( nlohmann::json::exception& e )
369 THROW_IO_ERRORF(
_(
"JSON error in '%s' at line %d: %s" ), aSource, currentLine, e.what() );
379struct V3_LIBRARY_CONTENTS
381 bool hasIndex =
false;
382 bool hasElibu =
false;
383 std::set<wxString> docTypes;
387static bool HasValidV3LibraryContents(
const V3_LIBRARY_CONTENTS& aContents,
const wxString& aRequiredDocType )
389 if( !aContents.hasIndex || !aContents.hasElibu )
392 if( aRequiredDocType.empty() )
395 return aContents.docTypes.contains( aRequiredDocType );
399static void ScanEpruDocTypes( wxInputStream& aInput,
const wxString& aSource, std::set<wxString>& aDocTypes )
401 wxBufferedInputStream buffered( aInput, 64 * 1024 );
402 wxTextInputStream txt( buffered, wxS(
" " ), wxConvUTF8 );
405 while( buffered.CanRead() )
407 wxString rawLine = txt.ReadLine();
409 if( rawLine.empty() )
415 if( rawLine.Last() ==
'|' )
416 rawLine.RemoveLast();
418 int sepPos = rawLine.Find( wxS(
"||" ) );
426 wxString outerStr = rawLine.Left( sepPos );
427 wxString innerStr = rawLine.Mid( sepPos + 2 );
431 nlohmann::json outer = nlohmann::json::parse(
ToStdString( outerStr ) );
433 if( GetString( outer,
"type" ) == wxS(
"DOCHEAD" ) && !innerStr.empty() )
435 nlohmann::json inner = nlohmann::json::parse(
ToStdString( innerStr ) );
436 wxString docType = ParseDocType( inner );
438 if( !docType.empty() )
439 aDocTypes.insert( docType );
442 catch( nlohmann::json::exception& e )
444 THROW_IO_ERRORF(
_(
"JSON error in '%s' at line %d: %s" ), aSource, currentLine, e.what() );
452static nlohmann::json ReadJsonEntry( wxInputStream& aInput,
const wxString& aEntryName )
454 wxMemoryOutputStream mem;
457 wxStreamBuffer* buf = mem.GetOutputStreamBuffer();
458 std::string jsonStr(
static_cast<const char*
>( buf->GetBufferStart() ), buf->GetBufferSize() );
462 return nlohmann::json::parse( jsonStr );
464 catch( nlohmann::json::exception& e )
466 THROW_IO_ERRORF(
_(
"JSON error reading '%s': %s" ), aEntryName, e.what() );
471static void MergeLibraryIndex( nlohmann::json& aTarget, nlohmann::json&& aIndex )
473 if( !aIndex.is_object() )
476 for(
auto& [key, value] : aIndex.items() )
478 if( aTarget.contains( key ) && aTarget[key].is_object() && value.is_object() )
480 for(
auto& [itemKey, itemValue] : value.items() )
481 aTarget[key][itemKey] = std::move( itemValue );
483 else if( !aTarget.contains( key ) || !value.is_object() || !value.empty() )
485 aTarget[key] = std::move( value );
491static V3_LIBRARY_CONTENTS ScanV3LibraryArchive(
const wxString& aFileName,
const wxString& aRequiredDocType )
493 V3_LIBRARY_CONTENTS contents;
495 std::shared_ptr<wxZipEntry> entry;
496 wxFFileInputStream in( aFileName );
497 wxZipInputStream
zip( in );
502 while( entry.reset(
zip.GetNextEntry() ), entry.get() )
504 wxString entryName = entry->GetName();
505 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' ).Lower();
507 if( baseName == wxS(
"symbol2.json" ) || baseName == wxS(
"footprint2.json" )
508 || baseName == wxS(
"device2.json" ) )
510 contents.hasIndex =
true;
512 else if( baseName.EndsWith( wxS(
".elibu" ) ) )
514 contents.hasElibu =
true;
516 if( !aRequiredDocType.empty() )
517 ScanEpruDocTypes(
zip, entryName, contents.docTypes );
520 if( HasValidV3LibraryContents( contents, aRequiredDocType ) )
528static std::vector<V3_DOC_RAW> ParseV3DocsFromArchive(
const wxString& aFileName, nlohmann::json& aProject2 )
530 std::vector<V3_DOC_RAW> docs;
532 bool hasProject2 =
false;
533 bool hasEpru =
false;
535 std::shared_ptr<wxZipEntry> entry;
536 wxFFileInputStream in( aFileName );
537 wxZipInputStream
zip( in );
542 while( entry.reset(
zip.GetNextEntry() ), entry.get() )
544 wxString entryName = entry->GetName();
545 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' );
547 if( baseName.CmpNoCase( wxS(
"project2.json" ) ) == 0 )
549 aProject2 = ReadJsonEntry(
zip, entryName );
552 else if( baseName.Lower().EndsWith( wxS(
".epru" ) ) )
555 ParseEpruStream(
zip, entryName, docs );
559 if( !hasProject2 || !hasEpru )
561 THROW_IO_ERRORF(
_(
"'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro v3 project. "
562 "Cannot find project2.json and .epru documents." ), aFileName );
569static std::vector<V3_DOC_RAW> ParseV3DocsFromLibraryArchive(
const wxString& aFileName, nlohmann::json& aLibraryIndex )
571 std::vector<V3_DOC_RAW> docs;
572 V3_LIBRARY_CONTENTS contents;
574 std::shared_ptr<wxZipEntry> entry;
575 wxFFileInputStream in( aFileName );
576 wxZipInputStream
zip( in );
581 aLibraryIndex = nlohmann::json::object();
583 while( entry.reset(
zip.GetNextEntry() ), entry.get() )
585 wxString entryName = entry->GetName();
586 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' );
587 wxString lowerBaseName = baseName.Lower();
589 if( lowerBaseName == wxS(
"symbol2.json" ) || lowerBaseName == wxS(
"footprint2.json" )
590 || lowerBaseName == wxS(
"device2.json" ) )
592 nlohmann::json
index = ReadJsonEntry(
zip, entryName );
594 MergeLibraryIndex( aLibraryIndex, std::move(
index ) );
596 contents.hasIndex =
true;
598 else if( lowerBaseName.EndsWith( wxS(
".elibu" ) ) )
600 contents.hasElibu =
true;
601 ParseEpruStream(
zip, entryName, docs );
604 contents.docTypes.insert( doc.
docType );
608 if( !HasValidV3LibraryContents( contents, wxEmptyString ) )
610 THROW_IO_ERRORF(
_(
"'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro v3 library. Cannot find "
611 "symbol2.json, footprint2.json or device2.json and .elibu documents." ),
633 wxFileName fn( aFileName );
634 wxString ext = fn.GetExt().Lower();
636 if( ext != wxS(
"epro2" ) && ext != wxS(
"zip" ) )
641 std::shared_ptr<wxZipEntry> entry;
642 wxFFileInputStream in( aFileName );
643 wxZipInputStream
zip( in );
648 bool hasProject2 =
false;
649 bool hasEpru =
false;
651 while( entry.reset(
zip.GetNextEntry() ), entry.get() != NULL )
653 wxString entryName = entry->GetName();
654 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' );
656 if( baseName.CmpNoCase( wxS(
"project2.json" ) ) == 0 )
658 else if( baseName.Lower().EndsWith( wxS(
".epru" ) ) )
661 if( hasProject2 && hasEpru )
676 wxFileName fn( aFileName );
678 if( fn.GetExt().Lower() != wxS(
"elibz2" ) )
683 V3_LIBRARY_CONTENTS contents = ScanV3LibraryArchive( aFileName, aRequiredDocType );
684 return HasValidV3LibraryContents( contents, aRequiredDocType );
704 m_rawDocs[raw.docType][raw.uuid] = std::move( raw );
719 m_rawDocs[raw.docType][raw.uuid] = std::move( raw );
725 static const std::map<wxString, V3_DOC_RAW>
empty;
738 auto typeIt =
m_rawDocs.find( aDocType );
743 auto docIt = typeIt->second.find( aUuid );
745 if( docIt == typeIt->second.end() )
748 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_ERRORF(msg,...)
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.