You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从HTML/RTF生成的NSAttributedString中提取表格?

刚好做过类似的需求!当你从HTML/RTF解析出NSAttributedString后,完全可以提取里面的表格结构——不管是拿到NSTextTableBlock、NSTextTable这类原生对象,还是获取单元格的NSRange,都有可行的方案。下面就用Swift 4给你详细拆解:

核心原理

NSAttributedString解析HTML/RTF表格时,会用NSTextTable代表整个表格,NSTextTableBlock代表每个单元格,这些对象会以属性的形式绑定在对应文本的范围上。我们只需要遍历字符串的属性,就能把这些结构化信息提取出来。

方案一:提取完整的表格层级结构

如果你想实现类似嵌套遍历表格→行→单元格的操作,可以先定义一个结构体来规整数据,然后遍历属性组装结构:

// 用来规整表格结构的自定义结构体
struct ParsedTable {
    let textTable: NSTextTable
    var rows: [[NSAttributedString]] = []
}

// 解析NSAttributedString中的表格
func parseTables(from attributedString: NSAttributedString) -> [ParsedTable] {
    var tables: [ParsedTable] = []
    var currentTable: ParsedTable?
    var currentRow: [NSAttributedString] = []
    
    attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: []) { attrs, range, stop in
        // 检查当前文本范围是否属于表格单元格
        guard let textBlock = attrs[.textBlock] as? NSTextTableBlock else {
            // 不在表格里时,收尾未完成的行和表格
            if !currentRow.isEmpty {
                currentTable?.rows.append(currentRow)
                currentRow = []
            }
            if let unfinishedTable = currentTable {
                tables.append(unfinishedTable)
                currentTable = nil
            }
            return
        }
        
        // 获取单元格所属的表格
        let textTable = textBlock.textTable
        if currentTable == nil {
            currentTable = ParsedTable(textTable: textTable)
        } else if currentTable?.textTable != textTable {
            // 遇到新表格,先把之前的表格存入结果
            tables.append(currentTable!)
            currentTable = ParsedTable(textTable: textTable)
            currentRow = []
        }
        
        // 提取单元格的富文本内容
        let cellContent = attributedString.attributedSubstring(from: range)
        currentRow.append(cellContent)
        
        // 判断是否是当前行的最后一个单元格,是的话就收尾该行
        if textBlock.isLastBlockInRow {
            currentTable?.rows.append(currentRow)
            currentRow = []
        }
    }
    
    // 处理最后一个未收尾的表格
    if !currentRow.isEmpty {
        currentTable?.rows.append(currentRow)
    }
    if let finalTable = currentTable {
        tables.append(finalTable)
    }
    
    return tables
}

使用示例

就像你期望的那样,解析后可以直接嵌套遍历:

let html = """
<table style="height: 51px;" width="147">
<tbody>
<tr>
<td style="width: 65.5px;">a</td>
<td style="width: 65.5px;">b</td>
</tr>
<tr>
<td style="width: 65.5px;">c</td>
<td style="width: 65.5px;">d</td>
</tr>
</tbody>
</table>
"""

// 解析HTML为NSAttributedString
guard let htmlData = html.data(using: .utf8),
      let attributedStr = try? NSAttributedString(data: htmlData, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) else {
    fatalError("Failed to parse HTML content")
}

// 提取表格
let parsedTables = parseTables(from: attributedStr)

// 遍历表格、行、单元格
for table in parsedTables {
    print("发现一个表格,包含 \(table.rows.count) 行")
    for row in table.rows {
        let cellTexts = row.map { $0.string }
        print("当前行内容:\(cellTexts.joined(separator: " | "))")
        for cell in row {
            // 在这里处理单元格内容,比如获取文本、样式等
            print("单元格文本:\(cell.string)")
        }
    }
}

方案二:仅获取表格/单元格的NSRange

如果不需要完整的内容结构,只需要获取表格和单元格的范围,可以用这个简化版的方法:

func getTableAndCellRanges(from attributedString: NSAttributedString) -> [(tableRange: NSRange, cellRanges: [NSRange])] {
    var tableRangeInfos: [(tableRange: NSRange, cellRanges: [NSRange])] = []
    var currentCellRanges: [NSRange] = []
    var currentTableStartLocation: Int?
    
    attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: []) { attrs, range, stop in
        if let _ = attrs[.textBlock] as? NSTextTableBlock {
            // 标记表格的起始位置
            if currentTableStartLocation == nil {
                currentTableStartLocation = range.location
            }
            currentCellRanges.append(range)
        } else {
            // 离开表格范围时,计算整个表格的范围并存入
            if let startLoc = currentTableStartLocation {
                let tableRange = NSRange(location: startLoc, length: range.location - startLoc)
                tableRangeInfos.append((tableRange: tableRange, cellRanges: currentCellRanges))
                currentTableStartLocation = nil
                currentCellRanges = []
            }
        }
    }
    
    // 处理最后一个未收尾的表格
    if let startLoc = currentTableStartLocation {
        let tableRange = NSRange(location: startLoc, length: attributedString.length - startLoc)
        tableRangeInfos.append((tableRange: tableRange, cellRanges: currentCellRanges))
    }
    
    return tableRangeInfos
}

使用示例

let rangeInfos = getTableAndCellRanges(from: attributedStr)
for info in rangeInfos {
    print("表格范围:\(info.tableRange)")
    print("包含的单元格范围:\(info.cellRanges)")
}

一些注意事项

  • HTML解析成NSAttributedString时,可能会自动插入空白段落或换行,遍历的时候可以通过判断文本是否为空来过滤这些无关内容;
  • NSTextTableBlock的isLastBlockInRow属性是判断行结束的关键,能帮你正确把单元格分组为行;
  • 这个逻辑对RTF文件完全适用,因为RTF表格在NSAttributedString中的标记方式和HTML一致。

内容的提问来源于stack exchange,提问作者alagris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:15:54