|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import absolute_import |
|
|
from __future__ import print_function |
|
|
from __future__ import unicode_literals |
|
|
from itertools import chain |
|
|
from xml.sax.saxutils import escape |
|
|
from xml.sax.saxutils import quoteattr |
|
|
import logging |
|
|
import sys |
|
|
|
|
|
from .filestructure import VERSION |
|
|
from .dataio import typed_struct_attributes |
|
|
from .dataio import Struct |
|
|
from .dataio import StructType |
|
|
from .dataio import ArrayType |
|
|
from .dataio import FlagsType |
|
|
from .dataio import EnumType |
|
|
from .dataio import WCHAR |
|
|
from .dataio import HWPUNIT |
|
|
from .dataio import HWPUNIT16 |
|
|
from .dataio import SHWPUNIT |
|
|
from .binmodel import COLORREF |
|
|
from .binmodel import BinStorageId |
|
|
from .binmodel import Margin |
|
|
from .binmodel import Text |
|
|
from .treeop import STARTEVENT |
|
|
from .treeop import ENDEVENT |
|
|
|
|
|
|
|
|
PY3 = sys.version_info.major == 3 |
|
|
if PY3: |
|
|
basestring = str |
|
|
unichr = chr |
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
def xmlattrval(value): |
|
|
if isinstance(value, basestring): |
|
|
return value |
|
|
elif isinstance(value, float): |
|
|
|
|
|
return repr(value) |
|
|
elif isinstance(type(value), EnumType): |
|
|
return value.name.lower() if value.name else str(int(value)) |
|
|
elif isinstance(value, type): |
|
|
return value.__name__ |
|
|
else: |
|
|
return str(value) |
|
|
|
|
|
|
|
|
def expanded_xmlattribute(ntv): |
|
|
name, (t, value) = ntv |
|
|
if isinstance(t, FlagsType): |
|
|
fmt = '%0' |
|
|
fmt += '%d' % (t.basetype.fixed_size * 2) |
|
|
fmt += 'X' |
|
|
yield name, fmt % int(value) |
|
|
for k, v in t.dictvalue(t(value)).items(): |
|
|
yield k, xmlattrval(v) |
|
|
elif t is Margin: |
|
|
for pos in ('left', 'right', 'top', 'bottom'): |
|
|
yield '-'.join([name, pos]), xmlattrval(value.get(pos)) |
|
|
elif t is COLORREF: |
|
|
yield name, xmlattrval(t(value)) |
|
|
elif t is VERSION: |
|
|
yield name, '.'.join(str(x) for x in value) |
|
|
elif t in (HWPUNIT, SHWPUNIT, HWPUNIT16): |
|
|
yield name, str(value) |
|
|
elif t is WCHAR: |
|
|
if value == 0: |
|
|
yield name, u'' |
|
|
else: |
|
|
if value in PUA_SYMBOLS: |
|
|
yield name, PUA_SYMBOLS[value] |
|
|
else: |
|
|
yield name, unichr(value) |
|
|
elif t is BinStorageId: |
|
|
yield name, 'BIN%04X' % value |
|
|
else: |
|
|
yield name, xmlattrval(value) |
|
|
|
|
|
|
|
|
|
|
|
PUA_SYMBOLS = { |
|
|
0xF046: u'☞', |
|
|
0xF06C: u'●', |
|
|
|
|
|
0xF09F: u'•', |
|
|
0xF0A1: u'○', |
|
|
|
|
|
|
|
|
|
|
|
0xF06E: u'■', |
|
|
0xF0A7: u'▪', |
|
|
0xF06F: u'☐', |
|
|
|
|
|
0xF075: u'◆', |
|
|
0xF077: u'⬩', |
|
|
|
|
|
|
|
|
0xF076: u'❖', |
|
|
0xF0A4: u'◉', |
|
|
|
|
|
0xF0AB: u'★', |
|
|
0xF0Fc: u'✓', |
|
|
0xF0FE: u'☑', |
|
|
} |
|
|
|
|
|
|
|
|
def xmlattr_dashednames(attrs): |
|
|
for k, v in attrs: |
|
|
yield k.replace('_', '-'), v |
|
|
|
|
|
|
|
|
def xmlattr_uniqnames(attrs): |
|
|
names = set([]) |
|
|
for k, v in attrs: |
|
|
assert k not in names, 'name clashes: %s' % k |
|
|
yield k, v |
|
|
names.add(k) |
|
|
|
|
|
|
|
|
def xmlattributes_for_plainvalues(context, plainvalues): |
|
|
ntvs = plainvalues.items() |
|
|
ntvs = chain(*(expanded_xmlattribute(ntv) for ntv in ntvs)) |
|
|
return dict(xmlattr_uniqnames(xmlattr_dashednames(ntvs))) |
|
|
|
|
|
|
|
|
def is_complex_type(type, value): |
|
|
if isinstance(value, dict): |
|
|
return True |
|
|
elif isinstance(type, ArrayType) and issubclass(type.itemtype, Struct): |
|
|
return True |
|
|
elif isinstance(type, ArrayType) and issubclass(type.itemtype, COLORREF): |
|
|
return True |
|
|
else: |
|
|
return False |
|
|
|
|
|
|
|
|
def separate_plainvalues(typed_attributes): |
|
|
d = [] |
|
|
p = dict() |
|
|
for named_item in typed_attributes: |
|
|
name, item = named_item |
|
|
t, value = item |
|
|
try: |
|
|
if t is Margin: |
|
|
p[name] = item |
|
|
elif is_complex_type(t, value): |
|
|
d.append(named_item) |
|
|
else: |
|
|
p[name] = item |
|
|
except Exception as e: |
|
|
logger.error('%s', (name, t, value)) |
|
|
logger.error('%s', t.__dict__) |
|
|
logger.exception(e) |
|
|
raise e |
|
|
return d, p |
|
|
|
|
|
|
|
|
def startelement(context, ma): |
|
|
model, attributes = ma |
|
|
if isinstance(model, StructType): |
|
|
typed_attributes = ((v['name'], (v['type'], v['value'])) |
|
|
for v in typed_struct_attributes(model, attributes, |
|
|
context)) |
|
|
else: |
|
|
typed_attributes = ((k, (type(v), v)) |
|
|
for k, v in attributes.items()) |
|
|
|
|
|
typed_attributes, plainvalues = separate_plainvalues(typed_attributes) |
|
|
|
|
|
if model is Text: |
|
|
text = plainvalues.pop('text')[1] |
|
|
elif '<text>' in plainvalues: |
|
|
text = plainvalues.pop('<text>')[1] |
|
|
else: |
|
|
text = None |
|
|
|
|
|
yield STARTEVENT, (model.__name__, |
|
|
xmlattributes_for_plainvalues(context, plainvalues)) |
|
|
if text: |
|
|
yield Text, text |
|
|
|
|
|
for _name, (_type, _value) in typed_attributes: |
|
|
if isinstance(_value, dict): |
|
|
assert isinstance(_value, dict) |
|
|
_value = dict(_value) |
|
|
_value['attribute-name'] = _name |
|
|
for x in element(context, (_type, _value)): |
|
|
yield x |
|
|
else: |
|
|
assert isinstance(_value, (tuple, list)), (_value, _type) |
|
|
|
|
|
if issubclass(_type.itemtype, Struct): |
|
|
yield STARTEVENT, ('Array', {'name': _name}) |
|
|
for _itemvalue in _value: |
|
|
for x in element(context, (_type.itemtype, _itemvalue)): |
|
|
yield x |
|
|
yield ENDEVENT, 'Array' |
|
|
elif issubclass(_type.itemtype, COLORREF): |
|
|
for _itemvalue in _value: |
|
|
yield STARTEVENT, (_name, { |
|
|
'r': '%d' % ((_itemvalue >> 0) & 0xff), |
|
|
'g': '%d' % ((_itemvalue >> 8) & 0xff), |
|
|
'b': '%d' % ((_itemvalue >> 16) & 0xff), |
|
|
'alpha': '%d' % ((_itemvalue >> 24) & 0xff), |
|
|
'hex': xmlattrval(_type.itemtype(_itemvalue)) |
|
|
}) |
|
|
yield ENDEVENT, _name |
|
|
else: |
|
|
assert False, (_value, _type) |
|
|
|
|
|
|
|
|
def element(context, ma): |
|
|
model, attributes = ma |
|
|
for x in startelement(context, ma): |
|
|
yield x |
|
|
yield ENDEVENT, model.__name__ |
|
|
|
|
|
|
|
|
def xmlevents_to_bytechunks(xmlevents, encoding='utf-8'): |
|
|
for textchunk in xmlevents_to_textchunks(xmlevents): |
|
|
yield textchunk.encode(encoding) |
|
|
|
|
|
|
|
|
def xmlevents_to_textchunks(xmlevents): |
|
|
entities = {'\r': ' ', |
|
|
'\n': ' ', |
|
|
'\t': '	'} |
|
|
for event, item in xmlevents: |
|
|
if event is STARTEVENT: |
|
|
yield '<' |
|
|
yield item[0] |
|
|
for n, v in item[1].items(): |
|
|
yield ' ' |
|
|
yield n |
|
|
yield '=' |
|
|
v = quoteattr(v, entities) |
|
|
v = v.replace('\x00', '') |
|
|
yield v |
|
|
yield '>' |
|
|
elif event is Text: |
|
|
text = escape(item) |
|
|
text = text.replace('\x00', '') |
|
|
yield text |
|
|
elif event is ENDEVENT: |
|
|
yield '</' |
|
|
yield item |
|
|
yield '>' |
|
|
|