1
This commit is contained in:
@@ -0,0 +1,763 @@
|
||||
"""Interpret SGF property values.
|
||||
|
||||
This is intended for use with SGF FF[4]; see http://www.red-bean.com/sgf/
|
||||
|
||||
This supports all general properties and Go-specific properties, but not
|
||||
properties for other games. Point, Move and Stone values are interpreted as Go
|
||||
points.
|
||||
|
||||
Adapted from gomill by Matthew Woodcraft, https://github.com/mattheww/gomill
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import codecs
|
||||
from math import isinf, isnan
|
||||
|
||||
import six
|
||||
|
||||
from tugo.gosgf import sgf_grammar
|
||||
from six.moves import range
|
||||
|
||||
# In python 2, indexing a str gives one-character strings.
|
||||
# In python 3, indexing a bytes gives ints.
|
||||
if six.PY2:
|
||||
_bytestring_ord = ord
|
||||
else:
|
||||
def identity(x):
|
||||
return x
|
||||
_bytestring_ord = identity
|
||||
|
||||
|
||||
def normalise_charset_name(s):
|
||||
"""Convert an encoding name to the form implied in the SGF spec.
|
||||
|
||||
In particular, normalises to 'ISO-8859-1' and 'UTF-8'.
|
||||
|
||||
Raises LookupError if the encoding name isn't known to Python.
|
||||
|
||||
"""
|
||||
if not isinstance(s, six.text_type):
|
||||
s = s.decode('ascii')
|
||||
return (codecs.lookup(s).name.replace("_", "-").upper()
|
||||
.replace("ISO8859", "ISO-8859"))
|
||||
|
||||
|
||||
def interpret_go_point(s, size):
|
||||
"""Convert a raw SGF Go Point, Move, or Stone value to coordinates.
|
||||
|
||||
s -- 8-bit string
|
||||
size -- board size (int)
|
||||
|
||||
Returns a pair (row, col), or None for a pass.
|
||||
|
||||
Raises ValueError if the string is malformed or the coordinates are out of
|
||||
range.
|
||||
|
||||
Only supports board sizes up to 26.
|
||||
|
||||
The returned coordinates are in the GTP coordinate system (as in the rest
|
||||
of gomill), where (0, 0) is the lower left.
|
||||
|
||||
"""
|
||||
if s == b"" or (s == b"tt" and size <= 19):
|
||||
return None
|
||||
# May propagate ValueError
|
||||
col_s, row_s = s
|
||||
col = _bytestring_ord(col_s) - 97 # 97 == ord("a")
|
||||
row = size - _bytestring_ord(row_s) + 96
|
||||
if not ((0 <= col < size) and (0 <= row < size)):
|
||||
raise ValueError
|
||||
return row, col
|
||||
|
||||
|
||||
def serialise_go_point(move, size):
|
||||
"""Serialise a Go Point, Move, or Stone value.
|
||||
|
||||
move -- pair (row, col), or None for a pass
|
||||
|
||||
Returns an 8-bit string.
|
||||
|
||||
Only supports board sizes up to 26.
|
||||
|
||||
The move coordinates are in the GTP coordinate system (as in the rest of
|
||||
gomill), where (0, 0) is the lower left.
|
||||
|
||||
"""
|
||||
if not 1 <= size <= 26:
|
||||
raise ValueError
|
||||
if move is None:
|
||||
# Prefer 'tt' where possible, for the sake of older code
|
||||
if size <= 19:
|
||||
return b"tt"
|
||||
else:
|
||||
return b""
|
||||
row, col = move
|
||||
if not ((0 <= col < size) and (0 <= row < size)):
|
||||
raise ValueError
|
||||
col_s = "abcdefghijklmnopqrstuvwxy"[col].encode('ascii')
|
||||
row_s = "abcdefghijklmnopqrstuvwxy"[size - row - 1].encode('ascii')
|
||||
return col_s + row_s
|
||||
|
||||
|
||||
class _Context:
|
||||
def __init__(self, size, encoding):
|
||||
self.size = size
|
||||
self.encoding = encoding
|
||||
|
||||
|
||||
def interpret_none(s, context=None):
|
||||
"""Convert a raw None value to a boolean.
|
||||
|
||||
That is, unconditionally returns True.
|
||||
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
def serialise_none(b, context=None):
|
||||
"""Serialise a None value.
|
||||
|
||||
Ignores its parameter.
|
||||
|
||||
"""
|
||||
return b""
|
||||
|
||||
|
||||
def interpret_number(s, context=None):
|
||||
"""Convert a raw Number value to the integer it represents.
|
||||
|
||||
This is a little more lenient than the SGF spec: it permits leading and
|
||||
trailing spaces, and spaces between the sign and the numerals.
|
||||
|
||||
"""
|
||||
return int(s, 10)
|
||||
|
||||
|
||||
def serialise_number(i, context=None):
|
||||
"""Serialise a Number value.
|
||||
|
||||
i -- integer
|
||||
|
||||
"""
|
||||
return ("%d" % i).encode('ascii')
|
||||
|
||||
|
||||
def interpret_real(s, context=None):
|
||||
"""Convert a raw Real value to the float it represents.
|
||||
|
||||
This is more lenient than the SGF spec: it accepts strings accepted as a
|
||||
float by the platform libc. It rejects infinities and NaNs.
|
||||
|
||||
"""
|
||||
result = float(s)
|
||||
if isinf(result):
|
||||
raise ValueError("infinite")
|
||||
if isnan(result):
|
||||
raise ValueError("not a number")
|
||||
return result
|
||||
|
||||
|
||||
def serialise_real(f, context=None):
|
||||
"""Serialise a Real value.
|
||||
|
||||
f -- real number (int or float)
|
||||
|
||||
If the absolute value is too small to conveniently express as a decimal,
|
||||
returns "0" (this currently happens if abs(f) is less than 0.0001).
|
||||
|
||||
"""
|
||||
f = float(f)
|
||||
try:
|
||||
i = int(f)
|
||||
except OverflowError:
|
||||
# infinity
|
||||
raise ValueError
|
||||
if f == i:
|
||||
# avoid trailing '.0'; also avoid scientific notation for large numbers
|
||||
return str(i).encode('ascii')
|
||||
s = repr(f)
|
||||
if 'e-' in s:
|
||||
return "0".encode('ascii')
|
||||
return s.encode('ascii')
|
||||
|
||||
|
||||
def interpret_double(s, context=None):
|
||||
"""Convert a raw Double value to an integer.
|
||||
|
||||
Returns 1 or 2 (unknown values are treated as 1).
|
||||
|
||||
"""
|
||||
if s.strip() == b"2":
|
||||
return 2
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
def serialise_double(i, context=None):
|
||||
"""Serialise a Double value.
|
||||
|
||||
i -- integer (1 or 2)
|
||||
|
||||
(unknown values are treated as 1)
|
||||
|
||||
"""
|
||||
if i == 2:
|
||||
return "2"
|
||||
return "1"
|
||||
|
||||
|
||||
def interpret_colour(s, context=None):
|
||||
"""Convert a raw Color value to a gomill colour.
|
||||
|
||||
Returns 'b' or 'w'.
|
||||
|
||||
"""
|
||||
colour = s.decode('ascii').lower()
|
||||
if colour not in ('b', 'w'):
|
||||
raise ValueError
|
||||
return colour
|
||||
|
||||
|
||||
def serialise_colour(colour, context=None):
|
||||
"""Serialise a Colour value.
|
||||
|
||||
colour -- 'b' or 'w'
|
||||
|
||||
"""
|
||||
if colour not in ('b', 'w'):
|
||||
raise ValueError
|
||||
return colour.upper().encode('ascii')
|
||||
|
||||
|
||||
def _transcode(s, encoding):
|
||||
"""Common implementation for interpret_text and interpret_simpletext."""
|
||||
# If encoding is UTF-8, we don't need to transcode, but we still want to
|
||||
# report an error if it's not properly encoded.
|
||||
u = s.decode(encoding)
|
||||
if encoding == "UTF-8":
|
||||
return s
|
||||
else:
|
||||
return u.encode("utf-8")
|
||||
|
||||
|
||||
def interpret_simpletext(s, context):
|
||||
"""Convert a raw SimpleText value to a string.
|
||||
|
||||
See sgf_grammar.simpletext_value() for details.
|
||||
|
||||
s -- raw value
|
||||
|
||||
Returns an 8-bit utf-8 string.
|
||||
|
||||
"""
|
||||
return _transcode(sgf_grammar.simpletext_value(s), context.encoding)
|
||||
|
||||
|
||||
def serialise_simpletext(s, context):
|
||||
"""Serialise a SimpleText value.
|
||||
|
||||
See sgf_grammar.escape_text() for details.
|
||||
|
||||
s -- 8-bit utf-8 string
|
||||
|
||||
"""
|
||||
if context.encoding != "UTF-8":
|
||||
s = s.decode("utf-8").encode(context.encoding)
|
||||
return sgf_grammar.escape_text(s)
|
||||
|
||||
|
||||
def interpret_text(s, context):
|
||||
"""Convert a raw Text value to a string.
|
||||
|
||||
See sgf_grammar.text_value() for details.
|
||||
|
||||
s -- raw value
|
||||
|
||||
Returns an 8-bit utf-8 string.
|
||||
|
||||
"""
|
||||
return _transcode(sgf_grammar.text_value(s), context.encoding)
|
||||
|
||||
|
||||
def serialise_text(s, context):
|
||||
"""Serialise a Text value.
|
||||
|
||||
See sgf_grammar.escape_text() for details.
|
||||
|
||||
s -- 8-bit utf-8 string
|
||||
|
||||
"""
|
||||
if context.encoding != "UTF-8":
|
||||
s = s.decode("utf-8").encode(context.encoding)
|
||||
return sgf_grammar.escape_text(s)
|
||||
|
||||
|
||||
def interpret_point(s, context):
|
||||
"""Convert a raw SGF Point or Stone value to coordinates.
|
||||
|
||||
See interpret_go_point() above for details.
|
||||
|
||||
Returns a pair (row, col).
|
||||
|
||||
"""
|
||||
result = interpret_go_point(s, context.size)
|
||||
if result is None:
|
||||
raise ValueError
|
||||
return result
|
||||
|
||||
|
||||
def serialise_point(point, context):
|
||||
"""Serialise a Point or Stone value.
|
||||
|
||||
point -- pair (row, col)
|
||||
|
||||
See serialise_go_point() above for details.
|
||||
|
||||
"""
|
||||
if point is None:
|
||||
raise ValueError
|
||||
return serialise_go_point(point, context.size)
|
||||
|
||||
|
||||
def interpret_move(s, context):
|
||||
"""Convert a raw SGF Move value to coordinates.
|
||||
|
||||
See interpret_go_point() above for details.
|
||||
|
||||
Returns a pair (row, col), or None for a pass.
|
||||
|
||||
"""
|
||||
return interpret_go_point(s, context.size)
|
||||
|
||||
|
||||
def serialise_move(move, context):
|
||||
"""Serialise a Move value.
|
||||
|
||||
move -- pair (row, col), or None for a pass
|
||||
|
||||
See serialise_go_point() above for details.
|
||||
|
||||
"""
|
||||
return serialise_go_point(move, context.size)
|
||||
|
||||
|
||||
def interpret_point_list(values, context):
|
||||
"""Convert a raw SGF list of Points to a set of coordinates.
|
||||
|
||||
values -- list of strings
|
||||
|
||||
Returns a set of pairs (row, col).
|
||||
|
||||
If 'values' is empty, returns an empty set.
|
||||
|
||||
This interprets compressed point lists.
|
||||
|
||||
Doesn't complain if there is overlap, or if a single point is specified as
|
||||
a 1x1 rectangle.
|
||||
|
||||
Raises ValueError if the data is otherwise malformed.
|
||||
|
||||
"""
|
||||
result = set()
|
||||
for s in values:
|
||||
# No need to use parse_compose(), as \: would always be an error.
|
||||
p1, is_rectangle, p2 = s.partition(b":")
|
||||
if is_rectangle:
|
||||
top, left = interpret_point(p1, context)
|
||||
bottom, right = interpret_point(p2, context)
|
||||
if not (bottom <= top and left <= right):
|
||||
raise ValueError
|
||||
for row in range(bottom, top + 1):
|
||||
for col in range(left, right + 1):
|
||||
result.add((row, col))
|
||||
else:
|
||||
pt = interpret_point(p1, context)
|
||||
result.add(pt)
|
||||
return result
|
||||
|
||||
|
||||
def serialise_point_list(points, context):
|
||||
"""Serialise a list of Points, Moves, or Stones.
|
||||
|
||||
points -- iterable of pairs (row, col)
|
||||
|
||||
Returns a list of strings.
|
||||
|
||||
If 'points' is empty, returns an empty list.
|
||||
|
||||
Doesn't produce a compressed point list.
|
||||
|
||||
"""
|
||||
result = [serialise_point(point, context) for point in points]
|
||||
result.sort()
|
||||
return result
|
||||
|
||||
|
||||
def interpret_AP(s, context):
|
||||
"""Interpret an AP (application) property value.
|
||||
|
||||
Returns a pair of strings (name, version number)
|
||||
|
||||
Permits the version number to be missing (which is forbidden by the SGF
|
||||
spec), in which case the second returned value is an empty string.
|
||||
|
||||
"""
|
||||
application, version = sgf_grammar.parse_compose(s)
|
||||
if version is None:
|
||||
version = b""
|
||||
return (interpret_simpletext(application, context),
|
||||
interpret_simpletext(version, context))
|
||||
|
||||
|
||||
def serialise_AP(value, context):
|
||||
"""Serialise an AP (application) property value.
|
||||
|
||||
value -- pair (application, version)
|
||||
application -- string
|
||||
version -- string
|
||||
|
||||
Note this takes a single parameter (which is a pair).
|
||||
|
||||
"""
|
||||
application, version = value
|
||||
return sgf_grammar.compose(serialise_simpletext(application, context),
|
||||
serialise_simpletext(version, context))
|
||||
|
||||
|
||||
def interpret_ARLN_list(values, context):
|
||||
"""Interpret an AR (arrow) or LN (line) property value.
|
||||
|
||||
Returns a list of pairs (point, point), where point is a pair (row, col)
|
||||
|
||||
"""
|
||||
result = []
|
||||
for s in values:
|
||||
p1, p2 = sgf_grammar.parse_compose(s)
|
||||
result.append((interpret_point(p1, context),
|
||||
interpret_point(p2, context)))
|
||||
return result
|
||||
|
||||
|
||||
def serialise_ARLN_list(values, context):
|
||||
"""Serialise an AR (arrow) or LN (line) property value.
|
||||
|
||||
values -- list of pairs (point, point), where point is a pair (row, col)
|
||||
|
||||
"""
|
||||
return [b":".join((serialise_point(p1, context), serialise_point(p2, context)))
|
||||
for p1, p2 in values]
|
||||
|
||||
|
||||
def interpret_FG(s, context):
|
||||
"""Interpret an FG (figure) property value.
|
||||
|
||||
Returns a pair (flags, string), or None.
|
||||
|
||||
flags is an integer; see http://www.red-bean.com/sgf/properties.html#FG
|
||||
|
||||
"""
|
||||
if s == b"":
|
||||
return None
|
||||
flags, name = sgf_grammar.parse_compose(s)
|
||||
return int(flags), interpret_simpletext(name, context)
|
||||
|
||||
|
||||
def serialise_FG(value, context):
|
||||
"""Serialise an FG (figure) property value.
|
||||
|
||||
value -- pair (flags, name), or None
|
||||
flags -- int
|
||||
name -- string
|
||||
|
||||
Use serialise_FG(None) to produce an empty value.
|
||||
|
||||
"""
|
||||
if value is None:
|
||||
return b""
|
||||
flags, name = value
|
||||
return str(flags).encode('ascii') + b":" + serialise_simpletext(name, context)
|
||||
|
||||
|
||||
def interpret_LB_list(values, context):
|
||||
"""Interpret an LB (label) property value.
|
||||
|
||||
Returns a list of pairs ((row, col), string).
|
||||
|
||||
"""
|
||||
result = []
|
||||
for s in values:
|
||||
point, label = sgf_grammar.parse_compose(s)
|
||||
result.append((interpret_point(point, context),
|
||||
interpret_simpletext(label, context)))
|
||||
return result
|
||||
|
||||
|
||||
def serialise_LB_list(values, context):
|
||||
"""Serialise an LB (label) property value.
|
||||
|
||||
values -- list of pairs ((row, col), string)
|
||||
|
||||
"""
|
||||
return [b":".join((serialise_point(point, context), serialise_simpletext(text, context)))
|
||||
for point, text in values]
|
||||
|
||||
|
||||
class Property_type:
|
||||
"""Description of a property type."""
|
||||
def __init__(self, interpreter, serialiser, uses_list,
|
||||
allows_empty_list=False):
|
||||
self.interpreter = interpreter
|
||||
self.serialiser = serialiser
|
||||
self.uses_list = bool(uses_list)
|
||||
self.allows_empty_list = bool(allows_empty_list)
|
||||
|
||||
|
||||
def _make_property_type(type_name, allows_empty_list=False):
|
||||
return Property_type(
|
||||
globals()["interpret_" + type_name],
|
||||
globals()["serialise_" + type_name],
|
||||
uses_list=(type_name.endswith("_list")),
|
||||
allows_empty_list=allows_empty_list)
|
||||
|
||||
|
||||
_property_types_by_name = {
|
||||
'none': _make_property_type('none'),
|
||||
'number': _make_property_type('number'),
|
||||
'real': _make_property_type('real'),
|
||||
'double': _make_property_type('double'),
|
||||
'colour': _make_property_type('colour'),
|
||||
'simpletext': _make_property_type('simpletext'),
|
||||
'text': _make_property_type('text'),
|
||||
'point': _make_property_type('point'),
|
||||
'move': _make_property_type('move'),
|
||||
'point_list': _make_property_type('point_list'),
|
||||
'point_elist': _make_property_type('point_list', allows_empty_list=True),
|
||||
'stone_list': _make_property_type('point_list'),
|
||||
'AP': _make_property_type('AP'),
|
||||
'ARLN_list': _make_property_type('ARLN_list'),
|
||||
'FG': _make_property_type('FG'),
|
||||
'LB_list': _make_property_type('LB_list'),
|
||||
}
|
||||
|
||||
P = _property_types_by_name
|
||||
|
||||
_property_types_by_ident = {
|
||||
b'AB': P['stone_list'], # setup Add Black
|
||||
b'AE': P['point_list'], # setup Add Empty
|
||||
b'AN': P['simpletext'], # game-info Annotation
|
||||
b'AP': P['AP'], # root Application
|
||||
b'AR': P['ARLN_list'], # - Arrow
|
||||
b'AW': P['stone_list'], # setup Add White
|
||||
b'B': P['move'], # move Black
|
||||
b'BL': P['real'], # move Black time left
|
||||
b'BM': P['double'], # move Bad move
|
||||
b'BR': P['simpletext'], # game-info Black rank
|
||||
b'BT': P['simpletext'], # game-info Black team
|
||||
b'C': P['text'], # - Comment
|
||||
b'CA': P['simpletext'], # root Charset
|
||||
b'CP': P['simpletext'], # game-info Copyright
|
||||
b'CR': P['point_list'], # - Circle
|
||||
b'DD': P['point_elist'], # - [inherit] Dim points
|
||||
b'DM': P['double'], # - Even position
|
||||
b'DO': P['none'], # move Doubtful
|
||||
b'DT': P['simpletext'], # game-info Date
|
||||
b'EV': P['simpletext'], # game-info Event
|
||||
b'FF': P['number'], # root Fileformat
|
||||
b'FG': P['FG'], # - Figure
|
||||
b'GB': P['double'], # - Good for Black
|
||||
b'GC': P['text'], # game-info Game comment
|
||||
b'GM': P['number'], # root Game
|
||||
b'GN': P['simpletext'], # game-info Game name
|
||||
b'GW': P['double'], # - Good for White
|
||||
b'HA': P['number'], # game-info Handicap
|
||||
b'HO': P['double'], # - Hotspot
|
||||
b'IT': P['none'], # move Interesting
|
||||
b'KM': P['real'], # game-info Komi
|
||||
b'KO': P['none'], # move Ko
|
||||
b'LB': P['LB_list'], # - Label
|
||||
b'LN': P['ARLN_list'], # - Line
|
||||
b'MA': P['point_list'], # - Mark
|
||||
b'MN': P['number'], # move set move number
|
||||
b'N': P['simpletext'], # - Nodename
|
||||
b'OB': P['number'], # move OtStones Black
|
||||
b'ON': P['simpletext'], # game-info Opening
|
||||
b'OT': P['simpletext'], # game-info Overtime
|
||||
b'OW': P['number'], # move OtStones White
|
||||
b'PB': P['simpletext'], # game-info Player Black
|
||||
b'PC': P['simpletext'], # game-info Place
|
||||
b'PL': P['colour'], # setup Player to play
|
||||
b'PM': P['number'], # - [inherit] Print move mode
|
||||
b'PW': P['simpletext'], # game-info Player White
|
||||
b'RE': P['simpletext'], # game-info Result
|
||||
b'RO': P['simpletext'], # game-info Round
|
||||
b'RU': P['simpletext'], # game-info Rules
|
||||
b'SL': P['point_list'], # - Selected
|
||||
b'SO': P['simpletext'], # game-info Source
|
||||
b'SQ': P['point_list'], # - Square
|
||||
b'ST': P['number'], # root Style
|
||||
b'SZ': P['number'], # root Size
|
||||
b'TB': P['point_elist'], # - Territory Black
|
||||
b'TE': P['double'], # move Tesuji
|
||||
b'TM': P['real'], # game-info Timelimit
|
||||
b'TR': P['point_list'], # - Triangle
|
||||
b'TW': P['point_elist'], # - Territory White
|
||||
b'UC': P['double'], # - Unclear pos
|
||||
b'US': P['simpletext'], # game-info User
|
||||
b'V': P['real'], # - Value
|
||||
b'VW': P['point_elist'], # - [inherit] View
|
||||
b'W': P['move'], # move White
|
||||
b'WL': P['real'], # move White time left
|
||||
b'WR': P['simpletext'], # game-info White rank
|
||||
b'WT': P['simpletext'], # game-info White team
|
||||
}
|
||||
_text_property_type = P['text']
|
||||
|
||||
del P
|
||||
|
||||
|
||||
class Presenter(_Context):
|
||||
"""Convert property values between Python and SGF-string representations.
|
||||
|
||||
Instantiate with:
|
||||
size -- board size (int)
|
||||
encoding -- encoding for the SGF strings
|
||||
|
||||
Public attributes (treat as read-only):
|
||||
size -- int
|
||||
encoding -- string (normalised form)
|
||||
|
||||
See the _property_types_by_ident table above for a list of properties
|
||||
initially known, and their types.
|
||||
|
||||
Initially, treats unknown (private) properties as if they had type Text.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, size, encoding):
|
||||
try:
|
||||
encoding = normalise_charset_name(encoding)
|
||||
except LookupError:
|
||||
raise ValueError("unknown encoding: %s" % encoding)
|
||||
_Context.__init__(self, size, encoding)
|
||||
self.property_types_by_ident = _property_types_by_ident.copy()
|
||||
self.default_property_type = _text_property_type
|
||||
|
||||
def get_property_type(self, identifier):
|
||||
"""Return the Property_type for the specified PropIdent.
|
||||
|
||||
Rasies KeyError if the property is unknown.
|
||||
|
||||
"""
|
||||
return self.property_types_by_ident[identifier]
|
||||
|
||||
def register_property(self, identifier, property_type):
|
||||
"""Specify the Property_type for a PropIdent."""
|
||||
self.property_types_by_ident[identifier] = property_type
|
||||
|
||||
def deregister_property(self, identifier):
|
||||
"""Forget the type for the specified PropIdent."""
|
||||
del self.property_types_by_ident[identifier]
|
||||
|
||||
def set_private_property_type(self, property_type):
|
||||
"""Specify the Property_type to use for unknown properties.
|
||||
|
||||
Pass property_type = None to make unknown properties raise an error.
|
||||
|
||||
"""
|
||||
self.default_property_type = property_type
|
||||
|
||||
def _get_effective_property_type(self, identifier):
|
||||
try:
|
||||
return self.property_types_by_ident[identifier]
|
||||
except KeyError:
|
||||
result = self.default_property_type
|
||||
if result is None:
|
||||
raise ValueError("unknown property")
|
||||
return result
|
||||
|
||||
def interpret_as_type(self, property_type, raw_values):
|
||||
"""Variant of interpret() for explicitly specified type.
|
||||
|
||||
property_type -- Property_type
|
||||
|
||||
"""
|
||||
if not raw_values:
|
||||
raise ValueError("no raw values")
|
||||
if property_type.uses_list:
|
||||
if raw_values == [b""]:
|
||||
raw = []
|
||||
else:
|
||||
raw = raw_values
|
||||
else:
|
||||
if len(raw_values) > 1:
|
||||
raise ValueError("multiple values")
|
||||
raw = raw_values[0]
|
||||
return property_type.interpreter(raw, self)
|
||||
|
||||
def interpret(self, identifier, raw_values):
|
||||
"""Return a Python representation of a property value.
|
||||
|
||||
identifier -- PropIdent
|
||||
raw_values -- nonempty list of 8-bit strings in the presenter's encoding
|
||||
|
||||
See the interpret_... functions above for details of how values are
|
||||
represented as Python types.
|
||||
|
||||
Raises ValueError if it cannot interpret the value.
|
||||
|
||||
Note that in some cases the interpret_... functions accept values which
|
||||
are not strictly permitted by the specification.
|
||||
|
||||
elist handling: if the property's value type is a list type and
|
||||
'raw_values' is a list containing a single empty string, passes an
|
||||
empty list to the interpret_... function (that is, this function treats
|
||||
all lists like elists).
|
||||
|
||||
Doesn't enforce range restrictions on values with type Number.
|
||||
|
||||
"""
|
||||
return self.interpret_as_type(
|
||||
self._get_effective_property_type(identifier), raw_values)
|
||||
|
||||
def serialise_as_type(self, property_type, value):
|
||||
"""Variant of serialise() for explicitly specified type.
|
||||
|
||||
property_type -- Property_type
|
||||
|
||||
"""
|
||||
serialised = property_type.serialiser(value, self)
|
||||
if property_type.uses_list:
|
||||
if serialised == []:
|
||||
if property_type.allows_empty_list:
|
||||
return [b""]
|
||||
else:
|
||||
raise ValueError("empty list")
|
||||
return serialised
|
||||
else:
|
||||
return [serialised]
|
||||
|
||||
def serialise(self, identifier, value):
|
||||
"""Serialise a Python representation of a property value.
|
||||
|
||||
identifier -- PropIdent
|
||||
value -- corresponding Python value
|
||||
|
||||
Returns a nonempty list of 8-bit strings in the presenter's encoding,
|
||||
suitable for use as raw PropValues.
|
||||
|
||||
See the serialise_... functions above for details of the acceptable
|
||||
values for each type.
|
||||
|
||||
elist handling: if the property's value type is an elist type and the
|
||||
serialise_... function returns an empty list, this returns a list
|
||||
containing a single empty string.
|
||||
|
||||
Raises ValueError if it cannot serialise the value.
|
||||
|
||||
In general, the serialise_... functions try not to produce an invalid
|
||||
result, but do not try to prevent garbage input happening to produce a
|
||||
valid result.
|
||||
|
||||
"""
|
||||
return self.serialise_as_type(
|
||||
self._get_effective_property_type(identifier), value)
|
||||
Reference in New Issue
Block a user