707 lines
21 KiB
Rust
707 lines
21 KiB
Rust
|
|
use calamine::{open_workbook, Data, DataType, Reader, Xlsx};
|
|||
|
|
use chrono::{Datelike, Timelike};
|
|||
|
|
use clap::{Parser, Subcommand};
|
|||
|
|
use std::path::PathBuf;
|
|||
|
|
|
|||
|
|
#[derive(Parser)]
|
|||
|
|
#[command(name = "dexcel")]
|
|||
|
|
#[command(about = "Excel 读写工具", long_about = None)]
|
|||
|
|
struct Cli {
|
|||
|
|
/// Excel 文件路径
|
|||
|
|
file: PathBuf,
|
|||
|
|
|
|||
|
|
#[command(subcommand)]
|
|||
|
|
command: Commands,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Subcommand)]
|
|||
|
|
enum Commands {
|
|||
|
|
/// 查询数据
|
|||
|
|
Query {
|
|||
|
|
/// 获取行数(正数从前获取,负数从后获取)
|
|||
|
|
#[arg(default_value_t = 0, allow_hyphen_values = true)]
|
|||
|
|
count: i32,
|
|||
|
|
|
|||
|
|
/// 开始行号(从1开始,负数表示倒数)
|
|||
|
|
#[arg(long, allow_hyphen_values = true)]
|
|||
|
|
start: Option<i32>,
|
|||
|
|
|
|||
|
|
/// 获取最后一行
|
|||
|
|
#[arg(long)]
|
|||
|
|
last: bool,
|
|||
|
|
|
|||
|
|
/// Sheet 名称(可选,默认为第一个sheet)
|
|||
|
|
#[arg(long)]
|
|||
|
|
sheet: Option<String>,
|
|||
|
|
},
|
|||
|
|
/// 统计行数
|
|||
|
|
Count {
|
|||
|
|
/// Sheet 名称(可选,默认为第一个sheet)
|
|||
|
|
#[arg(long)]
|
|||
|
|
sheet: Option<String>,
|
|||
|
|
},
|
|||
|
|
/// 插入行
|
|||
|
|
Insert {
|
|||
|
|
/// 要插入的值(使用 split 分隔列)
|
|||
|
|
value: String,
|
|||
|
|
|
|||
|
|
/// 插入位置(行号,不指定则追加到末尾)
|
|||
|
|
#[arg(long, allow_hyphen_values = true)]
|
|||
|
|
row: Option<i32>,
|
|||
|
|
|
|||
|
|
/// Sheet 名称(可选,默认为第一个sheet)
|
|||
|
|
#[arg(long)]
|
|||
|
|
sheet: Option<String>,
|
|||
|
|
|
|||
|
|
/// 分割符号(默认 |)
|
|||
|
|
#[arg(long, default_value = "|")]
|
|||
|
|
split: String,
|
|||
|
|
},
|
|||
|
|
/// 更新数据
|
|||
|
|
Update {
|
|||
|
|
/// 要更新的值(使用 split 分隔列)
|
|||
|
|
value: String,
|
|||
|
|
|
|||
|
|
/// 行号(必需)
|
|||
|
|
#[arg(long, allow_hyphen_values = true)]
|
|||
|
|
row: i32,
|
|||
|
|
|
|||
|
|
/// 起始列号(从1开始,不指定则从第1列开始)
|
|||
|
|
#[arg(long)]
|
|||
|
|
cell: Option<u32>,
|
|||
|
|
|
|||
|
|
/// Sheet 名称(可选,默认为第一个sheet)
|
|||
|
|
#[arg(long)]
|
|||
|
|
sheet: Option<String>,
|
|||
|
|
|
|||
|
|
/// 分割符号(默认 |)
|
|||
|
|
#[arg(long, default_value = "|")]
|
|||
|
|
split: String,
|
|||
|
|
},
|
|||
|
|
/// 删除数据
|
|||
|
|
Delete {
|
|||
|
|
/// 删除数量(单元格数或行数)
|
|||
|
|
#[arg(default_value_t = 1)]
|
|||
|
|
count: u32,
|
|||
|
|
|
|||
|
|
/// 行号(必需)
|
|||
|
|
#[arg(long, allow_hyphen_values = true)]
|
|||
|
|
row: i32,
|
|||
|
|
|
|||
|
|
/// 列号(可选,不指定则删除整行)
|
|||
|
|
#[arg(long)]
|
|||
|
|
cell: Option<u32>,
|
|||
|
|
|
|||
|
|
/// Sheet 名称(可选,默认为第一个sheet)
|
|||
|
|
#[arg(long)]
|
|||
|
|
sheet: Option<String>,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn main() {
|
|||
|
|
let cli = Cli::parse();
|
|||
|
|
|
|||
|
|
match cli.command {
|
|||
|
|
Commands::Query {
|
|||
|
|
count,
|
|||
|
|
start,
|
|||
|
|
last,
|
|||
|
|
sheet,
|
|||
|
|
} => {
|
|||
|
|
if let Err(e) = query_excel(&cli.file, count, start, last, &sheet) {
|
|||
|
|
eprintln!("错误: {}", e);
|
|||
|
|
std::process::exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Commands::Count { sheet } => {
|
|||
|
|
if let Err(e) = count_excel(&cli.file, &sheet) {
|
|||
|
|
eprintln!("错误: {}", e);
|
|||
|
|
std::process::exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Commands::Insert {
|
|||
|
|
value,
|
|||
|
|
row,
|
|||
|
|
sheet,
|
|||
|
|
split,
|
|||
|
|
} => {
|
|||
|
|
if let Err(e) = insert_excel(&cli.file, &value, row, &sheet, &split) {
|
|||
|
|
eprintln!("错误: {}", e);
|
|||
|
|
std::process::exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Commands::Update {
|
|||
|
|
value,
|
|||
|
|
row,
|
|||
|
|
cell,
|
|||
|
|
sheet,
|
|||
|
|
split,
|
|||
|
|
} => {
|
|||
|
|
if let Err(e) = update_excel(&cli.file, &value, row, cell, &sheet, &split) {
|
|||
|
|
eprintln!("错误: {}", e);
|
|||
|
|
std::process::exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Commands::Delete {
|
|||
|
|
count,
|
|||
|
|
row,
|
|||
|
|
cell,
|
|||
|
|
sheet,
|
|||
|
|
} => {
|
|||
|
|
if let Err(e) = delete_excel(&cli.file, count, row, cell, &sheet) {
|
|||
|
|
eprintln!("错误: {}", e);
|
|||
|
|
std::process::exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 格式化单元格值为字符串
|
|||
|
|
fn format_cell(cell: &Data) -> String {
|
|||
|
|
match cell {
|
|||
|
|
Data::Empty => String::new(),
|
|||
|
|
Data::Int(v) => v.to_string(),
|
|||
|
|
Data::Float(v) => {
|
|||
|
|
if *v > 1.0 && *v < 2958466.0 && (*v - v.floor()).abs() < 0.0001 {
|
|||
|
|
let temp_data = Data::Float(*v);
|
|||
|
|
if let Some(dt) = temp_data.as_datetime() {
|
|||
|
|
let year = dt.year();
|
|||
|
|
if year >= 1900 && year <= 2100 {
|
|||
|
|
return dt.format("%Y-%m-%d").to_string();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
v.to_string()
|
|||
|
|
}
|
|||
|
|
Data::String(s) => s.clone(),
|
|||
|
|
Data::Bool(b) => b.to_string(),
|
|||
|
|
Data::DateTime(d) => {
|
|||
|
|
if let Some(dt) = d.as_datetime() {
|
|||
|
|
if dt.hour() == 0 && dt.minute() == 0 && dt.second() == 0 {
|
|||
|
|
dt.format("%Y-%m-%d").to_string()
|
|||
|
|
} else {
|
|||
|
|
dt.format("%Y-%m-%d %H:%M:%S").to_string()
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
d.to_string()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Data::DateTimeIso(s) => s.clone(),
|
|||
|
|
Data::DurationIso(s) => s.clone(),
|
|||
|
|
Data::Error(e) => format!("#{}", e),
|
|||
|
|
_ => String::new(),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 统计行数(count 命令)
|
|||
|
|
fn count_excel(
|
|||
|
|
file: &PathBuf,
|
|||
|
|
sheet_name: &Option<String>,
|
|||
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
|
let mut workbook: Xlsx<_> = open_workbook(file)?;
|
|||
|
|
|
|||
|
|
let sheet = match sheet_name {
|
|||
|
|
Some(name) => name.clone(),
|
|||
|
|
None => {
|
|||
|
|
let sheets = workbook.sheet_names();
|
|||
|
|
if sheets.is_empty() {
|
|||
|
|
return Err("Excel 文件中没有 sheet".into());
|
|||
|
|
}
|
|||
|
|
sheets[0].clone()
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let range = workbook
|
|||
|
|
.worksheet_range(&sheet)
|
|||
|
|
.map_err(|e| format!("找不到 sheet: {}, 错误: {}", sheet, e))?;
|
|||
|
|
|
|||
|
|
let total_rows = range.height();
|
|||
|
|
|
|||
|
|
println!("{}", total_rows);
|
|||
|
|
Ok(())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查询数据(query 命令)
|
|||
|
|
fn query_excel(
|
|||
|
|
file: &PathBuf,
|
|||
|
|
count: i32,
|
|||
|
|
start: Option<i32>,
|
|||
|
|
last: bool,
|
|||
|
|
sheet_name: &Option<String>,
|
|||
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
|
let mut workbook: Xlsx<_> = open_workbook(file)?;
|
|||
|
|
|
|||
|
|
let sheet = match sheet_name {
|
|||
|
|
Some(name) => name.clone(),
|
|||
|
|
None => {
|
|||
|
|
let sheets = workbook.sheet_names();
|
|||
|
|
if sheets.is_empty() {
|
|||
|
|
return Err("Excel 文件中没有 sheet".into());
|
|||
|
|
}
|
|||
|
|
sheets[0].clone()
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let range = workbook
|
|||
|
|
.worksheet_range(&sheet)
|
|||
|
|
.map_err(|e| format!("找不到 sheet: {}, 错误: {}", sheet, e))?;
|
|||
|
|
|
|||
|
|
let total_rows = range.height();
|
|||
|
|
let _total_cols = range.width();
|
|||
|
|
|
|||
|
|
if total_rows == 0 {
|
|||
|
|
println!("表格为空");
|
|||
|
|
return Ok(());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算要读取的行范围
|
|||
|
|
let (start_row, end_row) = if last {
|
|||
|
|
(total_rows - 1, total_rows)
|
|||
|
|
} else if let Some(s) = start {
|
|||
|
|
// 使用 start 参数
|
|||
|
|
let actual_start = if s < 0 {
|
|||
|
|
let pos = total_rows as i32 + s;
|
|||
|
|
if pos < 0 { 0 } else { pos as usize }
|
|||
|
|
} else {
|
|||
|
|
(s as usize).saturating_sub(1)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if count > 0 {
|
|||
|
|
(actual_start, (actual_start + count as usize).min(total_rows))
|
|||
|
|
} else if count < 0 {
|
|||
|
|
let abs_count = (-count) as usize;
|
|||
|
|
let end = (actual_start + abs_count).min(total_rows);
|
|||
|
|
(actual_start, end)
|
|||
|
|
} else {
|
|||
|
|
// count 为 0,显示从 start 到末尾(最多20行)
|
|||
|
|
let max_display = 20;
|
|||
|
|
let end = (actual_start + max_display).min(total_rows);
|
|||
|
|
if total_rows - actual_start > max_display {
|
|||
|
|
eprintln!("提示: 从第 {} 行开始共有 {} 行数据,仅显示 {} 行",
|
|||
|
|
actual_start + 1, total_rows - actual_start, max_display);
|
|||
|
|
}
|
|||
|
|
(actual_start, end)
|
|||
|
|
}
|
|||
|
|
} else if count > 0 {
|
|||
|
|
(0, count as usize)
|
|||
|
|
} else if count < 0 {
|
|||
|
|
let abs_count = (-count) as usize;
|
|||
|
|
let start = if abs_count >= total_rows { 0 } else { total_rows - abs_count };
|
|||
|
|
(start, total_rows)
|
|||
|
|
} else {
|
|||
|
|
// 默认显示最后20行
|
|||
|
|
let max_display = 20;
|
|||
|
|
if total_rows > max_display {
|
|||
|
|
eprintln!("提示: 共有 {} 行数据,仅显示最后 {} 行", total_rows, max_display);
|
|||
|
|
eprintln!("如需查看更多数据,请使用 dexcel query <行数> 或 --last\n");
|
|||
|
|
(total_rows - max_display, total_rows)
|
|||
|
|
} else {
|
|||
|
|
(0, total_rows)
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let end_row = end_row.min(total_rows);
|
|||
|
|
|
|||
|
|
// 收集所有数据
|
|||
|
|
let mut data: Vec<Vec<String>> = Vec::new();
|
|||
|
|
for r in start_row..end_row {
|
|||
|
|
let mut row_values = Vec::new();
|
|||
|
|
for c in 0..range.width() {
|
|||
|
|
let cell = range.get((r, c));
|
|||
|
|
if let Some(cell_data) = cell {
|
|||
|
|
row_values.push(format_cell(cell_data));
|
|||
|
|
} else {
|
|||
|
|
row_values.push(String::new());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
data.push(row_values);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if data.is_empty() {
|
|||
|
|
println!("没有数据");
|
|||
|
|
return Ok(());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用 tabled 输出表格
|
|||
|
|
let output_count = data.len();
|
|||
|
|
let col_count = data[0].len();
|
|||
|
|
|
|||
|
|
// 使用 Builder API 创建动态表格
|
|||
|
|
use tabled::builder::Builder;
|
|||
|
|
let mut builder = Builder::default();
|
|||
|
|
|
|||
|
|
// 添加所有行
|
|||
|
|
for row in &data {
|
|||
|
|
builder.push_record(row.iter().map(|s| s.as_str()));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let mut table = builder.build();
|
|||
|
|
|
|||
|
|
// 设置样式
|
|||
|
|
table.with(tabled::settings::Style::modern());
|
|||
|
|
|
|||
|
|
println!("{}", table);
|
|||
|
|
eprintln!("\n共输出 {} 行数据,{} 列", output_count, col_count);
|
|||
|
|
Ok(())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 插入行(insert 命令)
|
|||
|
|
fn insert_excel(
|
|||
|
|
file: &PathBuf,
|
|||
|
|
value: &str,
|
|||
|
|
row: Option<i32>,
|
|||
|
|
sheet_name: &Option<String>,
|
|||
|
|
split: &str,
|
|||
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
|
use rust_xlsxwriter::*;
|
|||
|
|
|
|||
|
|
let sheet = match sheet_name {
|
|||
|
|
Some(name) => name.clone(),
|
|||
|
|
None => "Sheet1".to_string(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 如果文件不存在,创建新文件
|
|||
|
|
if !file.exists() {
|
|||
|
|
let mut workbook = Workbook::new();
|
|||
|
|
let worksheet = workbook.add_worksheet();
|
|||
|
|
worksheet.set_name(&sheet)?;
|
|||
|
|
|
|||
|
|
let values: Vec<&str> = value.split(split).collect();
|
|||
|
|
for (col_idx, val) in values.iter().enumerate() {
|
|||
|
|
let trimmed = val.trim();
|
|||
|
|
if !trimmed.is_empty() {
|
|||
|
|
worksheet.write_string(0, col_idx as u16, trimmed)?;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
workbook.save(file.as_path())?;
|
|||
|
|
println!("✓ 操作成功:已创建新文件并插入一行数据");
|
|||
|
|
eprintln!(" 文件: {}, Sheet: {}", file.display(), sheet);
|
|||
|
|
return Ok(());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let mut workbook: Xlsx<_> = open_workbook(file.as_path())?;
|
|||
|
|
|
|||
|
|
let range = workbook
|
|||
|
|
.worksheet_range(&sheet)
|
|||
|
|
.map_err(|e| format!("找不到 sheet: {}, 错误: {}", sheet, e))?;
|
|||
|
|
|
|||
|
|
let total_rows = range.height();
|
|||
|
|
let total_cols = range.width();
|
|||
|
|
|
|||
|
|
// 确定插入位置
|
|||
|
|
let insert_row = match row {
|
|||
|
|
Some(r) => {
|
|||
|
|
if r < 0 {
|
|||
|
|
let pos = total_rows as i32 + r + 1;
|
|||
|
|
if pos < 0 || pos > total_rows as i32 {
|
|||
|
|
return Err(format!("行号无效: {}", r).into());
|
|||
|
|
}
|
|||
|
|
pos as usize
|
|||
|
|
} else {
|
|||
|
|
let pos = r as usize - 1;
|
|||
|
|
if pos > total_rows {
|
|||
|
|
return Err(format!("行号超出范围: {} (总行数: {})", r, total_rows).into());
|
|||
|
|
}
|
|||
|
|
pos
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
None => total_rows, // 默认追加到末尾
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 创建新工作簿
|
|||
|
|
let mut new_workbook = Workbook::new();
|
|||
|
|
let new_worksheet = new_workbook.add_worksheet();
|
|||
|
|
new_worksheet.set_name(&sheet)?;
|
|||
|
|
|
|||
|
|
let values: Vec<&str> = value.split(split).collect();
|
|||
|
|
let mut new_row = 0u32;
|
|||
|
|
|
|||
|
|
// 复制插入行之前的数据
|
|||
|
|
for r in 0..insert_row {
|
|||
|
|
for c in 0..range.width() {
|
|||
|
|
if let Some(cell_data) = range.get((r, c)) {
|
|||
|
|
write_cell(new_worksheet, new_row, c as u16, cell_data)?;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
new_row += 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 写入新行
|
|||
|
|
for (col_idx, val) in values.iter().enumerate() {
|
|||
|
|
let trimmed = val.trim();
|
|||
|
|
if !trimmed.is_empty() {
|
|||
|
|
new_worksheet.write_string(new_row, col_idx as u16, trimmed)?;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
new_row += 1;
|
|||
|
|
|
|||
|
|
// 复制插入行之后的数据
|
|||
|
|
for r in insert_row..total_rows {
|
|||
|
|
for c in 0..range.width() {
|
|||
|
|
if let Some(cell_data) = range.get((r, c)) {
|
|||
|
|
write_cell(new_worksheet, new_row, c as u16, cell_data)?;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
new_row += 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存文件
|
|||
|
|
new_workbook.save(file.as_path())?;
|
|||
|
|
|
|||
|
|
println!("✓ 操作成功:已在第 {} 行插入一行数据", insert_row + 1);
|
|||
|
|
eprintln!(" 文件: {}, Sheet: {}, 总行数: {}", file.display(), sheet, total_rows + 1);
|
|||
|
|
|
|||
|
|
Ok(())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新数据(update 命令)
|
|||
|
|
fn update_excel(
|
|||
|
|
file: &PathBuf,
|
|||
|
|
value: &str,
|
|||
|
|
row: i32,
|
|||
|
|
cell: Option<u32>,
|
|||
|
|
sheet_name: &Option<String>,
|
|||
|
|
split: &str,
|
|||
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
|
use rust_xlsxwriter::*;
|
|||
|
|
|
|||
|
|
if !file.exists() {
|
|||
|
|
return Err(format!("文件不存在: {}", file.display()).into());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let mut workbook: Xlsx<_> = open_workbook(file.as_path())?;
|
|||
|
|
|
|||
|
|
let sheet = match sheet_name {
|
|||
|
|
Some(name) => name.clone(),
|
|||
|
|
None => {
|
|||
|
|
let sheets = workbook.sheet_names();
|
|||
|
|
if sheets.is_empty() {
|
|||
|
|
return Err("Excel 文件中没有 sheet".into());
|
|||
|
|
}
|
|||
|
|
sheets[0].clone()
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let range = workbook
|
|||
|
|
.worksheet_range(&sheet)
|
|||
|
|
.map_err(|e| format!("找不到 sheet: {}, 错误: {}", sheet, e))?;
|
|||
|
|
|
|||
|
|
let total_rows = range.height();
|
|||
|
|
let total_cols = range.width();
|
|||
|
|
|
|||
|
|
// 确定行号
|
|||
|
|
let target_row = if row < 0 {
|
|||
|
|
let pos = total_rows as i32 + row;
|
|||
|
|
if pos < 0 || pos >= total_rows as i32 {
|
|||
|
|
return Err(format!("行号无效: {}", row).into());
|
|||
|
|
}
|
|||
|
|
pos as usize
|
|||
|
|
} else {
|
|||
|
|
let pos = row as usize - 1;
|
|||
|
|
if pos >= total_rows {
|
|||
|
|
return Err(format!("行号超出范围: {} (总行数: {})", row, total_rows).into());
|
|||
|
|
}
|
|||
|
|
pos
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 确定起始列
|
|||
|
|
let start_col = cell.map(|c| (c - 1) as usize).unwrap_or(0);
|
|||
|
|
|
|||
|
|
// 创建新工作簿并复制所有数据
|
|||
|
|
let mut new_workbook = Workbook::new();
|
|||
|
|
let new_worksheet = new_workbook.add_worksheet();
|
|||
|
|
new_worksheet.set_name(&sheet)?;
|
|||
|
|
|
|||
|
|
let values: Vec<&str> = value.split(split).collect();
|
|||
|
|
|
|||
|
|
for r in 0..total_rows {
|
|||
|
|
for c in 0..range.width() {
|
|||
|
|
if r == target_row && c >= start_col {
|
|||
|
|
// 更新这一行的指定列
|
|||
|
|
let col_offset = c - start_col;
|
|||
|
|
if col_offset < values.len() {
|
|||
|
|
let trimmed = values[col_offset].trim();
|
|||
|
|
if !trimmed.is_empty() {
|
|||
|
|
new_worksheet.write_string(r as u32, c as u16, trimmed)?;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 超出提供的值,保持原样
|
|||
|
|
if let Some(cell_data) = range.get((r, c)) {
|
|||
|
|
write_cell(new_worksheet, r as u32, c as u16, cell_data)?;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 复制原有数据
|
|||
|
|
if let Some(cell_data) = range.get((r, c)) {
|
|||
|
|
write_cell(new_worksheet, r as u32, c as u16, cell_data)?;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存文件
|
|||
|
|
new_workbook.save(file.as_path())?;
|
|||
|
|
|
|||
|
|
println!("✓ 操作成功:已更新第 {} 行,从第 {} 列开始", target_row + 1, start_col + 1);
|
|||
|
|
eprintln!(" 文件: {}, Sheet: {}", file.display(), sheet);
|
|||
|
|
|
|||
|
|
Ok(())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除数据(delete 命令)
|
|||
|
|
fn delete_excel(
|
|||
|
|
file: &PathBuf,
|
|||
|
|
count: u32,
|
|||
|
|
row: i32,
|
|||
|
|
cell: Option<u32>,
|
|||
|
|
sheet_name: &Option<String>,
|
|||
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
|
use rust_xlsxwriter::*;
|
|||
|
|
|
|||
|
|
if !file.exists() {
|
|||
|
|
return Err(format!("文件不存在: {}", file.display()).into());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let mut workbook: Xlsx<_> = open_workbook(file.as_path())?;
|
|||
|
|
|
|||
|
|
let sheet = match sheet_name {
|
|||
|
|
Some(name) => name.clone(),
|
|||
|
|
None => {
|
|||
|
|
let sheets = workbook.sheet_names();
|
|||
|
|
if sheets.is_empty() {
|
|||
|
|
return Err("Excel 文件中没有 sheet".into());
|
|||
|
|
}
|
|||
|
|
sheets[0].clone()
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let range = workbook
|
|||
|
|
.worksheet_range(&sheet)
|
|||
|
|
.map_err(|e| format!("找不到 sheet: {}, 错误: {}", sheet, e))?;
|
|||
|
|
|
|||
|
|
let total_rows = range.height();
|
|||
|
|
let total_cols = range.width();
|
|||
|
|
|
|||
|
|
// 确定行号
|
|||
|
|
let target_row = if row < 0 {
|
|||
|
|
let pos = total_rows as i32 + row;
|
|||
|
|
if pos < 0 || pos >= total_rows as i32 {
|
|||
|
|
return Err(format!("行号无效: {}", row).into());
|
|||
|
|
}
|
|||
|
|
pos as usize
|
|||
|
|
} else {
|
|||
|
|
let pos = row as usize - 1;
|
|||
|
|
if pos >= total_rows {
|
|||
|
|
return Err(format!("行号超出范围: {} (总行数: {})", row, total_rows).into());
|
|||
|
|
}
|
|||
|
|
pos
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 显示确认信息
|
|||
|
|
if let Some(c) = cell {
|
|||
|
|
eprintln!("⚠ 警告:即将删除单元格");
|
|||
|
|
eprintln!(" 文件: {}", file.display());
|
|||
|
|
eprintln!(" Sheet: {}", sheet);
|
|||
|
|
eprintln!(" 位置: 第 {} 行, 第 {} 列", target_row + 1, c);
|
|||
|
|
} else {
|
|||
|
|
eprintln!("⚠ 警告:即将删除以下数据:");
|
|||
|
|
eprintln!(" 文件: {}", file.display());
|
|||
|
|
eprintln!(" Sheet: {}", sheet);
|
|||
|
|
eprintln!(" 删除行: 第 {} 行(共 {} 行)", target_row + 1, total_rows);
|
|||
|
|
|
|||
|
|
// 显示要删除的行的内容
|
|||
|
|
eprint!(" 内容: ");
|
|||
|
|
for c in 0..range.width() {
|
|||
|
|
if let Some(cell_data) = range.get((target_row, c)) {
|
|||
|
|
eprint!("{}", format_cell(cell_data));
|
|||
|
|
if c < range.width() - 1 {
|
|||
|
|
eprint!(" | ");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
eprintln!();
|
|||
|
|
}
|
|||
|
|
eprintln!();
|
|||
|
|
|
|||
|
|
// 请求用户确认
|
|||
|
|
eprint!("是否继续?(y/N): ");
|
|||
|
|
use std::io::{self, Write};
|
|||
|
|
io::stdout().flush().unwrap();
|
|||
|
|
|
|||
|
|
let mut input = String::new();
|
|||
|
|
io::stdin().read_line(&mut input)?;
|
|||
|
|
let input = input.trim().to_lowercase();
|
|||
|
|
|
|||
|
|
if input != "y" && input != "yes" {
|
|||
|
|
println!("✗ 操作已取消");
|
|||
|
|
return Ok(());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建新工作簿
|
|||
|
|
let mut new_workbook = Workbook::new();
|
|||
|
|
let new_worksheet = new_workbook.add_worksheet();
|
|||
|
|
new_worksheet.set_name(&sheet)?;
|
|||
|
|
|
|||
|
|
if let Some(c) = cell {
|
|||
|
|
// 删除单元格:将该单元格及其右侧的单元格左移
|
|||
|
|
let target_col = (c - 1) as usize;
|
|||
|
|
let mut new_row = 0u32;
|
|||
|
|
|
|||
|
|
for r in 0..total_rows {
|
|||
|
|
let mut new_col = 0u16;
|
|||
|
|
for col in 0..total_cols {
|
|||
|
|
if r == target_row && col == target_col {
|
|||
|
|
continue; // 跳过要删除的单元格
|
|||
|
|
}
|
|||
|
|
if let Some(cell_data) = range.get((r, col)) {
|
|||
|
|
write_cell(new_worksheet, new_row, new_col, cell_data)?;
|
|||
|
|
new_col += 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
new_row += 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
println!("✓ 操作成功:已删除第 {} 行第 {} 列的单元格", target_row + 1, c);
|
|||
|
|
} else {
|
|||
|
|
// 删除整行
|
|||
|
|
let mut new_row = 0u32;
|
|||
|
|
for r in 0..total_rows {
|
|||
|
|
if r == target_row {
|
|||
|
|
continue; // 跳过要删除的行
|
|||
|
|
}
|
|||
|
|
for c in 0..range.width() {
|
|||
|
|
if let Some(cell_data) = range.get((r, c)) {
|
|||
|
|
write_cell(new_worksheet, new_row, c as u16, cell_data)?;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
new_row += 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
println!("✓ 操作成功:已删除第 {} 行", target_row + 1);
|
|||
|
|
eprintln!(" 剩余行数: {}", total_rows - 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存文件
|
|||
|
|
new_workbook.save(file.as_path())?;
|
|||
|
|
eprintln!(" 文件: {}, Sheet: {}", file.display(), sheet);
|
|||
|
|
|
|||
|
|
Ok(())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 辅助函数:写入单元格
|
|||
|
|
fn write_cell(
|
|||
|
|
worksheet: &mut rust_xlsxwriter::Worksheet,
|
|||
|
|
row: u32,
|
|||
|
|
col: u16,
|
|||
|
|
data: &Data,
|
|||
|
|
) -> Result<(), rust_xlsxwriter::XlsxError> {
|
|||
|
|
match data {
|
|||
|
|
Data::String(s) => worksheet.write_string(row, col, s.as_str()).map(|_| ()),
|
|||
|
|
Data::Int(v) => worksheet.write_number(row, col, *v as f64).map(|_| ()),
|
|||
|
|
Data::Float(v) => worksheet.write_number(row, col, *v).map(|_| ()),
|
|||
|
|
Data::Bool(b) => worksheet.write_boolean(row, col, *b).map(|_| ()),
|
|||
|
|
_ => Ok(()),
|
|||
|
|
}
|
|||
|
|
}
|