Source code for gavo.stc.utypeast

"""
Parse utype sets into ASTs.

The rough plan is to build an ElementTree from a sequence of
utype/value pairs (as returned by utypegen.getUtypes).  This
ElementTree can then be used to build an AST using stcxast.  The ElementTree
contains common.ColRefs and thus cannot be serialized to an XML string.

Most of the magic happens in utypeParseToTree, where the utypes are
dissected; it is important that the utypes sequence passed to this
is sorted such that utypes for things below an STC-X node are actually
immediately below the utype pair for their parent.
"""

#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 stcxast
from gavo.utils import ElementTree


[docs]def parseUtype(utype): if utype is None: return None return tuple(utype.split(":")[-1].split("."))
class _Attribute(object): """a "value" for utypePairsToTree that causes an attribute to be set on an element. This is something a morph function would return. """ def __init__(self, name, value): self.name, self.value = name, value class _Child(object): """a "value" for utypesParisToTree that causes a child to be appended This is for morph functions on substitution groups. """ def __init__(self, name, value): self.name, self.value = name, value def _unifyTuples(fromTuple, toTuple): """returns a pair fromTail, toTail of tuples to bring fromTuple to toTuple. This is done such that fromTuple-fromTuple+toTail=toTuple for "appending" semantics. """ if toTuple is None: return (), () prefixLen = utils.commonPrefixLength(fromTuple, toTuple) return fromTuple[prefixLen:], toTuple[prefixLen:] def _replaceLastWithValue(utype, value): yield ".".join(parseUtype(utype)[:-1])+"."+value, None def _makeCoordFlavor(utype, value): # in addition to fixing syntax, we give a default for coord_naxis. # User settings would overwrite that. for pair in _replaceLastWithValue(utype, value): yield pair yield None, _Attribute("coord_naxes", "2") def _makeAttributeMaker(attName): def makeAttribute(utype, value): yield None, _Attribute(attName, value) return makeAttribute def _makeParentAttributeMaker(attName): def makeAttribute(utype, value): yield ".".join(parseUtype(utype)[:-1]), _Attribute(attName, value) return makeAttribute def _makeChildMaker(childName): def makeChild(utype, value): yield None, _Child(childName, value) return makeChild def _replaceUtype(utype): def replacer(_, value): yield utype, value return replacer def _appendFragment(frag): def appender(orig, value): yield "%s.%s"%(orig, frag), value return appender def _ignore(utype, value): if False: yield None _utypeMorphers = { 'AstroCoordSystem.RedshiftFrame.ReferencePosition': _replaceLastWithValue, 'AstroCoordSystem.RedshiftFrame.value_type': _makeParentAttributeMaker("value_type"), 'AstroCoordSystem.SpaceFrame.CoordFlavor': _makeCoordFlavor, 'AstroCoordSystem.SpaceFrame.CoordFlavor.coord_naxes': _makeAttributeMaker("coord_naxes"), 'AstroCoordSystem.SpaceFrame.CoordRefFrame': _replaceLastWithValue, 'AstroCoordSystem.SpaceFrame.CoordRefFrame.Equinox': _replaceUtype('AstroCoordSystem.SpaceFrame.Equinox'), 'AstroCoordSystem.SpaceFrame.ReferencePosition': _replaceLastWithValue, 'AstroCoordSystem.SpectralFrame.ReferencePosition': _replaceLastWithValue, 'AstroCoordSystem.TimeFrame.ReferencePosition': _replaceLastWithValue, 'AstroCoordSystem.href': _makeParentAttributeMaker( utils.ElementTree.QName(common.XlinkNamespace, "href")), 'AstroCoordSystem.SpaceFrame.ReferencePosition.PlanetaryEphem': _makeChildMaker("PlanetaryEphem"), 'AstroCoordSystem.TimeFrame.ReferencePosition.PlanetaryEphem': _makeChildMaker("PlanetaryEphem"), 'AstroCoordSystem.SpectralFrame.ReferencePosition.PlanetaryEphem': _makeChildMaker("PlanetaryEphem"), 'AstroCoordSystem.RedshiftFrame.ReferencePosition.PlanetaryEphem': _makeChildMaker("PlanetaryEphem"), 'AstroCoords.Time.TimeInstant': _appendFragment('ISOTime'), 'AstroCoords.TimeInterval.StartTime': _appendFragment('ISOTime'), 'AstroCoords.TimeInterval.EndTime': _appendFragment('ISOTime'), 'AstroCoords.Position1D.Epoch.yearDef': _makeParentAttributeMaker("yearDef"), 'AstroCoords.Position2D.Epoch.yearDef': _makeParentAttributeMaker("yearDef"), 'AstroCoords.Position3D.Epoch.yearDef': _makeParentAttributeMaker("yearDef"), 'DataModel.Uri': _ignore, 'DataModel.Name': _ignore, 'DataModel.Version': _ignore, }
[docs]def utypePairsToTree(utypes, nameQualifier=stcxast.STCElement): """returns an ElementTree from a sequence of (utype, value) pairs. nameQualifier(str) -> anything is a function producing element names. The utypes are processed as they come in. In practice this means you should sort them (or similar). The utype can be none, meaning "Use last node". """ utypes = list(utypes) root = ElementTree.Element(nameQualifier("STCSpec")) curParts, elementStack = (), [root] for parts, val in ((parseUtype(u), v) for u, v in utypes): toClose, toOpen = _unifyTuples(curParts, parts) if toClose or toOpen: # move in utype tree curParts = parts for elName in toClose: elementStack.pop() for elName in toOpen: elementStack.append( ElementTree.SubElement(elementStack[-1], nameQualifier(elName))) # _Attributes get special handling if isinstance(val, _Attribute): elementStack[-1].attrib[val.name] = val.value elif isinstance(val, _Child): ElementTree.SubElement(elementStack[-1], nameQualifier(val.name)).text = val.value # All other values go to the element content; if nothing was # opened or closed, add another node rather than clobber the # last one. else: if not toClose and not toOpen: elementStack.pop() elementStack.append(ElementTree.SubElement( elementStack[-1], nameQualifier(curParts[-1]))) elementStack[-1].text = val return root
[docs]def morphUtypes(morphers, utypeSeq): """returns a morphed sequence of utype/value pairs. morphers is a dictionary of utypes to handling generators. Each of those most take a utype and a value and generate utype/value pairs. This is used here to fix the abominations where system elements become values in utype representation. """ for index, (k, v) in enumerate(utypeSeq): if k in morphers: for pair in morphers[k](k, v): yield pair else: yield (k, v)
def _iterSortedUtypes(utypeSeq): """returns a sorted sequence of utype, value pairs from utypeSeq The result also has the abominable "stc:" cut off. """ return sorted((key.split(":")[-1], value) for key, value in utypeSeq)
[docs]def parseFromUtypes(utypeSeq): """returns an STC AST for a sequence of utype-value pairs. """ eTree = utypePairsToTree( morphUtypes(_utypeMorphers, _iterSortedUtypes(utypeSeq))) res = stcxast.parseFromETree(eTree)[0][1] return res