dingsir的个人博客分享 http://blog.sciencenet.cn/u/dingsir

博文

详解Excel的Open XML中单元格样式的cellStyleXfs,cellStyle,cellXfs之间关系

已有 3887 次阅读 2020-8-18 01:03 |个人分类:软件杂谈|系统分类:科研笔记| OOXML, 样式设置, 单元格

详解Excel的Open XML中单元格样式的cellStyleXfs,cellStyle,cellXfs之间关系

Excel的新文档格式xlsx使用了SpreadsheetML标记语言(以下简称sml), 这个规范中比较容易混淆的定义就有单元格的样式设置,有三个名字很相近的集合: cellXfs, cellStyle, cellStyleXfs, 一下子不容易搞懂.

这三个元素存在于/xl/Styles.xml

背景知识:

  1. 设计目的: 为了减少重复项的特征反复存贮,sml使用了集合的方法来存贮各种不同的格式,全部放在一起. 需要用到它的地方只需要某一个索引,指向这个集合中的某一项就可以了.这样就大大减少了描述样式的字符或数据,做到一次存贮,多处使用.

  2. 当讨论某个单元格具体的设置时,用到的是集合的某一种样式,我把这个叫做集合的一个子项. 比如集合有5种样式,我这次只用了第3种.这个第3种就是集合的一个子项.sml通过索引来指向这个子项,索引 从0开始计数, 第1项索引为0, 第N项索引为N-1.这个第3项的索引就是2.

  3. 集合的属性中设置了count属性,代表每个集合下面有多少个子项, count属性由Excel自动维护.

  4. 这三种都是复杂类型,有嵌套定义.

区别所在

为了防止大家记名字时混淆, 先来个不太严谨的介绍:

  • cellXfs 存贮了已经用了的单元格格式.
 <xsd:complexType name="CT_CellXfs">
   <xsd:sequence>
     <xsd:element name="xf" type="CT_Xf" minOccurs="1" maxOccurs="unbounded"/>
   </xsd:sequence>
   <xsd:attribute name="count" type="xsd:unsignedInt" use="optional"/>
 </xsd:complexType>

这个集合就是CT_Xf类型的记录的汇总.这个CT_Xf类型后面展开讲.

  • cellStyle 存贮了可能用到也可能还没有使用的单元格样式的粗略信息.

 有点像Word的样式, Word文档可能有许多种样式,但不是每一种都会在文档中应用. 可以理解为这是一个可能用得到的样式库. 它至少要存贮默认样式"Normal"或是"常规".

<xsd:complexType name="CT_CellStyles">
   <xsd:sequence>
     <xsd:element name="cellStyle" type="CT_CellStyle" minOccurs="1" maxOccurs="unbounded"/> <!--至少有1项-->
   </xsd:sequence>
   <xsd:attribute name="count" type="xsd:unsignedInt" use="optional"/>  <!--属性count是可选的,但通常都有-->
</xsd:complexType>
 
<!--看看上面的CT_CellStyle类型展开后是什么样的: -->  
<xsd:complexType name="CT_CellStyle">  <!--可以看出,这里没有具体的格式设置信息-->
   <xsd:sequence>
     <xsd:element name="extLst" type="CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
   </xsd:sequence>
   <xsd:attribute name="name" type="s:ST_Xstring" use="optional"/>   <!--但存贮了样式的名称-->
   <xsd:attribute name="xfId" type="ST_CellStyleXfId" use="required"/> <!--具体的样式由这个参数指定, 内容存贮在cellStylesXfs集合-->
   <xsd:attribute name="builtinId" type="xsd:unsignedInt" use="optional"/> <!--指定格式时,实际上看builtinId而不是name.多语言情景-->
   <xsd:attribute name="iLevel" type="xsd:unsignedInt" use="optional"/>
   <xsd:attribute name="hidden" type="xsd:boolean" use="optional"/>
   <xsd:attribute name="customBuiltin" type="xsd:boolean" use="optional"/> <!--是自定义内建样式吗? -->
</xsd:complexType>  

cellStyles从名字就看得出来,是样式的合集, 由多个子项(CT_cellStyle类型,注意这个类型没有s后缀了) 汇总而成. 子项也只存贮了样式名称、内部Id等信息,不含有具体的样式设置,但有一个属性xfId告诉Excel,从哪里找到样式的具体规定.

到哪里找这个样式,自然就是从下面这个集合里找:

  • cellStyleXfs 这个集合存放的是cellStyles中各种样式对应的格式设置信息.

用到了没有用到的都可以放在这里. 也有可能用到了的不放在这里. 但是cellStyles中指定的xfId,在这里应该都可以找得到相应的子项.

上一个集合cellStyles定义了样式的名称和内部Id, 这个cellStyleXfs集合定义了样式的具体设置. 连接这两个集合的就是上面cellStyles集合的子项的属性 xfId,它实际就是这个cellStyleXfs集合的子项的索引,当然也是从0开始数的.

<xsd:complexType name="CT_CellStyleXfs">
   <xsd:sequence>
     <xsd:element name="xf" type="CT_Xf" minOccurs="1" maxOccurs="unbounded"/>
   </xsd:sequence>
   <xsd:attribute name="count" type="xsd:unsignedInt" use="optional"/>
 </xsd:complexType>

下面我来举实例加深一下印象,以下是同一文件中的内容:

 <cellStyles count="1">
       <cellStyle name="常规" xfId="0" builtinId="0"/>
   </cellStyles>
 <cellStyleXfs count="1">
       <xf numFmtId="0" fontId="0" fillId="0" borderId="0">
           <alignment vertical="center"/>
       </xf>
   </cellStyleXfs>

cellStyles集合只有一项名字为"常规"的设置,它的xfID=0, 指向cellStyleXfs集合中第1项(也是唯一的一项), 具体设置是: 数字格式,字体,填充,边框都没有规定,排列居中.

<cellXfs count="8">
<!--内容太长删除一些-->
       <xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0">
           <alignment vertical="center"/>
       </xf>
       <xf numFmtId="0" fontId="2" fillId="0" borderId="0" xfId="0" applyFont="1">
           <alignment vertical="center"/>
       </xf>
       <xf numFmtId="0" fontId="2" fillId="2" borderId="0" xfId="0" applyFont="1" applyFill="1">
           <alignment vertical="center"/>
       </xf>
       <xf numFmtId="176" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1">
           <alignment vertical="center"/>
       </xf>
</cellXfs>

这个cellXfs集合中, 汇集了8种样式,但多数都没有出现在cellStyles中. 可以这么理解: 这三个集合中, cellStyles和cellStyleXfs是配合使用的,相当于一个公版的有名字的样式库,但里面的样式不一定都在表格中应用了; cellXfs集合是已经使用了的各种样式的全集,但它有点像个没有名字的样式库,它的各个子项就是各种样式规定,只是没有指定名字.

CT_Xf类型

心细的读者可能发现, cellStyleXfs集合和cellXfs集合, 其定义是相同的: 有count属性,有至少一个CT_xf类型的子项.

那我们来看看这两者共同的子项是怎么定义的:

 <xsd:complexType name="CT_Xf"> <!--这个定义很重要,CellStyleXfs和CellXfs的子项都是这个类型-->
   <xsd:sequence>
     <xsd:element name="alignment" type="CT_CellAlignment" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="protection" type="CT_CellProtection" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="extLst" type="CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
   </xsd:sequence>
   <xsd:attribute name="numFmtId" type="ST_NumFmtId" use="optional"/>
   <xsd:attribute name="fontId" type="ST_FontId" use="optional"/>
   <xsd:attribute name="fillId" type="ST_FillId" use="optional"/>
   <xsd:attribute name="borderId" type="ST_BorderId" use="optional"/>
   <xsd:attribute name="xfId" type="ST_CellStyleXfId" use="optional"/>
   <xsd:attribute name="quotePrefix" type="xsd:boolean" use="optional" default="false"/>
   <xsd:attribute name="pivotButton" type="xsd:boolean" use="optional" default="false"/>
   <xsd:attribute name="applyNumberFormat" type="xsd:boolean" use="optional"/>
   <xsd:attribute name="applyFont" type="xsd:boolean" use="optional"/>
   <xsd:attribute name="applyFill" type="xsd:boolean" use="optional"/>
   <xsd:attribute name="applyBorder" type="xsd:boolean" use="optional"/>
   <xsd:attribute name="applyAlignment" type="xsd:boolean" use="optional"/>
   <xsd:attribute name="applyProtection" type="xsd:boolean" use="optional"/>
 </xsd:complexType>

实际上,这个CT_Xf类型就是sml中对单元格样式(不包括文本串字符级别的定义)的全部定义.值得重点研究研究.

CT_Xf类型有个参数xfId,  也比较容易混淆. 它用于cellXfs集合时,代表着指向cellStyleXfs集合的零基索引,其类型的名称就表明了是cellStyleXfs的ID,实际就是无符号整数类型. 在cellStyleXfs集合中使用CT_Xf元素时,不用这个属性. 为此xml在规范18.8.45条款中特别提到: For xf records contained in cellXfs this is the zero-based index of an xf record contained in cellStyleXfs corresponding to the cell style applied to the cell. Not present for xf records contained in cellStyleXfs.

看到这里如果你头脑还没有发昏的话,可能会问:既然cellXfs中规定了样式设置的索引, 它又指向了cellStyleXfs中某一项样式,那里也规定了样式设置的索引,那到底单元格会用哪个来设置呢? sml中没有仔细说明,只说了都要读取并进行设置. (可能别的地方说了我不知道,毕竟标准太长了没看完).经过实验我发现是这样的逻辑:

  1. 优先看cellXfs记录中的设置(numFmtId, fontId等等),如果这个记录中没有规定applyXXX="0",那就用这个设置,不用看cellStyleXfs中的设置; 也就是如果你在这里修改, 会立即生效.

  2. 如果单元格的格式中没有<c ...s="X">这样的指令,表明是使用默认样式,那就找cellStyles集合中的"Normal"样式或"常规"样式. 根据这个记录中的xfId找出cellStyleXfs中索引xfId的子项,那里有详细的设定的索引..

  3. cellXfs记录中的xfId似乎只是表明它的基准样式是哪一款,没有实际用途. 既然当前记录中已经指定了各项设置, 再提供哪些被覆盖掉了的设置又有什么意义呢? 存疑!

从上述定义可以看到, 里面定义了很多格式的属性,还有三种子元素<alignment>, <protection>, <extLst>. 我们这里只讨论属性的情况,子元素的另文介绍.

这里我们可以看到许多设置的内容了,如数字格式的numFmtId, 字体格式的fontId, 填充样式的fillId, 边框设定的borderId, 等等. 如果你查看这些记录,这里面存储的还是一个个数字,没有实际的格式设置, 你还是不知道设置是什么. 聪明的你可能会想到,这些数字还是某个集合的Id,要想知道详细的设置,要继续找到它的集合.

进一步阅读: 有关单元格样式设置的几个集合.

<!-- 以下内容与三种集合的区别无关--> 事实正是这样子的, Styles.xml中(与上述三个集合都在这同一个文件)还有几个集合没有介绍,分别是:

数字样式集合.

  用于设定单元格里面数字的显示风格,如千分号,小数点后几位等.

<!--数字的样式集合-->
<xsd:complexType name="CT_NumFmts">
   <xsd:sequence>
     <xsd:element name="numFmt" type="CT_NumFmt" minOccurs="0" maxOccurs="unbounded"/>
   </xsd:sequence>
   <xsd:attribute name="count" type="xsd:unsignedInt" use="optional"/>
 </xsd:complexType>
 <!--上面是集合,下面是子项的定义:一个ID值,一个字符串来表示格式-->
 <xsd:complexType name="CT_NumFmt">
   <xsd:attribute name="numFmtId" type="ST_NumFmtId" use="required"/>
   <xsd:attribute name="formatCode" type="s:ST_Xstring" use="required"/>
 </xsd:complexType>

定义很简单, 技术都在这个字符串怎么设置上了. ,我们来看一下简单的例子,只有一种样式, 格式就是显示三位小数:

    <numFmts count="1">
       <numFmt numFmtId="176" formatCode="0.000"/>
   </numFmts>

实际上有些格式设置的字串可以是很复杂的. 根据定义, Styles.xml中没有这个集合也是允许的.

字体样式集合

<!--字体集合的定义-->
<xsd:complexType name="CT_Fonts">
   <xsd:sequence>
     <xsd:element name="font" type="CT_Font" minOccurs="0" maxOccurs="unbounded"/>
   </xsd:sequence>
   <xsd:attribute name="count" type="xsd:unsignedInt" use="optional"/>
 </xsd:complexType>
 
 <!--字体子项的定义比较长,有名称/加粗/斜体/阴影/颜色等-->
 
  <xsd:complexType name="CT_Font">
   <xsd:choice maxOccurs="unbounded">
     <xsd:element name="name" type="CT_FontName" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="charset" type="CT_IntProperty" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="family" type="CT_FontFamily" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="b" type="CT_BooleanProperty" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="i" type="CT_BooleanProperty" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="strike" type="CT_BooleanProperty" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="outline" type="CT_BooleanProperty" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="shadow" type="CT_BooleanProperty" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="condense" type="CT_BooleanProperty" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="extend" type="CT_BooleanProperty" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="color" type="CT_Color" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="sz" type="CT_FontSize" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="u" type="CT_UnderlineProperty" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="vertAlign" type="CT_VerticalAlignFontProperty" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="scheme" type="CT_FontScheme" minOccurs="0" maxOccurs="1"/>
   </xsd:choice>
 </xsd:complexType>

看个实际的例子, 其实也不复杂,这个集合有3种字体设置:

<fonts count="3" x14ac:knownFonts="1">
       <font>
           <sz val="11"/>
           <color theme="1"/>
           <name val="等线"/>
           <family val="2"/>
           <charset val="134"/>
           <scheme val="minor"/>
       </font>
       <font>
           <sz val="9"/>
           <name val="等线"/>
           <family val="2"/>
           <charset val="134"/>
           <scheme val="minor"/>
       </font>
       <font>
           <sz val="11"/>          <!--字体为11号-->
           <color rgb="FFFF0000"/>  <!--ARGB格式,Alpha R G B各两位-->
           <name val="Microsoft YaHei Light"/> <!--微软雅黑字体-->
           <family val="2"/>         <!--这两个俺也不了解.-->  
           <charset val="134"/>
       </font>
   </fonts>

填充样式集合

  <xsd:complexType name="CT_Fills">
   <xsd:sequence>
     <xsd:element name="fill" type="CT_Fill" minOccurs="0" maxOccurs="unbounded"/>
   </xsd:sequence>
   <xsd:attribute name="count" type="xsd:unsignedInt" use="optional"/>
 </xsd:complexType>
 <xsd:complexType name="CT_Fill">
   <xsd:choice minOccurs="1" maxOccurs="1">
     <xsd:element name="patternFill" type="CT_PatternFill" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="gradientFill" type="CT_GradientFill" minOccurs="0" maxOccurs="1"/>
   </xsd:choice>
 </xsd:complexType>

实际的示例简单:

<fills count="3">
       <fill>
           <patternFill patternType="none"/> <!--这种无填充-->
       </fill>
       <fill>
           <patternFill patternType="gray125"/> <!--填充样式gray125,内建的样式-->
       </fill>
       <fill>
           <patternFill patternType="solid">
               <fgColor rgb="FFFF0000"/>    <!--以红色为前景色 实心填充-->
           </patternFill>
       </fill>
   </fills>

边框样式集合

<!--Borders集合的定义-->
<xsd:complexType name="CT_Borders">
   <xsd:sequence>
     <xsd:element name="border" type="CT_Border" minOccurs="0" maxOccurs="unbounded"/>
   </xsd:sequence>
   <xsd:attribute name="count" type="xsd:unsignedInt" use="optional"/>
 </xsd:complexType>
 
 <!--子项 Border是个复合类型,由属性/元素组成.包含了始/终/顶/底/斜线/垂直排列/水平排列等,还有向上斜线,向下斜线,outline等属性-->
 <xsd:complexType name="CT_Border">
   <xsd:sequence>
     <xsd:element name="start" type="CT_BorderPr" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="end" type="CT_BorderPr" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="top" type="CT_BorderPr" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="bottom" type="CT_BorderPr" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="diagonal" type="CT_BorderPr" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="vertical" type="CT_BorderPr" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="horizontal" type="CT_BorderPr" minOccurs="0" maxOccurs="1"/>
   </xsd:sequence>
   <!--子项Border的属性比较简单,都是布尔值-->
   <xsd:attribute name="diagonalUp" type="xsd:boolean" use="optional"/>
   <xsd:attribute name="diagonalDown" type="xsd:boolean" use="optional"/>
   <xsd:attribute name="outline" type="xsd:boolean" use="optional" default="true"/>
 </xsd:complexType>
 <!--子项的元素是个复合类型,含有颜色和边框风格两种类型的元素-->
 <xsd:complexType name="CT_BorderPr">
   <xsd:sequence>
     <xsd:element name="color" type="CT_Color" minOccurs="0" maxOccurs="1"/>
   </xsd:sequence>
   <xsd:attribute name="style" type="ST_BorderStyle" use="optional" default="none"/>
 </xsd:complexType>
 <!--边框的类型ST_BorderStyle是枚举型的字符串,如下 -->
 <xsd:simpleType name="ST_BorderStyle">
   <xsd:restriction base="xsd:string">
     <xsd:enumeration value="none"/>
     <xsd:enumeration value="thin"/>
     <xsd:enumeration value="medium"/>
     <xsd:enumeration value="dashed"/>
     <xsd:enumeration value="dotted"/>
     <xsd:enumeration value="thick"/>
     <xsd:enumeration value="double"/>
     <xsd:enumeration value="hair"/>
     <xsd:enumeration value="mediumDashed"/>
     <xsd:enumeration value="dashDot"/>
     <xsd:enumeration value="mediumDashDot"/>
     <xsd:enumeration value="dashDotDot"/>
     <xsd:enumeration value="mediumDashDotDot"/>
     <xsd:enumeration value="slantDashDot"/>
   </xsd:restriction>
 </xsd:simpleType>

这个太抽象了,三层定义.来个实例看看:

<borders count="3">
       <border>  <!--这1种几乎什么都没有规定,采用默认样式-->
           <left/>  <!--这类自封闭的标签表示没有内容-->
           <right/>
           <top/>
           <bottom/>
           <diagonal/>
       </border>
       <border>  <!--规定了底边风格/颜色-->
           <left/>
           <right/>
           <top/>
           <bottom style="mediumDashDotDot">
               <color rgb="FFFF0000"/>
           </bottom>
           <diagonal/>
       </border>
       <border diagonalUp="1"> <!--规定了斜线方向,底边风格/颜色,斜线风格/颜色-->
           <left/>
           <right/>
           <top/>
           <bottom style="mediumDashDotDot">
               <color rgb="FFFF0000"/>
           </bottom>
           <diagonal style="double">
               <color rgb="FF00B050"/>
           </diagonal>
       </border>
   </borders>

其实是很好理解的,只是看起来复杂.

applyXXX属性

最后,有几个格式如applyXXX的属性.它标识是否启用这一记录中相应的属性是否使用. 假如某子项的属性fontId=2,但是后面还跟了一个applyFont="0",那前面的指定值就不会应用.如果没有指定相应的applyFont="0",就相当于设置了applyFont="1". 规格中话虽然这么说,但我针对Excel2019 64位的测试表明在cellXfs集合中, 这些applyFont,applyFill无论其值为0还是1,对显示结果都没有影响. 修改xfId指向的cellStyleXfs中的格式,也没有效果. (也就是xfId也是被忽略的). 真正起作用的还是cellXfs子项的fontId, fillId等属性.对它们的修改是马上生效的.

applyXXX属性的存在,使得设置一个属性有了两种可能的方式,一种是直接修改如fontId, fillId等的设置;另一种通过否定前面的这一设置来决定不启用这一设置,实质也是设置了另一种选择. MS Office软件在实现这一功能的时候,可能是选择了前一种.


本文的markdown文件和PDF文件在此:

链接:https://pan.baidu.com/s/1nRwozzGm6_7j_SAIualDGA 

提取码:h9dd






https://wap.sciencenet.cn/blog-1213210-1246772.html

上一篇:学习笔记: Excel文件格式之条件格式研究及XSL实现
下一篇:学习笔记: 用XPath查询XML数据
收藏 IP: 36.27.72.*| 热度|

0

该博文允许注册用户评论 请点击登录 评论 (1 个评论)

数据加载中...

Archiver|手机版|科学网 ( 京ICP备07017567号-12 )

GMT+8, 2024-4-20 09:37

Powered by ScienceNet.cn

Copyright © 2007- 中国科学报社

返回顶部