# 表格提取

从 CAD 图纸中自动识别并提取表格数据。

## 概念

`extractTables()` 函数可以自动识别图纸中由直线和文字构成的表格，提取表格结构和单元格数据。

支持识别：
- 由 Line/Polyline 构成的表格边框
- 单元格中的 Text/MText 内容
- 合并单元格

---

## 基本用法

### 全图提取

```javascript
const { extractTables } = vjcad;

// 全图提取（最简单）
const result = extractTables();

if (result.error) {
    console.error("提取失败:", result.error);
} else if (result.tables.length === 0) {
    console.log("未识别到表格数据");
} else {
    console.log(`识别到 ${result.tables.length} 个表格`);
    
    result.tables.forEach((table, index) => {
        console.log(`=== 表格 ${index + 1} ===`);
        console.log(`行数: ${table.rowCount}, 列数: ${table.colCount}`);
        
        // 打印表格数据
        for (let r = 0; r < table.rowCount; r++) {
            const row = table.datas[r] || [];
            console.log(`第${r + 1}行:`, row.join(' | '));
        }
    });
}
```

### 指定区域提取

```javascript
const result = extractTables({
    bounds: { 
        minX: 0, 
        minY: -200, 
        maxX: 500, 
        maxY: 100 
    }
});
```

### 指定图层提取

```javascript
const result = extractTables({
    layers: ['表格图层', '标注层']
});
```

---

## 完整参数

```javascript
const result = extractTables({
    // 提取范围（可选）
    bounds: { minX: 0, minY: -200, maxX: 500, maxY: 100 },
    
    // 指定图层（可选）
    layers: ['图层1', '图层2'],
    
    // 包含的实体类型
    includeLine: true,      // 包含直线（默认 true）
    includePolyline: true,  // 包含多段线（默认 true）
    includeText: true,      // 包含单行文字（默认 true）
    includeMText: true,     // 包含多行文字（默认 true）
    includeBlocks: true,    // 包含块中实体（默认 true）
    includeGroups: true,    // 包含组中实体（默认 true）
    
    // 识别参数
    digit: 4,               // 小数精度（默认 4）
    tol: 0,                 // 误差值（0 为自动）
    tableEdgeMinPoint: 12,  // 表格边线最少端点数（默认 12）
    tableTextMinCount: 4,   // 表格最少文字数（默认 4）
    cellEmptyRatio: 90,     // 空值所占最大比例（默认 90）
    
    // 调试
    debug: false            // 调试模式
});
```

---

## 返回数据结构

### 返回值

```typescript
interface TableExtractResult {
    tables: TableData[];      // 表格数组
    tol: number;              // 实际使用的误差值
    error?: string;           // 错误信息
    debug_table_lines?: string;   // 调试数据（debug=true 时）
    debug_table_bounds?: string;
    debug_table_posflag?: string;
    debug_table_corner?: string;
}
```

### 表格数据

```typescript
interface TableData {
    rowCount: number;              // 行数
    colCount: number;              // 列数
    rect: string;                  // 表格范围（字符串形式边界）
    rows: string[];                // 行位置信息
    cols: string[];                // 列位置信息
    spans: Record<string, string>; // 合并单元格信息（键→字符串编码）
    datas: string[][];             // 单元格数据 [行][列]
    attr: TableAttr;               // 识别过程属性
}

interface TableAttr {
    cellEmptyRatio: number;
    tableTextCount: number;
    tableCellMaxCount: number;
    unLinkLineRatio: number;
}
```

---

## 示例：绘制并提取表格

```javascript
const { 
    MainView, initCadContainer, 
    LineEnt, TextEnt, TextAlignmentEnum, 
    Engine, extractTables, message 
} = vjcad;

// 初始化
const cadView = new MainView({
    appname: "唯杰CAD",
    version: "v1.0.0",
    serviceUrl: env.serviceUrl,
    accessToken: env.accessToken,
    sidebarStyle: "none"
});

initCadContainer("map", cadView);
await cadView.onLoad();

// === 1. 绘制表格 ===
const tableStartX = 0;
const tableStartY = 0;
const cellWidth = 100;
const cellHeight = 40;
const rows = 3;
const cols = 4;

// 表格数据
const tableData = [
    ['序号', '名称', '数量', '单位'],
    ['1', '钢筋', '100', '吨'],
    ['2', '混凝土', '500', '立方米']
];

// 绘制水平线
for (let r = 0; r <= rows; r++) {
    const y = tableStartY - r * cellHeight;
    const line = new LineEnt(
        [tableStartX, y],
        [tableStartX + cols * cellWidth, y]
    );
    line.setDefaults();
    Engine.addEntities(line);
}

// 绘制垂直线
for (let c = 0; c <= cols; c++) {
    const x = tableStartX + c * cellWidth;
    const line = new LineEnt(
        [x, tableStartY],
        [x, tableStartY - rows * cellHeight]
    );
    line.setDefaults();
    Engine.addEntities(line);
}

// 填充文字内容
for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
        const text = new TextEnt();
        text.text = tableData[r][c];
        text.insertionPoint = [
            tableStartX + c * cellWidth + cellWidth / 2,
            tableStartY - r * cellHeight - cellHeight / 2
        ];
        text.height = 15;
        text.textAlignment = TextAlignmentEnum.MidCenter;
        text.setDefaults();
        Engine.addEntities(text);
    }
}

Engine.zoomExtents();
message.info("表格已绘制完成");

// === 2. 提取表格 ===
const result = extractTables();

if (result.tables.length > 0) {
    const table = result.tables[0];
    
    message.info(`识别到表格: ${table.rowCount}行 x ${table.colCount}列`);
    
    // 打印表头
    if (table.datas && table.datas[0]) {
        message.info("表头: " + table.datas[0].join(', '));
    }
    
    // 遍历数据
    for (let r = 0; r < table.rowCount; r++) {
        const row = table.datas[r] || [];
        console.log(`第${r + 1}行:`, row);
    }
}
```

---

## 处理合并单元格

```javascript
const result = extractTables();

result.tables.forEach(table => {
    // 检查合并单元格
    if (table.spans && Object.keys(table.spans).length > 0) {
        console.log("合并单元格信息:", table.spans);
        
        // spans 类型为 Record<string, string>——键为单元格标识，值为合并信息字符串
        for (const [key, span] of Object.entries(table.spans)) {
            console.log(`单元格 ${key} 合并信息:`, span);
        }
    }
});
```

---

## 导出为 JSON/CSV

### 导出为 JSON

```javascript
const result = extractTables();

if (result.tables.length > 0) {
    const table = result.tables[0];
    const json = JSON.stringify(table.datas, null, 2);
    console.log(json);
}
```

### 导出为 CSV

```javascript
const result = extractTables();

if (result.tables.length > 0) {
    const table = result.tables[0];
    const csv = table.datas.map(row => row.join(',')).join('\n');
    console.log(csv);
    
    // 下载 CSV 文件
    const blob = new Blob([csv], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'table.csv';
    a.click();
    URL.revokeObjectURL(url);
}
```

---

## 注意事项

1. **表格结构**：函数只能识别由直线/多段线构成的规则表格
2. **文字位置**：文字必须在单元格范围内才能被正确归类
3. **误差容限**：复杂图纸可能需要调整 `tol` 参数
4. **性能**：大型图纸建议使用 `bounds` 或 `layers` 限制范围

---

## API 参考

### extractTables(options?)

| 参数 | 类型 | 说明 |
|------|------|------|
| `bounds` | Bounds | 提取范围 `{minX, minY, maxX, maxY}` |
| `layers` | string[] | 指定图层 |
| `includeLine` | boolean | 包含直线（默认 true）|
| `includePolyline` | boolean | 包含多段线（默认 true）|
| `includeText` | boolean | 包含单行文字（默认 true）|
| `includeMText` | boolean | 包含多行文字（默认 true）|
| `includeBlocks` | boolean | 包含块中实体（默认 true）|
| `includeGroups` | boolean | 包含组中实体（默认 true）|
| `digit` | number | 小数精度（默认 4）|
| `tol` | number | 误差值（0 为自动）|
| `tableEdgeMinPoint` | number | 表格边线最少端点数（默认 12）|
| `tableTextMinCount` | number | 表格最少文字数（默认 4）|
| `cellEmptyRatio` | number | 空值所占最大比例（默认 90）|
| `debug` | boolean | 调试模式 |

### 返回值

| 字段 | 类型 | 说明 |
|------|------|------|
| `error` | string | 错误信息（可选）|
| `tables` | TableData[] | 表格数据数组 |

### TableData

| 字段 | 类型 | 说明 |
|------|------|------|
| `rowCount` | number | 行数 |
| `colCount` | number | 列数 |
| `rect` | string | 表格范围（字符串形式）|
| `rows` | string[] | 行位置信息 |
| `cols` | string[] | 列位置信息 |
| `datas` | string[][] | 单元格数据 `[行][列]` |
| `spans` | Record<string, string> | 合并单元格信息 |
| `attr` | TableAttr | 识别过程属性 |
