KiCad PCB EDA Suite
Loading...
Searching...
No Matches
easypc_document.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
22
23#include <set>
24
25#include <ki_exception.h>
26#include <wx/dir.h>
27#include <wx/filename.h>
28#include <wx/translation.h>
29
30
31namespace EASYPC
32{
33
34namespace
35{
36
37 const char16_t SCM_COMPONENT_STREAM[] = u"[ScmComponent]";
38
39
40 bool isLibrary( FILE_KIND aKind )
41 {
44 }
45
46
47 void requireEnd( const ARCHIVE& aAr, const wxString& aWhat )
48 {
49 if( !aAr.AtEnd() )
50 {
52 wxString::Format( _( "Easy-PC %s ends at offset %zu of %zu." ), aWhat, aAr.Pos(), aAr.Size() ) );
53 }
54 }
55
56
58 LOAD_TASK readItemList( ARCHIVE& aAr, std::vector<SOURCE_PROJECT_ITEM*>& aItems )
59 {
60 for( uint32_t i = aAr.Count(); i > 0; --i )
61 {
62 if( SOURCE_PROJECT_ITEM* item = co_await aAr.ObjectAs<SOURCE_PROJECT_ITEM>() )
63 aItems.push_back( item );
64 }
65 }
66
67} // namespace
68
69
71{
72 // Item fields gate on the project format
73 const int v = aAr.CurrentReadFormat();
74
75 aAr.Skip( 4 ); // type
76 Name = aAr.ReadString();
77 aAr.SkipString();
78
79 if( v > 2 )
80 aAr.Skip( 5 );
81
82 if( v == 4 )
83 aAr.Skip( 4 );
84
85 if( v > 3 )
86 aAr.SkipString();
87
88 if( v > 4 )
89 aAr.SkipString();
90
91 co_return;
92}
93
94
96{
97 if( uint32_t oleItems = aAr.U32() )
98 THROW_IO_ERROR( wxString::Format( _( "Easy-PC project holds %u OLE items." ), oleItems ) );
99
100 Format = aAr.I32();
101
103 THROW_IO_ERROR( wxString::Format( _( "Easy-PC project format %d is not supported." ), Format ) );
104
105 aAr.SetReadFormat( Format );
106
107 co_await readItemList( aAr, Boards );
108 co_await readItemList( aAr, Schematics );
109
110 std::vector<SOURCE_PROJECT_ITEM*> other;
111
112 if( Format > 0 )
113 co_await readItemList( aAr, other );
114
115 // Project properties: eleven strings, then a string map
116 if( Format >= 6 )
117 {
118 for( int i = 0; i < 11; ++i )
119 aAr.SkipString();
120
121 for( int32_t i = aAr.I32(); i > 0; --i )
122 {
123 aAr.SkipString();
124 aAr.SkipString();
125 }
126 }
127}
128
129
130std::unique_ptr<DESIGN_DOCUMENT> LoadDesign( const wxString& aPath )
131{
132 COMPOUND_FILE file( aPath );
133 std::unique_ptr<DESIGN_DOCUMENT> doc = std::make_unique<DESIGN_DOCUMENT>();
134
135 doc->FileKind = file.FileKind();
136
137 if( doc->FileKind != FILE_KIND::PCB_DESIGN && doc->FileKind != FILE_KIND::SCHEMATIC_DESIGN )
138 {
139 THROW_IO_ERROR( wxString::Format( _( "'%s' is a %s, not a design." ), aPath,
140 wxString::FromUTF8( FileKindString( doc->FileKind ) ) ) );
141 }
142
143 const SOURCE_FB_ENTRY* contents = file.FindChild( file.Root(), u"Contents" );
144
145 if( !contents || !contents->IsStream() )
146 THROW_IO_ERROR( wxString::Format( _( "'%s' has no Contents stream." ), aPath ) );
147
148 if( const SOURCE_FB_ENTRY* summary = file.FindChild( file.Root(), u"\x05"
149 u"SummaryInformation" ) )
150 doc->SummaryInformation = file.ReadStream( *summary );
151
152 doc->Kind = doc->FileKind == FILE_KIND::PCB_DESIGN ? DOC_KIND::PCB : DOC_KIND::SCHEMATIC;
153
154 std::vector<uint8_t> data = file.ReadStream( *contents );
155 const char* root = doc->Kind == DOC_KIND::PCB ? "CPcbDesign" : "CScmDesign";
156
157 // The design root sets the product from the header
158 ARCHIVE ar( data.data(), data.size(), doc->Kind, 0 );
159 doc->Design = ARCHIVE::Create( root );
160 ar.LoadEmbedded( *doc->Design, root );
161 requireEnd( ar, _( "design" ) );
162 doc->Objects = ar.TakeObjects();
163 return doc;
164}
165
166
167long long LIBRARY_FILE::Timestamp( const wxString& aPath )
168{
169 wxFileName fn( aPath );
170 return fn.FileExists() ? fn.GetModificationTime().GetValue().GetValue() : 0;
171}
172
173
174LIBRARY_FILE::LIBRARY_FILE( const wxString& aPath ) :
175 m_path( aPath ),
176 m_file( aPath ),
177 m_kind( m_file.FileKind() )
178{
179 if( !isLibrary( m_kind ) )
180 {
181 THROW_IO_ERROR( wxString::Format( _( "'%s' is a %s, not a library." ), m_path,
182 wxString::FromUTF8( FileKindString( m_kind ) ) ) );
183 }
184
185 m_product = m_file.LibraryProduct();
186
187 // Items are root streams, or for a component library root storages
188 const bool wantStorage = m_kind == FILE_KIND::COMPONENT_LIBRARY;
189
190 for( const SOURCE_FB_ENTRY* child : m_file.Children( m_file.Root() ) )
191 {
192 if( child->IsAppStream() )
193 continue;
194
195 if( wantStorage ? !child->IsStorage() : !child->IsStream() )
196 {
197 THROW_IO_ERROR( wxString::Format( _( "Library '%s' holds '%s', which is not an item." ), m_path,
198 UnescapeItemName( child->Name ) ) );
199 }
200
201 m_entries.push_back( child );
202 }
203
204 std::set<wxString> originalNames;
205
206 for( const SOURCE_FB_ENTRY* entry : m_entries )
207 originalNames.insert( UnescapeItemName( entry->Name ) );
208
209 for( size_t i = 0; i < m_entries.size(); ++i )
210 {
211 wxString name = UnescapeItemName( m_entries[i]->Name );
212
213 if( m_byName.count( name ) )
214 {
215 const wxString original = name;
216
217 for( int suffix = 2; m_byName.count( name ) || originalNames.count( name ); ++suffix )
218 name = wxString::Format( wxS( "%s (%d)" ), original, suffix );
219
220 m_warnings.push_back( wxString::Format( _( "Library '%s' lists duplicate item '%s' as '%s'." ), m_path,
221 original, name ) );
222 }
223
224 m_names.push_back( name );
225 m_byName.emplace( name, i );
226 }
227}
228
229
230std::unique_ptr<LIBRARY_ITEM> LIBRARY_FILE::LoadItem( const wxString& aItemName ) const
231{
232 auto it = m_byName.find( aItemName );
233
234 if( it == m_byName.end() )
235 THROW_IO_ERROR( wxString::Format( _( "Library '%s' has no item '%s'." ), m_path, aItemName ) );
236
237 const SOURCE_FB_ENTRY& entry = *m_entries[it->second];
238 std::unique_ptr<LIBRARY_ITEM> item = std::make_unique<LIBRARY_ITEM>();
239
240 item->FileKind = m_kind;
241 item->Name = aItemName;
242
243 std::vector<const SOURCE_FB_ENTRY*> streams = { &entry };
245
247 {
248 // The component stream first, then each package
249 const SOURCE_FB_ENTRY* scm = m_file.FindChild( entry, SCM_COMPONENT_STREAM );
250
251 if( !scm )
252 THROW_IO_ERROR( wxString::Format( _( "Component '%s' has no [ScmComponent] stream." ), aItemName ) );
253
254 kind = DOC_KIND::COMPONENT;
255 streams = { scm };
256
257 for( const SOURCE_FB_ENTRY* child : m_file.Children( entry ) )
258 {
259 if( child != scm )
260 streams.push_back( child );
261 }
262 }
263
264 for( const SOURCE_FB_ENTRY* stream : streams )
265 {
266 std::vector<uint8_t> data = m_file.ReadStream( *stream );
267 ARCHIVE ar( data.data(), data.size(), kind, m_product );
268
269 item->Streams.push_back( LIBRARY_STREAM{ ar.ReadObject(), UnescapeItemName( stream->Name ) } );
270
271 for( std::unique_ptr<OBJECT>& obj : ar.TakeObjects() )
272 item->Objects.push_back( std::move( obj ) );
273 }
274
275 return item;
276}
277
278
279std::unique_ptr<PROJECT_DOCUMENT> LoadProject( const wxString& aPath )
280{
281 std::vector<uint8_t> data = ReadWholeFile( aPath );
282 ARCHIVE ar( data.data(), data.size(), DOC_KIND::PROJECT, 0 );
283 std::unique_ptr<PROJECT_DOCUMENT> doc = std::make_unique<PROJECT_DOCUMENT>();
284
285 doc->Project = std::make_unique<SOURCE_PROJECT>();
286 ar.LoadEmbedded( *doc->Project, "CProject" );
287 requireEnd( ar, _( "project" ) );
288 doc->Objects = ar.TakeObjects();
289 return doc;
290}
291
292
293wxString ResolveProjectItem( const wxString& aProjectPath, const wxString& aItemName )
294{
295 // Item names are relative to the project's directory
296 wxString name = aItemName;
297 name.Replace( wxS( "\\" ), wxS( "/" ) );
298
299 wxFileName fn( name, wxPATH_UNIX );
300 fn.MakeAbsolute( wxFileName( aProjectPath ).GetPath() );
301
302 if( fn.FileExists() )
303 return fn.GetFullPath();
304
305 // Match project entries when the file was copied to a case-sensitive filesystem.
306 wxDir folder( fn.GetPath() );
307 wxString found;
308
309 for( bool more = folder.IsOpened() && folder.GetFirst( &found, wxEmptyString, wxDIR_FILES ); more;
310 more = folder.GetNext( &found ) )
311 {
312 if( found.CmpNoCase( fn.GetFullName() ) == 0 )
313 {
314 fn.SetFullName( found );
315 break;
316 }
317 }
318
319 return fn.GetFullPath();
320}
321
322
323FILE_INFO Probe( const wxString& aPath )
324{
325 try
326 {
327 if( wxFileName( aPath ).GetExt().CmpNoCase( wxS( "prj" ) ) == 0 )
328 {
329 LoadProject( aPath );
330 return { true, std::nullopt };
331 }
332
333 return { false, COMPOUND_FILE( aPath ).FileKind() };
334 }
335 catch( const std::exception& )
336 {
337 return {};
338 }
339}
340
341} // namespace EASYPC
const char * name
One archive load stream; it owns every object it creates.
void Skip(size_t aLen)
int CurrentReadFormat() const
static std::unique_ptr< OBJECT > Create(const char *aClass)
An object of a registered class, not yet loaded.
wxString ReadString()
ANSI (windows-1252) or the 0xFF 0xFFFE UTF-16 form.
std::vector< std::unique_ptr< OBJECT > > TakeObjects()
void SetReadFormat(int aFormat)
OBJECT * ReadObject()
ReadObject and LoadEmbedded outside any loader, running the nested loads to completion.
void LoadEmbedded(OBJECT &aObject, const char *aClass)
A compound file held in memory; any malformed structure throws IO_ERROR.
Definition easypc_file.h:80
std::vector< uint8_t > ReadStream(const SOURCE_FB_ENTRY &aStream) const
const SOURCE_FB_ENTRY & Root() const
Definition easypc_file.h:85
FILE_KIND FileKind() const
const SOURCE_FB_ENTRY * FindChild(const SOURCE_FB_ENTRY &aStorage, const std::u16string &aName) const
std::vector< const SOURCE_FB_ENTRY * > m_entries
std::unique_ptr< LIBRARY_ITEM > LoadItem(const wxString &aItemName) const
Throws IO_ERROR if no item has that name.
LIBRARY_FILE(const wxString &aPath)
Throws IO_ERROR when the file is not a library or is malformed.
std::map< wxString, size_t > m_byName
static long long Timestamp(const wxString &aPath)
A library file's modification time, 0 when it does not exist.
A loader coroutine. Awaiting one queues it on its archive's frame stack instead of nesting the call.
The project file (.prj), a bare archive stream.
Loading designs, library items and projects.
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
FILE_INFO Probe(const wxString &aPath)
Identify a file; projects are parsed fully, while designs and libraries are only inspected....
std::vector< uint8_t > ReadWholeFile(const wxString &aPath)
The whole content of a file; throws IO_ERROR when it cannot be read.
std::unique_ptr< DESIGN_DOCUMENT > LoadDesign(const wxString &aPath)
const char * FileKindString(FILE_KIND aKind)
DOC_KIND
Document kind, from the FileType stream or the file extension for projects.
@ SCH_SYMBOL
one item stream of a Schematic Symbol Library
@ COMPONENT
one stream of a Component Library storage
@ PCB_SYMBOL
one item stream of a PCB Symbol Library
FILE_KIND
The FileType strings the importer reads.
Definition easypc_file.h:44
wxString UnescapeItemName(const std::u16string &aName)
Undo the item name escaping, which stores # ! \ / : * ? " < > | as 0x06..0x10.
wxString ResolveProjectItem(const wxString &aProjectPath, const wxString &aItemName)
The file a project item names, as Easy-PC opens it: the Windows path relative to the project's direct...
std::unique_ptr< PROJECT_DOCUMENT > LoadProject(const wxString &aPath)
Definition easypc_file.h:65
bool IsStream() const
Definition easypc_file.h:71
LOAD_TASK Load(ARCHIVE &aAr) override
Read this object's fields.
wxString Name
Windows path relative to the project directory.
LOAD_TASK Load(ARCHIVE &aAr) override
Read this object's fields.
std::vector< SOURCE_PROJECT_ITEM * > Schematics
in sheet order
std::vector< SOURCE_PROJECT_ITEM * > Boards
at most one