/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2004-2015 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2008-2015 Wayne Stambaugh * Copyright (C) 2004-2016 KiCad Developers, see AUTHORS.txt for contributors. * * 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 2 * 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, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /** * @file class_library.cpp */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DUPLICATE_NAME_MSG \ _( "Library '%s' has duplicate entry name '%s'.\n" \ "This may cause some unexpected behavior when loading components into a schematic." ) PART_LIB::PART_LIB( int aType, const wxString& aFileName ) : // start @ != 0 so each additional library added // is immediately detectable, zero would not be. m_mod_hash( PART_LIBS::s_modify_generation ) { type = aType; isModified = false; timeStamp = 0; isCache = false; timeStamp = wxDateTime::Now(); versionMajor = 0; // Will be updated after reading the lib file versionMinor = 0; // Will be updated after reading the lib file fileName = aFileName; if( !fileName.IsOk() ) fileName = wxT( "unnamed.lib" ); } PART_LIB::~PART_LIB() { // When the library is destroyed, all of the alias objects on the heap should be deleted. for( LIB_ALIAS_MAP::iterator it = m_amap.begin(); it != m_amap.end(); ++it ) { wxLogTrace( traceSchLibMem, wxT( "Removing alias %s from library %s." ), GetChars( it->second->GetName() ), GetChars( GetLogicalName() ) ); LIB_PART* part = it->second->GetPart(); LIB_ALIAS* alias = it->second; delete alias; // When the last alias of a part is destroyed, the part is no longer required and it // too is destroyed. if( part && part->GetAliasCount() == 0 ) delete part; } m_amap.clear(); } void PART_LIB::GetEntryNames( wxArrayString& aNames ) { for( LIB_ALIAS_MAP::iterator it = m_amap.begin(); it!=m_amap.end(); it++ ) { aNames.Add( (*it).first ); } aNames.Sort(); } void PART_LIB::GetEntryTypePowerNames( wxArrayString& aNames ) { for( LIB_ALIAS_MAP::iterator it = m_amap.begin(); it!=m_amap.end(); it++ ) { LIB_ALIAS* alias = it->second; LIB_PART* root = alias->GetPart(); if( !root || !root->IsPower() ) continue; aNames.Add( (*it).first ); } aNames.Sort(); } bool PART_LIB::Conflicts( LIB_PART* aPart ) { wxCHECK_MSG( aPart != NULL, false, wxT( "Cannot test NULL component for conflicts in library " ) + GetName() ); for( size_t i=0; im_aliases.size(); i++ ) { LIB_ALIAS_MAP::iterator it = m_amap.find( aPart->m_aliases[i]->GetName() ); if( it != m_amap.end() ) return true; } return false; } LIB_ALIAS* PART_LIB::FindAlias( const wxString& aName ) { LIB_ALIAS_MAP::iterator it = m_amap.find( aName ); if( it != m_amap.end() ) return it->second; return NULL; } LIB_ALIAS* PART_LIB::GetFirstEntry() { if( m_amap.size() ) return m_amap.begin()->second; else return NULL; } LIB_PART* PART_LIB::FindPart( const wxString& aName ) { #if 0 && defined(DEBUG) if( !aName.Cmp( wxT( "TI_STELLARIS_BOOSTERPACK" ) ) ) { int breakhere = 1; (void) breakhere; } #endif if( LIB_ALIAS* alias = FindAlias( aName ) ) { return alias->GetPart(); } return NULL; } bool PART_LIB::HasPowerParts() { // return true if at least one power part is found in lib for( LIB_ALIAS_MAP::iterator it = m_amap.begin(); it!=m_amap.end(); it++ ) { LIB_ALIAS* alias = it->second; LIB_PART* root = alias->GetPart(); if( root && root->IsPower() ) return true; } return false; } bool PART_LIB::AddAlias( LIB_ALIAS* aAlias ) { wxASSERT( aAlias ); #if defined(DEBUG) && 0 if( !aAlias->GetName().Cmp( wxT( "TI_STELLARIS_BOOSTERPACK" ) ) ) { int breakhere = 1; (void) breakhere; } #endif LIB_ALIAS_MAP::iterator it = m_amap.find( aAlias->GetName() ); if( it != m_amap.end() ) { wxString msg; msg.Printf( _( "Cannot add duplicate alias '%s' to library '%s'." ), GetChars( aAlias->GetName() ), GetChars( fileName.GetName() ) ); return false; } wxString name = aAlias->GetName(); m_amap[ name ] = aAlias; isModified = true; ++m_mod_hash; return true; } bool PART_LIB::AddPart( LIB_PART* aPart ) { // Conflict detection: See if already existing aliases exist, // and if yes, ask user for continue or abort // Special case: if the library is the library cache of the project, // old aliases are always removed to avoid conflict, // and user is not prompted ) if( Conflicts( aPart ) && !IsCache() ) { wxFAIL_MSG( wxT( "Cannot add component <" ) + aPart->GetName() + wxT( "> to library <" ) + GetName() + wxT( "> due to name conflict." ) ); return false; } // add a clone, not the caller's copy LIB_PART* my_part = new LIB_PART( *aPart ); for( size_t i = 0; i < my_part->m_aliases.size(); i++ ) { wxString aliasname = my_part->m_aliases[i]->GetName(); if( LIB_ALIAS* alias = FindAlias( aliasname ) ) RemoveEntry( alias ); m_amap[ aliasname ] = my_part->m_aliases[i]; } isModified = true; ++m_mod_hash; return true; } LIB_ALIAS* PART_LIB::RemoveEntry( LIB_ALIAS* aEntry ) { wxCHECK_MSG( aEntry != NULL, NULL, wxT( "NULL pointer cannot be removed from library." ) ); LIB_ALIAS_MAP::iterator it = m_amap.find( aEntry->GetName() ); if( it == m_amap.end() ) return NULL; // If the entry pointer doesn't match the name it is mapped to in the library, we // have done something terribly wrong. wxCHECK_MSG( *it->second == aEntry, NULL, wxT( "Pointer mismatch while attempting to remove entry <" ) + aEntry->GetName() + wxT( "> from library <" ) + GetName() + wxT( ">." ) ); LIB_ALIAS* alias = aEntry; LIB_PART* part = alias->GetPart(); alias = part->RemoveAlias( alias ); if( !alias ) { delete part; if( m_amap.size() > 1 ) { LIB_ALIAS_MAP::iterator next = it; next++; if( next == m_amap.end() ) next = m_amap.begin(); alias = next->second; } } m_amap.erase( it ); isModified = true; ++m_mod_hash; return alias; } LIB_PART* PART_LIB::ReplacePart( LIB_PART* aOldPart, LIB_PART* aNewPart ) { wxASSERT( aOldPart != NULL ); wxASSERT( aNewPart != NULL ); /* Remove the old root component. The component will automatically be deleted * when all it's aliases are deleted. Do not place any code that accesses * aOldPart inside this loop that gets evaluated after the last alias is * removed in RemoveEntry(). Failure to heed this warning will result in a * segfault. */ size_t i = aOldPart->m_aliases.size(); while( i > 0 ) { i -= 1; RemoveEntry( aOldPart->m_aliases[ i ] ); } LIB_PART* my_part = new LIB_PART( *aNewPart, this ); // Add new aliases to library alias map. for( i = 0; i < my_part->m_aliases.size(); i++ ) { wxString aname = my_part->m_aliases[ i ]->GetName(); m_amap[ aname ] = my_part->m_aliases[ i ]; } isModified = true; ++m_mod_hash; return my_part; } LIB_ALIAS* PART_LIB::GetNextEntry( const wxString& aName ) { if( m_amap.empty() ) return NULL; LIB_ALIAS_MAP::iterator it = m_amap.find( aName ); it++; if( it == m_amap.end() ) it = m_amap.begin(); return it->second; } LIB_ALIAS* PART_LIB::GetPreviousEntry( const wxString& aName ) { if( m_amap.empty() ) return NULL; LIB_ALIAS_MAP::iterator it = m_amap.find( aName ); if( it == m_amap.begin() ) it = m_amap.end(); it--; return it->second; } bool PART_LIB::Load( wxString& aErrorMsg ) { if( fileName.GetFullPath().IsEmpty() ) { aErrorMsg = _( "The component library file name is not set." ); return false; } FILE* file = wxFopen( fileName.GetFullPath(), wxT( "rt" ) ); if( file == NULL ) { aErrorMsg = _( "The file could not be opened." ); return false; } FILE_LINE_READER reader( file, fileName.GetFullPath() ); if( !reader.ReadLine() ) { aErrorMsg = _( "The file is empty!" ); return false; } // There is no header if this is a symbol library. if( type == LIBRARY_TYPE_EESCHEMA ) { char* line = reader.Line(); header = FROM_UTF8( line ); wxStringTokenizer tkn( header ); /* * The file header (first line) in library versions 2.0 and lower * apparently started with EESchema-LIB. Sometime after 2.0, it * was changed to EESchema-LIBRARY. Therefore, the test for * EESchema-LIB will work in both cases. Don't change this unless * backwards compatibility is no longer required. */ if( !tkn.HasMoreTokens() || !tkn.GetNextToken().Upper().StartsWith(wxT( "EESCHEMA-LIB" ) ) ) { aErrorMsg = _( "The file is NOT an Eeschema library!" ); return false; } if( !tkn.HasMoreTokens() ) { aErrorMsg = _( "The file header is missing version and time stamp information." ); return false; } if( tkn.GetNextToken() != wxT( "Version" ) || !tkn.HasMoreTokens() ) { aErrorMsg = wxT( "The file header version information is invalid." ); return false; } long major, minor; wxStringTokenizer vers( tkn.GetNextToken(), wxT( "." ) ); if( !vers.HasMoreTokens() || !vers.GetNextToken().ToLong( &major ) || major < 1L || !vers.HasMoreTokens() || !vers.GetNextToken().ToLong( & minor ) || minor < 0L || minor > 99 ) { #if 0 // Note for developers: // Not sure this warning is very useful: old designs *must* be always loadable wxLogWarning( wxT( "The component library '%s' header version " "number is invalid.\n\nIn future versions of Eeschema this library may not " "load correctly. To resolve this problem open the library in the library " "editor and save it. If this library is the project cache library, save " "the current schematic." ), GetChars( GetName() ) ); #endif } else { versionMajor = (int) major; versionMinor = (int) minor; } } while( reader.ReadLine() ) { char * line = reader.Line(); if( type == LIBRARY_TYPE_EESCHEMA && strncasecmp( line, "$HEADER", 7 ) == 0 ) { if( !LoadHeader( reader ) ) { aErrorMsg = _( "An error occurred attempting to read the header." ); return false; } continue; } wxString msg; if( strncasecmp( line, "DEF", 3 ) == 0 ) { // Read one DEF/ENDDEF part entry from library: LIB_PART* part = new LIB_PART( wxEmptyString, this ); if( part->Load( reader, msg ) ) { // Check for duplicate entry names and warn the user about // the potential conflict. if( FindAlias( part->GetName() ) != NULL ) { wxLogWarning( DUPLICATE_NAME_MSG, GetChars( fileName.GetName() ), GetChars( part->GetName() ) ); } LoadAliases( part ); } else { wxLogWarning( _( "Library '%s' component load error %s." ), GetChars( fileName.GetName() ), GetChars( msg ) ); msg.Clear(); delete part; } } } ++m_mod_hash; return true; } void PART_LIB::LoadAliases( LIB_PART* aPart ) { wxCHECK_RET( aPart, wxT( "Cannot load aliases of NULL part. Bad programmer!" ) ); for( size_t i = 0; i < aPart->m_aliases.size(); i++ ) { if( FindAlias( aPart->m_aliases[i]->GetName() ) != NULL ) { wxLogError( DUPLICATE_NAME_MSG, GetChars( fileName.GetName() ), GetChars( aPart->m_aliases[i]->GetName() ) ); } wxString aname = aPart->m_aliases[i]->GetName(); m_amap[ aname ] = aPart->m_aliases[i]; } } bool PART_LIB::LoadHeader( LINE_READER& aLineReader ) { char* line, * text, * data; while( aLineReader.ReadLine() ) { line = (char*) aLineReader; text = strtok( line, " \t\r\n" ); data = strtok( NULL, " \t\r\n" ); if( strcasecmp( text, "TimeStamp" ) == 0 ) timeStamp = atol( data ); if( strcasecmp( text, "$ENDHEADER" ) == 0 ) return true; } return false; } bool PART_LIB::LoadDocs( wxString& aErrorMsg ) { int lineNumber = 0; char line[8000], * name, * text; LIB_ALIAS* entry; FILE* file; wxFileName fn = fileName; fn.SetExt( DOC_EXT ); file = wxFopen( fn.GetFullPath(), wxT( "rt" ) ); if( file == NULL ) { aErrorMsg.Printf( _( "Could not open component document library file '%s'." ), GetChars( fn.GetFullPath() ) ); return false; } if( GetLine( file, line, &lineNumber, sizeof(line) ) == NULL ) { aErrorMsg.Printf( _( "Part document library file '%s' is empty." ), GetChars( fn.GetFullPath() ) ); fclose( file ); return false; } if( strncasecmp( line, DOCFILE_IDENT, 10 ) != 0 ) { aErrorMsg.Printf( _( "File '%s' is not a valid component library document file." ), GetChars( fn.GetFullPath() ) ); fclose( file ); return false; } while( GetLine( file, line, &lineNumber, sizeof(line) ) ) { if( strncmp( line, "$CMP", 4 ) != 0 ) { aErrorMsg.Printf( wxT( "$CMP command expected in line %d, aborted." ), lineNumber ); fclose( file ); return false; } // Read one $CMP/$ENDCMP part entry from library: name = strtok( line + 5, "\n\r" ); wxString cmpname = FROM_UTF8( name ); entry = FindAlias( cmpname ); while( GetLine( file, line, &lineNumber, sizeof(line) ) ) { if( strncmp( line, "$ENDCMP", 7 ) == 0 ) break; text = strtok( line + 2, "\n\r" ); if( entry ) { switch( line[0] ) { case 'D': entry->SetDescription( FROM_UTF8( text ) ); break; case 'K': entry->SetKeyWords( FROM_UTF8( text ) ); break; case 'F': entry->SetDocFileName( FROM_UTF8( text ) ); break; } } } } fclose( file ); return true; } bool PART_LIB::Save( OUTPUTFORMATTER& aFormatter ) { if( isModified ) { timeStamp = GetNewTimeStamp(); isModified = false; } bool success = true; try { SaveHeader( aFormatter ); for( LIB_ALIAS_MAP::iterator it=m_amap.begin(); it!=m_amap.end(); it++ ) { if( !it->second->IsRoot() ) continue; it->second->GetPart()->Save( aFormatter ); } aFormatter.Print( 0, "#\n#End Library\n" ); } catch( const IO_ERROR& ioe ) { success = false; } return success; } bool PART_LIB::SaveDocs( OUTPUTFORMATTER& aFormatter ) { bool success = true; try { aFormatter.Print( 0, "%s\n", DOCFILE_IDENT ); for( LIB_ALIAS_MAP::iterator it=m_amap.begin(); it!=m_amap.end(); it++ ) { if( !it->second->SaveDoc( aFormatter ) ) success = false; } aFormatter.Print( 0, "#\n#End Doc Library\n" ); } catch( const IO_ERROR& ioe ) { success = false; } return success; } bool PART_LIB::SaveHeader( OUTPUTFORMATTER& aFormatter ) { aFormatter.Print( 0, "%s %d.%d\n", LIBFILE_IDENT, LIB_VERSION_MAJOR, LIB_VERSION_MINOR ); aFormatter.Print( 0, "#encoding utf-8\n"); #if 0 aFormatter.Print( 0, "$HEADER\n" ); aFormatter.Print( 0, "TimeStamp %8.8lX\n", m_TimeStamp ); aFormatter.Print( 0, "Parts %d\n", m_amap.size() ); aFormatter.Print( 0, "$ENDHEADER\n" ) != 1 ); #endif return true; } PART_LIB* PART_LIB::LoadLibrary( const wxString& aFileName ) throw( IO_ERROR, boost::bad_pointer ) { std::unique_ptr lib( new PART_LIB( LIBRARY_TYPE_EESCHEMA, aFileName ) ); wxBusyCursor ShowWait; // Do we want UI elements in PART_LIB? wxString errorMsg; #ifdef KICAD_USE_SCH_IO_MANAGER SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) ); wxArrayString tmp; pi->EnumerateSymbolLib( tmp, aFileName ); pi->TransferCache( *lib.get() ); #else if( !lib->Load( errorMsg ) ) THROW_IO_ERROR( errorMsg ); if( USE_OLD_DOC_FILE_FORMAT( lib->versionMajor, lib->versionMinor ) ) { #if 1 // not fatal if error here. lib->LoadDocs( errorMsg ); #else if( !lib->LoadDocs( errorMsg ) ) THROW_IO_ERROR( errorMsg ); #endif } #endif PART_LIB* ret = lib.release(); return ret; } PART_LIB* PART_LIBS::AddLibrary( const wxString& aFileName ) throw( IO_ERROR, boost::bad_pointer ) { PART_LIB* lib; #if 1 wxFileName fn = aFileName; // Don't reload the library if it is already loaded. lib = FindLibrary( fn.GetName() ); if( lib ) return lib; #endif lib = PART_LIB::LoadLibrary( aFileName ); push_back( lib ); return lib; } PART_LIB* PART_LIBS::AddLibrary( const wxString& aFileName, PART_LIBS::iterator& aIterator ) throw( IO_ERROR, boost::bad_pointer ) { #if 1 // Don't reload the library if it is already loaded. wxFileName fn( aFileName ); PART_LIB* lib = FindLibrary( fn.GetName() ); if( lib ) return lib; #endif lib = PART_LIB::LoadLibrary( aFileName ); if( aIterator >= begin() && aIterator < end() ) insert( aIterator, lib ); else push_back( lib ); return lib; } void PART_LIBS::RemoveLibrary( const wxString& aName ) { if( aName.IsEmpty() ) return; for( PART_LIBS::iterator it = begin(); it < end(); ++it ) { if( it->GetName().CmpNoCase( aName ) == 0 ) { erase( it ); return; } } } PART_LIB* PART_LIBS::FindLibrary( const wxString& aName ) { for( PART_LIBS::iterator it = begin(); it!=end(); ++it ) { if( it->GetName() == aName ) return &*it; } return NULL; } wxArrayString PART_LIBS::GetLibraryNames( bool aSorted ) { wxArrayString cacheNames; wxArrayString names; for( PART_LIB& lib : *this ) { if( lib.IsCache() && aSorted ) cacheNames.Add( lib.GetName() ); else names.Add( lib.GetName() ); } // Even sorted, the cache library is always at the end of the list. if( aSorted ) names.Sort(); for( unsigned int i = 0; i& aCandidates, const wxString& aEntryName, const wxString& aLibraryName ) { for( PART_LIB& lib : *this ) { if( !!aLibraryName && lib.GetName() != aLibraryName ) continue; LIB_ALIAS* entry = lib.GetFirstEntry(); if( ! entry ) continue; wxString first_entry_name = entry->GetName(); wxString entry_name = first_entry_name; for( ;; ) { if( entry_name.CmpNoCase( aEntryName ) == 0 ) aCandidates.push_back( entry ); entry = lib.GetNextEntry( entry_name ); entry_name = entry->GetName(); if( first_entry_name == entry_name ) break; } } } int PART_LIBS::s_modify_generation = 1; // starts at 1 and goes up int PART_LIBS::GetModifyHash() { int hash = 0; for( PART_LIBS::const_iterator it = begin(); it != end(); ++it ) { hash += it->m_mod_hash; } return hash; } void PART_LIBS::LibNamesAndPaths( PROJECT* aProject, bool doSave, wxString* aPaths, wxArrayString* aNames ) throw( IO_ERROR, boost::bad_pointer ) { wxString pro = aProject->GetProjectFullName(); PARAM_CFG_ARRAY ca; if( aPaths ) ca.push_back( new PARAM_CFG_FILENAME( wxT( "LibDir" ), aPaths ) ); if( aNames ) ca.push_back( new PARAM_CFG_LIBNAME_LIST( wxT( "LibName" ), aNames, GROUP_SCH_LIBS ) ); if( doSave ) { aProject->ConfigSave( Kiface().KifaceSearch(), GROUP_SCH, ca ); /* { wxString msg = wxString::Format( _( "Unable save project's '%s' file" ), GetChars( pro ) ); THROW_IO_ERROR( msg ); } */ } else { if( !aProject->ConfigLoad( Kiface().KifaceSearch(), GROUP_SCH, ca ) ) { wxString msg = wxString::Format( _( "Unable to load project's '%s' file" ), GetChars( pro ) ); THROW_IO_ERROR( msg ); } } } const wxString PART_LIBS::CacheName( const wxString& aFullProjectFilename ) { /* until apr 2009 the project cache lib was named: .cache.lib, * and after: -cache.lib. So if the -cache.lib is not found, * the old file will be renamed and returned. */ wxFileName new_name = aFullProjectFilename; new_name.SetName( new_name.GetName() + wxT( "-cache" ) ); new_name.SetExt( SchematicLibraryFileExtension ); if( new_name.FileExists() ) return new_name.GetFullPath(); else { wxFileName old_name = aFullProjectFilename; old_name.SetExt( wxT( "cache.lib" ) ); if( old_name.FileExists() ) { wxRenameFile( old_name.GetFullPath(), new_name.GetFullPath() ); return new_name.GetFullPath(); } } return wxEmptyString; } void PART_LIBS::LoadAllLibraries( PROJECT* aProject ) throw( IO_ERROR, boost::bad_pointer ) { wxFileName fn; wxString filename; wxString libs_not_found; SEARCH_STACK* lib_search = aProject->SchSearchS(); #if defined(DEBUG) && 1 lib_search->Show( __func__ ); #endif wxArrayString lib_names; LibNamesAndPaths( aProject, false, NULL, &lib_names ); // If the list is empty, force loading the standard power symbol library. if( !lib_names.GetCount() ) lib_names.Add( wxT( "power" ) ); wxASSERT( !size() ); // expect to load into "this" empty container. for( unsigned i = 0; i < lib_names.GetCount(); ++i ) { fn.Clear(); fn.SetName( lib_names[i] ); fn.SetExt( SchematicLibraryFileExtension ); // Skip if the file name is not valid.. if( !fn.IsOk() ) continue; if( !fn.FileExists() ) { filename = lib_search->FindValidPath( fn.GetFullPath() ); if( !filename ) { libs_not_found += fn.GetName(); libs_not_found += wxT( '\n' ); continue; } } else { filename = fn.GetFullPath(); } try { AddLibrary( filename ); } catch( const IO_ERROR& ioe ) { wxString msg = wxString::Format( _( "Part library '%s' failed to load. Error:\n" "%s" ), GetChars( filename ), GetChars( ioe.What() ) ); wxLogError( msg ); } } // add the special cache library. wxString cache_name = CacheName( aProject->GetProjectFullName() ); PART_LIB* cache_lib; if( !!cache_name ) { try { cache_lib = AddLibrary( cache_name ); if( cache_lib ) cache_lib->SetCache(); } catch( const IO_ERROR& ioe ) { wxString msg = wxString::Format( _( "Part library '%s' failed to load.\nError: %s" ), GetChars( cache_name ), GetChars( ioe.What() ) ); THROW_IO_ERROR( msg ); } } // Print the libraries not found if( !!libs_not_found ) { // Use a different exception type so catch()er can route to proper use // of the HTML_MESSAGE_BOX. THROW_PARSE_ERROR( wxEmptyString, UTF8( __func__ ), UTF8( libs_not_found ), 0, 0 ); } #if defined(DEBUG) && 1 printf( "%s: lib_names:\n", __func__ ); for( PART_LIBS::const_iterator it = begin(); it < end(); ++it ) printf( " %s\n", TO_UTF8( it->GetName() ) ); #endif }