# -*- coding: utf-8
#
# Copyright (C) 2020 Peter Rowlands <peter@pmrowla.com>
# Copyright (C) 2014 tinfoil <https://bitbucket.org/tinfoil/>
#
# This file is a part of pylivemaker.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
"""Core lmscript classes."""
import enum
from abc import ABC, abstractmethod
import construct
import numpy
from loguru import logger
from lxml import etree
[docs]class BaseSerializable(ABC):
"""Base class for serializable LiveMaker objects.
Note:
LiveMaker uses 3 different script serialization formats:
- LSC (old text .lsc)
- XML (new XML .lsc)
- LSB (compiled binary .lsb)
In pylivemaker, we currently only support serializing to and from
the binary LSB format. Subclasses of `BaseSerializable` do support
serialization to pseudo-LSC and pseudo-XML formats so that a script
can be examined for patching purposes, however, these exported formats
cannot currently be re-read as input by pylivemaker.
This means that pylivemaker cannot be used to compile .lsc files from
a LiveMaker/LiveNovel template or project directory.
"""
def __init__(*args, **kwargs):
pass
# __iter__ and/or __getitem__ need to be implemented in subclasses for
# construct to be able to build an object.
def __iter__(self):
raise NotImplementedError
def __getitem__(self, key):
raise NotImplementedError
[docs] def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
[docs] @abstractmethod
def to_lsc(self):
"""Serialize this object as pseudo-LSC data."""
pass
[docs] @classmethod
def from_lsc(cls, data):
"""Parse text .lsc data into an object."""
raise NotImplementedError
[docs] @abstractmethod
def to_xml(self):
"""Serialize an object as pseudo-LSC XML."""
pass
[docs] @classmethod
def from_xml(cls, root):
"""Parse XML into an object."""
raise NotImplementedError
@classmethod
def _struct(cls):
"""Return a construct Struct for this class."""
return construct.Struct()
[docs] @classmethod
def from_struct(cls, struct, **kwargs):
"""Instantiate an object from a construct Struct."""
if isinstance(struct, construct.Container):
d = {k: v for k, v in struct.items()}
d.update(kwargs)
return cls(**d)
raise NotImplementedError
[docs]class ParamType(enum.IntEnum):
""":class:`Param` data type."""
Var = 0x00
"""Variable name.
Internally, LiveMaker stores TParamVar as a Delphi Variant type which in theory supports
any possible Delphi type, but LiveMaker only uses it as a variable length string.
"""
Int = 0x01
"""Integer."""
Float = 0x02
"""Floating point value.
LiveMaker TParamFloats are IEEE 80-bit precision floats, in pylivemaker we handle them
as numpy `longdouble`.
According to numpy docs, ``np.longdouble` is either `float96` or ``float128`` depending on platform,
and in both cases they are actually ``float80`` padded with zeroes to 96 or 128 bits.
"""
Flag = 0x03
"""1-byte Enum/Flag type."""
Str = 0x04
"""CP932 encoded string.
Internally, pylivemaker handles all strings as Python unicode strings.
"""
[docs]class OpeDataType(enum.IntEnum):
""":class:`OpeData` operator type.
Operator type determines how an expression will be evaluated.
"""
None_ = 0x00
To = 0x01
"""Operator ``=`` (assignment)."""
Plus = 0x02
"""Operator ``+``.
Note:
In LiveMaker, + can be used for both addition and string concatenation, depending
on the data type of the result variable. If the result variable is a numeric type
and one of the arguments is a string, the string will be coerced to number
(i.e. for ``x = 1 + "2"`` and ``x`` is an ``Int``, the final value of ``x`` will be ``3``).
"""
Minus = 0x03
"""Operator ``-``."""
Mul = 0x04
"""Operator ``*``."""
Div = 0x05
"""Operator ``/``."""
Mod = 0x06
"""Operator ``%``."""
Or = 0x07
"""Operator ``|``.
Note:
In LiveMaker, ``|`` is used for both bitwise OR and logical OR, depending
on the data type of the operands.
"""
And = 0x08
"""Operator ``&``.
Note:
In LiveMaker, ``&`` is used for both bitwise AND and logical AND, depending
on the data type of the operands.
"""
Xor = 0x09
"""Operator ``^`` (bitwise XOR)."""
DimTo = 0x0A
"""Operator ``[]`` (array access)."""
Func = 0x0B
"""Operator ``()`` (function call).
Available functions are listed in :class:`OpeFuncType`.
"""
Equal = 0x0C
"""Operator ``==`` (equals)."""
Big = 0x0D
"""Operator ``>`` (greater than)."""
Small = 0x0E
"""Operator ``<`` (less than)."""
EBig = 0x0F
"""Operator ``>=`` (greater than or equals)."""
ESmall = 0x10
"""Operator ``<=`` (less than or equals)."""
ShiftL = 0x11
"""Operator ``<<`` (bitwise shift left)."""
ShiftR = 0x12
"""Operator ``>>`` (bitwise shift right)."""
ComboStr = 0x13
"""Operator ``++`` (string concatenation)."""
NEqual = 0x14
"""Operator ``!=`` (not equals)."""
[docs]class OpeFuncType(enum.IntEnum):
"""Function type.
See LiveNovel docs for details on each available function.
"""
IntToStr = 0x00
IntToHex = 0x01
GetProp = 0x02
SetProp = 0x03
GetArraySize = 0x04
Length = 0x05
JLength = 0x06
Copy = 0x07
JCopy = 0x08
Delete = 0x09
JDelete = 0x0A
Insert = 0x0B
JInsert = 0x0C
CompareStr = 0x0D
CompareText = 0x0E
Pos = 0x0F
JPos = 0x10
Trim = 0x11
JTrim = 0x12
Exists = 0x13
Not = 0x14
SetArray = 0x15
FillMem = 0x16
CopyMem = 0x17
GetCheck = 0x18
SetCheck = 0x19
Random = 0x1A
GetSaveCaption = 0x1B
ArrayToString = 0x1C
StringToArray = 0x1D
IndexOfStr = 0x1E
SortStr = 0x1F
ListCompo = 0x20
ToClientX = 0x21
ToClientY = 0x22
ToScreenX = 0x23
ToScreenY = 0x24
Int = 0x25
Float = 0x26
Sin = 0x27
Cos = 0x28
Tan = 0x29
ArcSin = 0x2A
ArcCos = 0x2B
ArcTan = 0x2C
ArcTan2 = 0x2D
Hypot = 0x2E
IndexOfMenu = 0x2F
Abs = 0x30
Fabs = 0x31
VarExists = 0x32
EncodeDate = 0x33
EncodeTime = 0x34
DecodeDate = 0x35
DecodeTime = 0x36
GetYear = 0x37
GetMonth = 0x38
GetDay = 0x39
GetHour = 0x3A
GetMin = 0x3B
GetSec = 0x3C
GetWeek = 0x3D
GetWeekStr = 0x3E
GetWeekJStr = 0x3F
FixStr = 0x40
GetDisplayMode = 0x41
AddArray = 0x42
InsertArray = 0x43
DeleteArray = 0x44
InPrimary = 0x45
CopyArray = 0x46
FileExists = 0x47
LoadTextFile = 0x48
LowerCase = 0x49
UpperCase = 0x4A
ExtractFilePath = 0x4B
ExtractFileName = 0x4C
ExtractFileExt = 0x4D
IsPathDelimiter = 0x4E
AddBackSlash = 0x4F
ChangeFileExt = 0x50
IsDelimiter = 0x51
StringOfChar = 0x52
StringReplace = 0x53
AssignTemp = 0x54
HanToZen = 0x55
ZenToHan = 0x56
DBCreateTable = 0x57
DBSetActive = 0x58
DBAddField = 0x59
DBSetRecNo = 0x5A
DBInsert = 0x5B
DBDelete = 0x5C
DBGetInt = 0x5D
DBSetInt = 0x5E
DBGetFloat = 0x5F
DBSetFloat = 0x60
DBGetBool = 0x61
DBSetBool = 0x62
DBGetStr = 0x63
DBSetStr = 0x64
DBRecordCount = 0x65
DBFindFirst = 0x66
DBFindLast = 0x67
DBFindNext = 0x68
DBFindPrior = 0x69
DBLocate = 0x6A
DBLoadTsvFile = 0x6B
DBDirectGetInt = 0x6C
DBDirectSetInt = 0x6D
DBDirectGetFloat = 0x6E
DBDirectSetFloat = 0x6F
DBDirectGetBool = 0x70
DBDirectSetBool = 0x71
DBDirectGetStr = 0x72
DBDirectSetStr = 0x73
DBCopyTable = 0x74
DBDeleteTable = 0x75
DBInsertTable = 0x76
DBCopy = 0x77
DBClearTable = 0x78
DBSort = 0x79
DBGetActive = 0x7A
DBGetRecNo = 0x7B
DBClearRecord = 0x7C
SetWallPaper = 0x7D
Min = 0x7E
Max = 0x7F
Fmin = 0x80
Fmax = 0x81
GetVarType = 0x82
GetEnabled = 0x83
SetEnabled = 0x84
AddDelimiter = 0x85
ListSaveCaption = 0x86
OpenUrl = 0x87
Calc = 0x88
SaveScreen = 0x89
StrToIntDef = 0x8A
StrToFloatDef = 0x8B
GetVisible = 0x8C
SetVisible = 0x8D
GetHistoryCount = 0x8E
GetHistoryMaxCount = 0x8F
SetHistoryMaxCount = 0x90
GetGroupIndex = 0x91
GetSelected = 0x92
SetSelected = 0x93
SelectOpenFile = 0x94
SelectSaveFile = 0x95
SelectDirectory = 0x96
ExtractFile = 0x97
Chr = 0x98
Ord = 0x99
InCabinet = 0x9A
PushVar = 0x9B
PopVar = 0x9C
DeleteStack = 0x9D
CopyFile = 0x9E
DBGetTableCount = 0x9F
DBGetTable = 0xA0
CreateObject = 0xA1
DeleteObject = 0xA2
GetItem = 0xA3
UniqueArray = 0xA4
TrimArray = 0xA5
GetImeOpened = 0xA6
SetImeOpened = 0xA7
Alert = 0xA8
GetCinemaProp = 0xA9
SetCinemaProp = 0xAA
[docs]class Param(BaseSerializable):
"""Expression parameter (operand).
Internally, LiveMaker subclasses each possible TParam type,
but in pylivemaker we handle them all here.
Args:
value: The value for this parameter.
type (:class:`ParamType`): The data type for this parameter.
If type is not specified, it will be guessed based on value.
Note:
If `value` is a variable name, ``Var`` type must be explicity specified,
otherwise it will incorrectly be guessed to be ``Str``.
If `value` is an integer flag, ``Flag`` type must be explicitly specified,
otherwise it will be incorrectly guessed to be ``Int``.
"""
def __init__(self, value=None, type=None, **kwargs):
self.value = value
if type is None:
if isinstance(value, int):
self.type = ParamType.Int
elif isinstance(value, (float, numpy.longdouble)):
self.type = ParamType.Float
self.value = numpy.longdouble(value)
elif isinstance(value, bool):
self.type = ParamType.Flag
elif isinstance(value, str):
self.type = ParamType.Str
else:
raise ValueError("Could not guess datatype for {}".format(value))
else:
self.type = ParamType(int(type))
def __str__(self):
return str(self.value)
def __iter__(self):
return iter(self.items())
def __getitem__(self, key):
if key == "type":
return self.type.name
elif key == "value":
return self.value
raise KeyError
[docs] def keys(self):
return ["type", "value"]
[docs] def items(self):
return [(k, self[k]) for k in self.keys()]
[docs] def to_lsc(self):
if self.type == ParamType.Str:
return '"{}"'.format(self.value)
return str(self)
[docs] def to_xml(self):
xml = self.to_lsc()
if self.type == ParamType.Var and "\x01" in xml:
logger.warning('Replacing invalid xml char "\\x01" in varname {}'.format(self.value))
xml = xml.replace("\x01", "*")
return xml
@classmethod
def _struct(cls):
return construct.Struct(
"type" / construct.Enum(construct.Byte, ParamType),
"value"
/ construct.Switch(
construct.this.type,
{
"Int": construct.Int32sl,
"Float": construct.ExprAdapter(
construct.Bytes(10),
lambda obj, ctx: numpy.frombuffer(obj.rjust(16, b"\x00"), dtype=numpy.longdouble),
lambda obj, ctx: numpy.longdouble(obj).tobytes()[-10:],
),
"Flag": construct.Byte,
"Str": construct.PascalString(construct.Int32ul, "cp932"),
},
# else 'Var' variable name type
construct.Select(
construct.PascalString(construct.Int32ul, "cp932"),
),
),
)
[docs]class OpeData(BaseSerializable):
"""Expression operator class.
Internal LiveMaker TOpeData class.
Args:
type (:class:`OpeDataType`): Operator type for this expression.
name (str): The name of result variable for this expression.
func (:class:`OpeFuncType`): Function for this expression (only applicable if
`type` is `OpeDataType.Func`.
operands (list(:class:`Param`)): The operands for this expression.
"""
def __init__(self, type=OpeDataType.None_, name="", func=None, operands=[], **kwargs):
self.type = OpeDataType(int(type))
self.name = name
if self.type == OpeDataType.Func:
self.func = OpeFuncType(int(func))
else:
self.func = None
if isinstance(operands, construct.ListContainer):
operands = [Param.from_struct(x) for x in operands]
self.operands = operands
def __str__(self):
out = []
for token in self.tokenize():
if isinstance(token, Param):
out.append(token.to_lsc())
else:
out.append(str(token))
return "".join(out)
def __iter__(self):
return iter(self.items())
def __len__(self):
return len(self.operands)
def __getitem__(self, key):
if key in self.keys():
v = getattr(self, key)
if isinstance(v, enum.Enum):
v = v.name
return v
raise KeyError
[docs] def keys(self):
return ["type", "name", "count", "func", "operands"]
[docs] def items(self):
return [(k, self[k]) for k in self.keys()]
@property
def count(self):
"""Return the number of operands in this expression."""
return len(self.operands)
[docs] def to_lsc(self):
return str(self)
[docs] def to_xml(self):
out = []
for token in self.tokenize():
if isinstance(token, Param):
out.append(token.to_xml())
else:
out.append(str(token))
return "".join(out)
@classmethod
def _struct(cls):
return construct.Struct(
"type" / construct.Enum(construct.Byte, OpeDataType),
"name" / construct.PascalString(construct.Int32ul, "cp932"),
"count" / construct.Int32ul,
"func" / construct.Switch(construct.this.type, {"Func": construct.Enum(construct.Byte, OpeFuncType)}),
"operands" / construct.Array(construct.this.count, Param._struct()),
)
def _to(self):
return [self.operands[-1]]
# TODO: LiveMaker allows for doing math operators on strings via type
# conversion - the docs say "50" + 100 should result in either 150 or "150"
# depending on the result var's datatype.
#
# For our purposes don't worry about dealing with type coercion unless
# someone finds a script that actually requires supporting it
def _plus(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " + ", p2]
elif p1.type == ParamType.Str or p2.type == ParamType.Str:
raise NotImplementedError("Plus() expected numeric type")
return [Param(value=p1.value + p2.value)]
def _minus(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " - ", p2]
elif p1.type == ParamType.Str or p2.type == ParamType.Str:
raise NotImplementedError("Minus() expected numeric type")
return [Param(value=p1.value - p2.value)]
def _mul(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " * ", p2]
elif p1.type == ParamType.Str or p2.type == ParamType.Str:
raise NotImplementedError("Mul() expected numeric type")
return [Param(value=p1.value * p2.value)]
def _div(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " / ", p2]
elif p1.type == ParamType.Str or p2.type == ParamType.Str:
raise NotImplementedError("Div() expected numeric type")
return [Param(value=p1.value / p2.value)]
def _mod(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " % ", p2]
elif p1.type == ParamType.Str or p2.type == ParamType.Str:
raise NotImplementedError("Mod() expected numeric type")
return [Param(value=p1.value % p2.value)]
def _or(self):
# LiveMaker uses | to specify both bitwise and boolean OR
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return ["(", p1, " | ", p2, ")"]
elif p1.type == ParamType.Str or p2.type == ParamType.Str:
raise NotImplementedError("Or() expected numeric type")
if p1.type == ParamType.Flag and p2.type == ParamType.Flag:
return [Param(value=p1.value or p2.value)]
return [Param(value=p1.value | p2.value)]
def _and(self):
# LiveMaker uses | to specify both bitwise and boolean AND
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return ["(", p1, " & ", p2, ")"]
elif p1.type == ParamType.Str or p2.type == ParamType.Str:
raise NotImplementedError("And() expected numeric type")
if p1.type == ParamType.Flag and p2.type == ParamType.Flag:
return [Param(value=p1.value and p2.value)]
return [p1.value & p2.value]
def _xor(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " ^ ", p2]
elif p1.type == ParamType.Str or p2.type == ParamType.Str:
raise NotImplementedError("Xor() expected numeric type")
return [Param(p1.value ^ p2.value)]
def _dimto(self):
# Array access
x = [self.operands[0]]
for p in self.operands[1:]:
x.extend(["[", p, "]"])
return x
def _func(self):
x = ["{}(".format(self.func.name)]
for i, p in enumerate(self.operands):
x.append(p)
if i != len(self.operands) - 1:
x.append(", ")
x.append(")")
return x
def _equal(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " == ", p2]
return [Param(value=p1.value == p2.value)]
def _big(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " > ", p2]
return [Param(value=p1.value > p2.value)]
def _small(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " < ", p2]
return [Param(value=p1.value < p2.value)]
def _ebig(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " >= ", p2]
return [Param(value=p1.value >= p2.value)]
def _esmall(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " <= ", p2]
return [Param(value=p1.value <= p2.value)]
def _shiftl(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " << ", p2]
elif p1.type == ParamType.Str or p2.type == ParamType.Str:
raise NotImplementedError("ShiftL() expected numeric type")
return [Param(p1.value << p2.value)]
def _shiftr(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " >> ", p2]
elif p1.type == ParamType.Str or p2.type == ParamType.Str:
raise NotImplementedError("ShiftR() expected numeric type")
return [Param(p1.value >> p2.value)]
def _combostr(self):
# String join
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " ++ ", p2]
elif p1.type != ParamType.Str or p2.type != ParamType.Str:
raise NotImplementedError("ComboStr() expected string type")
return [Param(value="".join([p1.value, p2.value]))]
def _nequal(self):
(p1, p2) = self.operands
if p1.type == ParamType.Var or p2.type == ParamType.Var:
return [p1, " != ", p2]
return [Param(value=p1.value != p2.value)]
[docs] def tokenize(self):
"""Return a tokenized version of this expression.
Returns:
list(str, :class:`Param`): List of tokens.
Raises:
NotImplementedError: If an operator does not support this combination of
operands.
"""
try:
tokens = {
OpeDataType.To: self._to,
OpeDataType.Plus: self._plus,
OpeDataType.Minus: self._minus,
OpeDataType.Mul: self._mul,
OpeDataType.Div: self._div,
OpeDataType.Mod: self._mod,
OpeDataType.Or: self._or,
OpeDataType.And: self._and,
OpeDataType.Xor: self._xor,
OpeDataType.DimTo: self._dimto,
OpeDataType.Func: self._func,
OpeDataType.Equal: self._equal,
OpeDataType.Big: self._big,
OpeDataType.Small: self._small,
OpeDataType.EBig: self._ebig,
OpeDataType.ESmall: self._esmall,
OpeDataType.ShiftL: self._shiftl,
OpeDataType.ShiftR: self._shiftr,
OpeDataType.ComboStr: self._combostr,
OpeDataType.NEqual: self._nequal,
}[self.type]()
return tokens
except KeyError:
raise NotImplementedError("Cannot compute value for {} types.".format(self.type))
[docs]class LiveParser(BaseSerializable):
"""Parses a list of OpeData expressions into one result expression.
Args:
entries (list(:class:`OpeData`)): List of child expressions
"""
def __init__(self, entries=[], **kwargs):
if isinstance(entries, construct.ListContainer):
entries = [OpeData.from_struct(x) for x in entries]
self.entries = entries
def __str__(self):
return self._simplify()
def __iter__(self):
return iter(self.items())
def __len__(self):
return len(self.entries)
def __getitem__(self, key):
if key == "entries":
return self.entries
raise KeyError
[docs] def keys(self):
return ["entries"]
[docs] def items(self):
return [(k, self[k]) for k in self.keys()]
[docs] def to_lsc(self):
return str(self)
[docs] def to_xml(self):
xml = self.to_lsc()
if "\x01" in xml:
logger.warning('Replacing invalid xml char "\\x01"')
xml = xml.replace("\x01", "*")
return xml
@classmethod
def _struct(cls):
return construct.Struct(
"entries" / construct.PrefixedArray(construct.Int32ul, OpeData._struct()),
)
# @classmethod
# def from_struct(cls, struct):
# """Return a LiveParser for the specified struct."""
# return cls(entries=[OpeData.from_struct(x) for x in struct.entries])
def _simplify(self):
"""Return a simplified expression for all expressions in this parser."""
def _resolve(var, exprs):
if var in exprs:
operands = exprs[var]
for i, op in enumerate(operands):
if isinstance(op, Param):
if op.type == ParamType.Var:
if op.value.startswith("____"):
# LiveParser parameter name
operands[i] = _resolve(op.value, exprs)
else:
operands[i] = op.value
elif op.type == ParamType.Str:
operands[i] = '"{}"'.format(op.value).replace("\n", "\\n").replace("\r", "\\r")
else:
operands[i] = op.value
else:
operands = [var]
return "".join([str(x) for x in operands])
exprs = {}
for e in self.entries:
exprs.update({e.name: e.tokenize()})
if self.entries:
e = self.entries[-1]
if e.type == OpeDataType.To:
if e.name == "____arg":
return _resolve("____arg", exprs)
return "{} = {}".format(e.name, _resolve(e.name, exprs))
else:
logger.warning("Last entry in LiveParser was not a To statement: {}".format(self.entries[-1]))
return ""
[docs]class LiveParserArray(BaseSerializable):
"""Internal use convenience class for handling arrays of :obj:`LiveParser` objects.
Args:
parsers (iterable): Iterable containing this array's parsers.
name (str): Name for this field, used as XML tag name when serializing.
"""
def __init__(self, parsers=[], prefixed=True):
if isinstance(parsers, construct.ListContainer):
parsers = [LiveParser.from_struct(x) for x in parsers]
self.parsers = parsers
self.prefixed = prefixed
def __str__(self):
return " ".join([str(x) for x in self.parsers])
def __iter__(self):
return iter(self.parsers)
def __len__(self):
return len(self.parsers)
[docs] def to_lsc(self):
"""Return this command in text .lsc format."""
out = []
for parser in self.parsers:
if parser:
out.append(parser.to_lsc())
else:
out.append("")
return "\t".join(out)
# @classmethod
# def from_lsc(cls, *args, **kwargs):
# parsers = []
# for arg in args:
# if arg:
# parsers.append(LiveParser.from_lsc(arg))
# else:
# parsers.append('')
# return cls(parsers)
[docs] def to_xml(self):
# NOTE: For whatever reason, LiveMaker uses a '\x02' separated list of
# strings for variable width lists, and child <Item> tags for fixed
# size lists.
#
# '\x02' is not a legal XMl character and lxml will not accept it, so
# we just use \t.
out = []
if self.prefixed:
for parser in self.parsers:
if hasattr(parser, "to_xml"):
out.append(parser.to_xml())
else:
out.append(str(parser))
return "\t".join(out)
for parser in self.parsers:
item = etree.Element("Item")
if hasattr(parser, "to_xml"):
item.text = parser.to_xml()
else:
item.text = str(parser)
out.append(item)
return out
# @classmethod
# def from_xml(cls, root, **kwargs):
# parsers = []
# prefixed = True
# if len(root):
# prefixed = False
# for child in root:
# if child.tag == 'Item':
# parsers.append(LiveParser.from_xml(child))
# else:
# for parser in root.text.split('\x02'):
# if parser:
# parsers.append(LiveParser.from_xml(child))
# else:
# parsers.append('')
# return LiveParserArray(parsers, prefixed=prefixed)
@classmethod
def _struct(cls, subcon, prefixed=True):
"""Return a construct Struct for this class.
Args:
subcon: Can be a subcon or fixed size, depending on value of `prefixed`.
prefixed: True if this is a PrefixedArray.
"""
if prefixed:
return construct.PrefixedArray(subcon, LiveParser._struct())
else:
return construct.Array(subcon, LiveParser._struct())
[docs] @classmethod
def from_struct(cls, struct, prefixed=True):
return cls(struct, prefixed=prefixed)
[docs]class PropertyType(enum.IntEnum):
"""LiveMaker object property constants."""
PR_NONE = 0x00
PR_NAME = 0x01
PR_PARENT = 0x02
PR_SOURCE = 0x03
PR_LEFT = 0x04
PR_TOP = 0x05
PR_WIDTH = 0x06
PR_HEIGHT = 0x07
PR_ZOOMX = 0x08
PR_COLOR = 0x09
PR_BORDERWIDTH = 0x0A
PR_BORDERCOLOR = 0x0B
PR_ALPHA = 0x0C
PR_PRIORITY = 0x0D
PR_OFFSETX = 0x0E
PR_OFFSETY = 0x0F
PR_FONTNAME = 0x10
PR_FONTHEIGHT = 0x11
PR_FONTSTYLE = 0x12
PR_LINESPACE = 0x13
PR_FONTCOLOR = 0x14
PR_FONTLINKCOLOR = 0x15
PR_FONTBORDERCOLOR = 0x16
PR_FONTHOVERCOLOR = 0x17
PR_FONTHOVERSTYLE = 0x18
PR_HOVERCOLOR = 0x19
PR_ANTIALIAS = 0x1A
PR_DELAY = 0x1B
PR_PAUSED = 0x1C
PR_VOLUME = 0x1D
PR_REPEAT = 0x1E
PR_BALANCE = 0x1F
PR_ANGLE = 0x20
PR_ONPLAYING = 0x21
PR_ONNOTIFY = 0x22
PR_ONMOUSEMOVE = 0x23
PR_ONMOUSEOUT = 0x24
PR_ONLBTNDOWN = 0x25
PR_ONLBTNUP = 0x26
PR_ONRBTNDOWN = 0x27
PR_ONRBTNUP = 0x28
PR_ONWHEELDOWN = 0x29
PR_ONWHEELUP = 0x2A
PR_BRIGHTNESS = 0x2B
PR_ONPLAYEND = 0x2C
PR_INDEX = 0x2D
PR_COUNT = 0x2E
PR_ONLINK = 0x2F
PR_VISIBLE = 0x30
PR_COLCOUNT = 0x31
PR_ROWCOUNT = 0x32
PR_TEXT = 0x33
PR_MARGINX = 0x34
PR_MARGINY = 0x35
PR_HALIGN = 0x36
PR_BORDERSOURCETL = 0x37
PR_BORDERSOURCETC = 0x38
PR_BORDERSOURCETR = 0x39
PR_BORDERSOURCECL = 0x3A
PR_BORDERSOURCECC = 0x3B
PR_BORDERSOURCECR = 0x3C
PR_BORDERSOURCEBL = 0x3D
PR_BORDERSOURCEBC = 0x3E
PR_BORDERSOURCEBR = 0x3F
PR_BORDERHALIGNT = 0x40
PR_BORDERHALIGNC = 0x41
PR_BORDERHALIGNB = 0x42
PR_BORDERVALIGNL = 0x43
PR_BORDERVALIGNC = 0x44
PR_BORDERVALIGNR = 0x45
PR_SCROLLSOURCE = 0x46
PR_CHECKSOURCE = 0x47
PR_AUTOSCRAP = 0x48
PR_ONSELECT = 0x49
PR_RCLICKSCRAP = 0x4A
PR_ONOPENING = 0x4B
PR_ONOPENED = 0x4C
PR_ONCLOSING = 0x4D
PR_ONCLOSED = 0x4E
PR_CARETX = 0x4F
PR_CARETY = 0x50
PR_IGNOREMOUSE = 0x51
PR_TEXTPAUSED = 0x52
PR_TEXTDELAY = 0x53
PR_HOVERSOURCE = 0x54
PR_PRESSEDSOURCE = 0x55
PR_GROUPINDEX = 0x56
PR_ALLOWALLUP = 0x57
PR_SELECTED = 0x58
PR_CAPTUREMASK = 0x59
PR_POWER = 0x5A
PR_ORIGWIDTH = 0x5B
PR_ORIGHEIGHT = 0x5C
PR_APPEARX = 0x5D
PR_APPEARY = 0x5E
PR_PARTMOTION = 0x5F
PR_PARAM = 0x60
PR_PARAM2 = 0x61
PR_TOPINDEX = 0x62
PR_READONLY = 0x63
PR_CURSOR = 0x64
PR_POSZOOMED = 0x65
PR_ONPLAYSTART = 0x66
PR_PARAM3 = 0x67
PR_ONMOUSEIN = 0x68
PR_ONMAPIN = 0x69
PR_ONMAPOUT = 0x6A
PR_MAPSOURCE = 0x6B
PR_AMP = 0x6C
PR_WAVELEN = 0x6D
PR_SCROLLX = 0x6E
PR_SCROLLY = 0x6F
PR_FLIPH = 0x70
PR_FLIPV = 0x71
PR_ONIDLE = 0x72
PR_DISTANCEX = 0x73
PR_DISTANCEY = 0x74
PR_CLIPLEFT = 0x75
PR_CLIPTOP = 0x76
PR_CLIPWIDTH = 0x77
PR_CLIPHEIGHT = 0x78
PR_DURATION = 0x79
PR_THUMBSOURCE = 0x7A
PR_BUTTONSOURCE = 0x7B
PR_MIN = 0x7C
PR_MAX = 0x7D
PR_VALUE = 0x7E
PR_ORIENTATION = 0x7F
PR_SMALLCHANGE = 0x80
PR_LARGECHANGE = 0x81
PR_MAPTEXT = 0x82
PR_GLYPHWIDTH = 0x83
PR_GLYPHHEIGHT = 0x84
PR_ZOOMY = 0x85
PR_CLICKEDSOURCE = 0x86
PR_ANIPAUSED = 0x87
PR_ONHOLD = 0x88
PR_ONRELEASE = 0x89
PR_REVERSE = 0x8A
PR_PLAYING = 0x8B
PR_REWINDONLOAD = 0x8C
PR_COMPOTYPE = 0x8D
PR_FONTSHADOWCOLOR = 0x8E
PR_FONTBORDER = 0x8F
PR_FONTSHADOW = 0x90
PR_ONKEYDOWN = 0x91
PR_ONKEYUP = 0x92
PR_ONKEYREPEAT = 0x93
PR_HANDLEKEY = 0x94
PR_ONFOCUSIN = 0x95
PR_ONFOCUSOUT = 0x96
PR_OVERLAY = 0x97
PR_TAG = 0x98
PR_CAPTURELINK = 0x99
PR_FONTHOVERBORDER = 0x9A
PR_FONTHOVERBORDERCOLOR = 0x9B
PR_FONTHOVERSHADOW = 0x9C
PR_FONTHOVERSHADOWCOLOR = 0x9D
PR_BARSIZE = 0x9E
PR_MUTEONLOAD = 0x9F
PR_PLUSX = 0xA0
PR_PLUSY = 0xA1
PR_CARETHEIGHT = 0xA2
PR_REPEATPOS = 0xA3
PR_BLURSPAN = 0xA4
PR_BLURDELAY = 0xA5
PR_FONTCHANGEABLED = 0xA6
PR_IMEMODE = 0xA7
PR_FLOATANGLE = 0xA8
PR_FLOATZOOMX = 0xA9
PR_FLOATZOOMY = 0xAA
PR_CAPMASKLEVEL = 0xAB
PR_PADDINGLEFT = 0xAC
PR_PADDING_RIGHT = 0xAD