Package gavo :: Package votable :: Module model
[frames] | no frames]

Source Code for Module gavo.votable.model

  1  """ 
  2  xmlstan elements of VOTable. 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  from gavo import utils 
 12  from gavo.utils import ElementTree 
 13  from gavo.utils.stanxml import ( 
 14          Element, registerPrefix, getPrefixInfo, schemaURL, escapePCDATA) 
 15  from gavo.votable import common 
 16   
 17   
 18  NAMESPACES = { 
 19          "1.3": "http://www.ivoa.net/xml/VOTable/v1.3", 
 20          "1.2": "http://www.ivoa.net/xml/VOTable/v1.2", 
 21          "1.1": "http://www.ivoa.net/xml/VOTable/v1.1", 
 22  } 
 23   
 24  # WARNING: if you change the default version of the VOTables generated, 
 25  # you'll probably have to adapt them in resources/xsl, too. 
 26  registerPrefix("vot", NAMESPACES["1.3"], schemaURL("VOTable-1.3.xsd")) 
 27  registerPrefix("vot2", NAMESPACES["1.2"], schemaURL("VOTable-1.2.xsd")) 
 28  registerPrefix("vot1", NAMESPACES["1.1"], schemaURL("VOTable-1.1.xsd")) 
29 30 31 -class _VODMLElements(object):
32 """extra VODML element, slated for VOTable 1.4. 33 """
34 - class _VODMLElement(Element):
35 _prefix = "vot" 36 _local = True 37 _a_ID = None
38
39 - class VODML(_VODMLElement):
40 _childSequence = ["MODEL", "GLOBALS", "TEMPLATES"]
41
42 - class MODEL(_VODMLElement):
43 _childSequence = ["NAME", "URL"]
44
45 - class GLOBALS(_VODMLElement):
46 _childSequence = ["INSTANCE"]
47
48 - class TEMPLATES(_VODMLElement):
49 _childSequence = ["INSTANCE"]
50
51 - class NAME(_VODMLElement):
52 _a_version = None
53
54 - class URL(_VODMLElement): pass
55
56 - class INSTANCE(_VODMLElement):
57 _a_dmtype = None 58 _childSequence = ["REFERENCE", "ATTRIBUTE"]
59
60 - class REFERENCE(_VODMLElement):
61 _a_dmrole = None 62 _childSequence = ["IDREF"]
63
64 - class IDREF(_VODMLElement): pass
65
66 - class ATTRIBUTE(_VODMLElement):
67 _a_dmrole = None 68 _childSequence = [ 69 "CONSTANT", "COLUMN", "LITERAL", "INSTANCE", "REFERENCE"]
70
71 - class CONSTANT(_VODMLElement):
72 _mayBeEmpty = True 73 _a_dmrole = None 74 _a_ref = None
75
76 - class COLUMN(_VODMLElement):
77 _mayBeEmpty = True 78 _a_ref = None 79 _a_dmrole = None
80
81 - class COMPOSITION(_VODMLElement):
82 _childSequence = ["INSTANCE"]
83
84 - class LITERAL(_VODMLElement):
85 _a_dmtype = None 86 _a_value = None
87
88 89 -class VOTable(_VODMLElements):
90 """The container for VOTable elements. 91 """
92 - class _VOTElement(Element):
93 _prefix = "vot" 94 _local = True
95
96 - class _DescribedElement(_VOTElement):
97 _a_ID = None 98 _a_ref = None 99 _a_name = None 100 _a_ucd = None 101 _a_utype = None 102 _mayBeEmpty = True 103
104 - def getDesignation(self):
105 """returns some name-like thing for a FIELD or PARAM. 106 """ 107 if self.name: 108 res = self.name 109 elif self.ID: 110 res = self.ID 111 else: 112 res = "%s_%s"%(self.__class__.__name__, "%x"%id(self)) 113 return res.encode("ascii", "ignore")
114
115 - def getDescription(self):
116 """returns the description for this element, or an empty string. 117 """ 118 try: 119 return self.iterChildrenOfType(VOTable.DESCRIPTION).next().text_ 120 except StopIteration: 121 return ""
122
123 - class _ValuedElement(_DescribedElement):
124 _a_unit = None 125 _a_xtype = None
126
127 - class _TypedElement(_ValuedElement):
128 _a_ref = None 129 _a_arraysize = None 130 _a_datatype = None 131 _a_precision = None 132 _a_ref = None 133 _a_type = None 134 _a_width = None 135 _a_format = None 136
137 - def isScalar(self):
138 return self.arraysize is None or self.arraysize=='1'
139
140 - def isMultiDim(self):
141 return common.isMultiDim(self.arraysize)
142
143 - def hasVarLength(self):
144 return common.hasVarLength(self.arraysize)
145
146 - def getLength(self):
147 """returns the number of items one should expect in value, or 148 None for variable-length arrays. 149 """ 150 return common.getLength(self.arraysize)
151
152 - def getShape(self):
153 return common.getShape(self.datatype, self.arraysize)
154
155 - def _setNULLValue(self, val):
156 """sets the null literal of self to val. 157 """ 158 valEls = list(self.iterChildrenWithName("VALUES")) 159 if valEls: 160 valEls[0](null=val) 161 else: 162 self[VOTable.VALUES(null=val)]
163
164 - class _RefElement(_ValuedElement):
165 _a_ref = None 166 _a_ucd = None 167 _a_utype = None 168 childSequence = []
169
170 - class _ContentElement(_VOTElement):
171 """An element containing tabular data. 172 173 These are usually serialized using some kind of streaming. 174 175 See votable.tablewriter for details. 176 """
177 - def write(self, file):
178 raise NotImplementedError("This _ContentElement cannot write yet")
179 180
181 - class _BinaryDataElement(_ContentElement):
182 """a base class for both BINARY and BINARY2. 183 """ 184 _childSequence = ["STREAM"] 185 encoding = "base64" 186
187 - def write(self, file):
188 # To be able to write incrementally, encode chunks of multiples 189 # of base64's block size until the stream is finished. 190 blockSize = 57 191 buf, bufFil, flushThreshold = [], 0, blockSize*20 192 file.write('<%s>'%self.name_) 193 file.write('<STREAM encoding="base64">') 194 try: 195 for data in self.iterSerialized(): 196 buf.append(data) 197 bufFil += len(data) 198 if bufFil>flushThreshold: 199 curData = ''.join(buf) 200 curBlockLen = (len(curData)//blockSize)*blockSize 201 file.write(curData[:curBlockLen].encode("base64")) 202 buf = [curData[curBlockLen:]] 203 finally: 204 file.write("".join(buf).encode("base64")) 205 file.write("</STREAM>") 206 file.write('</%s>'%self.name_)
207
208 - class BINARY(_BinaryDataElement):
209 pass
210
211 - class BINARY2(_BinaryDataElement):
212 pass
213
214 - class COOSYS(_VOTElement):
215 _mayBeEmpty = True 216 _a_ID = None 217 _a_epoch = None 218 _a_equinox = None 219 _a_system = None
220
221 - class TIMESYS(_VOTElement):
222 _mayBeEmpty = True 223 _a_ID = None 224 _a_timescale = None 225 _a_refposition = None 226 _a_timeorigin = None
227
228 - class TYPE(_VOTElement):
229 pass
230
231 - class ROLE(_VOTElement):
232 pass
233
234 - class DATA(_VOTElement):
235 _childSequence = ["INFO", "TABLEDATA", "BINARY", "BINARY2", "FITS"]
236
237 - class DEFINITIONS(_VOTElement):
238 pass
239
240 - class DESCRIPTION(_VOTElement):
241 _childSequence = [None]
242
243 - class FIELD(_TypedElement):
244 _childSequence = ["DESCRIPTION", "VALUES", "LINK"]
245
246 - class FIELDref(_RefElement): pass
247
248 - class FITS(_VOTElement):
249 _childSequence = ["STREAM"]
250
251 - class GROUP(_DescribedElement):
252 _mayBeEmpty = True 253 _a_ref = None 254 _childSequence = ["DESCRIPTION", "PARAM", "FIELDref", 255 "PARAMref", "GROUP"]
256 257
258 - class INFO(_ValuedElement):
259 _a_ref = None 260 _a_value = None 261 _childSequence = [None] 262
263 - def isEmpty(self):
264 return self.value is None
265
266 - class INFO_atend(INFO):
267 # a bad hack; TAP mandates INFO items below table, and this is 268 # the least complicated way to force this. 269 name_ = "INFO"
270 285 286
287 - class MAX(_VOTElement):
288 _a_inclusive = None 289 _a_value = None 290 _childSequence = [] 291
292 - def isEmpty(self):
293 return self.value is None
294 295
296 - class MIN(_VOTElement):
297 _a_inclusive = None 298 _a_value = None 299 _childSequence = [] 300
301 - def isEmpty(self):
302 return self.value is None
303 304
305 - class OPTION(_VOTElement):
306 _a_name = None 307 _a_value = "" # as with PARAM below 308 _childSequence = ["OPTION"] 309 _mayBeEmpty = True
310 311
312 - class PARAM(_TypedElement):
313 _mayBeEmpty = True 314 _a_value = "" # supposed to mean "ah, somewhat null" 315 # Needs to be cared for in client code. 316 _childSequence = ["DESCRIPTION", "VALUES", "LINK"]
317 318
319 - class PARAMref(_RefElement): pass
320 321
322 - class RESOURCE(_VOTElement):
323 _a_ID = None 324 _a_name = None 325 _a_type = None 326 _a_utype = None 327 _childSequence = ["DESCRIPTION", "VODML", "DEFINITIONS", 328 "INFO", "COOSYS", "TIMESYS", "GROUP", 329 "PARAM", "LINK", "TABLE", "INFO_atend", "RESOURCE", "stub"] 330 # (stub for delayed overflow warnings and such) 331
332 - def writeErrorElement(self, outputFile, exception):
333 outputFile.write( 334 """<INFO name="QUERY_STATUS" value="ERROR">%s</INFO>"""% 335 escapePCDATA("Error while serializing VOTable," 336 " content is probably incomplete: %s"% 337 utils.safe_str(exception)))
338 339
340 - class STREAM(_VOTElement):
341 _a_actuate = None 342 _a_encoding = None 343 _a_expires = None 344 _a_href = None 345 _a_rights = None 346 _a_type = None 347 _childSequence = [None]
348 349
350 - class TABLE(_DescribedElement):
351 """A TABLE element. 352 353 If you want to access fields by name (getFieldForName), make sure 354 name and ids are unique. 355 """ 356 _a_nrows = None 357 _childSequence = ["DESCRIPTION", "INFO", "GROUP", "FIELD", "PARAM", "LINK", 358 "DATA", "stub"] # (stub for delayed overflow warnings and such) 359 360 _fieldIndex = None 361 362 @utils.memoized
363 - def getFields(self):
365
366 - def _getFieldIndex(self):
367 if self._fieldIndex is None: 368 index = {} 369 for child in self.getFields(): 370 if child.name: 371 index[child.name] = child 372 if child.ID: 373 index[child.ID] = child 374 self._fieldIndex = index 375 return self._fieldIndex
376
377 - def getFieldForName(self, name):
378 """returns the FIELD having a name or id of name. 379 380 A KeyError is raised when the field does not exist; if names are 381 not unique, the last column with the name specified is returned. 382 """ 383 return self._getFieldIndex()["name"]
384 385
386 - class TABLEDATA(_ContentElement):
387 _childSequence = ["TR"] 388 encoding = "utf-8" 389
390 - def write(self, file):
391 file.write("<TABLEDATA>") 392 enc = self.encoding 393 try: 394 for row in self.iterSerialized(): 395 file.write(row.encode(enc)) 396 finally: 397 file.write("</TABLEDATA>")
398 399
400 - class TD(_VOTElement):
401 _a_encoding = None 402 _childSequence = [None] 403 _mayBeEmpty = True
404 405
406 - class TR(_VOTElement):
407 _a_ID = None 408 _childSequence = ["TD"]
409 410
411 - class VALUES(_VOTElement):
412 _a_ID = None 413 _a_null = None 414 _a_ref = None 415 _a_type = None 416
417 - def isEmpty(self):
418 return self.null is None and Element.isEmpty(self)
419 420
421 - class VOTABLE(_VOTElement):
422 _a_ID = None 423 _a_version = "1.3" 424 _prefix = "vot" 425 _supressedPrefix = "vot" 426 _mayBeEmpty = True 427 # The following is for when the xmlstan tree is processed by 428 # tablewriter.write rather than asETree 429 _fixedTagMaterial = ('xmlns="%s" xmlns:xsi="%s"' 430 ' xsi:schemaLocation="%s %s"')%(( 431 getPrefixInfo("vot")[0], 432 getPrefixInfo("xsi")[0]) 433 +getPrefixInfo("vot")) 434 _childSequence = ["DESCRIPTION", "VODML", "DEFINITIONS", "INFO", "COOSYS", 435 "TIMESYS", "GROUP", "PARAM", "RESOURCE"]
436 437
438 - class VOTABLE11(VOTABLE):
439 # An incredibly nasty hack that kinda works due to the fact that 440 # all elements here are local -- make this your top-level element 441 # and only use what's legal in VOTable 1.1, and you get a VOTable1.1 442 # conforming document 443 name_ = "VOTABLE" 444 _a_version = "1.1" 445 _prefix = "vot1" 446 _supressedPrefix = "vot1" 447 # The following is for when the xmlstan tree is processed by 448 # tablewriter.write rather than asETree 449 _fixedTagMaterial = ('xmlns="%s" xmlns:xsi="%s"' 450 ' xsi:schemaLocation="%s %s"')%(( 451 getPrefixInfo("vot1")[0], 452 getPrefixInfo("xsi")[0]) 453 +getPrefixInfo("vot1"))
454
455 - class VOTABLE12(VOTABLE):
456 # see VOTABLE11 457 name_ = "VOTABLE" 458 _a_version = "1.2" 459 _prefix = "vot2" 460 _supressedPrefix = "vot2" 461 # The following is for when the xmlstan tree is processed by 462 # tablewriter.write rather than asETree 463 _fixedTagMaterial = ('xmlns="%s" xmlns:xsi="%s"' 464 ' xsi:schemaLocation="%s %s"')%(( 465 getPrefixInfo("vot1")[0], 466 getPrefixInfo("xsi")[0]) 467 +getPrefixInfo("vot2"))
468
469 470 471 -def voTag(tagName, version="1.3"):
472 """returns the VOTable QName for tagName. 473 474 You only need this if you want to search in ElementTrees. 475 """ 476 return ElementTree.QName(NAMESPACES[version], tagName)
477