///|
pub struct PivotTableField {
data : String
name : String
subtotal : String
num_fmt : Int
compact : Bool
outline : Bool
show_all : Bool
insert_blank_row : Bool
default_subtotal : Bool
} derive(Debug)
///|
pub fn PivotTableField::new(
data : String,
name? : String = "",
subtotal? : String = "Sum",
num_fmt? : Int = 0,
compact? : Bool = false,
outline? : Bool = false,
show_all? : Bool = false,
insert_blank_row? : Bool = false,
default_subtotal? : Bool = false,
) -> PivotTableField raise XlsxError {
if data == "" {
raise InvalidPivotTable(msg="pivot field data empty")
}
{
data,
name,
subtotal,
num_fmt,
compact,
outline,
show_all,
insert_blank_row,
default_subtotal,
}
}
///|
/// Canonical dataField subtotal enum values accepted by the OOXML schema.
let pivot_subtotal_enums : Array[String] = [
"average", "count", "countNums", "max", "min", "product", "stdDev", "stdDevp",
"sum", "var", "varp",
]
///|
/// Normalizes a user-provided subtotal to its canonical schema spelling via
/// case-insensitive match, falling back to "sum" like Excelize.
fn normalize_pivot_subtotal(value : String) -> String {
let lowered = value.to_lower()
for enum_value in pivot_subtotal_enums {
if enum_value.to_lower() == lowered {
return enum_value
}
}
"sum"
}
///|
/// Returns the number format ID to emit for a pivot data field: built-in
/// format IDs (including the language-specific 27-36 and 50-81 ranges) pass
/// through, anything else maps to 0 and is omitted, mirroring Excelize.
fn pivot_data_field_num_fmt_id(num_fmt : Int) -> Int {
match num_fmt {
0..=4 | 9..=22 | 37..=49 => num_fmt
27..=36 | 50..=81 => num_fmt
_ => 0
}
}
///|
/// Truncates a pivot field display name to Excelize's MaxFieldLength of
/// 255 UTF-16 code units.
fn pivot_field_display_name(name : String) -> String {
if count_utf16_units(name) > max_field_length {
truncate_utf16_units(name, max_field_length)
} else {
name
}
}
///|
pub(all) struct PivotTableOptions {
data_range : String
pivot_table_range : String
mut name : String
rows : Array[PivotTableField]
columns : Array[PivotTableField]
data : Array[PivotTableField]
filter : Array[PivotTableField]
mut row_grand_totals : Bool
mut col_grand_totals : Bool
mut show_drill : Bool
mut use_auto_formatting : Bool
mut page_over_then_down : Bool
mut merge_item : Bool
mut classic_layout : Bool
mut compact_data : Bool
mut show_error : Bool
mut show_row_headers : Bool
mut show_col_headers : Bool
mut show_row_stripes : Bool
mut show_col_stripes : Bool
mut show_last_column : Bool
mut field_print_titles : Bool
mut item_print_titles : Bool
mut pivot_table_style_name : String
} derive(Debug)
///|
pub fn PivotTableOptions::new(
data_range : String,
pivot_table_range : String,
) -> PivotTableOptions raise XlsxError {
if data_range == "" {
raise InvalidPivotTable(msg="pivot data range empty")
}
if pivot_table_range == "" {
raise InvalidPivotTable(msg="pivot table range empty")
}
{
data_range,
pivot_table_range,
name: "",
rows: [],
columns: [],
data: [],
filter: [],
row_grand_totals: true,
col_grand_totals: true,
show_drill: true,
use_auto_formatting: false,
page_over_then_down: false,
merge_item: false,
classic_layout: false,
compact_data: false,
show_error: false,
show_row_headers: true,
show_col_headers: true,
show_row_stripes: true,
show_col_stripes: false,
show_last_column: false,
field_print_titles: false,
item_print_titles: false,
pivot_table_style_name: "PivotStyleLight16",
}
}
///|
fn split_sheet_range(value : StringView) -> (String, String) raise XlsxError {
let text = value.to_owned()
match text.find("!") {
Some(pos) => {
if pos <= 0 || pos + 1 >= text.length() {
raise InvalidPivotTable(msg="range must be Sheet!A1:B2")
}
let sheet = text[:pos]
let range_ref = text[pos + 1:]
(sheet.to_owned(), range_ref.to_owned())
}
None => raise InvalidPivotTable(msg="range must be Sheet!A1:B2")
}
}
///|
fn write_pivot_cache_definition_xml(
data_sheet : String,
data_ref : String,
fields : ArrayView[String],
) -> String {
let sb = StringBuilder::new()
sb.write_view("\n")
sb.write_view(
"\n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
for name in fields {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
}
sb.write_view(" \n")
sb.write_view("")
sb.to_string()
}
///|
fn write_pivot_table_definition_xml(
name : String,
cache_id : Int,
pivot_ref : String,
field_names : ArrayView[String],
row_fields : ArrayView[(Int, PivotTableField)],
col_fields : ArrayView[(Int, PivotTableField)],
page_fields : ArrayView[(Int, PivotTableField)],
data_fields : ArrayView[(Int, PivotTableField)],
opts : PivotTableOptions,
) -> String {
let sb = StringBuilder::new()
sb.write_view("\n")
sb.write_view(
"\n")
sb.write_view(" \n",
)
let row_opts : Map[Int, PivotTableField] = Map([])
for entry in row_fields {
let (idx, field) = entry
row_opts[idx] = field
}
let col_opts : Map[Int, PivotTableField] = Map([])
for entry in col_fields {
let (idx, field) = entry
col_opts[idx] = field
}
let page_opts : Map[Int, PivotTableField] = Map([])
for entry in page_fields {
let (idx, field) = entry
page_opts[idx] = field
}
let data_opts : Map[Int, PivotTableField] = Map([])
for entry in data_fields {
let (idx, field) = entry
data_opts[idx] = field
}
sb.write_view(" \n")
for i, _fname in field_names {
sb.write_view(" {
let pivot_name = if field.name == "" { field.data } else { field.name }
sb.write_view(" name=\"")
sb.write_view(escape_xml_attr(pivot_field_display_name(pivot_name)))
sb.write_view("\" axis=\"axisRow\"")
let compact = if opts.classic_layout { false } else { field.compact }
let outline = if opts.classic_layout { false } else { field.outline }
sb.write_view(" compact=\"")
sb.write_view(bool_attr(compact))
sb.write_view("\" outline=\"")
sb.write_view(bool_attr(outline))
sb.write_view("\" showAll=\"")
sb.write_view(bool_attr(field.show_all))
sb.write_view("\" defaultSubtotal=\"")
sb.write_view(bool_attr(field.default_subtotal))
sb.write_view("\"")
if field.insert_blank_row {
sb.write_view(" insertBlankRow=\"1\"")
}
match data_opts.get(i) {
Some(_) => sb.write_view(" dataField=\"1\"")
None => ()
}
sb.write_view(">")
if field.default_subtotal {
sb.write_view(" ")
} else {
sb.write_view(" ")
}
sb.write_view("\n")
continue
}
None => ()
}
match col_opts.get(i) {
Some(field) => {
let pivot_name = if field.name == "" { field.data } else { field.name }
sb.write_view(" name=\"")
sb.write_view(escape_xml_attr(pivot_field_display_name(pivot_name)))
sb.write_view("\" axis=\"axisCol\"")
let compact = if opts.classic_layout { false } else { field.compact }
let outline = if opts.classic_layout { false } else { field.outline }
sb.write_view(" compact=\"")
sb.write_view(bool_attr(compact))
sb.write_view("\" outline=\"")
sb.write_view(bool_attr(outline))
sb.write_view("\" showAll=\"")
sb.write_view(bool_attr(field.show_all))
sb.write_view("\" defaultSubtotal=\"")
sb.write_view(bool_attr(field.default_subtotal))
sb.write_view("\"")
if field.insert_blank_row {
sb.write_view(" insertBlankRow=\"1\"")
}
match data_opts.get(i) {
Some(_) => sb.write_view(" dataField=\"1\"")
None => ()
}
sb.write_view(">")
if field.default_subtotal {
sb.write_view(" ")
} else {
sb.write_view(" ")
}
sb.write_view("\n")
continue
}
None => ()
}
match page_opts.get(i) {
Some(field) => {
let pivot_name = if field.name == "" { field.data } else { field.name }
sb.write_view(" name=\"")
sb.write_view(escape_xml_attr(pivot_field_display_name(pivot_name)))
sb.write_view("\" axis=\"axisPage\"")
sb.write_view(" showAll=\"")
sb.write_view(bool_attr(field.show_all))
sb.write_view("\"")
match data_opts.get(i) {
Some(_) => sb.write_view(" dataField=\"1\"")
None => ()
}
sb.write_view(
"> \n",
)
continue
}
None => ()
}
match data_opts.get(i) {
Some(_) => {
if opts.classic_layout {
sb.write_view(" compact=\"0\" outline=\"0\"")
}
sb.write_view(" dataField=\"1\" showAll=\"0\"/>\n")
continue
}
None => ()
}
if opts.classic_layout {
sb.write_view(" compact=\"0\" outline=\"0\"")
}
sb.write_view(" showAll=\"0\"/>\n")
}
sb.write_view(" \n")
if row_fields.length() > 0 {
sb.write_view(" \n")
for entry in row_fields {
let (idx, _field) = entry
sb.write_view(" \n")
}
sb.write_view(" \n")
sb.write_view(" \n")
}
if col_fields.length() > 0 {
sb.write_view(" \n")
for entry in col_fields {
let (idx, _field) = entry
sb.write_view(" \n")
}
sb.write_view(" \n")
sb.write_view(" \n")
}
if page_fields.length() > 0 {
sb.write_view(" \n")
for entry in page_fields {
let (idx, _field) = entry
sb.write_view(" \n")
}
sb.write_view(" \n")
}
if data_fields.length() > 0 {
sb.write_view(" \n")
for entry in data_fields {
let (idx, field) = entry
sb.write_view(" \n")
}
sb.write_view(" \n")
}
sb.write_view(" \n")
sb.write_view("")
sb.to_string()
}