이번 주에 상사에게 창의적인 이유 #432를 제시해 보았는가? 제시하고 싶은 유혹을 느꼈는가?
다행히도 그럴 필요가 없었다. 이 시리즈의 Part 1은 Java™ 기술로 Microsoft® Excel® 스프레드시트에 대해 작업하는 방법을 이해하는 데 도움을 주었다(참고자료의 "읽기, 재활용 및 재사용: Excel, XML 및 Java 기술을 사용하여 간편하게 보고하기, Part 1" 참조). 단순히 Apache POI를 다운로드하여 이를 사용할 수 있도록 설정하면 Excel 스프레드시트를 다루는 것이 공원을 산책하는 것만큼 쉬워진다. 또한 친환경적이다.
하지만 Excel 파일 읽기는 시작에 불과하다. 이 기사에서는 Apache POI 및 XOM(XML Object Model)을 사용하여 XML 오브젝트에 Excel 파일을 저장하는 방법을 보여 준다. 그러면 이러한 오브젝트를 재활용하여 완전히 새로운 Excel 스프레드시트 및 XML 파일을 작성할 수 있다.
샘플 애플리케이션에는 가상의 Planet Power사의 Employee_List.xls라는 Excel 스프레드시트가 포함되어 있다. 빅 보스는 Planet Power의 임원들이 급여의 1%를 자신이 좋아하는 분야인 GEE WHIS(Genetically Engineered Enormous Wild Hamster Interplanetary Sanctuary)에 기부하도록 설득했다. 샘플 애플리케이션에서는 금액을 계산하고 보호 구역에 대한 XML 보고서를 작성한다. 그 사이에 애플리케이션은 빅 보스를 위해 Excel 스프레드시트를 작성한다.
이 기사의 예제를 따르려면 샘플을 다운로드하여 파일을 C:\Planet Power에 압축 해제한다. 그런 다음 Eclipse를 시작한다.
샘플 애플리케이션이 포함된 Employees2 Eclipse 프로젝트를 가져오려면 다음과 같은 단계를 수행한다.
- Eclipse에서 Package Explorer를 마우스 오른쪽 단추로 클릭한 후 Import를 클릭한다.
- General을 펼친 후 Existing Projects into Workspace를 선택한다.
Next를 클릭한다(그림 1).
그림 1. 기존 프로젝트를 작업 공간으로 가져오기
- Select root directory 옆의 Browse를 클릭한 후 C:\Planet Power\Employees2로 이동한다.
- Employees2 폴더를 선택하고 OK를 클릭한 후 Finish를
클릭한다(그림 2).
그림 2. Eclipse로 프로젝트 가져오기 완료하기
Employees2 폴더가 Package Explorer 분할창에 표시된다.
참고: 이 프로젝트의 경우 src\default_package 아래에 있는 Employees2 프로젝트의 ExcelXML.java 파일을 사용한다.
이 시리즈의 Part 1에서 첫 번째 단계는 Apache POI를 예외 및 파일 처리 클래스와 함께 가져오는 것이다(Part 1에 대한 링크는 참고자료 참조). 또한 Listing 1에서와 같이 숫자에 대해 작업하는 데 필요한 클래스와 함께 일부 XML API 클래스를 추가해야 한다.
Listing 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() 내에서 프로그래밍을
시작할 준비가 된다.
Listing 2에 있는 코드는 파일의 예외 처리 구조(IOException) 및
숫자 변환(ParseException) 오류를 나타낸다.
Listing 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을 XML 문서의 부분을 나타내는 오브젝트로 구문 분석하여 XML에 대한
작업을 단순화한다. 전체 XML 문서를 나타내는 클래스는 nu.xom.Document이다.
Document에서 다른 부분을 추가하거나 다른 부분에
액세스할 수 있다. XML에 대한 작업에 필요한 일부 클래스는 다음과 같다.
nu.xom.Buildernu.xom.Documentnu.xom.Elementsnu.xom.Elementnu.xom.Serializer
XML의 주요 부분을 요소라고 한다. 요소는 태그의 쌍과 그 사이에 있는 컨텐츠로 구성된다. weather_service.xml이라는 샘플 파일의 요소 예제 중 하나는 다음과 같다.
<dawn>07:00</dawn> |
dawn이라는 단어와 괄호(<>) 및
슬래시(/)를 태그라고 한다. 07:00은
컨텐츠이다.
요소는 다른 요소, 텍스트 컨텐츠 또는 둘 다를 포함할 수 있다. 포함하는 요소는 상위라고 하고 내부의 요소는 하위 요소 또는 하위라고 한다.
XOM 요소는 nu.xom.Element 오브젝트로 표시된다.
요소 세트는 nu.xom.Elements 오브젝트이다.
요소를 찾으려면 잘 구성된 모든 XML 문서가 가지고 있는 한 가지 요소인 루트 요소를 사용한다. 루트는 모든 기타 요소의 컨테이너 역할을 한다. 문서에 루트가 없으면 적절한 XML이 아니다.
루트를 찾으려면 getRootElement()를 Document
오브젝트에서 사용한다. 하나의 요소만 가져와도 문서 탐색을 위한 전략이 열린다.
루트의 하위에 대해 작업하길 원하는가? Element.getChildElements()의
변형을 사용하여 특정 이름을 가진 하위 요소 또는 모든 하위 요소의 목록을 가져온다.
단일 요소를 원하는가? Element.getFirstChildElement()에서
특정 이름을 가진 첫 번째 하위 요소만 가져온다.
이것이 익숙하지 않은가? 하위의 하위를 찾기 위해 XML 문서에서 반복하는 것은 Excel 워크시트에서 반복하는 것과 비슷하다.
이전 기사에서는 Apache POI의 getStringCellValue()
메소드가 Excel HSSFCell에서 문자열 값을 검색하는 방법을 확인했다.
이와 비슷하게 XOM에서는 Element.getValue()를 사용하여
XML 요소에서 문자열 컨텐츠를 가져온다. 하지만 셀에 대한 작업과는 다르게
XOM 요소에 대한 작업에는 데이터 유형 테스트가 필요하지 않다. XML은 모두 텍스트이다.
스프레드시트에서 이동하고 데이터를 추출할 때 데이터를 수에 관계없이 오브젝트(String, Array, Squirrel)에 저장할 수 있다. 이 기사에서는 XML 요소를 사용한다. (이 경우에는 Squirrel에서 수행하는 것이 더 쉽다.) 두 형식 간 변환에 대한 일부 정보도 제공한다.
Listing 3에서는 통합 문서의 정보를 저장할 새 HSSFWorkbook 및
XML Document를 준비한다.
Listing 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);
|
Listing 3에서 이전의 작업을 다시 수행하는 것처럼
보이는가? 코드는 Document를 작성하기 전에
새 Element를 작성한다. 이유가 뭘까?
새 Document에는 Element가
루트 요소가 되어야 한다는 한 가지 요구사항이 있다. Document는
루트 요소 없이 잘 구성된 XML 문서를 나타낼 수 없다는 점을 기억한다. 따라서 Document를
작성할 때 Document에 루트 요소를 전달해야 한다.
이와 대조적으로 루트에 속하지 않는 요소를 작성할 수 있다. 이러한 요소를 자유롭게 이동하는 요소로 남겨 두거나 문서 루트 또는 기타 요소에 추가할 수 있다. 하지만 시작하기 전에 XML의 구조에 대한 계획을 작성해야 한다.
XML은 주로 요소 및 속성을 통해 데이터에 대해 설명하고 데이터를 형식화한다. 속성은 이름-값 쌍이다. 속성의 이름 부분은 속성이 보유하는 데이터에 대해 설명한다. 속성의 값은 속성의 데이터이다. HTML 코드의 작성자는 다음과 같은 속성에 익숙하다.
<font size="12">Hello, Planet!</font> |
전체 리스팅은 요소이다. 태그는 <font>이다.
속성은 size="12"이다. 속성의 이름은 size이다.
속성의 값은 12이다. 등호 부호(=)는
속성의 이름과 값을 결합한다. 일반적으로 데이터는 요소에 저장되지만 메타데이터는
속성에 저장된다.
그러면 Employee_List.xls 통합 문서가 XML로 변환되는가?
XML을 구조화하는 방법 중 하나는 Excel 통합 문서의 실제 구조를 모방하는 것이다. Listing 4를 살펴본다.
Listing 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 열을 속성이 아니라 요소 이름으로 사용하는 것이 좋다.
XML이 데이터의 프리젠테이션을 기반으로 하도록 하는 대신 데이터에 대해 설명하는 구조를 사용해 본다. Listing 5에 표시된 대로 이 방법이 XML에 가장 적합하다.
Listing 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>
|
이 접근 방식을 사용하면 getChildElements("Salary")
메소드를 각 Employee 요소에서 사용하여 직원의 급여를
신속하게 찾을 수 있다.
하지만 요소 이름으로 Excel 열 이름을 사용하면 위험하다. Excel 열은 XML 요소 이름으로는 올바르지 않은 문자(예: 공백)를 사용할 수 있다. 따라서 잠재적인 요소 이름에서 이러한 문자를 삭제해야 한다.
이 방식으로 데이터를 구조화하려면 데이터에 익숙해야 한다. 프로그램적으로
Excel 스프레드시트의 행을 XML에서 Employee로
부르도록 하는 것은 쉽지 않다. 루트 요소의 이름(위 예제에서는
EmployeeData)을 예측하는 것도 어렵다.
그렇다면 구조가 변경되거나 빅 보스가 다른 스프레드시트의 코드를 재활용하려고 한다면 어떻게 될까? 스프레드시트 행에는 직원 대신 햄스터의 유형이 나열될 수 있다. 이런 경우에는 프로그램이 행에 지정하는 이름을 조정해야 한다.
Listing 6과 같이 Excel 구조 XML을 데이터 구조 마크업과 혼합해 본다.
Listing 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 통합 문서에서 이러한 혼합을 수행할 수 있다. 문서와 행에 있는
데이터는 스프레드시트마다 다를 수 있기 때문에 일반적인 sheet 및
row를 요소 이름으로 사용할 수 있으며 이들은
코드를 읽는 프로그래머에게 여전히 의미를 가진다.
참고: 순수한 데이터 중심 마크업과 마찬가지로 XML 요소 이름을 불명확하게 만드는 잘못된 문자에 유의한다.
이 기사에서는 XML-Excel 혼합 형식을 사용하여 셀 값을 저장한다. 그렇다면 데이터 유형 및 형식과 같은 기타 셀 정보를 추적하는 것은 어떨까?
데이터 유형과 형식은 메타데이터이다. 보존해야 하는 메타데이터가
무엇인지에 따라 dataFormat 및 dataType과
같은 속성을 사용할 수 있다.
XML의 모양을 결정한 후 XML 요소에 Excel 데이터 저장을 시작한다. 이 시리즈의 Part 1(링크는 참고자료 참조)에 있는 것과 동일한 루프를 사용하여 Excel 스프레드시트에서 이동한다. 그런 다음 XML을 추가한다. Listing 7에서는 이전 기사의 Excel 읽기 코드를 재활용한다.
Listing 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;
}
|
스프레드시트의 행에서 반복할 때 Listing 8과 같이 행을 나타내는 XML 요소를 작성한다.
Listing 8. 행 요소 작성하기(ExcelXML.java)
// Create an XML element to represent the row.
Element rowElement = new Element("row");
|
비어 있는 행이나 널(null) 행이 있는 경우를 대비하여 아직 Document에
행을 첨부하지 않는다. 셀을 추가한 후 행이 비어 있지 않으면 행 루프의
맨 아래에 있는 루트 요소에 행을 첨부할 수 있다.
다음으로 Listing 9와 같이 내부 루프를 시작하여 셀을 읽는다.
Listing 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 요소를 작성한다. Listing 10에서는 요소 이름이
비어 있으면 안 되기 때문에 각각의 이름은 기본값인 header로 지정된다.
첫 번째 행 이후 Excel 스프레드시트 열 이름이 포함된 첫 번째 행 요소에 저장된
데이터를 기반으로 새 이름을 예측한다.
Listing 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);
|
Listing 10에서는 많은 일이 벌어진다. 중간의 주석
// Set the elementName variable equal to the column name
아래에 있는 긴 행에서 elementName은 특정 항목과
동일하게 설정된다. 행의 끝 부분을 읽으면 elementName이 getValue()를
사용하는 요소 내부의 텍스트 값으로 설정되었음을 알 수 있다. 사용되는 요소 값은 무엇인가?
reportRoot 내부에 있는 첫 번째 행
요소(reportRoot.getFirstChildElement("row"))에서
코드는 getChild(cellColumnNumber)를 사용하여
색인 번호로 하위 요소를 찾는다. 스프레드시트의 첫 번째 행을 첫 번째 행
요소에 이미 저장했기 때문에 행의 하위 요소 값은 스프레드시트의 열 이름이다.
색인 번호는 스프레드시트에 있는 현재 셀의 열 번호와 동일하다. 따라서
elementName에 대해 설정된 값은 머리글 요소의
첫 번째 행에서 제공되는 해당 열 이름이다.
다음으로 코드는 스프레드시트에 있을 수 있는 잘못된 문자의 elementName
문자열을 삭제한다. 먼저 String의 replaceAll()
메소드는 모든 비ASCII 문자를 비어 있는 문자열로 바꾼다. 그런 다음 모든 공백을
대체한다. 두 행 모두 정규식을 사용한다. 정규식에 대한 정보는 참고자료를
참조한다.
마지막으로 Listing 10의 마지막 행에서는 적절한 열 이름을 사용하여 요소를 작성한다.
요소와 마찬가지로 속성을 독립적으로 작성할 수 있으며 addAttribute()
메소드를 사용하여 요소에 추가되기 전까지 이러한 속성을 자유롭게 이동할 수
있는 상태를 유지할 수 있다. Listing 11에서와 같이
속성을 작성한 후 Apache POI HSSFCell 오브젝트의
getDataFormatString()와 같은 getter 메소드를
사용하여 셀 메타데이터로 속성을 채운다.
Listing 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 메소드를 파악하는 것을 잊지 않는다.
어떻게든 데이터 유형에 대해 테스트를 하기 때문에 셀의 데이터 유형 정보를
저장하는 속성도 작성할 수 있다.
Listing 12에서와 같이 문자열 값에 대한 작업 시 요소에 데이터를 추가하고 요소를 행의 하위로 추가하는 것은 간단하다.
Listing 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 섹션을 반복한다. 하지만
숫자 데이터는 까다로울 수 있다. 이 기사 시리즈의 Part 1에서는 추출된 Excel
데이터가 스프레드시트 데이터와 동일하지 않음을 지적했다(참고자료에서
Part 1의 링크 참조). 이 데이터는 원시 데이터이다. 날짜와 같은 특정 숫자는 원시
데이터로는 매우 이상하기 때문에 형식화 없이 저장된 경우에는 올바른 값이 되지
않는다. 사용자는 요소에 추가하기 전에 숫자 데이터를 적절하게 형식화하려고 할 것이다.
Apache POI 클래스 HSSFDataFormatter와 해당
메소드 formatCellValue()를 사용하여 이를 달성할 수
있다. Listing 13을 참조한다.
Listing 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 요소가 비어 있는지 테스트한다. 비어 있지 않은 경우에는
해당 요소를 루트 요소에 추가한다. 그런 다음 Listing 14와 같이
중간 루프를 닫는다.
Listing 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을 사용하여 급여의 1%를 파악하는 것과 같은 계산을 수행하는 경우 문자열과 숫자 사이에서 변환해야 한다. Listing 15에 예제가 제공된다.
Listing 15. 급여의 1%를 계산하여 기부 요소에 저장하기(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 파일에
쉽게 작성할 수 있다. Listing 16과 같이 nu.xom.Serailizer.write()를
사용한다.
Listing 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은 햄스터의 모국어이다.
XML을 Excel 스프레드시트에 쓰려면 XML에서 반복한 후 셀 값 및 형식화를 설정한다. Listing 17에서는 행에서 루프를 설정하고 시작한다.
Listing 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의 색인 번호가 필요하다.
이 번호는 HSSFDataFormat 오브젝트가 아니라 짧은 숫자이다.
다행히 HSSFDataFormat 오브젝트에는 getFormat()라는
메소드가 있다. 원하는 형식을 나타내는 문자열이 전달된 경우에는 해당 문자열과
일치하는 HSSFDataFormat의 색인 번호가 리턴된다.
색인은 짧은 숫자로 리턴된다. 일치하는 항목이 없으면 새 HSSFDataFormat이
작성되고 색인이 리턴된다. Listing 18과 같이 해당 색인을
사용하여 형식을 셀 스타일에 적용하고 해당 셀 스타일을 셀에 적용할 수 있다.
Listing 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()를 사용하여
대부분의 데이터 유형을 셀에 추가한다.
하지만 숫자 데이터는 특별하게 처리해야 한다. 숫자를 텍스트가 아니라 숫자로 저장하려면 먼저 숫자를 double로 변환한다. 날짜는 double로 변환하지 않는다. 그렇지 않으면 날짜가 올바르지 않게 된다. 숫자 데이터의 데이터 형식을 테스트하여 날짜인지 판별한다(Listing 19 참조).
Listing 19. 셀 데이터를 삽입하여 특정 숫자 데이터를 위해 double로 변환하기
// 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
}
|
TODAY() 및 NOW()와
같은 날짜를 작성하는 Excel 함수의 경우에도 형식화가 필요하다. Listing 20을
참조한다.
Listing 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()
메소드를 사용하여 파일에 쓴다(Listing 21).
Listing 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); |
이제 GEE WHIS에 대한 기부를 계산하는 Excel 스프레드시트를 작성했다.
보고서가 완료되었다. Excel 읽기 및 XML 작성 이외에 Java 프로그래머는 이제 XML을 다시 Excel 파일에 쓸 수 있다. 두 형식 사이의 변환에 대한 기본사항을 이해하고 나면 보고하는 것에 부담을 느끼지 않을 것이다.
빅 보스도 만족하며 사용자는 Planet Power의 환경 영웅이 유전자 변형된 많은 야생 햄스터를 구하는 데 일조했다. 모두가 만족한다. 보고는 실제로 환경에 도움이 될 수 있다.
| 설명 | 이름 | 크기 | 다운로드 방식 |
|---|---|---|---|
| Sample Excel spreadsheet and Java code | Java-Excel-XML-Planet-Power2.zip | 17KB | HTTP |
교육
- 읽기, 재활용 및 재사용: Excel, XML 및 Java 기술을 사용하여 간편하게 보고하기, Part 1: Java 및 XML 기술을 사용하여 Excel 파일을 읽고 새 파일에 쓰기(Shaene M. Siders, developerWorks, 2010년 2월): 모든 회사는 비즈니스 데이터 추출이라는 과제에 직면해 있다. 이 시리즈의 Part 1에 있는 Java 기술을 사용하여 Excel에서 데이터를 추출한 후 Excel과 XML 사이에서 변환하는 방법을 살펴본다.
- Apache POI의 JavaDoc 문서: Apache POI 문서를 찾아보자.
- XML & Java: 데이터 바인딩, Part 2: 성능(Dennis Sosnoski, developerWorks, 2003년 1월): 테스트 드라이브를 위해 XML 데이터 바인딩 프레임워크를 가져올 수 있다.
- 정규식: 이 기사에서 사용된 ASCII POSIX 대괄호 표현식과 Java의 정규식을 사용하여 정규식에 대해 자세히 살펴보자.
- Busy Developers' Guide to HSSF and XSSF Features: Apache 사이트에 있는 이 안내서를 통해 POI 스킬을 배워보자.
- Get started with XPath(Bertrand Portier, developerWorks, 2004년 5월): XPath를 살펴보고 구문 및 시맨틱, XPath 위치 경로, XPath 표현식, XPath 함수 및 이 XPath 소개 튜토리얼에서 XPath와 XSLT의 관계에 대해 알아보자.
- XPath: W3Schools에서 XPath에 대해 살펴보자.
- developerWorks의 XML 영역: XML 영역에서 기술 향상에 도움이 되는 참고자료를 얻을 수 있다.
- IBM XML 인증: XML 및 관련 기술에 대한 IBM 인증 개발자가 되는 방법을 찾아볼 수 있다.
- XML Technical library: developerWorks XML 영역에서 다양한 기술 관련 기사와 팁, 튜토리얼, 표준 및 IBM Redbook을 볼 수 있다.
- developerWorks 기술 행사 및 웹 캐스트: 이들 세션에 참가하여 최신 기술에 대한 정보를 얻을 수 있다.
- Twitter의 developerWorks: 지금 참여하여 developerWorks의 트윗(Tweet)을 팔로우(follow)하자.
- developerWorks
팟캐스트: 소프트웨어 개발자의 흥미로운 인터뷰와 토론을 확인할 수 있다.
제품 및 기술 얻기
- Eclipse Classic: Eclipse를 다운로드하자. 이 기사에서는 버전 3.5.1을 사용한다.
- Apache POI: 안정적인 최신 릴리스인 Apache POI의 버전 3.6에 대해 자세히 알아보고 이를 다운로드해 보자.
- XOM의 전체 zip: Elliotte Rusty Harold의 XML API에 대해 자세히 알아보고 이를 다운로드해 보자.
- 정돈된 코드 스니펫 살펴보기: 공백이지만 널(null)은 아닌 Excel 행을 찾아서 제거해 보자.
- IBM 제품 평가판: IBM SOA Sandbox의 온라인 시험판을 다운로드하거나 살펴보고 DB2®,
Lotus®, Rational®, Tivoli® 및
WebSphere®의 애플리케이션 개발 도구 및 미들웨어 제품을 사용해 볼 수 있다.
토론
- XML 영역 토론 포럼: 여러 XML 관련 토론에 참여해 볼 수 있다.
- developerWorks
포럼 & 블로그: 이러한 블로그를 읽어보고 참여할 수 있다.
Shaene Siders는 기술, 교육 및 엔터테인먼트에 대한 컨설턴트이자 저술가이다. 1996에 저술과 웹 개발 회사를 시작했으며 2003년부터 ClearCase 및 ClearQuest에 대한 IBM Rational Certified Instructor로 근무하고 있다. 교육용 소프트웨어 시리즈인 Java for Mad Scientists의 작성자이자 제작자이며 Build Forge, Rational Functional Tester, Rational Performance Tester 및 Rational Manual Tester에 대한 IBM Rational Certified Instructor이기도 하다. Shaene의 이메일 주소는 consult@DragonUnderGlass.com이다.