表格-合并单元格

Word允许合并相邻的表单元格,这样两个或多个单元格看起来是一个单元格。单元格可以水平合并(跨越多个列)或垂直合并(跨越多行)。还可以同时在水平和垂直方向合并单元格,生成一个同时跨越行和列的单元格。只能合并单元格的矩形区域。

表格图表

下面的图表用于描述分析中的表格。水平跨距被描述为一个连续的水平单元,跨度内没有垂直分隔线。垂直跨距被描述为具有相同宽度的垂直单元格序列,其中连续单元格由虚线上边框分隔,并包含一个插入符号('^'),以表示上面单元格的延续。单元格“地址”显示在列和行网格线中。这在概念上很方便,因为它重用了列表索引(和切片)的概念,并使某些操作的指定更加直观。合并单元格 A 下面的top、left、bottom和right值分别为0、0、2和2::

\ 0   1   2   3
0 +---+---+---+
  | A     |   |
1 + - - - +---+
  | ^     |   |
2 +---+---+---+
  |   |   |   |
3 +---+---+---+

基本小区接入协议

有三种方法可以访问表单元格:

  • Table.cell(row_idx, col_idx)

  • Row.cells[col_idx]

  • Column.cells[col_idx]

访问3 x 3表格的中间单元格:

>>> table = document.add_table(3, 3)
>>> middle_cell = table.cell(1, 1)
>>> table.rows[1].cells[1] == middle_cell
True
>>> table.columns[1].cells[1] == middle_cell
True

基本合并协议

使用两个对角单元格指定合并:

>>> table = document.add_table(3, 3)
>>> a = table.cell(0, 0)
>>> b = table.cell(1, 1)
>>> A = a.merge(b)
\ 0   1   2   3
0 +---+---+---+        +---+---+---+
  | a |   |   |        | A     |   |
1 +---+---+---+        + - - - +---+
  |   | b |   |  -->   | ^     |   |
2 +---+---+---+        +---+---+---+
  |   |   |   |        |   |   |   |
3 +---+---+---+        +---+---+---+

访问合并单元格

单元通过其“布局格线”位置访问,而不考虑可能存在的任何跨距。位于某个范围内的网格地址返回该范围中最左的单元格。这意味着一个范围的地址和它所跨越的布局网格单元的地址一样多。例如,合并单元格 A 上面的地址可以是(0,0),(0,1),(1,0)或(1,1)。当表中存在跨距时,这种寻址方案会导致理想的访问行为。

长度行单元格始终等于轴网列数,而不管存在任何跨距。同样,长度列.单元格始终等于表行数,而不考虑任何跨距。

>>> table = document.add_table(2, 3)
>>> row = table.rows[0]
>>> len(row.cells)
3
>>> row.cells[0] == row.cells[1]
False

>>> a, b = row.cells[:2]
>>> a.merge(b)

>>> len(row.cells)
3
>>> row.cells[0] == row.cells[1]
True
\ 0   1   2   3
0 +---+---+---+        +---+---+---+
  | a | b |   |        | A     |   |
1 +---+---+---+  -->   +---+---+---+
  |   |   |   |        |   |   |   |
2 +---+---+---+        +---+---+---+

合并时的单元格内容行为

合并两个或多个单元格时,任何现有内容都将被连接并放置到结果合并的单元格中。每个原始单元格中的内容通过段落标记与上一个原始单元格中的内容分开。在contatenation过程中跳过没有内容的原始单元格。在Python中,过程大致如下所示:

merged_cell_text = '\n'.join(
    cell.text for cell in original_cells if cell.text
)

将四个单元格与内容合并 'a''b''''d' 分别导致合并的单元格中有文本 'a\nb\nd' .

合并时的单元格大小行为

合并单元格时,将添加单元格宽度和高度(如果存在):

>>> a, b = row.cells[:2]
>>> a.width.inches, b.width.inches
(1.0, 1.0)
>>> A = a.merge(b)
>>> A.width.inches
2.0

删除多余的行或列

折叠列。 当网格列中的所有单元格共享相同的 w:gridSpan 规范中,通过移除 w:gridSpan 属性。

言语行为

  • 当表不一致时,msapi中的行和列访问只是简单的中断。 Table.Rows(n)Cell.Row 提升 EnvironmentError 当表格包含垂直跨度时,以及 Table.Columns(n)Cell.Column 无条件提高 EnvironmentError 当表格包含水平跨距时。我们可以做得更好。

  • Table.Cell(n, m) 适用于任何非统一表,尽管它使用 可视网格 这使访问变得非常复杂。它引发了一个错误 nm 超出了可视范围,除了尝试/除了确定可视范围之外,没有其他方法,因为 Row.CountColumn.Count 不可用。

  • 在合并操作中,连续单元格的文本作为单独的段落附加到原始单元格的文本之后。

  • 如果合并区域包含以前合并的单元格,则该区域必须完全包围合并的单元格。

  • 当单元格被越界的行索引引用时,Word调整表的大小(添加行)。如果列超出边界,则引发异常。此行为将不会在中实现 python-docx .

术语表

布局网格

行和列的常规二维矩阵,用于确定表中单元格的布局。网格主要由 w:gridCol 定义表的布局列的元素。每一行实际上与另一行的布局相同,尽管其高度可能与其他行不同。表中的每个实际单元格都必须以布局网格“线”开始和结束,无论单元格是否合并。

跨度

占据一组合并单元面积的单个“组合”单元。

跳过的单元格

WordprocessingML(WML)规范允许“跳过”单元格,布局单元格位置不包含实际单元格。我找不到一种使用wordui来创建这样一个表的方法,而且还没有试验Word是否会加载在XML中手工构建的表。

统一表

一种表格,其中每个单元格与一个布局单元格完全对应。统一表不包含跨距或跳过的单元格。

非均匀表

一种包含一个或多个跨距的表,这样并不是每个单元格都对应于一个布局单元。我想它也适用于有一个或多个跳过单元格的情况,但在本次分析中,这个术语只用于表示具有一个或多个跨距的表。

均匀电池

不属于跨度的单元,在布置格线中占据一个单元。

原始细胞

跨度中最左上的单元格。对比 连续单元 .

连续单元

已包含在跨距中的布局单元。连续单元通常是一个抽象的概念,尽管它是一个实际的概念 w:tc 元素将始终存在于XML中垂直跨距中的每个连续单元格。

直观地理解合并XML

关键的一点是合并后的单元格总是如下图所示。水平跨距由单个 w:tc 元素,使用 gridSpan 属性来跨越其他网格列。垂直跨距是由每个连续行中的一个相同的单元完成的,具有相同的单元 gridSpan 值,并将vMerge设置为 continue (默认设置)。这些垂直连续单元格如下图所示,在最左边的网格列中用虚线的上边框和一个插入符号(“^”)表示上面单元格的连续性:

\ 0   1   2   3
0 +---+---+---+
  | A     |   |
1 + - - - +---+
  | ^     |   |
2 +---+---+---+
  |   |   |   |
3 +---+---+---+

上面描述的表对应于此XML(为清晰起见,最小化):

<w:tbl>
  <w:tblGrid>
     <w:gridCol/>
     <w:gridCol/>
     <w:gridCol/>
  </w:tblGrid>
  <w:tr>
     <w:tc>
        <w:tcPr>
           <w:gridSpan w:val="2"/>
           <w:vMerge w:val="restart"/>
        </w:tcPr>
     </w:tc>
     <w:tc/>
  </w:tr>
  <w:tr>
     <w:tc>
        <w:tcPr>
           <w:gridSpan w:val="2"/>
           <w:vMerge/>
        </w:tcPr>
     </w:tc>
     <w:tc/>
  </w:tr>
  <w:tr>
     <w:tc/>
     <w:tc/>
     <w:tc/>
  </w:tr>
</w:tbl>

XML语义

在水平合并中 <w:tc w:gridSpan="?"> 属性指示单元格应跨越的列数。只保留最左边的单元格;删除合并中的其余单元格。

对于垂直合并 w:vMerge 列最上面单元格的table cell属性设置为类型为“restart”的值 w:ST_Merge . 垂直合并中包含的下面的单元格必须具有 w:vMerge 单元属性中存在的元素 (w:TcPr )元素。它的值应该设置为“continue”,尽管不必明确定义它,因为它是默认值。一个单元格一垂直合并就结束了 w:TcPr 元素缺少 w:vMerge 元素。类似于 w:gridSpan 元素 w:vMerge 只有当表的布局在不同列之间不一致时,才需要元素。在这种情况下,只保留最上面的单元格;合并区域中其他较低的单元格将与其一起删除 w:vMerge 元素和 w:trHeight 表行属性用于指定合并单元格的组合高度。

的len()实现行单元格以及列.单元格

RowColumn 对象提供对其包含的单元格集合的访问。这些单元格集合的长度不受合并单元格的存在的影响。

len() 它的计数总是基于布局网格,就像没有合并的单元格一样。

  • len(Table.columns)w:gridCol 元素,表示网格列数,而不考虑表中是否存在合并单元格。

  • len(Table.rows)w:tr 元素,与表中可能存在的任何合并单元格无关。

  • len(Row.cells) 不管行中是否合并了任何单元格,都是网格列数。

  • len(Column.cells) 是表中的行数,而不管列中是否合并了任何单元格。

合并已包含跨距的单元格

只要指定的区域是矩形的,合并操作中的一个或两个“对角”单元格本身都可以是合并单元格。

例如::

\   0   1   2   3
  +---+---+---+---+       +---+---+---+---+
0 | a     | b |   |       | a\nb\nC   |   |
  + - - - +---+---+       + - - - - - +---+
1 | ^     | C |   |       | ^         |   |
  +---+---+---+---+  -->  +---+---+---+---+
2 |   |   |   |   |       |   |   |   |   |
  +---+---+---+---+       +---+---+---+---+
3 |   |   |   |   |       |   |   |   |   |
  +---+---+---+---+       +---+---+---+---+

  cell(0, 0).merge(cell(1, 2))

或:

    0   1   2   3   4
  +---+---+---+---+---+       +---+---+---+---+---+
0 | a     | b | c |   |       | abcD          |   |
  + - - - +---+---+---+       + - - - - - - - +---+
1 | ^     | D     |   |       | ^             |   |
  +---+---+---+---+---+  -->  +---+---+---+---+---+
2 |   |   |   |   |   |       |   |   |   |   |   |
  +---+ - - - +---+---+       +---+---+---+---+---+
3 |   |   |   |   |   |       |   |   |   |   |   |
  +---+---+---+---+---+       +---+---+---+---+---+

  cell(0, 0).merge(cell(1, 2))

相反,这两个合并操作中的任何一个都是非法的:

\ 0   1   2   3   4      0   1   2   3   4
0 +---+---+---+---+    0 +---+---+---+---+
  |   |   | b |   |      |   |   |   |   |
1 +---+---+ - +---+    1 +---+---+---+---+
  |   | a | ^ |   |      |   | a |   |   |
2 +---+---+ - +---+    2 +---+---+---+---+
  |   |   | ^ |   |      | b         |   |
3 +---+---+---+---+    3 +---+---+---+---+
  |   |   |   |   |      |   |   |   |   |
4 +---+---+---+---+    4 +---+---+---+---+

  a.merge(b)

一般算法

  • 查找左上角和目标宽度、高度

  • 对于目标高度的每个tr,正确成长(目标宽度)

XML样本

一个3x3表格,其中由左上2 x 2单元格定义的区域已合并,演示了 w:gridSpan 以及 w:vMerge 元素,由单词产生:

<w:tbl>
  <w:tblPr>
     <w:tblW w:w="0" w:type="auto" />
  </w:tblPr>
  <w:tblGrid>
     <w:gridCol w:w="3192" />
     <w:gridCol w:w="3192" />
     <w:gridCol w:w="3192" />
  </w:tblGrid>
  <w:tr>
     <w:tc>
        <w:tcPr>
           <w:tcW w:w="6384" w:type="dxa" />
           <w:gridSpan w:val="2" />
           <w:vMerge w:val="restart" />
        </w:tcPr>
     </w:tc>
     <w:tc>
        <w:tcPr>
           <w:tcW w:w="3192" w:type="dxa" />
        </w:tcPr>
     </w:tc>
  </w:tr>
  <w:tr>
     <w:tc>
        <w:tcPr>
           <w:tcW w:w="6384" w:type="dxa" />
           <w:gridSpan w:val="2" />
           <w:vMerge />
        </w:tcPr>
     </w:tc>
     <w:tc>
        <w:tcPr>
           <w:tcW w:w="3192" w:type="dxa" />
        </w:tcPr>
     </w:tc>
  </w:tr>
  <w:tr>
     <w:tc>
        <w:tcPr>
           <w:tcW w:w="3192" w:type="dxa" />
        </w:tcPr>
     </w:tc>
     <w:tc>
        <w:tcPr>
           <w:tcW w:w="3192" w:type="dxa" />
        </w:tcPr>
     </w:tc>
     <w:tc>
        <w:tcPr>
           <w:tcW w:w="3192" w:type="dxa" />
        </w:tcPr>
     </w:tc>
  </w:tr>
</w:tbl>

架构摘要

<xsd:complexType name="CT_Tc">  <!-- denormalized -->
  <xsd:sequence>
    <xsd:element name="tcPr" type="CT_TcPr" minOccurs="0"/>
    <xsd:choice minOccurs="1" maxOccurs="unbounded">
      <xsd:element name="p"                           type="CT_P"/>
      <xsd:element name="tbl"                         type="CT_Tbl"/>
      <xsd:element name="customXml"                   type="CT_CustomXmlBlock"/>
      <xsd:element name="sdt"                         type="CT_SdtBlock"/>
      <xsd:element name="proofErr"                    type="CT_ProofErr"/>
      <xsd:element name="permStart"                   type="CT_PermStart"/>
      <xsd:element name="permEnd"                     type="CT_Perm"/>
      <xsd:element name="ins"                         type="CT_RunTrackChange"/>
      <xsd:element name="del"                         type="CT_RunTrackChange"/>
      <xsd:element name="moveFrom"                    type="CT_RunTrackChange"/>
      <xsd:element name="moveTo"                      type="CT_RunTrackChange"/>
      <xsd:element  ref="m:oMathPara"                 type="CT_OMathPara"/>
      <xsd:element  ref="m:oMath"                     type="CT_OMath"/>
      <xsd:element name="bookmarkStart"               type="CT_Bookmark"/>
      <xsd:element name="bookmarkEnd"                 type="CT_MarkupRange"/>
      <xsd:element name="moveFromRangeStart"          type="CT_MoveBookmark"/>
      <xsd:element name="moveFromRangeEnd"            type="CT_MarkupRange"/>
      <xsd:element name="moveToRangeStart"            type="CT_MoveBookmark"/>
      <xsd:element name="moveToRangeEnd"              type="CT_MarkupRange"/>
      <xsd:element name="commentRangeStart"           type="CT_MarkupRange"/>
      <xsd:element name="commentRangeEnd"             type="CT_MarkupRange"/>
      <xsd:element name="customXmlInsRangeStart"      type="CT_TrackChange"/>
      <xsd:element name="customXmlInsRangeEnd"        type="CT_Markup"/>
      <xsd:element name="customXmlDelRangeStart"      type="CT_TrackChange"/>
      <xsd:element name="customXmlDelRangeEnd"        type="CT_Markup"/>
      <xsd:element name="customXmlMoveFromRangeStart" type="CT_TrackChange"/>
      <xsd:element name="customXmlMoveFromRangeEnd"   type="CT_Markup"/>
      <xsd:element name="customXmlMoveToRangeStart"   type="CT_TrackChange"/>
      <xsd:element name="customXmlMoveToRangeEnd"     type="CT_Markup"/>
      <xsd:element name="altChunk"                    type="CT_AltChunk"/>
    </xsd:choice>
  </xsd:sequence>
  <xsd:attribute name="id" type="s:ST_String" use="optional"/>
</xsd:complexType>

<xsd:complexType name="CT_TcPr">  <!-- denormalized -->
  <xsd:sequence>
    <xsd:element name="cnfStyle"             type="CT_Cnf"           minOccurs="0"/>
    <xsd:element name="tcW"                  type="CT_TblWidth"      minOccurs="0"/>
    <xsd:element name="gridSpan"             type="CT_DecimalNumber" minOccurs="0"/>
    <xsd:element name="hMerge"               type="CT_HMerge"        minOccurs="0"/>
    <xsd:element name="vMerge"               type="CT_VMerge"        minOccurs="0"/>
    <xsd:element name="tcBorders"            type="CT_TcBorders"     minOccurs="0"/>
    <xsd:element name="shd"                  type="CT_Shd"           minOccurs="0"/>
    <xsd:element name="noWrap"               type="CT_OnOff"         minOccurs="0"/>
    <xsd:element name="tcMar"                type="CT_TcMar"         minOccurs="0"/>
    <xsd:element name="textDirection"        type="CT_TextDirection" minOccurs="0"/>
    <xsd:element name="tcFitText"            type="CT_OnOff"         minOccurs="0"/>
    <xsd:element name="vAlign"               type="CT_VerticalJc"    minOccurs="0"/>
    <xsd:element name="hideMark"             type="CT_OnOff"         minOccurs="0"/>
    <xsd:element name="headers"              type="CT_Headers"       minOccurs="0"/>
    <xsd:choice  minOccurs="0">
      <xsd:element name="cellIns"            type="CT_TrackChange"/>
      <xsd:element name="cellDel"            type="CT_TrackChange"/>
      <xsd:element name="cellMerge"          type="CT_CellMergeTrackChange"/>
    </xsd:choice>
    <xsd:element name="tcPrChange"           type="CT_TcPrChange"    minOccurs="0"/>
  </xsd:sequence>
</xsd:complexType>

<xsd:complexType name="CT_DecimalNumber">
  <xsd:attribute name="val" type="ST_DecimalNumber" use="required"/>
</xsd:complexType>

<xsd:simpleType name="ST_DecimalNumber">
   <xsd:restriction base="xsd:integer"/>
</xsd:simpleType>

<xsd:complexType name="CT_VMerge">
  <xsd:attribute name="val" type="ST_Merge"/>
</xsd:complexType>

<xsd:complexType name="CT_HMerge">
  <xsd:attribute name="val" type="ST_Merge"/>
</xsd:complexType>

<xsd:simpleType name="ST_Merge">
  <xsd:restriction base="xsd:string">
    <xsd:enumeration value="continue"/>
    <xsd:enumeration value="restart"/>
  </xsd:restriction>
</xsd:simpleType>

未决问题

  • Word是否允许在行的开头“跳过”单元格 (w:gridBefore 元素)?规范中对这些进行了描述,但我在wordui中看不到创建这样一个表的方法。

资源

ISO规范中的相关章节

  • 17.4.17 gridSpan(当前表格单元格跨越的网格列)

  • 17.4.84 vMerge(垂直合并单元格)

  • 17.18.57 ST_Merge(合并单元类型)