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
20
21
22
23
24
25
26
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
40 """A sentinel class to tell the id resolver to use the sibling AstroCoordSys
41 element."""
42
43
44
45
48 _n = STCElement
49
50
52 """hacks the local tag name from a {ns}-serialized qName.
53 """
54 qName = str(qName)
55 return qName[qName.find("}")+1:]
56
57
59 """yields the items of buildArgs.
60
61 This can be used for "no-op" elements.
62 """
63 return buildArgs.iteritems()
64
65
68
71
72
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
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
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
114 pass
115 else:
116 buildArgs['vals'] = tuple(vals)
117
118
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):
132 _assembleVals(buildArgs, context)
133 yield kwName, (buildArgs["vals"],)
134 for res in units(node, buildArgs): yield res
135 return buildNode
136
137
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):
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
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):
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
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
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
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
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
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:
250 return (unit,)*nDim
251 return None
252
253
255 nDim = context.peekDim()
256
257
258 buildArgs["unit"] = _makeSpatialUnits(nDim, buildArgs.pop("unit", None),
259 node.get("unit", "").split())
260
261
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
269
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
279
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
328
329
330
331
332
333 _dimExceptions = {
334 }
335
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
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):
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
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
386
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
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
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):
435
436 - def stop(self, context, node):
438
439
440
441
442 _xlinkHref = utils.ElementTree.QName(common.XlinkNamespace, "href")
443
445 buildArgs["id"] = node.get("id", None)
446
447
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
461
462 if newEl.id is None:
463 newEl.id = utils.intToFunnyWord(id(newEl))
464 yield "astroSystem", newEl
465
466
468 res = node.text.strip()
469 if res:
470 yield 'planetaryEphemeris', res
471
472
474 refposName = _localname(node.tag)
475 if refposName=="UNKNOWNRefPos":
476 refposName = None
477 yield 'refPos', dm.RefPos(standardOrigin=refposName,
478 **buildArgs)
479
481 yield 'flavor', _localname(node.tag)
482 naxes = node.get("coord_naxes")
483 if naxes is not None:
484 yield 'nDim', int(naxes)
485
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
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:
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
513
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):
526
527 - def stop(self, context, node):
528 context.sysIdStack.pop()
529
530
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
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
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
588 yield "yearDef", node.get("yearDef", "J")
589 yield "epoch", float(node.text)
590
591
592
593
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):
603 - def stop(self, context, node):
604 context.specialHandlerStack.pop()
605
606
608 yield "vectors", (tuple(buildArgs["vector"])+tuple(buildArgs["offset"]),)
609
610
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
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
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
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
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
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
698
699 _areasAndPositions = [("timeAs", "time"), ("areas", "place"),
700 ("freqAs", "freq"), ("redshiftAs", "redshift"),
701 ("velocityAs", "velocity")]
702
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
722
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
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
748 _adaptAreaUnits(buildArgs)
749 if "astroSystem" not in buildArgs:
750
751
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
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
769 ob = idMap[self.idref]
770 if self.useAttr:
771 return getattr(ob, self.useAttr)
772 return ob
773
774
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
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
854 return self.nDimStack.pop()
855
857 if not self.nDimStack:
858
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
870
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
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
1013
1014 getActiveTags = utils.CachedGetter(_getActiveTags)
1015
1016
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
1038 if handler is None:
1039 return
1040
1041 if nDim is not None:
1042 context.pushDim(nDim)
1043
1044
1045 if csNode.tag in context.activeTags:
1046 context.startTag(csNode)
1047
1048
1049 for child in csNode:
1050 for res in buildTree(child, context):
1051 if res is None:
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
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
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
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