all files in submissions/spv parse
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 18 Oct 2025 14:41:16 +0000 (07:41 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 18 Oct 2025 14:59:18 +0000 (07:59 -0700)
rust/doc/src/spv/legacy-detail-binary.md
rust/doc/src/spv/legacy-detail-xml.md
rust/pspp/src/output/spv.rs
rust/pspp/src/output/spv/legacy.rs [deleted file]
rust/pspp/src/output/spv/legacy_xml.rs [new file with mode: 0644]

index e5edf4227a917710c85a886d3b8eee13225711d5..b8812b4f223c60b7e012a8d67c9e138e36526000 100644 (file)
@@ -31,13 +31,14 @@ of the other data in the member.  Versions 0xaf and 0xb0 are known.  We
 will refer to "version 0xaf" and "version 0xb0" members later on.
 
 A legacy member consists of `n-sources` data sources, each of which
-has Metadata and Data.
+has `Metadata` and `Data`.
 
 `member-size` is the size of the legacy binary member, in bytes.
 
-The Data and Strings above are commented out because the Metadata has
-some oddities that mean that the Data sometimes seems to start at an
-unexpected place.  The following section goes into detail.
+The `Data` and `Strings` above are commented out because the
+`Metadata` has some oddities that mean that the `Data` sometimes seems
+to start at an unexpected place.  The following section goes into
+detail.
 
 <!-- toc -->
 
@@ -57,21 +58,21 @@ values.
 0-bytes.  The names that appear in the corpus are very generic: usually
 `tableData` for pivot table data or `source0` for chart data.
 
-A given Metadata's `data-offset` is the offset, in bytes, from the
-beginning of the member to the start of the corresponding Data.  This
-allows programs to skip to the beginning of the data for a particular
-source.  In every case in the corpus, the Data follow the Metadata in
-the same order, but it is important to use `data-offset` instead of
-reading sequentially through the file because of the exception described
-below.
-
-One SPV file in the corpus has legacy binary members with version
-0xb0 but a 28-byte `source-name` field (and only a single source).  In
-practice, this means that the 64-byte `source-name` used in version 0xb0
-has a lot of 0-bytes in the middle followed by the `variable-name` of
-the following Data.  As long as a reader treats the first 0-byte in the
-`source-name` as terminating the string, it can properly interpret these
-members.
+A given `Metadata`'s `data-offset` is the offset, in bytes, from the
+beginning of the member to the start of the corresponding `Data`.
+This allows programs to skip to the beginning of the data for a
+particular source.  In every case in the corpus, the `Data` follow the
+`Metadata` in the same order, but it is important to use `data-offset`
+instead of reading sequentially through the file because of the
+exception described below.
+
+One SPV file in the corpus has legacy binary members with version 0xb0
+but a 28-byte `source-name` field (and only a single source).  In
+practice, this means that the 64-byte `source-name` used in version
+0xb0 has a lot of 0-bytes in the middle followed by the
+`variable-name` of the following `Data`.  As long as a reader treats
+the first 0-byte in the `source-name` as terminating the string, it
+can properly interpret these members.
 
 The meaning of `x` in version 0xb0 is unknown.
 
@@ -82,14 +83,14 @@ Data => Variable*[n-variables]
 Variable => byte*288[variable-name] double*[n-values]
 ```
 
-Data follow the `Metadata` in the legacy binary format, with sources
+`Data` follow the `Metadata` in the legacy binary format, with sources
 in the same order (but readers should use the `data-offset` in
-`Metadata` records, rather than reading sequentially).  Each Variable
-begins with a `variable-name` that generally indicates its role in the
-pivot table, e.g. "cell", "cellFormat", "dimension0categories",
-"dimension0group0", followed by the numeric data, one double per
-datum.  A double with the maximum negative double `-DBL_MAX`
-represents the system-missing value `SYSMIS`.
+`Metadata` records, rather than reading sequentially).  Each
+`Variable` begins with a `variable-name` that generally indicates its
+role in the pivot table, e.g. `cell`, `cellFormat`,
+`dimension0categories`, `dimension0group0`, followed by the numeric
+data, one double per datum.  A double with the maximum negative double
+`-DBL_MAX` represents the system-missing value `SYSMIS`.
 
 ## String Data
 
@@ -108,10 +109,10 @@ Label => int32[frequency] string[label]
 
 Each variable may include a mix of numeric and string data values.
 If a legacy binary member contains any string data, `Strings` is present;
-otherwise, it ends just after the last Data element.
+otherwise, it ends just after the last `Data` element.
 
 The string data overlays the numeric data.  When a variable includes
-any string data, its Variable represents the string values with a
+any string data, its `Variable` represents the string values with a
 `SYSMIS` or NaN placeholder.  (Not all such values need be
 placeholders.)
 
index 8d5edf3f87184d310fd38edc5b746ba4292396ab..a8871120f0699b97904e3d53d831410882e9007e 100644 (file)
@@ -568,8 +568,7 @@ Each `layer` element represents a dimension, e.g.:
 ## The `facetLayout` Element
 
 ```
-facetLayout => tableLayout setCellProperties[scp1]*
-               facetLevel+ setCellProperties[scp2]*
+facetLayout => tableLayout (setCellProperties | facetLevel)+
 
 tableLayout
    :verticalTitlesInCorner=bool
index a20ca1eabc0f99eb5a5aeb7526521dc7fef47581..1e9d6d377c97cac9ac6b55e19d06228a4d539c20 100644 (file)
@@ -32,14 +32,14 @@ use crate::output::{
     page::PageSetup,
     pivot::{PivotTable, TableProperties, Value},
     spv::{
-        legacy::Visualization,
+        legacy_xml::Visualization,
         light::{LightError, LightTable},
     },
 };
 
 mod css;
 pub mod html;
-mod legacy;
+mod legacy_xml;
 mod light;
 
 #[derive(Debug, Display, thiserror::Error)]
@@ -428,7 +428,7 @@ impl Table {
                     Ok(result) => result,
                     Err(error) => panic!("{error:?}"),
                 };
-                dbg!(_visualization);
+                //dbg!(_visualization);
                 Ok(PivotTable::new([]).into_item())
             }
         }
diff --git a/rust/pspp/src/output/spv/legacy.rs b/rust/pspp/src/output/spv/legacy.rs
deleted file mode 100644 (file)
index f6fab20..0000000
+++ /dev/null
@@ -1,1153 +0,0 @@
-// PSPP - a program for statistical analysis.
-// Copyright (C) 2025 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/>.
-
-use std::marker::PhantomData;
-
-use serde::Deserialize;
-
-use crate::output::pivot::Color;
-
-#[derive(Debug)]
-struct Ref<T> {
-    references: String,
-    _phantom: PhantomData<T>,
-}
-
-impl<'de, T> Deserialize<'de> for Ref<T> {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        Ok(Self {
-            references: String::deserialize(deserializer)?,
-            _phantom: PhantomData,
-        })
-    }
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-pub struct Visualization {
-    /// In format `YYYY-MM-DD`.
-    #[serde(rename = "@date")]
-    date: String,
-    // Locale used for output, e.g. `en-US`.
-    #[serde(rename = "@lang")]
-    lang: String,
-    /// Localized title of the pivot table.
-    #[serde(rename = "@name")]
-    name: String,
-    /// Base style for the pivot table.
-    #[serde(rename = "@style")]
-    style: Ref<Style>,
-
-    extension: Option<VisualizationExtension>,
-    user_source: UserSource,
-    #[serde(rename = "$value")]
-    variables: Vec<Variable>,
-    categorical_domain: Option<CategoricalDomain>,
-    graph: Graph,
-    #[serde(default, rename = "labelFrame")]
-    label_frames: Vec<LabelFrame>,
-    container: Option<Container>,
-    #[serde(rename = "style")]
-    styles: Vec<Style>,
-    layer_controller: Option<LayerController>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename = "extension", rename_all = "camelCase")]
-struct VisualizationExtension;
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum Variable {
-    SourceVariable(SourceVariable),
-    DerivedVariable(DerivedVariable),
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct SourceVariable {
-    #[serde(rename = "@id")]
-    id: String,
-
-    /// The name of a variable within the source, corresponding to the
-    /// `variable-name` in the `tableData.bin` member.
-    #[serde(rename = "@sourceName")]
-    source_name: String,
-
-    /// Variable label, if any.
-    #[serde(rename = "@label")]
-    label: Option<String>,
-
-    /// A variable whose string values correspond one-to-one with the values of
-    /// this variable and are suitable as value labels.
-    #[serde(rename = "@labelVariable")]
-    label_variable: Option<Ref<SourceVariable>>,
-
-    #[serde(default, rename = "extension")]
-    extensions: Vec<VariableExtension>,
-    format: Option<Format>,
-    string_format: Option<StringFormat>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct DerivedVariable {
-    #[serde(rename = "@id")]
-    id: String,
-
-    /// An expression that defines the variable's value.
-    #[serde(rename = "@value")]
-    value: String,
-    #[serde(default, rename = "extension")]
-    extensions: Vec<VariableExtension>,
-    format: Option<Format>,
-    string_format: Option<StringFormat>,
-    #[serde(default, rename = "valueMapEntry")]
-    value_map: Vec<ValueMapEntry>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename = "extension", rename_all = "camelCase")]
-struct VariableExtension {
-    #[serde(rename = "@from")]
-    from: String,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct UserSource {
-    #[serde(rename = "@id")]
-    id: String,
-
-    #[serde(rename = "@missing")]
-    missing: Option<Missing>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct CategoricalDomain {
-    variable_reference: VariableReference,
-    simple_sort: SimpleSort,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct VariableReference {
-    #[serde(rename = "@ref")]
-    reference: Option<String>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct SimpleSort {
-    #[serde(rename = "@method")]
-    category_order: CategoryOrder,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct CategoryOrder {}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum Missing {
-    Listwise,
-    Pairwise,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct StringFormat {
-    #[serde(default, rename = "relabel")]
-    relabels: Vec<Relabel>,
-    #[serde(default, rename = "affix")]
-    affixes: Vec<Affix>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Format {
-    #[serde(rename = "@baseFormat")]
-    base_format: Option<BaseFormat>,
-    #[serde(rename = "@errorCharacter")]
-    error_character: Option<char>,
-    #[serde(rename = "@separatorChars")]
-    separator_chars: Option<String>,
-    #[serde(rename = "@mdyOrder")]
-    mdy_order: Option<MdyOrder>,
-    #[serde(rename = "@showYear")]
-    show_year: Option<bool>,
-    #[serde(rename = "@showQuarter")]
-    show_quarter: Option<bool>,
-    #[serde(rename = "@quarterPrefix")]
-    quarter_prefix: Option<String>,
-    #[serde(rename = "@quarterSuffix")]
-    quarter_suffix: Option<String>,
-    #[serde(rename = "@yearAbbreviation")]
-    year_abbreviation: Option<bool>,
-    #[serde(rename = "@showMonth")]
-    show_month: Option<bool>,
-    #[serde(rename = "@monthFormat")]
-    month_format: Option<MonthFormat>,
-    #[serde(rename = "@dayPadding")]
-    day_padding: Option<bool>,
-    #[serde(rename = "@dayOfMonthPadding")]
-    day_of_month_padding: Option<bool>,
-    #[serde(rename = "@showWeek")]
-    show_week: Option<bool>,
-    #[serde(rename = "@weekPadding")]
-    week_padding: Option<bool>,
-    #[serde(rename = "@weekSuffix")]
-    week_suffix: Option<String>,
-    #[serde(rename = "@showDayOfWeek")]
-    show_day_of_week: Option<bool>,
-    #[serde(rename = "@dayOfWeekAbbreviation")]
-    day_of_week_abbreviation: Option<bool>,
-    #[serde(rename = "hourPadding")]
-    hour_padding: Option<bool>,
-    #[serde(rename = "minutePadding")]
-    minute_padding: Option<bool>,
-    #[serde(rename = "secondPadding")]
-    second_padding: Option<bool>,
-    #[serde(rename = "@showDay")]
-    show_day: Option<bool>,
-    #[serde(rename = "@showHour")]
-    show_hour: Option<bool>,
-    #[serde(rename = "@showMinute")]
-    show_minute: Option<bool>,
-    #[serde(rename = "@showSecond")]
-    show_second: Option<bool>,
-    #[serde(rename = "@showMillis")]
-    show_millis: Option<bool>,
-    #[serde(rename = "@dayType")]
-    day_type: Option<DayType>,
-    #[serde(rename = "@hourFormat")]
-    hour_format: Option<HourFormat>,
-    #[serde(rename = "@minimumIntegerDigits")]
-    minimum_integer_digits: Option<usize>,
-    #[serde(rename = "@maximumFractionDigits")]
-    maximum_fraction_digits: Option<usize>,
-    #[serde(rename = "@minimumFractionDigits")]
-    minimum_fraction_digits: Option<usize>,
-    #[serde(rename = "@useGrouping")]
-    use_grouping: Option<bool>,
-    #[serde(rename = "@scientific")]
-    scientific: Option<Scientific>,
-    #[serde(rename = "@small")]
-    small: Option<f64>,
-    #[serde(default, rename = "@prefix")]
-    prefix: String,
-    #[serde(default, rename = "@suffix")]
-    suffix: String,
-    #[serde(rename = "@tryStringsAsNumbers")]
-    try_strings_as_numbers: Option<bool>,
-    #[serde(rename = "@negativesOutside")]
-    negatives_outside: Option<bool>,
-    #[serde(default)]
-    relabel: Vec<Relabel>,
-    #[serde(default, rename = "affix")]
-    affixes: Vec<Affix>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct NumberFormat {
-    #[serde(rename = "@minimumIntegerDigits")]
-    minimum_integer_digits: Option<usize>,
-    #[serde(rename = "@maximumFractionDigits")]
-    maximum_fraction_digits: Option<usize>,
-    #[serde(rename = "@minimumFractionDigits")]
-    minimum_fraction_digits: Option<usize>,
-    #[serde(rename = "@useGrouping")]
-    use_grouping: Option<bool>,
-    #[serde(rename = "@scientific")]
-    scientific: Option<Scientific>,
-    #[serde(rename = "@small")]
-    small: Option<f64>,
-    #[serde(default, rename = "@prefix")]
-    prefix: String,
-    #[serde(default, rename = "@suffix")]
-    suffix: String,
-    #[serde(default, rename = "affix")]
-    affixes: Vec<Affix>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct DateTimeFormat {
-    #[serde(rename = "@baseFormat")]
-    base_format: Option<BaseFormat>,
-    #[serde(rename = "@separatorChars")]
-    separator_chars: Option<String>,
-    #[serde(rename = "@mdyOrder")]
-    mdy_order: Option<MdyOrder>,
-    #[serde(rename = "@showYear")]
-    show_year: Option<bool>,
-    #[serde(rename = "@showQuarter")]
-    show_quarter: Option<bool>,
-    #[serde(rename = "@quarterPrefix")]
-    quarter_prefix: Option<String>,
-    #[serde(rename = "@quarterSuffix")]
-    quarter_suffix: Option<String>,
-    #[serde(rename = "@yearAbbreviation")]
-    year_abbreviation: Option<bool>,
-    #[serde(rename = "@showMonth")]
-    show_month: Option<bool>,
-    #[serde(rename = "@monthFormat")]
-    month_format: Option<MonthFormat>,
-    #[serde(rename = "@dayPadding")]
-    day_padding: Option<bool>,
-    #[serde(rename = "@dayOfMonthPadding")]
-    day_of_month_padding: Option<bool>,
-    #[serde(rename = "@showWeek")]
-    show_week: Option<bool>,
-    #[serde(rename = "@weekPadding")]
-    week_padding: Option<bool>,
-    #[serde(rename = "@weekSuffix")]
-    week_suffix: Option<String>,
-    #[serde(rename = "@showDayOfWeek")]
-    show_day_of_week: Option<bool>,
-    #[serde(rename = "@dayOfWeekAbbreviation")]
-    day_of_week_abbreviation: Option<bool>,
-    #[serde(rename = "hourPadding")]
-    hour_padding: Option<bool>,
-    #[serde(rename = "minutePadding")]
-    minute_padding: Option<bool>,
-    #[serde(rename = "secondPadding")]
-    second_padding: Option<bool>,
-    #[serde(rename = "@showDay")]
-    show_day: Option<bool>,
-    #[serde(rename = "@showHour")]
-    show_hour: Option<bool>,
-    #[serde(rename = "@showMinute")]
-    show_minute: Option<bool>,
-    #[serde(rename = "@showSecond")]
-    show_second: Option<bool>,
-    #[serde(rename = "@showMillis")]
-    show_millis: Option<bool>,
-    #[serde(rename = "@dayType")]
-    day_type: Option<DayType>,
-    #[serde(rename = "@hourFormat")]
-    hour_format: Option<HourFormat>,
-    #[serde(default, rename = "affix")]
-    affixes: Vec<Affix>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct ElapsedTimeFormat {
-    #[serde(rename = "@baseFormat")]
-    base_format: Option<BaseFormat>,
-    #[serde(rename = "@dayPadding")]
-    day_padding: Option<bool>,
-    #[serde(rename = "hourPadding")]
-    hour_padding: Option<bool>,
-    #[serde(rename = "minutePadding")]
-    minute_padding: Option<bool>,
-    #[serde(rename = "secondPadding")]
-    second_padding: Option<bool>,
-    #[serde(rename = "@showDay")]
-    show_day: Option<bool>,
-    #[serde(rename = "@showHour")]
-    show_hour: Option<bool>,
-    #[serde(rename = "@showMinute")]
-    show_minute: Option<bool>,
-    #[serde(rename = "@showSecond")]
-    show_second: Option<bool>,
-    #[serde(rename = "@showMillis")]
-    show_millis: Option<bool>,
-    #[serde(rename = "@showYear")]
-    show_year: Option<bool>,
-    #[serde(default, rename = "affix")]
-    affixes: Vec<Affix>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum BaseFormat {
-    Date,
-    Time,
-    DateTime,
-    ElapsedTime,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum MdyOrder {
-    DayMonthYear,
-    MonthDayYear,
-    YearMonthDay,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum MonthFormat {
-    Long,
-    Short,
-    Number,
-    PaddedNumber,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum DayType {
-    Month,
-    Year,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum HourFormat {
-    #[serde(rename = "AMPM")]
-    AmPm,
-    #[serde(rename = "AS_24")]
-    As24,
-    #[serde(rename = "AS_12")]
-    As12,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum Scientific {
-    OnlyForSmall,
-    WhenNeeded,
-    True,
-    False,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Affix {
-    /// The footnote number as a natural number: 1 for the first footnote, 2 for
-    /// the second, and so on.
-    #[serde(rename = "@definesReference")]
-    defines_reference: u64,
-
-    /// Position for the footnote label.
-    #[serde(rename = "@position")]
-    position: Position,
-
-    /// Whether the affix is a suffix (true) or a prefix (false).
-    #[serde(rename = "@suffix")]
-    suffix: bool,
-
-    /// The text of the suffix or prefix. Typically a letter, e.g. `a` for
-    /// footnote 1, `b` for footnote 2, ...
-    #[serde(rename = "@value")]
-    value: String,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum Position {
-    Subscript,
-    Superscript,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Relabel {
-    #[serde(rename = "@from")]
-    from: f64,
-    #[serde(rename = "@to")]
-    to: String,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct ValueMapEntry {
-    #[serde(rename = "@from")]
-    from: String,
-    #[serde(rename = "@to")]
-    to: String,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Style {
-    #[serde(rename = "@id")]
-    id: Option<String>,
-
-    /// The text color or, in some cases, background color.
-    #[serde(rename = "@color")]
-    color: Option<Color>,
-
-    /// Not used.
-    #[serde(rename = "@color2")]
-    color2: Option<Color>,
-
-    /// Normally 0. The value -90 causes inner column or outer row labels to be
-    /// rotated vertically.
-    #[serde(rename = "@labelAngle")]
-    label_angle: Option<f64>,
-
-    #[serde(rename = "@border-bottom")]
-    border_bottom: Option<Border>,
-
-    #[serde(rename = "@border-top")]
-    border_top: Option<Border>,
-
-    #[serde(rename = "@border-left")]
-    border_left: Option<Border>,
-
-    #[serde(rename = "@border-right")]
-    border_right: Option<Border>,
-
-    #[serde(rename = "@border-bottom-color")]
-    border_bottom_color: Option<Color>,
-
-    #[serde(rename = "@border-top-color")]
-    border_top_color: Option<Color>,
-
-    #[serde(rename = "@border-left-color")]
-    border_left_color: Option<Color>,
-
-    #[serde(rename = "@border-right-color")]
-    border_right_color: Option<Color>,
-
-    #[serde(rename = "@font-family")]
-    font_family: Option<String>,
-
-    #[serde(rename = "@font-size")]
-    font_size: Option<String>,
-
-    #[serde(rename = "@font-weight")]
-    font_weight: Option<FontWeight>,
-
-    #[serde(rename = "@font-style")]
-    font_style: Option<FontStyle>,
-
-    #[serde(rename = "@font-underline")]
-    font_underline: Option<FontUnderline>,
-
-    #[serde(rename = "@margin-bottom")]
-    margin_bottom: Option<String>,
-
-    #[serde(rename = "@margin-top")]
-    margin_top: Option<String>,
-
-    #[serde(rename = "@margin-left")]
-    margin_left: Option<String>,
-
-    #[serde(rename = "@margin-right")]
-    margin_right: Option<String>,
-
-    #[serde(rename = "@textAlignment")]
-    text_alignment: Option<TextAlignment>,
-
-    #[serde(rename = "@labelLocationHorizontal")]
-    label_location_horizontal: Option<LabelLocation>,
-
-    #[serde(rename = "@labelLocationVertical")]
-    label_location_vertical: Option<LabelLocation>,
-
-    #[serde(rename = "@size")]
-    size: Option<String>,
-
-    #[serde(rename = "@width")]
-    width: Option<String>,
-
-    #[serde(rename = "@visible")]
-    visible: Option<bool>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum Border {
-    Solid,
-    Thick,
-    Thin,
-    Double,
-    None,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum FontWeight {
-    Regular,
-    Bold,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum FontStyle {
-    Regular,
-    Italic,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum FontUnderline {
-    None,
-    Underline,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum TextAlignment {
-    Left,
-    Right,
-    Center,
-    Decimal,
-    Mixed,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum LabelLocation {
-    Positive,
-    Negative,
-    Center,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Graph {
-    #[serde(rename = "@cellStyle")]
-    cell_style: Ref<Style>,
-
-    #[serde(rename = "@style")]
-    style: Ref<Style>,
-
-    #[serde(rename = "location")]
-    locations: Vec<Location>,
-    coordinates: Coordinates,
-    faceting: Faceting,
-    facet_layout: FacetLayout,
-    interval: Interval,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Coordinates;
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Location {
-    /// The part of the table being located.
-    #[serde(rename = "@part")]
-    part: Part,
-
-    /// How the location is determined.
-    #[serde(rename = "@method")]
-    method: Method,
-
-    /// Minimum size.
-    #[serde(rename = "@min")]
-    min: Option<String>,
-
-    /// Maximum size.
-    #[serde(rename = "@max")]
-    max: Option<String>,
-
-    /// An element to attach to. Required when method is attach or same, not
-    /// observed otherwise.
-    #[serde(rename = "@target")]
-    target: Option<String>,
-
-    #[serde(rename = "@value")]
-    value: Option<String>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum Part {
-    Height,
-    Width,
-    Top,
-    Bottom,
-    Left,
-    Right,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum Method {
-    SizeToContent,
-    Attach,
-    Fixed,
-    Same,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Faceting {
-    #[serde(rename = "@id")]
-    id: Option<String>,
-
-    #[serde(default)]
-    layers1: Vec<Layer>,
-    cross: Cross,
-    #[serde(default)]
-    layers2: Vec<Layer>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Cross {
-    #[serde(rename = "$value")]
-    children: Vec<CrossChild>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum CrossChild {
-    /// No dimensions along this axis.
-    Unity,
-    /// Dimensions along this axis.
-    Nest(Nest),
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Nest {
-    #[serde(rename = "variableReference")]
-    variable_references: Vec<VariableReference>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Layer {
-    #[serde(rename = "@variable")]
-    variable: String,
-
-    #[serde(rename = "@value")]
-    value: String,
-
-    #[serde(rename = "@visible")]
-    visible: Option<bool>,
-
-    #[serde(rename = "@titleVisible")]
-    title_visible: Option<bool>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct FacetLayout {
-    table_layout: TableLayout,
-    #[serde(default)]
-    #[serde(rename = "setCellProperties")]
-    scp1: Vec<SetCellProperties>,
-    #[serde(rename = "facetLevel")]
-    facet_levels: Vec<FacetLevel>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct TableLayout {
-    #[serde(rename = "@verticalTitlesInCorner")]
-    vertical_titles_in_corner: bool,
-
-    #[serde(rename = "@style")]
-    style: Option<Ref<Style>>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct SetCellProperties {
-    #[serde(rename = "@id")]
-    id: Option<String>,
-
-    #[serde(rename = "@applyToConverse")]
-    apply_to_converse: Option<bool>,
-
-    #[serde(rename = "$value")]
-    sets: Vec<Set>,
-
-    #[serde(rename = "union")]
-    unions: Option<Union>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Union {
-    #[serde(rename = "intersect")]
-    intersects: Vec<Intersect>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Intersect {
-    #[serde(rename = "$value")]
-    child: Vec<IntersectChild>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum IntersectChild {
-    Where(Where),
-    IntersectWhere(IntersectWhere),
-    Alternating,
-    Empty,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Where {
-    #[serde(rename = "@variable")]
-    variable: String,
-    #[serde(rename = "@include")]
-    include: String,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct IntersectWhere {
-    #[serde(rename = "@variable")]
-    variable: String,
-
-    #[serde(rename = "@variable2")]
-    variable2: String,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum Set {
-    SetStyle(SetStyle),
-    SetFrameStyle(SetFrameStyle),
-    SetFormat(SetFormat),
-    SetMetaData(SetMetaData),
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct SetStyle {
-    #[serde(rename = "@target")]
-    target: String,
-
-    #[serde(rename = "@style")]
-    style: Ref<Style>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct SetMetaData {
-    #[serde(rename = "@target")]
-    target: Ref<Graph>,
-
-    #[serde(rename = "@key")]
-    key: String,
-
-    #[serde(rename = "@value")]
-    value: String,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct SetFormat {
-    #[serde(rename = "@target")]
-    target: String,
-
-    #[serde(rename = "@reset")]
-    reset: Option<bool>,
-
-    #[serde(rename = "$value")]
-    child: SetFormatChild,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum SetFormatChild {
-    Format(Format),
-    NumberFormat(NumberFormat),
-    StringFormat(Vec<StringFormat>),
-    DateTimeFormat(DateTimeFormat),
-    ElapsedTimeFormat(ElapsedTimeFormat),
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct SetFrameStyle {
-    #[serde(rename = "@style")]
-    style: Ref<Style>,
-
-    #[serde(rename = "@target")]
-    target: Ref<MajorTicks>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Interval {
-    #[serde(rename = "@id")]
-    id: Option<String>,
-
-    #[serde(rename = "@style")]
-    style: Ref<Style>,
-
-    labeling: Labeling,
-    footnotes: Option<Footnotes>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Labeling {
-    #[serde(rename = "@style")]
-    style: Option<Ref<Style>>,
-
-    #[serde(rename = "@variable")]
-    variable: String,
-
-    #[serde(default)]
-    children: Vec<LabelingChild>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum LabelingChild {
-    Formatting(Formatting),
-    Format(Format),
-    Footnotes(Footnotes),
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Formatting {
-    #[serde(rename = "@variable")]
-    variable: String,
-
-    mappings: Vec<FormatMapping>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct FormatMapping {
-    #[serde(rename = "@from")]
-    from: i64,
-
-    format: Option<Format>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Footnotes {
-    #[serde(rename = "@superscript")]
-    superscript: Option<bool>,
-
-    #[serde(rename = "@variable")]
-    variable: String,
-
-    mappings: Vec<FootnoteMapping>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct FootnoteMapping {
-    #[serde(rename = "@definesReference")]
-    defines_reference: i64,
-
-    #[serde(rename = "@from")]
-    from: i64,
-
-    #[serde(rename = "@to")]
-    to: String,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct FacetLevel {
-    #[serde(rename = "@id")]
-    id: Option<String>,
-
-    #[serde(rename = "@level")]
-    level: usize,
-
-    #[serde(rename = "@gap")]
-    gap: Option<String>,
-    //axis: Axis,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Axis {
-    #[serde(rename = "@style")]
-    style: Ref<Style>,
-
-    label: Option<Label>,
-    major_ticks: MajorTicks,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct MajorTicks {
-    #[serde(rename = "@id")]
-    id: String,
-
-    #[serde(rename = "@labelAngle")]
-    label_angle: f64,
-
-    #[serde(rename = "@length")]
-    length: String,
-
-    #[serde(rename = "@style")]
-    style: Ref<Style>,
-
-    #[serde(rename = "@tickFrameStyle")]
-    tick_frame_style: Ref<Style>,
-
-    #[serde(rename = "@labelFrequency")]
-    label_frequency: Option<i64>,
-
-    #[serde(rename = "@stagger")]
-    stagger: Option<bool>,
-
-    gridline: Gridline,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Gridline {
-    #[serde(rename = "@style")]
-    style: Ref<Style>,
-
-    #[serde(rename = "@zOrder")]
-    z_order: i64,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Label {
-    #[serde(rename = "@style")]
-    style: Ref<Style>,
-
-    #[serde(rename = "@textFrameStyle")]
-    text_frame_style: Option<Ref<Style>>,
-
-    #[serde(rename = "@purpose")]
-    purpose: Option<Purpose>,
-
-    #[serde(rename = "$value")]
-    child: LabelChild,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum Purpose {
-    Title,
-    SubTitle,
-    SubSubTitle,
-    Layer,
-    Footnote,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum LabelChild {
-    Text(Vec<Text>),
-    DescriptionGroup(DescriptionGroup),
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Text {
-    #[serde(rename = "@usesReference")]
-    uses_reference: Option<i64>,
-
-    #[serde(rename = "@definesReference")]
-    defines_reference: Option<i64>,
-
-    #[serde(rename = "@position")]
-    position: Option<Position>,
-
-    #[serde(rename = "@style")]
-    style: Option<Ref<Style>>,
-
-    #[serde(default, rename = "$text")]
-    text: String,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct DescriptionGroup {
-    #[serde(rename = "@target")]
-    target: Ref<Faceting>,
-
-    #[serde(rename = "@separator")]
-    separator: Option<String>,
-
-    children: Vec<DescriptionGroupChild>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum DescriptionGroupChild {
-    Description(Description),
-    Text(Text),
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Description {
-    #[serde(rename = "@name")]
-    name: Name,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-enum Name {
-    Variable,
-    Value,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct LabelFrame {
-    #[serde(rename = "@id")]
-    id: Option<String>,
-
-    #[serde(rename = "@style")]
-    style: Ref<Style>,
-
-    #[serde(rename = "location")]
-    locations: Vec<Location>,
-
-    label: Option<Label>,
-    paragraph: Option<Paragraph>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Paragraph;
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct Container {
-    #[serde(rename = "@style")]
-    style: Ref<Style>,
-
-    #[serde(default, rename = "extension")]
-    extensions: Option<ContainerExtension>,
-    #[serde(default)]
-    locations: Vec<Location>,
-    #[serde(default)]
-    label_frames: Vec<LabelFrame>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename = "extension", rename_all = "camelCase")]
-struct ContainerExtension {
-    #[serde(rename = "@combinedFootnotes")]
-    combined_footnotes: Option<bool>,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct LayerController {
-    #[serde(rename = "@target")]
-    target: Option<Ref<Label>>,
-}
diff --git a/rust/pspp/src/output/spv/legacy_xml.rs b/rust/pspp/src/output/spv/legacy_xml.rs
new file mode 100644 (file)
index 0000000..2ea4eea
--- /dev/null
@@ -0,0 +1,1165 @@
+// PSPP - a program for statistical analysis.
+// Copyright (C) 2025 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/>.
+
+use std::marker::PhantomData;
+
+use serde::Deserialize;
+
+use crate::output::pivot::Color;
+
+#[derive(Debug)]
+struct Ref<T> {
+    references: String,
+    _phantom: PhantomData<T>,
+}
+
+impl<'de, T> Deserialize<'de> for Ref<T> {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        Ok(Self {
+            references: String::deserialize(deserializer)?,
+            _phantom: PhantomData,
+        })
+    }
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct Visualization {
+    /// In format `YYYY-MM-DD`.
+    #[serde(rename = "@date")]
+    date: String,
+    // Locale used for output, e.g. `en-US`.
+    #[serde(rename = "@lang")]
+    lang: String,
+    /// Localized title of the pivot table.
+    #[serde(rename = "@name")]
+    name: String,
+    /// Base style for the pivot table.
+    #[serde(rename = "@style")]
+    style: Ref<Style>,
+
+    #[serde(rename = "$value")]
+    children: Vec<VisChild>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum VisChild {
+    Extension(VisualizationExtension),
+    UserSource(UserSource),
+    SourceVariable(SourceVariable),
+    DerivedVariable(DerivedVariable),
+    CategoricalDomain(CategoricalDomain),
+    Graph(Graph),
+    LabelFrame(LabelFrame),
+    Container(Container),
+    Style(Style),
+    LayerController(LayerController),
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename = "extension", rename_all = "camelCase")]
+struct VisualizationExtension;
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum Variable {
+    SourceVariable(SourceVariable),
+    DerivedVariable(DerivedVariable),
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct SourceVariable {
+    #[serde(rename = "@id")]
+    id: String,
+
+    /// The name of a variable within the source, corresponding to the
+    /// `variable-name` in the `tableData.bin` member.
+    #[serde(rename = "@sourceName")]
+    source_name: String,
+
+    /// Variable label, if any.
+    #[serde(rename = "@label")]
+    label: Option<String>,
+
+    /// A variable whose string values correspond one-to-one with the values of
+    /// this variable and are suitable as value labels.
+    #[serde(rename = "@labelVariable")]
+    label_variable: Option<Ref<SourceVariable>>,
+
+    #[serde(default, rename = "extension")]
+    extensions: Vec<VariableExtension>,
+    format: Option<Format>,
+    string_format: Option<StringFormat>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct DerivedVariable {
+    #[serde(rename = "@id")]
+    id: String,
+
+    /// An expression that defines the variable's value.
+    #[serde(rename = "@value")]
+    value: String,
+    #[serde(default, rename = "extension")]
+    extensions: Vec<VariableExtension>,
+    format: Option<Format>,
+    string_format: Option<StringFormat>,
+    #[serde(default, rename = "valueMapEntry")]
+    value_map: Vec<ValueMapEntry>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename = "extension", rename_all = "camelCase")]
+struct VariableExtension {
+    #[serde(rename = "@from")]
+    from: String,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct UserSource {
+    #[serde(rename = "@id")]
+    id: String,
+
+    #[serde(rename = "@missing")]
+    missing: Option<Missing>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct CategoricalDomain {
+    variable_reference: VariableReference,
+    simple_sort: SimpleSort,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct VariableReference {
+    #[serde(rename = "@ref")]
+    reference: Option<String>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct SimpleSort {
+    #[serde(rename = "@method")]
+    category_order: CategoryOrder,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct CategoryOrder {}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum Missing {
+    Listwise,
+    Pairwise,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct StringFormat {
+    #[serde(default, rename = "relabel")]
+    relabels: Vec<Relabel>,
+    #[serde(default, rename = "affix")]
+    affixes: Vec<Affix>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Format {
+    #[serde(rename = "@baseFormat")]
+    base_format: Option<BaseFormat>,
+    #[serde(rename = "@errorCharacter")]
+    error_character: Option<char>,
+    #[serde(rename = "@separatorChars")]
+    separator_chars: Option<String>,
+    #[serde(rename = "@mdyOrder")]
+    mdy_order: Option<MdyOrder>,
+    #[serde(rename = "@showYear")]
+    show_year: Option<bool>,
+    #[serde(rename = "@showQuarter")]
+    show_quarter: Option<bool>,
+    #[serde(rename = "@quarterPrefix")]
+    quarter_prefix: Option<String>,
+    #[serde(rename = "@quarterSuffix")]
+    quarter_suffix: Option<String>,
+    #[serde(rename = "@yearAbbreviation")]
+    year_abbreviation: Option<bool>,
+    #[serde(rename = "@showMonth")]
+    show_month: Option<bool>,
+    #[serde(rename = "@monthFormat")]
+    month_format: Option<MonthFormat>,
+    #[serde(rename = "@dayPadding")]
+    day_padding: Option<bool>,
+    #[serde(rename = "@dayOfMonthPadding")]
+    day_of_month_padding: Option<bool>,
+    #[serde(rename = "@showWeek")]
+    show_week: Option<bool>,
+    #[serde(rename = "@weekPadding")]
+    week_padding: Option<bool>,
+    #[serde(rename = "@weekSuffix")]
+    week_suffix: Option<String>,
+    #[serde(rename = "@showDayOfWeek")]
+    show_day_of_week: Option<bool>,
+    #[serde(rename = "@dayOfWeekAbbreviation")]
+    day_of_week_abbreviation: Option<bool>,
+    #[serde(rename = "hourPadding")]
+    hour_padding: Option<bool>,
+    #[serde(rename = "minutePadding")]
+    minute_padding: Option<bool>,
+    #[serde(rename = "secondPadding")]
+    second_padding: Option<bool>,
+    #[serde(rename = "@showDay")]
+    show_day: Option<bool>,
+    #[serde(rename = "@showHour")]
+    show_hour: Option<bool>,
+    #[serde(rename = "@showMinute")]
+    show_minute: Option<bool>,
+    #[serde(rename = "@showSecond")]
+    show_second: Option<bool>,
+    #[serde(rename = "@showMillis")]
+    show_millis: Option<bool>,
+    #[serde(rename = "@dayType")]
+    day_type: Option<DayType>,
+    #[serde(rename = "@hourFormat")]
+    hour_format: Option<HourFormat>,
+    #[serde(rename = "@minimumIntegerDigits")]
+    minimum_integer_digits: Option<usize>,
+    #[serde(rename = "@maximumFractionDigits")]
+    maximum_fraction_digits: Option<usize>,
+    #[serde(rename = "@minimumFractionDigits")]
+    minimum_fraction_digits: Option<usize>,
+    #[serde(rename = "@useGrouping")]
+    use_grouping: Option<bool>,
+    #[serde(rename = "@scientific")]
+    scientific: Option<Scientific>,
+    #[serde(rename = "@small")]
+    small: Option<f64>,
+    #[serde(default, rename = "@prefix")]
+    prefix: String,
+    #[serde(default, rename = "@suffix")]
+    suffix: String,
+    #[serde(rename = "@tryStringsAsNumbers")]
+    try_strings_as_numbers: Option<bool>,
+    #[serde(rename = "@negativesOutside")]
+    negatives_outside: Option<bool>,
+    #[serde(default)]
+    relabel: Vec<Relabel>,
+    #[serde(default, rename = "affix")]
+    affixes: Vec<Affix>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct NumberFormat {
+    #[serde(rename = "@minimumIntegerDigits")]
+    minimum_integer_digits: Option<usize>,
+    #[serde(rename = "@maximumFractionDigits")]
+    maximum_fraction_digits: Option<usize>,
+    #[serde(rename = "@minimumFractionDigits")]
+    minimum_fraction_digits: Option<usize>,
+    #[serde(rename = "@useGrouping")]
+    use_grouping: Option<bool>,
+    #[serde(rename = "@scientific")]
+    scientific: Option<Scientific>,
+    #[serde(rename = "@small")]
+    small: Option<f64>,
+    #[serde(default, rename = "@prefix")]
+    prefix: String,
+    #[serde(default, rename = "@suffix")]
+    suffix: String,
+    #[serde(default, rename = "affix")]
+    affixes: Vec<Affix>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct DateTimeFormat {
+    #[serde(rename = "@baseFormat")]
+    base_format: Option<BaseFormat>,
+    #[serde(rename = "@separatorChars")]
+    separator_chars: Option<String>,
+    #[serde(rename = "@mdyOrder")]
+    mdy_order: Option<MdyOrder>,
+    #[serde(rename = "@showYear")]
+    show_year: Option<bool>,
+    #[serde(rename = "@showQuarter")]
+    show_quarter: Option<bool>,
+    #[serde(rename = "@quarterPrefix")]
+    quarter_prefix: Option<String>,
+    #[serde(rename = "@quarterSuffix")]
+    quarter_suffix: Option<String>,
+    #[serde(rename = "@yearAbbreviation")]
+    year_abbreviation: Option<bool>,
+    #[serde(rename = "@showMonth")]
+    show_month: Option<bool>,
+    #[serde(rename = "@monthFormat")]
+    month_format: Option<MonthFormat>,
+    #[serde(rename = "@dayPadding")]
+    day_padding: Option<bool>,
+    #[serde(rename = "@dayOfMonthPadding")]
+    day_of_month_padding: Option<bool>,
+    #[serde(rename = "@showWeek")]
+    show_week: Option<bool>,
+    #[serde(rename = "@weekPadding")]
+    week_padding: Option<bool>,
+    #[serde(rename = "@weekSuffix")]
+    week_suffix: Option<String>,
+    #[serde(rename = "@showDayOfWeek")]
+    show_day_of_week: Option<bool>,
+    #[serde(rename = "@dayOfWeekAbbreviation")]
+    day_of_week_abbreviation: Option<bool>,
+    #[serde(rename = "hourPadding")]
+    hour_padding: Option<bool>,
+    #[serde(rename = "minutePadding")]
+    minute_padding: Option<bool>,
+    #[serde(rename = "secondPadding")]
+    second_padding: Option<bool>,
+    #[serde(rename = "@showDay")]
+    show_day: Option<bool>,
+    #[serde(rename = "@showHour")]
+    show_hour: Option<bool>,
+    #[serde(rename = "@showMinute")]
+    show_minute: Option<bool>,
+    #[serde(rename = "@showSecond")]
+    show_second: Option<bool>,
+    #[serde(rename = "@showMillis")]
+    show_millis: Option<bool>,
+    #[serde(rename = "@dayType")]
+    day_type: Option<DayType>,
+    #[serde(rename = "@hourFormat")]
+    hour_format: Option<HourFormat>,
+    #[serde(default, rename = "affix")]
+    affixes: Vec<Affix>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct ElapsedTimeFormat {
+    #[serde(rename = "@baseFormat")]
+    base_format: Option<BaseFormat>,
+    #[serde(rename = "@dayPadding")]
+    day_padding: Option<bool>,
+    #[serde(rename = "hourPadding")]
+    hour_padding: Option<bool>,
+    #[serde(rename = "minutePadding")]
+    minute_padding: Option<bool>,
+    #[serde(rename = "secondPadding")]
+    second_padding: Option<bool>,
+    #[serde(rename = "@showDay")]
+    show_day: Option<bool>,
+    #[serde(rename = "@showHour")]
+    show_hour: Option<bool>,
+    #[serde(rename = "@showMinute")]
+    show_minute: Option<bool>,
+    #[serde(rename = "@showSecond")]
+    show_second: Option<bool>,
+    #[serde(rename = "@showMillis")]
+    show_millis: Option<bool>,
+    #[serde(rename = "@showYear")]
+    show_year: Option<bool>,
+    #[serde(default, rename = "affix")]
+    affixes: Vec<Affix>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum BaseFormat {
+    Date,
+    Time,
+    DateTime,
+    ElapsedTime,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum MdyOrder {
+    DayMonthYear,
+    MonthDayYear,
+    YearMonthDay,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum MonthFormat {
+    Long,
+    Short,
+    Number,
+    PaddedNumber,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum DayType {
+    Month,
+    Year,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum HourFormat {
+    #[serde(rename = "AMPM")]
+    AmPm,
+    #[serde(rename = "AS_24")]
+    As24,
+    #[serde(rename = "AS_12")]
+    As12,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum Scientific {
+    OnlyForSmall,
+    WhenNeeded,
+    True,
+    False,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Affix {
+    /// The footnote number as a natural number: 1 for the first footnote, 2 for
+    /// the second, and so on.
+    #[serde(rename = "@definesReference")]
+    defines_reference: u64,
+
+    /// Position for the footnote label.
+    #[serde(rename = "@position")]
+    position: Position,
+
+    /// Whether the affix is a suffix (true) or a prefix (false).
+    #[serde(rename = "@suffix")]
+    suffix: bool,
+
+    /// The text of the suffix or prefix. Typically a letter, e.g. `a` for
+    /// footnote 1, `b` for footnote 2, ...
+    #[serde(rename = "@value")]
+    value: String,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum Position {
+    Subscript,
+    Superscript,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Relabel {
+    #[serde(rename = "@from")]
+    from: f64,
+    #[serde(rename = "@to")]
+    to: String,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct ValueMapEntry {
+    #[serde(rename = "@from")]
+    from: String,
+    #[serde(rename = "@to")]
+    to: String,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Style {
+    #[serde(rename = "@id")]
+    id: Option<String>,
+
+    /// The text color or, in some cases, background color.
+    #[serde(rename = "@color")]
+    color: Option<Color>,
+
+    /// Not used.
+    #[serde(rename = "@color2")]
+    color2: Option<Color>,
+
+    /// Normally 0. The value -90 causes inner column or outer row labels to be
+    /// rotated vertically.
+    #[serde(rename = "@labelAngle")]
+    label_angle: Option<f64>,
+
+    #[serde(rename = "@border-bottom")]
+    border_bottom: Option<Border>,
+
+    #[serde(rename = "@border-top")]
+    border_top: Option<Border>,
+
+    #[serde(rename = "@border-left")]
+    border_left: Option<Border>,
+
+    #[serde(rename = "@border-right")]
+    border_right: Option<Border>,
+
+    #[serde(rename = "@border-bottom-color")]
+    border_bottom_color: Option<Color>,
+
+    #[serde(rename = "@border-top-color")]
+    border_top_color: Option<Color>,
+
+    #[serde(rename = "@border-left-color")]
+    border_left_color: Option<Color>,
+
+    #[serde(rename = "@border-right-color")]
+    border_right_color: Option<Color>,
+
+    #[serde(rename = "@font-family")]
+    font_family: Option<String>,
+
+    #[serde(rename = "@font-size")]
+    font_size: Option<String>,
+
+    #[serde(rename = "@font-weight")]
+    font_weight: Option<FontWeight>,
+
+    #[serde(rename = "@font-style")]
+    font_style: Option<FontStyle>,
+
+    #[serde(rename = "@font-underline")]
+    font_underline: Option<FontUnderline>,
+
+    #[serde(rename = "@margin-bottom")]
+    margin_bottom: Option<String>,
+
+    #[serde(rename = "@margin-top")]
+    margin_top: Option<String>,
+
+    #[serde(rename = "@margin-left")]
+    margin_left: Option<String>,
+
+    #[serde(rename = "@margin-right")]
+    margin_right: Option<String>,
+
+    #[serde(rename = "@textAlignment")]
+    text_alignment: Option<TextAlignment>,
+
+    #[serde(rename = "@labelLocationHorizontal")]
+    label_location_horizontal: Option<LabelLocation>,
+
+    #[serde(rename = "@labelLocationVertical")]
+    label_location_vertical: Option<LabelLocation>,
+
+    #[serde(rename = "@size")]
+    size: Option<String>,
+
+    #[serde(rename = "@width")]
+    width: Option<String>,
+
+    #[serde(rename = "@visible")]
+    visible: Option<bool>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum Border {
+    Solid,
+    Thick,
+    Thin,
+    Double,
+    None,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum FontWeight {
+    Regular,
+    Bold,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum FontStyle {
+    Regular,
+    Italic,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum FontUnderline {
+    None,
+    Underline,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum TextAlignment {
+    Left,
+    Right,
+    Center,
+    Decimal,
+    Mixed,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum LabelLocation {
+    Positive,
+    Negative,
+    Center,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Graph {
+    #[serde(rename = "@cellStyle")]
+    cell_style: Ref<Style>,
+
+    #[serde(rename = "@style")]
+    style: Ref<Style>,
+
+    #[serde(rename = "location")]
+    locations: Vec<Location>,
+    coordinates: Coordinates,
+    faceting: Faceting,
+    facet_layout: FacetLayout,
+    interval: Interval,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Coordinates;
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Location {
+    /// The part of the table being located.
+    #[serde(rename = "@part")]
+    part: Part,
+
+    /// How the location is determined.
+    #[serde(rename = "@method")]
+    method: Method,
+
+    /// Minimum size.
+    #[serde(rename = "@min")]
+    min: Option<String>,
+
+    /// Maximum size.
+    #[serde(rename = "@max")]
+    max: Option<String>,
+
+    /// An element to attach to. Required when method is attach or same, not
+    /// observed otherwise.
+    #[serde(rename = "@target")]
+    target: Option<String>,
+
+    #[serde(rename = "@value")]
+    value: Option<String>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum Part {
+    Height,
+    Width,
+    Top,
+    Bottom,
+    Left,
+    Right,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum Method {
+    SizeToContent,
+    Attach,
+    Fixed,
+    Same,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Faceting {
+    #[serde(rename = "@id")]
+    id: Option<String>,
+
+    #[serde(default)]
+    layers1: Vec<Layer>,
+    cross: Cross,
+    #[serde(default)]
+    layers2: Vec<Layer>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Cross {
+    #[serde(rename = "$value")]
+    children: Vec<CrossChild>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum CrossChild {
+    /// No dimensions along this axis.
+    Unity,
+    /// Dimensions along this axis.
+    Nest(Nest),
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Nest {
+    #[serde(rename = "variableReference")]
+    variable_references: Vec<VariableReference>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Layer {
+    #[serde(rename = "@variable")]
+    variable: String,
+
+    #[serde(rename = "@value")]
+    value: String,
+
+    #[serde(rename = "@visible")]
+    visible: Option<bool>,
+
+    #[serde(rename = "@titleVisible")]
+    title_visible: Option<bool>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct FacetLayout {
+    table_layout: TableLayout,
+    #[serde(rename = "$value")]
+    children: Vec<FacetLayoutChild>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum FacetLayoutChild {
+    SetCellProperties(SetCellProperties),
+    FacetLevel(FacetLevel),
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct TableLayout {
+    #[serde(rename = "@verticalTitlesInCorner")]
+    vertical_titles_in_corner: bool,
+
+    #[serde(rename = "@style")]
+    style: Option<Ref<Style>>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct SetCellProperties {
+    #[serde(rename = "@id")]
+    id: Option<String>,
+
+    #[serde(rename = "@applyToConverse")]
+    apply_to_converse: Option<bool>,
+
+    #[serde(rename = "$value")]
+    sets: Vec<Set>,
+
+    #[serde(rename = "union")]
+    unions: Option<Union>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Union {
+    #[serde(default, rename = "intersect")]
+    intersects: Vec<Intersect>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Intersect {
+    #[serde(default, rename = "$value")]
+    child: Vec<IntersectChild>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum IntersectChild {
+    Where(Where),
+    IntersectWhere(IntersectWhere),
+    Alternating,
+    #[serde(other)]
+    Empty,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Where {
+    #[serde(rename = "@variable")]
+    variable: String,
+    #[serde(rename = "@include")]
+    include: String,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct IntersectWhere {
+    #[serde(rename = "@variable")]
+    variable: String,
+
+    #[serde(rename = "@variable2")]
+    variable2: String,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum Set {
+    SetStyle(SetStyle),
+    SetFrameStyle(SetFrameStyle),
+    SetFormat(SetFormat),
+    SetMetaData(SetMetaData),
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct SetStyle {
+    #[serde(rename = "@target")]
+    target: String,
+
+    #[serde(rename = "@style")]
+    style: Ref<Style>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct SetMetaData {
+    #[serde(rename = "@target")]
+    target: Ref<Graph>,
+
+    #[serde(rename = "@key")]
+    key: String,
+
+    #[serde(rename = "@value")]
+    value: String,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct SetFormat {
+    #[serde(rename = "@target")]
+    target: String,
+
+    #[serde(rename = "@reset")]
+    reset: Option<bool>,
+
+    #[serde(rename = "$value")]
+    child: Option<SetFormatChild>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum SetFormatChild {
+    Format(Format),
+    NumberFormat(NumberFormat),
+    StringFormat(Vec<StringFormat>),
+    DateTimeFormat(DateTimeFormat),
+    ElapsedTimeFormat(ElapsedTimeFormat),
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct SetFrameStyle {
+    #[serde(rename = "@style")]
+    style: Ref<Style>,
+
+    #[serde(rename = "@target")]
+    target: Ref<MajorTicks>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Interval {
+    #[serde(rename = "@id")]
+    id: Option<String>,
+
+    #[serde(rename = "@style")]
+    style: Ref<Style>,
+
+    labeling: Labeling,
+    footnotes: Option<Footnotes>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Labeling {
+    #[serde(rename = "@style")]
+    style: Option<Ref<Style>>,
+
+    #[serde(rename = "@variable")]
+    variable: String,
+
+    #[serde(default)]
+    children: Vec<LabelingChild>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum LabelingChild {
+    Formatting(Formatting),
+    Format(Format),
+    Footnotes(Footnotes),
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Formatting {
+    #[serde(rename = "@variable")]
+    variable: String,
+
+    mappings: Vec<FormatMapping>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct FormatMapping {
+    #[serde(rename = "@from")]
+    from: i64,
+
+    format: Option<Format>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Footnotes {
+    #[serde(rename = "@superscript")]
+    superscript: Option<bool>,
+
+    #[serde(rename = "@variable")]
+    variable: String,
+
+    #[serde(default, rename = "footnoteMapping")]
+    mappings: Vec<FootnoteMapping>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct FootnoteMapping {
+    #[serde(rename = "@definesReference")]
+    defines_reference: i64,
+
+    #[serde(rename = "@from")]
+    from: i64,
+
+    #[serde(rename = "@to")]
+    to: String,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct FacetLevel {
+    #[serde(rename = "@id")]
+    id: Option<String>,
+
+    #[serde(rename = "@level")]
+    level: usize,
+
+    #[serde(rename = "@gap")]
+    gap: Option<String>,
+    //axis: Axis,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Axis {
+    #[serde(rename = "@style")]
+    style: Ref<Style>,
+
+    label: Option<Label>,
+    major_ticks: MajorTicks,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct MajorTicks {
+    #[serde(rename = "@id")]
+    id: String,
+
+    #[serde(rename = "@labelAngle")]
+    label_angle: f64,
+
+    #[serde(rename = "@length")]
+    length: String,
+
+    #[serde(rename = "@style")]
+    style: Ref<Style>,
+
+    #[serde(rename = "@tickFrameStyle")]
+    tick_frame_style: Ref<Style>,
+
+    #[serde(rename = "@labelFrequency")]
+    label_frequency: Option<i64>,
+
+    #[serde(rename = "@stagger")]
+    stagger: Option<bool>,
+
+    gridline: Gridline,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Gridline {
+    #[serde(rename = "@style")]
+    style: Ref<Style>,
+
+    #[serde(rename = "@zOrder")]
+    z_order: i64,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Label {
+    #[serde(rename = "@style")]
+    style: Ref<Style>,
+
+    #[serde(rename = "@textFrameStyle")]
+    text_frame_style: Option<Ref<Style>>,
+
+    #[serde(rename = "@purpose")]
+    purpose: Option<Purpose>,
+
+    #[serde(rename = "$value")]
+    child: LabelChild,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum Purpose {
+    Title,
+    SubTitle,
+    SubSubTitle,
+    Layer,
+    Footnote,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum LabelChild {
+    Text(Vec<Text>),
+    DescriptionGroup(DescriptionGroup),
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Text {
+    #[serde(rename = "@usesReference")]
+    uses_reference: Option<i64>,
+
+    #[serde(rename = "@definesReference")]
+    defines_reference: Option<i64>,
+
+    #[serde(rename = "@position")]
+    position: Option<Position>,
+
+    #[serde(rename = "@style")]
+    style: Option<Ref<Style>>,
+
+    #[serde(default, rename = "$text")]
+    text: String,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct DescriptionGroup {
+    #[serde(rename = "@target")]
+    target: Ref<Faceting>,
+
+    #[serde(rename = "@separator")]
+    separator: Option<String>,
+
+    #[serde(rename = "$value")]
+    children: Vec<DescriptionGroupChild>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum DescriptionGroupChild {
+    Description(Description),
+    Text(Text),
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Description {
+    #[serde(rename = "@name")]
+    name: Name,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum Name {
+    Variable,
+    Value,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct LabelFrame {
+    #[serde(rename = "@id")]
+    id: Option<String>,
+
+    #[serde(rename = "@style")]
+    style: Ref<Style>,
+
+    #[serde(rename = "location")]
+    locations: Vec<Location>,
+
+    label: Option<Label>,
+    paragraph: Option<Paragraph>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Paragraph;
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Container {
+    #[serde(rename = "@style")]
+    style: Ref<Style>,
+
+    #[serde(default, rename = "extension")]
+    extensions: Option<ContainerExtension>,
+    #[serde(default)]
+    locations: Vec<Location>,
+    #[serde(default)]
+    label_frames: Vec<LabelFrame>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename = "extension", rename_all = "camelCase")]
+struct ContainerExtension {
+    #[serde(rename = "@combinedFootnotes")]
+    combined_footnotes: Option<bool>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct LayerController {
+    #[serde(rename = "@target")]
+    target: Option<Ref<Label>>,
+}