【问题标题】:Is there a way to parse excel spreadsheets to xml(The spreadsheet contains paragraphs and tables) in Python?有没有办法在 Python 中将 excel 电子表格解析为 xml(电子表格包含段落和表格)?
【发布时间】:2020-08-04 00:55:49
【问题描述】:

感谢您分享您的时间,这是我在下面遇到的问题

[总结]

我正在尝试将 xml 信息提供给 Excel 电子表格报告。
这是电子表格的捕获,以及下面的所需输出。

Excel Spreadsheet

想要的输出就是这样。

<p>1. Title for Sheet1</p>
<p></p>
<p>Paragraph for Sheet1</p>
<p></p>
<p>Paragraph2 for Sheet1</p>
<p></p>
<table>
  <thead>
    <tr>
      <th>Content</th>
      <th>2019</th>
      <th>2020</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>A</td>
      <td>2,500</td>
      <td>3,500</td>
    </tr>
    <tr>
      <td>B</td>
      <td>4,500</td>
      <td>6,000</td>
    </tr>
  </tbody>
</table>

[问题]

我已经查找了将 excel 解析为 xml 的方法,但我不知道如何在电子表格中分隔段落值和表值。
我正在考虑一种将单元格识别为段落的逻辑,“如果 B 列中没有值”,否则,将它们识别为表格。
每张表中可能会有多个段落和表格。

[我需要什么]

  1. 将电子表格解析为 xml 的最佳方式
  2. 如何保留“对齐”、“宽度”和“高度”格式信息(我想将它们包含在 xml 中)。
  3. 如果可能,还要获取有关合并单元格的信息。我认为电子表格报告在某些情况下会包含合并的单元格。

我正在寻找关于 Python 的答案,如果需要的话,我也在寻找 VBA 的答案。 非常感谢您的所有帮助,希望您有美好的一天。

【问题讨论】:

  • 您在标题中提到了 Python,但您添加了 VBA 标签。这是否意味着您也在寻找潜在的 VBA 答案?
  • @DecimalTurn 是的,我没有学过 VBA,只对 VBA 脚本使用了复制和粘贴。但我对 VBA 的答案非常开放,如果需要,我愿意研究它们。
  • XML 还是 HTML ?您的示例看起来像 HTML。捕获样式信息的规范是什么?
  • 请注意,excel 中没有“段落”,因为所有内容都在一个单元格中。您可以将仅包含一个单元格内容的行解释为一个段落。
  • @Tim Williams XML。标签只是示例。我没有任何所需的样式规范信息,因为要在自定义程序中读取 xml。很抱歉描述含糊不清,我是所有方面的初学者。

标签: python excel xml vba


【解决方案1】:

电子表格非常复杂,所以如果您需要更多帮助,可以提供更多详细信息。

你需要:

  • 将电子表格解析为 XML 的最佳方式
    • 您的病情如何?
      • Excel 是指 Microsoft-Office、Google-Spreadsheet 还是?
      • 你能接受 Excel 的哪个version
      • 如果 Excel 包含脚本,你会怎么做? (VBA ...)
      • 如果单元格中包含公式怎么办?
      • 如果锁定(或受保护)怎么办?
      • 您是否需要将某些内容写回 Excel。 (可能不会)
      • 您是否关心性能? (COM)
      • 您是否要处理特定文本(例如,_x0020_,...) ...

我的意思是,如果您要谈论最好的,请附上一些条件。否则属于个人观点。

我给你一个link,这样你就可以看到哪个库对你有帮助


我仍然给你一个例子(我假设情况尽可能简单)如下,

测试数据

test_data.xlsx

你可以从这个link得到它

代码

from openpyxl import load_workbook
from xml.dom import minidom
from xml.etree.ElementTree import tostring
from xml.etree.ElementTree import Element, ElementTree
from openpyxl.worksheet.merge import MergedCellRange
from openpyxl.worksheet.dimensions import SheetFormatProperties
from openpyxl.worksheet.worksheet import Worksheet
from openpyxl.workbook.workbook import Workbook
from openpyxl.cell.cell import Cell

from typing import Tuple, List, Union


def prettify(elem: Element):
    """
    https://pymotw.com/2/xml/etree/ElementTree/create.html
    """
    rough_string = tostring(elem, 'utf-8')
    re_parsed = minidom.parseString(rough_string)
    return re_parsed.toprettyxml(indent='    ')  # 4 space


TABLE_START_ROW = 10


def main():
    wb: Workbook = load_workbook('test_data.xlsx')

    root = Element('root')
    table = Element('table')
    tbody = Element('tbody')
    for sheet in [wb[sheet_name] for sheet_name in wb.sheetnames]:
        # print(sheet.title)
        sheet: Worksheet
        sheet_prop: SheetFormatProperties = sheet.sheet_format
        default_width = sheet_prop.baseColWidth
        default_height = sheet_prop.defaultRowHeight
        merge_cell_list: MergedCellRange = sheet.merged_cells.ranges
        for row in sheet.rows:
            if not any([cell.value for cell in row]):
                root.append(Element('p'))  # empty line
                continue

            cur_row = row[0].row
            if cur_row < TABLE_START_ROW:
                # `NOT TABLE` DATA
                data_list = []
                for cell in row:
                    cell: Cell
                    if cell.value:
                        merge_info: List[Union[None, MergedCellRange]] = [
                            merge_cell for merge_cell in filter(lambda merge_cell: cell.coordinate in merge_cell,
                                                                merge_cell_list)
                        ]

                        is_merge = len(merge_info) > 0
                        if is_merge:
                            # It should recalculate the width and height.
                            merge_range: MergedCellRange = merge_info[0]
                            size: dict = merge_range.size
                            num_col = size['columns']
                            num_row = size['rows']

                            width = 0
                            for idx in range(num_col):
                                width += sheet.column_dimensions[
                                    chr(ord(cell.column_letter)+idx)  # A B ...
                                ].width

                            height = 0
                            for idx in range(num_row):
                                new_height = sheet.row_dimensions[cell.row+idx].height
                                height += new_height if new_height else default_height

                        else:
                            width = sheet.column_dimensions[cell.column_letter].width
                            height = sheet.row_dimensions[cell.row].height
                            height = default_height if height is None else height

                        merge_info: Union[Tuple[bool, str], List[None]] = \
                            (is_merge, merge_info[0].coord) if merge_info else [None]

                        align: Tuple[str, str] = (cell.alignment.horizontal, cell.alignment.vertical)
                        data_list.append((str(cell.value), align, (height, width), merge_info))

                p_list = []  # for the `div` tag
                for value, (text_align, vertical_align), (h, w), merge_info in data_list:
                    is_merge, *coord = merge_info
                    p = Element('p')
                    p.text = value
                    if text_align:
                        p.attrib['text_align'] = text_align
                        # show w and h only on align cells
                        p.attrib['width'] = str(w)
                        p.attrib['height'] = str(h)
                    if vertical_align:
                        p.attrib['vertical_align'] = vertical_align
                    if is_merge:
                        p.attrib['merge_coord'] = coord[0]
                    p_list.append(p)
                if len(data_list) > 1:
                    div = Element('div')
                    for tag_p in p_list:
                        div.append(tag_p)
                    root.append(div)
                else:
                    root.append(p_list[0])
            elif cur_row == TABLE_START_ROW:
                # TABLE HEADER
                thead = Element('thead')
                tr = Element('tr')
                thead.append(tr)
                for cell in row:
                    th = Element('th')
                    th.text = str(cell.value)
                    tr.append(th)
                table.append(thead)
                table.append(tbody)
                root.append(table)
            else:
                # TABLE ROW
                tr = Element('tr')
                for cell in row:
                    td = Element('td')
                    td.text = f'{cell.value:0,.0f}' if isinstance(cell.value, int) else str(cell.value)
                    tr.append(td)
                tbody.append(tr)

    if not 'output mode is compressed':
        tree = ElementTree(root)
        tree.write('result_compressed.xml')  # ugly (The output format compressed that is not easy for human reading.)
        return
    with open('result.xml', 'w', encoding='utf-8') as f:
        f.write(prettify(root))


if __name__ == '__main__':
    main()

在 Excel 2013 (v15.0) 上测试 | Python 3.7.3 | openpyxl 3.0.4

演示

<?xml version="1.0" ?>
<root>
    <p>1.Title for Sheet1</p>
    <p/>
    <div>
        <p>Paragraph for Sheet1</p>
        <p>p1.sub.msg</p>
    </div>
    <p/>
    <p height="47.25" merge_coord="A5:B6" text_align="center" width="53.0">Paragraph2 for Sheet1 (merge)</p>
    <p/>
    <p/>
    <p height="36.75" text_align="right" vertical_align="center" width="33.0">align_right_vertical_center</p>
    <p/>
    <table>
        <thead>
            <tr>
                <th>Content</th>
                <th>2019</th>
                <th>2020</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>A</td>
                <td>2,500</td>
                <td>3,500</td>
            </tr>
            <tr>
                <td>B</td>
                <td>4,500</td>
                <td>6,000</td>
            </tr>
        </tbody>
    </table>
</root>

我特意添加了div。这意味着它们在同一行。

【讨论】:

  • 我可以根据自己的喜好限制条件,因为我可以指定我的员工将使用的电子表格模板。非常感谢您花时间编写脚本和示例,我将从头开始研究以了解每一点。非常感谢,祝你一切顺利。
【解决方案2】:

没有任何好的选择,但这里有 4 种可能性:

选项 1. 从 excel 中,另存为 XML。然后,您必须指定到所需 XML 结构的映射。

选项 2. xlsx 文件实际上是一个压缩的 XML 文件,因此您可以保存一个 xlsx 文件(例如保存到 C:\temp\a.xlsx)。然后,将文件扩展名更改为“.zip”。如果您打开 zip 文件,则会有一个文件目录。第一个工作表的内容在:C:\temp\a.zip\xl\worksheets\sheet1.xml

使用 XSL 将 XML 转换为您想要的任何结构。 XML 包含值和维度以及样式格式(它是电子表格)。

压缩目录中还有其他元数据文件。

选项 3. 从 excel,另存为“.htm”。这个 html 文件实际上是一个脚本,所以您可能想在浏览器中打开 html 文件,然后再次保存 html 文件,以获取您想要的表格。

选项 4. 另存为 csv,然后编写一个脚本来循环遍历 csv 的每一行,并环绕每一行和每个单元格值。这可能是最简单的。

【讨论】:

  • 感谢您的回复。标签名称只是示例,而 xml 是所需的输出。我知道excel中没有诸如段落之类的概念,所以我试图将段落定义为行中的单元格,在B列中没有值。我想知道是否有比分解excel更简单的方法xml 文件本身,但我会试试你提到的内容。
  • 作为一个新手,我真的很喜欢你的 csv 解决方案,只要格式不重要。我会调查一切,再次感谢,你太棒了。
猜你喜欢
  • 1970-01-01
  • 2017-01-24
  • 2011-12-14
  • 2014-07-13
  • 1970-01-01
  • 2017-05-28
  • 1970-01-01
  • 2013-01-23
  • 2020-03-14
相关资源
最近更新 更多