Đọc các tệp Excel và viết chúng vào các tệp mới bằng các công nghệ Java và XML
Shaene M Siders, Tác giả và nhà tư vấn, Dragon Under Glass

(tiếp theo phần 1)

Tạo sẵn XOM và Apache POI với Eclipse

Về mặt kỹ thuật, các bước trong phần này đã được thực hiện trong dự án Employees Eclipse đã nhập khẩu. Tuy nhiên, trong trường hợp bạn bắt đầu dự án riêng của mình từ đầu, bạn sẽ cần nói cho dự án Eclipse biết để sử dụng các bản tải về XOM và Apache POI mới. Hãy hoàn thành các bước sau:

  1. Nhấn chuột phải vào thư mục Employees trong Package Explorer, rồi nhấn Properties (Các đặc tính).
  2. Nhấn Java Build Path (Đường dẫn xây dựng Java) ở ô bên trái.
  3. Nhấn thẻ Libraries (Các thư viện).
  4. Nhấn Add External JARs (Thêm các JAR bên ngoài), như trong Hình 4.

Hình 4. Thêm các tệp JAR ngoài vào đường dẫn xây dựng này

Lập trình Java cơ bản và nâng cao
Thêm đường dẫn xây dựng, ảnh chụp màn hình của hộp thoại Properties for Employees

5. Chọn tệp lưu trữ Java (JAR) có chứa các phần của Apache POI mà bạn sẽ sử dụng trong ví dụ mẫu này. (Nếu bạn đang sử dụng cùng một phiên 6. bản POI như bài viết này, đường dẫn sẽ là C:Program FilesEclipselibpoi-3.6poi-3.6-20091214.jar). Nhấn Open (Mở).
7. Nhấn lại Add External JARs.
8. Chọn tệp JAR chứa XOM (Nếu bạn đang sử dụng cùng một phiên bản POI như bài viết này, đường dẫn sẽ là C:Program FilesEclipselibXOMxom-1.2.1.jar). Nhấn Open.
9. Nhấn OK.

Sử dụng tệp ExcelReader.java

Với bài viết này, hãy sử dụng tệp ExcelReader.java nằm trong thư mục dự án Employees bên dưới thư mục src(default package). Hình 5 bên dưới thư mục.

Hình 5. Mở dự án Employees

Lập trình Java cơ bản và nâng cao
Ảnh chụp màn hình của tệp ExcelReader.java mở trong Eclipse SDK

Để chạy tệp này, nhấn vào nút mũi tên Run ở trên cùng màn hình, như trong Hình 6.

Hình 6. Chạy một tệp Java

Ảnh chụp màn hình cho thấy mũi tên Run trong thanh công cụ của Eclipse SDKr
Ảnh chụp màn hình cho thấy mũi tên Run
trong thanh công cụ của Eclipse SDK

Tệp ExcelReader.java đang chạy đọc thông tin từ các ô trong bảng tính Employee_List.xls và hiển thị nó bằng cách sử dụng thẻ Console của Eclipse, như trong Hình 7.

Hình 7. Kết quả đầu ra Java trong bàn giao diện Eclipse

Ảnh chụp màn hình của thẻ Console khi chạy ExcelReader.java trong Eclipse SDK
Ảnh chụp màn hình của thẻ Console khi chạy ExcelReader.java trong Eclipse SDK

Bắt đầu
Chìa khóa để hiểu công nghệ Java rất quen thuộc với ý tưởng làm việc với các đối tượng và khởi tạo (có nghĩa là tạo ra) các đối tượng đó. Định dạng chuẩn để tạo ra các đối tượng phù hợp là:

class objectName = new class();

objectName là tên của đối tượng mới được tạo ra. Nó giống như một biến dùng để xác định và đưa ra một cách làm việc với đối tượng cụ thể đó. Ngoài ra, thông tin, thường ở dạng các đối tượng khác hiện có, có thể đặt trong các dấu ngoặc đơn (()) sau class. Thông tin bên trong các dấu ngoặc đơn được sử dụng để tạo đối tượng mới.

Làm việc với các tệp
Bất cứ khi nào bạn làm việc với các tệp trong một môi trường Java, bạn có thể gặp nhiều vấn đề với tệp. Ví dụ, có thể thiếu tệp. Vì vậy, việc cố đọc một tệp có thể gây ra lỗi. Hãy nắm lấy bất kỳ các trường hợp ngoại lệ nào có thể do việc thao tác các tệp gây ra.

Để làm việc với các tệp Excel, bài viết này sử dụng lớp FileInputStream (java.io.FileInputStream). Lớp FileInputStream mô tả một tệp có thể không được tạo bằng văn bản thông thường. Vì các tệp Excel có chứa dữ liệu nhị phân, nên sử dụng FileInputStream thay cho lớp FileReader, dùng để đọc các tệp chỉ chứa các ký tự văn bản.

Bắt đầu lập trình
Bước đầu tiên để đọc một bảng tính Excel (Excel workbook) là chuẩn bị sử dụng Apache POI và các lớp cần thiết khác. Các lớp này đòi hỏi tệp ExcelReader.java có chứa một số lớp Apache POI, một số lớp ngoại lệ (lỗi), và một số lớp xử lý tệp. Liệt kê 1 cho thấy mã ở phần đầu của tệp ExcelReader.java, nhập khẩu các lớp này để tạo sẵn chúng cho sử dụng.

Liệt kê 1. Nhập khẩu các lớp (ExcelReader.java)

import java.io.FileInputStream;
import java.io.IOException;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

Sau khi nhập khẩu các lớp có liên quan, bạn có thể bắt đầu lập trình bên trong phần thân của phương thức chính bằng Apache POI.

HSSF có ý nghĩa gì trong Apache POI?
Các nhà lập trình Apache POI API chọn một quy ước đặt tên lạ thường cho các lớp của họ liên quan đến các bảng tính Excel; họ sử dụng tiền tố HSSF. Theo các tài liệu Java (Javadocs) của nó, tiền tố này thực sự là viết tắt của cụm từ Horrible Spread Sheet Format (Định dạng Bảng tính đáng ghét). Trong thực tế, theo Wikipedia, POI xuất phát từ nghĩa là Poor Obfuscation Implementation (Công cụ rắc rối đáng thương). Ai nói các nhà lập trình không biết đùa nào?

Các lớp HSSF dùng để làm việc với các phiên bản Excel có trước năm 2007 (nghĩa là các tệp .xls). Một tập các lớp khác —XSSF— dùng cho cho Excel 2007 và mới hơn (nghĩa là các tệp .xlsx). Tuy nhiên, có một tập —các lớp— làm việc với cả hai phiên bản. Vì mục tiêu đơn giản hóa, bài viết này chỉ sử dụng các lớp HSSF. Các mẫu mã có sử dụng các lớp khác được xác định trong tệp Readme.txt trong dự án Eclipse mà bạn đã tải xuống.

Các bảng tính
Lớp HSSF, đại diện cho một bảng tính Excel trong Apache POI, là org.apache.poi.hssf.usermodel.HSSFWorkbook. Chuyển cho HSSFWorkbook một FileInputStream làm hàm tạo của nó, và bạn có thể đòi hỏi nó biểu diễn tệp này nhờ dựa vào FileInputStream.

Nhưng hãy chờ đợi! Trong Javadocs cho Apache POI, không có hàm tạo nào dành cho HSSFWorkbook nói rằng nó có thể lấy một FileInputStream. Có một FileInputStream đang sử dụng một tính năng không có cơ sở không? Không. Có một hàm tạo chấp nhận một InputStream.

Vì FileInputStream là một lớp con của InputStream, về mặt kỹ thuật nó cũng là một InputStream, vì vậy có thể chuyển nó vào trong hàm tạo. Trong thực tế, InputStream là trừu tượng, do đó, cần có một số loại lớp con. FileInputStream sẽ làm việc một cách tốt đẹp.

Khi tạo FileInputStream, chuyển cho nó một String mô tả đường dẫn đến tệp Excel cần đọc. Đối với các tệp Windows®, hãy tránh có bất kỳ các ký tự đặc biệt trong đường dẫn tệp, đặc biệt là các dấu gạch chéo ngược phân cách-thư mục (). Sử dụng những dấu chéo ngược kép (\) để tạo ra một dấu gạch chéo ngược thoát trong chuỗi đường dẫn tệp.

Mã trong Liệt kê 2 tạo một FileInputStream mới, rồi tạo một HSSFworkbook mới dựa vào FileInputStream đó.

Liệt kê 2. Đọc một tệp Excel (ExcelReader.java)

public static void main(String[] args) {
// Create a FileInputStream to hold the file.
// Use double back-slashes to create one "escaped" slash.
// Use error handling (try/catch) around its creation in case
// the file to read does not exist.
// Be sure to import java.io.FileNotFoundException and java.io.IOException, or use
// the superclass IOException to handle both.

try {
FileInputStream excelFIS = new FileInputStream("C:\Planet Power\Employee_List.xls");

// Create an Excel Workbook Object using the FileInputStream created above
// (which contains the file).
// Use error handling around its creation in case of Input/Output Exception

HSSFWorkbook excelWB = new HSSFWorkbook(excelFIS);

}
catch (IOException e) {
System.out.println("Input/Output Exception!");
}

//End Main Method
}

Bây giờ, khi đang ở trong câu lệnh xử lý lỗi try tiến hành thu thập thông tin từ bảng tính Excel, bắt đầu với các tờ bảng tính của nó.

Các tờ bảng tính và các hàng

Một bảng tính có thể có một vài tầng các trang được gọi là các tờ. Lớp HSSFSheet (org.apache.poi.hssf.usermodel.HSSFSheet) mô tả một đối tượng tờ.

Một bảng tính có bao nhiêu tờ? Để tìm hiểu, bạn có thể sử dụng phương thức getNumberOfSheets() trên bảng tính. Tuy nhiên, với bài tập này, chỉ có một tờ, do đó, sử dụng số tờ của nó sẽ đơn giản hơn. Số của tờ đầu tiên là không (các máy tính thích đếm bắt đầu bằng không thay vì một). Mã sẽ trông giống như Liệt kê 3.

Liệt kê 3. Nhận được tờ bảng tính (ExcelReader.java)

// Start by getting the Spreadsheet (Excel books can have several
// sheets). Assuming there is just one sheet, it's the zero sheet.

HSSFSheet topSheet = excelWB.getSheetAt(0);

Sau khi có đối tượng tờ, di chuyển trên tờ này và làm việc với dữ liệu của nó. Các phương thức và các đặc tính có ích như các tên của chúng cho biết chúng làm gì:

– HSSFSheet.getFirstRowNum() và getLastRowNum() (nhận số hàng đầu và nhận số hàng cuối)
– HSSFSheet.getHeader() và getFooter() (nhận tiêu đề và nhận chân trang)
– HSSFSheet.getRow() (nhận hàng)
– HSSFSheet.getPhysicalNumberOfRows() (nhận số vật lý của hàng)

Để làm việc với dữ liệu trên tờ này, bắt đầu bằng cách nhận một đối tượng HSSFRow (org.apache.poi.hssf.usermodel.HSSFRow), mô tả một hàng trong tờ đó. Một cách để nhận được một hàng là sử dụng getRow() trên tờ đó và yêu cầu số hàng, như trong Liệt kê 4.

Liệt kê 4. Nhận được Hàng (ExcelReader.java)

// getRow() returns an HSSFRow object, but the numbering
// system is logical, not physical, and zero based.
// for example, use getRow(2) to get the third row.


HSSFRow thirdRow = topSheet.getRow(2);

Hãy nhớ rằng, topSheet là tờ bảng tính đã nhận được trước đó trong Liệt kê 3.

Sau khi có được hàng từ tờ này, hãy sử dụng hàng để truy vấn ngược đến mức ô.

Các ô
Để đi sâu vào dữ liệu của một ô riêng lẻ, sử dụng hàng này để nhận được một đối tượng HSSFCell (org.apache.poi.hssf.usermodel.HSSFCell) mô tả ô đó. Để có được thông tin theo định dạng String, sử dụng phương thức getStringCellValue() trên HSSFCell, như trong Liệt kê 5.

Liệt kê 5. Nhận được các ô và các chuỗi bên trong (ExcelReader.java)

// Get the first two cells in the row
HSSFCell lastnameCell = thirdRow.getCell(0);
HSSFCell firstnameCell = thirdRow.getCell(1);

// Get the string information in the cells
String firstName = firstnameCell.getStringCellValue();
String lastName = lastnameCell.getStringCellValue();

// Print out the value of the cells
System.out.println(firstName + " " + lastName);

Để thu thập tất cả các thông tin từ bảng tính này, lặp qua tất cả các tờ, từng hàng trong một tờ, và từng ô trong một hàng. Nhưng có một cách nắm bắt: Hãy thử chạy đoạn mã dưới đây, và nó làm việc với một số các ô. Tuy nhiên, nó sẽ thoát ra khi có một lỗi cố trích xuất các giá trị của ô và in ra (xem nhận xét trong Liệt kê 6). Tại sao?

Liệt kê 6. Vòng lặp qua tất cả các ô và in ra các giá trị. Bị hỏng!

// Traverse the sheets by looping through sheets, rows, and cells.
// Remember, excelWB is the workbook object obtained earlier.
// Outer Loop: Loop through each sheet

for (int sheetNumber = 0; sheetNumber < excelWB.getNumberOfSheets(); sheetNumber++) {
HSSFSheet oneSheet = excelWB.getSheetAt(sheetNumber);

// 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;
}

// 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);

// Get the value of the string in the cell.
// Print out the String value of the Cell
// This section will result in an error. Why?

String cellValue = oneCell.getStringCellValue();
System.out.println(cellValue + ", ");

// End Inner Loop
}
// End Middle Loop
}
// End Outer Loop
}

Cái gì gây lỗi? Phương thức getStringCellValue() chỉ làm việc với Strings. Do đó tên này gây ra lỗi.

Một số ô chứa các giá trị số. Để tránh lỗi này, hãy kiểm tra kiểu dữ liệu của ô và sử dụng phương thức thích hợp để lấy kiểu dữ liệu đó ra khỏi ô. Sử dụng getCellType() để xác định ô này chứa kiểu dữ liệu nào. Kiểu dữ liệu được trả về là một số nguyên đại diện cho kiểu dữ liệu này. Các trường tĩnh sau đây (hằng số) mô tả các kiểu dữ liệu:

HSSFCELL.CELL_TYPE_STRING. Sử dụng getStringCellValue().
HSSFCELL.CELL_TYPE_FORMULA. Sử dụng getCellFormula().
HSSFCELL.CELL_TYPE_NUMERIC. Sử dụng getNumericCellValue().
HSSFCELL.CELL_TYPE_BOOLEAN. Sử dụng getBooleanCellValue().

Ô có lẽ cũng chứa một lỗi Excel. Nếu vậy thì, getCellType() trả về toàn bộ HSSFCELL.CELL_TYPE_ERROR.

Trong khi lặp qua các ô, hãy kiểm tra các kiểu dữ liệu của chúng, như trong Liệt kê 7.

Liệt kê 7. Kiểm tra kiểu giá trị ô (ExcelReader.java)

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

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

// Test the value of the cell.
// Based on the value type, use the proper
// method for working with the value.

// 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;
}

switch (oneCell.getCellType()) {

case HSSFCell.CELL_TYPE_STRING:
System.out.println(oneCell.getStringCellValue());
break;

case HSSFCell.CELL_TYPE_FORMULA:
System.out.println(oneCell.getCellFormula());
break;

case HSSFCell.CELL_TYPE_NUMERIC:
System.out.println(oneCell.getNumericCellValue());
break;

case HSSFCell.CELL_TYPE_ERROR:
System.out.println("Error!");
break;
}

// End Inner Loop
}

Khi mã chạy, lưu ý rằng các ngày được hiển thị là các số, chứ không phải là các tên. Đó là do định dạng ngày không được lưu theo giá trị của nó. Đó là việc lựa chọn định dạng cho ô này. Phần 2 của loạt bài này sẽ thảo luận cách bảo toàn định dạng cho các ngày.

Một lưu ý quan trọng hơn: Trong các dòng mã sau đây từ Liệt kê 7, được ngắt ra trong Liệt kê 8 dưới đây, mã kiểm tra từng ô để đảm bảo rằng nó khác không.

Liệt kê 8. Đừng quên kiểm tra ô bằng không (ExcelReader.java)

// 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;
}

Trong một môi trường Java, nếu một đối tượng bằng không, việc cố thao tác nó sẽ gây ra một lỗi. Trước khi sử dụng các đối tượng như các hàng và các ô, hãy chắc chắn kiểm tra chúng để đảm bảo rằng chúng khác không.

Kết luận
Được trạng bị với những điều cơ bản về đọc các bảng tính Excel, bạn có thể bắt đầu chuyển đổi dữ liệu Excel vào trong các mảng, XML, hoặc các định dạng khác để thực hiện các phép tính hoặc tạo ra các bảng tính mới. Phần 2 của loạt bài này sẽ giải thích cách chuyển đổi thông tin bảng tính sang XML và tạo ra một bảng tính mới có các thay đổi so với dữ liệu gốc.

Khi giải quyết xong mã trong các bài viết này, hãy chắc chắn phục hồi nó để viết báo cáo xanh hơn.

Nguồn: ibm.com