Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9dc72c1ac0 | |||
| cb5d7d0768 | |||
| 72c2431f0f | |||
| 1abac4e28d | |||
| e9fe98b1f6 | |||
| 800755abf5 | |||
| 68e9f114be | |||
| 642fa49340 | |||
| 436475174d | |||
| f3df0a85da |
@@ -220,12 +220,14 @@ zip docs-backup.zip backup-log.xlsx -n complete-backup.zip -v
|
|||||||
|
|
||||||
```
|
```
|
||||||
tools/
|
tools/
|
||||||
|
├── .gitignore # Git 忽略文件配置
|
||||||
|
├── README.md # 本文件
|
||||||
├── cutPic/ # 图片裁切工具
|
├── cutPic/ # 图片裁切工具
|
||||||
│ ├── src/
|
│ ├── src/
|
||||||
│ │ └── main.rs
|
│ │ └── main.rs
|
||||||
│ ├── Cargo.toml
|
│ ├── Cargo.toml
|
||||||
│ ├── README.md
|
│ ├── README.md
|
||||||
│ └── target/
|
│ └── target/ # (被 Git 忽略)
|
||||||
│ └── release/
|
│ └── release/
|
||||||
│ └── cutpic.exe
|
│ └── cutpic.exe
|
||||||
│
|
│
|
||||||
@@ -234,23 +236,23 @@ tools/
|
|||||||
│ │ └── main.rs
|
│ │ └── main.rs
|
||||||
│ ├── Cargo.toml
|
│ ├── Cargo.toml
|
||||||
│ ├── README.md
|
│ ├── README.md
|
||||||
│ └── target/
|
│ └── target/ # (被 Git 忽略)
|
||||||
│ └── release/
|
│ └── release/
|
||||||
│ └── zip.exe
|
│ └── zip.exe
|
||||||
│
|
│
|
||||||
├── dexcel/ # Excel 读写工具
|
└── dexcel/ # Excel 读写工具
|
||||||
│ ├── src/
|
├── src/
|
||||||
│ │ └── main.rs
|
│ └── main.rs
|
||||||
│ ├── Cargo.toml
|
├── Cargo.toml
|
||||||
│ ├── README.md
|
├── README.md
|
||||||
│ ├── example.bat
|
├── example.bat
|
||||||
│ └── target/
|
└── target/ # (被 Git 忽略)
|
||||||
│ └── release/
|
└── release/
|
||||||
│ └── dexcel.exe
|
└── dexcel.exe
|
||||||
│
|
|
||||||
└── README.md # 本文件
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**注意**: `target/` 目录和 `Cargo.lock` 文件已被添加到 `.gitignore`,不会被提交到 Git 仓库。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🤝 贡献指南
|
## 🤝 贡献指南
|
||||||
|
|||||||
+7
-6
@@ -1,11 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dexcel"
|
name = "dexcel"
|
||||||
version = "0.1.0"
|
version = "0.4.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
calamine = { version = "0.26", features = ["dates"] }
|
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
|
||||||
rust_xlsxwriter = "0.80"
|
|
||||||
tabled = "0.16"
|
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
clap = { version = "4.6", features = ["derive"] }
|
||||||
|
umya-spreadsheet = "2.3"
|
||||||
|
tabled = "0.20"
|
||||||
|
thiserror = "2.0"
|
||||||
|
tempfile = "3.27"
|
||||||
+234
-56
@@ -1,22 +1,28 @@
|
|||||||
# dexcel - Excel 读写工具 📊
|
# dexcel - Excel 读写工具 📊
|
||||||
|
|
||||||
一个简单易用的命令行 Excel 处理工具,支持查询、统计、插入、更新和删除操作。
|
一个简单易用的命令行 Excel 处理工具,支持查询、统计、插入、更新、删除和样式设置操作。
|
||||||
|
|
||||||
|
**当前版本**: v0.4.0
|
||||||
|
|
||||||
## ✨ 功能特性
|
## ✨ 功能特性
|
||||||
|
|
||||||
### 核心操作
|
### 核心操作
|
||||||
- 🔍 **query**: 灵活查询 Excel 数据,支持行范围选择和自定义分隔符
|
- 🔍 **query**: 灵活查询 Excel 数据,支持行范围选择
|
||||||
- 📊 **count**: 快速统计行数,适合脚本使用
|
- 📊 **count**: 快速统计行数,适合脚本使用
|
||||||
- ➕ **insert**: 插入新行,支持指定位置或追加到末尾
|
- ➕ **insert**: 插入新行,支持指定位置或追加到末尾
|
||||||
- ✏️ **update**: 更新现有数据,支持部分列更新
|
- ✏️ **update**: 更新现有数据,支持部分列更新
|
||||||
- ❌ **delete**: 删除行或单元格,带安全确认机制
|
- ❌ **delete**: 删除行,支持灵活的起始位置和数量控制
|
||||||
|
- 🎨 **style**: 设置样式(行高、列宽、自动换行)
|
||||||
|
|
||||||
### 通用特性
|
### 通用特性
|
||||||
- 📑 **多 Sheet 支持**: 所有操作都支持 `--sheet` 参数指定工作表
|
- 📑 **多 Sheet 支持**: 所有操作都支持 `--sheet` 参数指定工作表
|
||||||
- 🔢 **负数索引**: 支持负数行号(-1 表示最后一行)
|
- 🔢 **负数索引**: 支持负数行号(-1 表示最后一行)
|
||||||
- 🆕 **自动创建**: insert 操作在文件不存在时自动创建
|
- ⚠️ **安全确认**: delete 操作默认需要用户确认,可用 `-y` 跳过
|
||||||
- ⚠️ **安全确认**: delete 操作需要用户确认
|
- 💾 **样式保留**: 使用 umya-spreadsheet 库,完整保留 Excel 样式
|
||||||
- 📤 **灵活输出**: query 支持自定义分隔符(默认 |)
|
- 📤 **表格输出**: query 以美观的表格格式显示数据
|
||||||
|
- 📅 **日期智能**: 自动识别和格式化日期时间单元格
|
||||||
|
- 🧹 **备份清理**: 操作成功后自动删除 .bak 备份文件
|
||||||
|
- ↩️ **换行支持**: 智能处理各种换行符格式,自动开启换行并调整行高
|
||||||
|
|
||||||
## 📦 安装
|
## 📦 安装
|
||||||
|
|
||||||
@@ -89,13 +95,26 @@ dexcel data.xlsx count --sheet "Sales"
|
|||||||
|
|
||||||
**输出**: 只输出一个数字,表示总行数,适合脚本中使用。
|
**输出**: 只输出一个数字,表示总行数,适合脚本中使用。
|
||||||
|
|
||||||
|
### 3. 创建新文件 (new)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建新的 Excel 文件
|
||||||
|
dexcel new mydata.xlsx
|
||||||
|
|
||||||
|
# 指定 Sheet 名称
|
||||||
|
dexcel new mydata.xlsx --sheet "数据表"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### new 参数说明
|
||||||
|
- `<NAME>`: 文件名(必填)**必需**
|
||||||
|
- `--sheet <SHEET>`: Sheet 名称(可选,默认为 Sheet1)
|
||||||
|
|
||||||
|
**注意**:如果文件已存在,会报错提示。
|
||||||
|
|
||||||
### 3. 插入数据 (insert)
|
### 3. 插入数据 (insert)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 创建新文件并插入表头
|
# 追加数据到末尾(文件必须已存在)
|
||||||
dexcel new-file.xlsx insert "姓名|年龄|城市"
|
|
||||||
|
|
||||||
# 追加数据到末尾
|
|
||||||
dexcel data.xlsx insert "张三|18|北京"
|
dexcel data.xlsx insert "张三|18|北京"
|
||||||
|
|
||||||
# 在指定位置插入(第2行)
|
# 在指定位置插入(第2行)
|
||||||
@@ -114,6 +133,20 @@ dexcel data.xlsx insert "数据" --sheet "Sheet2"
|
|||||||
- `--sheet <SHEET>`: Sheet 名称(可选)
|
- `--sheet <SHEET>`: Sheet 名称(可选)
|
||||||
- `--split <SPLIT>`: 分隔符(默认 |)
|
- `--split <SPLIT>`: 分隔符(默认 |)
|
||||||
|
|
||||||
|
**注意**:insert 命令要求文件必须已存在,如果文件不存在会报错。
|
||||||
|
|
||||||
|
**换行符支持**:
|
||||||
|
```bash
|
||||||
|
# 使用字面量 \n(单引号)
|
||||||
|
dexcel data.xlsx insert '第一行\n第二行\n第三行|其他列'
|
||||||
|
|
||||||
|
# 使用 PowerShell 转义(双引号 + `r`n)
|
||||||
|
dexcel data.xlsx insert "Line1`r`nLine2,Col2" --split ","
|
||||||
|
|
||||||
|
# 自动开启换行并调整行高
|
||||||
|
# 无需手动设置,程序会自动检测换行符并调整
|
||||||
|
```
|
||||||
|
|
||||||
### 4. 更新数据 (update)
|
### 4. 更新数据 (update)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -137,29 +170,75 @@ dexcel data.xlsx update "新值" --row 1 --sheet "Sheet2"
|
|||||||
- `--sheet <SHEET>`: Sheet 名称(可选)
|
- `--sheet <SHEET>`: Sheet 名称(可选)
|
||||||
- `--split <SPLIT>`: 分隔符(默认 |)
|
- `--split <SPLIT>`: 分隔符(默认 |)
|
||||||
|
|
||||||
|
**换行符支持**:
|
||||||
|
```bash
|
||||||
|
# 更新单元格为多行文本
|
||||||
|
dexcel data.xlsx update '新内容\n带换行' --row 5
|
||||||
|
|
||||||
|
# 自动开启换行并调整行高
|
||||||
|
dexcel data.xlsx update "Line1`nLine2" --row 3 --split ","
|
||||||
|
```
|
||||||
|
|
||||||
### 5. 删除数据 (delete)
|
### 5. 删除数据 (delete)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 删除整行(带确认)
|
# 删除第5行(需要确认)
|
||||||
dexcel data.xlsx delete --row 5
|
dexcel data.xlsx delete 5
|
||||||
|
|
||||||
|
# 从第3行开始删除5行(跳过确认)
|
||||||
|
dexcel data.xlsx delete 3 --count 5 -y
|
||||||
|
|
||||||
# 删除最后一行
|
# 删除最后一行
|
||||||
dexcel data.xlsx delete --row -1
|
dexcel data.xlsx delete -1 -y
|
||||||
|
|
||||||
# 删除单元格(第3行第2列)
|
# 删除最后3行
|
||||||
dexcel data.xlsx delete --row 3 --cell 2
|
dexcel data.xlsx delete -3 --count 3 -y
|
||||||
|
|
||||||
# 指定 Sheet
|
# 指定 Sheet
|
||||||
dexcel data.xlsx delete --row 1 --sheet "Sheet2"
|
dexcel data.xlsx delete 1 --sheet "Sheet2"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### delete 参数说明
|
#### delete 参数说明
|
||||||
- `[COUNT]`: 删除数量(默认1)
|
- `<START>`: 起始行号(必需,支持负数)
|
||||||
- `--row <ROW>`: 行号(必需,支持负数)
|
- 正数:从前往后数(如 `5` 表示第5行)
|
||||||
- `--cell <CELL>`: 列号(可选,不指定则删除整行)
|
- 负数:从后往前数(如 `-1` 表示最后一行)
|
||||||
|
- `--count <COUNT>`: 删除的行数(默认1)
|
||||||
|
- `-y, --yes`: 跳过确认
|
||||||
- `--sheet <SHEET>`: Sheet 名称(可选)
|
- `--sheet <SHEET>`: Sheet 名称(可选)
|
||||||
|
|
||||||
**注意**: delete 操作需要用户确认(输入 y 继续)
|
**使用示例**:
|
||||||
|
- `dexcel file.xlsx delete 5` - 删除第5行(需确认)
|
||||||
|
- `dexcel file.xlsx delete 5 --count 3 -y` - 从第5行开始删除3行(跳过确认)
|
||||||
|
- `dexcel file.xlsx delete -1 --count 2 -y` - 删除最后2行
|
||||||
|
|
||||||
|
**注意**: delete 操作默认需要用户确认(输入 y 继续),可使用 `-y` 跳过确认。
|
||||||
|
|
||||||
|
### 6. 设置样式 (style)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置第1行的行高
|
||||||
|
dexcel data.xlsx style --rows 1 --row-height 30
|
||||||
|
|
||||||
|
# 设置A列的列宽
|
||||||
|
dexcel data.xlsx style --cols 1 --col-width 20
|
||||||
|
|
||||||
|
# 设置自动换行
|
||||||
|
dexcel data.xlsx style --rows 1 --cols 1 --wrap-text
|
||||||
|
|
||||||
|
# 组合设置
|
||||||
|
dexcel data.xlsx style --rows 1 2 3 --cols 1 2 --row-height 25 --col-width 15 --wrap-text
|
||||||
|
|
||||||
|
# 指定 Sheet
|
||||||
|
dexcel data.xlsx style --rows 1 --row-height 20 --sheet "Sheet2"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### style 参数说明
|
||||||
|
- `--rows <ROWS>`: 行号列表(支持负数,不指定则应用到所有行)
|
||||||
|
- `--cols <COLS>`: 列号列表(不指定则应用到所有列)
|
||||||
|
- `--row-height <ROW_HEIGHT>`: 行高(像素)
|
||||||
|
- `--col-width <COL_WIDTH>`: 列宽(字符数)
|
||||||
|
- `--wrap-text`: 启用自动换行
|
||||||
|
- `--sheet <SHEET>`: Sheet 名称(可选)
|
||||||
|
|
||||||
## 💡 使用示例
|
## 💡 使用示例
|
||||||
|
|
||||||
@@ -218,14 +297,14 @@ done
|
|||||||
### 示例 4: 数据清理
|
### 示例 4: 数据清理
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 删除错误数据行
|
# 删除第5行
|
||||||
dexcel data.xlsx delete --row 10
|
dexcel data.xlsx delete 5 -y
|
||||||
|
|
||||||
# 删除最后一行
|
# 从第10行开始删除5行
|
||||||
dexcel data.xlsx delete --row -1
|
dexcel data.xlsx delete 10 --count 5 -y
|
||||||
|
|
||||||
# 删除特定单元格
|
# 删除最后3行
|
||||||
dexcel data.xlsx delete --row 5 --cell 3
|
dexcel data.xlsx delete -3 --count 3 -y
|
||||||
```
|
```
|
||||||
|
|
||||||
### 示例 5: 多 Sheet 操作
|
### 示例 5: 多 Sheet 操作
|
||||||
@@ -242,6 +321,43 @@ dexcel data.xlsx query --sheet "Sales"
|
|||||||
dexcel data.xlsx count --sheet "Sales"
|
dexcel data.xlsx count --sheet "Sales"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 示例 6: 设置样式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置第1行的行高为30像素
|
||||||
|
dexcel data.xlsx style --rows 1 --row-height 30
|
||||||
|
|
||||||
|
# 设置A列的列宽为20字符
|
||||||
|
dexcel data.xlsx style --cols 1 --col-width 20
|
||||||
|
|
||||||
|
# 设置自动换行
|
||||||
|
dexcel data.xlsx style --rows 1 --cols 1 --wrap-text
|
||||||
|
|
||||||
|
# 组合设置:多行多列,设置行高、列宽和自动换行
|
||||||
|
dexcel data.xlsx style --rows 1 2 3 --cols 1 2 --row-height 25 --col-width 15 --wrap-text
|
||||||
|
|
||||||
|
# 指定 Sheet
|
||||||
|
dexcel data.xlsx style --rows 1 --row-height 20 --sheet "Sheet2"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 7: 处理多行文本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 插入带换行的内容(单引号中使用 \n)
|
||||||
|
dexcel data.xlsx insert '地址:北京市\n朝阳区\n建国路100号|备注'
|
||||||
|
|
||||||
|
# 使用 PowerShell 转义符(双引号中使用 `r`n)
|
||||||
|
dexcel data.xlsx insert "Line1`r`nLine2`r`nLine3,Col2" --split ","
|
||||||
|
|
||||||
|
# 更新为多行文本
|
||||||
|
dexcel data.xlsx update '说明:第一项\n说明:第二项' --row 2
|
||||||
|
|
||||||
|
# 程序会自动:
|
||||||
|
# 1. 识别并转换所有换行格式(\r\n, \n, `r`n)
|
||||||
|
# 2. 开启单元格的自动换行功能
|
||||||
|
# 3. 根据行数自动调整行高(每行约15磅)
|
||||||
|
```
|
||||||
|
|
||||||
## 📊 输出格式
|
## 📊 输出格式
|
||||||
|
|
||||||
### query 输出(表格格式)
|
### query 输出(表格格式)
|
||||||
@@ -290,34 +406,26 @@ dexcel data.xlsx count --sheet "Sales"
|
|||||||
|
|
||||||
**delete 成功**:
|
**delete 成功**:
|
||||||
```
|
```
|
||||||
✓ 操作成功:已删除第 5 行
|
✓ 已删除第 5 至 7 行(共 3 行)
|
||||||
剩余行数: 9
|
|
||||||
文件: data.xlsx, Sheet: Sheet1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## ⚙️ 技术细节
|
## ⚙️ 技术细节
|
||||||
|
|
||||||
### 依赖库
|
### 依赖库
|
||||||
|
|
||||||
- **calamine**: Excel 文件读取库,支持 .xlsx 格式
|
- **umya-spreadsheet**: Excel 读写库,完整保留样式,支持 .xlsx 格式
|
||||||
- **rust_xlsxwriter**: Excel 文件写入库,功能强大
|
|
||||||
- **clap**: 命令行参数解析库
|
- **clap**: 命令行参数解析库
|
||||||
|
- **tabled**: 表格格式化输出库
|
||||||
- **chrono**: 日期时间处理库
|
- **chrono**: 日期时间处理库
|
||||||
|
- **thiserror**: 错误处理库
|
||||||
|
|
||||||
### 工作原理
|
### 工作原理
|
||||||
|
|
||||||
#### 查询流程
|
#### 工作流程
|
||||||
1. 打开 Excel 文件
|
1. 打开 Excel 文件(使用 umya-spreadsheet)
|
||||||
2. 选择指定的 Sheet(默认第一个)
|
2. 选择指定的 Sheet(默认第一个)
|
||||||
3. 根据参数计算读取范围
|
3. 执行相应操作(查询/插入/更新/删除/样式设置)
|
||||||
4. 遍历单元格并提取数据
|
4. 保存文件(保留原有样式)
|
||||||
5. 以指定分隔符格式输出
|
|
||||||
|
|
||||||
#### 写入流程
|
|
||||||
1. 检查文件是否存在(insert 可自动创建)
|
|
||||||
2. 读取现有数据到新工作簿
|
|
||||||
3. 执行插入/更新/删除操作
|
|
||||||
4. 保存文件
|
|
||||||
|
|
||||||
### 数据类型支持
|
### 数据类型支持
|
||||||
|
|
||||||
@@ -327,37 +435,55 @@ dexcel data.xlsx count --sheet "Sales"
|
|||||||
- 浮点数 (Float)
|
- 浮点数 (Float)
|
||||||
- 布尔值 (Bool)
|
- 布尔值 (Bool)
|
||||||
- 日期时间 (DateTime) - 自动识别并格式化
|
- 日期时间 (DateTime) - 自动识别并格式化
|
||||||
|
- `2024-01-15` → 显示为 `2024-01-15`
|
||||||
|
- `2024-12-25 14:30:00` → 显示为 `2024-12-25 14:30:00`
|
||||||
|
- `14:30:00` → 显示为 `14:30:00`
|
||||||
- 空值 (Empty)
|
- 空值 (Empty)
|
||||||
- 错误值 (Error)
|
- 错误值 (Error)
|
||||||
|
|
||||||
**写入时:**
|
**写入时:**
|
||||||
- 所有数据都作为字符串写入
|
- 所有数据都作为字符串写入
|
||||||
|
- 日期时间自动识别并转换为 Excel 序列号
|
||||||
|
- 样式完全保留
|
||||||
|
- **换行符智能处理**:
|
||||||
|
- 真正的 `\r\n` (ASCII 13+10) → 转换为 `\n`
|
||||||
|
- 字面量 `\r\n` (4个字符) → 转换为 `\n`
|
||||||
|
- 字面量 `\n` (2个字符) → 转换为 `\n`
|
||||||
|
- 自动开启单元格自动换行
|
||||||
|
- 自动调整行高(每行约15磅)
|
||||||
|
|
||||||
## ⚠️ 注意事项
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
1. **索引从 1 开始**: 行和列的索引都是从 1 开始,不是 0
|
1. **索引从 1 开始**: 行和列的索引都是从 1 开始,不是 0
|
||||||
2. **负数索引**: `-1` = 最后一行/列,`-2` = 倒数第二
|
2. **负数索引**: `-1` = 最后一行/列,`-2` = 倒数第二
|
||||||
3. **文件自动创建**: insert 操作在文件不存在时自动创建
|
3. **文件必须存在**: insert/update/delete 操作要求文件必须已存在
|
||||||
4. **分隔符默认**: 默认使用 `|` 分隔列
|
4. **分隔符默认**: 默认使用 `|` 分隔列
|
||||||
5. **文件格式**: 仅支持 `.xlsx`,不支持 `.xls`
|
5. **文件格式**: 仅支持 `.xlsx`,不支持 `.xls`
|
||||||
6. **删除需确认**: delete 操作必须用户确认才能执行
|
6. **删除需确认**: delete 操作默认需要用户确认,可用 `-y` 跳过
|
||||||
7. **日期自动识别**: 自动识别并格式化日期单元格
|
7. **日期自动识别**: 自动识别并格式化日期单元格 (如 `2024-01-15`, `2024-12-25 14:30:00`)
|
||||||
8. **不可撤销**: delete 操作不可撤销,请谨慎操作!
|
8. **样式保留**: 使用 umya-spreadsheet 库,完整保留 Excel 样式
|
||||||
|
9. **备份清理**: 操作成功后自动删除 .bak 备份文件
|
||||||
|
10. **不可撤销**: delete 操作不可撤销,请谨慎操作!
|
||||||
|
11. **样式设置**: style 命令可设置行高、列宽、自动换行等样式
|
||||||
|
12. **换行处理**: insert/update 自动处理换行符,开启自动换行并调整行高
|
||||||
|
|
||||||
### 限制
|
### 限制
|
||||||
|
|
||||||
**读取限制:**
|
**读取限制:**
|
||||||
- 公式: 不计算公式,只读取原始值
|
- 公式: 不计算公式,只读取原始值
|
||||||
- 样式: 不读取单元格样式(颜色、字体等)
|
|
||||||
- 图表: 不支持读取图表
|
- 图表: 不支持读取图表
|
||||||
- 宏: 不支持 VBA 宏
|
- 宏: 不支持 VBA 宏
|
||||||
- 大文件: > 10MB 的文件可能较慢
|
- 大文件: > 10MB 的文件可能较慢
|
||||||
|
|
||||||
**写入限制:**
|
**写入限制:**
|
||||||
- 无样式: 不能设置颜色、字体等样式
|
|
||||||
- 无公式: 不能写入公式
|
- 无公式: 不能写入公式
|
||||||
- 覆盖警告: update 会覆盖目标单元格的原有内容
|
- update 会覆盖目标单元格的原有内容
|
||||||
- insert 下移: insert 会将原有数据下移
|
- insert 会将原有数据下移
|
||||||
|
|
||||||
|
**优势:**
|
||||||
|
- ✅ 完整保留 Excel 样式(颜色、字体、边框等)
|
||||||
|
- ✅ 支持行高、列宽、自动换行等样式设置
|
||||||
|
- ✅ 插入/删除时自动调整样式
|
||||||
|
|
||||||
## 🔧 常见问题
|
## 🔧 常见问题
|
||||||
|
|
||||||
@@ -379,7 +505,7 @@ dexcel data.xlsx insert "数据3|数据4"
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
dexcel data.xlsx query -5 # 最后5行
|
dexcel data.xlsx query -5 # 最后5行
|
||||||
dexcel data.xlsx delete --row -1 # 删除最后一行
|
dexcel data.xlsx delete -1 -y # 删除最后一行
|
||||||
```
|
```
|
||||||
|
|
||||||
### Q4: 如何处理中文内容?
|
### Q4: 如何处理中文内容?
|
||||||
@@ -405,6 +531,49 @@ if ((dexcel data.xlsx count) -gt 100) {
|
|||||||
|
|
||||||
不能撤销,请谨慎操作!建议先备份文件。
|
不能撤销,请谨慎操作!建议先备份文件。
|
||||||
|
|
||||||
|
### Q7: 如何跳过删除确认?
|
||||||
|
|
||||||
|
使用 `-y` 或 `--yes` 参数:
|
||||||
|
```bash
|
||||||
|
dexcel data.xlsx delete 5 --count 3 -y
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q8: 如何处理多行文本?
|
||||||
|
|
||||||
|
**方法 1: 使用单引号 + 字面量 \n**
|
||||||
|
```bash
|
||||||
|
dexcel data.xlsx insert '第一行\n第二行\n第三行'
|
||||||
|
```
|
||||||
|
|
||||||
|
**方法 2: 使用 PowerShell 双引号 + `` `r`n ``**
|
||||||
|
```powershell
|
||||||
|
dexcel data.xlsx insert "Line1`r`nLine2`r`nLine3"
|
||||||
|
```
|
||||||
|
|
||||||
|
**方法 3: 使用 Bash/Shell**
|
||||||
|
```bash
|
||||||
|
dexcel data.xlsx insert $'Line1\nLine2\nLine3'
|
||||||
|
```
|
||||||
|
|
||||||
|
程序会自动:
|
||||||
|
- ✅ 识别并转换所有换行格式
|
||||||
|
- ✅ 开启单元格自动换行
|
||||||
|
- ✅ 根据行数自动调整行高(每行约15磅)
|
||||||
|
- ✅ 保留原有样式不变
|
||||||
|
|
||||||
|
### Q9: 行高会自动调整吗?
|
||||||
|
|
||||||
|
是的!当检测到单元格包含换行符时,程序会:
|
||||||
|
1. 计算行数(换行符数量 + 1)
|
||||||
|
2. 估算所需行高(每行约15磅)
|
||||||
|
3. 只在当前行高小于估算值时才调整
|
||||||
|
4. 不会覆盖用户手动设置的行高
|
||||||
|
|
||||||
|
如果需要精确控制行高,可以使用 style 命令:
|
||||||
|
```bash
|
||||||
|
dexcel data.xlsx style --rows 5 --row-height 50
|
||||||
|
```
|
||||||
|
|
||||||
## 🎯 应用场景
|
## 🎯 应用场景
|
||||||
|
|
||||||
### 1. 数据预览
|
### 1. 数据预览
|
||||||
@@ -436,7 +605,14 @@ dexcel data.xlsx update "新值" --row 6 --cell 2
|
|||||||
### 5. 数据清理
|
### 5. 数据清理
|
||||||
删除错误数据:
|
删除错误数据:
|
||||||
```bash
|
```bash
|
||||||
dexcel data.xlsx delete --row 10
|
# 删除第5行
|
||||||
|
dexcel data.xlsx delete 5 -y
|
||||||
|
|
||||||
|
# 从第10行开始删除5行
|
||||||
|
dexcel data.xlsx delete 10 --count 5 -y
|
||||||
|
|
||||||
|
# 删除最后3行
|
||||||
|
dexcel data.xlsx delete -3 --count 3 -y
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. 自动化脚本
|
### 6. 自动化脚本
|
||||||
@@ -490,12 +666,13 @@ Write-Host "处理完成!" -ForegroundColor Green
|
|||||||
欢迎提交 Issue 和 Pull Request!
|
欢迎提交 Issue 和 Pull Request!
|
||||||
|
|
||||||
可能的改进方向:
|
可能的改进方向:
|
||||||
- 支持更多数据类型(公式等)
|
- 支持公式计算
|
||||||
- 支持单元格样式(颜色、字体等)
|
- 支持更多数据类型
|
||||||
- 支持批量写入优化
|
- 支持批量写入优化
|
||||||
- 支持复制 Sheet
|
- 支持复制 Sheet
|
||||||
- 添加数据验证功能
|
- 添加数据验证功能
|
||||||
- 支持更多输出格式(CSV, JSON 等)
|
- 支持更多输出格式(CSV, JSON 等)
|
||||||
|
- 支持条件格式化
|
||||||
|
|
||||||
## 📄 许可证
|
## 📄 许可证
|
||||||
|
|
||||||
@@ -504,10 +681,11 @@ Write-Host "处理完成!" -ForegroundColor Green
|
|||||||
## 🙏 致谢
|
## 🙏 致谢
|
||||||
|
|
||||||
感谢以下开源项目:
|
感谢以下开源项目:
|
||||||
- [calamine](https://github.com/tafia/calamine) - 优秀的 Excel 读取库
|
- [umya-spreadsheet](https://github.com/nomiddlename/umya-spreadsheet) - 优秀的 Excel 读写库,完整保留样式
|
||||||
- [rust_xlsxwriter](https://github.com/jmcnamara/rust_xlsxwriter) - 强大的 Excel 写入库
|
- [tabled](https://github.com/zhiburt/tabled) - 美观的表格格式化库
|
||||||
- [clap](https://github.com/clap-rs/clap) - 优秀的命令行参数解析库
|
- [clap](https://github.com/clap-rs/clap) - 优秀的命令行参数解析库
|
||||||
- [chrono](https://github.com/chronotope/chrono) - 日期时间处理库
|
- [chrono](https://github.com/chronotope/chrono) - 日期时间处理库
|
||||||
|
- [thiserror](https://github.com/dtolnay/thiserror) - 错误处理库
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
@echo off
|
|
||||||
set DEXCEL=%~dp0target\release\dexcel.exe
|
|
||||||
echo ====================================
|
|
||||||
echo dexcel 工具使用示例
|
|
||||||
echo ====================================
|
|
||||||
echo.
|
|
||||||
|
|
||||||
echo 1. 创建测试 Excel 文件
|
|
||||||
echo -------------------
|
|
||||||
%DEXCEL% write -f example.xlsx -x 1 -y 1 -v "姓名"
|
|
||||||
%DEXCEL% write -f example.xlsx -x 1 -y 2 -v "年龄"
|
|
||||||
%DEXCEL% write -f example.xlsx -x 1 -y 3 -v "城市"
|
|
||||||
%DEXCEL% write -f example.xlsx -x 2 -y 1 -v "张三"
|
|
||||||
%DEXCEL% write -f example.xlsx -x 2 -y 2 -v "25"
|
|
||||||
%DEXCEL% write -f example.xlsx -x 2 -y 3 -v "北京"
|
|
||||||
%DEXCEL% write -f example.xlsx -x 3 -y 1 -v "李四"
|
|
||||||
%DEXCEL% write -f example.xlsx -x 3 -y 2 -v "30"
|
|
||||||
%DEXCEL% write -f example.xlsx -x 3 -y 3 -v "上海"
|
|
||||||
echo.
|
|
||||||
|
|
||||||
echo 2. 读取整个文件
|
|
||||||
echo -------------------
|
|
||||||
%DEXCEL% read -f example.xlsx
|
|
||||||
echo.
|
|
||||||
|
|
||||||
echo 3. 读取第2行开始的数据(2行)
|
|
||||||
echo -------------------
|
|
||||||
%DEXCEL% read -f example.xlsx -r 2 -n 2
|
|
||||||
echo.
|
|
||||||
|
|
||||||
echo 4. 只读取姓名列(第1列)
|
|
||||||
echo -------------------
|
|
||||||
%DEXCEL% read -f example.xlsx -c 1 -m 1
|
|
||||||
echo.
|
|
||||||
|
|
||||||
echo 5. 读取最后一列(城市列)
|
|
||||||
echo -------------------
|
|
||||||
%DEXCEL% read -f example.xlsx -c -1 -m 1
|
|
||||||
echo.
|
|
||||||
|
|
||||||
echo 6. 读取特定单元格(第2行,第2列)
|
|
||||||
echo -------------------
|
|
||||||
%DEXCEL% read -f example.xlsx -r 2 -n 1 -c 2 -m 1
|
|
||||||
echo.
|
|
||||||
|
|
||||||
echo ====================================
|
|
||||||
echo 示例完成!
|
|
||||||
echo ====================================
|
|
||||||
+469
-526
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "ptm"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.6", features = ["derive"] }
|
||||||
|
pdf-extract = "0.7"
|
||||||
|
clipboard-win = "5.4"
|
||||||
|
thiserror = "2.0"
|
||||||
+168
@@ -0,0 +1,168 @@
|
|||||||
|
# ptm - PDF to Markdown Converter
|
||||||
|
|
||||||
|
一个简单易用的命令行 PDF 转 Markdown 工具。
|
||||||
|
|
||||||
|
## ✨ 功能特性
|
||||||
|
|
||||||
|
- 📄 **PDF 文本提取**: 从 PDF 文件中提取纯文本内容
|
||||||
|
- 📝 **Markdown 转换**: 将提取的文本转换为 Markdown 格式
|
||||||
|
- 💾 **灵活输出**: 支持原地生成或指定输出路径
|
||||||
|
- 📋 **剪贴板支持**: 可直接复制生成的 Markdown 到剪贴板
|
||||||
|
|
||||||
|
## 📦 安装
|
||||||
|
|
||||||
|
### 从源码编译
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ptm
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
### 全局安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install --path . --force
|
||||||
|
```
|
||||||
|
|
||||||
|
安装后可直接使用 `ptm` 命令。
|
||||||
|
|
||||||
|
## 🚀 使用方法
|
||||||
|
|
||||||
|
**基本格式:**
|
||||||
|
```bash
|
||||||
|
ptm <PDF文件> [选项]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 命令示例
|
||||||
|
|
||||||
|
#### 1. 基本用法(原地生成)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 将 test.pdf 转换为 test.md(同一目录)
|
||||||
|
ptm test.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 指定输出路径
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 输出到指定位置
|
||||||
|
ptm test.pdf -o output/result.md
|
||||||
|
|
||||||
|
# 使用完整路径
|
||||||
|
ptm document.pdf -o C:\Users\macro\Documents\notes.md
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 复制到剪贴板
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 转换并复制到剪贴板
|
||||||
|
ptm test.pdf -v
|
||||||
|
|
||||||
|
# 指定输出并复制
|
||||||
|
ptm test.pdf -o result.md -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### 参数说明
|
||||||
|
|
||||||
|
| 参数 | 说明 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| `<INPUT>` | PDF 文件路径(必需) | `test.pdf` |
|
||||||
|
| `-o, --output` | 输出文件路径(可选,默认与输入同名) | `-o result.md` |
|
||||||
|
| `-v, --verbose` | 复制内容到剪贴板 | `-v` |
|
||||||
|
| `-h, --help` | 显示帮助信息 | `-h` |
|
||||||
|
| `-V, --version` | 显示版本信息 | `-V` |
|
||||||
|
|
||||||
|
## 💡 使用场景
|
||||||
|
|
||||||
|
### 场景 1: 快速转换 PDF
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ptm report.pdf
|
||||||
|
# 生成 report.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 2: 批量处理
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# PowerShell 批量转换
|
||||||
|
Get-ChildItem *.pdf | ForEach-Object {
|
||||||
|
ptm $_.FullName
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 3: 转换后直接粘贴
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ptm article.pdf -v
|
||||||
|
# 现在可以直接在编辑器中 Ctrl+V 粘贴
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 4: 自定义输出位置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ptm book.pdf -o notes/book-chapter1.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 工作原理
|
||||||
|
|
||||||
|
1. **读取 PDF**: 使用 `pdf-extract` 库解析 PDF 文件
|
||||||
|
2. **提取文本**: 提取 PDF 中的所有文本内容
|
||||||
|
3. **格式转换**:
|
||||||
|
- 清理多余空行
|
||||||
|
- 保留段落结构
|
||||||
|
- 转换为纯文本 Markdown
|
||||||
|
4. **写入文件**: 保存为 `.md` 文件
|
||||||
|
5. **可选复制**: 如指定 `-v`,复制到剪贴板
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
1. **文本型 PDF**: 仅支持包含文本层的 PDF,扫描版 PDF 需要 OCR 处理
|
||||||
|
2. **格式限制**: 当前版本主要提取纯文本,复杂格式(表格、图片)可能需要手动调整
|
||||||
|
3. **编码支持**: 支持 UTF-8 编码,包括中文等多语言内容
|
||||||
|
4. **文件大小**: 大文件可能需要较长时间处理
|
||||||
|
|
||||||
|
## 🔧 技术细节
|
||||||
|
|
||||||
|
### 依赖库
|
||||||
|
|
||||||
|
- **pdf-extract**: PDF 文本提取库
|
||||||
|
- **clap**: 命令行参数解析
|
||||||
|
- **clipboard-win**: Windows 剪贴板操作
|
||||||
|
- **thiserror**: 错误处理
|
||||||
|
|
||||||
|
### 转换逻辑
|
||||||
|
|
||||||
|
当前的 Markdown 转换采用简化策略:
|
||||||
|
- 保留原始段落结构
|
||||||
|
- 合并连续空行为单个空行
|
||||||
|
- 去除每行首尾空白
|
||||||
|
- 不尝试识别标题层级(需要更复杂的字体分析)
|
||||||
|
|
||||||
|
## 🎯 未来改进方向
|
||||||
|
|
||||||
|
- [ ] 智能识别标题层级(基于字体大小)
|
||||||
|
- [ ] 支持列表和表格转换
|
||||||
|
- [ ] 支持图片提取
|
||||||
|
- [ ] 更好的段落检测
|
||||||
|
- [ ] 支持 OCR 处理扫描版 PDF
|
||||||
|
- [ ] 批量处理模式
|
||||||
|
- [ ] 支持更多输出格式(HTML, TXT 等)
|
||||||
|
|
||||||
|
## 🤝 贡献
|
||||||
|
|
||||||
|
欢迎提交 Issue 和 Pull Request!
|
||||||
|
|
||||||
|
## 📄 许可证
|
||||||
|
|
||||||
|
本项目采用 MIT 许可证。
|
||||||
|
|
||||||
|
## 🙏 致谢
|
||||||
|
|
||||||
|
感谢以下开源项目:
|
||||||
|
- [pdf-extract](https://github.com/pdf-extract/pdf-extract-rs) - PDF 文本提取库
|
||||||
|
- [clap](https://github.com/clap-rs/clap) - 命令行参数解析库
|
||||||
|
- [clipboard-win](https://github.com/DoumanAsh/clipboard-win) - Windows 剪贴板库
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Made with ❤️ using Rust**
|
||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::fs;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
enum PtmError {
|
||||||
|
#[error("PDF 解析错误: {0}")]
|
||||||
|
PdfExtract(String),
|
||||||
|
|
||||||
|
#[error("文件 IO 错误: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("剪贴板操作失败: {0}")]
|
||||||
|
Clipboard(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, PtmError>;
|
||||||
|
|
||||||
|
/// PDF to Markdown converter
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(name = "ptm", version, about = "PDF 转 Markdown 工具")]
|
||||||
|
struct Args {
|
||||||
|
/// PDF 文件路径
|
||||||
|
input: PathBuf,
|
||||||
|
|
||||||
|
/// 输出文件路径(默认与输入文件同名,扩展名为 .md)
|
||||||
|
#[arg(short, long)]
|
||||||
|
output: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// 复制生成的 Markdown 内容到剪贴板
|
||||||
|
#[arg(short, long)]
|
||||||
|
verbose: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
if let Err(e) = run(args) {
|
||||||
|
eprintln!("❌ 错误: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(args: Args) -> Result<()> {
|
||||||
|
// 1. 验证输入文件存在
|
||||||
|
if !args.input.exists() {
|
||||||
|
return Err(PtmError::Io(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::NotFound,
|
||||||
|
format!("文件不存在: {}", args.input.display()),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("📄 正在处理: {}", args.input.display());
|
||||||
|
|
||||||
|
// 2. 提取 PDF 文本
|
||||||
|
let output_doc = pdf_extract::extract_text(&args.input)
|
||||||
|
.map_err(|e| PtmError::PdfExtract(e.to_string()))?;
|
||||||
|
|
||||||
|
// 3. 转换为简单的 Markdown 格式
|
||||||
|
let markdown = convert_to_markdown(&output_doc);
|
||||||
|
|
||||||
|
// 4. 确定输出路径
|
||||||
|
let output_path = match args.output {
|
||||||
|
Some(path) => path,
|
||||||
|
None => {
|
||||||
|
let mut path = args.input.clone();
|
||||||
|
path.set_extension("md");
|
||||||
|
path
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 5. 写入文件
|
||||||
|
fs::write(&output_path, &markdown)?;
|
||||||
|
println!("✅ 已生成: {}", output_path.display());
|
||||||
|
|
||||||
|
// 6. 如果需要,复制到剪贴板
|
||||||
|
if args.verbose {
|
||||||
|
copy_to_clipboard(&markdown)?;
|
||||||
|
println!("📋 已复制到剪贴板");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将 PDF 提取的文本转换为 Markdown 格式
|
||||||
|
fn convert_to_markdown(text: &str) -> String {
|
||||||
|
// 简单的转换逻辑:
|
||||||
|
// 1. 保留段落结构
|
||||||
|
// 2. 清理多余的空行
|
||||||
|
// 3. 尝试识别标题(基于字体大小等元数据不可用时,只能做简单处理)
|
||||||
|
|
||||||
|
let lines: Vec<&str> = text.lines().collect();
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut prev_empty = false;
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
if !prev_empty {
|
||||||
|
result.push('\n');
|
||||||
|
prev_empty = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push_str(trimmed);
|
||||||
|
result.push('\n');
|
||||||
|
prev_empty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 复制文本到剪贴板
|
||||||
|
fn copy_to_clipboard(text: &str) -> Result<()> {
|
||||||
|
use clipboard_win::{formats, set_clipboard};
|
||||||
|
|
||||||
|
set_clipboard(formats::Unicode, text)
|
||||||
|
.map_err(|e| PtmError::Clipboard(format!("剪贴板操作失败: {:?}", e)))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user