+ close_text_record (text);
+}
+
+static bool
+read_attributes (struct sfm_reader *r, struct text_record *text,
+ const char *variable)
+{
+ const char *key;
+ int index;
+
+ for (;;)
+ {
+ key = text_tokenize (text, '(');
+ if (key == NULL)
+ return true;
+
+ for (index = 1; ; index++)
+ {
+ /* Parse the value. */
+ const char *value = text_tokenize (text, '\n');
+ if (value == NULL)
+ {
+ sys_warn (r, _("%s: Error parsing attribute value %s[%d]"),
+ variable, key, index);
+ return false;
+ }
+ if (strlen (value) < 2
+ || value[0] != '\'' || value[strlen (value) - 1] != '\'')
+ sys_warn (r, _("%s: Attribute value %s[%d] is not quoted: %s"),
+ variable, key, index, value);
+ else
+ printf ("\t%s: %s[%d] = \"%.*s\"\n",
+ variable, key, index, (int) strlen (value) - 2, value + 1);
+
+ /* Was this the last value for this attribute? */
+ if (text_match (text, ')'))
+ break;
+ }
+
+ if (text_match (text, '/'))
+ return true;
+ }
+}
+
+/* Read extended number of cases record. */
+static void
+read_ncases64 (struct sfm_reader *r, size_t size, size_t count)
+{
+ int64_t unknown, ncases64;
+
+ if (size != 8)
+ {
+ sys_warn (r, _("Bad size %zu for extended number of cases."), size);
+ skip_bytes (r, size * count);
+ return;
+ }
+ if (count != 2)
+ {
+ sys_warn (r, _("Bad count %zu for extended number of cases."), size);
+ skip_bytes (r, size * count);
+ return;
+ }
+ unknown = read_int64 (r);
+ ncases64 = read_int64 (r);
+ printf ("%08llx: extended number of cases: "
+ "unknown=%"PRId64", ncases64=%"PRId64"\n",
+ (long long int) ftello (r->file), unknown, ncases64);
+}
+
+static void
+read_datafile_attributes (struct sfm_reader *r, size_t size, size_t count)
+{
+ struct text_record *text;
+
+ printf ("%08llx: datafile attributes\n", (long long int) ftello (r->file));
+ text = open_text_record (r, size * count);
+ read_attributes (r, text, "datafile");
+ close_text_record (text);
+}
+
+static void
+read_character_encoding (struct sfm_reader *r, size_t size, size_t count)
+{
+ long long int posn = ftello (r->file);
+ char *encoding = xcalloc (size, count + 1);
+ read_string (r, encoding, count + 1);
+
+ printf ("%08llx: Character Encoding: %s\n", posn, encoding);
+}
+
+static void
+read_long_string_value_labels (struct sfm_reader *r, size_t size, size_t count)
+{
+ long long int start = ftello (r->file);
+
+ printf ("%08llx: long string value labels\n", start);
+ while (ftello (r->file) - start < size * count)
+ {
+ long long posn = ftello (r->file);
+ char var_name[VAR_NAME_LEN + 1];
+ int var_name_len;
+ int n_values;
+ int width;
+ int i;
+
+ /* Read variable name. */
+ var_name_len = read_int (r);
+ if (var_name_len > VAR_NAME_LEN)
+ sys_error (r, _("Variable name length in long string value label "
+ "record (%d) exceeds %d-byte limit."),
+ var_name_len, VAR_NAME_LEN);
+ read_string (r, var_name, var_name_len + 1);
+
+ /* Read width, number of values. */
+ width = read_int (r);
+ n_values = read_int (r);
+
+ printf ("\t%08llx: %s, width %d, %d values\n",
+ posn, var_name, width, n_values);
+
+ /* Read values. */
+ for (i = 0; i < n_values; i++)
+ {
+ char *value;
+ int value_length;
+
+ char *label;
+ int label_length;
+
+ posn = ftello (r->file);
+
+ /* Read value. */
+ value_length = read_int (r);
+ value = xmalloc (value_length + 1);
+ read_string (r, value, value_length + 1);
+
+ /* Read label. */
+ label_length = read_int (r);
+ label = xmalloc (label_length + 1);
+ read_string (r, label, label_length + 1);
+
+ printf ("\t\t%08llx: \"%s\" (%d bytes) => \"%s\" (%d bytes)\n",
+ posn, value, value_length, label, label_length);
+
+ free (value);
+ free (label);
+ }
+ }
+}
+
+static void
+hex_dump (size_t offset, const void *buffer_, size_t buffer_size)
+{
+ const uint8_t *buffer = buffer_;
+
+ while (buffer_size > 0)
+ {
+ size_t n = MIN (buffer_size, 16);
+ size_t i;
+
+ printf ("%04zx", offset);
+ for (i = 0; i < 16; i++)
+ {
+ if (i < n)
+ printf ("%c%02x", i == 8 ? '-' : ' ', buffer[i]);
+ else
+ printf (" ");
+ }
+
+ printf (" |");
+ for (i = 0; i < 16; i++)
+ {
+ unsigned char c = i < n ? buffer[i] : ' ';
+ putchar (isprint (c) ? c : '.');
+ }
+ printf ("|\n");
+
+ offset += n;
+ buffer += n;
+ buffer_size -= n;
+ }
+}
+
+/* Reads and prints any type 7 record that we don't understand. */
+static void
+read_unknown_extension (struct sfm_reader *r, size_t size, size_t count)
+{
+ unsigned char *buffer;
+ size_t i;
+
+ if (size == 0 || count > 65536 / size)
+ skip_bytes (r, size * count);
+ else if (size != 1)
+ {
+ buffer = xmalloc (size);
+ for (i = 0; i < count; i++)
+ {
+ read_bytes (r, buffer, size);
+ hex_dump (i * size, buffer, size);
+ }
+ free (buffer);
+ }
+ else
+ {
+ buffer = xmalloc (count);
+ read_bytes (r, buffer, count);
+ if (memchr (buffer, 0, count) == 0)
+ for (i = 0; i < count; i++)
+ {
+ unsigned char c = buffer[i];
+
+ if (c == '\\')
+ printf ("\\\\");
+ else if (c == '\n' || isprint (c))
+ putchar (c);
+ else
+ printf ("\\%02x", c);
+ }
+ else
+ hex_dump (0, buffer, count);
+ free (buffer);
+ }
+}
+
+static void
+read_variable_attributes (struct sfm_reader *r, size_t size, size_t count)
+{
+ struct text_record *text;
+
+ printf ("%08llx: variable attributes\n", (long long int) ftello (r->file));
+ text = open_text_record (r, size * count);
+ for (;;)
+ {
+ const char *variable = text_tokenize (text, ':');
+ if (variable == NULL || !read_attributes (r, text, variable))
+ break;
+ }
+ close_text_record (text);
+}
+
+static void
+read_compressed_data (struct sfm_reader *r)
+{
+ enum { N_OPCODES = 8 };
+ uint8_t opcodes[N_OPCODES];
+ long long int opcode_ofs;
+ int opcode_idx;
+ int case_num;
+ int i;
+
+ read_int (r);
+ printf ("\n%08llx: compressed data:\n", (long long int) ftello (r->file));
+
+ opcode_idx = N_OPCODES;
+ opcode_ofs = 0;
+ case_num = 0;
+ for (case_num = 0; ; case_num++)
+ {
+ printf ("%08llx: case %d's uncompressible data begins\n",
+ (long long int) ftello (r->file), case_num);
+ for (i = 0; i < r->n_var_widths; )
+ {
+ int width = r->var_widths[i];
+ char raw_value[8];
+ int opcode;
+
+ if (opcode_idx >= N_OPCODES)
+ {
+ opcode_ofs = ftello (r->file);
+ if (i == 0)
+ {
+ if (!try_read_bytes (r, opcodes, 8))
+ return;
+ }
+ else
+ read_bytes (r, opcodes, 8);
+ opcode_idx = 0;
+ }
+ opcode = opcodes[opcode_idx];
+ printf ("%08llx: variable %d: opcode %d: ",
+ opcode_ofs + opcode_idx, i, opcode);
+
+ switch (opcode)
+ {
+ default:
+ printf ("%g", opcode - r->bias);
+ if (width != 0)
+ printf (", but this is a string variable (width=%d)", width);
+ printf ("\n");
+ i++;
+ break;
+
+ case 0:
+ printf ("ignored padding\n");
+ break;
+
+ case 252:
+ printf ("end of data\n");
+ return;
+
+ case 253:
+ read_bytes (r, raw_value, 8);
+ printf ("uncompressible data: ");
+ print_untyped_value (r, raw_value);
+ printf ("\n");
+ i++;
+ break;
+
+ case 254:
+ printf ("spaces");
+ if (width == 0)
+ printf (", but this is a numeric variable");
+ printf ("\n");
+ i++;
+ break;
+
+ case 255:
+ printf ("SYSMIS");
+ if (width != 0)
+ printf (", but this is a string variable (width=%d)", width);
+ printf ("\n");
+ i++;
+ break;
+ }
+
+ opcode_idx++;
+ }
+ }