KiCad PCB EDA Suite
fp_lib_table.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) 2010-2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
5  * Copyright (C) 2012-2016 Wayne Stambaugh <stambaughw@gmail.com>
6  * Copyright (C) 2012-2017 KiCad Developers, see AUTHORS.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 
27 #include <fctsys.h>
28 #include <common.h>
29 #include <kiface_i.h>
30 #include <footprint_info.h>
31 #include <lib_id.h>
32 #include <lib_table_lexer.h>
33 #include <fp_lib_table.h>
34 #include <class_module.h>
35 
36 #define OPT_SEP '|'
37 
38 using namespace LIB_TABLE_T;
39 
40 
41 static const wxChar global_tbl_name[] = wxT( "fp-lib-table" );
42 
43 
45 {
46  return LIB_TABLE_ROW::operator == ( aRow ) && type == aRow.type;
47 }
48 
49 
50 void FP_LIB_TABLE_ROW::SetType( const wxString& aType )
51 {
52  type = IO_MGR::EnumFromStr( aType );
53 
54  if( IO_MGR::PCB_FILE_T( -1 ) == type )
55  type = IO_MGR::KICAD_SEXP;
56 }
57 
58 
60  LIB_TABLE( aFallBackTable )
61 {
62  // not copying fall back, simply search aFallBackTable separately
63  // if "nickName not found".
64 }
65 
66 
68 {
69  T tok;
70  wxString errMsg; // to collect error messages
71 
72  // This table may be nested within a larger s-expression, or not.
73  // Allow for parser of that optional containing s-epression to have looked ahead.
74  if( in->CurTok() != T_fp_lib_table )
75  {
76  in->NeedLEFT();
77 
78  if( ( tok = in->NextTok() ) != T_fp_lib_table )
80  }
81 
82  while( ( tok = in->NextTok() ) != T_RIGHT )
83  {
84  std::unique_ptr< FP_LIB_TABLE_ROW > row( new FP_LIB_TABLE_ROW );
85 
86  if( tok == T_EOF )
87  in->Expecting( T_RIGHT );
88 
89  if( tok != T_LEFT )
90  in->Expecting( T_LEFT );
91 
92  // in case there is a "row integrity" error, tell where later.
93  int lineNum = in->CurLineNumber();
94 
95  if( ( tok = in->NextTok() ) != T_lib )
96  in->Expecting( T_lib );
97 
98  // (name NICKNAME)
99  in->NeedLEFT();
100 
101  if( ( tok = in->NextTok() ) != T_name )
102  in->Expecting( T_name );
103 
104  in->NeedSYMBOLorNUMBER();
105 
106  row->SetNickName( in->FromUTF8() );
107 
108  in->NeedRIGHT();
109 
110  // After (name), remaining (lib) elements are order independent, and in
111  // some cases optional.
112  bool sawType = false;
113  bool sawOpts = false;
114  bool sawDesc = false;
115  bool sawUri = false;
116  bool sawDisabled = false;
117 
118  while( ( tok = in->NextTok() ) != T_RIGHT )
119  {
120  if( tok == T_EOF )
121  in->Unexpected( T_EOF );
122 
123  if( tok != T_LEFT )
124  in->Expecting( T_LEFT );
125 
126  tok = in->NeedSYMBOLorNUMBER();
127 
128  switch( tok )
129  {
130  case T_uri:
131  if( sawUri )
132  in->Duplicate( tok );
133  sawUri = true;
134  in->NeedSYMBOLorNUMBER();
135  row->SetFullURI( in->FromUTF8() );
136  break;
137 
138  case T_type:
139  if( sawType )
140  in->Duplicate( tok );
141  sawType = true;
142  in->NeedSYMBOLorNUMBER();
143  row->SetType( in->FromUTF8() );
144  break;
145 
146  case T_options:
147  if( sawOpts )
148  in->Duplicate( tok );
149  sawOpts = true;
150  in->NeedSYMBOLorNUMBER();
151  row->SetOptions( in->FromUTF8() );
152  break;
153 
154  case T_descr:
155  if( sawDesc )
156  in->Duplicate( tok );
157  sawDesc = true;
158  in->NeedSYMBOLorNUMBER();
159  row->SetDescr( in->FromUTF8() );
160  break;
161 
162  case T_disabled:
163  if( sawDisabled )
164  in->Duplicate( tok );
165  sawDisabled = true;
166  row->SetEnabled( false );
167  break;
168 
169  default:
170  in->Unexpected( tok );
171  }
172 
173  in->NeedRIGHT();
174  }
175 
176  if( !sawType )
177  in->Expecting( T_type );
178 
179  if( !sawUri )
180  in->Expecting( T_uri );
181 
182  // all nickNames within this table fragment must be unique, so we do not
183  // use doReplace in InsertRow(). (However a fallBack table can have a
184  // conflicting nickName and ours will supercede that one since in
185  // FindLib() we search this table before any fall back.)
186  wxString nickname = row->GetNickName(); // store it to be able to used it
187  // after row deletion if an error occurs
188  LIB_TABLE_ROW* tmp = row.release();
189 
190  if( !InsertRow( tmp ) )
191  {
192  delete tmp; // The table did not take ownership of the row.
193 
194  wxString msg = wxString::Format(
195  _( "Duplicate library nickname '%s' found in footprint library "
196  "table file line %d" ), GetChars( nickname ), lineNum );
197 
198  if( !errMsg.IsEmpty() )
199  errMsg << '\n';
200 
201  errMsg << msg;
202  }
203  }
204 
205  if( !errMsg.IsEmpty() )
206  THROW_IO_ERROR( errMsg );
207 }
208 
209 
210 bool FP_LIB_TABLE::operator==( const FP_LIB_TABLE& aFpTable ) const
211 {
212  if( rows.size() == aFpTable.rows.size() )
213  {
214  for( unsigned i = 0; i < rows.size(); ++i )
215  {
216  if( (FP_LIB_TABLE_ROW&)rows[i] != (FP_LIB_TABLE_ROW&)aFpTable.rows[i] )
217  return false;
218  }
219 
220  return true;
221  }
222 
223  return false;
224 }
225 
226 
227 void FP_LIB_TABLE::Format( OUTPUTFORMATTER* aOutput, int aIndentLevel ) const
228 {
229  aOutput->Print( aIndentLevel, "(fp_lib_table\n" );
230 
231  for( LIB_TABLE_ROWS_CITER it = rows.begin(); it != rows.end(); ++it )
232  it->Format( aOutput, aIndentLevel+1 );
233 
234  aOutput->Print( aIndentLevel, ")\n" );
235 }
236 
237 
238 void FP_LIB_TABLE::FootprintEnumerate( wxArrayString& aFootprintNames, const wxString& aNickname )
239 {
240  const FP_LIB_TABLE_ROW* row = FindRow( aNickname );
241  wxASSERT( (PLUGIN*) row->plugin );
242  row->plugin->FootprintEnumerate( aFootprintNames, row->GetFullURI( true ),
243  row->GetProperties() );
244 }
245 
246 
247 void FP_LIB_TABLE::PrefetchLib( const wxString& aNickname )
248 {
249  const FP_LIB_TABLE_ROW* row = FindRow( aNickname );
250  wxASSERT( (PLUGIN*) row->plugin );
251  row->plugin->PrefetchLib( row->GetFullURI( true ), row->GetProperties() );
252 }
253 
254 
255 const FP_LIB_TABLE_ROW* FP_LIB_TABLE::FindRow( const wxString& aNickname )
256 {
257  FP_LIB_TABLE_ROW* row = dynamic_cast< FP_LIB_TABLE_ROW* >( findRow( aNickname ) );
258 
259  if( !row )
260  {
261  wxString msg = wxString::Format(
262  _( "fp-lib-table files contain no library with nickname '%s'" ),
263  GetChars( aNickname ) );
264 
265  THROW_IO_ERROR( msg );
266  }
267 
268  // We've been 'lazy' up until now, but it cannot be deferred any longer,
269  // instantiate a PLUGIN of the proper kind if it is not already in this
270  // FP_LIB_TABLE_ROW.
271  if( !row->plugin )
272  row->setPlugin( IO_MGR::PluginFind( row->type ) );
273 
274  return row;
275 }
276 
277 
278 MODULE* FP_LIB_TABLE::FootprintLoad( const wxString& aNickname, const wxString& aFootprintName )
279 {
280  const FP_LIB_TABLE_ROW* row = FindRow( aNickname );
281  wxASSERT( (PLUGIN*) row->plugin );
282 
283  MODULE* ret = row->plugin->FootprintLoad( row->GetFullURI( true ), aFootprintName,
284  row->GetProperties() );
285 
286  // The library cannot know its own name, because it might have been renamed or moved.
287  // Therefore footprints cannot know their own library nickname when residing in
288  // a footprint library.
289  // Only at this API layer can we tell the footprint about its actual library nickname.
290  if( ret )
291  {
292  // remove "const"-ness, I really do want to set nickname without
293  // having to copy the LIB_ID and its two strings, twice each.
294  LIB_ID& fpid = (LIB_ID&) ret->GetFPID();
295 
296  // Catch any misbehaving plugin, which should be setting internal footprint name properly:
297  wxASSERT( aFootprintName == fpid.GetLibItemName().wx_str() );
298 
299  // and clearing nickname
300  wxASSERT( !fpid.GetLibNickname().size() );
301 
302  fpid.SetLibNickname( row->GetNickName() );
303  }
304 
305  return ret;
306 }
307 
308 
310  const MODULE* aFootprint, bool aOverwrite )
311 {
312  const FP_LIB_TABLE_ROW* row = FindRow( aNickname );
313  wxASSERT( (PLUGIN*) row->plugin );
314 
315  if( !aOverwrite )
316  {
317  // Try loading the footprint to see if it already exists, caller wants overwrite
318  // protection, which is atypical, not the default.
319 
320  wxString fpname = aFootprint->GetFPID().GetLibItemName();
321 
322  std::unique_ptr<MODULE> footprint( row->plugin->FootprintLoad( row->GetFullURI( true ),
323  fpname, row->GetProperties() ) );
324 
325  if( footprint.get() )
326  return SAVE_SKIPPED;
327  }
328 
329  row->plugin->FootprintSave( row->GetFullURI( true ), aFootprint, row->GetProperties() );
330 
331  return SAVE_OK;
332 }
333 
334 
335 void FP_LIB_TABLE::FootprintDelete( const wxString& aNickname, const wxString& aFootprintName )
336 {
337  const FP_LIB_TABLE_ROW* row = FindRow( aNickname );
338  wxASSERT( (PLUGIN*) row->plugin );
339  return row->plugin->FootprintDelete( row->GetFullURI( true ), aFootprintName,
340  row->GetProperties() );
341 }
342 
343 
344 bool FP_LIB_TABLE::IsFootprintLibWritable( const wxString& aNickname )
345 {
346  const FP_LIB_TABLE_ROW* row = FindRow( aNickname );
347  wxASSERT( (PLUGIN*) row->plugin );
348  return row->plugin->IsFootprintLibWritable( row->GetFullURI( true ) );
349 }
350 
351 
352 void FP_LIB_TABLE::FootprintLibDelete( const wxString& aNickname )
353 {
354  const FP_LIB_TABLE_ROW* row = FindRow( aNickname );
355  wxASSERT( (PLUGIN*) row->plugin );
356  row->plugin->FootprintLibDelete( row->GetFullURI( true ), row->GetProperties() );
357 }
358 
359 
360 void FP_LIB_TABLE::FootprintLibCreate( const wxString& aNickname )
361 {
362  const FP_LIB_TABLE_ROW* row = FindRow( aNickname );
363  wxASSERT( (PLUGIN*) row->plugin );
364  row->plugin->FootprintLibCreate( row->GetFullURI( true ), row->GetProperties() );
365 }
366 
367 
369 {
370  wxString nickname = aFootprintId.GetLibNickname();
371  wxString fpname = aFootprintId.GetLibItemName();
372 
373  if( nickname.size() )
374  {
375  return FootprintLoad( nickname, fpname );
376  }
377 
378  // nickname is empty, sequentially search (alphabetically) all libs/nicks for first match:
379  else
380  {
381  std::vector<wxString> nicks = GetLogicalLibs();
382 
383  // Search each library going through libraries alphabetically.
384  for( unsigned i = 0; i < nicks.size(); ++i )
385  {
386  // FootprintLoad() returns NULL on not found, does not throw exception
387  // unless there's an IO_ERROR.
388  MODULE* ret = FootprintLoad( nicks[i], fpname );
389 
390  if( ret )
391  return ret;
392  }
393 
394  return NULL;
395  }
396 }
397 
398 
400 {
401  return "KISYSMOD";
402 }
403 
404 
406 {
407  bool tableExists = true;
408  wxFileName fn = GetGlobalTableFileName();
409 
410  if( !fn.FileExists() )
411  {
412  tableExists = false;
413 
414  if( !fn.DirExists() && !fn.Mkdir( 0x777, wxPATH_MKDIR_FULL ) )
415  {
416  THROW_IO_ERROR( wxString::Format( _( "Cannot create global library table path '%s'." ),
417  GetChars( fn.GetPath() ) ) );
418  }
419 
420  // Attempt to copy the default global file table from the KiCad
421  // template folder to the user's home configuration path.
422  wxString fileName = Kiface().KifaceSearch().FindValidPath( global_tbl_name );
423 
424  // The fallback is to create an empty global footprint table for the user to populate.
425  if( fileName.IsEmpty() || !::wxCopyFile( fileName, fn.GetFullPath(), false ) )
426  {
427  FP_LIB_TABLE emptyTable;
428 
429  emptyTable.Save( fn.GetFullPath() );
430  }
431  }
432 
433  aTable.Load( fn.GetFullPath() );
434 
435  return tableExists;
436 }
437 
438 
440 {
441  wxFileName fn;
442 
443  fn.SetPath( GetKicadConfigPath() );
444  fn.SetName( global_tbl_name );
445 
446  return fn.GetFullPath();
447 }
void setPlugin(PLUGIN *aPlugin)
Definition: fp_lib_table.h:93
static const wxChar global_tbl_name[]
bool IsFootprintLibWritable(const wxString &aNickname)
Function IsFootprintLibWritable.
LIB_TABLE_T::T CurTok()
Function CurTok returns whatever NextTok() returned the last time it was called.
Hold a record identifying a library accessed by the appropriate plug in object in the LIB_TABLE...
virtual void FootprintEnumerate(wxArrayString &aFootprintNames, const wxString &aLibraryPath, const PROPERTIES *aProperties=NULL)
Return a list of footprint names contained within the library at aLibraryPath.
Definition: plugin.cpp:61
virtual void PrefetchLib(const wxString &aLibraryPath, const PROPERTIES *aProperties=NULL)
Function PrefetchLib If possible, prefetches the specified library (e.g.
Definition: plugin.cpp:69
Class FP_LIB_TABLE_ROW.
Definition: fp_lib_table.h:42
const PROPERTIES * GetProperties() const
Return the constant PROPERTIES for this library (LIB_TABLE_ROW).
bool InsertRow(LIB_TABLE_ROW *aRow, bool doReplace=false)
Adds aRow if it does not already exist or if doReplace is true.
T
enum T contains all this lexer's tokens.
MODULE * FootprintLoad(const wxString &aNickname, const wxString &aFootprintName)
Function FootprintLoad.
virtual void FootprintSave(const wxString &aLibraryPath, const MODULE *aFootprint, const PROPERTIES *aProperties=NULL)
Function FootprintSave will write aModule to an existing library located at aLibraryPath.
Definition: plugin.cpp:85
FP_LIB_TABLE(FP_LIB_TABLE *aFallBackTable=NULL)
Constructor FP_LIB_TABLE.
void Unexpected(int aTok)
Function Unexpected throws an IO_ERROR exception with an input file specific error message...
Definition: dsnlexer.cpp:369
Class OUTPUTFORMATTER is an important interface (abstract class) used to output 8 bit text in a conve...
Definition: richio.h:327
bool operator==(const FP_LIB_TABLE &aFpTable) const
LIB_TABLE_ROWS rows
std::string::size_type size() const
Definition: utf8.h:115
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:51
void FootprintLibDelete(const wxString &aNickname)
wxString FromUTF8()
Function FromUTF8 returns the current token text as a wxString, assuming that the input byte stream i...
Definition: dsnlexer.h:498
const wxString GetFullURI(bool aSubstituted=false) const
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
KIFACE_I & Kiface()
Global KIFACE_I "get" accessor.
Definition: kicad.cpp:52
PLUGIN::RELEASER plugin
Definition: fp_lib_table.h:98
virtual bool IsFootprintLibWritable(const wxString &aLibraryPath)
Function IsFootprintLibWritable returns true iff the library at aLibraryPath is writable.
Definition: plugin.cpp:114
const LIB_ID & GetFPID() const
Definition: class_module.h:184
SEARCH_STACK & KifaceSearch()
Only for DSO specific 'non-library' files.
Definition: kiface_i.h:127
void NeedLEFT()
Function NeedLEFT calls NextTok() and then verifies that the token read in is a DSN_LEFT.
Definition: dsnlexer.cpp:393
wxString wx_str() const
Definition: utf8.cpp:48
static const wxString GlobalPathEnvVariableName()
Function GlobalPathEnvVarVariableName.
virtual void Parse(LIB_TABLE_LEXER *aLexer) override
Parse the LIB_TABLE_LEXER s-expression library table format into the appropriate LIB_TABLE_ROW object...
virtual MODULE * FootprintLoad(const wxString &aLibraryPath, const wxString &aFootprintName, const PROPERTIES *aProperties=NULL)
Function FootprintLoad loads a footprint having aFootprintName from the aLibraryPath containing a lib...
Definition: plugin.cpp:76
static bool LoadGlobalTable(FP_LIB_TABLE &aTable)
Function LoadGlobalTable loads the global footprint library table into aTable.
LIB_TABLE_T::T NeedSYMBOLorNUMBER()
Function NeedSYMBOLorNUMBER calls NextTok() and then verifies that the token read in satisfies bool I...
virtual bool FootprintLibDelete(const wxString &aLibraryPath, const PROPERTIES *aProperties=NULL)
Function FootprintLibDelete deletes an existing footprint library and returns true, or if library does not exist returns false, or throws an exception if library exists but is read only or cannot be deleted for some other reason.
Definition: plugin.cpp:106
static PCB_FILE_T EnumFromStr(const wxString &aFileType)
Function EnumFromStr returns the PCB_FILE_T from the corresponding plugin type name: "kicad"...
Definition: io_mgr.cpp:97
const UTF8 & GetLibItemName() const
Definition: lib_id.h:115
void PrefetchLib(const wxString &aNickname)
Function PrefetchLib If possible, prefetches the specified library (e.g.
bool operator==(const LIB_TABLE_ROW &r) const
void Save(const wxString &aFileName) const
Write this library table to aFileName in s-expression form.
bool operator==(const FP_LIB_TABLE_ROW &aRow) const
LIB_TABLE_ROW * findRow(const wxString &aNickname) const
Return a LIB_TABLE_ROW if aNickname is found in this table or in any chained fallBack table fragment...
void Expecting(int aTok)
Function Expecting throws an IO_ERROR exception with an input file specific error message...
Definition: dsnlexer.cpp:353
void Load(const wxString &aFileName)
Load the library table using the path defined by aFileName aFallBackTable.
wxString GetKicadConfigPath()
Function GetKicadConfigPath.
Definition: common.cpp:217
virtual void Format(OUTPUTFORMATTER *aOutput, int aIndentLevel) const override
Generate the table in s-expression format to aOutput with an indention level of aIndentLevel.
int SetLibNickname(const UTF8 &aNickname)
Override the logical library name portion of the LIB_ID to aNickname.
Definition: lib_id.cpp:219
void FootprintLibCreate(const wxString &aNickname)
const FP_LIB_TABLE_ROW * FindRow(const wxString &aNickName)
Function FindRow.
SAVE_T
Enum SAVE_T is the set of return values from FootprintSave() below.
Definition: fp_lib_table.h:187
const wxString & GetNickName() const
static const wxChar * GetChars(const wxString &s)
Function GetChars returns a wxChar* to the actual wxChar* data within a wxString, and is helpful for ...
Definition: macros.h:92
LIB_TABLE_ROWS::const_iterator LIB_TABLE_ROWS_CITER
LIB_TABLE_T::T NextTok()
Function NextTok returns the next token found in the input file or T_EOF when reaching the end of fil...
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:205
void Duplicate(int aTok)
Function Duplicate throws an IO_ERROR exception with a message saying specifically that aTok is a dup...
Definition: dsnlexer.cpp:377
static PLUGIN * PluginFind(PCB_FILE_T aFileType)
Function PluginFind returns a PLUGIN which the caller can use to import, export, save, or load design documents.
Definition: io_mgr.cpp:58
Class PLUGIN is a base class that BOARD loading and saving plugins should derive from.
Definition: io_mgr.h:265
virtual void FootprintDelete(const wxString &aLibraryPath, const wxString &aFootprintName, const PROPERTIES *aProperties=NULL)
Function FootprintDelete deletes aFootprintName from the library at aLibraryPath. ...
Definition: plugin.cpp:92
int CurLineNumber()
Function CurLineNumber returns the current line number within my LINE_READER.
Definition: dsnlexer.h:507
The common library.
virtual void FootprintLibCreate(const wxString &aLibraryPath, const PROPERTIES *aProperties=NULL)
Function FootprintLibCreate creates a new empty footprint library at aLibraryPath empty...
Definition: plugin.cpp:99
void NeedRIGHT()
Function NeedRIGHT calls NextTok() and then verifies that the token read in is a DSN_RIGHT.
Definition: dsnlexer.cpp:401
Class LIB_TABLE_LEXER is an automatically generated class using the TokenList2DnsLexer.cmake technology, based on keywords provided by file: /home/kicad/workspace/kicad-doxygen/common/lib_table.keywords.
MODULE * FootprintLoadWithOptionalNickname(const LIB_ID &aFootprintId)
Function FootprintLoadWithOptionalNickname loads a footprint having aFootprintId with possibly an emp...
wxString FindValidPath(const wxString &aFileName) const
Definition: search_stack.h:71
Module description (excepted pads)
void SetType(const wxString &aType) override
Function SetType.
int PRINTF_FUNC Print(int nestLevel, const char *fmt,...)
Function Print formats and writes text to the output stream.
Definition: richio.cpp:404
void FootprintEnumerate(wxArrayString &aFootprintNames, const wxString &aNickname)
Return a list of footprint names contained within the library given by aNickname. ...
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition: lib_id.h:98
SAVE_T FootprintSave(const wxString &aNickname, const MODULE *aFootprint, bool aOverwrite=true)
Function FootprintSave.
void FootprintDelete(const wxString &aNickname, const wxString &aFootprintName)
Function FootprintDelete.
PCB_FILE_T
Enum PCB_FILE_T is a set of file types that the IO_MGR knows about, and for which there has been a pl...
Definition: io_mgr.h:51
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:38
static wxString GetGlobalTableFileName()
Function GetGlobalTableFileName.
std::vector< wxString > GetLogicalLibs()
Return the logical library names, all of them that are pertinent to a look up done on this LIB_TABLE...
S-expression Pcbnew file format.
Definition: io_mgr.h:54
C++ does not put enum values in separate namespaces unless the enum itself is in a separate namespace...
Manage LIB_TABLE_ROW records (rows), and can be searched based on library nickname.