"""
Encoding to tabledata.
These are functions returning pieces of code that are assembled
into an encoding function. This still returns a normal string.
The encoding into utf-8 only takes place in model's TABLEDATA element.
"""
#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.
import datetime #noflake: used in generated code
from gavo import utils #noflake: used in generated code
from gavo.utils import stanxml
from gavo.utils import pgsphere #noflake: used in generated code
from gavo.votable import coding
from gavo.votable import common
def _getArrayShapingCode(field, padder):
"""returns common code for almost all array serialization.
Field must describe an array (as opposed to a single value).
padder must be python-source for whatever is used to pad
arrays that are too short.
"""
base = [
"if val is None: val = []"]
if field.isMultiDim():
# it's an n-d array (n>1): flatten out all values
base.append("val = coding.ravel(val)")
if field.hasVarLength():
return base
else:
return base+["val = coding.trim(val, %s, %s)"%(
field.getLength(), padder)]
def _addNullvalueCode(field, src, validator, defaultNullValue=None):
"""adds code handle null values where not default representation exists.
"""
nullvalue = coding.getNullvalue(field, validator)
if nullvalue is None:
if defaultNullValue is None:
action = (" raise common.BadVOTableData('None passed for field"
" that has no NULL value', None, '%s', hint='Integers in VOTable"
" have no natural serializations for missing values. You need to"
" define one using values null to allow for NULL in integer columns')"
)%field.getDesignation()
else:
action = (" tokens.append(%r)"%stanxml.escapePCDATA(defaultNullValue))
else:
action = " tokens.append(%r)"%stanxml.escapePCDATA(nullvalue)
return [
'if val is None:',
action,
'else:']+common.indentList(src, " ")
def _makeFloatEncoder(field):
return [
"if val is None or val!=val:", # NaN is a null value, too
" tokens.append('NaN')",
"else:",
" tokens.append(repr(float(val)))"]
def _makeComplexEncoder(field):
return [
"if val is None:",
" tokens.append('NaN NaN')",
"else:",
" try:",
" tokens.append('%s %s'%(repr(val.real), repr(val.imag)))",
" except AttributeError:",
" tokens.append(repr(val))",]
def _makeBooleanEncoder(field):
return [
"if val is None:",
" tokens.append('?')",
"elif val:",
" tokens.append('1')",
"else:",
" tokens.append('0')",]
def _makeUByteEncoder(field):
return _addNullvalueCode(field, [
"if isinstance(val, int):",
' tokens.append(str(val))',
"else:",
' tokens.append(str(ord(val[:1])))',],
common.validateVOTInt, "")
def _makeIntEncoder(field):
return _addNullvalueCode(field, [
"tokens.append(str(val))"],
common.validateVOTInt)
def _makeCharEncoder(field):
src = []
src.extend(common.getXtypeEncoderCode(field))
src.append("val = coding.trimString(val, %s)"%repr(field.arraysize))
if field.datatype=="char":
# we're not supposed to put non-ascii into chars.
src.extend([
'if isinstance(val, str):',
' val = val.encode("ascii", "replace").decode("ascii")'])
src.extend([
"tokens.append(stanxml.escapePCDATA(val))"])
return _addNullvalueCode(field, src, lambda _: True, "")
_encoders = {
'boolean': _makeBooleanEncoder,
'bit': _makeIntEncoder,
'unsignedByte': _makeUByteEncoder,
'short': _makeIntEncoder,
'int': _makeIntEncoder,
'long': _makeIntEncoder,
'char': _makeCharEncoder,
'unicodeChar': _makeCharEncoder,
'float': _makeFloatEncoder,
'double': _makeFloatEncoder,
'floatComplex': _makeComplexEncoder,
'doubleComplex': _makeComplexEncoder,
}
def _getArrayEncoderLinesNotNULL(field):
"""returns python lines to encode array values of field.
Again, the specs are a bit nuts, so we end up special casing almost
everything.
For fixed-length arrays we enforce the given length by
cropping or adding nulls (except, currently, for bit and char arrays).
"""
type = field.datatype
# bit array literals are integers, real special handling
if type=="bit":
return ['tokens.append(utils.toBinary(val))']
# char array literals are strings, real special handling
if type=='char' or type=='unicodeChar':
return _makeCharEncoder(field)
src = common.getXtypeEncoderCode(field)
src.extend(_getArrayShapingCode(field, '[None]'))
src.extend([ # Painful name juggling to avoid functions
'fullTokens = tokens',
'tokens = []',
'arr = val'])
src.extend(['for val in coding.ravel(arr):']+common.indentList(
_encoders[type](field), " "))
src.append("fullTokens.append(' '.join(tokens))")
src.append("tokens = fullTokens")
return src
def _getArrayEncoderLines(field):
"""returns python lines to encode array values of field.
This will encode NULL array as empty strings.
"""
return [
"if val is None:",
" tokens.append('')",
"else:",]+common.indentList(_getArrayEncoderLinesNotNULL(field), " ")
[docs]def getLinesFor(field):
"""returns a sequence of python source lines to encode values described
by field into tabledata.
"""
if field.isScalar():
return _encoders[field.datatype](field)
else:
return _getArrayEncoderLines(field)
[docs]def getPostamble(tableDefinition):
return [
"return '<TR>%s</TR>'%(''.join('<TD>%s</TD>'%v for v in tokens))"]
[docs]def getGlobals(tableDefinition):
return globals()