rxlsb 0.3.0

Pure Rust XLSB (Excel Binary Workbook) reader/writer library
Documentation
# Excel XLSB格式(styleIndex)映射机制详解

## 问题背景

在实现XLSB写入器时,cell.styleIndex的正确映射是一个容易出错的关键问题。

**错误理解**:
- cell.styleIndex是全局XF索引(XF[0], XF[1], XF[2]...)
- 需要手动+1映射:styles[i] → XF[i+1]

**正确理解**:
- cell.styleIndex是styles列表索引(0-based)
- Excel/WPS读取后自动+1映射到全局XF索引

---

## XF结构说明

### BrtBeginXFs vs BrtBeginStyles

**XF全局索引结构**:
```
XF[0]: BrtBeginXFs (cell XF, ixf=0xffff)
       用于普通单元格,无特殊格式
       
XF[1+]: BrtBeginStyles (style XF, ixf=0x0000)
       用于带格式的单元格
       styles列表对应这部分
```

### styles列表与XF的映射关系

```
styles列表    全局XF索引
styles[0]  →  XF[1]  (BrtBeginStyles[0])
styles[1]  →  XF[2]  (BrtBeginStyles[1])
styles[2]  →  XF[3]  (BrtBeginStyles[2])
styles[i]  →  XF[i+1] (自动+1映射)
```

**关键点**:
- XF[0]不在styles列表中
- styles列表从XF[1]开始
- Excel/WPS自动处理+1偏移

---

## 实现要点

### 1. styles注册函数返回值

**正确实现**:
```rust
// Rust (rxlsb)
pub fn get_style_id_for_format(&mut self, format_string: &str) -> u32 {
    let format_id = self.format_registry.get_or_add_format(format_string);
    
    // 查找已存在的style
    for (i, xf) in self.style_xfs.iter().enumerate() {
        if xf.num_fmt_id == format_id {
            return i as u32;  // 返回styles列表索引
        }
    }
    
    // 创建新style
    self.style_xfs.push(StyleXF { num_fmt_id: format_id, ... });
    return (self.style_xfs.len() - 1) as u32;  // 返回styles列表索引
}
```

```c
// C (cxlsb)
int cxlsb_styles_registry_add_style(cxlsb_styles_registry_t* registry, int num_fmt_id) {
    // 查找已存在的style
    for (uint32_t i = 0; i < registry->style_count; i++) {
        if (registry->styles[i].num_fmt_id == num_fmt_id) {
            return i;  // 返回styles列表索引 ✓ 正确
        }
    }
    
    // 创建新style
    registry->styles[registry->style_count].num_fmt_id = num_fmt_id;
    return registry->style_count++;  // 返回styles列表索引 ✓ 正确
}
```

**错误实现**(不要这样写):
```rust
// ✗ 错误:手动+1
return (i + 1) as u32;  // 会导致错位
return self.style_xfs.len() as u32;  // 会导致错位
```

### 2. 写入cell.styleIndex

**写入方式**:
```rust
// 直接写入styles列表索引
write_cell_real_with_style(row, col, value, style_idx);  // style_idx是styles索引
```

**字节序列**:
```
cell.styleIndex字段(4字节uint32,小端序):
styleIndex=1 → 01 00 00 00
styleIndex=2 → 02 00 00 00
styleIndex=3 → 03 00 00 00
```

**Excel/WPS读取后**:
```
读取styleIndex=1 → 自动+1 → 使用XF[2]
读取styleIndex=2 → 自动+1 → 使用XF[3]
读取styleIndex=3 → 自动+1 → 使用XF[4]
```

---

## 实际案例验证

### 案例:format_test2.xlsb

**styles注册顺序**:
```
SheetWriter构造:
  get_style_id_for_format("m/d/yy h:mm") → ifmt=22
  styles[1] = {numFmtId: 22} → 返回1

Col 0 (date):
  get_style_id_for_format("m/d/yy h:mm") → ifmt=22
  找到styles[1] → 返回1

Col 1 (time):
  get_style_id_for_format("h:mm:ss") → ifmt=21
  styles[2] = {numFmtId: 21} → 返回2

Col 2 (currency):
  get_style_id_for_format("¥#,##0.00") → ifmt=164
  styles[3] = {numFmtId: 164} → 返回3

Col 3 (negRed):
  get_style_id_for_format("#,##0.00;[Red]-#,##0.00") → ifmt=165
  styles[4] = {numFmtId: 165} → 返回4
```

**XF ifmt顺序**:
```
XF[0]: ifmt=0   (cell XF, BrtBeginXFs)
XF[1]: ifmt=0   (style XF, styles[0], default)
XF[2]: ifmt=22  (style XF, styles[1], date)
XF[3]: ifmt=21  (style XF, styles[2], time)
XF[4]: ifmt=164 (style XF, styles[3], currency)
XF[5]: ifmt=165 (style XF, styles[4], negRed)
```

**cell.styleIndex映射验证**:
```
Col 0: 写入styleIndex=1 → Excel/WPS +1 → XF[2] (ifmt=22, date) ✓
Col 1: 写入styleIndex=2 → Excel/WPS +1 → XF[3] (ifmt=21, time) ✓
Col 2: 写入styleIndex=3 → Excel/WPS +1 → XF[4] (ifmt=164, currency) ✓
Col 3: 写入styleIndex=4 → Excel/WPS +1 → XF[5] (ifmt=165, negRed) ✓
```

---

## 错误案例分析

### 第一次错误(rxlsb)

**错误代码**:
```rust
pub fn get_style_id_for_format(&mut self, format_string: &str) -> u32 {
    for (i, xf) in self.style_xfs.iter().enumerate() {
        if xf.num_fmt_id == format_id {
            return (i + 1) as u32;  // ✗ 错误:手动+1
        }
    }
    return self.style_xfs.len() as u32;  // ✗ 错误:手动+1
}
```

**结果**:
```
写入styleIndex=2 → Excel/WPS +1 → XF[3]
期望XF[2] (ifmt=22),实际XF[3] (ifmt=21)
→ 所有列错位一位!
```

**用户反馈**:
```
"要求:第一列日期,第二列时间,第三列金额,第四列数值(有负数,标红)
 实际:第一列时间,第二列人民币符号+4xxxxx.yy,第三列数值,负红,第四列数值
 感觉是都错位了一位"
```

### 第二次修正(正确)

**正确代码**:
```rust
pub fn get_style_id_for_format(&mut self, format_string: &str) -> u32 {
    for (i, xf) in self.style_xfs.iter().enumerate() {
        if xf.num_fmt_id == format_id {
            return i as u32;  // ✓ 正确:返回styles索引
        }
    }
    return (self.style_xfs.len() - 1) as u32;  // ✓ 正确:返回styles索引
}
```

**结果**:
```
写入styleIndex=1 → Excel/WPS +1 → XF[2] (ifmt=22, date) ✓
所有列显示正确!
```

---

## cxlsb检查结果

**cxlsb当前实现**(src/format/styles_registry.c):
```c
int cxlsb_styles_registry_add_style(cxlsb_styles_registry_t* registry, int num_fmt_id) {
    for (uint32_t i = 0; i < registry->style_count; i++) {
        if (registry->styles[i].num_fmt_id == num_fmt_id) {
            return i;  // ✓ 正确:返回styles列表索引
        }
    }
    
    registry->styles[registry->style_count].num_fmt_id = num_fmt_id;
    return registry->style_count++;  // ✓ 正确:返回styles列表索引
}
```

**结论**:cxlsb的实现是正确的!不需要修改。

---

## 核心要点总结

1. **cell.styleIndex是styles列表索引(0-based)**
   - 不是全局XF索引
   - 不需要手动+1

2. **Excel/WPS自动+1映射**
   - styles[i] → XF[i+1]
   - 因为XF[0]在BrtBeginXFs,XF[1+]在BrtBeginStyles

3. **get_style_id_for_format返回值**
   - 返回styles列表索引i
   - 不要返回i+1(会导致错位)

4. **写入cell时**
   - 直接写入styles列表索引
   - Excel/WPS会自动处理偏移

5. **验证方法**
   - 检查XF ifmt顺序
   - 验证styleIndex映射
   - 在Excel/WPS中打开确认显示

---

## 参考文件

- rxlsb: `/disk2/helly_data/code/rust/rxlsb/src/format/styles_registry.rs`
- cxlsb: `/disk2/helly_data/code/ansi_c/cxlsb/src/format/styles_registry.c`
- 测试: `/disk2/helly_data/code/rust/rxlsb/examples/format_test2.rs`

---

## 相关知识

- MS-XLSB规范:[MS-XLSB] BrtXF结构
- Excel内置格式ID:0-22
- 自定义格式ID起始:164
- BrtCellReal结构:cell(8字节) + xnum(8字节)
- cell结构:column(4字节) + styleIndex(4字节)