KiCad PCB EDA Suite
Loading...
Searching...
No Matches
orcad_cis.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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 <algorithm>
24#include <cctype>
25#include <charconv>
26#include <boost/algorithm/string/split.hpp>
27
28#include <ki_exception.h>
29
30
31namespace
32{
33
34std::string payload( const std::vector<char>& aData )
35{
36 if( aData.size() < 4 )
37 THROW_IO_ERROR( wxS( "OrCAD CIS stream is missing its length" ) );
38
39 ORCAD_STREAM stream( aData );
40 uint32_t length = stream.ReadU32();
41
42 if( length != aData.size() - 4 )
43 THROW_IO_ERROR( wxS( "OrCAD CIS stream length is invalid" ) );
44
45 return std::string( aData.begin() + 4, aData.end() );
46}
47
48
49std::vector<std::string> split( const std::string& aText, unsigned char aSeparator )
50{
51 std::vector<std::string> result;
52 boost::split( result, aText,
53 [aSeparator]( unsigned char aByte ) { return aByte == aSeparator; } );
54
55 return result;
56}
57
58
59uint32_t decimal( const std::string& aText, const wxString& aWhat )
60{
61 if( aText.empty() )
62 THROW_IO_ERROR( aWhat );
63
64 uint32_t value = 0;
65 auto [end, error] = std::from_chars( aText.data(), aText.data() + aText.size(), value );
66
67 if( error != std::errc() || end != aText.data() + aText.size() )
68 THROW_IO_ERROR( aWhat );
69
70 return value;
71}
72
73
74std::vector<std::string> readStrings( ORCAD_STREAM& aStream, const wxString& aWhat )
75{
76 uint32_t count = aStream.ReadU32();
77 std::vector<std::string> result;
78
79 if( count > aStream.Remaining() / 3 )
80 THROW_IO_ERROR( aWhat );
81
82 result.reserve( count );
83
84 for( uint32_t i = 0; i < count; ++i )
85 result.push_back( aStream.ReadLzt() );
86
87 return result;
88}
89
90} // namespace
91
92
93std::vector<std::string> OrcadCisParseCountedList( const std::vector<char>& aData, uint8_t aSeparator )
94{
95 std::string contents = payload( aData );
96
97 if( contents.size() == 1 && static_cast<unsigned char>( contents.front() ) == 0xFB )
98 return {};
99
100 std::vector<std::string> fields = split( contents, aSeparator );
101
102 uint32_t count = decimal( fields.front(), wxS( "OrCAD CIS list count is invalid" ) );
103 fields.erase( fields.begin() );
104
105 if( fields.size() != count )
106 THROW_IO_ERROR( wxS( "OrCAD CIS list count does not match its contents" ) );
107
108 return fields;
109}
110
111
112std::map<uint32_t, ORCAD_CIS_PROPERTIES> OrcadCisParsePropertyUpdates( const std::vector<char>& aData )
113{
114 std::map<uint32_t, ORCAD_CIS_PROPERTIES> result;
115 std::string text = payload( aData );
116 constexpr char namesMark = static_cast<char>( 0xB0 );
117 constexpr char valuesMark = static_cast<char>( 0xC0 );
118
119 // Values may contain '~', so only the final '~' or one before another "<id>°<names>À" header ends a record
120 auto closesRecord = [&]( size_t aTilde )
121 {
122 if( aTilde + 1 == text.size() )
123 return true;
124
125 size_t digits = aTilde + 1;
126
127 while( digits < text.size() && std::isdigit( static_cast<unsigned char>( text[digits] ) ) )
128 ++digits;
129
130 if( digits == aTilde + 1 || digits >= text.size() || text[digits] != namesMark )
131 return false;
132
133 size_t values = text.find( valuesMark, digits );
134 return values != std::string::npos && text.find( '~', digits ) > values;
135 };
136
137 size_t start = 0;
138
139 while( start < text.size() )
140 {
141 size_t namesAt = text.find( namesMark, start );
142 size_t valuesAt = namesAt == std::string::npos ? std::string::npos : text.find( valuesMark, namesAt + 1 );
143
144 if( valuesAt == std::string::npos )
145 THROW_IO_ERROR( wxS( "OrCAD CIS property update is malformed" ) );
146
147 size_t end = text.find( '~', valuesAt );
148
149 while( end != std::string::npos && !closesRecord( end ) )
150 end = text.find( '~', end + 1 );
151
152 uint32_t occurrence =
153 decimal( text.substr( start, namesAt - start ), wxS( "OrCAD CIS occurrence ID is invalid" ) );
154 std::vector<std::string> names = split( text.substr( namesAt + 1, valuesAt - namesAt - 1 ), '^' );
155 std::vector<std::string> values =
156 split( text.substr( valuesAt + 1, end == std::string::npos ? end : end - valuesAt - 1 ), '^' );
157
158 start = end == std::string::npos ? text.size() : end + 1;
159
160 // Capture truncates a stream's last value at a '~', leaving fewer values than names
161 if( names.size() != values.size() )
162 {
163 if( start < text.size() )
164 THROW_IO_ERROR( wxS( "OrCAD CIS property names and values have different counts" ) );
165
166 continue;
167 }
168
169 auto& properties = result[occurrence];
170
171 for( size_t i = 0; i < names.size(); ++i )
172 {
173 if( names[i].empty() )
174 THROW_IO_ERROR( wxS( "OrCAD CIS property name is empty" ) );
175
176 properties[names[i]] = values[i];
177 }
178 }
179
180 return result;
181}
182
183
184std::map<uint32_t, bool> OrcadCisParseMemberships( const std::vector<char>& aData )
185{
186 std::map<uint32_t, bool> result;
187 std::vector<std::string> records = split( payload( aData ), 0xB0 );
188
189 // The first field flags whether the group carries property updates
190 for( size_t i = 1; i < records.size(); ++i )
191 {
192 const std::string& record = records[i];
193
194 if( record.empty() )
195 continue;
196
197 size_t separator = record.find( '~' );
198
199 if( separator == std::string::npos || separator + 2 != record.size()
200 || ( record.back() != '0' && record.back() != '1' ) )
201 {
202 THROW_IO_ERROR( wxS( "OrCAD CIS group membership is malformed" ) );
203 }
204
205 uint32_t occurrence =
206 decimal( record.substr( 0, separator ), wxS( "OrCAD CIS membership occurrence ID is invalid" ) );
207 result[occurrence] = record.back() == '1';
208 }
209
210 return result;
211}
212
213
215{
216 ORCAD_STREAM stream( aData );
218 uint32_t recordCount = stream.ReadU32();
219
220 for( uint32_t recordIndex = 0; recordIndex < recordCount; ++recordIndex )
221 {
222 uint32_t databaseId = stream.ReadU32();
223 std::vector<std::string> groups = readStrings( stream, wxS( "OrCAD CIS group list is invalid" ) );
224 std::vector<std::string> states = readStrings( stream, wxS( "OrCAD CIS group state list is invalid" ) );
225 uint32_t propertySetCount = stream.ReadU32();
226
227 if( states.size() != groups.size() || propertySetCount != groups.size() )
228 THROW_IO_ERROR( wxS( "OrCAD CIS schematic group counts do not match" ) );
229
230 for( uint32_t groupIndex = 0; groupIndex < propertySetCount; ++groupIndex )
231 {
232 std::vector<std::string> names =
233 readStrings( stream, wxS( "OrCAD CIS schematic property names are invalid" ) );
234 std::vector<std::string> values =
235 readStrings( stream, wxS( "OrCAD CIS schematic property values are invalid" ) );
236
237 if( names.size() != values.size() )
238 THROW_IO_ERROR( wxS( "OrCAD CIS schematic property counts do not match" ) );
239
240 ORCAD_CIS_PROPERTIES& properties = result[groups[groupIndex]][databaseId];
241
242 for( size_t propertyIndex = 0; propertyIndex < names.size(); ++propertyIndex )
243 {
244 if( names[propertyIndex].empty() )
245 THROW_IO_ERROR( wxS( "OrCAD CIS schematic property name is empty" ) );
246
247 properties[names[propertyIndex]] = values[propertyIndex];
248 }
249 }
250
251 stream.ReadU32();
252 }
253
254 if( !stream.AtEnd() )
255 THROW_IO_ERROR( wxS( "OrCAD CIS schematic stream has trailing data" ) );
256
257 return result;
258}
259
260
261std::string OrcadCisSelectVariant( const std::vector<std::string>& aNames,
262 const std::optional<std::string>& aRequested )
263{
264 if( aRequested )
265 {
266 if( std::find( aNames.begin(), aNames.end(), *aRequested ) == aNames.end() )
267 THROW_IO_ERROR( wxS( "The requested OrCAD CIS variant does not exist" ) );
268
269 return *aRequested;
270 }
271
272 if( aNames.empty() )
273 return {};
274
275 return *std::min_element( aNames.begin(), aNames.end() );
276}
The caller owns the buffer.
size_t Remaining() const
Bytes left; 0 when the cursor is at or past the end.
bool AtEnd() const
std::string ReadLzt()
– strings ----------------------------------------------------------------—
uint32_t ReadU32()
static bool empty(const wxTextEntryBase *aCtrl)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
std::map< uint32_t, ORCAD_CIS_PROPERTIES > OrcadCisParsePropertyUpdates(const std::vector< char > &aData)
Occurrence id -> property overrides.
std::map< uint32_t, bool > OrcadCisParseMemberships(const std::vector< char > &aData)
Occurrence id -> installed state of each part in a group.
std::string OrcadCisSelectVariant(const std::vector< std::string > &aNames, const std::optional< std::string > &aRequested)
ORCAD_CIS_SCHEMATIC_INFO OrcadCisParseSchematicInfo(const std::vector< char > &aData)
std::vector< std::string > OrcadCisParseCountedList(const std::vector< char > &aData, uint8_t aSeparator)
Definition orcad_cis.cpp:93
std::map< std::string, std::map< uint32_t, ORCAD_CIS_PROPERTIES > > ORCAD_CIS_SCHEMATIC_INFO
Definition orcad_cis.h:31
std::map< std::string, std::string > ORCAD_CIS_PROPERTIES
Definition orcad_cis.h:30
static std::vector< std::string > split(const std::string &aStr, const std::string &aDelim)
Split the input string into a vector of output strings.
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.