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 = [&]()
262 aDocs.push_back( std::move( *currentDoc ) );
267 while( buffered.CanRead() )
269 wxString rawLine = txt.ReadLine();
271 if( rawLine.empty() )
277 if( rawLine.Last() ==
'|' )
278 rawLine.RemoveLast();
280 int sepPos = rawLine.Find( wxS(
"||" ) );
288 wxString outerStr = rawLine.Left( sepPos );
289 wxString innerStr = rawLine.Mid( sepPos + 2 );
293 nlohmann::json outer = nlohmann::json::parse(
ToStdString( outerStr ) );
294 nlohmann::json inner;
296 if( !innerStr.empty() )
297 inner = nlohmann::json::parse(
ToStdString( innerStr ) );
299 wxString type = GetString( outer,
"type" );
301 if( type == wxS(
"DOCHEAD" ) )
307 doc.
docType = ParseDocType( inner );
308 doc.
uuid = ParseDocUuid( inner );
311 THROW_IO_ERRORF(
_(
"Invalid DOCHEAD in '%s' at line %d" ), aSource, currentLine );
313 currentDoc = std::move( doc );
319 THROW_IO_ERRORF(
_(
"Expected DOCHEAD in '%s' at line %d" ), aSource, currentLine );
322 row.
outer = std::move( outer );
323 row.
inner = std::move( inner );
325 row.
id = GetString( row.
outer,
"id" );
328 wxString dedupId = row.
id;
330 if( dedupId.empty() )
331 dedupId = wxString::Format(
"__line_%d", currentLine );
333 auto it = currentDoc->rowById.find( dedupId );
335 if( it == currentDoc->rowById.end() )
337 currentDoc->rowById[dedupId] = currentDoc->rows.size();
338 currentDoc->rows.emplace_back( std::move( row ) );
340 else if( row.
ticket >= currentDoc->rows[it->second].ticket )
342 currentDoc->rows[it->second] = std::move( row );
345 catch( nlohmann::json::exception& e )
347 THROW_IO_ERRORF(
_(
"JSON error in '%s' at line %d: %s" ), aSource, currentLine, e.what() );
357struct V3_LIBRARY_CONTENTS
359 bool hasIndex =
false;
360 bool hasElibu =
false;
361 std::set<wxString> docTypes;
365static bool HasValidV3LibraryContents(
const V3_LIBRARY_CONTENTS& aContents,
const wxString& aRequiredDocType )
367 if( !aContents.hasIndex || !aContents.hasElibu )
370 if( aRequiredDocType.empty() )
373 return aContents.docTypes.contains( aRequiredDocType );
377static void ScanEpruDocTypes( wxInputStream& aInput,
const wxString& aSource, std::set<wxString>& aDocTypes )
379 wxBufferedInputStream buffered( aInput, 64 * 1024 );
380 wxTextInputStream txt( buffered, wxS(
" " ), wxConvUTF8 );
383 while( buffered.CanRead() )
385 wxString rawLine = txt.ReadLine();
387 if( rawLine.empty() )
393 if( rawLine.Last() ==
'|' )
394 rawLine.RemoveLast();
396 int sepPos = rawLine.Find( wxS(
"||" ) );
404 wxString outerStr = rawLine.Left( sepPos );
405 wxString innerStr = rawLine.Mid( sepPos + 2 );
409 nlohmann::json outer = nlohmann::json::parse(
ToStdString( outerStr ) );
411 if( GetString( outer,
"type" ) == wxS(
"DOCHEAD" ) && !innerStr.empty() )
413 nlohmann::json inner = nlohmann::json::parse(
ToStdString( innerStr ) );
414 wxString docType = ParseDocType( inner );
416 if( !docType.empty() )
417 aDocTypes.insert( docType );
420 catch( nlohmann::json::exception& e )
422 THROW_IO_ERRORF(
_(
"JSON error in '%s' at line %d: %s" ), aSource, currentLine, e.what() );
430static nlohmann::json ReadJsonEntry( wxInputStream& aInput,
const wxString& aEntryName )
432 wxMemoryOutputStream mem;
435 wxStreamBuffer* buf = mem.GetOutputStreamBuffer();
436 std::string jsonStr(
static_cast<const char*
>( buf->GetBufferStart() ), buf->GetBufferSize() );
440 return nlohmann::json::parse( jsonStr );
442 catch( nlohmann::json::exception& e )
444 THROW_IO_ERRORF(
_(
"JSON error reading '%s': %s" ), aEntryName, e.what() );
449static void MergeLibraryIndex( nlohmann::json& aTarget, nlohmann::json&& aIndex )
451 if( !aIndex.is_object() )
454 for(
auto& [key, value] : aIndex.items() )
456 if( aTarget.contains( key ) && aTarget[key].is_object() && value.is_object() )
458 for(
auto& [itemKey, itemValue] : value.items() )
459 aTarget[key][itemKey] = std::move( itemValue );
461 else if( !aTarget.contains( key ) || !value.is_object() || !value.empty() )
463 aTarget[key] = std::move( value );
469static V3_LIBRARY_CONTENTS ScanV3LibraryArchive(
const wxString& aFileName,
const wxString& aRequiredDocType )
471 V3_LIBRARY_CONTENTS contents;
473 std::shared_ptr<wxZipEntry> entry;
474 wxFFileInputStream in( aFileName );
475 wxZipInputStream
zip( in );
480 while( entry.reset(
zip.GetNextEntry() ), entry.get() )
482 wxString entryName = entry->GetName();
483 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' ).Lower();
485 if( baseName == wxS(
"symbol2.json" ) || baseName == wxS(
"footprint2.json" )
486 || baseName == wxS(
"device2.json" ) )
488 contents.hasIndex =
true;
490 else if( baseName.EndsWith( wxS(
".elibu" ) ) )
492 contents.hasElibu =
true;
494 if( !aRequiredDocType.empty() )
495 ScanEpruDocTypes(
zip, entryName, contents.docTypes );
498 if( HasValidV3LibraryContents( contents, aRequiredDocType ) )
506static std::vector<V3_DOC_RAW> ParseV3DocsFromArchive(
const wxString& aFileName, nlohmann::json& aProject2 )
508 std::vector<V3_DOC_RAW> docs;
510 bool hasProject2 =
false;
511 bool hasEpru =
false;
513 std::shared_ptr<wxZipEntry> entry;
514 wxFFileInputStream in( aFileName );
515 wxZipInputStream
zip( in );
520 while( entry.reset(
zip.GetNextEntry() ), entry.get() )
522 wxString entryName = entry->GetName();
523 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' );
525 if( baseName.CmpNoCase( wxS(
"project2.json" ) ) == 0 )
527 aProject2 = ReadJsonEntry(
zip, entryName );
530 else if( baseName.Lower().EndsWith( wxS(
".epru" ) ) )
533 ParseEpruStream(
zip, entryName, docs );
537 if( !hasProject2 || !hasEpru )
539 THROW_IO_ERRORF(
_(
"'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro v3 project. "
540 "Cannot find project2.json and .epru documents." ), aFileName );
547static std::vector<V3_DOC_RAW> ParseV3DocsFromLibraryArchive(
const wxString& aFileName, nlohmann::json& aLibraryIndex )
549 std::vector<V3_DOC_RAW> docs;
550 V3_LIBRARY_CONTENTS contents;
552 std::shared_ptr<wxZipEntry> entry;
553 wxFFileInputStream in( aFileName );
554 wxZipInputStream
zip( in );
559 aLibraryIndex = nlohmann::json::object();
561 while( entry.reset(
zip.GetNextEntry() ), entry.get() )
563 wxString entryName = entry->GetName();
564 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' );
565 wxString lowerBaseName = baseName.Lower();
567 if( lowerBaseName == wxS(
"symbol2.json" ) || lowerBaseName == wxS(
"footprint2.json" )
568 || lowerBaseName == wxS(
"device2.json" ) )
570 nlohmann::json
index = ReadJsonEntry(
zip, entryName );
572 MergeLibraryIndex( aLibraryIndex, std::move(
index ) );
574 contents.hasIndex =
true;
576 else if( lowerBaseName.EndsWith( wxS(
".elibu" ) ) )
578 contents.hasElibu =
true;
579 ParseEpruStream(
zip, entryName, docs );
582 contents.docTypes.insert( doc.
docType );
586 if( !HasValidV3LibraryContents( contents, wxEmptyString ) )
588 THROW_IO_ERRORF(
_(
"'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro v3 library. Cannot find "
589 "symbol2.json, footprint2.json or device2.json and .elibu documents." ),
611 wxFileName fn( aFileName );
612 wxString ext = fn.GetExt().Lower();
614 if( ext != wxS(
"epro2" ) && ext != wxS(
"zip" ) )
619 std::shared_ptr<wxZipEntry> entry;
620 wxFFileInputStream in( aFileName );
621 wxZipInputStream
zip( in );
626 bool hasProject2 =
false;
627 bool hasEpru =
false;
629 while( entry.reset(
zip.GetNextEntry() ), entry.get() != NULL )
631 wxString entryName = entry->GetName();
632 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' );
634 if( baseName.CmpNoCase( wxS(
"project2.json" ) ) == 0 )
636 else if( baseName.Lower().EndsWith( wxS(
".epru" ) ) )
639 if( hasProject2 && hasEpru )
654 wxFileName fn( aFileName );
656 if( fn.GetExt().Lower() != wxS(
"elibz2" ) )
661 V3_LIBRARY_CONTENTS contents = ScanV3LibraryArchive( aFileName, aRequiredDocType );
662 return HasValidV3LibraryContents( contents, aRequiredDocType );
682 m_rawDocs[raw.docType][raw.uuid] = std::move( raw );
697 m_rawDocs[raw.docType][raw.uuid] = std::move( raw );
703 static const std::map<wxString, V3_DOC_RAW>
empty;
716 auto typeIt =
m_rawDocs.find( aDocType );
721 auto docIt = typeIt->second.find( aUuid );
723 if( docIt == typeIt->second.end() )
726 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.