From 2f0ceddc7f019efd3b5418a94a8feda29af0fc1f Mon Sep 17 00:00:00 2001 From: Dominik Stadler Date: Thu, 17 Sep 2015 18:30:13 +0000 Subject: [PATCH] Apply patch from bug 57890 to add support for different datatypes in XSSFImportFromXML git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1703665 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/poi/TestAllFiles.java | 1 + .../poi/xssf/extractor/XSSFImportFromXML.java | 80 +++++++++++++++++- .../xssf/extractor/TestXSSFImportFromXML.java | 49 +++++++++-- test-data/spreadsheet/57890.xlsx | Bin 0 -> 10641 bytes 4 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 test-data/spreadsheet/57890.xlsx diff --git a/src/integrationtest/org/apache/poi/TestAllFiles.java b/src/integrationtest/org/apache/poi/TestAllFiles.java index b7427d4d59..d453da27f9 100644 --- a/src/integrationtest/org/apache/poi/TestAllFiles.java +++ b/src/integrationtest/org/apache/poi/TestAllFiles.java @@ -203,6 +203,7 @@ public class TestAllFiles { // TODO: fails XMLExportTest, is this ok? EXPECTED_FAILURES.add("spreadsheet/CustomXMLMapping-singleattributenamespace.xlsx"); EXPECTED_FAILURES.add("spreadsheet/55864.xlsx"); + EXPECTED_FAILURES.add("spreadsheet/57890.xlsx"); // TODO: these fail now with some NPE/file read error because we now try to compute every value via Cell.toString()! EXPECTED_FAILURES.add("spreadsheet/44958.xls"); diff --git a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFImportFromXML.java b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFImportFromXML.java index 1cde38b30a..bc6ce74472 100644 --- a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFImportFromXML.java +++ b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFImportFromXML.java @@ -19,8 +19,15 @@ package org.apache.poi.xssf.extractor; import java.io.IOException; import java.io.StringReader; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilder; @@ -30,7 +37,10 @@ import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.util.CellReference; import org.apache.poi.util.DocumentHelper; +import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; import org.apache.poi.xssf.usermodel.XSSFCell; @@ -39,6 +49,7 @@ import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFTable; import org.apache.poi.xssf.usermodel.helpers.XSSFSingleXmlCell; import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.STXmlDataType; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; @@ -95,6 +106,7 @@ public class XSSFImportFromXML { for (XSSFSingleXmlCell singleXmlCell : singleXmlCells) { + STXmlDataType.Enum xmlDataType = singleXmlCell.getXmlDataType(); String xpathString = singleXmlCell.getXpath(); Node result = (Node) xpath.evaluate(xpathString, doc, XPathConstants.NODE); // result can be null if value is optional (xsd:minOccurs=0), see bugzilla 55864 @@ -104,7 +116,7 @@ public class XSSFImportFromXML { XSSFCell cell = singleXmlCell.getReferencedCell(); logger.log(POILogger.DEBUG, "Setting '" + textContent + "' to cell " + cell.getColumnIndex() + "-" + cell.getRowIndex() + " in sheet " + cell.getSheet().getSheetName()); - cell.setCellValue(textContent); + setCellValue(textContent, cell, xmlDataType); } } @@ -146,12 +158,74 @@ public class XSSFImportFromXML { } logger.log(POILogger.DEBUG, "Setting '" + value + "' to cell " + cell.getColumnIndex() + "-" + cell.getRowIndex() + " in sheet " + table.getXSSFSheet().getSheetName()); - cell.setCellValue(value.trim()); + setCellValue(value, cell, xmlColumnPr.getXmlDataType()); } } } } + private static enum DataType { + BOOLEAN(STXmlDataType.BOOLEAN), // + DOUBLE(STXmlDataType.DOUBLE), // + INTEGER(STXmlDataType.INT, STXmlDataType.UNSIGNED_INT, STXmlDataType.INTEGER), // + STRING(STXmlDataType.STRING), // + DATE(STXmlDataType.DATE); + + private Set xmlDataTypes; + + private DataType(STXmlDataType.Enum... xmlDataTypes) { + this.xmlDataTypes = new HashSet(Arrays.asList(xmlDataTypes)); + } + + public static DataType getDataType(STXmlDataType.Enum xmlDataType) { + for (DataType dataType : DataType.values()) { + if (dataType.xmlDataTypes.contains(xmlDataType)) { + return dataType; + } + } + return null; + } + } + + private void setCellValue(String value, XSSFCell cell, STXmlDataType.Enum xmlDataType) { + DataType type = DataType.getDataType(xmlDataType); + try { + if (value.isEmpty() || type == null) { + cell.setCellValue((String) null); + } else { + switch (type) { + case BOOLEAN: + cell.setCellValue(Boolean.parseBoolean(value)); + break; + case DOUBLE: + cell.setCellValue(Double.parseDouble(value)); + break; + case INTEGER: + cell.setCellValue(Integer.parseInt(value)); + break; + case DATE: + DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", LocaleUtil.getUserLocale()); + Date date = sdf.parse(value); + cell.setCellValue(date); + if (!DateUtil.isValidExcelDate(cell.getNumericCellValue())) { + cell.setCellValue(value); + } + break; + case STRING: + default: + cell.setCellValue(value.trim()); + break; + } + } + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(String.format(LocaleUtil.getUserLocale(), "Unable to format value '%s' as %s for cell %s", value, + type, new CellReference(cell).formatAsString())); + } catch (ParseException e) { + throw new IllegalArgumentException(String.format(LocaleUtil.getUserLocale(), "Unable to format value '%s' as %s for cell %s", value, + type, new CellReference(cell).formatAsString())); + } + } + private static final class DefaultNamespaceContext implements NamespaceContext { /** * Node from which to start searching for a xmlns attribute that binds a @@ -219,7 +293,7 @@ public class XSSFImportFromXML { // Dummy implementation - not used! @Override - public Iterator getPrefixes(String val) { + public Iterator getPrefixes(String val) { return null; } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFImportFromXML.java b/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFImportFromXML.java index 16f1ab4714..12a0bbf566 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFImportFromXML.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFImportFromXML.java @@ -17,6 +17,8 @@ package org.apache.poi.xssf.extractor; +import java.text.SimpleDateFormat; +import java.util.Date; import junit.framework.TestCase; @@ -129,10 +131,10 @@ public class TestXSSFImportFromXML extends TestCase { public void testSingleAttributeCellWithNamespace() throws Exception{ XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("CustomXMLMapping-singleattributenamespace.xlsx"); try { - String id = "a"; + int id = 1; String displayName = "dispName"; String ref="19"; - String count = "21"; + int count = 21; String testXML = ""+ ""+ @@ -146,16 +148,15 @@ public class TestXSSFImportFromXML extends TestCase { //Check for Schema element XSSFSheet sheet=wb.getSheetAt(0); - assertEquals(id,sheet.getRow(28).getCell(1).getStringCellValue()); - assertEquals(displayName,sheet.getRow(11).getCell(5).getStringCellValue()); - assertEquals(ref,sheet.getRow(14).getCell(7).getStringCellValue()); - assertEquals(count,sheet.getRow(18).getCell(3).getStringCellValue()); + assertEquals(new Double(id), sheet.getRow(28).getCell(1).getNumericCellValue()); + assertEquals(displayName, sheet.getRow(11).getCell(5).getStringCellValue()); + assertEquals(ref, sheet.getRow(14).getCell(7).getStringCellValue()); + assertEquals(new Double(count), sheet.getRow(18).getCell(3).getNumericCellValue()); } finally { wb.close(); } } - - + public void testOptionalFields_Bugzilla_55864() throws Exception { XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("55864.xlsx"); try { @@ -195,4 +196,36 @@ public class TestXSSFImportFromXML extends TestCase { wb.close(); } } + + public void testOptionalFields_Bugzilla_57890() throws Exception { + XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("57890.xlsx"); + + String testXML = "" + "" + + "" + "" + Integer.MIN_VALUE + "" + "12345" + + "1.0000123" + "1991-03-14" + "" + ""; + + XSSFMap map = wb.getMapInfo().getXSSFMapByName("TestInfoRoot_Map"); + assertNotNull(map); + XSSFImportFromXML importer = new XSSFImportFromXML(map); + + importer.importFromXML(testXML); + + XSSFSheet sheet = wb.getSheetAt(0); + + XSSFRow rowHeadings = sheet.getRow(0); + XSSFRow rowData = sheet.getRow(1); + + assertEquals("Date", rowHeadings.getCell(0).getStringCellValue()); + Date date = new SimpleDateFormat("yyyy-MM-dd").parse("1991-3-14"); + assertEquals(date, rowData.getCell(0).getDateCellValue()); + + assertEquals("Amount Int", rowHeadings.getCell(1).getStringCellValue()); + assertEquals(new Double(Integer.MIN_VALUE), rowData.getCell(1).getNumericCellValue()); + + assertEquals("Amount Double", rowHeadings.getCell(2).getStringCellValue()); + assertEquals(1.0000123, rowData.getCell(2).getNumericCellValue()); + + assertEquals("Amount UnsignedInt", rowHeadings.getCell(3).getStringCellValue()); + assertEquals(new Double(12345), rowData.getCell(3).getNumericCellValue()); + } } diff --git a/test-data/spreadsheet/57890.xlsx b/test-data/spreadsheet/57890.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..91c751baa89906e83a6426bf25a09e41080bac39 GIT binary patch literal 10641 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