Add support for .tlo TableLook files from SPSS 15 and earlier.
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 9 Nov 2020 02:29:49 +0000 (18:29 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Tue, 10 Nov 2020 07:30:30 +0000 (23:30 -0800)
13 files changed:
NEWS
doc/automake.mk
doc/dev/spv-file-format.texi
doc/dev/tlo-file-format.texi [new file with mode: 0644]
doc/pspp-dev.texi
doc/pspp-output.texi
src/output/pivot-table.c
src/output/spv/automake.mk
src/output/spv/binary-parser-generator
src/output/spv/spv-table-look.c
src/output/spv/tlo.grammar [new file with mode: 0644]
utilities/pspp-output.1
utilities/pspp-output.c

diff --git a/NEWS b/NEWS
index 997bedade9324e44a5b408d38633689728f5bd28..204d773f17df087c23dd8fe4e35463ff26bb0edd 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -26,7 +26,7 @@ Changes from 1.4.1 to 1.5.2:
 
    - New --table-look and --nth-commands options.
 
-   - New get-table-look command.
+   - New get-table-look and convert-table-look commands.
 
 Changes from 1.4.0 to 1.4.1:
 
index 4e60b98ca51e4077c05a80c0a92a2cddb6454e74..6b8c37331a3333a1eca80800c45de40d40a4b279 100644 (file)
@@ -59,6 +59,7 @@ doc_pspp_dev_TEXINFOS = doc/version-dev.texi \
        doc/dev/pc+-file-format.texi \
        doc/dev/portable-file-format.texi \
        doc/dev/spv-file-format.texi \
+       doc/dev/tlo-file-format.texi \
        doc/dev/encrypted-file-wrappers.texi \
        doc/dev/q2c.texi
 
index e001d9e5906e84b3580b642ebe8e7f48d1c97be8..453f7a8ecdbed4c2cdf67bc8a5f812ab4942d436 100644 (file)
@@ -3630,6 +3630,7 @@ XML, which has the following @code{tableProperties} element:
 
 @example
 tableProperties
+   :name?
 => generalProperties footnoteProperties cellFormatProperties borderProperties printingProperties
 
 generalProperties
@@ -3688,3 +3689,6 @@ printingProperties
    :printEachLayerOnSeparatePage=bool?
 => EMPTY
 @end example
+
+The @code{name} attribute appears only in standalone @file{.stt} files
+(@pxref{SPSS TableLook STT Format}).
diff --git a/doc/dev/tlo-file-format.texi b/doc/dev/tlo-file-format.texi
new file mode 100644 (file)
index 0000000..c117816
--- /dev/null
@@ -0,0 +1,331 @@
+@c PSPP - a program for statistical analysis.
+@c Copyright (C) 2020 Free Software Foundation, Inc.
+@c Permission is granted to copy, distribute and/or modify this document
+@c under the terms of the GNU Free Documentation License, Version 1.3
+@c or any later version published by the Free Software Foundation;
+@c with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
+@c A copy of the license is included in the section entitled "GNU
+@c Free Documentation License".
+@c
+
+@node SPSS TableLook File Formats
+@appendix SPSS TableLook File Formats
+
+SPSS has a concept called a TableLook to control the styling of pivot
+tables in output.  SPSS 15 and earlier used @file{.tlo} files with a
+special binary format to save TableLooks to disk; SPSS 16 and later
+use @file{.stt} files in an XML format to save them.  Both formats
+expose roughly the same features, although the older @file{.tlo}
+format does have some features that @file{.stt} does not.
+
+This appendix describes both formats.
+
+@menu
+* SPSS TableLook STT Format::
+* SPSS TableLook TLO Format::
+@end menu
+
+@node SPSS TableLook STT Format
+@section The @file{.stt} Format
+
+The @file{.stt} file format is an XML file that contains a subset of
+the SPV structure member format (@pxref{SPV Structure Member Format}).
+Its root element is a @code{tableProperties} element (@pxref{SPV
+Detail Legacy Properties}).
+
+@node SPSS TableLook TLO Format
+@section The @file{.tlo} Format
+
+A @file{.tlo} file has a custom binary format.  This section describes
+it using the syntax used previously for SPV binary members (@pxref{SPV
+Light Detail Member Format}).  There is one new convention: TLO files
+express colors as @code{int32} values in which the low 8 bits are the
+red component, the next 8 bits are green, and next 8 bits are blue,
+and the high bits are zeros.
+
+TLO files support various features that SPV files do not.  PSPP
+implements the SPV feature set, so it mostly ignores the added TLO
+features.  The details of this mapping are explained below.
+
+At the top level, a TLO file consists of five sections.  The first
+four are always present and the last one is optional:
+
+@example
+TableLook =>
+   PTTableLook[tl]
+   PVSeparatorStyle[ss]
+   PVCellStyle[cs]
+   PVTextStyle[ts]
+   V2Styles?
+@end example
+
+Each section is described below.
+
+@menu
+* PTTableLook in SPSS TLO Files::
+* PVSeparatorStyle in SPSS TLO Files::
+* PVCellStyle and PVTextStyle in SPSS TLO Files::
+* V2Styles in SPSS TLO Files::
+@end menu
+
+@node PTTableLook in SPSS TLO Files
+@subsection @code{PTTableLook}
+
+@example
+PTTableLook =>
+   ff ff 00 00 "PTTableLook" (00|02)[version]
+   int16[flags]
+   00 00
+   bool[nested-row-labels] 00
+   bool[footnote-marker-subscripts] 00
+   i54 i18
+@end example
+
+In PTTableLook, @code{version} is 00 or 02.  The only difference is
+that version 00 lacks V2Styles (@pxref{V2Styles in SPSS TLO Files})
+and that version 02 includes it.  Both TLO versions are seen in the
+wild.
+
+@code{flags} is a bit-mapped field.  Its bits have the following
+meanings:
+
+@table @asis
+@item 0x2
+If set to 1, hide empty rows and columns; otherwise, show them.
+
+@item 0x4
+If set to 1, use numeric footnote markers; otherwise, use alphabetic
+footnote markers.
+
+@item 0x8
+If set to 1, print all layers; otherwise, print only the current
+layer.
+
+@item 0x10
+If set to 1, scale the table to fit the page width; otherwise, break
+it horizontally if necessary.
+
+@item 0x20
+If set to 1, scale the table to fit the page length; otherwise, break
+it vertically if necessary.
+
+@item 0x40
+If set to 1, print each layer on a separate page (only if all layers
+are being printed); otherwise, paginate layers naturally.
+
+@item 0x80
+If set to 1, print a continuation string at the top of a table that is
+split between pages.
+
+@item 0x100
+If set to 1, print a continuation string at the bottom of a table that
+is split between pages.
+@end table
+
+When @code{nested-row-labels} is 1, row dimension labels appear
+nested; otherwise, they are put into the upper-left corner of the
+pivot table.
+
+When @code{footnote-marker-subscripts} is 1, footnote markers are
+shown as subscripts; otherwise, they are shown as superscripts.
+
+@node PVSeparatorStyle in SPSS TLO Files
+@subsection @code{PVSeparatorStyle}
+
+@example
+PVSeparatorStyle =>
+   ff ff 00 00 "PVSeparatorStyle" 00
+   Separator*4[sep1]
+   03 80 00
+   Separator*4[sep2]
+
+Separator =>
+   case(
+       00 00
+     | 01 00 int32[color] int16[style] int16[width]
+   )[type]
+@end example
+
+PVSeparatorStyle contains eight Separators, in two groups.  Each
+Separator represents a border between pivot table elements.  TLO and
+SPV files have the same concepts for borders.  @xref{SPV Light Member
+Borders}, for the treatment of borders in SPV files.
+
+A Separator's @code{type} is 00 if the border is not drawn, 01
+otherwise.  For a border that is drawn, @code{color} is the color that
+it is drawn in.  @code{style} and @code{width} have the following
+meanings:
+
+@table @asis
+@item @code{style} = 0 and 0 @leq{} @code{width} @leq{} 3
+An increasingly thick single line.  SPV files only have three line
+thicknesses.  PSPP treats @code{width} 0 as a thin line, @code{width}
+1 as a solid (normal width) line, and @code{width} 2 or 3 as a thick
+line.
+
+@item @code{style} = 1 and 0 @leq{} @code{width} @leq{} 1
+A doubled line, composed of normal-width (0) or thick (1) lines.  SPV
+files only have ``normal'' width double lines, so PSPP maps both
+variants the same way.
+
+@item @code{style} = 2
+A dashed line.
+@end table
+
+The first group, @code{sep1}, represents the following borders within
+the pivot table, by index:
+
+@enumerate 0
+@item Horizontal dimension rows
+@item Vertical dimension rows
+@item Horizontal category rows
+@item Vertical category rows
+@end enumerate
+
+The second group, @code{sep2}, represents the following borders within
+the pivot table, by index:
+
+@enumerate 0
+@item Horizontal dimension columns
+@item Vertical dimension columns
+@item Horizontal category columns
+@item Vertical category columns
+@end enumerate
+
+@node PVCellStyle and PVTextStyle in SPSS TLO Files
+@subsection @code{PVCellStyle} and @code{PVTextStyle}
+
+@example
+PVCellStyle =>
+   ff ff 00 00 "PVCellStyle"
+   AreaColor[title-color]
+
+PVTextStyle =>
+   ff ff 00 00 "PVTextStyle" 00
+   AreaStyle[title-style] MostAreas*7[most-areas]
+
+MostAreas =>
+   06 80
+   AreaColor[color] 08 80 00 AreaStyle[style]
+@end example
+
+These sections hold the styling and coloring for each of the 8 areas
+in a pivot table.  They are conceptually similar to the area style
+information in SPV light members (@pxref{SPV Light Member Areas}).
+
+The styling and coloring for the title area is split between
+PVCellStyle and PVTextStyle: the former holds @code{title-color}, the
+latter holds @code{title-style}.  The style for the remaining 7 areas
+is in @code{most-areas} in PVTextStyle, in the following order:
+layers, corner, row labels, column labels, data, caption, and footer.
+
+@example
+AreaColor =>
+   00 01 00 int32[color10] int32[color0] byte[shading] 00
+@end example
+
+AreaColor represents the background color of an area.  TLO files, but
+not SPV files, describe backgrounds that are a shaded combination of
+two colors: @code{shading} of 0 is pure @code{color0}, @code{shading}
+of 10 is pure @code{color10}, and value in between mix pixels of the
+two different colors in linear degree.  PSPP does not implement
+shading, so for 1 @leq{} @code{shading} @leq{} 9 it interpolates RGB
+values between colors to arrive at an intermediate shade.
+
+@example
+AreaStyle =>
+   int16[valign] int16[halign] int16[decimal-offset]
+   int16[left-margin] int16[right-margin] int16[top-margin] int16[bottom-margin]
+   00 00 01 00
+   int32[font-size] int16[stretch]
+   00*2
+   int32[rotation-angle]
+   00*4
+   int16[weight]
+   00*2
+   bool[italic] bool[underline] bool[strikethrough]
+   int32[rtf-charset-number]
+   22
+   byte[font-name-len] byte*[font-name-len][font-name]
+   int32[text-color]
+   00*2
+@end example
+
+AreaStyle represents style properties of an area.
+
+@code{valign} is 0 for top alignment, 1 for bottom alginment, 2 for
+center.
+
+@code{halign} is 0 for left alignment, 1 for right, 2 for center, 3
+for mixed, 4 for decimal.  For decimal alignment,
+@code{decimal-offset} is the offset of the decimal point in 20ths of a
+point.
+
+@code{left-margin}, @code{right-margin}, @code{top-margin}, and
+@code{bottom-margin} are also measured in 20ths of a point.
+
+@code{font-size} is negative 96ths of an inch, e.g. 9 point is -12 or
+0xfffffff3.
+
+@code{stretch} has something to do with font size or stretch.  The
+usual value is 01 and values larger than that do weird things.  A
+reader can safely ignore it.
+
+@code{rotation-angle} is a font rotation angle.  A reader can safely
+ignore it.
+
+@code{weight} is 400 for a normal-weight font, 700 indicates bold.
+(This is a Windows API convention.)
+
+@code{italic} and @code{underline} have the obvious meanings.  So does
+@code{strikethrough}, which PSPP ignores.
+
+@code{rtf-charset-number} is a character set number from RTF.  A
+reader can safely ignore it.
+
+The @code{font-name} is the name of a font, such as @code{Arial}.
+Only US-ASCII characters have been observed here.
+
+@code{text-color} is the color of the text itself.
+
+@node V2Styles in SPSS TLO Files
+@subsection @code{V2Styles}
+
+@example
+V2Styles =>
+   Separator*11[sep3]
+   byte[continuation-len] byte*[continuation-len][continuation]
+   int32[min-col-width] int32[max-col-width]
+   int32[min-row-height] int32[max-row-height]
+@end example
+
+This final, optional, part of the TLO file format contains some
+additional style information.  It begins with @code{sep3}, which
+represents the following borders within the pivot table, by index:
+
+@table @asis
+@item 0
+Title.
+@item 1@dots{}4
+Left, right, top, and bottom inner frame.
+@item 5@dots{}8
+Left, right, top, and bottom outer frame.
+@item 9, 10
+Left and top of data area.
+@end table
+
+When V2Styles is absent, the inner frame borders default to a solid
+line and the others listed above to no line.
+
+@code{continuation} is the string that goes at the top or bottom
+of a table broken across pages.  When V2Styles is absent, the
+default is @code{(Cont.)}.
+
+@code{min-col-width} is the minimum width that a column will be
+assigned automatically.  @code{max-col-width} is the maximum width
+that a column will be assigned to accommodate a long column label.
+@code{min-row-width} and @code{max-row-width} are a similar range for
+the width of row labels.  All of these measurements are in points.
+When V2Styles is absent, the defaults are 36 for @code{min-col-width} and
+@code{min-row-height}, 72 for @code{max-col-width}, and 120 for
+@code{max-row-height}.
index 1b8fb8a4272d941326b60e3734bc5c0564ae22a5..f751dd879cf4d856afa47eae130930b96c867473 100644 (file)
@@ -94,6 +94,7 @@ Free Documentation License".
 * System File Format::          Format of PSPP system files.
 * SPSS/PC+ System File Format:: Format of SPSS/PC+ system files.
 * SPSS Viewer File Format::     Format of SPSS Viewer (SPV) files.
+* SPSS TableLook File Formats:: Formats of .stt and .tlo files.
 * Encrypted File Wrappers::     Common wrapper for encrypted SPSS files.
 * q2c Input Format::            Format of syntax accepted by q2c.
 
@@ -114,6 +115,7 @@ Free Documentation License".
 @include dev/system-file-format.texi
 @include dev/pc+-file-format.texi
 @include dev/spv-file-format.texi
+@include dev/tlo-file-format.texi
 @include dev/encrypted-file-wrappers.texi
 @include dev/q2c.texi
 
index fa4de813d32d7be9ed6c87904d87da810f620629..f8b7931d735c20afb8979dd8850409867b4ad172 100644 (file)
@@ -31,6 +31,8 @@ SPSS 15 and earlier versions instead use @file{.spo} files.
 
 @t{pspp-output} [@var{options}] @t{get-table-look} @var{source} @var{destination}
 
+@t{pspp-output} [@var{options}] @t{convert-table-look} @var{source} @var{destination}
+
 @t{pspp-output -@w{-}help}
 
 @t{pspp-output -@w{-}version}
@@ -45,6 +47,7 @@ developers may find useful for debugging.
 * The pspp-output dir Command::
 * The pspp-output convert Command::
 * The pspp-output get-table-look Command::
+* The pspp-output convert-table-look Command::
 * Input Selection Options::
 @end menu
 
@@ -116,7 +119,8 @@ the destination is written as best it can, even with errors.
 
 @item --table-look=@var{file}
 Reads a table style from @var{file} and applies it to all of the
-output tables.  The file should a TableLook @file{.stt} file.
+output tables.  The file should be a TableLook @file{.stt} or
+@file{.tlo} file.
 @end table
 
 @node The pspp-output get-table-look Command
@@ -136,6 +140,19 @@ The user may use the TableLook file to change the style of tables in
 other files, by passing it to the @option{--table-look} option on the
 @code{convert} command.
 
+@node The pspp-output convert-table-look Command
+@section The @code{convert-table-look} Command
+
+@display
+@t{pspp-output} [@var{options}] @t{convert-table-look} @var{source} @var{destination}
+@end display
+
+Reads @file{.stt} or @file{.tlo} file @var{source}, and writes it back
+to @var{destination} (typically with an @file{.stt} extension) in the
+TableLook XML format.  This is useful for converting a TableLook
+@file{.tlo} file from SPSS 15 or earlier into the newer @file{.stt}
+format.
+
 @node Input Selection Options
 @section Input Selection Options
 
index b30961dacb31d01ab35420a44aa3bd1a32bb6a0f..272ccc9e34849dca95a9d133712d219b2a3e0b75 100644 (file)
@@ -202,7 +202,7 @@ pivot_table_look_init (struct pivot_table_look *look)
 
   look->omit_empty = false;
   look->row_labels_in_corner = true;
-  look->width_ranges[TABLE_HORZ][0] = 50;
+  look->width_ranges[TABLE_HORZ][0] = 36;
   look->width_ranges[TABLE_HORZ][1] = 72;
   look->width_ranges[TABLE_VERT][0] = 36;
   look->width_ranges[TABLE_VERT][1] = 120;
index 970c821318e15e0e2f3cdc6df685a9ea4c453a78..01f8a334a9d58e72f10301c5ace53463a377324b 100644 (file)
@@ -106,3 +106,19 @@ nodist_src_output_liboutput_la_SOURCES += $(structure_xml_out)
 BUILT_SOURCES += $(structure_xml_out)
 CLEANFILES += $(structure_xml_out)
 EXTRA_DIST += $(structure_xml_in)
+
+tlo_in = \
+       src/output/spv/binary-parser-generator \
+       src/output/spv/tlo.grammar
+tlo_out = \
+       src/output/spv/tlo-parser.c \
+       src/output/spv/tlo-parser.h
+src/output/spv/tlo-parser.c: $(tlo_in)
+       $(AM_V_GEN)$(PYTHON) $^ code tlo '"output/spv/tlo-parser.h"' > $@.tmp
+       $(AM_V_at)mv $@.tmp $@
+src/output/spv/tlo-parser.h: $(tlo_in)
+       $(AM_V_GEN)$(PYTHON) $^ header tlo > $@.tmp && mv $@.tmp $@
+nodist_src_output_liboutput_la_SOURCES += $(tlo_out)
+BUILT_SOURCES += $(tlo_out)
+CLEANFILES += $(tlo_out)
+EXTRA_DIST += $(tlo_in)
index 820cf23d89ae7fc2571aaef3372beebabf0a8794..ccae0e474e42c3f773249311e83ee469b672f2cc 100644 (file)
@@ -104,6 +104,13 @@ def get_token():
     elif line.startswith('...'):
         token = (line[:3], )
         line = line[3:]
+    elif line.startswith('"'):
+        n = 1
+        while n < len(line) and (line[n] != '"'):
+            n += 1
+        s = line[1:n].encode()
+        line = line[n+1:]
+        token = ('bytes', struct.pack('<h', len(s)) + s)
     elif line[0].isalnum() or line[0] == '-':
         n = 1
         while n < len(line) and (line[n].isalnum() or line[n] == '-'):
@@ -136,9 +143,11 @@ def usage():
     argv0 = os.path.basename(sys.argv[0])
     print('''\
 %(argv0)s, parser generator for SPV binary members
-usage: %(argv0)s GRAMMAR header
-       %(argv0)s GRAMMAR code HEADER_NAME
-  where GRAMMAR contains grammar definitions\
+usage: %(argv0)s GRAMMAR header PREFIX
+       %(argv0)s GRAMMAR code PREFIX HEADER_NAME
+  where GRAMMAR contains grammar definitions,
+        PREFIX is the identifier prefix to use,
+    and HEADER_NAME is the name of the header to include.
 ''' % {"argv0": argv0})
     sys.exit(0)
 
index 122594ef303a823d39151f64da1536975a4149f5..d229af2c0c8d536bc66efdc2ac4086b05b6703b0 100644 (file)
 #include <libxml/xmlwriter.h>
 #include <string.h>
 
+#include "libpspp/i18n.h"
 #include "output/spv/structure-xml-parser.h"
+#include "output/spv/tlo-parser.h"
 #include "output/pivot-table.h"
 #include "output/table.h"
 
 #include "gl/read-file.h"
 #include "gl/xalloc.h"
+#include "gl/xmemdup0.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
@@ -279,7 +282,216 @@ error:
   *outp = NULL;
   return error;
 }
+\f
+static struct cell_color
+tlo_decode_color (uint32_t c)
+{
+  return (struct cell_color) CELL_COLOR (c, c >> 8, c >> 16);
+}
+
+static void
+tlo_decode_border (const struct tlo_separator *in,
+                   struct table_border_style *out)
+{
+  if (in->type == 0)
+    {
+      out->stroke = TABLE_STROKE_NONE;
+      return;
+    }
+
+  out->color = tlo_decode_color (in->type_01.color);
+
+  switch (in->type_01.style)
+    {
+    case 0:
+      out->stroke = (in->type_01.width == 0 ? TABLE_STROKE_THIN
+                     : in->type_01.width == 1 ? TABLE_STROKE_SOLID
+                     : TABLE_STROKE_THICK);
+      break;
+
+    case 1:
+      out->stroke = TABLE_STROKE_DOUBLE;
+      break;
+
+    case 2:
+      out->stroke = TABLE_STROKE_DASHED;
+      break;
+    }
+}
+
+static struct cell_color
+interpolate_colors (struct cell_color c0, struct cell_color c1, int shading)
+{
+  if (shading <= 0)
+    return c0;
+  else if (shading >= 10)
+    return c1;
+  else
+    {
+      int x0 = 10 - shading;
+      int x1 = shading;
+
+      return (struct cell_color) CELL_COLOR ((c0.r * x0 + c1.r * x1) / 10,
+                                             (c0.g * x0 + c1.g * x1) / 10,
+                                             (c0.b * x0 + c1.b * x1) / 10);
+    }
+}
+
+static void
+tlo_decode_area (const struct tlo_area_color *color,
+                 const struct tlo_area_style *style,
+                 struct table_area_style *out)
+{
+  out->cell_style.halign = (style->halign == 0 ? TABLE_HALIGN_LEFT
+                            : style->halign == 1 ? TABLE_HALIGN_RIGHT
+                            : style->halign == 2 ? TABLE_HALIGN_CENTER
+                            : style->halign == 4 ? TABLE_HALIGN_DECIMAL
+                            : TABLE_HALIGN_MIXED);
+  out->cell_style.valign = (style->valign == 0 ? TABLE_VALIGN_TOP
+                            : style->valign == 1 ? TABLE_VALIGN_BOTTOM
+                            : TABLE_VALIGN_CENTER);
+  out->cell_style.decimal_offset = style->decimal_offset / 20;
+  out->cell_style.decimal_char = '.';                  /* XXX */
+  out->cell_style.margin[TABLE_HORZ][0] = style->left_margin / 20;
+  out->cell_style.margin[TABLE_HORZ][1] = style->right_margin / 20;
+  out->cell_style.margin[TABLE_VERT][0] = style->top_margin / 20;
+  out->cell_style.margin[TABLE_VERT][1] = style->bottom_margin / 20;
+
+  out->font_style.bold = style->weight > 400;
+  out->font_style.italic = style->italic;
+  out->font_style.underline = style->underline;
+  out->font_style.markup = false;
+
+  out->font_style.fg[0] = out->font_style.fg[1]
+    = tlo_decode_color (style->text_color);
+
+  struct cell_color c0 = tlo_decode_color (color->color0);
+  struct cell_color c10 = tlo_decode_color (color->color10);
+  struct cell_color bg = interpolate_colors (c0, c10, color->shading);
+  out->font_style.bg[0] = out->font_style.bg[1] = bg;
+
+  free (out->font_style.typeface);
+  out->font_style.typeface = recode_string (
+    "UTF-8", "ISO-8859-1",
+    CHAR_CAST (char *, style->font_name), style->font_name_len);
+  out->font_style.size = -style->font_size * 3 / 4;
+}
+
+static struct pivot_table_look *
+tlo_decode (const struct tlo_table_look *in)
+{
+  struct pivot_table_look *out = xmalloc (sizeof *out);
+  pivot_table_look_init (out);
+
+  const uint16_t flags = in->tl->flags;
 
+  out->omit_empty = (flags & 0x02) != 0;
+  out->row_labels_in_corner = !in->tl->nested_row_labels;
+  if (in->v2_styles)
+    {
+      out->width_ranges[TABLE_HORZ][0] = in->v2_styles->min_col_width;
+      out->width_ranges[TABLE_HORZ][1] = in->v2_styles->max_col_width;
+      out->width_ranges[TABLE_VERT][0] = in->v2_styles->min_row_height;
+      out->width_ranges[TABLE_VERT][1] = in->v2_styles->max_row_height;
+    }
+  else
+    {
+      out->width_ranges[TABLE_HORZ][0] = 36;
+      out->width_ranges[TABLE_HORZ][1] = 72;
+      out->width_ranges[TABLE_VERT][0] = 36;
+      out->width_ranges[TABLE_VERT][1] = 120;
+    }
+
+  out->show_numeric_markers = flags & 0x04;
+  out->footnote_marker_superscripts = !in->tl->footnote_marker_subscripts;
+
+  for (int i = 0; i < 4; i++)
+    {
+      static const enum pivot_border map[4] =
+        {
+          PIVOT_BORDER_DIM_ROW_HORZ,
+          PIVOT_BORDER_DIM_ROW_VERT,
+          PIVOT_BORDER_CAT_ROW_HORZ,
+          PIVOT_BORDER_CAT_ROW_VERT,
+        };
+      tlo_decode_border (in->ss->sep1[i], &out->borders[map[i]]);
+    }
+
+  for (int i = 0; i < 4; i++)
+    {
+      static const enum pivot_border map[4] =
+        {
+          PIVOT_BORDER_DIM_COL_HORZ,
+          PIVOT_BORDER_DIM_COL_VERT,
+          PIVOT_BORDER_CAT_COL_HORZ,
+          PIVOT_BORDER_CAT_COL_VERT,
+        };
+      tlo_decode_border (in->ss->sep2[i], &out->borders[map[i]]);
+    }
+
+  if (in->v2_styles)
+    for (int i = 0; i < 11; i++)
+      {
+        static const enum pivot_border map[11] =
+          {
+            PIVOT_BORDER_TITLE,
+            PIVOT_BORDER_INNER_LEFT,
+            PIVOT_BORDER_INNER_RIGHT,
+            PIVOT_BORDER_INNER_TOP,
+            PIVOT_BORDER_INNER_BOTTOM,
+            PIVOT_BORDER_OUTER_LEFT,
+            PIVOT_BORDER_OUTER_RIGHT,
+            PIVOT_BORDER_OUTER_TOP,
+            PIVOT_BORDER_OUTER_BOTTOM,
+            PIVOT_BORDER_DATA_LEFT,
+            PIVOT_BORDER_DATA_TOP,
+          };
+        tlo_decode_border (in->v2_styles->sep3[i], &out->borders[map[i]]);
+      }
+  else
+    {
+      out->borders[PIVOT_BORDER_TITLE].stroke = TABLE_STROKE_NONE;
+      out->borders[PIVOT_BORDER_INNER_LEFT].stroke = TABLE_STROKE_SOLID;
+      out->borders[PIVOT_BORDER_INNER_TOP].stroke = TABLE_STROKE_SOLID;
+      out->borders[PIVOT_BORDER_INNER_RIGHT].stroke = TABLE_STROKE_SOLID;
+      out->borders[PIVOT_BORDER_INNER_BOTTOM].stroke = TABLE_STROKE_SOLID;
+      out->borders[PIVOT_BORDER_OUTER_LEFT].stroke = TABLE_STROKE_NONE;
+      out->borders[PIVOT_BORDER_OUTER_TOP].stroke = TABLE_STROKE_NONE;
+      out->borders[PIVOT_BORDER_OUTER_RIGHT].stroke = TABLE_STROKE_NONE;
+      out->borders[PIVOT_BORDER_OUTER_BOTTOM].stroke = TABLE_STROKE_NONE;
+      out->borders[PIVOT_BORDER_DATA_LEFT].stroke = TABLE_STROKE_NONE;
+      out->borders[PIVOT_BORDER_DATA_TOP].stroke = TABLE_STROKE_NONE;
+    }
+
+  tlo_decode_area (in->cs->title_color, in->ts->title_style,
+                   &out->areas[PIVOT_AREA_TITLE]);
+  for (int i = 0; i < 7; i++)
+    {
+      static const enum pivot_area map[7] = {
+        PIVOT_AREA_LAYERS,
+        PIVOT_AREA_CORNER,
+        PIVOT_AREA_ROW_LABELS,
+        PIVOT_AREA_COLUMN_LABELS,
+        PIVOT_AREA_DATA,
+        PIVOT_AREA_CAPTION,
+        PIVOT_AREA_FOOTER
+      };
+      tlo_decode_area (in->ts->most_areas[i]->color,
+                       in->ts->most_areas[i]->style,
+                       &out->areas[map[i]]);
+    }
+
+  out->print_all_layers = flags & 0x08;
+  out->paginate_layers = flags & 0x40;
+  out->shrink_to_fit[TABLE_HORZ] = flags & 0x10;
+  out->shrink_to_fit[TABLE_VERT] = flags & 0x20;
+  out->top_continuation = flags & 0x80;
+  out->bottom_continuation = flags & 0x100;
+  /* n_orphan_lines isn't in .tlo files AFAICT. */
+
+  return out;
+}
+\f
 char * WARN_UNUSED_RESULT
 spv_table_look_read (const char *filename, struct pivot_table_look **outp)
 {
@@ -291,23 +503,42 @@ spv_table_look_read (const char *filename, struct pivot_table_look **outp)
     return xasprintf ("%s: failed to read file (%s)",
                       filename, strerror (errno));
 
-  xmlDoc *doc = xmlReadMemory (file, length, NULL, NULL, XML_PARSE_NOBLANKS);
-  free (file);
-  if (!doc)
-    return xasprintf ("%s: failed to parse XML", filename);
+  if ((uint8_t) file[0] == 0xff)
+    {
+      struct spvbin_input input;
+      spvbin_input_init (&input, file, length);
+
+      struct tlo_table_look *look;
+      char *error = NULL;
+      if (!tlo_parse_table_look (&input, &look))
+        error = spvbin_input_to_error (&input, NULL);
+      else
+        {
+          *outp = tlo_decode (look);
+          tlo_free_table_look (look);
+        }
+      return error;
+    }
+  else
+    {
+      xmlDoc *doc = xmlReadMemory (file, length, NULL, NULL, XML_PARSE_NOBLANKS);
+      free (file);
+      if (!doc)
+        return xasprintf ("%s: failed to parse XML", filename);
 
-  struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
-  struct spvsx_table_properties *tp;
-  spvsx_parse_table_properties (&ctx, xmlDocGetRootElement (doc), &tp);
-  char *error = spvxml_context_finish (&ctx, &tp->node_);
+      struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
+      struct spvsx_table_properties *tp;
+      spvsx_parse_table_properties (&ctx, xmlDocGetRootElement (doc), &tp);
+      char *error = spvxml_context_finish (&ctx, &tp->node_);
 
-  if (!error)
-    error = spv_table_look_decode (tp, outp);
+      if (!error)
+        error = spv_table_look_decode (tp, outp);
 
-  spvsx_free_table_properties (tp);
-  xmlFreeDoc (doc);
+      spvsx_free_table_properties (tp);
+      xmlFreeDoc (doc);
 
-  return error;
+      return error;
+    }
 }
 
 static void
diff --git a/src/output/spv/tlo.grammar b/src/output/spv/tlo.grammar
new file mode 100644 (file)
index 0000000..3f939f7
--- /dev/null
@@ -0,0 +1,81 @@
+# PSPP - a program for statistical analysis.
+# Copyright (C) 2017, 2018, 2019 Free Software Foundation, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+TableLook =>
+   PTTableLook[tl]
+   PVSeparatorStyle[ss]
+   PVCellStyle[cs]
+   PVTextStyle[ts]
+   V2Styles?
+
+PTTableLook =>
+   ff ff 00 00 "PTTableLook" (00|02)[version]
+   int16[flags]
+   00 00
+   bool[nested-row-labels] 00
+   bool[footnote-marker-subscripts] 00
+   i54 i18
+
+PVSeparatorStyle =>
+   ff ff 00 00 "PVSeparatorStyle" 00
+   Separator*4[sep1]
+   03 80 00
+   Separator*4[sep2]
+
+Separator =>
+   case(
+       00 (00)
+     | 01 (00) int32[color] int16[style] int16[width]
+   )[type]
+
+PVCellStyle =>
+   ff ff 00 00 "PVCellStyle"
+   AreaColor[title-color]
+
+PVTextStyle =>
+   ff ff 00 00 "PVTextStyle" 00
+   AreaStyle[title-style] MostAreas*7[most-areas]
+
+MostAreas =>
+   06 80
+   AreaColor[color] 08 80 00 AreaStyle[style]
+
+AreaColor =>
+   00 01 00 int32[color10] int32[color0] byte[shading] 00
+
+AreaStyle =>
+   int16[valign] int16[halign] int16[decimal-offset]
+   int16[left-margin] int16[right-margin] int16[top-margin] int16[bottom-margin]
+   00 00 01 00
+   int32[font-size] int16[stretch]
+   00*2
+   int32[rotation-angle]
+   00*4
+   int16[weight]
+   00*2
+   bool[italic] bool[underline] bool[strikethrough]
+   int32[rtf-charset-number]
+   22
+   byte[font-name-len] byte*[font-name-len][font-name]
+   int32[text-color]
+   00*2
+
+V2Styles =>
+   Separator*11[sep3]
+   byte[continuation-len] byte*[continuation-len][continuation]
+   int32[min-col-width] int32[max-col-width]
+   int32[min-row-height] int32[max-row-height]
+
index 252fec9a005a9edbf4634a7272088896a310b939..941c6684f7bfe14a296a5da9624fdb38bc4c687d 100644 (file)
@@ -18,6 +18,8 @@ pspp\-output \- convert and operate on SPSS viewer (SPV) files
 .br
 \fBpspp\-output \fR[\fIoptions\fR] \fBget\-table\-look\fR \fIsource destination\fR
 .br
+\fBpspp\-output \fR[\fIoptions\fR] \fBconvert\-table\-look\fR \fIsource destination\fR
+.br
 \fBpspp\-output \-\-help\fR | \fB\-h\fR
 .br
 \fBpspp\-output \-\-version\fR | \fB\-v\fR
@@ -84,7 +86,8 @@ the destination is not written.  These option make \fBpspp\-output\fR
 write the output as best it can, even with errors.
 .IP \fB\-\-table\-look=\fIfile\fR
 Reads a table style from \fIfile\fR and applies it to all of the
-output tables.  The file should a TableLook \fB.stt\fR file.
+output tables.  The file should a TableLook \fB.stt\fR or \fB.tlo\fR
+file.
 .SS The \fBget\-table\-look\fR command
 When invoked as \fBpspp\-output get\-table\-look \fIsource
 destination\fR, \fBpspp\-output\fR reads SPV file \fIsource\fR,
@@ -97,6 +100,13 @@ TableLook XML format.
 The user may use the TableLook file to change the style of tables in
 other files, by passing it to the \fB\-\-table\-look\fR option on the
 \fBconvert\fR command.
+.SS The \fBconvert\-table\-look\fR command
+When invoked as \fBpspp\-output convert\-table\-look \fIsource
+destination\fR, \fBpspp\-output\fR reads \fB.stt\fR or \fR.tlo\fR file
+\fIsource\fR, and writes it back to \fIdestination\fR (typically with
+an \fB.stt\fR extension) in the TableLook XML format.  This is useful
+for converting a TableLook \fB.tlo\fR file from SPSS 15 or earlier
+into the newer \fB.stt\fR format.
 .SS "Input Selection Options"
 The \fBdir\fR and \fBconvert\fR commands, by default, operate on all
 of the objects in the source SPV file, except for objects that are not
index 256a6e257519d517b17e8a502445387082c8fe8f..b3b4b57fa0fe42d2f9c88cebd462d8773a8bc85b 100644 (file)
@@ -362,6 +362,22 @@ run_get_table_look (int argc UNUSED, char **argv)
   spv_close (spv);
 }
 
+static void
+run_convert_table_look (int argc UNUSED, char **argv)
+{
+  struct pivot_table_look *look;
+  char *err = spv_table_look_read (argv[1], &look);
+  if (err)
+    error (1, 0, "%s", err);
+
+  err = spv_table_look_write (argv[2], look);
+  if (err)
+    error (1, 0, "%s", err);
+
+  pivot_table_look_uninit (look);
+  free (look);
+}
+
 static void
 run_dump (int argc UNUSED, char **argv)
 {
@@ -710,6 +726,7 @@ static const struct command commands[] =
     { "dir", 1, 1, run_directory },
     { "convert", 2, 2, run_convert },
     { "get-table-look", 2, 2, run_get_table_look },
+    { "convert-table-look", 2, 2, run_convert_table_look },
 
     /* Undocumented commands. */
     { "dump", 1, 1, run_dump },
@@ -1101,6 +1118,7 @@ The following commands are available:\n\
   dir FILE               List tables and other items in FILE.\n\
   convert SOURCE DEST    Convert .spv SOURCE to DEST.\n\
   get-table-look SOURCE DEST  Copies first selected TableLook into DEST\n\
+  convert-table-look SOURCE DEST  Copies .tlo or .stt SOURCE into DEST\n\
 \n\
 Input selection options for \"dir\" and \"convert\":\n\
   --select=CLASS...   include only some kinds of objects\n\