Package gavo :: Package formats :: Module votablewrite
[frames] | no frames]

Source Code for Module gavo.formats.votablewrite

  1  """ 
  2  Generating VOTables from internal data representations. 
  3   
  4  This is glue code to the more generic GAVO votable library.  In particular, 
  5  it governs the application of base.SerManagers and their column descriptions 
  6  (which are what is passed around as colDescs in this module to come up with  
  7  VOTable FIELDs and the corresponding values. 
  8   
  9  You should access this module through formats.votable. 
 10  """ 
 11   
 12  #c Copyright 2008-2019, the GAVO project 
 13  #c 
 14  #c This program is free software, covered by the GNU GPL.  See the 
 15  #c COPYING file in the source distribution. 
 16   
 17   
 18  import contextlib 
 19  import functools 
 20  import itertools 
 21  from cStringIO import StringIO 
 22  import warnings 
 23   
 24  from gavo import base 
 25  from gavo import dm 
 26  from gavo import rsc 
 27  from gavo import stc 
 28  from gavo import utils 
 29  from gavo import votable 
 30  from gavo.base import meta 
 31  from gavo.base import valuemappers 
 32  from gavo.formats import common 
 33  from gavo.votable import V 
 34  from gavo.votable import modelgroups 
35 36 37 -class Error(base.Error):
38 pass
39 40 41 tableEncoders = { 42 "td": V.TABLEDATA, 43 "binary": V.BINARY, 44 "binary2": V.BINARY2, 45 }
46 47 48 -class VOTableContext(utils.IdManagerMixin):
49 """A context object for writing VOTables. 50 51 The constructor arguments work as keyword arguments to ``getAsVOTable``. 52 Some other high-level functions accept finished contexts. 53 54 This class provides management for unique ID attributes, the value mapper 55 registry, and possibly additional services for writing VOTables. 56 57 VOTableContexts optionally take 58 59 - a value mapper registry (by default, valuemappers.defaultMFRegistry) 60 - the tablecoding (currently, td, binary, or binary2) 61 - version=(1,1) to order a 1.1-version VOTable, (1,2) for 1.2. 62 (default is now 1.3). 63 - acquireSamples=False to suppress reading some rows to get 64 samples for each column 65 - suppressNamespace=False to leave out a namespace declaration 66 (mostly convenient for debugging) 67 - overflowElement (see votable.tablewriter.OverflowElement) 68 69 There's also an attribute produceVODML that will automatically be 70 set for VOTable 1.4; you can set it to true manually, but the 71 resulting VOTables will probably be invalid. 72 73 If VO-DML processing is enabled, the context also manages models declared; 74 that's the modelsUsed dictionary, mapping prefix -> dm.Model instances 75 """
76 - def __init__(self, mfRegistry=valuemappers.defaultMFRegistry, 77 tablecoding='binary', version=None, acquireSamples=True, 78 suppressNamespace=False, overflowElement=None):
79 self.mfRegistry = mfRegistry 80 self.tablecoding = tablecoding 81 self.version = version or (1,3) 82 self.acquireSamples = acquireSamples 83 self.suppressNamespace = suppressNamespace 84 self.overflowElement = overflowElement 85 self._containerStack = [] 86 self._tableStack = [] 87 self._pushedRefs = {} 88 89 # state for VO-DML serialisation 90 self.produceVODML = self.version[0]>1 or self.version[1]>3 91 # group-serialising annotations must enter their python ids when they 92 # end up in the tree so they get only included once when 93 # referenced by multiple other annotations. 94 self.groupIdsInTree = set() 95 self.modelsUsed = {} 96 self.rootVODML = V.VODML() 97 self.vodmlTemplates = V.TEMPLATES() 98 self.rootVODML[self.vodmlTemplates]
99
100 - def addVODMLPrefix(self, prefix):
101 """arranges the DM with prefix to be included in modelsUsed. 102 """ 103 if prefix not in self.modelsUsed: 104 self.modelsUsed[prefix] = dm.getModelForPrefix(prefix)
105
106 - def addVODMLMaterial(self, stuff):
107 """adds VODML annotation to this VOTable. 108 109 Note that it will only be rendered if produceVODML is true 110 (in general, for target versions >1.3). 111 """ 112 self.vodmlTemplates[stuff]
113
114 - def makeTable(self, table):
115 """returns xmlstan for a table. 116 117 This is exposed as a method of context as the dm subpackage 118 needs it, but I don't want to import formats there (yet). 119 120 This may go away as I fix the interdependence of dm, votable, and 121 format. 122 """ 123 return makeTable(self, table)
124
125 - def getEnclosingTable(self):
126 """returns the xmlstan element of the table currently built. 127 128 This returns a ValueError if the context isn't aware of a table 129 being built. 130 131 (This depends builders using activeContainer) 132 """ 133 for el in reversed(self._containerStack): 134 if el.name_=="TABLE": 135 return el 136 raise ValueError("Not currently building a table.")
137
138 - def getEnclosingResource(self):
139 """returns the xmlstan element of the resource currently built. 140 141 This returns a ValueError if the context isn't aware of a resource 142 being built. 143 144 (This depends builders using activeContainer) 145 """ 146 for el in reversed(self._containerStack): 147 if el.name_=="RESOURCE": 148 return el 149 raise ValueError("Not currently building a table.")
150
151 - def getEnclosingContainer(self):
152 """returns the innermost container element the builders have declared. 153 """ 154 return self._containerStack[-1]
155 156 @property
157 - def currentTable(self):
158 """the DaCHS table object from which things are currently built. 159 160 If no builder has declared a table being built (using buildingFromTable), 161 it's a value error. 162 """ 163 if not self._tableStack: 164 raise ValueError("No table being processed.") 165 return self._tableStack[-1]
166 167 @contextlib.contextmanager
168 - def activeContainer(self, container):
169 """a context manager to be called by VOTable builders when 170 they open a new TABLE or RESOURCE. 171 """ 172 self._containerStack.append(container) 173 try: 174 yield 175 finally: 176 self._containerStack.pop()
177 178 @contextlib.contextmanager
179 - def buildingFromTable(self, table):
180 """a context manager to control code that works on a DaCHS table. 181 """ 182 self._tableStack.append(table) 183 try: 184 yield 185 finally: 186 self._tableStack.pop()
187
188 - def pushRefFor(self, rdEl, refVal):
189 """orders refVal to be set as ref on rdEl's VOTable representation 190 if such a thing is being serialised. 191 192 This currently is more a hack for PARAMs with COOSYS than something 193 that should be really used; if this were to become a general pattern, 194 we should work out a way to assign the ref if rdEl's representation 195 already is in the tree... 196 """ 197 self._pushedRefs[id(rdEl)] = refVal
198
199 - def addID(self, rdEl, votEl):
200 """adds an ID attribute to votEl if rdEl has an id managed by self. 201 202 Also, if a ref has been noted for rdEl, a ref attribute is being 203 added, too. This is a special hack for params and coosys; and I suspect 204 we shouldn't go beyond that. 205 """ 206 try: 207 votEl.ID = self.getIdFor(rdEl) 208 except base.NotFoundError: 209 # the param is not referenced and thus needs no ID 210 pass 211 212 if id(rdEl) in self._pushedRefs: 213 votEl.ref = self._pushedRefs[id(rdEl)] 214 215 return votEl
216
217 218 ################# Turning simple metadata into VOTable elements. 219 220 -def _iterInfoInfos(dataSet):
221 """returns a sequence of V.INFO items from the info meta of dataSet. 222 """ 223 for infoItem in dataSet.getMeta("info", default=[]): 224 name, value, id = infoItem.infoName, infoItem.infoValue, infoItem.infoId 225 yield V.INFO(name=name, value=value, ID=id)[infoItem.getContent()]
226
227 228 -def _iterWarningInfos(dataSet):
229 """yields INFO items containing warnings from the tables in dataSet. 230 """ 231 for table in dataSet.tables.values(): 232 for warning in table.getMeta("_warning", propagate=False, default=[]): 233 yield V.INFO(name="warning", value="In table %s: %s"%( 234 table.tableDef.id, warning.getContent("text", macroPackage=table)))
235
236 237 -def _iterDatalinkResources(ctx, dataSet):
238 """yields RESOURCE elements for datalink services defined for tables 239 we have. 240 241 This needs to be called before the tables are serialised because we 242 put ids on fields. 243 """ 244 for table in dataSet.tables.values(): 245 for svcMeta in table.iterMeta("_associatedDatalinkService"): 246 try: 247 service = base.resolveId(table.tableDef.rd, 248 base.getMetaText(svcMeta, "serviceId")) 249 250 # datalink cannot be globally imported, as we can't depend on 251 # anything in protocols. Importing it here is ok, though; 252 # we'll just not produce datalink declarations if the server 253 # can't do it. 254 from gavo.protocols import datalink 255 yield datalink.makeDatalinkServiceDescriptor( 256 ctx, service, table.tableDef, 257 base.getMetaText(svcMeta, "idColumn")) 258 259 # Temporary hack: for now, if something wants to have immediate 260 # soda resources on results (so far, only SSAP does), they add 261 # generating functions to data.sodaGenerators. This needs 262 # to be replaced with something better when we understand what 263 # we actually want. 264 while getattr(dataSet, "sodaGenerators", []): 265 yield dataSet.sodaGenerators.pop()(ctx) 266 267 except Exception, ex: 268 base.ui.notifyWarning("RD %s: request for datalink service" 269 " could not be satisfied (%s)"%( 270 getattr(table.tableDef.rd, "sourceId", "<internal>"), 271 ex))
272
273 274 275 -def _iterResourceMeta(ctx, dataSet):
276 """adds resource metadata to the Resource parent. 277 """ 278 yield V.DESCRIPTION[base.getMetaText(dataSet, "description", 279 macroPackage=dataSet.dd.rd, propagate=False)] 280 for el in itertools.chain( 281 _iterInfoInfos(dataSet), _iterWarningInfos(dataSet)): 282 yield el 283 284 sourcesSeen, citeLinksSeen = set(), set() 285 for table in dataSet.tables.values(): 286 for m in table.iterMeta("source", propagate="True"): 287 src = m.getContent("text") 288 289 ucd = None 290 if ctx.version>(1,1): 291 if utils.couldBeABibcode(src): 292 ucd = "meta.bib.bibcode" 293 else: 294 ucd = "meta.bib" 295 296 if src not in sourcesSeen: 297 yield V.INFO(name="citation", value=src, ucd=ucd)[ 298 "This resource contains data associated with the publication" 299 " %s."%src] 300 sourcesSeen.add(src) 301 302 if ctx.version>(1,1): 303 ucd = "meta.bib" 304 else: 305 ucd = None 306 307 for m in table.iterMeta("howtociteLink"): 308 link = m.getContent("text") 309 if link not in citeLinksSeen: 310 yield V.INFO(name="citation", value=link, ucd=ucd)[ 311 "For advice on how to cite the resource(s)" 312 " that contributed to this result, see %s"%link] 313 citeLinksSeen.add(link)
314
315 316 -def _iterToplevelMeta(ctx, dataSet):
317 """yields meta elements for the entire VOTABLE from dataSet's RD. 318 """ 319 rd = dataSet.dd.rd 320 if rd is None: 321 return 322 yield V.DESCRIPTION[base.getMetaText(rd, "description", 323 macroPackage=dataSet.dd.rd)] 324 325 for infoItem in rd.iterMeta("copyright"): 326 yield V.INFO(name="legal", value=infoItem.getContent("text", 327 macroPackage=dataSet.dd.rd))
328
329 330 # link elements may be defined using the votlink meta on RESOURCE, TABLE, 331 # GROUP, FIELD, or PARAM; within in the DC, GROUPs have no meta structure, 332 # so we don't run _linkBuilder on them. 333 334 -def _makeLinkForMeta(args, localattrs=None):
335 localattrs.update({"href": args[0]}) 336 return V.LINK(**localattrs)
337 338 339 _linkBuilder = meta.ModelBasedBuilder([ 340 ('votlink', _makeLinkForMeta, (), { 341 "href": "href", 342 "content_role": "role", 343 "content_type": "contentType", 344 "name": "linkname",})])
345 346 347 ################# Generating FIELD and PARAM elements. 348 349 -def _makeValuesForColDesc(colDesc):
350 """returns a VALUES element for a column description. 351 352 This just stringifies whatever is in colDesc's respective columns, 353 so for anything fancy pass in byte strings to begin with. 354 """ 355 valEl = V.VALUES() 356 if colDesc.get("min") is None: 357 colDesc["min"] = getattr(colDesc.original.values, "min", None) 358 if colDesc.get("max") is None: 359 colDesc["max"] = getattr(colDesc.original.values, "max", None) 360 361 if colDesc["max"] is utils.Infimum: 362 colDesc["max"] = None 363 if colDesc["min"] is utils.Supremum: 364 colDesc["min"] = None 365 366 if colDesc["min"] is not None: 367 valEl[V.MIN(value=str(colDesc["min"]))] 368 if colDesc["max"] is not None: 369 valEl[V.MAX(value=str(colDesc["max"]))] 370 if colDesc["nullvalue"] is not None: 371 valEl(null=colDesc["nullvalue"]) 372 373 for option in getattr(colDesc.original.values, "options", []): 374 valEl[V.OPTION(value=option.content_ or "", name=option.title)] 375 376 return valEl
377 378 379 # keys copied from colDescs to FIELDs in _getFieldFor 380 _voFieldCopyKeys = ["name", "datatype", "ucd", "utype", "ref"]
381 382 -def defineField(ctx, element, colDesc):
383 """adds attributes and children to element from colDesc. 384 385 element can be a V.FIELD or a V.PARAM *instance* and is changed in place. 386 387 This function returns None to remind people we're changing in place 388 here. 389 """ 390 # bomb if you got an Element rather than an instance -- with an 391 # Element, things would appear to work, but changes are lost when 392 # this function ends. 393 assert not isinstance(element, type), ("Got FIELD/PARAM element" 394 " instead of instance in VOTable defineField") 395 396 if colDesc["arraysize"]!='1': 397 element(arraysize=colDesc["arraysize"]) 398 # (for char, keep arraysize='1' to keep topcat happy) 399 if colDesc["datatype"]=='char' and colDesc["arraysize"]=='1': 400 element(arraysize='1') 401 402 if colDesc["unit"]: 403 element(unit=colDesc["unit"]) 404 element(ID=colDesc["id"]) 405 406 # don't include xtype if writing 1.1 407 xtype = colDesc.get("xtype") 408 if ctx.version>(1,1): 409 element(xtype=xtype) 410 411 if isinstance(element, V.PARAM): 412 if hasattr(colDesc.original, "getStringValue"): 413 try: 414 element(value=str(colDesc.original.getStringValue())) 415 except: 416 # there's too much that can legitimately go wrong here to bother: 417 pass 418 419 if colDesc.original: 420 rscCol = colDesc.original 421 if rscCol.hasProperty("targetType"): 422 element[V.LINK( 423 content_type=rscCol.getProperty("targetType"), 424 title=rscCol.getProperty("targetTitle", "Link"))] 425 426 element(**dict((key, colDesc.get(key) or None) 427 for key in _voFieldCopyKeys))[ 428 V.DESCRIPTION[colDesc["description"]], 429 _makeValuesForColDesc(colDesc), 430 _linkBuilder.build(colDesc.original) 431 ]
432
433 434 -def makeFieldFromColumn(ctx, colType, rscCol):
435 """returns a VOTable colType for a rscdef column-type thing. 436 437 This function lets you make PARAM and FIELD elements (colType) from 438 column or param instances. 439 """ 440 instance = colType() 441 defineField(ctx, instance, valuemappers.AnnotatedColumn(rscCol)) 442 return instance
443
444 445 -def _iterFields(ctx, serManager):
446 """iterates over V.FIELDs based on serManger's columns. 447 """ 448 for colDesc in serManager: 449 el = V.FIELD() 450 defineField(ctx, el, colDesc) 451 yield el
452
453 454 -def _makeVOTParam(ctx, param):
455 """returns VOTable stan for param. 456 """ 457 # note that we're usually accessing the content, i.e., the string 458 # serialization we got. The only exception is when we're seeing 459 # nulls or null-equivalents. 460 if param.content_ is base.NotGiven or param.value is None: 461 content = None 462 else: 463 content = param.content_ 464 465 el = V.PARAM() 466 defineField(ctx, el, valuemappers.AnnotatedColumn(param)) 467 if content is None: 468 el.value = "" 469 else: 470 el.value = content 471 return el
472
473 474 -def _iterTableParams(ctx, serManager):
475 """iterates over V.PARAMs based on the table's param elements. 476 """ 477 for param in serManager.table.iterParams(): 478 votEl = _makeVOTParam(ctx, param) 479 if votEl is not None: 480 ctx.addID(param, votEl) 481 yield votEl
482
483 484 -def _iterParams(ctx, dataSet):
485 """iterates over the entries in the parameters table of dataSet. 486 """ 487 # deprecate this. The parameters table of a data object was a grave 488 # mistake. 489 # Let's see who's using it and then remove this in favor of actual 490 # data parameters (or table parameters) 491 try: 492 parTable = dataSet.getTableWithRole("parameters") 493 except base.DataError: # no parameter table 494 return 495 496 warnings.warn("Parameters table used. You shouldn't do that any more.") 497 values = {} 498 if parTable: # no data for parameters: keep empty values. 499 values = parTable.rows[0] 500 501 for item in parTable.tableDef: 502 colDesc = valuemappers.AnnotatedColumn(item) 503 el = V.PARAM() 504 el(value=ctx.mfRegistry.getMapper(colDesc)(values.get(item.name))) 505 defineField(ctx, el, colDesc) 506 ctx.addID(el, item) 507 yield el
508 509 510 ####################### Tables and Resources 511 512 # an ad-hoc mapping of what STC1 has in frame to what VOTable 1.1 has for 513 # COOSYS/@system 514 STC_FRAMES_TO_COOSYS = { 515 'ICRS': 'ICRS', 516 'FK5': 'eq_FK5', 517 'FK4': 'eq_FK4', 518 'ECLIPTIC': 'ecl_FK5', # neglecting the odds there's actually ecl_FK4 data 519 'GALACTIC_II': 'galactic', 520 'SUPERGALACTIC': 'supergalactic'} 521 522 # these are utypes for which column refs should be ref-ed to the COOSYS. 523 COLUMN_REF_UTYPES = [ 524 "stc:astrocoords.position2d.value2.c1", 525 "stc:astrocoords.position2d.value2.c2", 526 "stc:astrocoords.position2d.error2.c1", 527 "stc:astrocoords.position2d.error2.c2", 528 "stc:astrocoords.velocity2d.value2.c1", 529 "stc:astrocoords.velocity2d.value2.c2", 530 "stc:astrocoords.velocity2d.error2.c1", 531 "stc:astrocoords.velocity2d.error2.c2", 532 "stc:astrocoords.redshift.value", 533 "stc:astrocoords.position3d.value3.c1", 534 "stc:astrocoords.position3d.value3.c2", 535 "stc:astrocoords.position3d.value3.c3", 536 "stc:astrocoords.time.timeinstant", 537 "stc:astrocoords.velocity3d.value3.c1", 538 "stc:astrocoords.velocity3d.value3.c2", 539 "stc:astrocoords.velocity3d.value3.c3", 540 "stc:astrocoords.position2d.epoch", 541 ]
542 543 544 -def _makeCOOSYSFromSTC1(utypeMap, serManager):
545 """returns a VOTable 1.1 COOSYS element inferred from a map of stc utypes 546 to STC1 values. 547 548 We let through coordinate frames not defined in VOTable 1.1 at the 549 expense of making the VOTables XSD-invalid with such frames; this 550 seems preferable to not declaring anything. 551 552 As a side effect, this will change column/@ref attributes (possibly also 553 param/@ref). If a column is part of two STC structures, the first 554 one will win. Yeah, that spec sucks. 555 """ 556 coosys = V.COOSYS() 557 sysId = serManager.makeIdFor(coosys, "system") 558 coosys(ID=sysId) 559 560 if "stc:astrocoords.position2d.epoch" in utypeMap: 561 epVal = utypeMap["stc:astrocoords.position2d.epoch"] 562 if not isinstance(epVal, stc.ColRef): 563 # If the epoch is not a column reference, inline it. 564 # (the code below will ignore it in that case) 565 coosys(epoch="%s%s"%( 566 utypeMap.get("stc:astrocoords.position2d.epoch.yeardef", "J"), 567 epVal)) 568 569 stcFrame = utypeMap.get( 570 "stc:astrocoordsystem.spaceframe.coordrefframe", None) 571 coosys(system=STC_FRAMES_TO_COOSYS.get(stcFrame, stcFrame)) 572 573 for utype in COLUMN_REF_UTYPES: 574 if utype in utypeMap: 575 val = utypeMap[utype] 576 if isinstance(val, stc.ColRef): 577 col = serManager.getColumnByName(str(val)) 578 if not col.get("ref"): 579 col["ref"] = sysId 580 581 return coosys
582
583 584 -def _iterSTC(ctx, tableDef, serManager):
585 """adds STC groups for the systems to votTable fetching data from 586 tableDef. 587 """ 588 def getColumnFor(colRef): 589 try: 590 return serManager.getColumnByName(colRef.dest) 591 except KeyError: 592 # in ADQL processing, names are lower-cased, and there's not 593 # terribly much we can do about it without breaking other things. 594 # Hence, let's try and see whether our target is there with 595 # case normalization: 596 return serManager.getColumnByName(colRef.dest.lower())
597 598 def getIdFor(colRef): 599 return getColumnFor(colRef)["id"] 600 601 for ast in tableDef.getSTCDefs(): 602 container, utypeMap = modelgroups.marshal_STC(ast, getIdFor) 603 if ctx.version>(1,1): 604 # "Note-style" STC only supported in 1.2 and higher 605 yield container 606 # legacy COOSYS specification supported everywhere 607 ctx.getEnclosingResource()[ 608 _makeCOOSYSFromSTC1(utypeMap, serManager)] 609
610 611 -def _addStupidRefByAnnotation(ctx, annotation, serManager, destId):
612 """adds a @ref attribute to destId to a PARAM or FIELD that 613 annotation references. 614 615 This is for legacy TIMESYS or COOSYS. Sigh. 616 """ 617 if isinstance(annotation, dm.ColumnAnnotation): 618 col = serManager.getColumnByName(annotation.value.name) 619 if not col.get("ref"): 620 col["ref"] = destId 621 622 elif isinstance(annotation, dm.ParamAnnotation): 623 ctx.pushRefFor( 624 serManager.table.getParamByName(annotation.value.name), 625 destId)
626
627 628 -def _addLegacySYSFromSTC2(ctx, tableDef, serManager):
629 """adds COOSYS and TIMESYS elements from stc2 annotation to the enclosing 630 RESOURCE. 631 """ 632 for ann in tableDef.iterAnnotationsOfType("stc2:Coords"): 633 if "time" in ann: 634 timeFrame = ann["time"].get("frame", {}) 635 timesysEl = V.TIMESYS( 636 timescale=timeFrame.get("timescale"), 637 refposition=timeFrame.get("refPosition"), 638 timeorigin=timeFrame.get("time0")) 639 timesysId = serManager.makeIdFor(timesysEl, "ts") 640 timesysEl(ID=timesysId) 641 ctx.getEnclosingResource()[ 642 timesysEl] 643 _addStupidRefByAnnotation( 644 ctx, 645 ann["time"]["location"], 646 serManager, 647 timesysId) 648 649 if "space" in ann: 650 spaceFrame = ann["space"]["frame"] 651 coosys = V.COOSYS() 652 sysId = serManager.makeIdFor(coosys, "system") 653 # we probably should be more careful with literals vs. references 654 # for the orientation, too 655 coosys(ID=sysId, 656 system=spaceFrame.get("orientation")) 657 epVal = spaceFrame.get("epoch") 658 if isinstance(epVal, basestring): 659 coosys(epoch=epVal) 660 661 # this would probably need to recurse into children. Let's see 662 # how the DM will look like in the end 663 for child in ann["space"].iterChildRoles(): 664 _addStupidRefByAnnotation(ctx, child, serManager, sysId) 665 666 ctx.getEnclosingResource()[coosys]
667
668 669 -def _iterNotes(serManager):
670 """yields GROUPs for table notes. 671 672 The idea is that the note is in the group's description, and the FIELDrefs 673 give the columns that the note applies to. 674 """ 675 # add notes as a group with FIELDrefs, but don't fail on them 676 for key, note in serManager.notes.iteritems(): 677 noteId = serManager.getOrMakeIdFor(note) 678 noteGroup = V.GROUP(name="note-%s"%key, ID=noteId)[ 679 V.DESCRIPTION[note.getContent(targetFormat="text")]] 680 for col in serManager: 681 if col["note"] is note: 682 noteGroup[V.FIELDref(ref=col["id"])] 683 yield noteGroup
684
685 686 -def _makeRef(baseType, ref, container, serManager):
687 """returns a new node of baseType reflecting the group.TypedRef 688 instance ref. 689 690 container is the destination of the reference. For columns, that's 691 the table definition, but for parameters, this must be the table 692 itself rather than its definition because it's the table's 693 params that are embedded in the VOTable. 694 """ 695 return baseType( 696 ref=serManager.getOrMakeIdFor(ref.resolve(container)), 697 utype=ref.utype, 698 ucd=ref.ucd)
699
700 701 -def _iterGroups(ctx, container, serManager):
702 """yields GROUPs for the RD groups within container, taking params and 703 fields from serManager's table. 704 705 container can be a tableDef or a group. 706 """ 707 for group in container.groups: 708 votGroup = V.GROUP(ucd=group.ucd, utype=group.utype, name=group.name) 709 votGroup[V.DESCRIPTION[group.description]] 710 711 for ref in group.columnRefs: 712 votGroup[_makeRef(V.FIELDref, ref, 713 serManager.table.tableDef, serManager)] 714 715 for ref in group.paramRefs: 716 votGroup[_makeRef(V.PARAMref, ref, 717 serManager.table, serManager)] 718 719 for param in group.params: 720 votGroup[_makeVOTParam(ctx, param)] 721 722 for subgroup in _iterGroups(ctx, group, serManager): 723 votGroup[subgroup] 724 725 yield votGroup
726
727 728 -def makeTable(ctx, table):
729 """returns a Table node for the table.Table instance table. 730 """ 731 sm = valuemappers.SerManager(table, mfRegistry=ctx.mfRegistry, 732 idManager=ctx, acquireSamples=ctx.acquireSamples) 733 734 # this must happen before FIELDs and such are serialised to ensure 735 # referenced things have IDs. 736 737 result = V.TABLE() 738 with ctx.activeContainer(result): 739 # start out with VO-DML annotation so everything that needs 740 # an id has one. 741 if ctx.produceVODML: 742 for ann in table.tableDef.annotations: 743 try: 744 ctx.addVODMLMaterial(ann.getVOT(ctx, table)) 745 except Exception as msg: 746 # never fail just because stupid DM annotation doesn't work out 747 base.ui.notifyError("%s-typed DM annotation failed: %s"%( 748 ann.type, msg)) 749 750 # iterate STC before serialising the columns so the columns 751 # have the stupid ref to COOSYS 752 result[_iterSTC(ctx, table.tableDef, sm)] 753 754 # same for STC2 (except here the FIELDs need reliable IDs) 755 _addLegacySYSFromSTC2(ctx, table.tableDef, sm) 756 757 result( 758 name=table.tableDef.id, 759 utype=base.getMetaText(table, "utype", macroPackage=table.tableDef, 760 propagate=False))[ 761 # _iterGroups must run before _iterFields and _iterParams since it 762 # may need to add ids to the respective items. XSD-correct ordering of 763 # the elements is done by xmlstan. 764 V.DESCRIPTION[base.getMetaText(table, "description", 765 macroPackage=table.tableDef, propagate=False)], 766 _iterGroups(ctx, table.tableDef, sm), 767 _iterFields(ctx, sm), 768 _iterTableParams(ctx, sm), 769 _iterNotes(sm), 770 _linkBuilder.build(table.tableDef), 771 ] 772 773 774 return votable.DelayedTable(result, 775 sm.getMappedTuples(), 776 tableEncoders[ctx.tablecoding], 777 overflowElement=ctx.overflowElement)
778
779 780 -def _makeResource(ctx, data):
781 """returns a Resource node for the rsc.Data instance data. 782 """ 783 res = V.RESOURCE() 784 with ctx.activeContainer(res): 785 res(type=base.getMetaText(data, "_type"), 786 utype=base.getMetaText(data, "utype"))[ 787 _iterResourceMeta(ctx, data), 788 _iterParams(ctx, data), [ 789 _makeVOTParam(ctx, param) for param in data.iterParams()], 790 _linkBuilder.build(data.dd), 791 ] 792 for table in data: 793 with ctx.buildingFromTable(table): 794 res[makeTable(ctx, table)] 795 res[ctx.overflowElement] 796 return res
797 798 ############################# Toplevel/User-exposed code 799 800 makeResource = _makeResource
801 802 803 -def makeVOTable(data, ctx=None, **kwargs):
804 """returns a votable.V.VOTABLE object representing data. 805 806 data can be an rsc.Data or an rsc.Table. data can be a data or a table 807 instance, tablecoding any key in votable.tableEncoders. 808 809 You may pass a VOTableContext object; if you don't a context 810 with all defaults will be used. 811 812 A deprecated alternative is to directly pass VOTableContext constructor 813 arguments as additional keyword arguments. Don't do this, though, 814 we'll probably remove the option to do so at some point. 815 816 You will usually pass the result to votable.write. The object returned 817 contains DelayedTables, i.e., most of the content will only be realized at 818 render time. 819 """ 820 ctx = ctx or VOTableContext(**kwargs) 821 822 data = rsc.wrapTable(data) 823 if ctx.version==(1,1): 824 vot = V.VOTABLE11() 825 elif ctx.version==(1,2): 826 vot = V.VOTABLE12() 827 elif ctx.version==(1,3): 828 vot = V.VOTABLE() 829 elif ctx.version==(1,4): 830 vot = V.VOTABLE() # TODO: When 1.4 XSD comes out, actually implement 831 else: 832 raise votable.VOTableError("No toplevel element for VOTable version %s"% 833 ctx.version) 834 835 dlResources = list(_iterDatalinkResources(ctx, data)) 836 vot[_iterToplevelMeta(ctx, data)] 837 vot[_makeResource(ctx, data)] 838 vot[dlResources] 839 840 if ctx.produceVODML: 841 if ctx.modelsUsed: 842 # if we declare any models, we'll need vo-dml 843 ctx.addVODMLPrefix("vo-dml") 844 845 ctx.rootVODML[[ 846 model.getVOT(ctx, None) 847 for model in ctx.modelsUsed.values()]] 848 849 vot[ctx.rootVODML] 850 851 if ctx.suppressNamespace: 852 # use this for "simple" table with nice element names 853 vot._fixedTagMaterial = "" 854 855 # What follows is a hack around the insanity of stuffing 856 # unused namespaces and similar detritus into VOTable's roots. 857 rootAttrs = data.getMeta("_votableRootAttributes") 858 if rootAttrs: 859 rootHacks = [vot._fixedTagMaterial]+[ 860 item.getContent() for item in rootAttrs] 861 vot._fixedTagMaterial = " ".join(s for s in rootHacks if s) 862 863 864 return vot
865
866 867 -def writeAsVOTable(data, outputFile, ctx=None, **kwargs):
868 """writes ``data`` to the ``outputFile``. 869 870 data can be a table or ``Data`` item. 871 872 ``ctx`` can be a ``VOTableContext`` instance; alternatively, 873 ``VOTableContext`` constructor arguments can be passed in as 874 ``kwargs``. 875 """ 876 ctx = ctx or VOTableContext(**kwargs) 877 vot = makeVOTable(data, ctx) 878 votable.write(vot, outputFile)
879
880 881 -def getAsVOTable(data, ctx=None, **kwargs):
882 """returns a string containing a VOTable representation of data. 883 884 ``kwargs`` can be constructor arguments for VOTableContext. 885 """ 886 ctx = ctx or VOTableContext(**kwargs) 887 dest = StringIO() 888 writeAsVOTable(data, dest, ctx) 889 return dest.getvalue()
890
891 892 -def format(data, outputFile, **ctxargs):
893 # used for construction of the formats.common interface 894 return writeAsVOTable(data, outputFile, VOTableContext(**ctxargs))
895 896 common.registerDataWriter("votable", format, 897 base.votableType, "Default VOTable", ".vot") 898 common.registerDataWriter("votableb2", functools.partial( 899 format, tablecoding="binary2"), 900 "application/x-votable+xml;serialization=BINARY2", 901 "Binary2 VOTable", 902 ".votb2") 903 common.registerDataWriter("votabletd", functools.partial( 904 format, tablecoding="td"), 905 "application/x-votable+xml;serialization=TABLEDATA", "Tabledata VOTable", 906 ".vottd", 907 "text/xml") 908 common.registerDataWriter("votabletd1.1", functools.partial( 909 format, tablecoding="td", version=(1,1)), 910 "application/x-votable+xml;serialization=TABLEDATA;version=1.1", 911 "Tabledata VOTable version 1.1", 912 ".vot1", 913 "text/xml") 914 common.registerDataWriter("votable1.1", functools.partial( 915 format, tablecoding="binary", version=(1,1)), 916 "application/x-votable+xml;version=1.1", 917 "Tabledata VOTable version 1.1", 918 ".vot1", 919 "text/xml") 920 common.registerDataWriter("votabletd1.2", functools.partial( 921 format, tablecoding="td", version=(1,2)), 922 "application/x-votable+xml;serialization=TABLEDATA;version=1.2", 923 "Tabledata VOTable version 1.2", 924 ".vot2", 925 "text/xml") 926 common.registerDataWriter("vodml", functools.partial( 927 format, tablecoding="td", version=(1,4)), 928 "application/x-votable+xml;serialization=TABLEDATA;version=1.4", 929 "VOTable version 1.4", 930 ".vot4") 931