"""
Generating a utype/value sequence for ASTs.
Yet another insane serialization for an insane data model. Sigh.
The way we come up with the STC utypes here is described in an IVOA note.
Since the utypes are basically xpaths into STC-X, there is not terribly
much we need to do here. However, due to STC-X being a nightmare,
certain rules need to be formulated what utypes to generate.
Here, we use UtypeMakers for this. There's a default UtypeMaker
that implements the basic algorithm of the STC note (in iterUtypes,
a method that yields all utype/value pairs for an STC-X node, which
is a stanxml Element).
To customize what is being generated, define _gener_<child name>
methods, where <child name> is a key within the dictionaries
returned by stanxml.Element's getChildDict method.
To make the definition of the _gener_ methods easier, there's
the handles decorator that you can pass a list of such child names.
"""
#c Copyright 2008-2023, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL. See the
#c COPYING file in the source distribution.
from gavo import utils
from gavo.stc import common
from gavo.stc import stcxgen
from gavo.stc.stcx import STC
#################### utype maker definition
[docs]def handles(seq):
"""is a decorator for UtypeMaker methods.
It adds a "handles" attribute as evaluated by AutoUtypeMaker.
"""
def deco(meth):
meth.handles = seq
return meth
return deco
[docs]class UtypeMaker_t(type):
"""A metaclass to facilite easy definition of UtypeMakers.
This metaclass primarily operates on the handles hints left by the
decorator.
"""
def __init__(cls, name, bases, dict):
type.__init__(cls, name, bases, dict)
cls._createHandlesMethods(list(dict.values()))
def _createHandlesMethods(cls, items):
for item in items:
for name in getattr(item, "handles", ()):
setattr(cls, "_gener_"+name, item)
[docs]class UtypeMaker(object, metaclass=UtypeMaker_t):
"""An object encapsulating information on how to turn a stanxml
node into a sequence of utype/value pairs.
This is an "universal" base, serving as a simple default.
Any class handling specific node types must fill out at least
the rootType attribute, giving the utype at which this UtypeMaker
should kick in.
By default, utype/value pairs are only returned for nonempty
element content. To change this, define _gener_<name>(node,
prefix) -> iterator methods.
The actual pairs are retrieved by calling iterUtypes(node,
parentPrefix).
"""
# attributes that don't get serialized to utypes per spec
bannedAttributes = set("id frame_id coord_system_id unit"
" pos_angle_unit pos_unit spectral_unit time_unit"
" vel_time_unit gen_unit xsi:type ucd xmlns:stc xmlns xmlns:xsi"
" xsi:schemaLocation".split())
rootType = None
def _generPlain(self, name, child, prefix):
childType = utypejoin(prefix, name)
maker = _getUtypeMaker(childType)
for item in child:
for pair in maker.iterUtypes(item, childType):
yield pair
def _gener__colRef(self, name, child, prefix):
yield prefix, child[0]
[docs] def iterUtypes(self, node, prefix):
children = node.getChildDict()
if node.text_:
yield prefix, node.text_
for attName, name in node.iterAttNames():
if name not in self.bannedAttributes:
val = getattr(node, attName)
if val is not None:
yield "%s.%s"%(prefix, name.split(":")[-1]), val
for name, child in children.items():
handler = getattr(self, "_gener_"+name, self._generPlain)
for pair in handler(name, child, prefix):
yield pair
class _NotImplementedUtypeMaker(UtypeMaker):
def _generPlain(self, name, child, prefix):
raise common.STCNotImplementedError("Cannot create utypes for %s yet."%
self.utypeFrag)
#################### utype specific makers
class _CoordFrameMaker(UtypeMaker):
@handles(common.stcRefPositions)
def _refPos(self, name, child, prefix):
if name!='UNKNOWNRefPos':
yield utypejoin(prefix, "ReferencePosition"), name
for pair in self._generPlain("ReferencePosition", child, prefix):
yield pair
[docs]class TimeFrameMaker(_CoordFrameMaker):
rootType = "AstroCoordSystem.TimeFrame"
@handles(common.stcTimeScales)
def _timeScale(self, name, child, prefix):
yield utypejoin(prefix, "TimeScale"), name
[docs]class SpaceFrameMaker(_CoordFrameMaker):
rootType = "AstroCoordSystem.SpaceFrame"
@handles(common.stcSpaceRefFrames)
def _coordFrame(self, name, child, prefix):
myPrefix = utypejoin(prefix, "CoordRefFrame")
yield myPrefix, name
for pair in self._generPlain(None, child, myPrefix):
yield pair
@handles(common.stcCoordFlavors)
def _coordFlavor(self, name, child, prefix):
prefix = utypejoin(prefix, "CoordFlavor")
yield prefix, name
if child:
if child[0].coord_naxes!="2":
yield utypejoin(prefix, "coord_naxes"), child[0].coord_naxes
yield utypejoin(prefix, "handedness"), child[0].handedness
[docs]class RedshiftFrameMaker(_CoordFrameMaker):
rootType = "AstroCoordSystem.RedshiftFrame"
[docs] def iterUtypes(self, node, prefix):
for pair in _CoordFrameMaker.iterUtypes(self, node, prefix):
yield pair
[docs]class SpectralFrameMaker(_CoordFrameMaker):
rootType = "AstroCoordSystem.SpectralFrame"
class _TimeValueMaker(UtypeMaker):
@handles(["ISOTime", "JDTime", "MJDTime"])
def _absoluteTime(self, name, child, prefix):
yield utypejoin(prefix, "xtype"), name
for item in child:
for pair in self.iterUtypes(item, prefix):
yield pair
[docs]class TimeInstantMaker(_TimeValueMaker):
rootType = "AstroCoords.Time.TimeInstant"
[docs]class StartTimeMaker(_TimeValueMaker):
rootType = "AstroCoordArea.TimeInterval.StartTime"
[docs]class StopTimeMaker(_TimeValueMaker):
rootType = "AstroCoordArea.TimeInterval.StopTime"
#################### toplevel code
[docs]def utypejoin(*utypes):
return ".".join(u for u in utypes if u)
# A resolver of element names to their handling classes. For most
# elements, this is just a plain UtypeMaker.
_getUtypeMaker = utils.buildClassResolver(
UtypeMaker,
list(globals().values()),
default=UtypeMaker(),
instances=True,
key=lambda obj:obj.rootType)
[docs]def getUtypes(ast, includeDMURI=False, suppressXtype=True):
"""returns a lists of utype/value pairs for an STC AST.
If you pass includeDMURI, a utype/value pair for the data model URI will
be generated in addition to what comes in from ast.
"""
cst = stcxgen.astToStan(ast, STC.STCSpec)
utypes = []
for utype, val in _getUtypeMaker("").iterUtypes(cst, ""):
if val is None or val=='':
continue
if suppressXtype:
if utype.endswith("xtype"):
continue
utypes.append(("stc:"+utype, val))
if includeDMURI:
utypes.append(("stc:DataModel.URI", common.STCNamespace))
utypes.sort()
return utypes