1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
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
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
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
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):
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):
95
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
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
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
150 return (u'%s\0%s' % (self.last or '', self.first or '')).lower ()
151
152
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
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
171
173 return "Person (%s, %s)" % (repr(self.last), repr(self.first))
174
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
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
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
223
225 return '%.4d%.2d%.2d' % (self.year or 0,
226 self.month or 0,
227 self.day or 0)
228
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
241 if not self == other or not isinstance (other, Date):
242 return False
243 return _Qualified.deep_equal (self, other)
244
245
247 return hash ((self.year, self.month, self.day))
248
249
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
286 idx = map (string.lower, re_split.split(self))
287 return filter (None, idx)
288
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
302 self.q = {}
303 str.__init__ (self, text)
304 return
305
307 return k (xml.attrib ['href'])
308
309 xmlread = classmethod (xmlread)
310
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
324
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
334
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
346 self.q = {}
347 unicode.__init__ (self, text)
348 return
349
351 return k (xml.attrib ['value'])
352
353 xmlread = classmethod (xmlread)
354
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
368
371
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
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
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
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
430 return [ '%s/%d' % (self.group, self.id) ]
431
433 return '%s/%d' % (self.group, self.id)
434
436
437 return 'Txo (%s, %s)' % (`self.group`, `self.id`)
438
440 try:
441 return cmp (self.group, other.group) or cmp (self.id, other.id)
442 except AttributeError:
443
444
445 return 1
446
448 if not self == other or not isinstance (other, Txo):
449 return False
450 return _Qualified.deep_equal (self, other)
451
453 return hash ((self.group, self.id))
454
455
456
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
467 C_to_N = {}
468
469 for _k, _v in N_to_C.items (): C_to_N [_v] = _k
470 del _k, _v
471