将 Excel 2007 XML 转换成 OASIS 代码清单

采用 Genericode

Genericode — 一种 OASIS 规范 — 是以 XML 方式对代码清单或权限清单进行编码的一种 XML 词典。这些清单通常是由熟悉 Microsoft® Office 解决方案但不熟悉 XML 技术的业务用户所开发和维护的。本文将详细介绍使用 Microsoft® Office Excel 2007 电子表格创建和维护代码清单,然后使用 XSLT 将其转换为符合 Genericode 规范的电子表格,这是诸如在线表单等应用程序很容易使用的格式。

Betty Harvey, 信息架构师, IBM

Betty Harvey 是 Electronic Commerce Connection, Inc. 的总裁。她参与了许多政府和商业企业的结构化信息迁移的规划和执行。Betty 参与创建了 ebXML,并且是 Professional ebXML Foundations (Wrox, 2001) 及许多其他著作的合著者。Betty 发起并主持了 Washington, D.C. 区 SGML/XML 用户群组活动,同时也是 XML Guild 的成员之一。



2011 年 2 月 21 日

Genericode 是在高级结构化信息标准组织(OASIS)的规范通用商业语言(UBL)技术协会(见 参考资料)中开始使用的。UBL 一开始属于联合国贸易促进中心和电子商务(UN/CEFACT)核心组件技术规范(见 参考资料 中的链接)— 一个直接在 W3C 模式中使用枚举包含代码清单的规范,如当前代码和国家代码。

常用缩写词

  • W3C:世界万维网联合会
  • XML:可扩展标记语言
  • XSLT:可扩展样式表语言转换

UBL 社区很快发现这个代码清单是经常变化的。虽然将清单直接嵌入到模式中可以简化验证过程,但是当清单中一个代码发生变化时,这个模式就必须更新。而在多个合作伙伴中处理模式更新可能是极为耗时的。此外,许多产品需要重新编译模式,并基于更新的模式重新初始化数据库。

UBL 技术协会开发作为一个抽象层的 Genericode,用它来解决每当代码变化时模式的更新(参考 参考资料 中链接了解更多信息)。其他验证方法,如 Schematron,可以作为验证层进行代码验证。Genericode 具有比 UBL 更广泛的用户群,而 OASIS 决定建立一个新的技术协会来管理 Genericode 规范。

本文将介绍如何使用 Microsoft Office Excel 2007 电子表格来创建代码清单。然后您可以使用 XSLT 将这些清单从 Excel 电子表格转换成 Genericode。

Office Excel 2007 中的代码清单

虽然本文主要是介绍 Genericode 转换,但是其中的概念适用于其他类型的 Excel 文档和 XML 词汇。例如,我曾经进行过许多的 Microsoft Office Excel 2003 转换,并且发现这个过程是非常简单的。要从 Office Excel 2003 转换成 XML,您必须将电子表格保存成 Office Excel 2003 XML 格式。我的其中一个转换是一个能够从 W3C 模式创建电子表格的模式分析电子表格。Excel 格式提供了一种可供非技术人员理解模式组件之间关系及其定义的简单方法。

我创建的另一个 “go-to” 转换是从 Office Excel 2003 XML 到 Genericode 的转换,这是一个可以在生产环境中使用的转换。在这个转换中,许多代码清单都具有空单元格。然而,在 Office Excel 2003 XML 中,如果一个单元格是空的,它就不会输出到 XML 中,而且无法简单地自动分析整行和确定哪些单元格是空白的。这种情况在 Office Excel 2003 和 Office Excel 2007 均会出现。然而,Office Excel 2007 逻辑上能够导出一个空白单元格。

根据本文撰写的目的,我使用了 图 1 所示的简单结构,它表示一个 Excel 代码清单。

图 1. 示例 Excel 代码清单
截图显示示例 Excel 代码清单的 CodeList 名称、代码值名称(列标签)和各个值

OASIS Genericode 和 Office Excel 2007 结构

在开始任何转换项目之前,您必须理解初始结构和终止结构。Genericode 可能变得很复杂,但是人们所使用的 98% 的代码清单都是非常简单的。一个示例代码清单可以分成三个部分:

  • 标识
  • 列规范
  • 代码清单数据

图 2 显示了 Genericode 的 XML 模式的图形视图。一个 CodeList 文档(类型:gc:CodeListDocument)包含了它的描述性属性。gc:DocumentHeader 则包含了注释(类型:gc:Annotation)和标识(类型:gc:Identification)。gc:ColumnSetChoice 包含了 ColumnSet(类型:gc:ColumnSet) 和 ColumnSetRef(类型:gc:ColumnSetRef)。gc:OuterCodeListChoice 包含了 gc:SimpleCodeListSequence 和 SimpleCodeList(类型:gc:SimpleCodeList)。

图 2. Genericode 的结构
Genericode 结构示意图

Office Excel 2007 XML 比 Genericode 稍微复杂一些。事实上,这个结构是非常奇怪的。这个 XLSX 文件实际上是一个包含多个文件的 ZIP 文件:

  • 根目录:
    • _rels
    • docProps
    • xl
      • _rels
      • printerSettings
      • theme
      • worksheets

每一个目录都包含多个 XML 文件。仅为了实现转换,您并不需要关注于所有文件及其作用。它们中有一些是 Excel 内部用来控制 Excel 应用程序的。与本文所介绍的转换有关的文件有:

  • /docProps/app.xml. 包含了结构信息
  • /docProps/core.xml. 包含了时间、作者等元数据
  • /xl/worksheets/sheet1.xml. 包含了数据的映射
  • /xl/sharedStrings.xml. 包含了实际的数据

Although there is logic to the files, at a quick glance that logic isn't obvious. 这些文件都各自有其作用,我们需要稍微分析一下才能了解。


Excel 数据映射

sharedStrings.xml 和 sheet1.xml 文件是一起工作的。让我们看一下 图 1 所示的 sharedStrings.xml 文件:清单 1 显示了这个 XML 文件的文件结构。

清单 1. sharedStrings.xml 的文件结构
<sst>
     <si>
          <t>My Data 25</t>
     </si>
     <si>
          <t>My Data 10</t>
     </si>
     ...
</sst>

这个文件有意思的一个地方就是它本身似乎并没有表示 sharedStrings.xml 文件所包含数据的逻辑顺序。它本质上一个包含 Excel 电子表格中所有单元格的简单清单。

根据一个单元格中的数据类型,不同类型的数据存储位置是不同的:

  • 字符串数据。 位于 sharedStrings.xml 文件
  • 整形数据。 位于 sheet1.xml 文件
  • 公式数据。 位于 sheet1.xml 文件

说明: 代码清单中一般不太可能有公式数据。

sheet1.xml 文件包含了数据的映射。图 3 显示了 sheet1.xml 文件的结构。(查看 图 3全文版本。)

图 3. sheet1.xml 的结构
sheet1.xml 的结构图

数据的内容位于 <row> 元素中。表 1 显示了 sheet1.xml 文件所包含的元素。

表 1. sheet1.xml 的元素和属性
元素或属性描述
rowExcel 电子表格中一行的开始。
@r行号。
@spans该行占据的列数。第一个数字是开始位置; 第二个数字是结束位置。
c单元格信息。
@r单元格的物理位置 — 例如,B10 是第 B 列第 10 行。
@s="1"单元格的实际值。在这里,sharedStrings.xml 不需要使用查找。
@t数据类型 — 例如,s 表示一个字符串。当具有 @t 时,<v> 元素将作为 sharedStrings.xml 文件的一个查找。
v<si> 元素个数加上 1,而实际的数据位于 sharedStrings.xml 中。

这个元素的值是 sharedStrings.xml 文件中 <si> 元素的 XPath 数(+1)。第一个单元格表示字符串数据。t 属性的值被设置为 ss 表示一个字符串值。

第二、第三个单元格没有 t 属性。没有 t 属性表示这个文件包含了数据。您可以在第一个字符上添加加号(+)来区分一个计算得到的域与整数。


开发 XSLT

由于开发这个转换方法涉及多个文件并且有多种方法,所以您必须选择一个 XML 文件作为您的基本 XML 文件。我选择使用 sheet1.xml 文件,因为它包含了数据的映射。数据文件(sharedStrings.xml)是静态文件,并且很难推断它的智能导航。正如之前提到的,Genericode 具有三个主要的信息窗容器。让我们一起详细了解每一个容器的转换。

全局变量

您需要在多个文件中执行查找。我并没有使用各个文件的完整路径,因为我发现创建全局变量更简单且更具可读性。

下面的参数设置了这些 XML 文件的根目录。如果您使用一个 XProc 或批处理任务来转换多个代码清单,您可以将这个参数的值发送到这个过程中:

<xsl:param name="ExcelRoot">MyCodelist</xsl:param>

您可以使用这里所显示的变量获得包含信息的文件的完整路径和文件名:

<xsl:variable name=”DataFile”>
  <xsl:value-of select=”$ExcelRoot”/>/xl/sharedStrings.xml</xsl:variable>

根模板

<worksheet> 模板是转换的起点。清单 2<worksheet> 的一个示例模板。基本上,我们初始模板会调用每一个部分的转换。您希望启动转换的文件是 sheet1.xml。其命名空间前缀是 Excel(但是您可以根据需要修改成其他名称)。其中根元素是 <worksheet>。注意,我的模板是 Excel:worksheet

清单 2. 开始模板
<xsl:template match="Excel:worksheet">
   <CodeList xmlns="http://docs.oasis-open.org/codelist/ns/genericode/1.0/"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:era="http://archives.gov/era/schema"
         xsi:schemaLocation="http://docs.oasis-open.org/codelist/ns/genericode/1.0/
         http://docs.oasis-open.org/codelist/cs-genericode-1.0/xsd/genericode.xsd">

         <xsl:call-template name="commentBlock"/>
         <xsl:call-template name="identificationBlock"/>
         <xsl:call-template name="createColumnSet"/>
         <xsl:apply-templates/>
        </CodeList>
    </xsl:template>

您还必须创建一个默认模板来覆盖不想要的 Excel 特有的数据:

<xsl:template match=”*”/>

基于本文的撰写目的,我主要关注于代码清单(<SimpleCodeList>),因为当您实现这个逻辑之后,其他部分就很简单了。但是每一个组织都有其不同的开发 Excel 代码清单模板的方法。


创建 <SimpleCodeList> 和 <Row> 输出

<sheetData> 元素是 <worksheet> 元素的直系子元素。<SimpleCodeList> 元素属于 <sheetData> 元素。而 <Row> 元素属于 <row> 元素。您可以看到,这个方法是非常简单的。清单 3 显示了创建 <SimpleCodeList><Row> 元素的 XML 代码。

清单 3. 创建 <SimpleCodeList> 和 <Row>
   <xsl:template match="Excel:sheetData">
        <SimpleCodeList>
            <xsl:apply-templates/>
        </SimpleCodeList>
    </xsl:template>

    <xsl:template match="Excel:row">
        <Row>
            <xsl:apply-templates/>
        </Row>
    </xsl:template>

创建列引用

Genericode 要求在 Genericode 中每一个条目中使用列引用。这个信息可以从 <ColumnSet> 元素获取。我并没有介绍如何创建 <Identification><ColumnSet> 条目,因为它们会根据您的公司用于维护 Excel 代码清单的模板不同而不同。然而,列引用信息是生成 <Value> 元素的 @ColumnRef 信息时必须使用的。

我手动为每一个标题设置这个变量。可能有其他更灵活的设置这些变量的方法,但是我这种方法也是可行的。然后,我为所有列创建一个变量。在 清单 4 中,我选择单元格 A2 的 <v> 元素的值。记住,由于数字是从 0 开始的,所以您要给 <v> 值加上 1。

清单 4. 创建列引用
<xsl:variable name="ColumnALocation">
   <xsl:value-of select=
    "//Excel:worksheet/Excel:sheetData[1]/Excel:row[2]/Excel:c[1]/Excel:v + 1"
 />

这样,您就可以获得单元格的实际值(见 清单 5)。在这里,我执行了一个测试来保证列位置不为 null。如果值不为 null,那么我会在 sharedStrings.xml 文件中执行一个查找,其中位置轴(position())等于 sheet1.xml 中 <v> 元素的值。我在后面会使用这个变量来判断 @ColumnRef 属性。

清单 5. 查询一个单元格的实际值
<xsl:variable name="Column-A">
    <xsl:choose>
        <xsl:when test="$ColumnALocation = ''"/>
        <xsl:otherwise>
            <xsl:value-of
                select="document($DataFile)/Excel:sst/Excel:si[position() =
            $ColumnALocation]"
            />
        </xsl:otherwise>
    </xsl:choose>
</xsl:variable>

创建 <Value> 元素

如果您回去查看 图 1 所示例子,您可以看到列 A1 的值是 a1。您希望您的 Genericode 表现如下:

<Value ColumnRef="ColumnA">
    <SimpleValue>a1</SimpleValue> 
</Value>

<c> 元素的 @r 属性包含了单元格位置。例如,A2 表示第 A 列第 2 行。这个例子显示的列标题是 A2B2C2。您需要知道标题和列标题位于哪些单元格上。

现在,您需要设置以下三个变量:

  • column列号 — 例如,B 表示第二列。@r 的值包含了这个信息。
  • dataLoc数据的位置,即 sharedStrings.xml 文件中 XPath + 1 位置的数据。
  • cellValue这个单元格的实际值。您可以给这变量设置 sharedStrings.xml 文件的一个直接查找。

在设置这些变量之后,您就可以开始填充数据。这个过程的第二部分是填充 @ColumnRef 属性。您可以在相同的模板上执行这个操作,但是我选择创建另一个模板,然后传递这个列变量来查找列名,如 清单 6 所示。

清单 6. 示例 <c> 模板
<xsl:template match="Excel:c">
    <xsl:variable name="column"><xsl:value-of select="substring(@r, 1, 1)"/>
    </xsl:variable>
    <xsl:variable name="dataLoc"><xsl:value-of select="number(Excel:v) + 1"/>
    </xsl:variable>
    <xsl:variable name="cellValue">
       <xsl:value-of 
         select="document($DataFile)/Excel:sst/Excel:si[position() = $dataLoc]"/>
    </xsl:variable>
   <xsl:choose>
      <xsl:when test="Excel:v">
         <Value>
            <xsl:attribute name="ColumnRef">
                 <xsl:call-template name="getColref">
                    <xsl:with-param name="column"><xsl:value-of select="$column"/>
                    </xsl:with-param>
                  </xsl:call-template>
                 </xsl:attribute>
                <SimpleValue><xsl:value-of select="$cellValue"/></SimpleValue>
              </Value>
           </xsl:when>
        <xsl:otherwise/>
    </xsl:choose>
</xsl:template>

列引用名称

最后一步是获取列名。只要您的代码清单中包含的列不是非常多,那么 清单 7 的方法就足够了。大多数代码清单都只具有 2 至 5 个列。

清单 7. 创建列引用
  <xsl:template name="getColref">
        <xsl:param name="column"/>
        <xsl:choose>
            <xsl:when test="$column = 'A'">
                <xsl:value-of select="translate($Column-A, ' ', '')"/>
            </xsl:when>
            <xsl:when test="$column = 'B'">
                <xsl:value-of select="translate($Column-B, ' ', '')"/>
            </xsl:when>
            <xsl:when test="$column = 'C'">
                <xsl:value-of select="translate($Column-C, ' ', '')"/>
            </xsl:when>
  </xsl:template>

结束语

代码清单的所有者一般是熟悉 Microsoft 工具的业务用户。业务用户可以在一个他们熟悉的工具中更新和管理代码清单数据。使用 XSLT 将技术需求转换为业务需求是很有意义的;这个工作流实现了业务主题专家和技术需要的分离,简化了许多操作,是一种高效实用的技术解决方案。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=628124
ArticleTitle=将 Excel 2007 XML 转换成 OASIS 代码清单
publish-date=02212011