Package Pyblio :: Module Attribute
[hide private]
[frames] | no frames]

Source Code for Module Pyblio.Attribute

  1  # This file is part of pybliographer 
  2  #  
  3  # Copyright (C) 1998-2006 Frederic GOBRY 
  4  # Email : gobry@pybliographer.org 
  5  #           
  6  # This program is free software; you can redistribute it and/or 
  7  # modify it under the terms of the GNU General Public License 
  8  # as published by the Free Software Foundation; either version 2  
  9  # of the License, or (at your option) any later version. 
 10  #    
 11  # This program is distributed in the hope that it will be useful, 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  # GNU General Public License for more details.  
 15  #  
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the Free Software 
 18  # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 19  #  
 20   
 21  """ Basic data types that can be used as attributes for a L{Record 
 22  <Pyblio.Store.Record>}. 
 23   
 24  Basic attributes of a record can be B{qualified} by one or more 
 25  additional sub-attributes. For instance, an attribute I{author} of 
 26  type L{Person} can have, for every Person instance, a sub-attribute of 
 27  type L{Date} that represents its birth date.""" 
 28   
 29  import string, re, urlparse, os 
 30   
 31  from xml import sax 
 32  from xml.sax.saxutils import escape, quoteattr 
 33   
 34  from Pyblio import I18n 
 35   
 36  from gettext import gettext as _ 
 37   
 38  re_split = re.compile (r'\W+', re.UNICODE) 
 39   
40 -class _Qualified(object):
41 """ Mix-in class that provides qualifiers to attributes, making 42 them behave like composite data types (but not arbitrarily nested 43 data, though)""" 44
45 - def _xmlsubwrite(self, fd, offset = 1):
46 ws = ' ' * offset 47 48 for k, vs in self.q.items (): 49 fd.write (ws + '<attribute id=%s>\n' % quoteattr (k)) 50 for v in vs: 51 v.xmlwrite (fd, offset + 1) 52 fd.write ('\n') 53 fd.write (ws + '</attribute>\n') 54 return
55
56 - def deep_equal(self, other):
57 for k in self.q: 58 if not k in other.q or not len (self.q [k]) == len (other.q [k]): 59 return False 60 61 for x, y in zip (self.q [k], other.q [k]): 62 if not x.deep_equal (y): 63 return False 64 65 for k in other.q: 66 if not k in self.q: 67 return False 68 return True
69
70 - def is_complete(self):
71 """Returns True if the field has an actual value (ie, hasn't 72 been created by adding qualifiers only)""" 73 return True
74
75 -class UnknownContent(_Qualified):
76 """ 77 An invalid type. 78 79 It is used, when you add qualifiers before you add the main field 80 to a record. Trying to store it will raise an error. 81 """
82 - def __init__ (self):
83 self.q = {}
84
85 - def xmlwrite (self, fd, offset = 0):
86 raise Exceptions.ParserError ("Attribute.UnknownContent has qualifiers, "\ 87 "but is empty: %s" % self.q)
88
89 - def deep_equal (self, other):
90 if not isinstance (other, UnknownContent): return False 91 return _Qualified.deep_equal (self, other)
92
93 - def is_complete(self):
94 return False
95
96 -class Person(_Qualified):
97 ''' A person name. ''' 98
99 - def __init__ (self, honorific = None, first = None, last = None, lineage = None, 100 xml = None):
101 102 self.q = {} 103 104 self.honorific = honorific 105 self.first = first 106 self.last = last 107 self.lineage = lineage 108 return
109
110 - def xmlread (k, xml, inside = False):
111 p = k () 112 113 for f in ('honorific', 'first', 'last', 'lineage'): 114 setattr (p, f, xml.attrib.get (f, None)) 115 116 return p
117 118 xmlread = classmethod (xmlread) 119
120 - def xmlwrite (self, fd, offset = 0):
121 122 ws = ' ' * offset 123 124 data = [] 125 for f in ('honorific', 'first', 'last', 'lineage'): 126 v = getattr (self, f) 127 if v: 128 data.append ('%s=%s' % (f, quoteattr (v.encode ('utf-8')))) 129 130 data = ' '.join (data) 131 132 if not self.q: 133 fd.write (ws + '<person %s/>' % data) 134 else: 135 fd.write (ws + '<person %s>\n' % data) 136 self._xmlsubwrite (fd, offset + 1) 137 fd.write (ws + '</person>') 138 139 return
140
141 - def index (self):
142 idx = [] 143 for x in (self.first, self.last): 144 if x: idx = idx + map (string.lower, re_split.split(x)) 145 146 return filter (None, idx)
147 148
149 - def sort (self):
150 return (u'%s\0%s' % (self.last or '', self.first or '')).lower ()
151 152
153 - def __eq__ (self, other):
154 155 return self.last == other.last and \ 156 self.first == other.first and \ 157 self.honorific == other.honorific and \ 158 self.lineage == other.lineage
159
160 - def __ne__ (self, other):
161 162 return self.last != other.last or \ 163 self.first != other.first or \ 164 self.honorific != other.honorific or \ 165 self.lineage != other.lineage
166
167 - def deep_equal (self, other):
168 if not self == other or not isinstance (other, Person): 169 return False 170 return _Qualified.deep_equal (self, other)
171
172 - def __repr__ (self):
173 return "Person (%s, %s)" % (repr(self.last), repr(self.first))
174
175 - def __hash__ (self):
176 return hash ((self.last, self.first, self.lineage, self.honorific))
177 178
179 -class Date (_Qualified):
180 ''' A date. ''' 181
182 - def __init__ (self, year = None, month = None, day = None):
183 self.q = {} 184 185 self.year = year 186 self.month = month 187 self.day = day 188 return
189
190 - def xmlread (k, xml):
191 d = k () 192 193 for f in ('year', 'month', 'day'): 194 v = xml.attrib.get (f, None) 195 if v: setattr (d, f, int (v)) 196 197 return d
198 199 xmlread = classmethod (xmlread) 200 201
202 - def xmlwrite (self, fd, offset = 0):
203 204 ws = ' ' * offset 205 206 data = [] 207 for f in ('year', 'month', 'day'): 208 v = getattr (self, f) 209 if v: 210 data.append ('%s="%d"' % (f, v)) 211 212 fd.write (ws + '<date %s' % string.join (data, ' ')) 213 if self.q: 214 fd.write ('>\n') 215 self._xmlsubwrite (fd, offset + 1) 216 fd.write (ws + '</date>') 217 else: 218 fd.write ('/>') 219 return
220
221 - def index (self):
222 return []
223
224 - def sort (self):
225 return '%.4d%.2d%.2d' % (self.year or 0, 226 self.month or 0, 227 self.day or 0)
228
229 - def __cmp__ (self, other):
230 if not isinstance (other, Date): return 1 231 232 for x, y in ((self.year, other.year), 233 (self.month, other.month), 234 (self.day, other.day)): 235 a = cmp (x, y) 236 if a: return a 237 238 return 0
239
240 - def deep_equal (self, other):
241 if not self == other or not isinstance (other, Date): 242 return False 243 return _Qualified.deep_equal (self, other)
244 245
246 - def __hash__ (self):
247 return hash ((self.year, self.month, self.day))
248 249
250 - def __repr__ (self):
251 252 return 'Date (year = %s, month = %s, day = %s)' % ( 253 repr (self.year), repr (self.month), repr (self.day))
254 255
256 -class Text (unicode, _Qualified):
257 ''' Textual data ''' 258
259 - def __init__ (self, text = u''):
260 unicode.__init__ (self, text) 261 self.q = {} 262 return
263
264 - def xmlread (k, xml):
265 content = xml.find ('./content') 266 if content is not None: 267 return k (content.text) 268 else: 269 return k (xml.text)
270 271 xmlread = classmethod (xmlread) 272
273 - def xmlwrite (self, fd, offset = 0):
274 ws = ' ' * offset 275 276 if self.q: 277 fd.write (ws + '<text>\n') 278 fd.write (ws + ' <content>%s</content>\n' % escape (self.encode ('utf-8'))) 279 self._xmlsubwrite (fd, offset + 1) 280 fd.write (ws + '</text>') 281 else: 282 fd.write (ws + '<text>%s</text>' % escape (self.encode ('utf-8'))) 283 return
284
285 - def index (self):
286 idx = map (string.lower, re_split.split(self)) 287 return filter (None, idx)
288
289 - def sort (self):
290 return self.lower ()
291
292 - def deep_equal (self, other):
293 if not self == other or not isinstance (other, Text): 294 return False 295 return _Qualified.deep_equal (self, other)
296 297
298 -class URL (str, _Qualified):
299 ''' An URL ''' 300
301 - def __init__ (self, text = ''):
302 self.q = {} 303 str.__init__ (self, text) 304 return
305
306 - def xmlread (k, xml):
307 return k (xml.attrib ['href'])
308 309 xmlread = classmethod (xmlread) 310
311 - def xmlwrite (self, fd, offset = 0):
312 ws = ' ' * offset 313 314 fd.write (ws + '<url href=%s' % quoteattr (self.encode ('utf-8'))) 315 if self.q: 316 fd.write ('>\n') 317 self._xmlsubwrite (fd, offset + 1) 318 fd.write (ws + '</url>') 319 else: 320 fd.write ('/>') 321 return
322
323 - def index (self):
324 # do not index the document suffix, only the server name and document page 325 url = urlparse.urlparse (self) 326 327 idx = re_split.split (url [1]) + \ 328 re_split.split (os.path.splitext (url [2]) [0]) 329 330 return filter (None, idx)
331
332 - def sort (self):
333 return self
334
335 - def deep_equal (self, other):
336 if not self == other or not isinstance (other, URL): 337 return False 338 return _Qualified.deep_equal (self, other)
339 340
341 -class ID (unicode, _Qualified):
342 343 ''' An external identifier ''' 344
345 - def __init__ (self, text = u''):
346 self.q = {} 347 unicode.__init__ (self, text) 348 return
349
350 - def xmlread (k, xml):
351 return k (xml.attrib ['value'])
352 353 xmlread = classmethod (xmlread) 354
355 - def xmlwrite (self, fd, offset = 0):
356 ws = ' ' * offset 357 fd.write (ws + '<id value=%s' % quoteattr (self.encode ('utf-8'))) 358 if self.q: 359 fd.write ('>\n') 360 self._xmlsubwrite (fd, offset + 1) 361 fd.write (ws + '</id>') 362 else: 363 fd.write ('/>') 364 return
365
366 - def index (self):
367 return []
368
369 - def sort (self):
370 return self
371
372 - def deep_equal (self, other):
373 if not self == other or not isinstance (other, ID): 374 return False 375 return _Qualified.deep_equal (self, other)
376 377 378
379 -class Txo (_Qualified):
380 381 """ Element of a taxonomy. 382 383 In the simplest case, this can be seen as a value in a enumerated 384 set of possible values. The possible values are defined as 385 L{Pyblio.Schema.TxoItem}s, and are stored in the 386 L{Store.Database}, in the B{txo} attribute, and L{Store.Record}s 387 can contain Txo attributes which refer to these 388 L{Pyblio.Schema.TxoItem}s. Say you have a list of known document 389 types in the I{document-type} taxonomy. You can then affect the 390 document type to the I{type} attribute of a record with the 391 following operations: 392 393 >>> item = db.txo['document-type'].byname('article') 394 >>> record.add('type', item, Attribute.Txo) 395 396 """ 397
398 - def __init__ (self, item = None):
399 self.q = {} 400 if item: 401 self.group = item.group 402 self.id = item.id 403 else: 404 self.group = None 405 self.id = None 406 return
407
408 - def xmlread (k, xml):
409 txo = k () 410 txo.group = xml.attrib ['group'] 411 txo.id = int (xml.attrib ['id']) 412 413 return txo
414 415 xmlread = classmethod (xmlread) 416
417 - def xmlwrite (self, fd, offset = 0):
418 ws = ' ' * offset 419 fd.write (ws + '<txo group="%s" id="%d"' % (self.group, self.id)) 420 421 if self.q: 422 fd.write ('>\n') 423 self._xmlsubwrite (fd, offset + 1) 424 fd.write (ws + '</txo>') 425 else: 426 fd.write ('/>') 427 return
428
429 - def index (self):
430 return [ '%s/%d' % (self.group, self.id) ]
431
432 - def sort (self):
433 return '%s/%d' % (self.group, self.id)
434
435 - def __repr__ (self):
436 437 return 'Txo (%s, %s)' % (`self.group`, `self.id`)
438
439 - def __cmp__ (self, other):
440 try: 441 return cmp (self.group, other.group) or cmp (self.id, other.id) 442 except AttributeError: 443 # If 'other' is not of the proper type, simply consider 444 # 'self' as superior. 445 return 1
446
447 - def deep_equal (self, other):
448 if not self == other or not isinstance (other, Txo): 449 return False 450 return _Qualified.deep_equal (self, other)
451
452 - def __hash__ (self):
453 return hash ((self.group, self.id))
454 455 456 # A mapping between class names and class objects 457 N_to_C = { 458 'person' : Person, 459 'date' : Date, 460 'text' : Text, 461 'url' : URL, 462 'id' : ID, 463 'txo' : Txo, 464 } 465 466 # A mapping from class objects to class names 467 C_to_N = {} 468 469 for _k, _v in N_to_C.items (): C_to_N [_v] = _k 470 del _k, _v 471