mirror of
https://github.com/openai/codex.git
synced 2026-03-05 21:45:28 +03:00
@@ -3,6 +3,7 @@ mod error;
|
||||
mod formula;
|
||||
mod manager;
|
||||
mod model;
|
||||
mod style;
|
||||
mod xlsx;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -12,3 +13,4 @@ pub use address::*;
|
||||
pub use error::*;
|
||||
pub use manager::*;
|
||||
pub use model::*;
|
||||
pub use style::*;
|
||||
|
||||
@@ -109,6 +109,21 @@ impl SpreadsheetArtifactManager {
|
||||
"set_row_heights_bulk" => self.set_row_heights_bulk(request),
|
||||
"get_row_height" => self.get_row_height(request),
|
||||
"cleanup_and_validate_sheet" => self.cleanup_and_validate_sheet(request),
|
||||
"create_text_style" => self.create_text_style(request),
|
||||
"get_text_style" => self.get_text_style(request),
|
||||
"create_fill" => self.create_fill(request),
|
||||
"get_fill" => self.get_fill(request),
|
||||
"create_border" => self.create_border(request),
|
||||
"get_border" => self.get_border(request),
|
||||
"create_number_format" => self.create_number_format(request),
|
||||
"get_number_format" => self.get_number_format(request),
|
||||
"create_cell_format" => self.create_cell_format(request),
|
||||
"get_cell_format" => self.get_cell_format(request),
|
||||
"create_differential_format" => self.create_differential_format(request),
|
||||
"get_differential_format" => self.get_differential_format(request),
|
||||
"get_cell_format_summary" => self.get_cell_format_summary(request),
|
||||
"get_range_format_summary" => self.get_range_format_summary(request),
|
||||
"get_reference" => self.get_reference(request),
|
||||
"get_cell" => self.get_cell(request),
|
||||
"get_cell_by_indices" => self.get_cell_by_indices(request),
|
||||
"get_cell_field" => self.get_cell_field(request),
|
||||
@@ -312,6 +327,13 @@ impl SpreadsheetArtifactManager {
|
||||
.map(|entry| SpreadsheetCellRangeRef::new(sheet.name.clone(), entry));
|
||||
response.rendered_text = Some(sheet.to_rendered_text(range.as_ref()));
|
||||
response.range = range.as_ref().map(|entry| sheet.get_range_view(entry));
|
||||
response.top_left_style_index = range
|
||||
.as_ref()
|
||||
.map(|entry| sheet.top_left_style_index(entry));
|
||||
response.range_format = range.as_ref().map(|entry| sheet.range_format(entry));
|
||||
response.cell_format_summary = response
|
||||
.top_left_style_index
|
||||
.and_then(|style_index| artifact.cell_format_summary(style_index));
|
||||
response.serialized_dict = Some(sheet.to_dict()?);
|
||||
Ok(response)
|
||||
}
|
||||
@@ -665,6 +687,417 @@ impl SpreadsheetArtifactManager {
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn create_text_style(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: CreateTextStyleArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
|
||||
let style_id = artifact.create_text_style(
|
||||
args.style,
|
||||
args.source_style_id,
|
||||
args.merge_with_existing_components.unwrap_or(false),
|
||||
)?;
|
||||
let style = artifact.get_text_style(style_id).cloned().ok_or_else(|| {
|
||||
SpreadsheetArtifactError::Serialization {
|
||||
message: format!("created text style `{style_id}` was not available"),
|
||||
}
|
||||
})?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Created text style `{style_id}`"),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.style_id = Some(style_id);
|
||||
response.serialized_dict = Some(to_serialized_value(style)?);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn get_text_style(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: GetStyleArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact(&artifact_id, &request.action)?;
|
||||
let style = artifact.get_text_style(args.id).cloned().ok_or_else(|| {
|
||||
SpreadsheetArtifactError::InvalidArgs {
|
||||
action: request.action.clone(),
|
||||
message: format!("text style `{}` was not found", args.id),
|
||||
}
|
||||
})?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Retrieved text style `{}`", args.id),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.style_id = Some(args.id);
|
||||
response.serialized_dict = Some(to_serialized_value(style)?);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn create_fill(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: CreateFillArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
|
||||
let style_id = artifact.create_fill(
|
||||
args.fill,
|
||||
args.source_fill_id,
|
||||
args.merge_with_existing_components.unwrap_or(false),
|
||||
)?;
|
||||
let fill = artifact.get_fill(style_id).cloned().ok_or_else(|| {
|
||||
SpreadsheetArtifactError::Serialization {
|
||||
message: format!("created fill `{style_id}` was not available"),
|
||||
}
|
||||
})?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Created fill `{style_id}`"),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.style_id = Some(style_id);
|
||||
response.serialized_dict = Some(to_serialized_value(fill)?);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn get_fill(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: GetStyleArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact(&artifact_id, &request.action)?;
|
||||
let fill = artifact.get_fill(args.id).cloned().ok_or_else(|| {
|
||||
SpreadsheetArtifactError::InvalidArgs {
|
||||
action: request.action.clone(),
|
||||
message: format!("fill `{}` was not found", args.id),
|
||||
}
|
||||
})?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Retrieved fill `{}`", args.id),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.style_id = Some(args.id);
|
||||
response.serialized_dict = Some(to_serialized_value(fill)?);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn create_border(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: CreateBorderArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
|
||||
let style_id = artifact.create_border(
|
||||
args.border,
|
||||
args.source_border_id,
|
||||
args.merge_with_existing_components.unwrap_or(false),
|
||||
)?;
|
||||
let border = artifact.get_border(style_id).cloned().ok_or_else(|| {
|
||||
SpreadsheetArtifactError::Serialization {
|
||||
message: format!("created border `{style_id}` was not available"),
|
||||
}
|
||||
})?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Created border `{style_id}`"),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.style_id = Some(style_id);
|
||||
response.serialized_dict = Some(to_serialized_value(border)?);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn get_border(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: GetStyleArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact(&artifact_id, &request.action)?;
|
||||
let border = artifact.get_border(args.id).cloned().ok_or_else(|| {
|
||||
SpreadsheetArtifactError::InvalidArgs {
|
||||
action: request.action.clone(),
|
||||
message: format!("border `{}` was not found", args.id),
|
||||
}
|
||||
})?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Retrieved border `{}`", args.id),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.style_id = Some(args.id);
|
||||
response.serialized_dict = Some(to_serialized_value(border)?);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn create_number_format(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: CreateNumberFormatArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
|
||||
let style_id = artifact.create_number_format(
|
||||
args.number_format,
|
||||
args.source_number_format_id,
|
||||
args.merge_with_existing_components.unwrap_or(false),
|
||||
)?;
|
||||
let number_format = artifact
|
||||
.get_number_format(style_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| SpreadsheetArtifactError::Serialization {
|
||||
message: format!("created number format `{style_id}` was not available"),
|
||||
})?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Created number format `{style_id}`"),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.style_id = Some(style_id);
|
||||
response.serialized_dict = Some(to_serialized_value(number_format)?);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn get_number_format(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: GetStyleArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact(&artifact_id, &request.action)?;
|
||||
let number_format = artifact
|
||||
.get_number_format(args.id)
|
||||
.cloned()
|
||||
.ok_or_else(|| SpreadsheetArtifactError::InvalidArgs {
|
||||
action: request.action.clone(),
|
||||
message: format!("number format `{}` was not found", args.id),
|
||||
})?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Retrieved number format `{}`", args.id),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.style_id = Some(args.id);
|
||||
response.serialized_dict = Some(to_serialized_value(number_format)?);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn create_cell_format(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: CreateCellFormatArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
|
||||
let style_id = artifact.create_cell_format(
|
||||
args.format,
|
||||
args.source_format_id,
|
||||
args.merge_with_existing_components.unwrap_or(false),
|
||||
)?;
|
||||
let format = artifact.get_cell_format(style_id).cloned().ok_or_else(|| {
|
||||
SpreadsheetArtifactError::Serialization {
|
||||
message: format!("created cell format `{style_id}` was not available"),
|
||||
}
|
||||
})?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Created cell format `{style_id}`"),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.style_id = Some(style_id);
|
||||
response.serialized_dict = Some(to_serialized_value(format)?);
|
||||
response.cell_format_summary = artifact.cell_format_summary(style_id);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn get_cell_format(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: GetStyleArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact(&artifact_id, &request.action)?;
|
||||
let format = artifact.get_cell_format(args.id).cloned().ok_or_else(|| {
|
||||
SpreadsheetArtifactError::InvalidArgs {
|
||||
action: request.action.clone(),
|
||||
message: format!("cell format `{}` was not found", args.id),
|
||||
}
|
||||
})?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Retrieved cell format `{}`", args.id),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.style_id = Some(args.id);
|
||||
response.serialized_dict = Some(to_serialized_value(format)?);
|
||||
response.cell_format_summary = artifact.cell_format_summary(args.id);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn create_differential_format(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: CreateDifferentialFormatArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact_mut(&artifact_id, &request.action)?;
|
||||
let style_id = artifact.create_differential_format(args.format);
|
||||
let format = artifact
|
||||
.get_differential_format(style_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| SpreadsheetArtifactError::Serialization {
|
||||
message: format!("created differential format `{style_id}` was not available"),
|
||||
})?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Created differential format `{style_id}`"),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.style_id = Some(style_id);
|
||||
response.serialized_dict = Some(to_serialized_value(format)?);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn get_differential_format(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: GetStyleArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact(&artifact_id, &request.action)?;
|
||||
let format = artifact
|
||||
.get_differential_format(args.id)
|
||||
.cloned()
|
||||
.ok_or_else(|| SpreadsheetArtifactError::InvalidArgs {
|
||||
action: request.action.clone(),
|
||||
message: format!("differential format `{}` was not found", args.id),
|
||||
})?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Retrieved differential format `{}`", args.id),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.style_id = Some(args.id);
|
||||
response.serialized_dict = Some(to_serialized_value(format)?);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn get_cell_format_summary(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: CellAddressArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact(&artifact_id, &request.action)?;
|
||||
let sheet = artifact.sheet_lookup(
|
||||
&request.action,
|
||||
args.sheet_name.as_deref(),
|
||||
args.sheet_index.map(|value| value as usize),
|
||||
)?;
|
||||
let cell = sheet.get_cell_view(CellAddress::parse(&args.address)?);
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Retrieved cell format summary for `{}`", args.address),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.cell_ref = Some(sheet.cell_ref(&args.address)?);
|
||||
response.cell = Some(cell.clone());
|
||||
response.cell_format_summary = artifact.cell_format_summary(cell.style_index);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn get_range_format_summary(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: RangeArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact(&artifact_id, &request.action)?;
|
||||
let sheet = artifact.sheet_lookup(
|
||||
&request.action,
|
||||
args.sheet_name.as_deref(),
|
||||
args.sheet_index.map(|value| value as usize),
|
||||
)?;
|
||||
let range = CellRange::parse(&args.range)?;
|
||||
let top_left_style_index = sheet.top_left_style_index(&range);
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!("Retrieved range format summary for `{}`", args.range),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.range_ref = Some(SpreadsheetCellRangeRef::new(sheet.name.clone(), &range));
|
||||
response.range_format = Some(sheet.range_format(&range));
|
||||
response.range = Some(sheet.get_range_view(&range));
|
||||
response.top_left_style_index = Some(top_left_style_index);
|
||||
response.cell_format_summary = artifact.cell_format_summary(top_left_style_index);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn get_reference(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
) -> Result<SpreadsheetArtifactResponse, SpreadsheetArtifactError> {
|
||||
let args: ReferenceArgs = parse_args(&request.action, &request.args)?;
|
||||
let artifact_id = required_artifact_id(&request)?;
|
||||
let artifact = self.get_artifact(&artifact_id, &request.action)?;
|
||||
let sheet = artifact.sheet_lookup(
|
||||
&request.action,
|
||||
args.sheet_name.as_deref(),
|
||||
args.sheet_index.map(|value| value as usize),
|
||||
)?;
|
||||
let mut response = SpreadsheetArtifactResponse::new(
|
||||
artifact_id,
|
||||
request.action,
|
||||
format!(
|
||||
"Resolved reference `{}` on `{}`",
|
||||
args.reference, sheet.name
|
||||
),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
match sheet.reference(&args.reference)? {
|
||||
crate::SpreadsheetSheetReference::Cell { cell_ref } => {
|
||||
let address = cell_ref.cell_address()?;
|
||||
let cell = sheet.get_cell_view(address);
|
||||
response.cell_format_summary = artifact.cell_format_summary(cell.style_index);
|
||||
response.cell = Some(cell);
|
||||
response.raw_cell = sheet.get_raw_cell(address);
|
||||
response.cell_ref = Some(cell_ref);
|
||||
}
|
||||
crate::SpreadsheetSheetReference::Range { range_ref } => {
|
||||
let range = range_ref.range()?;
|
||||
let top_left_style_index = sheet.top_left_style_index(&range);
|
||||
response.range = Some(sheet.get_range_view(&range));
|
||||
response.range_ref = Some(range_ref);
|
||||
response.range_format = Some(sheet.range_format(&range));
|
||||
response.top_left_style_index = Some(top_left_style_index);
|
||||
response.cell_format_summary = artifact.cell_format_summary(top_left_style_index);
|
||||
response.rendered_text = Some(sheet.to_rendered_text(Some(&range)));
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn get_cell(
|
||||
&mut self,
|
||||
request: SpreadsheetArtifactRequest,
|
||||
@@ -684,7 +1117,9 @@ impl SpreadsheetArtifactManager {
|
||||
format!("Retrieved cell `{}` from `{}`", args.address, sheet.name),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.cell_format_summary = artifact.cell_format_summary(cell.style_index);
|
||||
response.cell = Some(cell);
|
||||
response.raw_cell = sheet.get_raw_cell(CellAddress::parse(&args.address)?);
|
||||
response.cell_ref = Some(sheet.cell_ref(&args.address)?);
|
||||
Ok(response)
|
||||
}
|
||||
@@ -714,7 +1149,10 @@ impl SpreadsheetArtifactManager {
|
||||
),
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.cell = Some(sheet.get_cell_view_by_indices(args.column_index, args.row_index));
|
||||
let cell = sheet.get_cell_view_by_indices(args.column_index, args.row_index);
|
||||
response.cell_format_summary = artifact.cell_format_summary(cell.style_index);
|
||||
response.cell = Some(cell);
|
||||
response.raw_cell = sheet.get_raw_cell(address);
|
||||
response.cell_ref = Some(sheet.cell_ref(address.to_a1())?);
|
||||
Ok(response)
|
||||
}
|
||||
@@ -798,6 +1236,11 @@ impl SpreadsheetArtifactManager {
|
||||
snapshot_for_artifact(artifact),
|
||||
);
|
||||
response.range = Some(sheet.get_range_view(&range));
|
||||
response.range_ref = Some(SpreadsheetCellRangeRef::new(sheet.name.clone(), &range));
|
||||
response.range_format = Some(sheet.range_format(&range));
|
||||
response.top_left_style_index = Some(sheet.top_left_style_index(&range));
|
||||
response.cell_format_summary =
|
||||
artifact.cell_format_summary(sheet.top_left_style_index(&range));
|
||||
response.rendered_text = Some(sheet.to_rendered_text(Some(&range)));
|
||||
Ok(response)
|
||||
}
|
||||
@@ -1470,12 +1913,22 @@ pub struct SpreadsheetArtifactResponse {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub range_ref: Option<SpreadsheetCellRangeRef>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub range_format: Option<crate::SpreadsheetRangeFormat>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cell: Option<crate::SpreadsheetCellView>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub raw_cell: Option<crate::SpreadsheetCell>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub style_id: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cell_field: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub range: Option<SpreadsheetRangeView>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub top_left_style_index: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cell_format_summary: Option<crate::SpreadsheetCellFormatSummary>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub rendered_text: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub row_height: Option<f64>,
|
||||
@@ -1505,9 +1958,14 @@ impl SpreadsheetArtifactResponse {
|
||||
sheet_ref: None,
|
||||
cell_ref: None,
|
||||
range_ref: None,
|
||||
range_format: None,
|
||||
cell: None,
|
||||
raw_cell: None,
|
||||
style_id: None,
|
||||
cell_field: None,
|
||||
range: None,
|
||||
top_left_style_index: None,
|
||||
cell_format_summary: None,
|
||||
rendered_text: None,
|
||||
row_height: None,
|
||||
serialized_dict: None,
|
||||
@@ -1636,6 +2094,51 @@ struct GetRowHeightArgs {
|
||||
row_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateTextStyleArgs {
|
||||
style: crate::SpreadsheetTextStyle,
|
||||
source_style_id: Option<u32>,
|
||||
merge_with_existing_components: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateFillArgs {
|
||||
fill: crate::SpreadsheetFill,
|
||||
source_fill_id: Option<u32>,
|
||||
merge_with_existing_components: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateBorderArgs {
|
||||
border: crate::SpreadsheetBorder,
|
||||
source_border_id: Option<u32>,
|
||||
merge_with_existing_components: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateNumberFormatArgs {
|
||||
number_format: crate::SpreadsheetNumberFormat,
|
||||
source_number_format_id: Option<u32>,
|
||||
merge_with_existing_components: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateCellFormatArgs {
|
||||
format: crate::SpreadsheetCellFormat,
|
||||
source_format_id: Option<u32>,
|
||||
merge_with_existing_components: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateDifferentialFormatArgs {
|
||||
format: crate::SpreadsheetDifferentialFormat,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct GetStyleArgs {
|
||||
id: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CellAddressArgs {
|
||||
sheet_name: Option<String>,
|
||||
@@ -1643,6 +2146,13 @@ struct CellAddressArgs {
|
||||
address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ReferenceArgs {
|
||||
sheet_name: Option<String>,
|
||||
sheet_index: Option<u32>,
|
||||
reference: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CellIndicesArgs {
|
||||
sheet_name: Option<String>,
|
||||
@@ -1903,3 +2413,9 @@ fn resolve_path(cwd: &Path, path: &Path) -> PathBuf {
|
||||
cwd.join(path)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_serialized_value<T: Serialize>(value: T) -> Result<Value, SpreadsheetArtifactError> {
|
||||
serde_json::to_value(value).map_err(|error| SpreadsheetArtifactError::Serialization {
|
||||
message: error.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -65,6 +65,11 @@ impl TryFrom<Value> for SpreadsheetCellValue {
|
||||
|
||||
fn try_from(value: Value) -> Result<Self, SpreadsheetArtifactError> {
|
||||
match value {
|
||||
Value::Object(_) => serde_json::from_value(value).map_err(|error| {
|
||||
SpreadsheetArtifactError::Serialization {
|
||||
message: error.to_string(),
|
||||
}
|
||||
}),
|
||||
Value::Bool(value) => Ok(Self::Bool(value)),
|
||||
Value::Number(value) => {
|
||||
if let Some(integer) = value.as_i64() {
|
||||
@@ -337,6 +342,14 @@ impl SpreadsheetCellRangeRef {
|
||||
Ok(self.get(sheet)?.data)
|
||||
}
|
||||
|
||||
pub fn top_left_style_index(
|
||||
&self,
|
||||
sheet: &SpreadsheetSheet,
|
||||
) -> Result<u32, SpreadsheetArtifactError> {
|
||||
self.ensure_sheet(sheet)?;
|
||||
Ok(sheet.top_left_style_index(&self.range()?))
|
||||
}
|
||||
|
||||
pub fn set_value(
|
||||
&self,
|
||||
sheet: &mut SpreadsheetSheet,
|
||||
@@ -420,6 +433,13 @@ impl SpreadsheetCellRangeRef {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum SpreadsheetSheetReference {
|
||||
Cell { cell_ref: SpreadsheetCellRef },
|
||||
Range { range_ref: SpreadsheetCellRangeRef },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SpreadsheetSheetSummary {
|
||||
pub name: String,
|
||||
@@ -686,6 +706,19 @@ impl SpreadsheetSheet {
|
||||
Ok(SpreadsheetCellRangeRef::new(self.name.clone(), &range))
|
||||
}
|
||||
|
||||
pub fn reference(
|
||||
&self,
|
||||
address: impl AsRef<str>,
|
||||
) -> Result<SpreadsheetSheetReference, SpreadsheetArtifactError> {
|
||||
let address = address.as_ref();
|
||||
if let Ok(cell_ref) = self.cell_ref(address) {
|
||||
return Ok(SpreadsheetSheetReference::Cell { cell_ref });
|
||||
}
|
||||
Ok(SpreadsheetSheetReference::Range {
|
||||
range_ref: self.range_ref(address)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_column_widths(
|
||||
&mut self,
|
||||
reference: &str,
|
||||
@@ -1156,6 +1189,12 @@ impl SpreadsheetSheet {
|
||||
self.get_cell(address).cloned()
|
||||
}
|
||||
|
||||
pub fn top_left_style_index(&self, range: &CellRange) -> u32 {
|
||||
self.get_cell(range.start)
|
||||
.map(|cell| cell.style_index)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn get_range_view(&self, range: &CellRange) -> SpreadsheetRangeView {
|
||||
let mut values = Vec::new();
|
||||
let mut formulas = Vec::new();
|
||||
@@ -1307,6 +1346,18 @@ pub struct SpreadsheetArtifact {
|
||||
#[serde(default)]
|
||||
pub sheets: Vec<SpreadsheetSheet>,
|
||||
pub auto_recalculate: bool,
|
||||
#[serde(default)]
|
||||
pub text_styles: BTreeMap<u32, crate::SpreadsheetTextStyle>,
|
||||
#[serde(default)]
|
||||
pub fills: BTreeMap<u32, crate::SpreadsheetFill>,
|
||||
#[serde(default)]
|
||||
pub borders: BTreeMap<u32, crate::SpreadsheetBorder>,
|
||||
#[serde(default)]
|
||||
pub number_formats: BTreeMap<u32, crate::SpreadsheetNumberFormat>,
|
||||
#[serde(default)]
|
||||
pub cell_formats: BTreeMap<u32, crate::SpreadsheetCellFormat>,
|
||||
#[serde(default)]
|
||||
pub differential_formats: BTreeMap<u32, crate::SpreadsheetDifferentialFormat>,
|
||||
}
|
||||
|
||||
impl SpreadsheetArtifact {
|
||||
@@ -1316,6 +1367,12 @@ impl SpreadsheetArtifact {
|
||||
name,
|
||||
sheets: Vec::new(),
|
||||
auto_recalculate: false,
|
||||
text_styles: BTreeMap::new(),
|
||||
fills: BTreeMap::new(),
|
||||
borders: BTreeMap::new(),
|
||||
number_formats: BTreeMap::new(),
|
||||
cell_formats: BTreeMap::new(),
|
||||
differential_formats: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
580
codex-rs/artifact-spreadsheet/src/style.rs
Normal file
580
codex-rs/artifact-spreadsheet/src/style.rs
Normal file
@@ -0,0 +1,580 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::CellRange;
|
||||
use crate::SpreadsheetArtifact;
|
||||
use crate::SpreadsheetArtifactError;
|
||||
use crate::SpreadsheetCellRangeRef;
|
||||
use crate::SpreadsheetSheet;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub struct SpreadsheetFontFace {
|
||||
pub font_family: Option<String>,
|
||||
pub font_scheme: Option<String>,
|
||||
pub typeface: Option<String>,
|
||||
}
|
||||
|
||||
impl SpreadsheetFontFace {
|
||||
fn merge(&self, patch: &Self) -> Self {
|
||||
Self {
|
||||
font_family: patch
|
||||
.font_family
|
||||
.clone()
|
||||
.or_else(|| self.font_family.clone()),
|
||||
font_scheme: patch
|
||||
.font_scheme
|
||||
.clone()
|
||||
.or_else(|| self.font_scheme.clone()),
|
||||
typeface: patch.typeface.clone().or_else(|| self.typeface.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct SpreadsheetTextStyle {
|
||||
pub bold: Option<bool>,
|
||||
pub italic: Option<bool>,
|
||||
pub underline: Option<bool>,
|
||||
pub font_size: Option<f64>,
|
||||
pub font_color: Option<String>,
|
||||
pub text_alignment: Option<String>,
|
||||
pub anchor: Option<String>,
|
||||
pub vertical_text_orientation: Option<String>,
|
||||
pub text_rotation: Option<i32>,
|
||||
pub paragraph_spacing: Option<bool>,
|
||||
pub bottom_inset: Option<f64>,
|
||||
pub left_inset: Option<f64>,
|
||||
pub right_inset: Option<f64>,
|
||||
pub top_inset: Option<f64>,
|
||||
pub font_family: Option<String>,
|
||||
pub font_scheme: Option<String>,
|
||||
pub typeface: Option<String>,
|
||||
pub font_face: Option<SpreadsheetFontFace>,
|
||||
}
|
||||
|
||||
impl SpreadsheetTextStyle {
|
||||
fn merge(&self, patch: &Self) -> Self {
|
||||
Self {
|
||||
bold: patch.bold.or(self.bold),
|
||||
italic: patch.italic.or(self.italic),
|
||||
underline: patch.underline.or(self.underline),
|
||||
font_size: patch.font_size.or(self.font_size),
|
||||
font_color: patch.font_color.clone().or_else(|| self.font_color.clone()),
|
||||
text_alignment: patch
|
||||
.text_alignment
|
||||
.clone()
|
||||
.or_else(|| self.text_alignment.clone()),
|
||||
anchor: patch.anchor.clone().or_else(|| self.anchor.clone()),
|
||||
vertical_text_orientation: patch
|
||||
.vertical_text_orientation
|
||||
.clone()
|
||||
.or_else(|| self.vertical_text_orientation.clone()),
|
||||
text_rotation: patch.text_rotation.or(self.text_rotation),
|
||||
paragraph_spacing: patch.paragraph_spacing.or(self.paragraph_spacing),
|
||||
bottom_inset: patch.bottom_inset.or(self.bottom_inset),
|
||||
left_inset: patch.left_inset.or(self.left_inset),
|
||||
right_inset: patch.right_inset.or(self.right_inset),
|
||||
top_inset: patch.top_inset.or(self.top_inset),
|
||||
font_family: patch
|
||||
.font_family
|
||||
.clone()
|
||||
.or_else(|| self.font_family.clone()),
|
||||
font_scheme: patch
|
||||
.font_scheme
|
||||
.clone()
|
||||
.or_else(|| self.font_scheme.clone()),
|
||||
typeface: patch.typeface.clone().or_else(|| self.typeface.clone()),
|
||||
font_face: match (&self.font_face, &patch.font_face) {
|
||||
(Some(base), Some(update)) => Some(base.merge(update)),
|
||||
(None, Some(update)) => Some(update.clone()),
|
||||
(Some(base), None) => Some(base.clone()),
|
||||
(None, None) => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SpreadsheetGradientStop {
|
||||
pub position: f64,
|
||||
pub color: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SpreadsheetFillRectangle {
|
||||
pub left: f64,
|
||||
pub right: f64,
|
||||
pub top: f64,
|
||||
pub bottom: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct SpreadsheetFill {
|
||||
pub solid_fill_color: Option<String>,
|
||||
pub pattern_type: Option<String>,
|
||||
pub pattern_foreground_color: Option<String>,
|
||||
pub pattern_background_color: Option<String>,
|
||||
#[serde(default)]
|
||||
pub color_transforms: Vec<String>,
|
||||
pub gradient_fill_type: Option<String>,
|
||||
#[serde(default)]
|
||||
pub gradient_stops: Vec<SpreadsheetGradientStop>,
|
||||
pub gradient_kind: Option<String>,
|
||||
pub angle: Option<f64>,
|
||||
pub scaled: Option<bool>,
|
||||
pub path_type: Option<String>,
|
||||
pub fill_rectangle: Option<SpreadsheetFillRectangle>,
|
||||
pub image_reference: Option<String>,
|
||||
}
|
||||
|
||||
impl SpreadsheetFill {
|
||||
fn merge(&self, patch: &Self) -> Self {
|
||||
Self {
|
||||
solid_fill_color: patch
|
||||
.solid_fill_color
|
||||
.clone()
|
||||
.or_else(|| self.solid_fill_color.clone()),
|
||||
pattern_type: patch
|
||||
.pattern_type
|
||||
.clone()
|
||||
.or_else(|| self.pattern_type.clone()),
|
||||
pattern_foreground_color: patch
|
||||
.pattern_foreground_color
|
||||
.clone()
|
||||
.or_else(|| self.pattern_foreground_color.clone()),
|
||||
pattern_background_color: patch
|
||||
.pattern_background_color
|
||||
.clone()
|
||||
.or_else(|| self.pattern_background_color.clone()),
|
||||
color_transforms: if patch.color_transforms.is_empty() {
|
||||
self.color_transforms.clone()
|
||||
} else {
|
||||
patch.color_transforms.clone()
|
||||
},
|
||||
gradient_fill_type: patch
|
||||
.gradient_fill_type
|
||||
.clone()
|
||||
.or_else(|| self.gradient_fill_type.clone()),
|
||||
gradient_stops: if patch.gradient_stops.is_empty() {
|
||||
self.gradient_stops.clone()
|
||||
} else {
|
||||
patch.gradient_stops.clone()
|
||||
},
|
||||
gradient_kind: patch
|
||||
.gradient_kind
|
||||
.clone()
|
||||
.or_else(|| self.gradient_kind.clone()),
|
||||
angle: patch.angle.or(self.angle),
|
||||
scaled: patch.scaled.or(self.scaled),
|
||||
path_type: patch.path_type.clone().or_else(|| self.path_type.clone()),
|
||||
fill_rectangle: patch
|
||||
.fill_rectangle
|
||||
.clone()
|
||||
.or_else(|| self.fill_rectangle.clone()),
|
||||
image_reference: patch
|
||||
.image_reference
|
||||
.clone()
|
||||
.or_else(|| self.image_reference.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub struct SpreadsheetBorderLine {
|
||||
pub style: Option<String>,
|
||||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub struct SpreadsheetBorder {
|
||||
pub top: Option<SpreadsheetBorderLine>,
|
||||
pub right: Option<SpreadsheetBorderLine>,
|
||||
pub bottom: Option<SpreadsheetBorderLine>,
|
||||
pub left: Option<SpreadsheetBorderLine>,
|
||||
}
|
||||
|
||||
impl SpreadsheetBorder {
|
||||
fn merge(&self, patch: &Self) -> Self {
|
||||
Self {
|
||||
top: patch.top.clone().or_else(|| self.top.clone()),
|
||||
right: patch.right.clone().or_else(|| self.right.clone()),
|
||||
bottom: patch.bottom.clone().or_else(|| self.bottom.clone()),
|
||||
left: patch.left.clone().or_else(|| self.left.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub struct SpreadsheetAlignment {
|
||||
pub horizontal: Option<String>,
|
||||
pub vertical: Option<String>,
|
||||
}
|
||||
|
||||
impl SpreadsheetAlignment {
|
||||
fn merge(&self, patch: &Self) -> Self {
|
||||
Self {
|
||||
horizontal: patch.horizontal.clone().or_else(|| self.horizontal.clone()),
|
||||
vertical: patch.vertical.clone().or_else(|| self.vertical.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub struct SpreadsheetNumberFormat {
|
||||
pub format_id: Option<u32>,
|
||||
pub format_code: Option<String>,
|
||||
}
|
||||
|
||||
impl SpreadsheetNumberFormat {
|
||||
fn merge(&self, patch: &Self) -> Self {
|
||||
Self {
|
||||
format_id: patch.format_id.or(self.format_id),
|
||||
format_code: patch
|
||||
.format_code
|
||||
.clone()
|
||||
.or_else(|| self.format_code.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn normalized(mut self) -> Self {
|
||||
if self.format_code.is_none() {
|
||||
self.format_code = self.format_id.and_then(builtin_number_format_code);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct SpreadsheetCellFormat {
|
||||
pub text_style_id: Option<u32>,
|
||||
pub fill_id: Option<u32>,
|
||||
pub border_id: Option<u32>,
|
||||
pub alignment: Option<SpreadsheetAlignment>,
|
||||
pub number_format_id: Option<u32>,
|
||||
pub wrap_text: Option<bool>,
|
||||
pub base_cell_style_format_id: Option<u32>,
|
||||
}
|
||||
|
||||
impl SpreadsheetCellFormat {
|
||||
pub fn wrap(mut self) -> Self {
|
||||
self.wrap_text = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn unwrap(mut self) -> Self {
|
||||
self.wrap_text = Some(false);
|
||||
self
|
||||
}
|
||||
|
||||
fn merge(&self, patch: &Self) -> Self {
|
||||
Self {
|
||||
text_style_id: patch.text_style_id.or(self.text_style_id),
|
||||
fill_id: patch.fill_id.or(self.fill_id),
|
||||
border_id: patch.border_id.or(self.border_id),
|
||||
alignment: match (&self.alignment, &patch.alignment) {
|
||||
(Some(base), Some(update)) => Some(base.merge(update)),
|
||||
(None, Some(update)) => Some(update.clone()),
|
||||
(Some(base), None) => Some(base.clone()),
|
||||
(None, None) => None,
|
||||
},
|
||||
number_format_id: patch.number_format_id.or(self.number_format_id),
|
||||
wrap_text: patch.wrap_text.or(self.wrap_text),
|
||||
base_cell_style_format_id: patch
|
||||
.base_cell_style_format_id
|
||||
.or(self.base_cell_style_format_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct SpreadsheetDifferentialFormat {
|
||||
pub text_style_id: Option<u32>,
|
||||
pub fill_id: Option<u32>,
|
||||
pub border_id: Option<u32>,
|
||||
pub alignment: Option<SpreadsheetAlignment>,
|
||||
pub number_format_id: Option<u32>,
|
||||
pub wrap_text: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SpreadsheetCellFormatSummary {
|
||||
pub style_index: u32,
|
||||
pub text_style: Option<SpreadsheetTextStyle>,
|
||||
pub fill: Option<SpreadsheetFill>,
|
||||
pub border: Option<SpreadsheetBorder>,
|
||||
pub alignment: Option<SpreadsheetAlignment>,
|
||||
pub number_format: Option<SpreadsheetNumberFormat>,
|
||||
pub wrap_text: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SpreadsheetRangeFormat {
|
||||
pub sheet_name: String,
|
||||
pub range: String,
|
||||
}
|
||||
|
||||
impl SpreadsheetRangeFormat {
|
||||
pub fn new(sheet_name: String, range: &CellRange) -> Self {
|
||||
Self {
|
||||
sheet_name,
|
||||
range: range.to_a1(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range_ref(&self) -> Result<SpreadsheetCellRangeRef, SpreadsheetArtifactError> {
|
||||
let range = CellRange::parse(&self.range)?;
|
||||
Ok(SpreadsheetCellRangeRef::new(
|
||||
self.sheet_name.clone(),
|
||||
&range,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn top_left_style_index(
|
||||
&self,
|
||||
sheet: &SpreadsheetSheet,
|
||||
) -> Result<u32, SpreadsheetArtifactError> {
|
||||
self.range_ref()?.top_left_style_index(sheet)
|
||||
}
|
||||
|
||||
pub fn top_left_cell_format(
|
||||
&self,
|
||||
artifact: &SpreadsheetArtifact,
|
||||
sheet: &SpreadsheetSheet,
|
||||
) -> Result<Option<SpreadsheetCellFormatSummary>, SpreadsheetArtifactError> {
|
||||
let range = self.range_ref()?.range()?;
|
||||
Ok(artifact.cell_format_summary(sheet.top_left_style_index(&range)))
|
||||
}
|
||||
}
|
||||
|
||||
impl SpreadsheetArtifact {
|
||||
pub fn create_text_style(
|
||||
&mut self,
|
||||
style: SpreadsheetTextStyle,
|
||||
source_style_id: Option<u32>,
|
||||
merge_with_existing_components: bool,
|
||||
) -> Result<u32, SpreadsheetArtifactError> {
|
||||
let created = if let Some(source_style_id) = source_style_id {
|
||||
let source = self
|
||||
.text_styles
|
||||
.get(&source_style_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| SpreadsheetArtifactError::InvalidArgs {
|
||||
action: "create_text_style".to_string(),
|
||||
message: format!("text style `{source_style_id}` was not found"),
|
||||
})?;
|
||||
if merge_with_existing_components {
|
||||
source.merge(&style)
|
||||
} else {
|
||||
style
|
||||
}
|
||||
} else {
|
||||
style
|
||||
};
|
||||
Ok(insert_with_next_id(&mut self.text_styles, created))
|
||||
}
|
||||
|
||||
pub fn get_text_style(&self, style_id: u32) -> Option<&SpreadsheetTextStyle> {
|
||||
self.text_styles.get(&style_id)
|
||||
}
|
||||
|
||||
pub fn create_fill(
|
||||
&mut self,
|
||||
fill: SpreadsheetFill,
|
||||
source_fill_id: Option<u32>,
|
||||
merge_with_existing_components: bool,
|
||||
) -> Result<u32, SpreadsheetArtifactError> {
|
||||
let created = if let Some(source_fill_id) = source_fill_id {
|
||||
let source = self.fills.get(&source_fill_id).cloned().ok_or_else(|| {
|
||||
SpreadsheetArtifactError::InvalidArgs {
|
||||
action: "create_fill".to_string(),
|
||||
message: format!("fill `{source_fill_id}` was not found"),
|
||||
}
|
||||
})?;
|
||||
if merge_with_existing_components {
|
||||
source.merge(&fill)
|
||||
} else {
|
||||
fill
|
||||
}
|
||||
} else {
|
||||
fill
|
||||
};
|
||||
Ok(insert_with_next_id(&mut self.fills, created))
|
||||
}
|
||||
|
||||
pub fn get_fill(&self, fill_id: u32) -> Option<&SpreadsheetFill> {
|
||||
self.fills.get(&fill_id)
|
||||
}
|
||||
|
||||
pub fn create_border(
|
||||
&mut self,
|
||||
border: SpreadsheetBorder,
|
||||
source_border_id: Option<u32>,
|
||||
merge_with_existing_components: bool,
|
||||
) -> Result<u32, SpreadsheetArtifactError> {
|
||||
let created = if let Some(source_border_id) = source_border_id {
|
||||
let source = self
|
||||
.borders
|
||||
.get(&source_border_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| SpreadsheetArtifactError::InvalidArgs {
|
||||
action: "create_border".to_string(),
|
||||
message: format!("border `{source_border_id}` was not found"),
|
||||
})?;
|
||||
if merge_with_existing_components {
|
||||
source.merge(&border)
|
||||
} else {
|
||||
border
|
||||
}
|
||||
} else {
|
||||
border
|
||||
};
|
||||
Ok(insert_with_next_id(&mut self.borders, created))
|
||||
}
|
||||
|
||||
pub fn get_border(&self, border_id: u32) -> Option<&SpreadsheetBorder> {
|
||||
self.borders.get(&border_id)
|
||||
}
|
||||
|
||||
pub fn create_number_format(
|
||||
&mut self,
|
||||
format: SpreadsheetNumberFormat,
|
||||
source_number_format_id: Option<u32>,
|
||||
merge_with_existing_components: bool,
|
||||
) -> Result<u32, SpreadsheetArtifactError> {
|
||||
let created = if let Some(source_number_format_id) = source_number_format_id {
|
||||
let source = self
|
||||
.number_formats
|
||||
.get(&source_number_format_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| SpreadsheetArtifactError::InvalidArgs {
|
||||
action: "create_number_format".to_string(),
|
||||
message: format!("number format `{source_number_format_id}` was not found"),
|
||||
})?;
|
||||
if merge_with_existing_components {
|
||||
source.merge(&format)
|
||||
} else {
|
||||
format
|
||||
}
|
||||
} else {
|
||||
format
|
||||
};
|
||||
Ok(insert_with_next_id(
|
||||
&mut self.number_formats,
|
||||
created.normalized(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_number_format(&self, number_format_id: u32) -> Option<&SpreadsheetNumberFormat> {
|
||||
self.number_formats.get(&number_format_id)
|
||||
}
|
||||
|
||||
pub fn create_cell_format(
|
||||
&mut self,
|
||||
format: SpreadsheetCellFormat,
|
||||
source_format_id: Option<u32>,
|
||||
merge_with_existing_components: bool,
|
||||
) -> Result<u32, SpreadsheetArtifactError> {
|
||||
let created = if let Some(source_format_id) = source_format_id {
|
||||
let source = self
|
||||
.cell_formats
|
||||
.get(&source_format_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| SpreadsheetArtifactError::InvalidArgs {
|
||||
action: "create_cell_format".to_string(),
|
||||
message: format!("cell format `{source_format_id}` was not found"),
|
||||
})?;
|
||||
if merge_with_existing_components {
|
||||
source.merge(&format)
|
||||
} else {
|
||||
format
|
||||
}
|
||||
} else {
|
||||
format
|
||||
};
|
||||
Ok(insert_with_next_id(&mut self.cell_formats, created))
|
||||
}
|
||||
|
||||
pub fn get_cell_format(&self, format_id: u32) -> Option<&SpreadsheetCellFormat> {
|
||||
self.cell_formats.get(&format_id)
|
||||
}
|
||||
|
||||
pub fn create_differential_format(&mut self, format: SpreadsheetDifferentialFormat) -> u32 {
|
||||
insert_with_next_id(&mut self.differential_formats, format)
|
||||
}
|
||||
|
||||
pub fn get_differential_format(
|
||||
&self,
|
||||
format_id: u32,
|
||||
) -> Option<&SpreadsheetDifferentialFormat> {
|
||||
self.differential_formats.get(&format_id)
|
||||
}
|
||||
|
||||
pub fn resolve_cell_format(&self, style_index: u32) -> Option<SpreadsheetCellFormat> {
|
||||
let format = self.cell_formats.get(&style_index)?.clone();
|
||||
resolve_cell_format_recursive(&self.cell_formats, &format, 0)
|
||||
}
|
||||
|
||||
pub fn cell_format_summary(&self, style_index: u32) -> Option<SpreadsheetCellFormatSummary> {
|
||||
let resolved = self.resolve_cell_format(style_index)?;
|
||||
Some(SpreadsheetCellFormatSummary {
|
||||
style_index,
|
||||
text_style: resolved
|
||||
.text_style_id
|
||||
.and_then(|id| self.text_styles.get(&id).cloned()),
|
||||
fill: resolved.fill_id.and_then(|id| self.fills.get(&id).cloned()),
|
||||
border: resolved
|
||||
.border_id
|
||||
.and_then(|id| self.borders.get(&id).cloned()),
|
||||
alignment: resolved.alignment,
|
||||
number_format: resolved
|
||||
.number_format_id
|
||||
.and_then(|id| self.number_formats.get(&id).cloned()),
|
||||
wrap_text: resolved.wrap_text,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SpreadsheetSheet {
|
||||
pub fn range_format(&self, range: &CellRange) -> SpreadsheetRangeFormat {
|
||||
SpreadsheetRangeFormat::new(self.name.clone(), range)
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_with_next_id<T>(map: &mut BTreeMap<u32, T>, value: T) -> u32 {
|
||||
let next_id = map.last_key_value().map(|(key, _)| key + 1).unwrap_or(1);
|
||||
map.insert(next_id, value);
|
||||
next_id
|
||||
}
|
||||
|
||||
fn resolve_cell_format_recursive(
|
||||
cell_formats: &BTreeMap<u32, SpreadsheetCellFormat>,
|
||||
format: &SpreadsheetCellFormat,
|
||||
depth: usize,
|
||||
) -> Option<SpreadsheetCellFormat> {
|
||||
if depth > 32 {
|
||||
return None;
|
||||
}
|
||||
let base = format
|
||||
.base_cell_style_format_id
|
||||
.and_then(|id| cell_formats.get(&id))
|
||||
.and_then(|base| resolve_cell_format_recursive(cell_formats, base, depth + 1));
|
||||
Some(match base {
|
||||
Some(base) => base.merge(format),
|
||||
None => format.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn builtin_number_format_code(format_id: u32) -> Option<String> {
|
||||
match format_id {
|
||||
0 => Some("General".to_string()),
|
||||
1 => Some("0".to_string()),
|
||||
2 => Some("0.00".to_string()),
|
||||
3 => Some("#,##0".to_string()),
|
||||
4 => Some("#,##0.00".to_string()),
|
||||
9 => Some("0%".to_string()),
|
||||
10 => Some("0.00%".to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,16 @@ use crate::SpreadsheetArtifact;
|
||||
use crate::SpreadsheetArtifactManager;
|
||||
use crate::SpreadsheetArtifactRequest;
|
||||
use crate::SpreadsheetCell;
|
||||
use crate::SpreadsheetCellFormat;
|
||||
use crate::SpreadsheetCellFormatSummary;
|
||||
use crate::SpreadsheetCellValue;
|
||||
use crate::SpreadsheetFileType;
|
||||
use crate::SpreadsheetFill;
|
||||
use crate::SpreadsheetFontFace;
|
||||
use crate::SpreadsheetNumberFormat;
|
||||
use crate::SpreadsheetSheet;
|
||||
use crate::SpreadsheetSheetReference;
|
||||
use crate::SpreadsheetTextStyle;
|
||||
|
||||
#[test]
|
||||
fn manager_can_create_edit_recalculate_and_export() -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -554,3 +561,491 @@ fn manager_supports_bulk_sizes_and_row_heights() -> Result<(), Box<dyn std::erro
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manager_style_registry_and_format_summaries_work() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
let mut manager = SpreadsheetArtifactManager::default();
|
||||
let created = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: None,
|
||||
action: "create".to_string(),
|
||||
args: serde_json::json!({ "name": "Styles" }),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
let artifact_id = created.artifact_id;
|
||||
|
||||
manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "create_sheet".to_string(),
|
||||
args: serde_json::json!({ "name": "Sheet1" }),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
|
||||
let text_style = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "create_text_style".to_string(),
|
||||
args: serde_json::json!({
|
||||
"style": {
|
||||
"bold": true,
|
||||
"italic": true,
|
||||
"underline": true,
|
||||
"font_size": 14.0,
|
||||
"font_color": "#112233",
|
||||
"text_alignment": "center",
|
||||
"anchor": "middle",
|
||||
"vertical_text_orientation": "stacked",
|
||||
"text_rotation": 90,
|
||||
"paragraph_spacing": true,
|
||||
"bottom_inset": 1.0,
|
||||
"left_inset": 2.0,
|
||||
"right_inset": 3.0,
|
||||
"top_inset": 4.0,
|
||||
"font_family": "IBM Plex Sans",
|
||||
"font_scheme": "minor",
|
||||
"typeface": "IBM Plex Sans",
|
||||
"font_face": {
|
||||
"font_family": "IBM Plex Sans",
|
||||
"font_scheme": "minor",
|
||||
"typeface": "IBM Plex Sans"
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
let text_style_id = text_style.style_id.expect("text style id");
|
||||
|
||||
let fill = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "create_fill".to_string(),
|
||||
args: serde_json::json!({
|
||||
"fill": {
|
||||
"solid_fill_color": "#ffeeaa",
|
||||
"pattern_type": "solid",
|
||||
"pattern_foreground_color": "#ffeeaa",
|
||||
"pattern_background_color": "#221100",
|
||||
"color_transforms": ["tint:0.2"],
|
||||
"gradient_fill_type": "linear",
|
||||
"gradient_stops": [
|
||||
{ "position": 0.0, "color": "#ffeeaa" },
|
||||
{ "position": 1.0, "color": "#aa5500" }
|
||||
],
|
||||
"gradient_kind": "linear",
|
||||
"angle": 45.0,
|
||||
"scaled": true,
|
||||
"path_type": "rect",
|
||||
"fill_rectangle": {
|
||||
"left": 0.0,
|
||||
"right": 1.0,
|
||||
"top": 0.0,
|
||||
"bottom": 1.0
|
||||
},
|
||||
"image_reference": "image://fill"
|
||||
}
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
let fill_id = fill.style_id.expect("fill id");
|
||||
|
||||
let border = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "create_border".to_string(),
|
||||
args: serde_json::json!({
|
||||
"border": {
|
||||
"top": { "style": "solid", "color": "#111111" },
|
||||
"right": { "style": "dashed", "color": "#222222" },
|
||||
"bottom": { "style": "double", "color": "#333333" },
|
||||
"left": { "style": "solid", "color": "#444444" }
|
||||
}
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
let border_id = border.style_id.expect("border id");
|
||||
|
||||
let number_format = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "create_number_format".to_string(),
|
||||
args: serde_json::json!({
|
||||
"number_format": {
|
||||
"format_id": 4
|
||||
}
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
let number_format_id = number_format.style_id.expect("number format id");
|
||||
|
||||
let base_format = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "create_cell_format".to_string(),
|
||||
args: serde_json::json!({
|
||||
"format": {
|
||||
"text_style_id": text_style_id,
|
||||
"number_format_id": number_format_id,
|
||||
"alignment": {
|
||||
"horizontal": "center",
|
||||
"vertical": "middle"
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
let base_format_id = base_format.style_id.expect("base format id");
|
||||
|
||||
let derived_format = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "create_cell_format".to_string(),
|
||||
args: serde_json::json!({
|
||||
"format": {
|
||||
"fill_id": fill_id,
|
||||
"border_id": border_id,
|
||||
"wrap_text": true,
|
||||
"base_cell_style_format_id": base_format_id
|
||||
}
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
let derived_format_id = derived_format.style_id.expect("derived format id");
|
||||
|
||||
let merged_format = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "create_cell_format".to_string(),
|
||||
args: serde_json::json!({
|
||||
"source_format_id": derived_format_id,
|
||||
"merge_with_existing_components": true,
|
||||
"format": {
|
||||
"alignment": {
|
||||
"vertical": "bottom"
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
let merged_format_id = merged_format.style_id.expect("merged format id");
|
||||
|
||||
let differential_format = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "create_differential_format".to_string(),
|
||||
args: serde_json::json!({
|
||||
"format": {
|
||||
"fill_id": fill_id,
|
||||
"wrap_text": true
|
||||
}
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
let differential_format_id = differential_format.style_id.expect("dxf id");
|
||||
|
||||
manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "set_cell_style".to_string(),
|
||||
args: serde_json::json!({
|
||||
"sheet_name": "Sheet1",
|
||||
"address": "A1",
|
||||
"style_index": merged_format_id
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
|
||||
let summary = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "get_cell_format_summary".to_string(),
|
||||
args: serde_json::json!({
|
||||
"sheet_name": "Sheet1",
|
||||
"address": "A1"
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
assert_eq!(
|
||||
summary.cell_format_summary,
|
||||
Some(SpreadsheetCellFormatSummary {
|
||||
style_index: merged_format_id,
|
||||
text_style: Some(SpreadsheetTextStyle {
|
||||
bold: Some(true),
|
||||
italic: Some(true),
|
||||
underline: Some(true),
|
||||
font_size: Some(14.0),
|
||||
font_color: Some("#112233".to_string()),
|
||||
text_alignment: Some("center".to_string()),
|
||||
anchor: Some("middle".to_string()),
|
||||
vertical_text_orientation: Some("stacked".to_string()),
|
||||
text_rotation: Some(90),
|
||||
paragraph_spacing: Some(true),
|
||||
bottom_inset: Some(1.0),
|
||||
left_inset: Some(2.0),
|
||||
right_inset: Some(3.0),
|
||||
top_inset: Some(4.0),
|
||||
font_family: Some("IBM Plex Sans".to_string()),
|
||||
font_scheme: Some("minor".to_string()),
|
||||
typeface: Some("IBM Plex Sans".to_string()),
|
||||
font_face: Some(SpreadsheetFontFace {
|
||||
font_family: Some("IBM Plex Sans".to_string()),
|
||||
font_scheme: Some("minor".to_string()),
|
||||
typeface: Some("IBM Plex Sans".to_string()),
|
||||
}),
|
||||
}),
|
||||
fill: Some(SpreadsheetFill {
|
||||
solid_fill_color: Some("#ffeeaa".to_string()),
|
||||
pattern_type: Some("solid".to_string()),
|
||||
pattern_foreground_color: Some("#ffeeaa".to_string()),
|
||||
pattern_background_color: Some("#221100".to_string()),
|
||||
color_transforms: vec!["tint:0.2".to_string()],
|
||||
gradient_fill_type: Some("linear".to_string()),
|
||||
gradient_stops: vec![
|
||||
crate::SpreadsheetGradientStop {
|
||||
position: 0.0,
|
||||
color: "#ffeeaa".to_string(),
|
||||
},
|
||||
crate::SpreadsheetGradientStop {
|
||||
position: 1.0,
|
||||
color: "#aa5500".to_string(),
|
||||
},
|
||||
],
|
||||
gradient_kind: Some("linear".to_string()),
|
||||
angle: Some(45.0),
|
||||
scaled: Some(true),
|
||||
path_type: Some("rect".to_string()),
|
||||
fill_rectangle: Some(crate::SpreadsheetFillRectangle {
|
||||
left: 0.0,
|
||||
right: 1.0,
|
||||
top: 0.0,
|
||||
bottom: 1.0,
|
||||
}),
|
||||
image_reference: Some("image://fill".to_string()),
|
||||
}),
|
||||
border: Some(crate::SpreadsheetBorder {
|
||||
top: Some(crate::SpreadsheetBorderLine {
|
||||
style: Some("solid".to_string()),
|
||||
color: Some("#111111".to_string()),
|
||||
}),
|
||||
right: Some(crate::SpreadsheetBorderLine {
|
||||
style: Some("dashed".to_string()),
|
||||
color: Some("#222222".to_string()),
|
||||
}),
|
||||
bottom: Some(crate::SpreadsheetBorderLine {
|
||||
style: Some("double".to_string()),
|
||||
color: Some("#333333".to_string()),
|
||||
}),
|
||||
left: Some(crate::SpreadsheetBorderLine {
|
||||
style: Some("solid".to_string()),
|
||||
color: Some("#444444".to_string()),
|
||||
}),
|
||||
}),
|
||||
alignment: Some(crate::SpreadsheetAlignment {
|
||||
horizontal: Some("center".to_string()),
|
||||
vertical: Some("bottom".to_string()),
|
||||
}),
|
||||
number_format: Some(SpreadsheetNumberFormat {
|
||||
format_id: Some(4),
|
||||
format_code: Some("#,##0.00".to_string()),
|
||||
}),
|
||||
wrap_text: Some(true),
|
||||
})
|
||||
);
|
||||
|
||||
let retrieved_format = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "get_cell_format".to_string(),
|
||||
args: serde_json::json!({ "id": merged_format_id }),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
let retrieved_format: SpreadsheetCellFormat =
|
||||
serde_json::from_value(retrieved_format.serialized_dict.expect("cell format"))?;
|
||||
assert_eq!(
|
||||
retrieved_format,
|
||||
SpreadsheetCellFormat {
|
||||
text_style_id: None,
|
||||
fill_id: Some(fill_id),
|
||||
border_id: Some(border_id),
|
||||
alignment: Some(crate::SpreadsheetAlignment {
|
||||
horizontal: None,
|
||||
vertical: Some("bottom".to_string()),
|
||||
}),
|
||||
number_format_id: None,
|
||||
wrap_text: Some(true),
|
||||
base_cell_style_format_id: Some(base_format_id),
|
||||
}
|
||||
);
|
||||
|
||||
let retrieved_number_format = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "get_number_format".to_string(),
|
||||
args: serde_json::json!({ "id": number_format_id }),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
assert_eq!(
|
||||
serde_json::from_value::<SpreadsheetNumberFormat>(
|
||||
retrieved_number_format
|
||||
.serialized_dict
|
||||
.expect("number format")
|
||||
)?,
|
||||
SpreadsheetNumberFormat {
|
||||
format_id: Some(4),
|
||||
format_code: Some("#,##0.00".to_string()),
|
||||
}
|
||||
);
|
||||
|
||||
let retrieved_text_style = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "get_text_style".to_string(),
|
||||
args: serde_json::json!({ "id": text_style_id }),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
assert_eq!(
|
||||
serde_json::from_value::<SpreadsheetTextStyle>(
|
||||
retrieved_text_style.serialized_dict.expect("text style")
|
||||
)?
|
||||
.bold,
|
||||
Some(true)
|
||||
);
|
||||
|
||||
let range_summary = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "get_range_format_summary".to_string(),
|
||||
args: serde_json::json!({
|
||||
"sheet_name": "Sheet1",
|
||||
"range": "A1:B2"
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
assert_eq!(range_summary.top_left_style_index, Some(merged_format_id));
|
||||
assert_eq!(
|
||||
range_summary
|
||||
.range_format
|
||||
.as_ref()
|
||||
.map(|format| format.range.clone()),
|
||||
Some("A1:B2".to_string())
|
||||
);
|
||||
|
||||
let retrieved_dxf = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id),
|
||||
action: "get_differential_format".to_string(),
|
||||
args: serde_json::json!({ "id": differential_format_id }),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
assert_eq!(
|
||||
serde_json::from_value::<crate::SpreadsheetDifferentialFormat>(
|
||||
retrieved_dxf.serialized_dict.expect("differential format")
|
||||
)?
|
||||
.wrap_text,
|
||||
Some(true)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sheet_references_resolve_cells_and_ranges() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let sheet = SpreadsheetSheet::new("Sheet1".to_string());
|
||||
assert_eq!(
|
||||
sheet.reference("A1")?,
|
||||
SpreadsheetSheetReference::Cell {
|
||||
cell_ref: sheet.cell_ref("A1")?,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
sheet.reference("A1:B2")?,
|
||||
SpreadsheetSheetReference::Range {
|
||||
range_ref: sheet.range_ref("A1:B2")?,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manager_get_reference_and_xlsx_import_preserve_workbook_name()
|
||||
-> Result<(), Box<dyn std::error::Error>> {
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
let path = temp_dir.path().join("named.xlsx");
|
||||
|
||||
let mut artifact = SpreadsheetArtifact::new(Some("Named Workbook".to_string()));
|
||||
artifact.create_sheet("Sheet1".to_string())?.set_value(
|
||||
CellAddress::parse("A1")?,
|
||||
Some(SpreadsheetCellValue::Integer(9)),
|
||||
)?;
|
||||
artifact.export(&path)?;
|
||||
|
||||
let restored = SpreadsheetArtifact::from_source_file(&path, None)?;
|
||||
assert_eq!(restored.name, Some("Named Workbook".to_string()));
|
||||
|
||||
let mut manager = SpreadsheetArtifactManager::default();
|
||||
let imported = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: None,
|
||||
action: "read".to_string(),
|
||||
args: serde_json::json!({ "path": path }),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
let artifact_id = imported.artifact_id;
|
||||
|
||||
let cell_reference = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id.clone()),
|
||||
action: "get_reference".to_string(),
|
||||
args: serde_json::json!({
|
||||
"sheet_name": "Sheet1",
|
||||
"reference": "A1"
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
assert_eq!(
|
||||
cell_reference
|
||||
.raw_cell
|
||||
.as_ref()
|
||||
.and_then(|cell| cell.value.clone()),
|
||||
Some(SpreadsheetCellValue::Integer(9))
|
||||
);
|
||||
|
||||
let range_reference = manager.execute(
|
||||
SpreadsheetArtifactRequest {
|
||||
artifact_id: Some(artifact_id),
|
||||
action: "get_reference".to_string(),
|
||||
args: serde_json::json!({
|
||||
"sheet_name": "Sheet1",
|
||||
"reference": "A1:B2"
|
||||
}),
|
||||
},
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
assert_eq!(
|
||||
range_reference
|
||||
.range_ref
|
||||
.as_ref()
|
||||
.map(|range_ref| range_ref.address.clone()),
|
||||
Some("A1:B2".to_string())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -195,6 +195,13 @@ pub(crate) fn import_xlsx(
|
||||
|
||||
let workbook_xml = read_zip_entry(&mut archive, "xl/workbook.xml", path)?;
|
||||
let workbook_rels = read_zip_entry(&mut archive, "xl/_rels/workbook.xml.rels", path)?;
|
||||
let workbook_name = if archive.by_name("docProps/core.xml").is_ok() {
|
||||
let title =
|
||||
extract_workbook_title(&read_zip_entry(&mut archive, "docProps/core.xml", path)?);
|
||||
(!title.trim().is_empty()).then_some(title)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let shared_strings = if archive.by_name("xl/sharedStrings.xml").is_ok() {
|
||||
Some(parse_shared_strings(&read_zip_entry(
|
||||
&mut archive,
|
||||
@@ -226,11 +233,11 @@ pub(crate) fn import_xlsx(
|
||||
})
|
||||
.collect::<Result<Vec<_>, SpreadsheetArtifactError>>()?;
|
||||
|
||||
let mut artifact = SpreadsheetArtifact::new(
|
||||
let mut artifact = SpreadsheetArtifact::new(workbook_name.or_else(|| {
|
||||
path.file_stem()
|
||||
.and_then(|value| value.to_str())
|
||||
.map(str::to_string),
|
||||
);
|
||||
.map(str::to_string)
|
||||
}));
|
||||
if let Some(artifact_id) = artifact_id {
|
||||
artifact.artifact_id = artifact_id;
|
||||
}
|
||||
@@ -802,6 +809,18 @@ fn first_tag_text(xml: &str, tag: &str) -> Option<String> {
|
||||
captures.get(1).map(|value| value.as_str().to_string())
|
||||
}
|
||||
|
||||
fn extract_workbook_title(xml: &str) -> String {
|
||||
let Ok(regex) =
|
||||
Regex::new(r#"(?s)<(?:[A-Za-z0-9_]+:)?title\b[^>]*>(.*?)</(?:[A-Za-z0-9_]+:)?title>"#)
|
||||
else {
|
||||
return String::new();
|
||||
};
|
||||
regex
|
||||
.captures(xml)
|
||||
.and_then(|captures| captures.get(1).map(|value| xml_unescape(value.as_str())))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn all_text_nodes(xml: &str) -> Result<String, SpreadsheetArtifactError> {
|
||||
let regex = Regex::new(r#"(?s)<t\b[^>]*>(.*?)</t>"#).map_err(|error| {
|
||||
SpreadsheetArtifactError::Serialization {
|
||||
|
||||
Reference in New Issue
Block a user