KiCad PCB EDA Suite
Loading...
Searching...
No Matches
s_expr_loader.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 (C) 2012-2013 Alexander Lunev <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
22
23#include <dsnlexer.h>
24#include <memory>
25#include <macros.h>
26#include <xnode.h>
27
28#include <algorithm>
29
30#include <wx/ffile.h>
31#include <wx/string.h>
32#include <wx/translation.h>
33#include <wx/xml/xml.h>
34
35namespace PCAD2KICAD {
36
37static KEYWORD empty_keywords[1] = {};
38static const char ACCEL_ASCII_KEYWORD[] = "ACCEL_ASCII";
39
40
41// P-CAD text values may span lines inside one quoted string, but DSNLEXER
42// scans strings within a single line. Escape raw line breaks inside quoted
43// strings so the lexer's escape handling reconstructs them; CR is dropped so
44// CRLF files yield the same text the old text-mode reader produced. The scan
45// mirrors the lexer's rules. A quote opens a string only at a token boundary
46// (some writers emit unescaped quotes inside bare tokens), and inside a
47// string a backslash consumes the following character, so an escaped quote
48// does not terminate it.
49static void escapeStringLineBreaks( std::string& aContent )
50{
51 std::string result;
52 bool modified = false;
53 bool inString = false;
54 bool atBoundary = true;
55
56 for( size_t i = 0; i < aContent.size(); i++ )
57 {
58 char c = aContent[i];
59
60 if( !modified && ( c == '\n' || c == '\r' ) && inString )
61 {
62 // first in-string line break; switch to rewriting from here on
63 result.assign( aContent, 0, i );
64 modified = true;
65 }
66
67 if( inString )
68 {
69 if( c == '\\' && i + 1 < aContent.size() )
70 {
71 if( modified )
72 {
73 result += c;
74 result += aContent[i + 1];
75 }
76
77 ++i;
78 continue;
79 }
80
81 if( c == '"' )
82 {
83 // a closing quote also ends the token, so a quote directly
84 // after it starts a new string
85 inString = false;
86 atBoundary = true;
87 }
88 else if( c == '\n' )
89 {
90 result += "\\n";
91 continue;
92 }
93 else if( c == '\r' )
94 {
95 continue;
96 }
97 }
98 else if( c == '"' && atBoundary )
99 {
100 inString = true;
101 }
102 else
103 {
104 // NUL counts as whitespace to DSNLEXER
105 atBoundary = ( c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '('
106 || c == ')' || c == '\0' );
107 }
108
109 if( modified )
110 result += c;
111 }
112
113 if( modified )
114 aContent.swap( result );
115}
116
117
118bool FileMatchesFormat( const wxString& aFileName, const std::string& aSection )
119{
120 wxFFile file( aFileName, wxT( "rb" ) );
121
122 if( !file.IsOpened() )
123 return false;
124
125 char header[sizeof( ACCEL_ASCII_KEYWORD )];
126
127 if( file.Read( header, sizeof( header ) - 1 )
128 != static_cast<size_t>( sizeof( header ) - 1 ) )
129 {
130 return false;
131 }
132
133 if( memcmp( header, ACCEL_ASCII_KEYWORD, sizeof( ACCEL_ASCII_KEYWORD ) - 1 ) != 0 )
134 return false;
135
136 if( aSection.empty() )
137 return true;
138
139 // Writers emit the design sections at the start of a line; anchoring the
140 // search there keeps a section name quoted inside a text value from
141 // matching. A file holds exactly one design section, so seeing the
142 // sibling section rejects the file without reading to the end.
143 const std::string wanted = "\n(" + aSection;
144 const std::string sibling = ( aSection == "pcbDesign" ) ? "\n(schematicDesign"
145 : "\n(pcbDesign";
146
147 const size_t overlap = std::max( wanted.size(), sibling.size() );
148
149 // seed with a line break so a section directly after the header matches
150 std::string window = "\n";
151 char buf[64 * 1024];
152
153 for( ;; )
154 {
155 size_t got = file.Read( buf, sizeof( buf ) );
156
157 if( got == 0 || got == static_cast<size_t>( wxInvalidOffset ) )
158 return false;
159
160 // normalize CRLF so the line anchor matches either ending
161 window.append( buf, got );
162 window.erase( std::remove( window.begin(), window.end(), '\r' ), window.end() );
163
164 if( window.find( wanted ) != std::string::npos )
165 return true;
166
167 if( window.find( sibling ) != std::string::npos )
168 return false;
169
170 if( window.size() > overlap )
171 window.erase( 0, window.size() - overlap );
172 }
173}
174
175
176void LoadInputFile( const wxString& aFileName, wxXmlDocument* aXmlDoc )
177{
178 int tok = 0;
179 XNODE* iNode = nullptr, *cNode = nullptr;
180 wxString str, propValue, content;
181 wxCSConv conv( wxT( "windows-1251" ) );
182
183 wxFFile file( aFileName, wxT( "rb" ) );
184
185 if( !file.IsOpened() )
186 THROW_IO_ERROR( wxString::Format( _( "Cannot open file '%s'." ), aFileName ) );
187
188 std::string fileContent;
189
190 {
191 wxFileOffset length = file.Length();
192
193 if( length <= 0 )
194 THROW_IO_ERROR( wxString::Format( _( "Cannot read file '%s'." ), aFileName ) );
195
196 fileContent.resize( static_cast<size_t>( length ) );
197
198 if( file.Read( fileContent.data(), fileContent.size() ) != fileContent.size() )
199 THROW_IO_ERROR( wxString::Format( _( "Cannot read file '%s'." ), aFileName ) );
200 }
201
202 // check file format; the first line starts with "ACCEL_ASCII" with optional
203 // stuff on the same line after that.
204 if( fileContent.compare( 0, sizeof( ACCEL_ASCII_KEYWORD ) - 1, ACCEL_ASCII_KEYWORD ) != 0 )
205 THROW_IO_ERROR( wxString::Format( _( "'%s' is not a P-CAD ASCII file." ), aFileName ) );
206
207 escapeStringLineBreaks( fileContent );
208
209 DSNLEXER lexer( empty_keywords, 0, nullptr, fileContent, aFileName );
210
211 // owns the tree on every throw path until it is handed to the document
212 std::unique_ptr<XNODE> root( new XNODE( wxXML_ELEMENT_NODE, wxT( "www.lura.sk" ) ) );
213
214 iNode = root.get();
215
216 while( ( tok = lexer.NextTok() ) != DSN_EOF )
217 {
218 if( tok == DSN_RIGHT )
219 {
220 iNode = iNode->GetParent();
221
222 // this will happen if there are more right parens than left
223 if( !iNode )
224 THROW_IO_ERROR( wxString::Format( _( "Unbalanced parentheses in '%s'." ),
225 aFileName ) );
226 }
227 else if( tok == DSN_LEFT )
228 {
229 tok = lexer.NextTok();
230 str = wxEmptyString;
231 cNode = new XNODE( wxXML_ELEMENT_NODE, wxString( lexer.CurText(), conv ) );
232 iNode->AddChild( cNode );
233 iNode = cNode;
234 }
235 else if( cNode )
236 {
237 str = wxString( lexer.CurText(), conv );
238
239 if( tok == DSN_STRING )
240 {
241 // update attribute
242 if( iNode->GetAttribute( wxT( "Name" ), &propValue ) )
243 {
244 iNode->DeleteAttribute( wxT( "Name" ) );
245 iNode->AddAttribute( wxT( "Name" ), propValue + wxT( ' ' ) + str );
246 }
247 else
248 {
249 iNode->AddAttribute( wxT( "Name" ), str );
250 }
251 }
252 else if( str != wxEmptyString )
253 {
254 // update node content
255 content = cNode->GetNodeContent() + wxT( ' ' ) + str;
256
257 if( cNode->GetChildren() )
258 cNode->GetChildren()->SetContent( content );
259 else
260 cNode->AddChild( new wxXmlNode( wxXML_TEXT_NODE, wxEmptyString, content ) );
261 }
262 }
263 }
264
265 // more left parens than right means the file was truncated mid-save
266 if( iNode != root.get() )
267 THROW_IO_ERROR( wxString::Format( _( "Unbalanced parentheses in '%s'." ), aFileName ) );
268
269 aXmlDoc->SetRoot( root.release() );
270}
271
272} // namespace PCAD2KICAD
Implement a lexical analyzer for the SPECCTRA DSN file format.
Definition dsnlexer.h:75
const char * CurText() const
Return a pointer to the current token's text.
Definition dsnlexer.h:411
int NextTok()
Return the next token found in the input file or DSN_EOF when reaching the end of file.
Definition dsnlexer.cpp:537
An extension of wxXmlNode that can format its contents as KiCad-style s-expressions.
Definition xnode.h:67
XNODE * GetParent() const
Definition xnode.h:107
void AddAttribute(const wxString &aName, const wxString &aValue) override
Definition xnode.cpp:88
@ DSN_LEFT
Definition dsnlexer.h:63
@ DSN_RIGHT
Definition dsnlexer.h:62
@ DSN_STRING
Definition dsnlexer.h:64
@ DSN_EOF
Definition dsnlexer.h:65
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
This file contains miscellaneous commonly used macros and functions.
bool FileMatchesFormat(const wxString &aFileName, const std::string &aSection)
Check that a file is ACCEL_ASCII and, when aSection is not empty, that it contains the given section ...
void LoadInputFile(const wxString &aFileName, wxXmlDocument *aXmlDoc)
static KEYWORD empty_keywords[1]
static void escapeStringLineBreaks(std::string &aContent)
static const char ACCEL_ASCII_KEYWORD[]
Hold a keyword string and its unique integer token.
Definition dsnlexer.h:36
std::vector< std::string > header
wxString result
Test unit parsing edge cases and error handling.