#!/usr/bin/env python
"""
oleform.py
oleform is a python module to parse VBA forms in Microsoft Office files.
Authors: see https://github.com/decalage2/oletools/commits/master/oletools/oleform.py
License: BSD, see source code or documentation
oleform is part of the python-oletools package:
http://www.decalage.info/python/oletools
"""
# === LICENSE ==================================================================
# oletools is copyright (c) 2012-2020 Philippe Lagadec (http://www.decalage.info)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# REFERENCES:
# - MS-OFORMS: https://msdn.microsoft.com/en-us/library/office/cc313125%28v=office.12%29.aspx?f=255&MSPPError=-2147217396
# CHANGELOG:
# 2018-02-19 v0.53 PL: - fixed issue #260, removed long integer literals
import struct
class OleFormParsingError(Exception):
pass
class Mask(object):
def __init__(self, val):
self._val = [(val & (1<<i))>>i for i in range(self._size)]
def __str__(self):
return ', '.join(self._names[i] for i in range(self._size) if self._val[i])
def __getattr__(self, name):
return self._val[self._names.index(name)]
def __len__(self):
return self.size
def __getitem__(self, key):
return self._val[self._names.index(key)]
def consume(self, stream, props):
for (name, size) in props:
if self[name]:
stream.read(size)
class FormPropMask(Mask):
"""FormPropMask: [MS-OFORMS] 2.2.10.2"""
_size = 28
_names = ['Unused1', 'fBackColor', 'fForeColor', 'fNextAvailableID', 'Unused2_0', 'Unused2_1',
'fBooleanProperties', 'fBooleanProperties', 'fMousePointer', 'fScrollBars',
'fDisplayedSize', 'fLogicalSize', 'fScrollPosition', 'fGroupCnt', 'Reserved',
'fMouseIcon', 'fCycle', 'fSpecialEffect', 'fBorderColor', 'fCaption', 'fFont',
'fPicture', 'fZoom', 'fPictureAlignment', 'fPictureTiling', 'fPictureSizeMode',
'fShapeCookie', 'fDrawBuffer']
class SitePropMask(Mask):
"""SitePropMask: [MS-OFORMS] 2.2.10.12.2"""
_size = 15
_names = ['fName', 'fTag', 'fID', 'fHelpContextID', 'fBitFlags', 'fObjectStreamSize',
'fTabIndex', 'fClsidCacheIndex', 'fPosition', 'fGroupID', 'Unused1',
'fControlTipText', 'fRuntimeLicKey', 'fControlSource', 'fRowSource']
class MorphDataPropMask(Mask):
"""MorphDataPropMask: [MS-OFORMS] 2.2.5.2"""
_size = 33
_names = ['fVariousPropertyBits', 'fBackColor', 'fForeColor', 'fMaxLength', 'fBorderStyle',
'fScrollBars', 'fDisplayStyle', 'fMousePointer', 'fSize', 'fPasswordChar',
'fListWidth', 'fBoundColumn', 'fTextColumn', 'fColumnCount', 'fListRows',
'fcColumnInfo', 'fMatchEntry', 'fListStyle', 'fShowDropButtonWhen', 'UnusedBits1',
'fDropButtonStyle', 'fMultiSelect', 'fValue', 'fCaption', 'fPicturePosition',
'fBorderColor', 'fSpecialEffect', 'fMouseIcon', 'fPicture', 'fAccelerator',
'UnusedBits2', 'Reserved', 'fGroupName']
class ImagePropMask(Mask):
"""ImagePropMask: [MS-OFORMS] 2.2.3.2"""
_size = 15
_names = ['UnusedBits1_1', 'UnusedBits1_2', 'fAutoSize', 'fBorderColor', 'fBackColor',
'fBorderStyle', 'fMousePointer', 'fPictureSizeMode', 'fSpecialEffect', 'fSize',
'fPicture', 'fPictureAlignment', 'fPictureTiling', 'fVariousPropertyBits',
'fMouseIcon']
class CommandButtonPropMask(Mask):
"""CommandButtonPropMask: [MS-OFORMS] 2.2.1.2"""
_size = 11
_names = ['fForeColor', 'fBackColor', 'fVariousPropertyBits', 'fCaption', 'fPicturePosition',
'fSize', 'fMousePointer', 'fPicture', 'fAccelerator', 'fTakeFocusOnClick',
'fMouseIcon']
class SpinButtonPropMask(Mask):
"""SpinButtonPropMask: [MS-OFORMS] 2.2.8.2"""
_size = 15
_names = ['fForeColor', 'fBackColor', 'fVariousPropertyBits', 'fSize', 'UnusedBits1',
'fMin', 'fMax', 'fPosition', 'fPrevEnabled', 'fNextEnabled', 'fSmallChange',
'fOrientation', 'fDelay', 'fMouseIcon', 'fMousePointer']
class TabStripPropMask(Mask):
"""TabStripPropMask: [MS-OFORMS] 2.2.9.2"""
_size = 25
_names = ['fListIndex', 'fBackColor', 'fForeColor', 'Unused1', 'fSize', 'fItems',
'fMousePointer', 'Unused2', 'fTabOrientation', 'fTabStyle', 'fMultiRow',
'fTabFixedWidth', 'fTabFixedHeight', 'fTooltips', 'Unused3', 'fTipStrings',
'Unused4', 'fNames', 'fVariousPropertyBits', 'fNewVersion', 'fTabsAllocated',
'fTags', 'fTabData', 'fAccelerator', 'fMouseIcon']
class LabelPropMask(Mask):
"""LabelPropMask: [MS-OFORMS] 2.2.4.2"""
_size = 13
_names = ['fForeColor', 'fBackColor', 'fVariousPropertyBits', 'fCaption',
'fPicturePosition', 'fSize', 'fMousePointer', 'fBorderColor', 'fBorderStyle',
'fSpecialEffect', 'fPicture', 'fAccelerator', 'fMouseIcon']
class ScrollBarPropMask(Mask):
"""ScrollBarPropMask: [MS-OFORMS] 2.2.7.2"""
_size = 17
_names = ['fForeColor', 'fBackColor', 'fVariousPropertyBits', 'fSize', 'fMousePointer',
'fMin', 'fMax', 'fPosition', 'UnusedBits1', 'fPrevEnabled', 'fNextEnabled',
'fSmallChange', 'fLargeChange', 'fOrientation', 'fProportionalThumb',
'fDelay', 'fMouseIcon']
class ExtendedStream(object):
def __init__(self, stream, path):
self._pos = 0
self._jumps = []
self._stream = stream
self._path = path
self._padding = False
self._pad_start = 0
@classmethod
def open(cls, ole_file, path):
stream = ole_file.openstream(path)
return cls(stream, path)
def _read(self, size):
self._pos += size
return self._stream.read(size)
def _pad(self, start, size=4):
offset = (self._pos - start) % size
if offset:
self._read(size - offset)
def read(self, size):
if self._padding:
self._pad(self._pad_start, size)
return self._read(size)
def will_jump_to(self, size):
self._next_jump = ('jump', (self._pos, size))
return self
def will_pad(self):
self._next_jump = ('pad', self._pos)
return self
def padded_struct(self):
self._next_jump = ('padded', (self._padding, self._pad_start))
self._padding = True
self._pad_start = self._pos
return self
def __enter__(self):
assert(self._next_jump)
self._jumps.append(self._next_jump)
self._next_jump = None
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is None:
(jump_type, data) = self._jumps.pop()
if jump_type == 'jump':
(start, size) = data
consummed = self._pos - start
if consummed > size:
self.raise_error('Bad jump: too much read ({0} > {1})'.format(consummed, size))
self.read(size - consummed)
elif jump_type == 'pad':
self._pad(data)
elif jump_type == 'padded':
(prev_padding, prev_pad_start) = data
self._pad(self._pad_start)
self._padding = prev_padding
self._pad_start = prev_pad_start
def unpacks(self, format, size):
return struct.unpack(format, self.read(size))
def unpack(self, format, size):
return self.unpacks(format, size)[0]
def raise_error(self, reason, back=0):
raise OleFormParsingError('{0}:{1}: {2}'.format(self._path, self._pos - back, reason))
def check_values(self, name, format, size, expected):
value = self.unpacks(format, size)
if value != expected:
self.raise_error('Invalid {0}: expected {1} got {2}'.format(name, str(expected), str(value)))
def check_value(self, name, format, size, expected):
self.check_values(name, format, size, (expected,))
def consume_TextProps(stream):
# TextProps: [MS-OFORMS] 2.3.1
stream.check_values('TextProps (versions)', '<BB', 2, (0, 2))
cbTextProps = stream.unpack('<H', 2)
stream.read(cbTextProps)
def consume_GuidAndFont(stream):
# GuidAndFont: [MS-OFORMS] 2.4.7
UUIDS = stream.unpacks('<LHH', 8) + stream.unpacks('>Q', 8)
if UUIDS == (199447043, 36753, 4558, 11376937813817407569):
# UUID == {0BE35203-8F91-11CE-9DE300AA004BB851}
# StdFont: [MS-OFORMS] 2.4.12
stream.check_value('StdFont (version)', '<B', 1, 1)
# Skip sCharset, bFlags, sWeight, ulHeight
stream.read(9)
bFaceLen = stream.unpack('<B', 1)
stream.read(bFaceLen)
elif UUIDS == (2948729120, 55886, 4558, 13349514450607572916):
# UUID == {AFC20920-DA4E-11CE-B94300AA006887B4}
consume_TextProps(stream)
else:
stream.raise_error('Invalid GuidAndFont (UUID)', 16)
def consume_GuidAndPicture(stream):
# GuidAndPicture: [MS-OFORMS] 2.4.8
# UUID == {0BE35204-8F91-11CE-9DE3-00AA004BB851}
stream.check_values('GuidAndPicture (UUID part 1)', '<LHH', 8, (199447044, 36753, 4558))
stream.check_value('GuidAndPicture (UUID part 1)', '>Q', 8, 11376937813817407569)
# StdPicture: [MS-OFORMS] 2.4.13
stream.check_value('StdPicture (Preamble)', '<L', 4, 0x0000746C)
size = stream.unpack('<L', 4)
stream.read(size)
def consume_CountOfBytesWithCompressionFlag(stream):
# CountOfBytesWithCompressionFlag or CountOfCharsWithCompressionFlag: [MS-OFORMS] 2.4.14.2 or 2.4.14.3
count = stream.unpack('<L', 4)
return count & 0x7FFFFFFF
def consume_SiteClassInfo(stream):
# SiteClassInfo: [MS-OFORMS] 2.2.10.10.1
stream.check_value('SiteClassInfo (version)', '<H', 2, 0)
cbClassTable = stream.unpack('<H', 2)
stream.read(cbClassTable)
def consume_FormObjectDepthTypeCount(stream):
# FormObjectDepthTypeCount: [MS-OFORMS] 2.2.10.7
(depth, mixed) = stream.unpacks('<BB', 2)
if mixed & 0x80:
stream.check_value('FormObjectDepthTypeCount (SITE_TYPE)', '<B', 1, 1)
return mixed ^ 0x80
if mixed != 1:
stream.raise_error('Invalid FormObjectDepthTypeCount (SITE_TYPE): expected 1 got {0}'.format(str(mixed)))
return 1
def consume_OleSiteConcreteControl(stream):
# OleSiteConcreteControl: [MS-OFORMS] 2.2.10.12.1
stream.check_value('OleSiteConcreteControl (version)', '<H', 2, 0)
cbSite = stream.unpack('<H', 2)
with stream.will_jump_to(cbSite):
propmask = SitePropMask(stream.unpack('<L', 4))
# SiteDataBlock: [MS-OFORMS] 2.2.10.12.3
with stream.padded_struct():
name_len = tag_len = id = 0
if propmask.fName:
name_len = consume_CountOfBytesWithCompressionFlag(stream)
if propmask.fTag:
tag_len = consume_CountOfBytesWithCompressionFlag(stream)
if propmask.fID:
id = stream.unpack('<L', 4)
propmask.consume(stream, [('fHelpContextID', 4), ('fBitFlags', 4), ('fObjectStreamSize', 4)])
tabindex = ClsidCacheIndex = 0
if propmask.fTabIndex:
tabindex = stream.unpack('<H', 2)
if propmask.fClsidCacheIndex:
ClsidCacheIndex = stream.unpack('<H', 2)
if propmask.fGroupID:
stream.read(2)
# Get the size of the ControlTipText, if needed.
control_tip_text_len = 0
if propmask.fControlTipText:
control_tip_text_len = consume_CountOfBytesWithCompressionFlag(stream)
propmask.consume(stream, [('fRuntimeLicKey', 4), ('fControlSource', 4), ('fRowSource', 4)])
# SiteExtraDataBlock: [MS-OFORMS] 2.2.10.12.4
name = None
if (name_len > 0):
name = stream.read(name_len)
# Consume 2 null bytes between name and tag.
#if ((tag_len > 0) or (control_tip_text_len > 0)):
# stream.read(2)
# # Sometimes it looks like 2 extra null bytes go here whether or not there is a tag.
tag = None
if (tag_len > 0):
tag = stream.read(tag_len)
# Skip SitePosition.
if propmask.fPosition:
stream.read(8)
control_tip_text = stream.read(control_tip_text_len)
if (len(control_tip_text) == 0):
control_tip_text = None
return {'name': name, 'tag': tag, 'id': id, 'tabindex': tabindex,
'ClsidCacheIndex': ClsidCacheIndex, 'value': None, 'caption': None,
'control_tip_text':control_tip_text}
def consume_FormControl(stream):
# FormControl: [MS-OFORMS] 2.2.10.1
stream.check_values('FormControl (versions)', '<BB', 2, (0, 4))
cbform = stream.unpack('<H', 2)
with stream.will_jump_to(cbform):
propmask = FormPropMask(stream.unpack('<L', 4))
# FormDataBlock: [MS-OFORMS] 2.2.10.3
propmask.consume(stream, [('fBackColor', 4), ('fForeColor', 4), ('fNextAvailableID', 4)])
if propmask.fBooleanProperties:
BooleanProperties = stream.unpack('<L', 4)
FORM_FLAG_DONTSAVECLASSTABLE = (BooleanProperties & (1<<15)) >> 15
else:
FORM_FLAG_DONTSAVECLASSTABLE = 0
# Skip the rest of DataBlock and ExtraDataBlock
# FormStreamData: [MS-OFORMS] 2.2.10.5
if propmask.fMouseIcon:
consume_GuidAndPicture(stream)
if propmask.fFont:
consume_GuidAndFont(stream)
if propmask.fPicture:
consume_GuidAndPicture(stream)
# FormSiteData: [MS-OFORMS] 2.2.10.6
if not FORM_FLAG_DONTSAVECLASSTABLE:
CountOfSiteClassInfo = stream.unpack('<H', 2)
for i in range(CountOfSiteClassInfo):
consume_SiteClassInfo(stream)
(CountOfSites, CountOfBytes) = stream.unpacks('<LL', 8)
remaining_SiteDepthsAndTypes = CountOfSites
with stream.will_jump_to(CountOfBytes):
with stream.will_pad():
while remaining_SiteDepthsAndTypes > 0:
remaining_SiteDepthsAndTypes -= consume_FormObjectDepthTypeCount(stream)
for i in range(CountOfSites):
yield consume_OleSiteConcreteControl(stream)
def consume_MorphDataControl(stream):
# MorphDataControl: [MS-OFORMS] 2.2.5.1
stream.check_values('MorphDataControl (versions)', '<BB', 2, (0, 2))
cbMorphData = stream.unpack('<H', 2)
with stream.will_jump_to(cbMorphData):
propmask = MorphDataPropMask(stream.unpack('<Q', 8))
# MorphDataDataBlock: [MS-OFORMS] 2.2.5.3
with stream.padded_struct():
propmask.consume(stream, [('fVariousPropertyBits', 4), ('fBackColor', 4),
('fForeColor', 4), ('fMaxLength', 4),
('fBorderStyle', 1), ('fScrollBars', 1),
('fDisplayStyle', 1), ('fMousePointer', 1),
('fPasswordChar', 2), ('fListWidth', 4),
('fBoundColumn', 2), ('fTextColumn', 2),
('fColumnCount', 2), ('fListRows', 2),
('fcColumnInfo', 2), ('fMatchEntry', 1),
('fListStyle', 1), ('fShowDropButtonWhen', 1),
('fDropButtonStyle', 1), ('fMultiSelect', 1)])
if propmask.fValue:
value_size = consume_CountOfBytesWithCompressionFlag(stream)
else:
value_size = 0
if propmask.fCaption:
caption_size = consume_CountOfBytesWithCompressionFlag(stream)
else:
caption_size = 0
propmask.consume(stream, [('fPicturePosition', 4),
('fBorderColor', 4), ('fSpecialEffect', 4),
('fMouseIcon', 2), ('fPicture', 2),
('fAccelerator', 2)])
if propmask.fGroupName:
group_name_size = consume_CountOfBytesWithCompressionFlag(stream)
else:
group_name_size = 0
# MorphDataExtraDataBlock: [MS-OFORMS] 2.2.5.4
# Discard Size
stream.read(8)
value = stream.read(value_size)
# Read caption text.
caption = ""
if (caption_size > 0):
caption = stream.read(caption_size)
# Read groupname text.
group_name = ""
if (group_name_size > 0):
group_name = stream.read(group_name_size)
# MorphDataStreamData: [MS-OFORMS] 2.2.5.5
if propmask.fMouseIcon:
consume_GuidAndPicture(stream)
if propmask.fPicture:
consume_GuidAndPicture(stream)
consume_TextProps(stream)
return (value, caption, group_name)
def consume_ImageControl(stream):
# ImageControl: [MS-OFORMS] 2.2.3.1
stream.check_values('ImageControl (versions)', '<BB', 2, (0, 2))
cbImage = stream.unpack('<H', 2)
with stream.will_jump_to(cbImage):
propmask = ImagePropMask(stream.unpack('<L', 4))
# Skip the DataBlock and the ExtraDataBlock
# ImageStreamData: [MS-OFORMS] 2.2.3.5
if propmask.fPicture:
consume_GuidAndPicture(stream)
if propmask.fMouseIcon:
consume_GuidAndPicture(stream)
def consume_CommandButtonControl(stream):
# CommandButtonControl: [MS-OFORMS] 2.2.1.1
stream.check_values('CommandButtonControl (versions)', '<BB', 2, (0, 2))
cbCommandButton = stream.unpack('<H', 2)
with stream.will_jump_to(cbCommandButton):
propmask = CommandButtonPropMask(stream.unpack('<L', 4))
# Skip the DataBlock and the ExtraDataBlock
# ImageStreamData: [MS-OFORMS] 2.2.1.5
if propmask.fPicture:
consume_GuidAndPicture(stream)
if propmask.fMouseIcon:
consume_GuidAndPicture(stream)
consume_TextProps(stream)
def consume_SpinButtonControl(stream):
# SpinButtonControl: [MS-OFORMS] 2.2.8.1
stream.check_values('SpinButtonControl (versions)', '<BB', 2, (0, 2))
cbSpinButton = stream.unpack('<H', 2)
with stream.will_jump_to(cbSpinButton):
propmask = SpinButtonPropMask(stream.unpack('<L', 4))
# Skip the DataBlock and the ExtraDataBlock
# SpinButtonStreamData: [MS-OFORMS] 2.2.8.5
if propmask.fMouseIcon:
consume_GuidAndPicture(stream)
def consume_TabStripControl(stream):
# TabStripControl: [MS-OFORMS] 2.2.9.1
stream.check_values('TabStripControl (versions)', '<BB', 2, (0, 2))
cbTabStrip = stream.unpack('<H', 2)
with stream.will_jump_to(cbTabStrip):
propmask = TabStripPropMask(stream.unpack('<L', 4))
# TabStripDataBlock: [MS-OFORMS] 2.2.9.3
propmask.consume(stream, [('fListIndex', 4), ('fBackColor', 4),
('fForeColor', 4), ('fSize', 4),
('fMousePointer', 1), ('fTabOrientation', 4),
('fTabStyle', 4), ('fTabFixedWidth', 4),
('fTabFixedHeight', 4), ('fTipStrings', 4),
('fNames', 4), ('fVariousPropertyBits', 4),
('fTabsAllocated', 4), ('fTags', 4)])
tabData = 0
if propmask['fTabData']:
tabData = stream.unpack('<L', 4)
# Skip the ExtraDataBlock
# TabStripStreamData: [MS-OFORMS] 2.2.9.5
if propmask.fMouseIcon:
consume_GuidAndPicture(stream)
# TextProps
consume_TextProps(stream)
# TabStripTabFlagData: [MS-OFORMS] 2.2.9.6
for i in range(tabData):
stream.read(4)
def consume_LabelControl(stream):
# LabelControl: [MS-OFORMS] 2.2.4.1
stream.check_values('LabelControl (versions)', '<BB', 2, (0, 2))
cbLabel = stream.unpack('<H', 2)
with stream.will_jump_to(cbLabel):
propmask = LabelPropMask(stream.unpack('<L', 4))
# LabelDataBlock: [MS-OFORMS] 2.2.4.3
with stream.padded_struct():
propmask.consume(stream, [('fForeColor', 4), ('fBackColor', 4),
('fVariousPropertyBits', 4)])
if propmask.fCaption:
caption_size = consume_CountOfBytesWithCompressionFlag(stream)
else:
caption_size = 0
propmask.consume(stream, [('fPicturePosition', 4), ('fMousePointer', 1),
('fBorderColor', 4), ('fBorderStyle', 2),
('fSpecialEffect', 2), ('fPicture', 2),
('fAccelerator', 2), ('fMouseIcon', 2)])
# LabelExtraDataBlock: [MS-OFORMS] 2.2.4.4
caption = stream.read(caption_size)
stream.read(8)
# LabelStreamData: [MS-OFORMS] 2.2.4.5
if propmask.fPicture:
consume_GuidAndPicture(stream)
if propmask.fMouseIcon:
consume_GuidAndPicture(stream)
# TextProps
consume_TextProps(stream)
return caption
def consume_ScrollBarControl(stream):
# ScrollBarControl: [MS-OFORMS] 2.2.7.1
stream.check_values('LabelControl (versions)', '<BB', 2, (0, 2))
cbScrollBar = stream.unpack('<H', 2)
with stream.will_jump_to(cbScrollBar):
propmask = ScrollBarPropMask(stream.unpack('<L', 4))
# Skip the DataBlock and the ExtraDataBlock
# ScrollBarStreamData: [MS-OFORMS] 2.2.7.5
if propmask.fMouseIcon:
consume_GuidAndPicture(stream)
def extract_OleFormVariables(ole_file, stream_dir):
control = ExtendedStream.open(ole_file, '/'.join(stream_dir + ['f']))
variables = list(consume_FormControl(control))
data = ExtendedStream.open(ole_file, '/'.join(stream_dir + ['o']))
for var in variables:
# See FormEmbeddedActiveXControlCached for type definition: [MS-OFORMS] 2.4.5
if var['ClsidCacheIndex'] == 7:
consume_FormControl(data)
elif var['ClsidCacheIndex'] == 12:
consume_ImageControl(data)
elif var['ClsidCacheIndex'] == 14:
consume_FormControl(data)
elif var['ClsidCacheIndex'] in [15, 23, 24, 25, 26, 27, 28]:
var['value'], var['caption'], var['group_name'] = consume_MorphDataControl(data)
elif var['ClsidCacheIndex'] == 16:
consume_SpinButtonControl(data)
elif var['ClsidCacheIndex'] == 17:
consume_CommandButtonControl(data)
elif var['ClsidCacheIndex'] == 18:
consume_TabStripControl(data)
elif var['ClsidCacheIndex'] == 21:
var['caption'] = consume_LabelControl(data)
elif var['ClsidCacheIndex'] == 47:
consume_ScrollBarControl(data)
elif var['ClsidCacheIndex'] == 57:
consume_FormControl(data)
else:
# TODO: use logging instead of print
print('ERROR: Unsupported stored type in user form: {0}'.format(str(var['ClsidCacheIndex'])))
break
return variables