--- /dev/null
+// 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,
+ variables: Vec<Variable>,
+ categorical_domain: Option<CategoricalDomain>,
+ graph: Graph,
+ lf1: LabelFrame,
+ container: Option<Container>,
+ lf2: LabelFrame,
+ 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>>,
+
+ 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,
+ format: Option<Format>,
+ string_format: Option<StringFormat>,
+ 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 = "@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>,
+ 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: f64,
+}
+
+#[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 {
+ /// 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>,
+
+ 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,
+ Wdith,
+ 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(default)]
+ layers1: Vec<Layer>,
+ cross: Cross,
+ #[serde(default)]
+ layers2: Vec<Layer>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Cross {
+ #[serde(rename = "$value")]
+ child: CrossChild,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+enum CrossChild {
+ /// No dimensions along this axis.
+ Unity,
+ /// Dimensions along this axis.
+ Nest(
+ /// From innermost to outermost.
+ 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)]
+ scp1: Vec<SetCellProperties>,
+ #[serde(rename = "facetLevel")]
+ facet_levels: Vec<FacetLevel>,
+ #[serde(default)]
+ #[serde(rename = "setCellProperties")]
+ scp2: Vec<SetCellProperties>,
+}
+
+#[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 = "@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")]
+enum Intersect {
+ 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 = "@style")]
+ style: Ref<Style>,
+
+ labeling: Labeling,
+ footnotes: Option<Footnotes>,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct Labeling {
+ #[serde(rename = "@style")]
+ style: Ref<Style>,
+
+ #[serde(rename = "@variable")]
+ variable: String,
+
+ 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 = "@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 = "@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: Ref<Style>,
+
+ #[serde(rename = "@purpose")]
+ purpose: Option<Purpose>,
+
+ 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: 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 = "@style")]
+ style: Ref<Style>,
+ 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>,
+
+ extensions: Option<ContainerExtension>,
+ locations: Vec<Location>,
+ 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>>,
+}