Package gavo :: Package stc :: Module stcxast
[frames] | no frames]

Source Code for Module gavo.stc.stcxast

   1  """ 
   2  Building ASTs from STC-X trees. 
   3   
   4  The idea here is run buildTree on an ElementTree of the STC-X input. 
   5   
   6  buildTree has a dictionary mapping element names to handlers.  This dictionary 
   7  is built with a modicum of metaprogramming within _getHandlers. 
   8   
   9  Each handler receives the ElementTree node it is to operate on, the current 
  10  buildArgs (i.e., a dictionary containing for building instances collected by 
  11  buildTree while walking the tree), and a context object. 
  12   
  13  Handlers yield keyword-value pairs that are added to the buildArgs.  If 
  14  the value is a tuple or list, it will be appended to the current value 
  15  for that keyword, otherwise it will fill this keyword.  Overwrites are 
  16  not allowed. 
  17  """ 
  18   
  19  #c Copyright 2008-2019, the GAVO project 
  20  #c 
  21  #c This program is free software, covered by the GNU GPL.  See the 
  22  #c COPYING file in the source distribution. 
  23   
  24   
  25  # TODO: throw out all XML namespacing and only operate on local names 
  26  # throughout (well, actually: throw out the whole thing:-) 
  27   
  28  from gavo import utils 
  29  from gavo.stc import common 
  30  from gavo.stc import dm 
  31  from gavo.stc import syslib 
  32  from gavo.stc import times 
  33  from gavo.stc import units 
  34   
  35   
  36  WIGGLE_TYPES = ["error", "resolution", "size", "pixSize"] 
  37   
  38   
39 -class SIBLING_ASTRO_SYSTEM(object):
40 """A sentinel class to tell the id resolver to use the sibling AstroCoordSys 41 element."""
42 43 44 ####################### Helpers 45
46 -def STCElement(name):
47 return utils.ElementTree.QName(common.STCNamespace, name)
48 _n = STCElement 49 50
51 -def _localname(qName):
52 """hacks the local tag name from a {ns}-serialized qName. 53 """ 54 qName = str(qName) 55 return qName[qName.find("}")+1:]
56 57
58 -def _passthrough(node, buildArgs, context):
59 """yields the items of buildArgs. 60 61 This can be used for "no-op" elements. 62 """ 63 return buildArgs.iteritems()
64 65
66 -def _noIter(ign, ored):
67 if False: yield None
68
69 -def _buildTuple(val):
70 return (val,)
71 72
73 -def _makeUnitYielder(unitKeys, prefix="", tuplify=False):
74 """returns a function that yields unit information from an elementTree 75 node. 76 """ 77 if tuplify: 78 mkRes = _buildTuple 79 else: 80 mkRes = utils.identity 81 def yieldUnits(node, buildArgs): 82 for key in unitKeys: 83 if key in node.attrib: 84 yield prefix+key, mkRes(node.get(key)) 85 elif key in buildArgs: 86 yield prefix+key, buildArgs[key]
87 return yieldUnits 88 89
90 -def _makeKeywordBuilder(kw):
91 """returns a builder that returns the node's text content under kw. 92 """ 93 def buildKeyword(node, buildArgs, context): 94 yield kw, node.text
95 return buildKeyword 96 97
98 -def _assembleVals(buildArgs, context):
99 """takes C1, C2, C3 from buildArgs to add a vals key to buildArgs. 100 101 If vals already is there, nothing is changed. 102 103 Calling this is necessary for vector-valued values. buildArgs is changed 104 in place. 105 """ 106 if "vals" in buildArgs: 107 return 108 109 vals = [] 110 for cooInd in range(context.peekDim()): 111 vals.append(buildArgs.pop("C%d"%(cooInd+1), None)) 112 if set(vals)==frozenset([None]): 113 # no coordinates given, don't bother 114 pass 115 else: 116 buildArgs['vals'] = tuple(vals)
117 118
119 -def _makeKwValuesBuilder(kwName, tuplify=False, units=_noIter):
120 """returns a builder that takes vals from the buildArgs and 121 returns a tuple of them under kwName. 122 123 The vals key is left by builders like _buildVector. 124 """ 125 if tuplify: 126 def buildNode(node, buildArgs, context): 127 _assembleVals(buildArgs, context) 128 yield kwName, (tuple(buildArgs["vals"]),) 129 for res in units(node, buildArgs): yield res
130 else: 131 def buildNode(node, buildArgs, context): #noflake: previous def conditional 132 _assembleVals(buildArgs, context) 133 yield kwName, (buildArgs["vals"],) 134 for res in units(node, buildArgs): yield res 135 return buildNode 136 137
138 -def _makeKwValueBuilder(kwName, tuplify=False, units=_noIter):
139 """returns a builder that takes vals from the buildArgs and 140 returns a single value under kwName. 141 142 The vals key is left by builders like _buildVector. 143 """ 144 if tuplify: 145 def buildNode(node, buildArgs, context): 146 _assembleVals(buildArgs, context) 147 yield kwName, tuple(buildArgs.get("vals", ())), 148 for res in units(node, buildArgs): yield res
149 else: 150 def buildNode(node, buildArgs, context): #noflake: previous def conditional 151 _assembleVals(buildArgs, context) 152 yield kwName, buildArgs.get("vals", None), 153 for res in units(node, buildArgs): yield res 154 return buildNode 155 156
157 -def _makeKwFloatBuilder(kwName, multiple=True, units=_noIter):
158 """returns a builder that returns float(node.text) under kwName. 159 160 The builder will also yield unit keys if units are present. 161 162 If multiple is True, the values will be returned in 1-tuples, else as 163 simple values. 164 """ 165 if multiple: 166 def buildNode(node, buildArgs, context): 167 if isinstance(node.text, common.ColRef): 168 yield kwName, (node.text,) 169 elif node.text and node.text.strip(): 170 yield kwName, (float(node.text),) 171 for res in units(node, buildArgs): yield res
172 else: 173 def buildNode(node, buildArgs, context): #noflake: previous def conditional 174 if isinstance(node.text, common.ColRef): 175 yield kwName, node.text 176 elif node.text and node.text.strip(): 177 yield kwName, float(node.text) 178 for res in units(node, buildArgs): yield res 179 return buildNode 180 181
182 -def _makeNodeBuilder(kwName, astObject):
183 """returns a builder that makes astObject with the current buildArgs 184 and returns the thing under kwName. 185 """ 186 def buildNode(node, buildArgs, context): 187 buildArgs["id"] = node.get("id", None) 188 yield kwName, astObject(**buildArgs)
189 return buildNode 190 191
192 -def _fixSpectralUnits(node, buildArgs, context):
193 unit = None 194 if "unit" in node.attrib: 195 unit = node.get("unit") 196 if "unit" in buildArgs: 197 unit = buildArgs["unit"] 198 if "spectral_unit" in buildArgs: 199 unit = buildArgs["spectral_unit"] 200 del buildArgs["spectral_unit"] 201 buildArgs["unit"] = unit
202 203
204 -def _fixTimeUnits(node, buildArgs, context):
205 unit = None 206 if "unit" in node.attrib: 207 unit = node.get("unit") 208 if "unit" in buildArgs: 209 unit = buildArgs["unit"] 210 if "time_unit" in buildArgs: 211 unit = buildArgs["time_unit"] 212 del buildArgs["time_unit"] 213 buildArgs["unit"] = unit
214 215
216 -def _fixRedshiftUnits(node, buildArgs, context):
217 sUnit = node.get("unit") 218 if "unit" in buildArgs: 219 sUnit = buildArgs["unit"] 220 if "pos_unit" in buildArgs: 221 sUnit = buildArgs["pos_unit"] 222 del buildArgs["pos_unit"] 223 vUnit = node.get("vel_time_unit") 224 if "vel_time_unit" in buildArgs: 225 vUnit = buildArgs["vel_time_unit"] 226 del buildArgs["vel_time_unit"] 227 buildArgs["unit"] = sUnit 228 buildArgs["velTimeUnit"] = vUnit
229 230
231 -def _makeSpatialUnits(nDim, *unitSources):
232 """returns a units value from unitSources. 233 234 The tuple has length nDim, unitSources are arguments that are either 235 None, strings, or tuples. The first non-None-one wins, strings and 1-tuples 236 are expanded to length nDim. 237 """ 238 for unit in unitSources: 239 if not unit: 240 continue 241 if isinstance(unit, (tuple, list)): 242 if len(unit)==1: 243 return tuple(unit*nDim) 244 elif len(unit)==nDim: 245 return tuple(unit) 246 else: 247 raise common.STCValueError("Cannot construct %d-dimensional units from" 248 " %s."%(nDim, repr(unit))) 249 else: # a string or something similar 250 return (unit,)*nDim 251 return None
252 253
254 -def _fixSpatialUnits(node, buildArgs, context):
255 nDim = context.peekDim() 256 257 # buildArgs["unit"] may have been left in build_args from upstream 258 buildArgs["unit"] = _makeSpatialUnits(nDim, buildArgs.pop("unit", None), 259 node.get("unit", "").split()) 260 261 # This only kicks in for velocities 262 buildArgs["velTimeUnit"] = _makeSpatialUnits(nDim, 263 buildArgs.pop("vel_time_unit", ()), node.get("vel_time_unit", "").split()) 264 if not buildArgs["velTimeUnit"]: 265 del buildArgs["velTimeUnit"] 266 267 if not buildArgs["unit"]: 268 # it's actually legal to have to unit on the position but on some 269 # wiggle (sigh). Adopt the first one we find if that's true. 270 for wiggleType in WIGGLE_TYPES: 271 if wiggleType in buildArgs: 272 if buildArgs[wiggleType].origUnit: 273 buildArgs["unit"] = buildArgs[wiggleType].origUnit 274 break 275 else: 276 del buildArgs["unit"] 277 278 # sometimes people give position1d for nDim=2... *Presumably* 279 # actual units are the same on both axes then. Sigh 280 mainUnit = buildArgs.get("unit") 281 if mainUnit and len(mainUnit)==2 and mainUnit[1] is None: 282 buildArgs["unit"] = (mainUnit[0], mainUnit[0])
283 284 285 _unitFixers = { 286 "spectralFrame": _fixSpectralUnits, 287 "redshiftFrame": _fixRedshiftUnits, 288 "timeFrame": _fixTimeUnits, 289 "spaceFrame": _fixSpatialUnits, 290 } 291
292 -def _fixUnits(frameName, node, buildArgs, context):
293 """changes the keys in buildArgs to match the requirements of node. 294 295 This fans out to frame type-specific helper functions. The principle is: 296 Attributes inherited from lower-level items (i.e. the specific values) 297 override a unit specification on node. 298 """ 299 return _unitFixers[frameName](node, buildArgs, context)
300 301
302 -def _iterCooMeta(node, context, frameName):
303 """yields various meta information for coordinate-like objects. 304 305 For frame, it returns a proxy for a coordinate's reference frame. 306 For unit, if one is given on the element, override whatever we may 307 have got from downtree. 308 309 Rules for inferring the frame: 310 311 If there's a frame id on node, use it. 312 313 Else see if there's a coo sys id on the frame. If it's missing, take 314 it from the context, then make a proxy to the referenced system's 315 spatial frame. 316 """ 317 if "frame_id" in node.attrib: 318 yield "frame", IdProxy(idref=node.get("frame_id")) 319 elif "coord_system_id" in node.attrib: 320 yield "frame", IdProxy(idref=node.get("frame_id"), useAttr=frameName) 321 else: 322 yield "frame", IdProxy(idref=context.sysIdStack[-1], 323 useAttr=frameName) 324 if "fill_factor" in node.attrib and node.get("fill_factor"): 325 yield "fillFactor", float(node.get("fill_factor")) 326 if "id" in node.attrib and node.get("id"): 327 yield "id", node.get("id")
328 329 330 # A dictionary mapping STC-X element names to the dimensionality of 331 # coordinates within them. You only need to give them here if _guessNDim 332 # doesn't otherwise the it right. 333 _dimExceptions = { 334 } 335
336 -def _guessNDim(kw):
337 """guesses the number of dimensions that should be present under the STC-X 338 element named kw by inspecting the name. 339 """ 340 if kw in _dimExceptions: 341 return _dimExceptions[kw] 342 if "3" in kw: 343 return 3 344 elif "2" in kw: 345 return 2 346 else: 347 return 1
348 349
350 -def _makeIntervalBuilder(kwName, astClass, frameName, tuplify=False):
351 """returns a builder that makes astObject with the current buildArgs 352 and fixes its frame reference. 353 """ 354 if tuplify: 355 def mkVal(v): 356 if isinstance(v, (tuple, list)): 357 return v 358 else: 359 return (v,)
360 else: 361 def mkVal(v): #noflake: previous def conditional 362 return v 363 def buildNode(node, buildArgs, context): 364 for key, value in _iterCooMeta(node, context, frameName): 365 buildArgs[key] = value 366 if "lowerLimit" in buildArgs: 367 buildArgs["lowerLimit"] = mkVal(buildArgs["lowerLimit"][0]) 368 if "upperLimit" in buildArgs: 369 buildArgs["upperLimit"] = mkVal(buildArgs["upperLimit"][0]) 370 _fixUnits(frameName, node, buildArgs, context) 371 buildArgs["origUnit"] = (buildArgs.pop("unit", None), 372 buildArgs.pop("velTimeUnit", None)) 373 yield kwName, (astClass(**buildArgs),) 374 return buildNode 375 376
377 -def _fixWiggles(buildArgs):
378 """modifies buildArgs so all wiggles are properly wrapped in their 379 classes. 380 """ 381 for wiggleType in WIGGLE_TYPES: 382 localArgs = {} 383 wigClass = None 384 385 # pop any units destined for us from buildArgs -- boy, this whole 386 # units stuff is messy. How the heck was that meant to work? 387 velTimeUnit = buildArgs.pop(wiggleType+"vel_time_unit", None) 388 if wiggleType+"unit" in buildArgs: 389 localArgs["origUnit"] = (buildArgs.pop(wiggleType+"unit", None), 390 velTimeUnit) 391 if wiggleType+"pos_unit" in buildArgs: 392 localArgs["origUnit"] = (buildArgs.pop(wiggleType+"pos_unit", None), 393 velTimeUnit) 394 395 if wiggleType in buildArgs: 396 localArgs["values"] = tuple(buildArgs.pop(wiggleType)) 397 wigClass = dm.CooWiggle 398 elif wiggleType+"Radius" in buildArgs: 399 wigClass = dm.RadiusWiggle 400 localArgs["radii"] = buildArgs.pop(wiggleType+"Radius") 401 elif wiggleType+"Matrix" in buildArgs: 402 localArgs["matrices"] = buildArgs.pop(wiggleType+"Matrix") 403 wigClass = dm.MatrixWiggle 404 if wigClass is not None: 405 buildArgs[wiggleType] = wigClass(**localArgs)
406 407
408 -def _makePositionBuilder(kw, astClass, frameName, tuplify=False):
409 """returns a builder for a coordinate of astClass to be added with kw. 410 """ 411 def buildPosition(node, buildArgs, context): 412 _assembleVals(buildArgs, context) 413 if buildArgs.get("vals"): 414 buildArgs["value"] = buildArgs["vals"][0] 415 # Fix 1D space coordinates 416 if tuplify and not isinstance(buildArgs["value"], (list, tuple)): 417 buildArgs["value"] = (buildArgs["value"],) 418 del buildArgs["vals"] 419 for key, value in _iterCooMeta(node, context, frameName): 420 buildArgs[key] = value 421 _fixWiggles(buildArgs) 422 _fixUnits(frameName, node, buildArgs, context) 423 yield kw, astClass(**buildArgs)
424 return buildPosition 425 426
427 -class ContextActions(object):
428 """A specification of context actions for certain elements. 429 430 You will want to override both start and stop. The methods 431 should not change node. 432 """
433 - def start(self, context, node):
434 pass
435
436 - def stop(self, context, node):
437 pass
438 439 440 ################ Coordinate systems 441 442 _xlinkHref = utils.ElementTree.QName(common.XlinkNamespace, "href") 443
444 -def _buildAstroCoordSystem(node, buildArgs, context):
445 buildArgs["id"] = node.get("id", None) 446 # allow non-qnamed href, too, mainly for accomodating our 447 # namespace-eating relational resource importer 448 if _xlinkHref in node.attrib: 449 hrefVal = node.attrib[_xlinkHref] 450 elif "xlink:href" in node.attrib: 451 hrefVal = node.attrib["xlink:href"] 452 else: 453 hrefVal = None 454 455 if hrefVal is None: 456 newEl = dm.CoordSys(**buildArgs) 457 else: 458 newEl = syslib.getLibrarySystem(hrefVal).change(**buildArgs) 459 460 # Hack -- make sure we have a good id here, even when this means 461 # a violation of our non-mutability ideology. 462 if newEl.id is None: 463 newEl.id = utils.intToFunnyWord(id(newEl)) 464 yield "astroSystem", newEl
465 466
467 -def _buildPlanetaryEphem(node, buildArgs, context):
468 res = node.text.strip() 469 if res: 470 yield 'planetaryEphemeris', res
471 472
473 -def _buildRefpos(node, buildArgs, context):
474 refposName = _localname(node.tag) 475 if refposName=="UNKNOWNRefPos": 476 refposName = None 477 yield 'refPos', dm.RefPos(standardOrigin=refposName, 478 **buildArgs)
479
480 -def _buildFlavor(node, buildArgs, context):
481 yield 'flavor', _localname(node.tag) 482 naxes = node.get("coord_naxes") 483 if naxes is not None: 484 yield 'nDim', int(naxes)
485
486 -def _buildRefFrame(node, buildArgs, context):
487 frameName = _localname(node.tag) 488 if frameName=="UNKNOWNFrame": 489 yield 'refFrame', None 490 else: 491 yield 'refFrame', frameName 492 for item in buildArgs.iteritems(): 493 yield item
494
495 -def _makeFrameBuilder(attName, frameObj, **defaults):
496 """returns a function yielding keywords for frames. 497 498 You can pass additional defaults. 499 """ 500 def buildFrame(node, buildArgs, context): 501 if "value_type" in node.attrib: # for redshifts 502 buildArgs["type"] = node.get("value_type") 503 buildArgs["id"] = node.get("id") 504 505 for key, val in defaults.iteritems(): 506 if key not in buildArgs: 507 buildArgs[key] = val 508 yield attName, frameObj(**buildArgs)
509 return buildFrame 510 511 512 ################# Coordinates 513
514 -class CooSysActions(ContextActions):
515 """Actions for containers of coordinates. 516 517 The actions push and pop the system ids of the coordinate containers 518 so leaves can build their frame proxies from them. 519 520 If none are present, None is pushed, which is to be understood as 521 "use any ol' AstroCoordSystem you can find". 522 """
523 - def start(self, context, node):
524 context.sysIdStack.append(node.get("coord_system_id", 525 SIBLING_ASTRO_SYSTEM))
526
527 - def stop(self, context, node):
528 context.sysIdStack.pop()
529 530
531 -def _buildTime(node, buildArgs, context):
532 """adds vals from the time node. 533 534 node gets introspected to figure out what kind of time we're talking 535 about. The value always is a datetime instance. 536 """ 537 if isinstance(node.text, common.ColRef): 538 yield "vals", (node.text,) 539 else: 540 parser = { 541 "ISOTime": times.parseISODT, 542 "JDTime": lambda v: times.jdnToDateTime(float(v)), 543 "MJDTime": lambda v: times.mjdToDateTime(float(v)), 544 }[_localname(node.tag)] 545 yield "vals", (parser(node.text),)
546 547 548 _handledUnits = ("unit", "vel_time_unit", "pos_unit") 549 _buildFloat = _makeKwFloatBuilder("vals", 550 units=_makeUnitYielder(_handledUnits, tuplify=True)) 551 552 _unitKeys = ("unit", "vel_time_unit") 553 _genUnitKeys = ("pos_unit", "time_unit", "spectral_unit", "angle_unit", 554 "gen_unit") 555
556 -def _buildVector(node, buildArgs, context):
557 _assembleVals(buildArgs, context) 558 if 'vals' in buildArgs: 559 yield 'vals', (tuple(buildArgs["vals"][:context.peekDim()]),) 560 561 for uk in _unitKeys: 562 if uk in buildArgs: 563 yield uk, tuple(buildArgs[uk]) 564 for uk in _genUnitKeys: 565 if uk in buildArgs: 566 yield "unit", tuple(buildArgs[uk])
567 568
569 -def _buildElnameFloat(node, buildArgs, context, 570 units=_makeUnitYielder(_handledUnits, tuplify=True)):
571 """returns float(node.context) as the value for node.tag.name. 572 573 An empty text is equivalent to a None value. 574 """ 575 tagName = _localname(node.tag) 576 if node.text: 577 if isinstance(node.text, basestring): 578 yield tagName, float(node.text) 579 else: 580 yield tagName, node.text 581 else: 582 yield tagName, None 583 584 for res in units(node, buildArgs): yield res
585 586
587 -def _buildEpoch(node, buildArgs, context):
588 yield "yearDef", node.get("yearDef", "J") 589 yield "epoch", float(node.text)
590 591 592 ################# Geometries 593
594 -class BoxActions(ContextActions):
595 """Context actions for Boxes: register a special handler for Size. 596 """ 597 boxHandlers = { 598 _n("Size"): _makeKwValueBuilder("boxsize", tuplify=True, units= 599 _makeUnitYielder(("unit",), "size")), 600 }
601 - def start(self, context, node):
602 context.specialHandlerStack.append(self.boxHandlers)
603 - def stop(self, context, node):
604 context.specialHandlerStack.pop()
605 606
607 -def _buildHalfspace(node, buildArgs, context):
608 yield "vectors", (tuple(buildArgs["vector"])+tuple(buildArgs["offset"]),)
609 610
611 -def _adaptCircleUnits(buildArgs):
612 buildArgs["unit"] = buildArgs.pop("unit", ("deg", "deg")) 613 if "radiuspos_unit" in buildArgs: 614 buildArgs["radius"] = units.getBasicConverter( 615 buildArgs.pop("radiuspos_unit"), buildArgs["unit"][0])( 616 buildArgs["radius"])
617 618
619 -def _adaptEllipseUnits(buildArgs):
620 buildArgs["unit"] = buildArgs.pop("unit", ("deg", "deg")) 621 if "smajAxispos_unit" in buildArgs: 622 buildArgs["smajAxis"] = units.getBasicConverter( 623 buildArgs.pop("smajAxispos_unit"), buildArgs["unit"][0])( 624 buildArgs["smajAxis"]) 625 if "sminAxispos_unit" in buildArgs: 626 buildArgs["sminAxis"] = units.getBasicConverter( 627 buildArgs.pop("sminAxispos_unit"), buildArgs["unit"][0])( 628 buildArgs["sminAxis"]) 629 if "posAngleunit" in buildArgs: 630 buildArgs["posAngle"] = units.getBasicConverter( 631 buildArgs.pop("posAngleunit"), "deg")(buildArgs["posAngle"])
632 633
634 -def _adaptBoxUnits(buildArgs):
635 buildArgs["unit"] = buildArgs.get("unit", ("deg", "deg")) 636 if "sizeunit" in buildArgs: 637 su = buildArgs.pop("sizeunit") 638 if isinstance(su, basestring): 639 su = (su, su) 640 buildArgs["boxsize"] = units.getVectorConverter(su, 641 buildArgs["unit"])(buildArgs["boxsize"])
642 643
644 -def _makeGeometryBuilder(astClass, adaptDepUnits=None):
645 """returns a builder for STC-S geometries. 646 """ 647 def buildGeo(node, buildArgs, context): 648 for key, value in _iterCooMeta(node, context, "spaceFrame"): 649 buildArgs[key] = value 650 _fixSpatialUnits(node, buildArgs, context) 651 if adaptDepUnits: 652 adaptDepUnits(buildArgs) 653 buildArgs["origUnit"] = (buildArgs.pop("unit", None), None) 654 if isinstance(node.text, common.ColRef): 655 buildArgs["geoColRef"] = common.GeometryColRef(str(node.text)) 656 yield 'areas', (astClass(**buildArgs),)
657 return buildGeo 658 659
660 -def _validateCompoundChildren(buildArgs):
661 """makes sure that all children of a future compound geometry agree in 662 units and propagates units as necessary. 663 664 origUnit attributes of children are nulled out in the process. Sorry 665 'bout ignoring immutability. 666 """ 667 children = buildArgs.pop("areas") 668 cUnits, selfUnit = [], buildArgs.pop("unit", None) 669 for c in children: 670 if c.origUnit!=(None,None): 671 cUnits.append(c.origUnit) 672 c.origUnit = None 673 674 if len(set(cUnits))>1: 675 raise common.STCNotImplementedError( 676 "Different units within compound children are not supported") 677 elif len(set(cUnits))==1: 678 ou = (selfUnit, None) 679 if selfUnit is not None and ou!=cUnits[0]: 680 raise common.STCNotImplementedError( 681 "Different units on compound and compound children are not supported") 682 buildArgs["origUnit"] = cUnits[0] 683 else: 684 if selfUnit is not None: 685 buildArgs["origUnit"] = (selfUnit, None) 686 buildArgs["children"] = children
687 688
689 -def _makeCompoundBuilder(astClass):
690 def buildCompound(node, buildArgs, context): 691 _validateCompoundChildren(buildArgs) 692 buildArgs.update(_iterCooMeta(node, context, "spaceFrame")) 693 yield "areas", (astClass(**buildArgs),)
694 return buildCompound 695 696 697 ################# Toplevel 698 699 _areasAndPositions = [("timeAs", "time"), ("areas", "place"), 700 ("freqAs", "freq"), ("redshiftAs", "redshift"), 701 ("velocityAs", "velocity")] 702
703 -def _addPositionsForAreas(buildArgs):
704 """adds positions for areas defined by buildArgs. 705 706 This only happens if no position is given so far. The function is a 707 helper for _adaptAreaUnits. BuildArgs is changed in place. 708 """ 709 for areaAtt, posAtt in _areasAndPositions: 710 if buildArgs.get(areaAtt) and not buildArgs.get(posAtt): 711 areas = buildArgs[areaAtt] 712 posAttrs = {} 713 for area in areas: 714 if area.origUnit is not None: 715 posAttrs["unit"] = area.origUnit[0] 716 if area.origUnit[1]: 717 posAttrs["velTimeUnit"] = area.origUnit[1] 718 area.origUnit = None 719 break 720 721 # if no spatial area had a unit, it's probably something like AllSky, 722 # but we still must have *something*, anything 723 if posAtt=="place" and areas and posAttrs["unit"] is None: 724 posAttrs["unit"] = ("deg", "deg") 725 726 buildArgs[posAtt] = area.getPosition(posAttrs)
727 728
729 -def _adaptAreaUnits(buildArgs):
730 """changes area's units in buildArgs to match the positions's units. 731 732 When areas without positions are present, synthesize the appropriate 733 positions to hold units. 734 """ 735 _addPositionsForAreas(buildArgs) 736 for areaAtt, posAtt in _areasAndPositions: 737 newAreas = [] 738 for area in buildArgs.get(areaAtt, ()): 739 if area.origUnit is not None and area.origUnit[0] is not None: 740 newAreas.append(area.adaptValuesWith( 741 buildArgs[posAtt].getUnitConverter(area.origUnit))) 742 else: 743 newAreas.append(area) 744 buildArgs[areaAtt] = tuple(newAreas)
745 746
747 -def _buildToplevel(node, buildArgs, context):
748 _adaptAreaUnits(buildArgs) 749 if "astroSystem" not in buildArgs: 750 # even for a disastrous STC-X, make sure there's a AstroCoords 751 # with rudimentary space and time frames 752 buildArgs["astroSystem"] = dm.CoordSys( 753 timeFrame=dm.TimeFrame(refPos=dm.RefPos()), 754 spaceFrame=dm.SpaceFrame(refPos=dm.RefPos(), 755 flavor="SPHERICAL", nDim=2)) 756 yield 'stcSpec', ((node.tag, dm.STCSpec(**buildArgs)),)
757 758
759 -class IdProxy(common.ASTNode):
760 """A stand-in for a coordinate system during parsing. 761 762 We do this to not depend on ids being located before positions. STC 763 should have that in general, but let's be defensive here. 764 """ 765 _a_idref = None 766 _a_useAttr = None 767
768 - def resolve(self, idMap):
769 ob = idMap[self.idref] 770 if self.useAttr: 771 return getattr(ob, self.useAttr) 772 return ob
773 774
775 -def resolveProxies(forest):
776 """replaces IdProxies in the AST sequence forest with actual references. 777 """ 778 map = {} 779 for rootTag, ast in forest: 780 ast.buildIdMap() 781 map.update(ast.idMap) 782 for rootTag, ast in forest: 783 map[SIBLING_ASTRO_SYSTEM] = ast.astroSystem 784 for node in ast.iterNodes(): 785 for attName, value in node.iterAttributes(skipEmpty=True): 786 if isinstance(value, IdProxy): 787 setattr(node, attName, value.resolve(map))
788 789 790 _2VEC_TAGS = [ 791 "Value2", "Position2D", "Resolution2", "PixSize2", "Error2", 792 "Size2", "HiLimit2Vec", "LoLimit2Vec", "Position2VecInterval", 793 "Velocity2D", 794 "Velocity2VecInterval", "Circle", "Circle2", "Box", "Box2", 795 "Polygon", "Polygon2", "Union", "PositionInterval", "Ellipse", 796 "Union", "Intersection", "Difference", "Negation", "AllSky"] 797 _3VEC_TAGS = [ 798 "Value3", "Position3D", "Resolution3", "PixSize3", "Error3", 799 "Size3", "HiLimit3Vec", "LoLimit3Vec", "Position3VecInterval", 800 "Velocity3D", "Velocity3VecInterval", "Convex"] 801
802 -def getVecTags():
803 """returns a mapping from STC-X names to the number of dimensions 804 child vectors should have. 805 """ 806 res = {} 807 for t in _2VEC_TAGS: 808 res[_n(t)] = 2 809 for t in _3VEC_TAGS: 810 res[_n(t)] = 3 811 return res
812 813
814 -class STCXContext(object):
815 """A parse context containing handlers, stacks, etc. 816 817 A special feature is that there are "context-active" tags. For those 818 the context gets notified by buildTree when their processing is started 819 or ended. We use this to note the active coordinate systems during, e.g., 820 AstroCoords parsing. 821 """
822 - def __init__(self, 823 elementHandlers, activeTags, vecTags=getVecTags(), **kwargs):
824 self.sysIdStack = [] 825 self.nDimStack = [] 826 self.specialHandlerStack = [{}] 827 self.elementHandlers = elementHandlers 828 self.activeTags = activeTags 829 self.vecTags = vecTags 830 for k, v in kwargs.iteritems(): 831 setattr(self, k, v)
832
833 - def getHandler(self, elementName):
834 """returns a builder for the qName elementName, and the expected 835 dimension of child vectors. 836 837 If no such handler exists, we return None, None 838 """ 839 nD = self.vecTags.get(elementName) 840 if elementName in self.specialHandlerStack[-1]: 841 return self.specialHandlerStack[-1][elementName], nD 842 return self.elementHandlers.get(elementName), nD
843
844 - def startTag(self, node):
845 self.activeTags[node.tag].start(self, node)
846
847 - def endTag(self, node):
848 self.activeTags[node.tag].stop(self, node)
849
850 - def pushDim(self, nDim):
851 self.nDimStack.append(nDim)
852
853 - def popDim(self):
854 return self.nDimStack.pop()
855
856 - def peekDim(self):
857 if not self.nDimStack: 858 # if we're not processing vectors, we're processing scalars 859 return 1 860 861 return self.nDimStack[-1]
862 863 864 _yieldErrUnits = _makeUnitYielder(_handledUnits, "error") 865 _yieldPSUnits = _makeUnitYielder(_handledUnits, "pixSize") 866 _yieldResUnits = _makeUnitYielder(_handledUnits, "resolution") 867 _yieldSzUnits = _makeUnitYielder(_handledUnits, "size") 868 869 # A sequence of tuples (dict builder, [stcxElementNames]) to handle 870 # STC-X elements by calling functions 871 _stcBuilders = [ 872 (_buildElnameFloat, ["C1", "C2", "C3"]), 873 (_buildTime, ["ISOTime", "JDTime", "MJDTime"]), 874 (_buildVector, ["Value2", "Value3"]), 875 (_buildRefpos, common.stcRefPositions), 876 (_buildFlavor, common.stcCoordFlavors), 877 (_buildRefFrame, common.stcSpaceRefFrames), 878 879 (_makePositionBuilder('place', dm.SpaceCoo, "spaceFrame", tuplify=True), 880 ["Position1D", "Position3D", "Position2D"]), 881 (_makePositionBuilder('velocity', dm.VelocityCoo, "spaceFrame", 882 tuplify=True), 883 ["Velocity1D", "Velocity3D", "Velocity2D"]), 884 885 (_makeKwValuesBuilder("resolution", tuplify=True, units=_yieldResUnits), 886 ["Resolution2", "Resolution3"]), 887 (_makeKwValuesBuilder("pixSize", tuplify=True, units=_yieldPSUnits), 888 ["PixSize2", "PixSize3"]), 889 (_makeKwValuesBuilder("error", tuplify=True, units=_yieldErrUnits), 890 ["Error2", "Error3"]), 891 (_makeKwValuesBuilder("size", tuplify=True, units=_yieldSzUnits), 892 ["Size2", "Size3"]), 893 894 (_makeKwFloatBuilder("resolutionRadius", units=_yieldResUnits), 895 ["Resolution2Radius", "Resolution3Radius"]), 896 (_makeKwFloatBuilder("pixSizeRadius", units=_yieldPSUnits), 897 ["PixSize2Radius", "PixSize3Radius"]), 898 (_makeKwFloatBuilder("errorRadius", units=_yieldErrUnits), 899 ["Error2Radius", "Error3Radius"]), 900 (_makeKwFloatBuilder("sizeRadius", units=_yieldSzUnits), 901 ["Size2Radius", "Size3Radius"]), 902 903 (_makeKwValuesBuilder("resolutionMatrix", units=_yieldResUnits), 904 ["Resolution2Matrix", "Resolution3Matrix"]), 905 (_makeKwValuesBuilder("pixSizeMatrix", units=_yieldSzUnits), 906 ["PixSize2Matrix", "PixSize3Matrix"]), 907 (_makeKwValuesBuilder("errorMatrix", units=_yieldErrUnits), 908 ["Error2Matrix", "Error3Matrix"]), 909 (_makeKwValuesBuilder("sizeMatrix", units=_yieldSzUnits), 910 ["Size2Matrix", "Size3Matrix"]), 911 912 (_makeKwValuesBuilder("upperLimit", tuplify=True), 913 ["HiLimit2Vec", "HiLimit3Vec"]), 914 (_makeKwValuesBuilder("lowerLimit", tuplify=True), 915 ["LoLimit2Vec", "LoLimit3Vec"]), 916 917 (_makeIntervalBuilder("areas", dm.SpaceInterval, "spaceFrame", tuplify=True), 918 ["PositionScalarInterval", "Position2VecInterval", 919 "Position3VecInterval"]), 920 (_makeIntervalBuilder("velocityAs", dm.VelocityInterval, "spaceFrame", 921 tuplify=True), 922 ["VelocityScalarInterval", "Velocity2VecInterval", 923 "Velocity3VecInterval"]), 924 925 (_makeGeometryBuilder(dm.AllSky), ["AllSky", "AllSky2"]), 926 (_makeGeometryBuilder(dm.Circle, _adaptCircleUnits), ["Circle", "Circle2"]), 927 (_makeGeometryBuilder(dm.Ellipse, _adaptEllipseUnits), [ 928 "Ellipse", "Ellipse2"]), 929 (_makeGeometryBuilder(dm.Box, _adaptBoxUnits), ["Box", "Box2"]), 930 (_makeGeometryBuilder(dm.Polygon), ["Polygon", "Polygon2"]), 931 (_makeGeometryBuilder(dm.Convex), ["Convex", "Convex2"]), 932 (_makeCompoundBuilder(dm.Union), ["Union", "Union2"]), 933 (_makeCompoundBuilder(dm.Intersection), ["Intersection", "Intersection2"]), 934 (_makeCompoundBuilder(dm.Difference), ["Difference", "Difference2"]), 935 (_makeCompoundBuilder(dm.Not), ["Negation", "Negation2"]), 936 937 (_buildToplevel, ["ObservatoryLocation", "ObservationLocation", 938 "STCResourceProfile", "STCSpec"]), 939 (_passthrough, ["ObsDataLocation", "AstroCoords", "TimeInstant", 940 "AstroCoordArea", "Position"]), 941 ] 942 943
944 -def _getHandlers():
945 handlers = { 946 _n("AstroCoordSystem"): _buildAstroCoordSystem, 947 _n("PlanetaryEphem"): _buildPlanetaryEphem, 948 _n("Error"): _makeKwFloatBuilder("error", units=_yieldErrUnits), 949 _n("PixSize"): _makeKwFloatBuilder("pixSize", units=_yieldPSUnits), 950 _n("Resolution"): _makeKwFloatBuilder("resolution", units=_yieldResUnits), 951 _n("Size"): _makeKwFloatBuilder("size", units=_yieldSzUnits), 952 953 _n("Redshift"): _makePositionBuilder('redshift', dm.RedshiftCoo, 954 "redshiftFrame"), 955 _n("Spectral"): _makePositionBuilder('freq', 956 dm.SpectralCoo, "spectralFrame"), 957 _n("StartTime"): _makeKwValueBuilder("lowerLimit"), 958 _n("StopTime"): _makeKwValueBuilder("upperLimit"), 959 _n("LoLimit"): _makeKwFloatBuilder("lowerLimit"), 960 _n("HiLimit"): _makeKwFloatBuilder("upperLimit"), 961 _n("Time"): _makePositionBuilder('time', dm.TimeCoo, "timeFrame"), 962 _n("Timescale"): _makeKeywordBuilder("timeScale"), 963 _n("TimeScale"): _makeKeywordBuilder("timeScale"), 964 _n("Equinox"): _makeKeywordBuilder("equinox"), 965 _n("Value"): _makeKwFloatBuilder("vals"), 966 967 _n("Epoch"): _buildEpoch, 968 _n("Radius"): _makeKwFloatBuilder("radius", multiple=False, 969 units=_makeUnitYielder(("pos_unit",), "radius")), 970 _n("Center"): _makeKwValueBuilder("center", tuplify=True, 971 units=_makeUnitYielder(("unit",))), 972 _n("SemiMajorAxis"): _makeKwFloatBuilder("smajAxis", multiple=False, 973 units=_makeUnitYielder(("pos_unit",), "smajAxis")), 974 _n("SemiMinorAxis"): _makeKwFloatBuilder("sminAxis", multiple=False, 975 units=_makeUnitYielder(("pos_unit",), "sminAxis")), 976 _n("PosAngle"): _makeKwFloatBuilder("posAngle", multiple=False, 977 units=_makeUnitYielder(("unit",), "posAngle")), 978 _n("Vertex"): _makeKwValuesBuilder("vertices", tuplify=True), 979 _n("Vector"): _makeKwValueBuilder("vector", tuplify=True), 980 _n("Offset"): _makeKwFloatBuilder("offset"), 981 _n("Halfspace"): _buildHalfspace, 982 983 _n('TimeFrame'): _makeFrameBuilder('timeFrame', dm.TimeFrame, 984 timeScale="TT"), 985 _n('SpaceFrame'): _makeFrameBuilder('spaceFrame', dm.SpaceFrame), 986 _n('SpectralFrame'): _makeFrameBuilder('spectralFrame', dm.SpectralFrame), 987 _n('RedshiftFrame'): _makeFrameBuilder('redshiftFrame', dm.RedshiftFrame), 988 989 990 _n("DopplerDefinition"): _makeKeywordBuilder("dopplerDef"), 991 _n("TimeInterval"): _makeIntervalBuilder("timeAs", dm.TimeInterval, 992 "timeFrame"), 993 _n("SpectralInterval"): _makeIntervalBuilder("freqAs", 994 dm.SpectralInterval, "spectralFrame"), 995 _n("RedshiftInterval"): _makeIntervalBuilder("redshiftAs", 996 dm.RedshiftInterval, "redshiftFrame"), 997 } 998 for builder, stcEls in _stcBuilders: 999 for el in stcEls: 1000 handlers[_n(el)] = builder 1001 return handlers
1002 1003 getHandlers = utils.CachedGetter(_getHandlers) 1004 1005
1006 -def _getActiveTags():
1007 return { 1008 _n("AstroCoords"): CooSysActions(), 1009 _n("AstroCoordArea"): CooSysActions(), 1010 _n("Box"): BoxActions(), 1011 _n("Box2"): BoxActions(), 1012 }
1013 1014 getActiveTags = utils.CachedGetter(_getActiveTags) 1015 1016
1017 -def buildTree(csNode, context):
1018 """traverses the ElementTree cst, trying handler functions for 1019 each node. 1020 1021 The handler functions are taken from the context.elementHandler 1022 dictionary that maps QNames to callables. These callables have 1023 the signature handle(STCNode, context) -> iterator, where the 1024 iterator returns key-value pairs for inclusion into the argument 1025 dictionaries for STCNodes. 1026 1027 Unknown nodes are simply ignored. If you need to bail out on certain 1028 nodes, raise explicit exceptions in handlers. 1029 1030 This also manages the expected number of dimensions in vectors by 1031 pushing and popping from the context's dimension stack. The actual 1032 wisdom on what elements set which dimensions is kept in the context 1033 object as well. 1034 """ 1035 resDict = {} 1036 handler, nDim = context.getHandler(csNode.tag) 1037 # Elements with no handlers are ignored (add option to fail on these?) 1038 if handler is None: 1039 return 1040 1041 if nDim is not None: 1042 context.pushDim(nDim) 1043 # print(">>>>>>>>> %s %s"%(csNode.tag, context.peekDim())) 1044 1045 if csNode.tag in context.activeTags: 1046 context.startTag(csNode) 1047 1048 # collect constructor keywords from child nodes 1049 for child in csNode: 1050 for res in buildTree(child, context): 1051 if res is None: # ignored child 1052 continue 1053 k, v = res 1054 if isinstance(v, (tuple, list)): 1055 resDict[k] = resDict.get(k, ())+v 1056 else: 1057 if k in resDict: 1058 raise common.STCInternalError("Attempt to overwrite key '%s', old" 1059 " value %s, new value %s (this should probably have been" 1060 " a tuple)"%(k, resDict[k], v)) 1061 resDict[k] = v 1062 1063 # collect constructor keywords from this node's handler 1064 for res in handler(csNode, resDict, context): 1065 yield res 1066 1067 if csNode.tag in context.activeTags: 1068 context.endTag(csNode) 1069 1070 if nDim is not None: 1071 context.popDim()
1072 1073
1074 -def parseFromETree(eTree):
1075 """returns a sequence of pairs (root element, AST) for eTree containing 1076 parsed STC-X. 1077 """ 1078 context = STCXContext( 1079 elementHandlers=getHandlers(), 1080 activeTags=getActiveTags()) 1081 parsed = dict(buildTree(eTree, context)) 1082 if "stcSpec" not in parsed: 1083 raise common.STCXBadError("No STC-X found in or below %r"%eTree) 1084 forest = parsed["stcSpec"] 1085 resolveProxies(forest) 1086 return [(rootTag, ast.polish()) for rootTag, ast in forest]
1087 1088
1089 -def parseSTCX(stcxLiteral):
1090 """returns a sequence of pairs (root element, AST) for the STC-X 1091 specifications in stcxLiteral. 1092 """ 1093 return parseFromETree(utils.ElementTree.fromstring(stcxLiteral))
1094