[Libreoffice-commits] .: 4 commits - misc/test-files.sh src/xlsparser.py src/xlsrecord.py src/xlsstream.py

Kohei Yoshida kohei at kemper.freedesktop.org
Tue Oct 25 14:11:51 PDT 2011


 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 <kohei.yoshida at suse.com>
Date:   Tue Oct 25 17:10:04 2011 -0400

    Merge branch 'tilarids-master'

commit 0263e7846fdd7aac6180f9f385070edf32f96c64
Author: Sergey Kishchenko <voidwrk at gmail.com>
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 <voidwrk at gmail.com>
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 <voidwrk at gmail.com>
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", "?"],


More information about the Libreoffice-commits mailing list