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 );
309 THROW_IO_ERRORF(
_(
"Invalid DOCHEAD in '%s' at line %d" ), aSource, currentLine );
311 currentDoc = std::move( doc );
317 THROW_IO_ERRORF(
_(
"Expected DOCHEAD in '%s' at line %d" ), aSource, currentLine );
320 row.
outer = std::move( outer );
321 row.
inner = std::move( inner );
323 row.
id = GetString( row.
outer,
"id" );
326 wxString dedupId = row.
id;
328 if( dedupId.empty() )
329 dedupId = wxString::Format(
"__line_%d", currentLine );
331 auto it = currentDoc->rowById.find( dedupId );
333 if( it == currentDoc->rowById.end() )
335 currentDoc->rowById[dedupId] = currentDoc->rows.size();
336 currentDoc->rows.emplace_back( std::move( row ) );
338 else if( row.
ticket >= currentDoc->rows[it->second].ticket )
340 currentDoc->rows[it->second] = std::move( row );
343 catch( nlohmann::json::exception& e )
345 THROW_IO_ERRORF(
_(
"JSON error in '%s' at line %d: %s" ), aSource, currentLine, e.what() );
355struct V3_LIBRARY_CONTENTS
357 bool hasIndex =
false;
358 bool hasElibu =
false;
359 std::set<wxString> docTypes;
363static bool HasValidV3LibraryContents(
const V3_LIBRARY_CONTENTS& aContents,
const wxString& aRequiredDocType )
365 if( !aContents.hasIndex || !aContents.hasElibu )
368 if( aRequiredDocType.empty() )
371 return aContents.docTypes.contains( aRequiredDocType );
375static void ScanEpruDocTypes( wxInputStream& aInput,
const wxString& aSource, std::set<wxString>& aDocTypes )
377 wxTextInputStream txt( aInput, wxS(
" " ), wxConvUTF8 );
380 while( aInput.CanRead() )
382 wxString rawLine = txt.ReadLine();
384 if( rawLine.empty() )
390 if( rawLine.Last() ==
'|' )
391 rawLine.RemoveLast();
393 int sepPos = rawLine.Find( wxS(
"||" ) );
401 wxString outerStr = rawLine.Left( sepPos );
402 wxString innerStr = rawLine.Mid( sepPos + 2 );
406 nlohmann::json outer = nlohmann::json::parse(
ToStdString( outerStr ) );
408 if( GetString( outer,
"type" ) == wxS(
"DOCHEAD" ) && !innerStr.empty() )
410 nlohmann::json inner = nlohmann::json::parse(
ToStdString( innerStr ) );
411 wxString docType = ParseDocType( inner );
413 if( !docType.empty() )
414 aDocTypes.insert( docType );
417 catch( nlohmann::json::exception& e )
419 THROW_IO_ERRORF(
_(
"JSON error in '%s' at line %d: %s" ), aSource, currentLine, e.what() );
427static nlohmann::json ReadJsonEntry( wxInputStream& aInput,
const wxString& aEntryName )
429 wxMemoryOutputStream mem;
432 wxStreamBuffer* buf = mem.GetOutputStreamBuffer();
433 std::string jsonStr(
static_cast<const char*
>( buf->GetBufferStart() ), buf->GetBufferSize() );
437 return nlohmann::json::parse( jsonStr );
439 catch( nlohmann::json::exception& e )
441 THROW_IO_ERRORF(
_(
"JSON error reading '%s': %s" ), aEntryName, e.what() );
446static void MergeLibraryIndex( nlohmann::json& aTarget, nlohmann::json&& aIndex )
448 if( !aIndex.is_object() )
451 for(
auto& [key, value] : aIndex.items() )
453 if( aTarget.contains( key ) && aTarget[key].is_object() && value.is_object() )
455 for(
auto& [itemKey, itemValue] : value.items() )
456 aTarget[key][itemKey] = std::move( itemValue );
458 else if( !aTarget.contains( key ) || !value.is_object() || !value.empty() )
460 aTarget[key] = std::move( value );
466static V3_LIBRARY_CONTENTS ScanV3LibraryArchive(
const wxString& aFileName,
const wxString& aRequiredDocType )
468 V3_LIBRARY_CONTENTS contents;
470 std::shared_ptr<wxZipEntry> entry;
471 wxFFileInputStream in( aFileName );
472 wxZipInputStream
zip( in );
477 while( entry.reset(
zip.GetNextEntry() ), entry.get() )
479 wxString entryName = entry->GetName();
480 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' ).Lower();
482 if( baseName == wxS(
"symbol2.json" ) || baseName == wxS(
"footprint2.json" )
483 || baseName == wxS(
"device2.json" ) )
485 contents.hasIndex =
true;
487 else if( baseName.EndsWith( wxS(
".elibu" ) ) )
489 contents.hasElibu =
true;
491 if( !aRequiredDocType.empty() )
492 ScanEpruDocTypes(
zip, entryName, contents.docTypes );
495 if( HasValidV3LibraryContents( contents, aRequiredDocType ) )
503static std::vector<V3_DOC_RAW> ParseV3DocsFromArchive(
const wxString& aFileName, nlohmann::json& aProject2 )
505 std::vector<V3_DOC_RAW> docs;
507 bool hasProject2 =
false;
508 bool hasEpru =
false;
510 std::shared_ptr<wxZipEntry> entry;
511 wxFFileInputStream in( aFileName );
512 wxZipInputStream
zip( in );
517 while( entry.reset(
zip.GetNextEntry() ), entry.get() )
519 wxString entryName = entry->GetName();
520 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' );
522 if( baseName.CmpNoCase( wxS(
"project2.json" ) ) == 0 )
524 aProject2 = ReadJsonEntry(
zip, entryName );
527 else if( baseName.Lower().EndsWith( wxS(
".epru" ) ) )
530 ParseEpruStream(
zip, entryName, docs );
534 if( !hasProject2 || !hasEpru )
536 THROW_IO_ERRORF(
_(
"'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro v3 project. "
537 "Cannot find project2.json and .epru documents." ), aFileName );
544static std::vector<V3_DOC_RAW> ParseV3DocsFromLibraryArchive(
const wxString& aFileName, nlohmann::json& aLibraryIndex )
546 std::vector<V3_DOC_RAW> docs;
547 V3_LIBRARY_CONTENTS contents;
549 std::shared_ptr<wxZipEntry> entry;
550 wxFFileInputStream in( aFileName );
551 wxZipInputStream
zip( in );
556 aLibraryIndex = nlohmann::json::object();
558 while( entry.reset(
zip.GetNextEntry() ), entry.get() )
560 wxString entryName = entry->GetName();
561 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' );
562 wxString lowerBaseName = baseName.Lower();
564 if( lowerBaseName == wxS(
"symbol2.json" ) || lowerBaseName == wxS(
"footprint2.json" )
565 || lowerBaseName == wxS(
"device2.json" ) )
567 nlohmann::json
index = ReadJsonEntry(
zip, entryName );
569 MergeLibraryIndex( aLibraryIndex, std::move(
index ) );
571 contents.hasIndex =
true;
573 else if( lowerBaseName.EndsWith( wxS(
".elibu" ) ) )
575 contents.hasElibu =
true;
576 ParseEpruStream(
zip, entryName, docs );
579 contents.docTypes.insert( doc.
docType );
583 if( !HasValidV3LibraryContents( contents, wxEmptyString ) )
585 THROW_IO_ERRORF(
_(
"'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro v3 library. Cannot find "
586 "symbol2.json, footprint2.json or device2.json and .elibu documents." ),
608 wxFileName fn( aFileName );
609 wxString ext = fn.GetExt().Lower();
611 if( ext != wxS(
"epro2" ) && ext != wxS(
"zip" ) )
616 std::shared_ptr<wxZipEntry> entry;
617 wxFFileInputStream in( aFileName );
618 wxZipInputStream
zip( in );
623 bool hasProject2 =
false;
624 bool hasEpru =
false;
626 while( entry.reset(
zip.GetNextEntry() ), entry.get() != NULL )
628 wxString entryName = entry->GetName();
629 wxString baseName = entryName.AfterLast(
'\\' ).AfterLast(
'/' );
631 if( baseName.CmpNoCase( wxS(
"project2.json" ) ) == 0 )
633 else if( baseName.Lower().EndsWith( wxS(
".epru" ) ) )
636 if( hasProject2 && hasEpru )
651 wxFileName fn( aFileName );
653 if( fn.GetExt().Lower() != wxS(
"elibz2" ) )
658 V3_LIBRARY_CONTENTS contents = ScanV3LibraryArchive( aFileName, aRequiredDocType );
659 return HasValidV3LibraryContents( contents, aRequiredDocType );
679 m_rawDocs[raw.docType][raw.uuid] = std::move( raw );
694 m_rawDocs[raw.docType][raw.uuid] = std::move( raw );
700 static const std::map<wxString, V3_DOC_RAW>
empty;
713 auto typeIt =
m_rawDocs.find( aDocType );
718 auto docIt = typeIt->second.find( aUuid );
720 if( docIt == typeIt->second.end() )
723 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.