misc/test-files.sh | 16 + src/xlsparser.py | 150 ++++++++++++---- src/xlsrecord.py | 489 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/xlsstream.py | 39 ++-- 4 files changed, 642 insertions(+), 52 deletions(-)
New commits: commit d433336619d71185141c47cbe6c77c7103c812cf Merge: 06a8b38... 0263e78... Author: Kohei Yoshida <[email protected]> Date: Tue Oct 25 17:10:04 2011 -0400 Merge branch 'tilarids-master' commit 0263e7846fdd7aac6180f9f385070edf32f96c64 Author: Sergey Kishchenko <[email protected]> Date: Tue Sep 27 15:32:59 2011 +0300 Charts dump was improved (several new records, etc) diff --git a/src/xlsparser.py b/src/xlsparser.py index c4b0f1c..322e9b2 100644 --- a/src/xlsparser.py +++ b/src/xlsparser.py @@ -67,7 +67,7 @@ class BaseParser(object): return Seq(self, other) def safeParse(parser, stream): - print "TRACE:[%s,%s]" % (str(parser), str(stream.tokens[stream.currentIndex])) + #print "TRACE:[%s,%s]" % (str(parser), str(stream.tokens[stream.currentIndex])) parsed = None try: @@ -178,7 +178,7 @@ class OneOf(BaseParser): def parse(self, stream): for parser in self.__parsers: - parsed = safeParse(parser, stream) + parsed = getParsedOrNone(parser, stream) if not parsed is None: return parsed raise ParseException("No suitable options: [%s]" % ','.join(str(x) for x in self.__parsers)) @@ -374,7 +374,8 @@ class Chart3DBarShape(BaseParser): class PieFormat(BaseParser): PARSER = Term(xlsrecord.PieFormat) -class SerFmt(BaseParser): pass +class SerFmt(BaseParser): + PARSER = Term(xlsrecord.SerFmt) class MarkerFormat(BaseParser): PARSER = Term(xlsrecord.MarkerFormat) @@ -389,17 +390,25 @@ class FontX(BaseParser): PARSER = Term(xlsrecord.FontX) class AlRuns(BaseParser): pass -class ObjectLink(BaseParser): pass -class DataLabExtContents(BaseParser): pass + +class ObjectLink(BaseParser): + PARSER = Term(xlsrecord.ObjectLink) + +class DataLabExtContents(BaseParser): + PARSER = Term(xlsrecord.DataLabExtContents) + class CrtLayout12(BaseParser): pass class CRTMLFRT(BaseParser): pass class TEXTPROPS(BaseParser): pass +class AttachedLabel(BaseParser): + PARSER = Term(xlsrecord.AttachedLabel) + class ATTACHEDLABEL(BaseParser): #ATTACHEDLABEL = Text Begin Pos [FontX] [AlRuns] AI [FRAME] [ObjectLink] [DataLabExtContents] #[CrtLayout12] [TEXTPROPS] [CRTMLFRT] End - PARSER = Group('attached-label', Req(Text()) << Req(Begin()) << Req(Pos()) << FontX() << AlRuns() << Req(AI()) << + PARSER = Group('attached-label-struct', Req(Text()) << Req(Begin()) << Req(Pos()) << FontX() << AlRuns() << Req(AI()) << Opt(FRAME()) << ObjectLink() << DataLabExtContents() << CrtLayout12() << TEXTPROPS() << CRTMLFRT() << Req(End())) class SS(BaseParser): @@ -407,10 +416,16 @@ class SS(BaseParser): #[GELFRAME] [MarkerFormat] [AttachedLabel] *2SHAPEPROPS [CRTMLFRT] End PARSER = Group('ss', Seq(Req(DataFormat()), Req(Begin()), Chart3DBarShape(), Opt(Seq(Req(LineFormat()), Req(AreaFormat()), Req(PieFormat()))), - SerFmt(), Opt(GELFRAME()), MarkerFormat(), Opt(ATTACHEDLABEL()), # ATTACHEDLABEL was used instead of AttachedLabel + SerFmt(), Opt(GELFRAME()), MarkerFormat(), AttachedLabel(), Many('shape-props-list', SHAPEPROPS(), max=2), CRTMLFRT(), Req(End()))) +class StartBlock(BaseParser): + PARSER = Term(xlsrecord.StartBlock) + +class EndBlock(BaseParser): + PARSER = Term(xlsrecord.EndBlock) + class SERIESFORMAT(BaseParser): #SERIESFORMAT = Series Begin 4AI *SS (SerToCrt / (SerParent (SerAuxTrend / SerAuxErrBar))) #*(LegendException [Begin ATTACHEDLABEL [TEXTPROPS] End]) End @@ -419,7 +434,7 @@ class SERIESFORMAT(BaseParser): Many('legend-exceptions', Group('legend-exception-root', Seq(Req(LegendException()), Seq(Req(Begin()), Req(ATTACHEDLABEL()), TEXTPROPS(), Req(End()))))) << - Req(End())) + EndBlock() << Req(End())) @@ -472,11 +487,6 @@ class ContinueFrt12(BaseParser): pass class ChartFrtInfo(BaseParser): PARSER = Term(xlsrecord.ChartFrtInfo) -class StartBlock(BaseParser): - PARSER = Term(xlsrecord.StartBlock) - -class EndBlock(BaseParser): - PARSER = Term(xlsrecord.EndBlock) class AXS(BaseParser): # AXS = [IFmtRecord] [Tick] [FontX] *4(AxisLine LineFormat) [AreaFormat] [GELFRAME] @@ -524,7 +534,9 @@ class AXES(BaseParser): class ChartFormat(BaseParser): PARSER = Term(xlsrecord.ChartFormat) -class BobPop(BaseParser): pass +class BobPop(BaseParser): + PARSER = Term(xlsrecord.BobPop) + class BobPopCustom(BaseParser): pass class Bar(BaseParser): @@ -533,18 +545,26 @@ class Bar(BaseParser): class Line(BaseParser): PARSER = Term(xlsrecord.CHLine) -class Pie(BaseParser): +class Pie(BaseParser): PARSER = Term(xlsrecord.CHPie) -class Area(BaseParser): pass -class Scatter(BaseParser): pass +class Area(BaseParser): + PARSER = Term(xlsrecord.CHArea) + +class Scatter(BaseParser): + PARSER = Term(xlsrecord.CHScatter) + class Radar(BaseParser): PARSER = Term(xlsrecord.CHRadar) class RadarArea(BaseParser): pass -class Surf(BaseParser): pass -class SeriesList(BaseParser): pass +class Surf(BaseParser): + PARSER = Term(xlsrecord.CHSurf) + +class SeriesList(BaseParser): + PARSER = Term(xlsrecord.SeriesList) + class Chart3d(BaseParser): PARSER = Term(xlsrecord.Chart3d) @@ -569,20 +589,32 @@ class CrtLine(BaseParser): PARSER = Term(xlsrecord.CrtLine) class CrtLayout12A(BaseParser): pass -class DAT(BaseParser): pass + +class Dat(BaseParser): + PARSER = Term(xlsrecord.Dat) + +class DAT(BaseParser): + #DAT = Dat Begin LD End + PARSER = Group('dat-root', Req(Dat()) << Req(Begin()) << Req(LD()) << Req(End())) + class CRT(BaseParser): # It seems 2DROPBAR should be considered *2DROPBAR #CRT = ChartFormat Begin (Bar / Line / (BopPop [BopPopCustom]) / Pie / Area / Scatter / Radar / #RadarArea / Surf) CrtLink [SeriesList] [Chart3d] [LD] [*2DROPBAR] *4(CrtLine LineFormat) #*2DFTTEXT [DataLabExtContents] [SS] *4SHAPEPROPS End + # It seems there are optional StartBlock and EndBlock on the last line: + #*2DFTTEXT [StartBlock] [DataLabExtContents] [SS] *4SHAPEPROPS [EndBlock] End + + PARSER = Group('crt', Req(ChartFormat()) << Req(Begin()) << OneOf(Bar(), Line(), Opt(Seq(Req(BobPop()), BobPopCustom())), Pie(), Area(), Scatter(), Radar(), RadarArea(), Surf()) << Req(CrtLink()) << SeriesList() << Chart3d() << Opt(LD()) << Many('drop-bars', DROPBAR(), max=2) << Many('crt-lines', Seq(Req(CrtLine()), Req(LineFormat()))) << Many('dft-texts', DFTTEXT()) << - DataLabExtContents() << Opt(SS()) << Many('shape-props-list', SHAPEPROPS(), max=4) << Req(End())) + StartBlock() << DataLabExtContents() << Opt(SS()) << + Many('shape-props-list', SHAPEPROPS(), max=4) << EndBlock() << Req(End())) class AXISPARENT(BaseParser): # Original: diff --git a/src/xlsrecord.py b/src/xlsrecord.py index 880e3db..c846d5e 100644 --- a/src/xlsrecord.py +++ b/src/xlsrecord.py @@ -124,6 +124,15 @@ def dumpCfrtid(cfrtid): 'end': cfrtid.end} +class FrtHeader(object): + def __init__ (self, rt, flags): + self.rt = rt + self.flags = flags + +def dumpFrtHeader(header): + return {'rt': header.rt, + 'flags': header.flags} + class BaseRecordHandler(globals.ByteStream): def __init__ (self, header, size, bytes, strmData): @@ -235,6 +244,9 @@ Like parseBytes(), the derived classes must overwrite this method.""" def readCFRTID (self): return CFRTID(self.readUnsignedInt(2),self.readUnsignedInt(2)) + def readFrtHeader (self): + return FrtHeader(self.readUnsignedInt(2), self.readUnsignedInt(2)) + class AutofilterInfo(BaseRecordHandler): def __parseBytes (self): @@ -3611,6 +3623,25 @@ class DataFormat(BaseRecordHandler): 'yi': self.yi, 'iss': self.iss}) +class SerFmt(BaseRecordHandler): + def __parseBytes(self): + flags = self.readUnsignedInt(2) + self.smoothedLine = (flags & 0x001) != 0 + self.bubbles3D = (flags & 0x002) != 0 + self.arShadow = (flags & 0x004) != 0 + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Smoothed line: %s" % self.getTrueFalse(self.smoothedLine)) + self.appendLine("3D bubbles: %s" % self.getTrueFalse(self.bubbles3D)) + self.appendLine("With shadow: %s" % self.getTrueFalse(self.arShadow)) + + def dumpData(self): + self.__parseBytes() + return ('ser-fmt', {'smoothed-line': self.smoothedLine, + 'bubbles-3d': self.bubbles3D, + 'ar-shadow': self.arShadow}) + class ChartFormat(BaseRecordHandler): def __parseBytes(self): reserved1 = self.readUnsignedInt(4) @@ -3630,6 +3661,37 @@ class ChartFormat(BaseRecordHandler): return ('chart-format', {'varied': self.varied, 'icrt': self.icrt}) +class DataLabExtContents(BaseRecordHandler): + def __parseBytes(self): + self.header = self.readFrtHeader() + flags = self.readUnsignedInt(2) + self.serName = (flags & 0x001) != 0 # A + self.catName = (flags & 0x002) != 0 # B + self.value = (flags & 0x004) != 0 # C + self.percent = (flags & 0x008) != 0 # D + self.bubSizes = (flags & 0x010) != 0 # E + self.sep = self.readUnicodeString() + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Header: %s, %s" % (str(self.header.rt), str(self.header.flags))) + self.appendLine("Display series name: %s" % self.getTrueFalse(self.serName)) + self.appendLine("Display categories name: %s" % self.getTrueFalse(self.catName)) + self.appendLine("Display value: %s" % self.getTrueFalse(self.value)) + self.appendLine("Is a percent: %s" % self.getTrueFalse(self.percent)) + self.appendLine("Display bubble size: %s" % self.getTrueFalse(self.bubSizes)) + self.appendLine("Separator: %s" % str(self.sep)) + + def dumpData(self): + self.__parseBytes() + return ('datalab-ext-contents', {'ser-name': self.serName, + 'cat-name': self.catName, + 'value': self.value, + 'percent': self.percent, + 'bub-sizes': self.bubSizes, + 'sep': self.sep}, + [('header', dumpFrtHeader(self.header))]) + class ChartFrtInfo(BaseRecordHandler): def __parseBytes(self): self.headerOld = self.readUnsignedInt(4) @@ -3744,7 +3806,6 @@ class DropBar(BaseRecordHandler): def parseBytes (self): self.__parseBytes() self.appendLine('Gap: %s' % str(self.gap)) - # TODO: dump all data def dumpData(self): self.__parseBytes() @@ -3757,12 +3818,58 @@ class CrtLine(BaseRecordHandler): def parseBytes (self): self.__parseBytes() self.appendLine('ID: %s' % str(self.id)) - # TODO: dump all data def dumpData(self): self.__parseBytes() return ('crt-line', {'id': self.id}) +class ObjectLink(BaseRecordHandler): + def __parseBytes(self): + self.linkObj = self.readUnsignedInt(2) + self.linkVar1 = self.readUnsignedInt(2) + self.linkVar2 = self.readUnsignedInt(2) + + def parseBytes (self): + self.__parseBytes() + self.appendLine('Link object: %s' % str(self.linkObj)) + self.appendLine('Link var1: %s' % str(self.linkVar1)) + self.appendLine('Link var2: %s' % str(self.linkVar2)) + + def dumpData(self): + self.__parseBytes() + return ('object-link', {'link-obj': self.linkObj, + 'link-var1': self.linkVar1, + 'link-var2': self.linkVar2}) + +class AttachedLabel(BaseRecordHandler): + def __parseBytes(self): + flag = self.readUnsignedInt(2) + self.showValue = (flag & 0x0001) != 0 # A + self.showPercent = (flag & 0x0002) != 0 # B + self.showLabelAndPerc = (flag & 0x0004) != 0 # C + unused = (flag & 0x0008) != 0 # D + self.showLabel = (flag & 0x0010) != 0 # E + self.showBubbleSizes = (flag & 0x0020) != 0 # F + self.showSeriesName = (flag & 0x0040) != 0 # G + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Show value: %s" % self.getTrueFalse(self.showValue)) + self.appendLine("Show percent: %s" % self.getTrueFalse(self.showPercent)) + self.appendLine("Show label and percent: %s" % self.getTrueFalse(self.showLabelAndPerc)) + self.appendLine("Show bubble sizes: %s" % self.getTrueFalse(self.showBubbleSizes)) + self.appendLine("Show series name: %s" % self.getTrueFalse(self.showSeriesName)) + # TODO: dump all data + + def dumpData(self): + self.__parseBytes() + return ('attached-label', {'show-value': self.showValue, + 'show-percent': self.showPercent, + 'show-label-and-perc': self.showLabelAndPerc, + 'show-label': self.showLabel, + 'show-bubble-sizes': self.showBubbleSizes, + 'show-series-name': self.showSeriesName}) + class Chart3d(BaseRecordHandler): def __parseBytes(self): self.rot = self.readSignedInt(2) @@ -3853,6 +3960,74 @@ class AxisParent(BaseRecordHandler): self.__parseBytes() return ('axis-parent', {'iax': self.iax}) +class BobPop(BaseRecordHandler): + def __parseBytes(self): + self.pst = self.readUnsignedInt(1) + self.autoSplit = self.readUnsignedInt(1) + self.split = self.readUnsignedInt(2) + self.splitPos = self.readSignedInt(2) + self.splitPercent = self.readSignedInt(2) + self.pie2Size = self.readSignedInt(2) + self.gap = self.readSignedInt(1) + self.splitValue = self.readDouble() + + flag = self.readUnsignedInt(2) + self.hasShadow = (flag & 0x0001) != 0 # A + + def dumpData(self): + self.__parseBytes() + return ('bobpop', {'pst': self.pst, + 'auto-split': self.autoSplit, + 'split': self.split, + 'split-pos': self.splitPos, + 'split-percent': self.splitPercent, + 'pie-2-size': self.pie2Size, + 'gap': self.gap, + 'split-balue': self.splitValue, + 'has-shadow': self.hasShadow}) + + def parseBytes (self): + self.__parseBytes() + + self.appendLine("Chart group type: %s" % str(self.pst)) + self.appendLine("Auto split: %s" % self.getTrueFalse(self.autoSplit)) + self.appendLine("Split type: %s" % str(self.split)) + if self.split == 0x0: # pos + self.appendLine("Split pos: %s" % str(self.splitPos)) + elif self.split == 0x1: # value + self.appendLine("Split value: %s" % str(self.splitValue)) + elif self.split == 0x2: # percent + self.appendLine("Split percent: %s" % str(self.splitPercent)) + else: + self.appendLine("Custom split is specified in BopPopCustom record that follows") + + self.appendLine("Size of a secondary pie/bar: %s" % str(self.pie2Size)) + self.appendLine("Gap: %s" % str(self.gap)) + self.appendLine("Has shadow: %s" % self.getTrueFalse(self.hasShadow)) + +class Dat(BaseRecordHandler): + def __parseBytes(self): + flag = self.readUnsignedInt(2) + self.hasBordHorz = (flag & 0x0001) != 0 # A + self.hasBordVert = (flag & 0x0002) != 0 # B + self.hasBordOutline = (flag & 0x0004) != 0 # C + self.showSeriesKey = (flag & 0x0008) != 0 # D + + def dumpData(self): + self.__parseBytes() + return ('dat', {'has-bord-horz': self.hasBordHorz, + 'has-bord-vert': self.hasBordVert, + 'has-bord-outline': self.hasBordOutline, + 'show-series-key': self.showSeriesKey}) + + def parseBytes (self): + self.__parseBytes() + + self.appendLine("Has horizontal borders: %s" % self.getTrueFalse(self.hasBordHorz)) + self.appendLine("Has vertical borders: %s" % self.getTrueFalse(self.hasBordVert)) + self.appendLine("Has outline borders: %s" % self.getTrueFalse(self.hasBordOutline)) + self.appendLine("Show series key: %s" % self.getTrueFalse(self.showSeriesKey)) + class AxcExt(BaseRecordHandler): def __parseBytes (self): self.catMin = self.readUnsignedInt(2) @@ -3928,6 +4103,26 @@ class Tick(BaseRecordHandler): [('rgb', dumpRgb(self.rgb)), ('icv', dumpIcv(self.icv))]) +class SeriesList(BaseRecordHandler): + def __parseBytes(self): + self.cser = self.readUnsignedInt(2) + self.series = [] + for x in xrange(self.cser): + self.series.append(self.readUnsignedInt(2)) + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Series count : %s" % str(self.cser)) + for x in self.series: + self.appendLine("Series id : %s" % str(x)) + + def dumpData(self): + self.__parseBytes() + return ('series-list', + {'cser': self.cser}, + map(lambda x: ('series-index',x), + self.series)) + class AxisLine(BaseRecordHandler): def __parseBytes(self): self.id = self.readUnsignedInt(2) @@ -4353,8 +4548,68 @@ class CHRadar(BaseRecordHandler): self.__parseBytes() return ('radar', {'rdr-ax-lab': self.rdrAxLab, 'has-shadow': self.hasShadow}) + +class CHSurf(BaseRecordHandler): + def __parseBytes (self): + flags = self.readUnsignedInt(2) + self.fillSurface = (flags & 0x0001) != 0 # A + self.phongShade3D = (flags & 0x0002) != 0 # B + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Surface has a fill: %s"%self.getYesNo(self.fillSurface)) + self.appendLine("3D Phong shading: %s"%self.getYesNo(self.phongShade3D)) + + def dumpData(self): + self.__parseBytes() + return ('surf', {'fill-surface': self.fillSurface, + 'phong-shade-3d': self.phongShade3D}) +class CHArea(BaseRecordHandler): + def __parseBytes (self): + flags = self.readUnsignedInt(2) + self.stacked = (flags & 0x0001) != 0 # A + self.f100 = (flags & 0x0002) != 0 # B + self.hasShadow = (flags & 0x0002) != 0 # B + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Is stacked: %s"%self.getYesNo(self.stacked)) + self.appendLine("Data points are percentage of sum: %s"%self.getYesNo(self.f100)) + self.appendLine("Has shadow: %s"%self.getYesNo(self.hasShadow)) + + def dumpData(self): + self.__parseBytes() + return ('surf', {'stacked': self.stacked, + 'f100': self.f100, + 'has-shadow': self.hasShadow}) + +class CHScatter(BaseRecordHandler): + def __parseBytes (self): + self.bubbleSizeRatio = self.readUnsignedInt(2) + self.bubbleSize = self.readUnsignedInt(2) + flags = self.readUnsignedInt(2) + self.bubbles = (flags & 0x0001) != 0 # A + self.showNegBubbles = (flags & 0x0002) != 0 # B + self.hasShadow = (flags & 0x0004) != 0 # C + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Bubble size ratio: %s" % str(self.bubbleSizeRatio)) + self.appendLine("Bubble size: %s" % str(self.bubbleSize)) + self.appendLine("Is a bubble chart group: %s"%self.getYesNo(self.bubbles)) + self.appendLine("Show negative bubbles: %s"%self.getYesNo(self.showNegBubbles)) + self.appendLine("Data points have shadow: %s"%self.getYesNo(self.hasShadow)) + + def dumpData(self): + self.__parseBytes() + return ('pie', {'bubble-size-ratio': self.bubbleSizeRatio, + 'bubble-size': self.bubbleSize, + 'bubble': self.bubbles, + 'show-neg-bubbles': self.showNegBubbles, + 'has-shadow': self.hasShadow}) + class CHPie(BaseRecordHandler): def __parseBytes (self): self.start = self.readUnsignedInt(2) diff --git a/src/xlsstream.py b/src/xlsstream.py index 68bb59c..944346b 100644 --- a/src/xlsstream.py +++ b/src/xlsstream.py @@ -233,6 +233,7 @@ recData = { 0x0864: ["SXADDL", "Pivot Table Additional Info", xlsrecord.SXAddlInfo], 0x0867: ["FEATHEADR", "Shared Feature Header", xlsrecord.FeatureHeader], 0x0868: ["RANGEPROTECTION", "Protected Range on Protected Sheet"], + 0x086B: ["DATALABEXTCONTENTS", "Contents of an extended data label", xlsrecord.DataLabExtContents], 0x087D: ["XFEXT", "XF Extension"], 0x088C: ["COMPAT12", "Compatibility Checker 12"], 0x088E: ["TABLESTYLES", "Table Styles"], @@ -250,15 +251,16 @@ recData = { 0x1009: ["CHMARKERFORMAT", "Color, size, and shape of the markers", xlsrecord.MarkerFormat], 0x100A: ["AREAFORMAT", "Patterns and Colors in Filled Region of Chart", xlsrecord.AreaFormat], 0x100B: ["CHPIEFORMAT", "Distance of a data point from the center", xlsrecord.PieFormat], - 0x100C: ["CHATTACHEDLABEL", "?"], + 0x100C: ["CHATTACHEDLABEL", "Properties of a data label", xlsrecord.AttachedLabel], 0x100D: ["SERIESTEXT", "Series Category Name or Title Text in Chart", xlsrecord.SeriesText], 0x1014: ["CHTYPEGROUP", "Properties of a chart group", xlsrecord.ChartFormat], 0x1015: ["LEGEND", "Legend Properties", xlsrecord.Legend], - 0x1017: ["CHBAR, CHCOLUMN", "?", xlsrecord.CHBar], - 0x1018: ["CHLINE", "?", xlsrecord.CHLine], + 0x1016: ["SERIESLIST", "Series of the chart", xlsrecord.SeriesList], + 0x1017: ["CHBAR, CHCOLUMN", "Bar chart group", xlsrecord.CHBar], + 0x1018: ["CHLINE", "Line chart group", xlsrecord.CHLine], 0x1019: ["CHPIE", "Pie/Doughnut chart group", xlsrecord.CHPie], - 0x101A: ["CHAREA", "?"], - 0x101B: ["CHSCATTER", "?"], + 0x101A: ["CHAREA", "Area chart group", xlsrecord.CHArea], + 0x101B: ["CHSCATTER", "Scatter/Bubble chart group", xlsrecord.CHScatter], 0x101C: ["CHCHARTLINE", "Specifies the presence of lines", xlsrecord.CrtLine], 0x101D: ["CHAXIS", "Chart Axis", xlsrecord.CHAxis], 0x101E: ["CHTICK", "Attributes of the axis labels and tick marks", xlsrecord.Tick], @@ -269,7 +271,7 @@ recData = { 0x1024: ["DEFAULTTEXT", "Default Text", xlsrecord.DefaultText], 0x1025: ["TEXT", "Label Properties", xlsrecord.Text], 0x1026: ["CHFONT", "Font for a given text element", xlsrecord.FontX], - 0x1027: ["CHOBJECTLINK", "?"], + 0x1027: ["CHOBJECTLINK", "Object on a chart that is linked to a text", xlsrecord.ObjectLink], 0x1032: ["FRAME", "Type, Size and Position of the Frame around A Chart", xlsrecord.Frame], 0x1033: ["BEGIN", "Start of Chart Sheet Substream", xlsrecord.Begin], 0x1034: ["END", "End of Chart Sheet Substream", xlsrecord.End], @@ -278,7 +280,7 @@ recData = { 0x103C: ["CHPICFORMAT", "?"], 0x103D: ["CHDROPBAR", "Attributes of the up/down bars between multiple series", xlsrecord.DropBar], 0x103E: ["CHRADARLINE", "Radar chart group", xlsrecord.CHRadar], - 0x103F: ["CHSURFACE", "?"], + 0x103F: ["CHSURFACE", "Surface chart group", xlsrecord.CHRadar], 0x1040: ["CHRADARAREA", "?"], 0x1041: ["CHAXESSET", "Properties of an axis group", xlsrecord.AxisParent], 0x1044: ["CHPROPERTIES", "Properties of a chart(2.4.261)", xlsrecord.CHProperties], @@ -292,11 +294,12 @@ recData = { 0x1050: ["CHFORMATRUNS", "?"], 0x1051: ["BRAI", "Data Source of A Chart", xlsrecord.Brai], 0x105B: ["CHSERERRORBAR", "?"], - 0x105D: ["CHSERIESFORMAT", "?"], + 0x105D: ["CHSERIESFORMAT", "Series properties", xlsrecord.SerFmt], 0x105F: ["CH3DDATAFORMAT", "Shape of the data points(2.4.47)", xlsrecord.Chart3DBarShape], 0x1060: ["FBI", "Font Information for Chart", xlsrecord.Fbi], - 0x1061: ["CHPIEEXT", "?"], + 0x1061: ["CHPIEEXT", "Pie/bar of pie chart group", xlsrecord.BobPop], 0x1062: ["AXCEXT", "Additional extension properties of a date axis(2.4.9)", xlsrecord.AxcExt], + 0x1063: ["DAT", "Options of the data table(2.4.73)", xlsrecord.Dat], 0x1064: ["PLOTGROWTH", "Font Scaling Information in the Plot Area", xlsrecord.PlotGrowth], 0x1065: ["CHSIINDEX*", "Part of a group of records which specify the data of a chart", xlsrecord.SIIndex], 0x1066: ["CHESCHERFORMAT", "Properties of a fill pattern", xlsrecord.GelFrame] commit 273385b124263f1b3142335b671fec2d0bfe5b3d Author: Sergey Kishchenko <[email protected]> Date: Thu Sep 22 21:02:35 2011 +0300 Several records were added; GelFrame is not parsed as a MsoDrawing for now :( diff --git a/src/xlsparser.py b/src/xlsparser.py index 1e04ce2..c4b0f1c 100644 --- a/src/xlsparser.py +++ b/src/xlsparser.py @@ -243,8 +243,10 @@ class LeftMargin(MarginBaseParser): pass class RightMargin(MarginBaseParser): pass class TopMargin(MarginBaseParser): pass class BottomMargin(MarginBaseParser): pass -class Pls(MarginBaseParser): pass -class Continue(MarginBaseParser): pass +class Pls(BaseParser): + PARSER = Term(xlsrecord.Pls) + +class Continue(BaseParser): pass class Setup(BaseParser): PARSER = Term(xlsrecord.Setup) @@ -319,13 +321,22 @@ class LineFormat(BaseParser): class AreaFormat(BaseParser): PARSER = Term(xlsrecord.AreaFormat) + +class PICF(BaseParser): pass # PICF = Begin PicF End + +class GelFrame(BaseParser): + PARSER = Term(xlsrecord.GelFrame) + +class GELFRAME(BaseParser): + #GELFRAME = 1*2GelFrame *Continue [PICF] + PARSER = Group('gel-frame-root', Many('gel-frame-list', GelFrame(), min=1, max=2) << + Many('continue-list', Continue()) << Opt(PICF())) -class GELFRAME(BaseParser): pass class SHAPEPROPS(BaseParser): pass class FRAME(BaseParser): PARSER = Group('frame', Req(Frame()) << Req(Begin()) << Req(LineFormat()) << Req(AreaFormat()) << - GELFRAME() << SHAPEPROPS() << Req(End())) + Opt(GELFRAME()) << Opt(SHAPEPROPS()) << Req(End())) class Scl(BaseParser): @@ -396,7 +407,7 @@ class SS(BaseParser): #[GELFRAME] [MarkerFormat] [AttachedLabel] *2SHAPEPROPS [CRTMLFRT] End PARSER = Group('ss', Seq(Req(DataFormat()), Req(Begin()), Chart3DBarShape(), Opt(Seq(Req(LineFormat()), Req(AreaFormat()), Req(PieFormat()))), - SerFmt(), GELFRAME(), MarkerFormat(), Opt(ATTACHEDLABEL()), # ATTACHEDLABEL was used instead of AttachedLabel + SerFmt(), Opt(GELFRAME()), MarkerFormat(), Opt(ATTACHEDLABEL()), # ATTACHEDLABEL was used instead of AttachedLabel Many('shape-props-list', SHAPEPROPS(), max=2), CRTMLFRT(), Req(End()))) @@ -472,19 +483,19 @@ class AXS(BaseParser): # *4SHAPEPROPS [TextPropsStream *ContinueFrt12] PARSER = Group('axs', IFmtRecord() << Tick() << FontX() << Many('axis-lines', Seq(Req(AxisLine()), Req(LineFormat())), max=4) << - AreaFormat() << GELFRAME() << Many('shape-props-list', SHAPEPROPS(), max=4) << + AreaFormat() << Opt(GELFRAME()) << Many('shape-props-list', SHAPEPROPS(), max=4) << Opt(Seq(Req(TextPropsStream()), Many('continue-frt12-list', ContinueFrt12())))) class IVAXIS(BaseParser): # original ABNF: # IVAXIS = Axis Begin [CatSerRange] AxcExt [CatLab] AXS [CRTMLFRT] End # it seems it's usual too have several future records indicators just after AxcExt and before the End: - # IVAXIS = Axis Begin [CatSerRange] AxcExt [ChartFrtInfo *StartBlock] [CatLab] AXS [CRTMLFRT] [EndBlock] End + # IVAXIS = Axis Begin [CatSerRange] AxcExt ([ChartFrtInfo] *StartBlock) [CatLab] AXS [CRTMLFRT] [EndBlock] End PARSER = Group('ivaxis', Req(Axis()) << Req(Begin()) << CatSerRange() << Req(AxcExt()) << - Group('future', Opt(Seq(Req(ChartFrtInfo()), - Many('start-blocks', StartBlock())))) << CatLab() << - Req(AXS()) << CRTMLFRT() << EndBlock() << Req(End())) + Group('future', Seq(ChartFrtInfo(), + Many('start-blocks', StartBlock()))) << CatLab() << + Req(AXS()) << Opt(CRTMLFRT()) << EndBlock() << Req(End())) class ValueRange(BaseParser): PARSER = Term(xlsrecord.CHValueRange) @@ -496,12 +507,15 @@ class DVAXIS(BaseParser): PARSER = Group('dvaxis', Req(Axis()) << Req(Begin()) << ValueRange() << AXM() << Req(AXS()) << CRTMLFRT() << Req(End())) -class SERIESAXIS(BaseParser): pass #SERIESAXIS = Axis Begin [CatSerRange] AXS [CRTMLFRT] End +class SERIESAXIS(BaseParser): + #SERIESAXIS = Axis Begin [CatSerRange] AXS [CRTMLFRT] End + PARSER = Group('series-axis', Req(Axis()) << Req(Begin()) << CatSerRange() << + Req(AXS()) << Opt(CRTMLFRT()) << Req(End())) class AXES(BaseParser): #AXES = [IVAXIS DVAXIS [SERIESAXIS] / DVAXIS DVAXIS] *3ATTACHEDLABEL [PlotArea FRAME] # TODO: recheck it. The rule above leaks some brackets :( - PARSER = Group('axes', Seq(OneOf(Seq(Req(IVAXIS()), Req(DVAXIS()), SERIESAXIS()), + PARSER = Group('axes', Seq(OneOf(Seq(Req(IVAXIS()), Req(DVAXIS()), Opt(SERIESAXIS())), Seq(Req(DVAXIS()), Req(DVAXIS()))), Many('attached-labels', ATTACHEDLABEL(), max=3), Opt(Seq(Req(PlotArea()), Req(FRAME()))))) @@ -519,10 +533,14 @@ class Bar(BaseParser): class Line(BaseParser): PARSER = Term(xlsrecord.CHLine) -class Pie(BaseParser): pass +class Pie(BaseParser): + PARSER = Term(xlsrecord.CHPie) + class Area(BaseParser): pass class Scatter(BaseParser): pass -class Radar(BaseParser): pass +class Radar(BaseParser): + PARSER = Term(xlsrecord.CHRadar) + class RadarArea(BaseParser): pass class Surf(BaseParser): pass class SeriesList(BaseParser): pass @@ -539,19 +557,29 @@ class LD(BaseParser): PARSER = Group('ld', Req(Legend()) << Req(Begin()) << Req(Pos()) << Req(ATTACHEDLABEL()) << Opt(FRAME()) << CrtLayout12() << TEXTPROPS() << CRTMLFRT() << Req(End())) -class TWODROPBAR(BaseParser): pass -class CrtLine(BaseParser): pass +class DropBar(BaseParser): + PARSER = Term(xlsrecord.DropBar) + +class DROPBAR(BaseParser): + # DROPBAR = DropBar Begin LineFormat AreaFormat [GELFRAME] [SHAPEPROPS] End + PARSER = Group('drop-bar-root', Req(DropBar()) << Req(Begin()) << Req(LineFormat()) << + Req(AreaFormat()) << Opt(GELFRAME()) << Opt(SHAPEPROPS()) << + Req(End())) +class CrtLine(BaseParser): + PARSER = Term(xlsrecord.CrtLine) + class CrtLayout12A(BaseParser): pass class DAT(BaseParser): pass class CRT(BaseParser): + # It seems 2DROPBAR should be considered *2DROPBAR #CRT = ChartFormat Begin (Bar / Line / (BopPop [BopPopCustom]) / Pie / Area / Scatter / Radar / - #RadarArea / Surf) CrtLink [SeriesList] [Chart3d] [LD] [2DROPBAR] *4(CrtLine LineFormat) + #RadarArea / Surf) CrtLink [SeriesList] [Chart3d] [LD] [*2DROPBAR] *4(CrtLine LineFormat) #*2DFTTEXT [DataLabExtContents] [SS] *4SHAPEPROPS End - PARSER = Group('crt', Req(ChartFormat()) << Req(Begin()) << OneOf(Bar(), Line(), Seq(Req(BobPop()), BobPopCustom()), + PARSER = Group('crt', Req(ChartFormat()) << Req(Begin()) << OneOf(Bar(), Line(), Opt(Seq(Req(BobPop()), BobPopCustom())), Pie(), Area(), Scatter(), Radar(), RadarArea(), Surf()) << - Req(CrtLink()) << SeriesList() << Chart3d() << Opt(LD()) << TWODROPBAR() << + Req(CrtLink()) << SeriesList() << Chart3d() << Opt(LD()) << Many('drop-bars', DROPBAR(), max=2) << Many('crt-lines', Seq(Req(CrtLine()), Req(LineFormat()))) << Many('dft-texts', DFTTEXT()) << DataLabExtContents() << Opt(SS()) << Many('shape-props-list', SHAPEPROPS(), max=4) << Req(End())) diff --git a/src/xlsrecord.py b/src/xlsrecord.py index fa51c6a..880e3db 100644 --- a/src/xlsrecord.py +++ b/src/xlsrecord.py @@ -3737,6 +3737,32 @@ class Chart3DBarShape(BaseRecordHandler): return ('chart-3dbar-shape', {'riser': self.riser, 'taper': self.taper}) +class DropBar(BaseRecordHandler): + def __parseBytes(self): + self.gap = self.readSignedInt(2) + + def parseBytes (self): + self.__parseBytes() + self.appendLine('Gap: %s' % str(self.gap)) + # TODO: dump all data + + def dumpData(self): + self.__parseBytes() + return ('drop-bar', {'gap': self.gap}) + +class CrtLine(BaseRecordHandler): + def __parseBytes(self): + self.id = self.readUnsignedInt(2) + + def parseBytes (self): + self.__parseBytes() + self.appendLine('ID: %s' % str(self.id)) + # TODO: dump all data + + def dumpData(self): + self.__parseBytes() + return ('crt-line', {'id': self.id}) + class Chart3d(BaseRecordHandler): def __parseBytes(self): self.rot = self.readSignedInt(2) @@ -4311,8 +4337,46 @@ class CHLine(BaseRecordHandler): return ('line', {'stacked': self.stacked, 'percent': self.percent, 'shadow': self.shadow}) + +class CHRadar(BaseRecordHandler): + def __parseBytes (self): + flags = self.readUnsignedInt(2) + self.rdrAxLab = (flags & 0x0001) != 0 # A + self.hasShadow = (flags & 0x0002) != 0 # B + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Display category labels: %s"%self.getYesNo(self.rdrAxLab)) + self.appendLine("Data markers have shadow: %s"%self.getYesNo(self.hasShadow)) + + def dumpData(self): + self.__parseBytes() + return ('radar', {'rdr-ax-lab': self.rdrAxLab, + 'has-shadow': self.hasShadow}) +class CHPie(BaseRecordHandler): + def __parseBytes (self): + self.start = self.readUnsignedInt(2) + self.donut = self.readUnsignedInt(2) + flags = self.readUnsignedInt(2) + self.hasShadow = (flags & 0x0001) != 0 # A + self.showLdrLines = (flags & 0x0002) != 0 # B + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Start angle: %s"%self.getYesNo(self.start)) + self.appendLine("Size of center hole: %s"%self.getYesNo(self.donut)) + self.appendLine("Data points have shadow: %s"%self.getYesNo(self.hasShadow)) + self.appendLine("Show leader lines: %s"%self.getYesNo(self.showLdrLines)) + + def dumpData(self): + self.__parseBytes() + return ('pie', {'start': self.start, + 'donut': self.donut, + 'has-shadow': self.hasShadow, + 'show-ldr-Lines': self.showLdrLines}) + class Brai(BaseRecordHandler): destTypes = [ @@ -4420,3 +4484,27 @@ class MSODrawingSelection(BaseRecordHandler): self.__parseBytes() self.msodHdl.fillModel(model) +class GelFrame(BaseRecordHandler): + def __parseBytes (self): + self.msodHdl = msodraw.MSODrawHandler(self.bytes, self) + + def parseBytes (self): + self.__parseBytes() + # it seems there are errors in msodraw parser :( + #self.msodHdl.parseBytes() + + def dumpData(self): + # we don't dump data! + return ('gel-frame', {'bytes': globals.getRawBytes(self.bytes, False, False)}) + + +class Pls(BaseRecordHandler): + def __parseBytes (self): + # DEVMODE is quite a big structure so we don't parse it + pass + + def parseBytes (self): + self.__parseBytes() + + def dumpData(self): + return ('devmode', {'bytes': globals.getRawBytes(self.bytes, False, False)}) diff --git a/src/xlsstream.py b/src/xlsstream.py index 67c6ee3..68bb59c 100644 --- a/src/xlsstream.py +++ b/src/xlsstream.py @@ -73,7 +73,7 @@ recData = { 0x0040: ["BACKUP", "Save Backup Version of the File"], 0x0041: ["PANE", "Number of Panes and Their Position"], 0x0042: ["CODEPAGE/CODENAME", "Default Code Page/VBE Object Name"], - 0x004D: ["PLS", "Environment-Specific Print Record"], + 0x004D: ["PLS", "Environment-Specific Print Record", xlsrecord.Pls], 0x0050: ["DCON", "Data Consolidation Information"], 0x0051: ["DCONREF", "Data Consolidation References", xlsrecord.DConRef], 0x0052: ["DCONNAME", "Data Consolidation Named References", xlsrecord.DConName], @@ -256,10 +256,10 @@ recData = { 0x1015: ["LEGEND", "Legend Properties", xlsrecord.Legend], 0x1017: ["CHBAR, CHCOLUMN", "?", xlsrecord.CHBar], 0x1018: ["CHLINE", "?", xlsrecord.CHLine], - 0x1019: ["CHPIE", "?"], + 0x1019: ["CHPIE", "Pie/Doughnut chart group", xlsrecord.CHPie], 0x101A: ["CHAREA", "?"], 0x101B: ["CHSCATTER", "?"], - 0x001C: ["CHCHARTLINE", "?"], + 0x101C: ["CHCHARTLINE", "Specifies the presence of lines", xlsrecord.CrtLine], 0x101D: ["CHAXIS", "Chart Axis", xlsrecord.CHAxis], 0x101E: ["CHTICK", "Attributes of the axis labels and tick marks", xlsrecord.Tick], 0x101F: ["CHVALUERANGE", "Chart Axis Value Range", xlsrecord.CHValueRange], @@ -276,8 +276,8 @@ recData = { 0x1035: ["CHPLOTFRAME", "Chart Plot Frame (indicates the frame that follows)", xlsrecord.PlotArea], 0x103A: ["CHCHART3D", "Attributes of the 3-D plot area", xlsrecord.Chart3d], 0x103C: ["CHPICFORMAT", "?"], - 0x103D: ["CHDROPBAR", "?"], - 0x103E: ["CHRADARLINE", "?"], + 0x103D: ["CHDROPBAR", "Attributes of the up/down bars between multiple series", xlsrecord.DropBar], + 0x103E: ["CHRADARLINE", "Radar chart group", xlsrecord.CHRadar], 0x103F: ["CHSURFACE", "?"], 0x1040: ["CHRADARAREA", "?"], 0x1041: ["CHAXESSET", "Properties of an axis group", xlsrecord.AxisParent], @@ -299,7 +299,7 @@ recData = { 0x1062: ["AXCEXT", "Additional extension properties of a date axis(2.4.9)", xlsrecord.AxcExt], 0x1064: ["PLOTGROWTH", "Font Scaling Information in the Plot Area", xlsrecord.PlotGrowth], 0x1065: ["CHSIINDEX*", "Part of a group of records which specify the data of a chart", xlsrecord.SIIndex], - 0x1066: ["CHESCHERFORMAT", "?"] + 0x1066: ["CHESCHERFORMAT", "Properties of a fill pattern", xlsrecord.GelFrame] } recDataRev = { commit 689527a0425446bfedce9a7cc2d322bbdafcb996 Author: Sergey Kishchenko <[email protected]> Date: Thu Sep 22 17:32:02 2011 +0300 Simple future records support was added diff --git a/misc/test-files.sh b/misc/test-files.sh index 164952d..5b4c1bc 100755 --- a/misc/test-files.sh +++ b/misc/test-files.sh @@ -9,7 +9,21 @@ then fi for x in `find $test_dir -name \*.xls`; do - (python xls-dump.py $x | grep -v "rror inter" > /dev/null) || echo "Flat dump failed for" $x + touch tmp + python xls-dump.py $x > tmp + ret_val=$? + if [ $ret_val -eq 0 ] + then + grep "rror inter" tmp > /dev/null + if [ $? -eq 0 ] + then + echo "Flat dump failed for" $x "- failure in xlsrecord parse" + fi + else + echo "Flat dump failed for" $x "- unrecognised failure" + fi + + rm tmp python xls-dump.py --dump-mode=xml $x > /dev/null || echo "Xml dump failed for" $x python xls-dump.py --dump-mode=cxml $x > /dev/null || echo "CXml dump failed for" $x done diff --git a/src/xlsparser.py b/src/xlsparser.py index d105f24..1e04ce2 100644 --- a/src/xlsparser.py +++ b/src/xlsparser.py @@ -67,7 +67,7 @@ class BaseParser(object): return Seq(self, other) def safeParse(parser, stream): - #print "TRACE:[%s,%s]" % (str(parser), str(stream.tokens[stream.currentIndex])) + print "TRACE:[%s,%s]" % (str(parser), str(stream.tokens[stream.currentIndex])) parsed = None try: @@ -443,7 +443,8 @@ class CatSerRange(BaseParser): class AxcExt(BaseParser): PARSER = Term(xlsrecord.AxcExt) -class CatLab(BaseParser): pass +class CatLab(BaseParser): + PARSER = Term(xlsrecord.CatLab) class IFmtRecord(BaseParser): pass @@ -457,6 +458,15 @@ class AxisLine(BaseParser): class TextPropsStream(BaseParser): pass class ContinueFrt12(BaseParser): pass +class ChartFrtInfo(BaseParser): + PARSER = Term(xlsrecord.ChartFrtInfo) + +class StartBlock(BaseParser): + PARSER = Term(xlsrecord.StartBlock) + +class EndBlock(BaseParser): + PARSER = Term(xlsrecord.EndBlock) + class AXS(BaseParser): # AXS = [IFmtRecord] [Tick] [FontX] *4(AxisLine LineFormat) [AreaFormat] [GELFRAME] # *4SHAPEPROPS [TextPropsStream *ContinueFrt12] @@ -466,9 +476,15 @@ class AXS(BaseParser): Opt(Seq(Req(TextPropsStream()), Many('continue-frt12-list', ContinueFrt12())))) class IVAXIS(BaseParser): + # original ABNF: # IVAXIS = Axis Begin [CatSerRange] AxcExt [CatLab] AXS [CRTMLFRT] End + # it seems it's usual too have several future records indicators just after AxcExt and before the End: + # IVAXIS = Axis Begin [CatSerRange] AxcExt [ChartFrtInfo *StartBlock] [CatLab] AXS [CRTMLFRT] [EndBlock] End + PARSER = Group('ivaxis', Req(Axis()) << Req(Begin()) << CatSerRange() << Req(AxcExt()) << - CatLab() << Req(AXS()) << CRTMLFRT() << Req(End())) + Group('future', Opt(Seq(Req(ChartFrtInfo()), + Many('start-blocks', StartBlock())))) << CatLab() << + Req(AXS()) << CRTMLFRT() << EndBlock() << Req(End())) class ValueRange(BaseParser): PARSER = Term(xlsrecord.CHValueRange) @@ -510,7 +526,10 @@ class Radar(BaseParser): pass class RadarArea(BaseParser): pass class Surf(BaseParser): pass class SeriesList(BaseParser): pass -class Chart3d(BaseParser): pass + +class Chart3d(BaseParser): + PARSER = Term(xlsrecord.Chart3d) + class Legend(BaseParser): PARSER = Term(xlsrecord.Legend) @@ -538,9 +557,13 @@ class CRT(BaseParser): DataLabExtContents() << Opt(SS()) << Many('shape-props-list', SHAPEPROPS(), max=4) << Req(End())) class AXISPARENT(BaseParser): - #AXISPARENT = AxisParent Begin Pos [AXES] 1*4CRT End + # Original: + # AXISPARENT = AxisParent Begin Pos [AXES] 1*4CRT End + # It seems AXISPARENT can have EndBlock before End: + # AXISPARENT = AxisParent Begin Pos [AXES] 1*4CRT [EndBlock] End PARSER = Group('axis-root', Req(AxisParent()) << Req(Begin()) << Req(Pos()) << Opt(AXES()) << Many('crt-list', CRT(), min=1, max=4) << + EndBlock() << Req(End())) @@ -552,6 +575,7 @@ class CHARTFORMATS(BaseParser): #CHARTFOMATS = Chart Begin *2FONTLIST Scl PlotGrowth [FRAME] *SERIESFORMAT *SS ShtProps #*2DFTTEXT AxesUsed 1*2AXISPARENT [CrtLayout12A] [DAT] *ATTACHEDLABEL [CRTMLFRT] #*([DataLabExt StartObject] ATTACHEDLABEL [EndObject]) [TEXTPROPS] *2CRTMLFRT End + # It seems it is possible to have optional EndBlock just before an end PARSER = Group('chart-fmt', Req(Chart()) << Req(Begin()) << Many('font-lists', FONTLIST(), max=2) << Req(Scl()) << Req(PlotGrowth()) << Opt(FRAME()) << Many('series-fmt-list', SERIESFORMAT()) << Many('ss-list', SS()) << Req(ShtProps()) << Many('dft-texts', DFTTEXT(), max=2) << @@ -561,7 +585,7 @@ class CHARTFORMATS(BaseParser): Req(StartObject()))), Req(ATTACHEDLABEL()), EndObject())) << - Opt(TEXTPROPS()) << Many('crtmlfrt-list', CRTMLFRT()) << Req(End())) + Opt(TEXTPROPS()) << Many('crtmlfrt-list', CRTMLFRT()) << EndBlock() << Req(End())) class Dimensions(BaseParser): PARSER = Term(xlsrecord.Dimensions) diff --git a/src/xlsrecord.py b/src/xlsrecord.py index 21f0beb..fa51c6a 100644 --- a/src/xlsrecord.py +++ b/src/xlsrecord.py @@ -112,7 +112,18 @@ class ICV(object): def dumpIcv(icv): return {'value': icv.value} - + +class CFRTID(object): + def __init__ (self, start, end): + self.start = start + self.end = end + + +def dumpCfrtid(cfrtid): + return {'start': cfrtid.start, + 'end': cfrtid.end} + + class BaseRecordHandler(globals.ByteStream): def __init__ (self, header, size, bytes, strmData): @@ -221,6 +232,9 @@ Like parseBytes(), the derived classes must overwrite this method.""" def readICV (self): return ICV(self.readUnsignedInt(2)) + def readCFRTID (self): + return CFRTID(self.readUnsignedInt(2),self.readUnsignedInt(2)) + class AutofilterInfo(BaseRecordHandler): def __parseBytes (self): @@ -3606,7 +3620,7 @@ class ChartFormat(BaseRecordHandler): flags = self.readUnsignedInt(2) self.varied = (flags & 0x001) != 0 # A self.icrt = self.readUnsignedInt(2) - + def parseBytes (self): self.__parseBytes() # TODO: dump all data @@ -3616,6 +3630,99 @@ class ChartFormat(BaseRecordHandler): return ('chart-format', {'varied': self.varied, 'icrt': self.icrt}) +class ChartFrtInfo(BaseRecordHandler): + def __parseBytes(self): + self.headerOld = self.readUnsignedInt(4) + self.verOriginator = self.readUnsignedInt(1) + self.verWriter = self.readUnsignedInt(1) + self.cCFRTID = self.readUnsignedInt(2) + self.cfrtids = [] + for x in xrange(self.cCFRTID): + self.cfrtids.append(self.readCFRTID()) + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Header old: %s" % str(self.headerOld)) + self.appendLine("verOriginator (version of app that created the file): %s" % str(self.verOriginator)) + self.appendLine("verWriter (version of app that last saved the file): %s" % str(self.verWriter)) + self.appendLine("Count of CFRTID records: %s" % str(self.cCFRTID)) + for cfrtid in self.cfrtids: + self.appendLine("CFRTID: [%s, %s]" % (cfrtid.start, cfrtid.end)) + + def dumpData(self): + self.__parseBytes() + return ('chart-frt-info', {'header-old': self.headerOld, + 'ver-originator': self.verOriginator, + 'ver-writer': self.verWriter, + 'cfrtid-count': self.cCFRTID}, + [('cfrtid-list', map(lambda x: ('cfrtid', dumpCfrtid(x)), + self.cfrtids))]) + +class StartBlock(BaseRecordHandler): + def __parseBytes(self): + self.headerOld = self.readUnsignedInt(4) + self.objectKind = self.readUnsignedInt(2) + self.objectContext = self.readUnsignedInt(2) + self.objectInstance1 = self.readUnsignedInt(2) + self.objectInstance2 = self.readUnsignedInt(2) + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Header old: %s" % str(self.headerOld)) + self.appendLine("Object kind: %s" % str(self.objectKind)) + self.appendLine("Object context: %s" % str(self.objectContext)) + self.appendLine("Object instance 1: %s" % str(self.objectInstance1)) + self.appendLine("Object instance 2: %s" % str(self.objectInstance2)) + + def dumpData(self): + self.__parseBytes() + return ('start-block', {'header-old': self.headerOld, + 'object-kind': self.objectKind, + 'object-context': self.objectContext, + 'object-instance1': self.objectInstance1, + 'object-instance2': self.objectInstance2}) + +class EndBlock(BaseRecordHandler): + def __parseBytes(self): + self.headerOld = self.readUnsignedInt(4) + self.objectKind = self.readUnsignedInt(2) + unused = self.readUnsignedInt(2) + unused = self.readUnsignedInt(2) + unused = self.readUnsignedInt(2) + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Header old: %s" % str(self.headerOld)) + self.appendLine("Object kind: %s" % str(self.objectKind)) + + def dumpData(self): + self.__parseBytes() + return ('end-block', {'header-old': self.headerOld, + 'object-kind': self.objectKind}) + +class CatLab(BaseRecordHandler): + def __parseBytes(self): + self.headerOld = self.readUnsignedInt(4) + self.offset = self.readUnsignedInt(2) + self.at = self.readUnsignedInt(2) + flags = self.readUnsignedInt(2) + self.autoCatLabelReal = (flags & 0x0001) != 0 # A + reserved = self.readUnsignedInt(2) + + def parseBytes (self): + self.__parseBytes() + self.appendLine("Header old: %s" % str(self.headerOld)) + self.appendLine("Offset: %s" % str(self.offset)) + self.appendLine("At(alignment): %s" % str(self.at)) + self.appendLine("Auto category label real: %s" % str(self.autoCatLabelReal)) + + def dumpData(self): + self.__parseBytes() + return ('catlab', {'header-old': self.headerOld, + 'offset': self.offset, + 'at': self.at, + 'auto-catlabel-real': self.autoCatLabelReal}) + class Chart3DBarShape(BaseRecordHandler): def __parseBytes(self): self.riser = self.readUnsignedInt(1) @@ -3630,6 +3737,41 @@ class Chart3DBarShape(BaseRecordHandler): return ('chart-3dbar-shape', {'riser': self.riser, 'taper': self.taper}) +class Chart3d(BaseRecordHandler): + def __parseBytes(self): + self.rot = self.readSignedInt(2) + self.elev = self.readSignedInt(2) + self.dist = self.readSignedInt(2) + self.height = self.readUnsignedInt(2) # TODO: it can be a signed int too + self.depth = self.readUnsignedInt(2) + self.gap = self.readUnsignedInt(2) + + flag = self.readUnsignedInt(2) + self.perspective = (flag & 0x0001) != 0 # A + self.cluster = (flag & 0x0002) != 0 # B + self.scaling = (flag & 0x0004) != 0 # C + reserved = (flag & 0x0008) != 0 # D + self.notPieChart = (flag & 0x0010) != 0 # E + self.walls2D = (flag & 0x0020) != 0 # F + + def parseBytes (self): + self.__parseBytes() + # TODO: dump all data + + def dumpData(self): + self.__parseBytes() + return ('chart-3d', {'rot': self.rot, + 'elev': self.elev, + 'dist': self.dist, + 'height': self.height, + 'depth': self.depth, + 'gap': self.gap, + 'perspective': self.perspective, + 'cluster': self.cluster, + 'scaling': self.scaling, + 'not-pie-chart': self.notPieChart, + 'walls-2d': self.walls2D}) + class SerToCrt(BaseRecordHandler): def __parseBytes(self): self.id = self.readUnsignedInt(2) diff --git a/src/xlsstream.py b/src/xlsstream.py index ceb6eeb..67c6ee3 100644 --- a/src/xlsstream.py +++ b/src/xlsstream.py @@ -223,6 +223,10 @@ recData = { 0x0802: ["QSISXTAG", "Pivot Table and Query Table Extensions", xlsrecord.PivotQueryTableEx], 0x0809: ["BOF", "Beginning of File", xlsrecord.BOF], 0x0810: ["SXVIEWEX9", "Pivot Table Extensions", xlsrecord.SXViewEx9], + 0x0850: ["CHARTFRTINFO", "Versions of the application that edited the file", xlsrecord.ChartFrtInfo], + 0x0852: ["CHSTARTBLOCK", "Specifies the beginning of future records", xlsrecord.StartBlock], + 0x0853: ["CHENDBLOCK", "Specifies the end of future records", xlsrecord.EndBlock], + 0x0856: ["CATLAB", "Attributes of axis label", xlsrecord.CatLab], 0x0858: ["CHPIVOTREF", "Pivot Chart Reference"], 0x0862: ["SHEETLAYOUT", "Tab Color below Sheet Name"], 0x0863: ["BOOKEXT", "Extra Book Info"], @@ -270,7 +274,7 @@ recData = { 0x1033: ["BEGIN", "Start of Chart Sheet Substream", xlsrecord.Begin], 0x1034: ["END", "End of Chart Sheet Substream", xlsrecord.End], 0x1035: ["CHPLOTFRAME", "Chart Plot Frame (indicates the frame that follows)", xlsrecord.PlotArea], - 0x103A: ["CHCHART3D", "?"], + 0x103A: ["CHCHART3D", "Attributes of the 3-D plot area", xlsrecord.Chart3d], 0x103C: ["CHPICFORMAT", "?"], 0x103D: ["CHDROPBAR", "?"], 0x103E: ["CHRADARLINE", "?"], _______________________________________________ Libreoffice-commits mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
