内容


读取、回收和重用:使用 Excel、XML 和 Java 技术轻松搞定报告,第 2 部分

在 XML 和 Excel 报告格式之间转换

Comments

本周您推脱老板的 #432 创新理由了吗?您是否尝试过推脱?

有幸的是,您不必推脱。本系列的第 1 部分已帮助您理解如何使用 Java™ 技术处理 Microsoft® Excel® 电子表格(参见 参考资料 中到 “读取、回收和重用:使用 Excel、XML 和 Java 技术轻松搞定报告,第 1 部分” 的链接)。只要下载 Apache POI 并准备使用它,很快,处理 Excel 电子表格就跟逛公园一样简单了,而且几乎是绿色环保的。

但是读取 Excel 文件只是一个开始。本期文章将展示如何使用 Apache POI 和 XML Object Model (XOM) 将 Excel 文件存储在 XML 对象中。然后,可以重复利用这些对象,编写全新的 Excel 电子表格和 XML 文件。

样例应用程序

样例应用程序包含来自虚构的 Planet Power 公司的一个叫做 Employee_List.xls 的 Excel 电子表格。Big Boss 已经确信,Planet Power 的顶级员工将他们 1% 的薪水献给自己喜欢的事业:Genetically Engineered Enormous Wild Hamster Interplanetary Sanctuary (GEE WHIS)。样例应用程序计算出金额并创建一个 XML 报告,交给避难所(sanctuary)。同时,应用程序为 Big Boss 编写一个 Excel 电子表格。

要完成本文中的例子,下载样例 并将文件解压到 C:\Planet Power。然后,启动 Eclipse。

Employees2 Eclipse 项目

要导入包含样例应用程序的 Employees2 Eclipse 项目,需执行以下步骤:

  1. 在 Eclipse 中,右键单击 Package Explorer,然后单击 Import
  2. 展开 General,然后选择 Existing Projects into Workspace。单击 Next(参见 图 1)。
    图 1. 导入现有项目到工作空间
    Eclipse Import 对话框的屏幕截图,已选中一个现有项目将被导入到工作空间
    Eclipse Import 对话框的屏幕截图,已选中一个现有项目将被导入到工作空间
  3. 单击 Select root directory 旁边的 Browse,然后导航到 C:\Planet Power\Employees2。
  4. 选择 Employees2 文件夹,单击 OK,然后单击 Finish(参见 图 2)。
    图 2. 完成导入项目到 Eclipse 中
    Eclipse Import 对话框的屏幕截图,已完成导入项目到 Eclipse 中
    Eclipse Import 对话框的屏幕截图,已完成导入项目到 Eclipse 中

Employees2 文件夹应该出现在 Package Explorer 窗格中。

注意:对于该项目,使用 src\default_package 目录下 Employees2 项目中的文件 ExcelXML.java。

开始

在本系列的第 1 部分中,第一步是导入 Apache POI 以及异常和文件处理类(参见 参考资料 中到第 1 部分的链接)。此外,还需要添加一些 XML API 类以及用于处理数字的类,如 清单 1 中所示。

清单 1. 导入类 (ExcelXML.java)
// File and exception handling imports
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.ParseException;

// Apache POI imports
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFHyperlink;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFDataFormatter;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFDataFormat;

// XOM imports
import nu.xom.Document;
import nu.xom.Elements;
import nu.xom.Element;
import nu.xom.Attribute;
import nu.xom.Serializer;

// Imports for later calculations
import java.text.NumberFormat;

导入类之后,就可以开始在 main() 中编程了。

处理文件

清单 2 中的代码给出了一个针对文件(IOException)和数字转换(ParseException)错误的异常处理结构。

清单 2. 设置异常处理 (ExcelXML.java)
public class ExcelXML {

   public static void main(String[] args) {

      try {

      // Put file and number handling code here.

   // End try
   }
   catch (IOException e) {
      System.out.println("File Input/Output Exception!");
   }
   catch (ParseException e) {
      System.out.println("Number Parse Exception!");
   }

   // End main method
   }

// End class block
}

现在可以开始处理 XOM 了。

XOM、文档和 XML 对象

XOM 通过将 XML 解析成表示 XML 文档片段(piece)的对象,可以简化 XML 的处理。表示整个 XML 文档的类是 nu.xom.Document。从 Document 可以添加或访问其他片段。下面是一些用于处理 XML 的类:

  • nu.xom.Builder
  • nu.xom.Document
  • nu.xom.Elements
  • nu.xom.Element
  • nu.xom.Serializer

XML 的主片段叫做元素(element)。元素由一对标记和其中的内容组成。下面是一个示例元素,来自名叫 weather_service.xml 的样例文件。

<dawn>07:00</dawn>

单词 dawn 和它们的括号(<>)及斜杠(/)叫做标记(tag)07:00 是内容。

元素可以包含其他元素、文本内容,或者两者都包括。包含元素叫做父亲(parent),被包含在里面的元素叫做孩子(child 或 children) 元素。

在 XOM 中,元素由 nu.xom.Element 对象表示。一组元素是一个 nu.xom.Elements 对象。

根元素

为了找到元素,需使用这么一个元素,即格式非常好的 XML 文档必须具有的元素:根(root)元素。根元素充当所有其他元素的容器。文档没有根元素,就不是严格意义上的 XML 文档。

要找到根元素,可在 Document 对象上使用 getRootElement()。只取得一个元素为探索文档开启了玄机。想要处理根的子元素?使用 Element.getChildElements() 的变体得到所有子元素或者具有特定名称的子元素。想要单个元素?从 Element.getFirstChildElement() 得到第一个具有特定名称的子元素。

这似乎很熟悉?迭代通过 XML 文档以找到子元素的子元素类似于迭代通过 Excel 电子表格。

在前一篇文章中,看到了 Apache POI 的 getStringCellValue() 方法如何从 Excel HSSFCell 获得字符串值。类似地,XOM 使用 Element.getValue() 从 XML 元素得到字符串内容。但是,与处理单元格不同,处理 XOM 元素不需要测试数据类型。XML 都是文本。

Excel-XML 互相转换和 XML 标记

遍历电子表格抽取数据时,可以将数据存储在任意数量的对象中:Strings. Arrays. Squirrels。本文使用 XML 元素。(这样,遍历 Squirrels 更容易一些。)本文也提供一些在两种格式之间转换的方法。

清单 3 准备一个新的 HSSFWorkbook 和 XML Document 来存储工作簿的信息。

清单 3. 准备工作簿和 XML Document (ExcelXML.java)
// First, create a new XML Document object to load the Excel sheet into XML.
// To create an XML Document, first create an element to be its root.
Element reportRoot = new Element("sheet");

// Create a new XML Document object using the root element
Document XMLReport = new Document(reportRoot);

// Set up a FileInputStream to represent the Excel spreadsheet
FileInputStream excelFIS = new FileInputStream("C:\\Planet Power\\Employee_List.xls");

// Create an Excel Workbook object using the FileInputStream created above 
HSSFWorkbook excelWB = new HSSFWorkbook(excelFIS);

清单 3 中是否有步骤顺序反了?代码在创建 Document 之前创建一个新的 Element。为什么?

崭新的 Document 需要一样东西:即一个 Element 作为它的根元素。记住,Document 没有根元素就不能代表格式良好的 XML 文档。因此,创建 Document 时,必须给它传递一个根元素。

相反,可以创建不属于根元素的元素。可以将它们作为自由元素或者将它们附加到文档根或其他元素。但是,在开始之前,应该先规划好 XML 的结构。

XML 结构

XML 描述和格式化数据,主要通过元素和属性达到此目的。属性(Attribute)是名-值对。属性的名描述属性持有的数据。属性的值是属性的数据。HTML 代码的编写者熟悉诸如以下属性:

<font size="12">Hello, Planet!</font>

整个清单是一个元素。标记 <font>。属性是 size="12"。属性的名称是 size。属性值是 12。等号 (=) 连接两者。一般来说,数据存储在元素中,元数据存储在属性中。

那么,Employee_List.xls 工作簿应该如何转换成 XML?

标记方法 1:模仿工作簿结构

一种组织 XML 结构的方式是模拟 Excel 工作簿的物理结构。来看一下 清单 4

清单 4. 基于一般工作簿结构的 XML 结构
<sheet>
   <row>
      <cell>
         Thumb
      </cell>
      <cell>
         Green
      </cell>
      <cell>
         Growing Plants
      </cell>
      <cell>
         5:00 AM
      </cell>
      <cell>
         2:00 PM
      </cell>
      <cell>
         150,000
      </cell>
   </row>
</sheet>

利用这种格式,行和单元格都很清楚。但是每个单元格存有什么数据呢?5:00 AM 是员工的上班或下班时间?使用列名作为单元格属性怎么样?

如果说维护 Excel 电子表格结构很重要,也许可以这样做。但是在 XOM 中,不学习如何编写 XPath 查询,就没有容易的方法来基于属性值提取元素集合。将列名存储为属性的话,需要额外的代码来从特定的列找到所有元素的集合。由于 XOM 包含一个方法来按名称找到元素,所以我们考虑使用 Excel 列作为元素名而不是作为属性。

标记方法 2:模拟数据结构

这次的 XML 不是基于数据显示,而是考虑一种用于描述数据的结构。这是 XML 最擅长的,如 清单 5 所示。

清单 5. 基于描述数据的 XML 结构
<EmployeeData>
   <Employee>
      <EmployeeLastName>
         Thumb
      </EmployeeLastName>
      <EmployeeFirstName>
         Green
      </EmployeeFirstName>
      <MainSuperPower>
         Growing Plants
      </MainSuperPower>
      <DailyStartTime>
         5:00 AM
      </DailyStartTime>
      <DailyEndTime>
         2:00 PM
      </DailyEndTime>
      <Salary>
         150,000
      </Salary>
   </Employee>
</EmployeeData>

这样,可以在每个 Employee 元素上使用 getChildElements("Salary") 方法来快速找到员工的薪水。

但是,对元素名使用 Excel 列名是有风险的。Excel 列可以使用在 XML 元素名中无效的字符,比如说空格。所以,要确保潜在元素名中没有这样的字符。

要这样组织数据结构,必须对数据了如指掌。很难以程序方式计算出 Excel 电子表格中的一行是 XML 中的一个 Employee。也很难计算出根元素的名称(上例中是 EmployeeData)。

还有,如果结构发生改变或者 Big Boss 想要将代码重复用于其他电子表格,情况会如何呢?如果电子表格应该列出鼠类而不是员工的类型,那么您必须调整调用行的程序。

标记方法 3:Excel XML 格式混合

考虑混合 Excel 结构的 XML 和数据结构的标记,如 清单 6 所示。

清单 6. 混合工作簿结构和基于数据的标记
<sheet>
   <row>
      <EmployeeLastName>
         Thumb
      </EmployeeLastName>
      <EmployeeFirstName>
         Green
      </EmployeeFirstName>
      <MainSuperPower>
         Growing Plants
      </MainSuperPower>
      <DailyStartTime>
         5:00 AM
      </DailyStartTime>
      <DailyEndTime>
         2:00 PM
      </DailyEndTime>
      <Salary>
         150,000
      </Salary>
   </row>
</sheet>

如果每个 Excel 电子表格中的第一行都包含列名,那么该混合将足够灵活,可以处理多个 Excel 工作簿。由于文档和行中的数据在各个电子表格之间可能并不一致,所以您可以使用一般的 sheetrow 作为元素名,它们对于阅读代码的程序员仍然是有意义的。

注意:就纯数据为中心的标记而言,一定要小心那些混入 XML 元素名称中的非法字符。

本文使用 XML-Excel 混合格式来存储单元格值。但是怎么跟踪其他单元格信息呢(比如说数据类型和格式)?

数据类型和格式是元数据。根据必须保留什么元数据,可以使用诸如 dataFormatdataType 之类的属性。

编写代码进行转换

决定了 XML 的样子之后,就开始将 Excel 数据存储到 XML 元素中。使用跟本系列第 1 部分(参见 参考资料 中的链接)中相同的循环来遍历 Excel 电子表格。然后,添加 XML。清单 7 重复利用前一篇文章的 Excel 读取代码。

清单 7. 开始遍历 Excel 电子表格 (ExcelXML.java)
// Traverse the workbook's rows and cells.			
// Remember, excelWB is the workbook object obtained earlier.

// Just use the first sheet in the book to keep the example simple.
// Pretend this is an outer loop (looping through sheets).

HSSFSheet oneSheet = excelWB.getSheetAt(0);

      // Now get the number of rows in the sheet
      int rows = oneSheet.getPhysicalNumberOfRows();				

      // Middle loop: Loop through rows in the sheet

         for (int rowNumber = 0; rowNumber < rows; rowNumber++) {
            HSSFRow oneRow = oneSheet.getRow(rowNumber);

            // Skip empty (null) rows.
            if (oneRow == null) {
               continue;
            }

在迭代电子表格的行时,创建 XML 元素来表示行,如 清单 8 所示。

清单 8. 创建行元素 (ExcelXML.java)
            // Create an XML element to represent the row.
            Element rowElement = new Element("row");

还不要将行附加到 Document,以免具有空行或 null 行。如果行在添加单元格之后不为空,那么您可以在行循环的底部将它附加到根元素。

接下来,开始内部循环以读取单元格,如 清单 9 所示。

清单 9. 继续迭代 Excel 单元格 (ExcelXML.java)
            // Get the number of cells in the row
            int cells = oneRow.getPhysicalNumberOfCells();

            // Inner loop: Loop through each cell in the row

            for (int cellNumber = 0; cellNumber < cells; cellNumber++) {
               HSSFCell oneCell = oneRow.getCell(cellNumber);

               // If the cell is blank, the cell object is null, so don't 
               // try to use it. It will cause errors.
               // Use continue to skip it and just keep going.
               if (oneCell == null) {
                  continue;
               }

一旦进入内部循环,就创建一个 XML 元素来表示单元格,使用适当的列名作为元素名。在 清单 10 中,由于元素名不能为空,每个名称默认为 header。在第一行之后,基于存储在第一行元素中的数据计算新的名称(第一行中包含有 Excel 电子表格列名)。

清单 10. 使用列名作为元素名为单元格创建元素 (ExcelXML.java)
// Set up a string to use just "header" as the element name
// to store the column header cells themselves.

String elementName="header";

// Figure out the column position of the Excel cell.
int cellColumnNumber = oneCell.getColumnIndex();

// If on the first Excel row, don't change the element name from "header,"  
// because the first row is headers. Before changing the element name,
// test to make sure you're past the first row, which is the zero row.

if (rowNumber > 0)

 // Set the elementName variable equal to the column name
 elementName=
reportRoot.getFirstChildElement("row").getChild(cellColumnNumber).getValue();

// Remove weird characters and spaces from elementName,
// as they're not allowed in element names.
elementName = elementName.replaceAll("[\\P{ASCII}]","");
elementName = elementName.replaceAll(" ", "");

// Create an XML element to represent the cell, using 
// the calculated elementName

Element cellElement = new Element(elementName);

清单 10 中发生了很多事情。在代码清单中间位置,注释行 // Set the elementName variable equal to the column name 下面的一长行代码中,elementName 被设置为等于某某东西。读到行尾可以知道,它被使用 getValue() 设置为一个元素中的文本值。它使用的是哪个元素的值呢?

reportRoot 中,第一个行元素(reportRoot.getFirstChildElement("row"))中,代码使用 getChild(cellColumnNumber) 根据索引号找到一个子元素。已经将电子表格的第一行存储在第一个行元素中,所以行的子元素的值就是电子表格中的列名。索引号与电子表格中当前单元格的列号相同。所以 elementName 的值集就是对应的来自第一行 heading 元素的列名。

接下来,代码剔除电子表格中可能存在的含非法字符的 elementName 字符串。首先,StringreplaceAll() 方法用空字符串取代所有非 ASCII 字符。然后,再取代所有空格。两行都使用了正则表达式。有关正则表达式的信息,参见 参考资料

最后,清单 10 的最后一行使用适当的列名创建元素。

附加属性和元素

就跟元素一样,您可以单独地创建属性,并且属性可以一直保持游离状态,直到使用元素的 addAttribute() 方法将它们附加到元素为止。创建一个属性,然后使用 getter 方法(比如来自 Apache POI 的 HSSFCell 对象的 getDataFormatString()),用单元格元数据填充它,如 清单 11 所示。

清单 11. 添加属性 (ExcelXML.java)
// Create an attribute to hold the cell's format.
// May be repeated for any other formatting item of interest.
String attributeValue = oneCell.getCellStyle().getDataFormatString();
Attribute dataFormatAttribute = new Attribute("dataFormat", attributeValue);

// Add the attribute to the cell element
cellElement.addAttribute(dataFormatAttribute);

现在,元素已存在,并具有一个属性。

要得到元素数据,请记住测试每个 HSSFCell 的数据类型,以便知道使用哪个 getter 方法。因为总是要测试数据类型,所以也可以创建一个属性用于存储单元格的数据类型信息。

在处理字符串值时,向元素添加数据和将元素附加为行的子元素是非常直观的,如 清单 12 所示。

清单 12. 添加属性,将单元格文本附加到元素,以及将元素附加到行中 (ExcelXML.java)
switch (oneCell.getCellType()) {

   case HSSFCell.CELL_TYPE_STRING:

      // If the cell value is string, create an attribute
      // for the cellElement to state the data type is a string

      Attribute strTypeAttribute = new Attribute("dataType", "String");
      cellElement.addAttribute(strTypeAttribute);

      // Append the cell text into the element
      cellElement.appendChild(oneCell.getStringCellValue());

      // Append the cell element into the row
      rowElement.appendChild(cellElement);

      break;

对每一数据类型重复 case 部分。但是数值类型有点复杂。本系列的第 1 部分指出了,提取的 Excel 数据不同于电子表格数据(参见 参考资料 中到第 1 部分的链接)。它是原始数据。某些数字,比如日期,作为原始数据时看起来很奇怪,如果未带格式存储,那么可能会是不正确的值。数值数据进入元素之前,应该进行适当的格式化。可以使用 Apache POI 类 HSSFDataFormatter 和它的方法 formatCellValue() 来完成此任务。参见 清单 13

清单 13. 添加属性,格式化数值数据,以及附加单元格元素 (ExcelXML.java)
   case HSSFCell.CELL_TYPE_NUMERIC:
      // If the cell value is a number, create an attribute
      // for the cellElement to state the data type is numeric
      Attribute cellAttribute = new Attribute("dataType", "Numeric");

      // Add the attribute to the cell
      cellElement.addAttribute(cellAttribute);

      // Apply the formatting from the cells to the raw data
      // to get the right format in the XML. First, create an
      // HSSFDataFormatter object.

      HSSFDataFormatter dataFormatter = new HSSFDataFormatter();

      // Then use the HSSFDataFormatter to return a formatted string
      // from the cell rather than a raw numeric value:
      String cellFormatted = dataFormatter.formatCellValue(oneCell);

      //Append the formatted data into the element
      cellElement.appendChild(cellFormatted);

      // Append the cell element into the row
      rowElement.appendChild(cellElement);

      break;

为每种可能的单元格类型重复 case 部分。检查 ExcelXML.java 中的完整例子。

存储单元格数据之后,关闭内部循环。在关闭中间循环之前(该循环表示行),测试 row 元素是否为空。如果不为空,就将它附加到根元素。然后,关闭中间循环,如 清单 14 所示。

清单 14. 将行元素附加到根元素中 (ExcelXML.java)
      // End inner loop
      }

   // Append the row element into the root 
   // if the row isn't empty.  
   if (rowElement.getChildCount() > 0) {
      reportRoot.appendChild(rowElement);
   }

   // End middle loop	
   }

现在,您的 Excel 文件是一个完整的 XML 文档了。

XML 内部

在使用 XML 执行计算时,比如求出薪水的 1%,必须在字符串和数字之间进行转换。清单 15 提供了一个例子。

清单 15. 计算薪水的 1% 并存储到一个 donation 元素中 (ExcelXML.java)
// To get employees' salaries, iterate through row elements and get a collection of rows

Elements rowElements = reportRoot.getChildElements("row");

// For each row element

for (int i = 0; i < rowElements.size(); i++) {

   // Get the salary element, 
   // Calculate 1% of it and store it in a donation element.
      // Unless it's the first row (0), which needs a header element.
   if (i==0) {
      Element donationElement = new Element("header");
      donationElement.appendChild("Donation");

      Attribute dataType = new Attribute("dataType","String");
      donationElement.addAttribute(dataType);

      Attribute dataFormat = new Attribute("dataFormat","General");
      donationElement.addAttribute(dataFormat);

      // Append the donation element to the row element.
      rowElements.get(i).appendChild(donationElement);
   }

   // If the row is not the first row, put the donation in the element.
   else {
      Element donationElement = new Element("Donation");

      Attribute dataType = new Attribute("dataType","Numeric");
      donationElement.addAttribute(dataType);

      // The dataFormat of the donation should be the same 
      // number format as salary, which looking at the XML file tells
      // us is "#,##0".
      Attribute dataFormat = new Attribute("dataFormat","#,##0");
      donationElement.addAttribute(dataFormat);

      // Get the salary element and its value
      Element salaryElement = rowElements.get(i).getFirstChildElement("Salary");
      String salaryString = salaryElement.getValue();

      // Calculate 1% of the salary. Salary is a string
      // with commas, so it 
      // must be converted for the calculation.

      // Get a java.text.NumberFormat object for converting string to a double
      NumberFormat numberFormat = NumberFormat.getInstance(); 

      // Use numberFormat.parse() to convert string to double.
      // Throws ParseException
      Number salaryNumber = numberFormat.parse(salaryString);

      // Use Number.doubleValue() method on salaryNumber to 
      // return a double to use in the calculation.
      // Perform the calculation to figure out 1%.
      double donationAmount = salaryNumber.doubleValue()*.01;

      // Append the value of the donation into the donationElement.
      // donationAmount is a double and must be converted to a string.
      donationElement.appendChild(Double.toString(donationAmount));

      // Append the donation element to the row element
      rowElements.get(i).appendChild(donationElement);

      //End else
      }

// End for loop 
}

现在,为每一行存储了一个额外的 Donation 元素。已经完成 XML Document 对象的构建。

有了 XOM,很容易将 Document 对象写入 XML 文件。使用 nu.xom.Serailizer.write(),如 清单 16 所示。

清单 16. 将 Excel 写入 XML (ExcelXML.java)
   // Print out the XML version of the spreadsheet to see it in the console
   System.out.println(XMLReport.toXML());

   // To save the XML into a file for GEE WHIS, start with a FileOutputStream
   // to represent the file to write, C:\Planet Power\GEE_WHIS.xml.
   FileOutputStream hamsterFile = new FileOutputStream("C:\\Planet Power\\GEE_WHIS.xml");

   // Create a serializer to handle writing the XML
   Serializer saveTheHamsters = new Serializer(hamsterFile);

   // Set child element indent level to 5 spaces to make it pretty
   saveTheHamsters.setIndent(5);

   // Write the XML to the file C:\Planet Power\GEE_WHIS.xml
   saveTheHamsters.write(XMLReport);

鼠类将会喜欢它们的新捐款报告。XML 是它们的本土语言。

写回 Excel

要将 XML 写回 Excel 电子表格,应该迭代 XML,并设置单元格值和格式。清单 17 做好准备并开始循环通过行元素。

清单 17. 准备将 XML 写入 Excel (ExcelXML.java)
// Create a new Excel workbook and iterate through the XML 
// to fill the cells.
// Create an Excel workbook object 
HSSFWorkbook donationWorkbook = new HSSFWorkbook();

// Next, create a sheet for the workbook.	
HSSFSheet donationSheet = donationWorkbook.createSheet(); 

// Iterate through the row elements and then cell elements

// Outer loop: There was already an elements collection of all row elements
// created earlier. It's called rowElements. 
// For each row element in rowElements:

for (int j = 0; j < rowElements.size(); j++) {

   // Create a row in the workbook for each row element (j)
   HSSFRow createdRow = donationSheet.createRow(j);

   // Get the cell elements from that row element and add them to the workbook.
   Elements cellElements = rowElements.get(j).getChildElements();

跟循环通过行元素一样,循环通过单元格元素也很直观。比较困难的部分是格式化单元格。

HSSFCellStyle 对象表示单元格的样式选项,比如字体、边框和数值格式(包括日期和时间格式)。但是,样式是针对工作簿,而不是针对单元格的。HSSFCellStyle 对象表示工作簿中存在的可应用于单元格的指定样式。这些样式是样式选项组,就像 Microsoft Office Word 中指定的样式一样。类似地,HSSFDataFormat 是针对工作簿创建的,但是只表示数值格式,比如日期和时间格式。

要指定单元格的样式,应该为工作簿创建一个新的 HSSFCellStyle,或者使用现有的 HSSFCellStyle。然后,使用 HSSFCell.setCellStyle() 将它应用于单元格。要为单元格设置数值格式,设置 HSSFCellStyle 而不是单元格的数值格式。然后,将 HSSFCellStyle 应用于单元格。

HSSFDataFormat 对象按工作簿中的数字进行索引。要告诉 HSSFCellStyle 使用哪种 HSSFDataFormat,需要用到 HSSFDataFormat 的索引号。这是 short 类型的数值,不是 HSSFDataFormat 对象。

幸运的是,HSSFDataFormat 对象具有一个叫做 getFormat() 的方法。给它传递一个表示某格式的字符串,它会返回匹配该字符串的 HSSFDataFormat 的索引号。索引是作为 short 类型的数值返回的。如果没有匹配项,它就创建一个新的 HSSFDataFormat 并返回其索引。您可以使用该索引来将格式应用于单元格样式以及将单元格样式应用于单元格,如 清单 18 所示。

清单 18. 循环通过单元格元素并在插入数据之前设置适当的数值格式
   // Middle loop: Loop through the cell elements.
   for (int k = 0; k < cellElements.size(); k++) {	

      // Create cells and cell styles. Use the row's
      // createCell (int column) method.
      // The column index is the same as the cell element index, which is k.
      HSSFCell createdCell = createdRow.createCell(k);	

      // To set the cell data format, retrieve it from the attribute 
      // where it was stored: the dataFormat attribute. Store it in a string.
      String dataFormatString = cellElements.get(k).getAttributeValue("dataFormat");

      // Create an HSSFCellStyle using the createCellStyle() method of the workbook.
      HSSFCellStyle currentCellStyle = donationWorkbook.createCellStyle();

      // Create an HSSFDataFormat object from the workbook's method
      HSSFDataFormat currentDataFormat = donationWorkbook.createDataFormat();

      // Get the index of the HSSFDataFormat to use. The index of the numeric format
      // matching the dataFormatString is returned by getFormat(dataFormatString).
      short dataFormatIndex = currentDataFormat.getFormat(dataFormatString);

      // Next, use the retrieved index to set the HSSFCellStyle object's DataFormat.
      currentCellStyle.setDataFormat(dataFormatIndex);

      // Then apply the HSSFCellStyle to the created cell.
      createdCell.setCellStyle(currentCellStyle);

设置单元格样式之后,使用 setCellValue() 将大多数数据类型插入单元格中。

但是,数值数据需要特殊处理。要将数字存储为数字而不是文本,需要先将它们转换成 doubles 类型。不要将日期转换成 doubles 类型,否则将不正确。测试数值数据的数据格式,以确定它是否是日期(参见 清单 19)。

清单 19. 插入单元格数据,对于某些数值数据,要转换成 doubles 类型
      // Set cell value and types depending on the dataType attribute

      if (cellElements.get(k).getAttributeValue("dataType")=="String") {
         createdCell.setCellType(HSSFCell.CELL_TYPE_STRING);
         createdCell.setCellValue(cellElements.get(k).getValue());
      }

      if (cellElements.get(k).getAttributeValue("dataType")=="Numeric") {
         createdCell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);

         // In this spreadsheet, number styles are times, dates,
         // or salaries. To store as a number and not as text,
         // salaries should be converted to doubles first.
         // Dates and times should not be converted to doubles first,
         // or you'll be inputting the wrong date or time value.
         // Dates and times can be entered as Java Date objects.

         if (cellElements.get(k).getAttributeValue("dataFormat").contains("#")) {

            // If formatting contains a pound sign, it's not a date.
            // Use a Java NumberFormat to format the numeric type cell as a double,
            // because like before, the element has commas in it.
            NumberFormat numberFormat = NumberFormat.getInstance(); 
            Number cellValueNumber = numberFormat.parse(cellElements.get(k).getValue());
            createdCell.setCellValue(cellValueNumber.doubleValue());

            // Add a hyperlink to the fictional GEE WHIS Web site just
            // to demonstrate that you can.
            HSSFHyperlink hyperlink = new HSSFHyperlink(HSSFHyperlink.LINK_URL);
            hyperlink.setAddress("http://www.ibm.com/developerworks/");
            createdCell.setHyperlink(hyperlink);
         }

         else {
            // if it's a date, don't convert to double
            createdCell.setCellValue(cellElements.get(k).getValue());
         }

      }

//  Handle formula and error type cells. See ExcelXML.java for the full example.

      //End middle (cell) for loop
      }
   // End outer (row) for loop	
   }

格式化对于创建日期的 Excel 函数(比如 TODAY()NOW())也是必要的。参见 清单 20

清单 20. 使用具有适当格式的 Excel 函数
// Demonstrate functions:
// Add the TODAY() and NOW() functions at bottom of the Excel report
// to say when the workbook was opened.

// Find the last row and increment by two to skip a row
int lastRowIndex = donationSheet.getLastRowNum()+2;

// Create a row and three cells to hold the information.
HSSFRow lastRow = donationSheet.createRow(lastRowIndex);
HSSFCell notationCell = lastRow.createCell(0);
HSSFCell reportDateCell = lastRow.createCell(1);
HSSFCell reportTimeCell = lastRow.createCell(2);

// Set a regular string value in one cell
notationCell.setCellValue("Time:");

// Setting formula values uses setCellFormula()
reportDateCell.setCellFormula("TODAY()");
reportTimeCell.setCellFormula("NOW()");	

// Create HSSFCellStyle objects for the date and time cells.
// Use the createCellStyle() method of the workbook.

HSSFCellStyle dateCellStyle = donationWorkbook.createCellStyle();
HSSFCellStyle timeCellStyle = donationWorkbook.createCellStyle();

// Get a HSSFDataFormat object to set the time and date formats for the cell styles
HSSFDataFormat dataFormat = donationWorkbook.createDataFormat();

// Set the cell styles to the right format by using the index numbers of
// the desired formats retrieved from the getFormat() function of the HSSFDataFormat.
dateCellStyle.setDataFormat(dataFormat.getFormat("m/dd/yy"));
timeCellStyle.setDataFormat(dataFormat.getFormat("h:mm AM/PM"));

// Set the date and time cells to the appropriate HSSFCellStyles.
reportDateCell.setCellStyle(dateCellStyle);
reportTimeCell.setCellStyle(timeCellStyle);

最后,完成想要的工作簿对象之后,使用工作簿的 write() 方法将它写入一个文件(参见 清单 21)。

清单 21. 将 Excel 工作簿写入文件
// Write out the workbook to a file. First,
// you need some sort of OutputStream to represent the file.
	
	String filePathString = "C:\\Planet Power\\Employee_Donations.xls";
	FileOutputStream donationStream = new FileOutputStream(filePathString);
	
	donationWorkbook.write(donationStream);

现在已经编写了一个 Excel 电子表格,它计算针对 GEE WHIS 的捐款。

结束语

报告完成了。不仅能够读取 Excel 和创建 XML,Java 程序员现在也可以将 XML 写回到 Excel 文件了。理解了两种格式之间的转换的基本知识之后,就会对整个报告思路豁然开朗。

Big Boss 很高兴,您尽自己的职责帮助了 Planet Power 的环保英雄们拯救 Genetically Engineered Enormous Wild Hamsters。大家都高兴。报告确实可以有利于环境。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Java technology
ArticleID=482838
ArticleTitle=读取、回收和重用:使用 Excel、XML 和 Java 技术轻松搞定报告,第 2 部分
publish-date=04152010