/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright The KiCad Developers, see AUTHORS.txt for contributors. * @author Jon Evans * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include #include #include #include #include #include #include #include #include const wxString LIBRARY_TABLE_ROW::TABLE_TYPE_NAME = wxT( "Table" ); bool LIBRARY_TABLE_ROW::operator==( const LIBRARY_TABLE_ROW& aOther ) const { return m_scope == aOther.m_scope && m_nickname == aOther.m_nickname && m_uri == aOther.m_uri && m_type == aOther.m_type && m_options == aOther.m_options && m_description == aOther.m_description && m_disabled == aOther.m_disabled && m_hidden == aOther.m_hidden; } std::map LIBRARY_TABLE_ROW::GetOptionsMap() const { return LIBRARY_TABLE::ParseOptions( TO_UTF8( m_options ) ); } LIBRARY_TABLE::LIBRARY_TABLE( const wxFileName &aPath, LIBRARY_TABLE_SCOPE aScope ) : m_scope( aScope ) { LIBRARY_TABLE_PARSER parser; wxFileName fn( aPath ); WX_FILENAME::ResolvePossibleSymlinks( fn ); m_path = fn.GetAbsolutePath(); if( !fn.FileExists() ) { m_ok = false; m_errorDescription = wxString::Format( _( "The library table path '%s' does not exist" ), fn.GetFullPath() ); return; } tl::expected ir = parser.Parse( m_path.ToStdString() ); if( ir.has_value() ) { m_ok = initFromIR( *ir ); } else { m_ok = false; m_errorDescription = ir.error().description; } } /** * Note: @param aFromClipboard isn't actually used, but might keep people from calling this with a string * filepath, which isn't going to do what they expected. */ LIBRARY_TABLE::LIBRARY_TABLE( bool aFromClipboard, const wxString &aBuffer, LIBRARY_TABLE_SCOPE aScope ) : m_path( wxEmptyString ), m_scope( aScope ) { LIBRARY_TABLE_PARSER parser; tl::expected ir = parser.ParseBuffer( aBuffer.ToStdString() ); if( ir.has_value() ) { m_ok = initFromIR( *ir ); } else { m_ok = false; m_errorDescription = ir.error().description; } } bool LIBRARY_TABLE::operator==( const LIBRARY_TABLE& aOther ) const { return m_path == aOther.m_path && m_scope == aOther.m_scope && m_type == aOther.m_type && m_version == aOther.m_version && m_rows == aOther.m_rows; } bool LIBRARY_TABLE::initFromIR( const LIBRARY_TABLE_IR& aIR ) { m_type = aIR.type; try { m_version = boost::lexical_cast( aIR.version ); } catch( const boost::bad_lexical_cast & ) { m_version = std::nullopt; } for( const LIBRARY_TABLE_ROW_IR& row : aIR.rows ) addRowFromIR( row ); return true; } bool LIBRARY_TABLE::addRowFromIR( const LIBRARY_TABLE_ROW_IR& aIR ) { LIBRARY_TABLE_ROW row; row.m_nickname = wxString::FromUTF8( aIR.nickname ); row.m_uri = wxString::FromUTF8( aIR.uri ); row.m_type = wxString::FromUTF8( aIR.type ); row.m_options = wxString::FromUTF8( aIR.options ); row.m_description = wxString::FromUTF8( aIR.description ); row.m_hidden = aIR.hidden; row.m_disabled = aIR.disabled; row.m_ok = true; row.m_scope = m_scope; m_rows.emplace_back( row ); return true; } void LIBRARY_TABLE::Format( OUTPUTFORMATTER* aOutput ) const { static const std::map types = { { LIBRARY_TABLE_TYPE::SYMBOL, "sym_lib_table" }, { LIBRARY_TABLE_TYPE::FOOTPRINT, "fp_lib_table" }, { LIBRARY_TABLE_TYPE::DESIGN_BLOCK, "design_block_lib_table" } }; wxCHECK( types.contains( Type() ), /* void */ ); XNODE self( wxXML_ELEMENT_NODE, types.at( Type() ) ); // TODO(JE) library tables - version management? self.AddAttribute( "version", 7 ); for( const LIBRARY_TABLE_ROW& row : Rows() ) { wxString uri = row.URI(); uri.Replace( '\\', '/' ); XNODE* rowNode = new XNODE( wxXML_ELEMENT_NODE, "lib" ); rowNode->AddAttribute( "name", row.Nickname() ); rowNode->AddAttribute( "type", row.Type() ); rowNode->AddAttribute( "uri", uri ); rowNode->AddAttribute( "options", row.Options() ); rowNode->AddAttribute( "descr", row.Description() ); if( row.Disabled() ) rowNode->AddChild( new XNODE( wxXML_ELEMENT_NODE, "disabled" ) ); if( row.Hidden() ) rowNode->AddChild( new XNODE( wxXML_ELEMENT_NODE, "hidden" ) ); self.AddChild( rowNode ); } self.Format( aOutput ); } LIBRARY_TABLE_ROW LIBRARY_TABLE::MakeRow() const { LIBRARY_TABLE_ROW row = {}; row.SetScope( m_scope ); row.SetOk(); return row; } LIBRARY_TABLE_ROW& LIBRARY_TABLE::InsertRow() { return Rows().emplace_back( MakeRow() ); } bool LIBRARY_TABLE::HasRow( const wxString& aNickname ) const { for( const LIBRARY_TABLE_ROW& row : m_rows ) { if( row.Nickname() == aNickname ) return true; } return false; } bool LIBRARY_TABLE::HasRowWithURI( const wxString& aUri, const PROJECT& aProject, bool aSubstituted ) const { for( const LIBRARY_TABLE_ROW& row : m_rows ) { if( !aSubstituted && row.URI() == aUri ) return true; if( aSubstituted && LIBRARY_MANAGER::ExpandURI( row.URI(), aProject ) == aUri ) return true; } return false; } std::optional LIBRARY_TABLE::Row( const wxString& aNickname ) { for( LIBRARY_TABLE_ROW& row : m_rows ) { if( row.Nickname() == aNickname ) return &row; } return std::nullopt; } std::optional LIBRARY_TABLE::Row( const wxString& aNickname ) const { for( const LIBRARY_TABLE_ROW& row : m_rows ) { if( row.Nickname() == aNickname ) return &row; } return std::nullopt; } LIBRARY_RESULT LIBRARY_TABLE::Save() { wxLogTrace( traceLibraries, "Saving %s", Path() ); wxFileName fn( Path() ); // This should already be normalized, but just in case... fn.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS ); try { PRETTIFIED_FILE_OUTPUTFORMATTER formatter( fn.GetFullPath(), KICAD_FORMAT::FORMAT_MODE::LIBRARY_TABLE ); Format( &formatter ); } catch( IO_ERROR& e ) { wxLogTrace( traceLibraries, "Exception while saving: %s", e.What() ); return tl::unexpected( LIBRARY_ERROR( e.What() ) ); } return LIBRARY_RESULT(); } #define OPT_SEP '|' ///< options separator character std::map LIBRARY_TABLE::ParseOptions( const std::string& aOptionsList ) { std::map props; if( aOptionsList.size() ) { const char* cp = &aOptionsList[0]; const char* end = cp + aOptionsList.size(); std::string pair; // Parse all name=value pairs while( cp < end ) { pair.clear(); // Skip leading white space. while( cp < end && isspace( *cp ) ) ++cp; // Find the end of pair/field while( cp < end ) { if( *cp == '\\' && cp + 1 < end && cp[1] == OPT_SEP ) { ++cp; // skip the escape pair += *cp++; // add the separator } else if( *cp == OPT_SEP ) { ++cp; // skip the separator break; // process the pair } else { pair += *cp++; } } // stash the pair if( pair.size() ) { // first equals sign separates 'name' and 'value'. size_t eqNdx = pair.find( '=' ); if( eqNdx != pair.npos ) { std::string name = pair.substr( 0, eqNdx ); std::string value = pair.substr( eqNdx + 1 ); props[name] = value; } else { props[pair] = ""; // property is present, but with no value. } } } } return props; } UTF8 LIBRARY_TABLE::FormatOptions( const std::map* aProperties ) { UTF8 ret; if( aProperties ) { for( std::map::const_iterator it = aProperties->begin(); it != aProperties->end(); ++it ) { const std::string& name = it->first; const UTF8& value = it->second; if( ret.size() ) ret += OPT_SEP; ret += name; // the separation between name and value is '=' if( value.size() ) { ret += '='; for( std::string::const_iterator si = value.begin(); si != value.end(); ++si ) { // escape any separator in the value. if( *si == OPT_SEP ) ret += '\\'; ret += *si; } } } } return ret; }