1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2010 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
5  * Copyright (C) 2012 Wayne Stambaugh <stambaughw@gmail.com>
6  * Copyright (C) 2010-2020 KiCad Developers, see change_log.txt for contributors.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, you may find one here:
20  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21  * or you may search the http://www.gnu.org website for the version 2 license,
22  * or you may write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
24  */
25 
26 #include <cstring>
27 #include <memory>
28 #include <wx/translation.h>
29 
30 #include <macros.h>     // TO_UTF8()
31 #include <lib_id.h>
32 
33 
okLogical(const UTF8 & aField)34 static inline int okLogical( const UTF8& aField )
35 {
36     // std::string::npos is largest positive number, casting to int makes it -1.
37     // Returning that means success.
38     return int( aField.find_first_of( ":" ) );
39 }
40 
41 
clear()42 void LIB_ID::clear()
43 {
44     m_libraryName.clear();
45     m_itemName.clear();
46 }
47 
48 
Parse(const UTF8 & aId,bool aFix)49 int LIB_ID::Parse( const UTF8& aId, bool aFix )
50 {
51     clear();
52 
53     size_t partNdx;
54     int    offset = -1;
55 
56     //=====<library nickname>=============================
57     if( ( partNdx = aId.find( ':' ) ) != aId.npos )
58     {
59         offset = SetLibNickname( aId.substr( 0, partNdx ) );
60 
61         if( offset > -1 )
62             return offset;
63 
64         ++partNdx;  // skip ':'
65     }
66     else
67     {
68         partNdx = 0;
69     }
70 
71     //=====<item name>====================================
72     UTF8 fpname = aId.substr( partNdx );
73 
74     // Be sure the item name is valid.
75     // Some chars can be found in legacy files converted files from other EDA tools.
76     if( aFix )
77         fpname = FixIllegalChars( fpname, false );
78     else
79         offset = HasIllegalChars( fpname );
80 
81     if( offset > -1 )
82         return offset;
83 
84     SetLibItemName( fpname );
85 
86     return -1;
87 }
88 
89 
LIB_ID(const wxString & aLibraryName,const wxString & aItemName)90 LIB_ID::LIB_ID( const wxString& aLibraryName, const wxString& aItemName ) :
91         m_libraryName( aLibraryName ),
92         m_itemName( aItemName )
93 {
94 }
95 
96 
SetLibNickname(const UTF8 & aLogical)97 int LIB_ID::SetLibNickname( const UTF8& aLogical )
98 {
99     int offset = okLogical( aLogical );
100 
101     if( offset == -1 )
102         m_libraryName = aLogical;
103 
104     return offset;
105 }
106 
107 
SetLibItemName(const UTF8 & aLibItemName)108 int LIB_ID::SetLibItemName( const UTF8& aLibItemName )
109 {
110     m_itemName = aLibItemName;
111 
112     return -1;
113 }
114 
115 
Format() const116 UTF8 LIB_ID::Format() const
117 {
118     UTF8    ret;
119 
120     if( m_libraryName.size() )
121     {
122         ret += m_libraryName;
123         ret += ':';
124     }
125 
126     ret += m_itemName;
127 
128     return ret;
129 }
130 
131 
Format(const UTF8 & aLibraryName,const UTF8 & aLibItemName)132 UTF8 LIB_ID::Format( const UTF8& aLibraryName, const UTF8& aLibItemName )
133 {
134     UTF8    ret;
135     int     offset;
136 
137     if( aLibraryName.size() )
138     {
139         offset = okLogical( aLibraryName );
140 
141         if( offset != -1 )
142         {
143             THROW_PARSE_ERROR( _( "Illegal character found in logical library name" ),
144                                wxString::FromUTF8( aLibraryName.c_str() ), aLibraryName.c_str(),
145                                0, offset );
146         }
147 
148         ret += aLibraryName;
149         ret += ':';
150     }
151 
152     ret += aLibItemName;    // TODO: Add validity test.
153 
154     return ret;
155 }
156 
157 
compare(const LIB_ID & aLibId) const158 int LIB_ID::compare( const LIB_ID& aLibId ) const
159 {
160     // Don't bother comparing the same object.
161     if( this == &aLibId )
162         return 0;
163 
164     int retv = m_libraryName.compare( aLibId.m_libraryName );
165 
166     if( retv != 0 )
167         return retv;
168 
169     return m_itemName.compare( aLibId.m_itemName );
170 }
171 
172 
HasIllegalChars(const UTF8 & aLibItemName)173 int LIB_ID::HasIllegalChars( const UTF8& aLibItemName )
174 {
175     int offset = 0;
176 
177     for( auto ch : aLibItemName )
178     {
179         if( !isLegalChar( ch ) )
180             return offset;
181         else
182             ++offset;
183     }
184 
185     return -1;
186 }
187 
188 
FixIllegalChars(const UTF8 & aLibItemName,bool aLib)189 UTF8 LIB_ID::FixIllegalChars( const UTF8& aLibItemName, bool aLib )
190 {
191     UTF8 fixedName;
192 
193     for( UTF8::uni_iter chIt = aLibItemName.ubegin(); chIt < aLibItemName.uend(); ++chIt )
194     {
195         auto ch = *chIt;
196         if( aLib )
197             fixedName += isLegalLibraryNameChar( ch ) ? ch : '_';
198         else
199             fixedName += isLegalChar( ch ) ? ch : '_';
200     }
201 
202     return fixedName;
203 }
204 
205 
isLegalChar(unsigned aUniChar)206 bool LIB_ID::isLegalChar( unsigned aUniChar )
207 {
208     bool const space_allowed = true;
209     bool const illegal_filename_chars_allowed = false;
210 
211     if( aUniChar < ' ' )
212         return false;
213 
214     // This list of characters is also duplicated in validators.cpp and footprint.cpp
215     // TODO: Unify forbidden character lists
216     switch( aUniChar )
217     {
218     case ':':
219     case '\t':
220     case '\n':
221     case '\r':
222         return false;
223 
224     case '/':
225     case '\\':
226     case '<':
227     case '>':
228     case '"':
229         return illegal_filename_chars_allowed;
230 
231     case ' ':
232         return space_allowed;
233 
234     default:
235         return true;
236     }
237 }
238 
239 
FindIllegalLibraryNameChar(const UTF8 & aLibraryName)240 unsigned LIB_ID::FindIllegalLibraryNameChar( const UTF8& aLibraryName )
241 {
242     for( unsigned ch : aLibraryName )
243     {
244         if( !isLegalLibraryNameChar( ch ) )
245             return ch;
246     }
247 
248     return 0;
249 }
250 
251 
isLegalLibraryNameChar(unsigned aUniChar)252 bool LIB_ID::isLegalLibraryNameChar( unsigned aUniChar )
253 {
254     bool const space_allowed = true;
255 
256     if( aUniChar < ' ' )
257         return false;
258 
259     switch( aUniChar )
260     {
261     case '\\':
262     case ':':
263         return false;
264 
265     case ' ':
266         return space_allowed;
267 
268     default:
269         return true;
270     }
271 }
272 
273