]> source.dussan.org Git - poi.git/commitdiff
support for custom XML mappings in XSSF, see Bugzilla 47520
authorYegor Kozlov <yegor@apache.org>
Thu, 16 Jul 2009 05:46:14 +0000 (05:46 +0000)
committerYegor Kozlov <yegor@apache.org>
Thu, 16 Jul 2009 05:46:14 +0000 (05:46 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@794539 13f79535-47bb-0310-9956-ffa450edef68

16 files changed:
src/documentation/content/xdocs/status.xml
src/examples/src/org/apache/poi/xssf/usermodel/examples/CustomXMLMapping.java [new file with mode: 0755]
src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java [new file with mode: 0755]
src/ooxml/java/org/apache/poi/xssf/model/MapInfo.java [new file with mode: 0755]
src/ooxml/java/org/apache/poi/xssf/model/SingleXmlCells.java [new file with mode: 0755]
src/ooxml/java/org/apache/poi/xssf/model/Table.java [new file with mode: 0755]
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFMap.java [new file with mode: 0755]
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java
src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFSingleXmlCell.java [new file with mode: 0755]
src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFXmlColumnPr.java [new file with mode: 0755]
src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFExportToXML.java [new file with mode: 0755]
src/ooxml/testcases/org/apache/poi/xssf/model/TestMapInfo.java [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/data/CustomXMLMappings-complex-type.xlsx [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/data/CustomXMLMappings.xlsx [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/data/CustomXmlMappings-inverse-order.xlsx [new file with mode: 0755]

index 79993d965f13ebde04f4b37848126307676eeb48..415f5390ecf39b63329019942f332ffaeeb61d34 100644 (file)
@@ -33,6 +33,7 @@
 
     <changes>
         <release version="3.5-beta7" date="2009-??-??">
+           <action dev="POI-DEVELOPERS" type="add">47520 - Initial support for custom XML mappings in XSSF</action>
            <action dev="POI-DEVELOPERS" type="fix">47460 - Fixed NPE when retrieving core properties from a newly created workbook</action>
            <action dev="POI-DEVELOPERS" type="fix">47498 - Fixed HyperlinkRecord to properly handle URL monikers</action>
            <action dev="POI-DEVELOPERS" type="fix">47504 - Fixed XSSFWorkbook to read files with hyperlinks to document locations</action>
diff --git a/src/examples/src/org/apache/poi/xssf/usermodel/examples/CustomXMLMapping.java b/src/examples/src/org/apache/poi/xssf/usermodel/examples/CustomXMLMapping.java
new file mode 100755 (executable)
index 0000000..3e69df2
--- /dev/null
@@ -0,0 +1,42 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.xssf.usermodel.examples;\r
+\r
+import org.apache.poi.xssf.extractor.XSSFExportToXml;\r
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;\r
+import org.apache.poi.xssf.usermodel.XSSFMap;\r
+\r
+import java.io.ByteArrayOutputStream;\r
+\r
+/**\r
+ * Print all custom XML mappings registered in the given workbook\r
+ */\r
+public class CustomXMLMapping {\r
+\r
+    public static void main(String[] args) throws Exception {\r
+        XSSFWorkbook wb = new XSSFWorkbook(args[0]);\r
+\r
+        for (XSSFMap map : wb.getCustomXMLMappings()) {\r
+            XSSFExportToXml exporter = new XSSFExportToXml(map);\r
+\r
+            ByteArrayOutputStream os = new ByteArrayOutputStream();\r
+            exporter.exportToXML(os, true);\r
+            String xml = os.toString("UTF-8");\r
+            System.out.println(xml);\r
+        }\r
+    }\r
+}\r
diff --git a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java
new file mode 100755 (executable)
index 0000000..01d9683
--- /dev/null
@@ -0,0 +1,544 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.extractor;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.xssf.model.Table;
+import org.apache.poi.xssf.usermodel.XSSFCell;
+import org.apache.poi.xssf.usermodel.XSSFMap;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+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;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * 
+ * Maps an XLSX to an XML according to one of the mapping defined.
+ * 
+ * 
+ * The output XML Schema must respect this limitations:
+ * 
+ * <ul>
+ * <li> all mandatory elements and attributes must be mapped </li>
+ * 
+ * <li> no &lt;any&gt; in complex type/element declaration </li>
+ * <li> no &lt;anyAttribute&gt; attributes declaration </li>
+ * <li> no recursive structures: recursive structures can't be nested more than one level </li>
+ * <li> no abstract elements: abstract complex types can be declared but must not be used in elements. </li>
+ * <li> no mixed content: an element can't contain simple text and child element(s) together </li>
+ * <li> no &lt;substitutionGroup&gt; in complex type/element declaration </li>
+ * </ul>
+ * 
+ * @author Roberto Manicardi
+ * 
+ * 
+ *
+ */
+
+
+public class XSSFExportToXml implements Comparator<String>{
+       
+       private XSSFMap map;
+       
+       /**
+        * Creates a new exporter and sets the mapping to be used when generating the XML output document
+        * 
+        * @param map the mapping rule to be used
+        */
+       public XSSFExportToXml(XSSFMap map){
+               this.map = map;
+       }
+       
+       /**
+        * 
+        * Exports the data in an XML stream 
+        * 
+        * @param os OutputStream in which will contain the output XML
+        * @param validate if true, validates the XML againts the XML Schema
+        * @throws SAXException
+        */
+       public void exportToXML(OutputStream os, boolean validate) throws SAXException{
+               exportToXML(os,"UTF-8", validate);
+       }
+       
+       private Document getEmptyDocument() throws ParserConfigurationException{
+               
+       
+               DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
+        DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
+        Document doc = docBuilder.newDocument();
+
+               
+        
+        return doc;
+       }
+       
+       /**
+        * Exports the data in an XML stream
+        * 
+        * @param os OutputStream in which will contain the output XML
+        * @param encoding the output charset encoding 
+        * @param validate if true, validates the XML againts the XML Schema
+        * @throws SAXException
+        * @throws InvalidFormatException
+        */
+       
+       public void exportToXML(OutputStream os, String encoding, boolean validate) throws SAXException{
+               List<XSSFSingleXmlCell> singleXMLCells = map.getRelatedSingleXMLCell();
+               List<Table> tables = map.getRelatedTables();
+               
+               String rootElement = map.getCtMap().getRootElement();
+               
+               try{
+                       
+                       Document doc = getEmptyDocument();
+                       
+               Element root = null;
+       
+               if(isNamespaceDeclared()){
+                        root=doc.createElementNS(getNamespace(),rootElement);          
+               }else{ 
+                       root=doc.createElement(rootElement);
+               }
+               doc.appendChild(root);
+               
+               
+               List<String> xpaths = new Vector<String>();
+               Map<String,XSSFSingleXmlCell> singleXmlCellsMappings = new HashMap<String,XSSFSingleXmlCell>();
+               Map<String,Table> tableMappings = new HashMap<String,Table>();
+               
+               for(XSSFSingleXmlCell simpleXmlCell : singleXMLCells){
+                       xpaths.add(simpleXmlCell.getXpath());
+                       singleXmlCellsMappings.put(simpleXmlCell.getXpath(), simpleXmlCell);
+               }
+               for(Table table : tables){
+                       String commonXPath = table.getCommonXpath();
+                       xpaths.add(commonXPath);
+                       tableMappings.put(commonXPath, table);
+               }
+               
+               
+               Collections.sort(xpaths,this);
+               
+               for(String xpath : xpaths){
+                       
+                               XSSFSingleXmlCell simpleXmlCell = singleXmlCellsMappings.get(xpath);
+                               Table table = tableMappings.get(xpath);         
+                               
+                               if(!xpath.matches(".*\\[.*")){
+                                       
+                                       // Exports elements and attributes mapped with simpleXmlCell
+                                       if(simpleXmlCell!=null){
+                                               XSSFCell cell = simpleXmlCell.getReferencedCell();
+                                               if(cell!=null){
+                                                       Node currentNode = getNodeByXPath(xpath,doc.getFirstChild(),doc,false);
+                                                       STXmlDataType.Enum dataType = simpleXmlCell.getXmlDataType();                   
+                                                       mapCellOnNode(cell,currentNode,dataType);       
+                                               }
+                                       }
+                                       
+                                       // Exports elements and attributes mapped with tables
+                                       if(table!=null){
+                                               
+                                               List<XSSFXmlColumnPr> tableColumns = table.getXmlColumnPrs();
+                                               
+                                               XSSFSheet sheet = table.getXSSFSheet();
+                                               
+                                               int startRow = table.getStartCellReference().getRow();
+                                               // In mappings created with Microsoft Excel the first row contains the table header and must be skipped
+                                               startRow +=1;
+                                               
+                                               int endRow = table.getEndCellReference().getRow();
+                                               
+                                               for(int i = startRow; i<= endRow; i++){
+                                                       XSSFRow row = sheet.getRow(i);
+                                                       
+                                                       Node tableRootNode = getNodeByXPath(table.getCommonXpath(),doc.getFirstChild(),doc,true);
+                                                       
+                                                       short startColumnIndex = table.getStartCellReference().getCol();
+                                                       for(int j = startColumnIndex; j<= table.getEndCellReference().getCol();j++){
+                                                               XSSFCell cell = row.getCell(j);
+                                                               if(cell!=null){
+                                                                       XSSFXmlColumnPr pointer = tableColumns.get(j-startColumnIndex);
+                                                                       String localXPath = pointer.getLocalXPath();
+                                                                       Node currentNode = getNodeByXPath(localXPath,tableRootNode,doc,false);
+                                                                       STXmlDataType.Enum dataType = pointer.getXmlDataType();
+                                                                       
+                                                                       
+                                                                       mapCellOnNode(cell,currentNode,dataType);
+                                                               }
+                                                       
+                                                       }
+                                                       
+                                               }
+                                               
+                                               
+                                               
+                                       }
+                               }else{
+                                       // TODO:  implement filtering management in xpath
+                               }
+               }
+               
+               boolean isValid = true;
+               if(validate){
+                       isValid =isValid(doc);
+               }
+               
+          
+       
+               if(isValid){
+                               
+                               /////////////////
+                       //Output the XML
+               
+                       //set up a transformer
+                       TransformerFactory transfac = TransformerFactory.newInstance();
+                       Transformer trans = transfac.newTransformer();
+                       trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+                       trans.setOutputProperty(OutputKeys.INDENT, "yes");
+                       trans.setOutputProperty(OutputKeys.ENCODING, encoding);
+                       
+                       //create string from xml tree
+                      
+                       StreamResult result = new StreamResult(os);
+                       DOMSource source = new DOMSource(doc);
+                       trans.transform(source, result);
+                       
+               }
+        }catch(ParserConfigurationException e){
+                       e.printStackTrace();
+               }catch(TransformerException e){
+                       e.printStackTrace();
+               }
+
+               
+       }
+       
+       /**
+        * Validate the generated XML against the XML Schema associated with the XSSFMap
+        * 
+        * @param xml the XML to validate
+        * @return
+        */
+
+       private boolean isValid(Document xml) throws SAXException{
+               boolean isValid = false;
+               try{
+               String language = XMLConstants.W3C_XML_SCHEMA_NS_URI;
+             SchemaFactory factory = SchemaFactory.newInstance(language);
+
+             Source source = new DOMSource(map.getSchema());
+             Schema schema = factory.newSchema(source);
+             Validator validator = schema.newValidator();
+             validator.validate(new DOMSource(xml));
+             //if no exceptions where raised, the document is valid
+             isValid=true;
+             
+             
+               }catch(IOException e){
+                       e.printStackTrace();
+               }
+           return isValid;
+       }
+       
+       
+       private void mapCellOnNode(XSSFCell cell, Node node, STXmlDataType.Enum  outputDataType){
+               
+               
+               String value ="";
+               switch (cell.getCellType()){
+               
+                       case XSSFCell.CELL_TYPE_STRING: value = cell.getStringCellValue(); break;
+                       case XSSFCell.CELL_TYPE_BOOLEAN: value += cell.getBooleanCellValue(); break;
+                       case XSSFCell.CELL_TYPE_ERROR: value = cell.getErrorCellString();
+                       case XSSFCell.CELL_TYPE_FORMULA: value = cell.getStringCellValue(); break;
+                       case XSSFCell.CELL_TYPE_NUMERIC: value += cell.getRawValue(); break;
+                       default: ;
+               
+               }
+               if(node instanceof Element){
+                       Element currentElement = (Element) node;
+                       currentElement.setTextContent(value);
+               }else{
+                       node.setNodeValue(value);
+               }               
+       
+               
+       }
+       
+       private String removeNamespace(String elementName){
+               return elementName.matches(".*:.*")?elementName.split(":")[1]:elementName;
+       }
+       
+       
+       
+       private Node getNodeByXPath(String xpath,Node rootNode,Document doc,boolean createMultipleInstances){
+               String[] xpathTokens = xpath.split("/");
+               
+               
+               Node currentNode =rootNode;
+               // The first token is empty, the second is the root node
+               for(int i =2; i<xpathTokens.length;i++){
+                       
+                       String axisName = removeNamespace(xpathTokens[i]);
+
+                       
+                       if(!axisName.startsWith("@")){
+                       
+                       NodeList list =currentNode.getChildNodes();
+                       
+                       Node selectedNode = null;
+                       if(!(createMultipleInstances && i==xpathTokens.length-1) ){
+                               // select the last child node only if we need to map to a single cell
+                               selectedNode = selectNode(axisName, list);
+                               }
+                       if(selectedNode==null){
+                               selectedNode = createElement(doc, currentNode, axisName);
+                       }
+                       currentNode = selectedNode;
+                       }else{
+                               
+                               
+                               Node attribute = createAttribute(doc, currentNode, axisName);
+                                       
+                               currentNode = attribute;                        
+                               
+                       }
+                       
+               }
+               return currentNode;
+       }
+
+       private Node createAttribute(Document doc, Node currentNode, String axisName) {
+               String attributeName = axisName.substring(1);
+               NamedNodeMap attributesMap = currentNode.getAttributes();
+               Node attribute = attributesMap.getNamedItem(attributeName);
+               if(attribute==null){
+                       attribute = doc.createAttribute(attributeName);
+                       attributesMap.setNamedItem(attribute);
+               }
+               return attribute;
+       }
+
+       private Node createElement(Document doc, Node currentNode, String axisName) {
+               Node selectedNode;
+               if(isNamespaceDeclared()){
+                       selectedNode =doc.createElementNS(getNamespace(),axisName);
+               }else{
+                       selectedNode =doc.createElement(axisName);
+               }
+               currentNode.appendChild(selectedNode);
+               return selectedNode;
+       }
+
+       private Node selectNode(String axisName, NodeList list) {
+               Node selectedNode = null;
+               for(int j=0;j<list.getLength();j++){
+                       Node node = list.item(j);
+                       if(node.getNodeName().equals(axisName)){
+                               selectedNode=node;
+                               break;
+                       }
+               }
+               return selectedNode;
+       }
+       
+       
+       private boolean isNamespaceDeclared(){
+               String schemaNamespace = getNamespace();
+               return schemaNamespace!=null && !schemaNamespace.equals("");
+       }
+       
+       private String getNamespace(){
+               return map.getCTSchema().getNamespace();
+       }
+
+       
+       /**
+        * Compares two xpaths to define an ordering according to the XML Schema
+        * 
+        */
+       public int compare(String leftXpath, String rightXpath) {
+               
+               int result = 0; 
+               Node xmlSchema = map.getSchema();
+               
+               
+               
+               
+               String[] leftTokens = leftXpath.split("/");
+               String[] rightTokens = rightXpath.split("/");
+               
+               int minLenght = leftTokens.length< rightTokens.length? leftTokens.length : rightTokens.length;
+               
+               Node localComplexTypeRootNode = xmlSchema;
+               
+               
+               for(int i =1;i <minLenght; i++){
+                       
+                       String leftElementName =leftTokens[i];
+                       String rightElementName = rightTokens[i];
+                       
+                       if(leftElementName.equals(rightElementName)){
+                               
+                               
+                               Node complexType = getComplexTypeForElement(leftElementName, xmlSchema,localComplexTypeRootNode);
+                               localComplexTypeRootNode = complexType;                 
+                       }else{
+                               int leftIndex = indexOfElementInComplexType(leftElementName,localComplexTypeRootNode);
+                               int rightIndex = indexOfElementInComplexType(rightElementName,localComplexTypeRootNode);
+                               if(leftIndex!=-1 && rightIndex!=-1){
+                                       if( leftIndex < rightIndex){
+                                               result = -1;
+                                       }if( leftIndex > rightIndex){
+                                               result = 1;     
+                                       }
+                               }else{
+                                       // NOTE: the xpath doesn't match correctly in the schema
+                               }
+                                       
+                               
+                       }
+                       
+               }
+               
+               
+               
+       
+               return result;
+       }
+       
+       private int indexOfElementInComplexType(String elementName,Node complexType){
+               
+               NodeList list  = complexType.getChildNodes();
+               int indexOf = -1;
+               
+               for(int i=0; i< list.getLength();i++){
+                       Node node = list.item(i);
+                       if(node instanceof Element){
+                               if(node.getLocalName().equals("element")){
+                                       Node nameAttribute  = node.getAttributes().getNamedItem("name");
+                                       if(nameAttribute.getNodeValue().equals(removeNamespace(elementName))){
+                                               indexOf = i;
+                                               break;
+                                       }
+                                       
+                               }
+                       }
+               }
+               
+               return indexOf;
+               
+       }
+       
+       private Node getComplexTypeForElement(String elementName,Node xmlSchema,Node localComplexTypeRootNode){
+               Node complexTypeNode = null;
+               
+               String elementNameWithoutNamespace = removeNamespace(elementName);
+
+               
+               NodeList  list  = localComplexTypeRootNode.getChildNodes();
+               String complexTypeName = "";
+               
+
+               
+               for(int i=0; i< list.getLength();i++){
+                       Node node = list.item(i);
+                       if( node instanceof Element){
+                               if(node.getLocalName().equals("element")){
+                                       Node nameAttribute  = node.getAttributes().getNamedItem("name");
+                                       if(nameAttribute.getNodeValue().equals(elementNameWithoutNamespace)){
+                                               Node complexTypeAttribute = node.getAttributes().getNamedItem("type");
+                                               if(complexTypeAttribute!=null){
+                                                       complexTypeName = complexTypeAttribute.getNodeValue();
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+               }
+               // Note: we expect that all the complex types are defined at root level
+               if(!complexTypeName.equals("")){
+                       NodeList  complexTypeList  = xmlSchema.getChildNodes();
+                       for(int i=0; i< complexTypeList.getLength();i++){
+                               Node node = list.item(i);
+                               if( node instanceof Element){
+                                       if(node.getLocalName().equals("complexType")){
+                                               Node nameAttribute  = node.getAttributes().getNamedItem("name");
+                                               if(nameAttribute.getNodeValue().equals(complexTypeName)){
+                                                       
+                                                       NodeList complexTypeChildList  =node.getChildNodes();
+                                                       for(int j=0; j<complexTypeChildList.getLength();j++){
+                                                               Node sequence = complexTypeChildList.item(j);
+                                                               
+                                                               if( sequence instanceof Element){
+                                                                       if(sequence.getLocalName().equals("sequence")){
+                                                                               complexTypeNode = sequence;
+                                                                               break;
+                                                                       }
+                                                               }
+                                                       }
+                                                       if(complexTypeNode!=null){
+                                                               break;
+                                                       }
+                                                       
+                                               }
+                                       }
+                               }
+                       }
+               }
+               
+               return complexTypeNode;
+               
+               
+       }
+       
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xssf/model/MapInfo.java b/src/ooxml/java/org/apache/poi/xssf/model/MapInfo.java
new file mode 100755 (executable)
index 0000000..d301f18
--- /dev/null
@@ -0,0 +1,148 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.model;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Iterator;
+
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.xssf.usermodel.XSSFMap;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.xmlbeans.XmlException;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTMap;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTMapInfo;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSchema;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.MapInfoDocument;
+
+/**
+ * 
+ * This class implements the Custom XML Mapping Part (Open Office XML Part 1:
+ * chapter 12.3.6)
+ * 
+ * An instance of this part type contains a schema for an XML file, and
+ * information on the behavior that is used when allowing this custom XML schema
+ * to be mapped into the spreadsheet.
+ * 
+ * @author Roberto Manicardi
+ */
+
+public class MapInfo extends POIXMLDocumentPart {
+
+       private CTMapInfo mapInfo;
+       
+       private Map<Integer, XSSFMap> maps ;
+
+       public MapInfo() {
+               super();
+               mapInfo = CTMapInfo.Factory.newInstance();
+
+       }
+
+       public MapInfo(PackagePart part, PackageRelationship rel)
+                       throws IOException {
+               super(part, rel);
+               readFrom(part.getInputStream());
+       }
+
+       public void readFrom(InputStream is) throws IOException {
+               try {
+                       MapInfoDocument doc = MapInfoDocument.Factory.parse(is);
+                       mapInfo = doc.getMapInfo();
+
+            maps= new HashMap<Integer, XSSFMap>();
+            for(CTMap map :mapInfo.getMapArray()){
+                maps.put((int)map.getID(), new XSSFMap(map,this));
+            }
+
+               } catch (XmlException e) {
+                       throw new IOException(e.getLocalizedMessage());
+               }
+       }
+       
+       /**
+     * Returns the parent XSSFWorkbook
+     *
+     * @return the parent XSSFWorkbook
+     */
+    public XSSFWorkbook getWorkbook() {
+        return (XSSFWorkbook)getParent();
+    }
+       
+       /**
+        * 
+        * @return the internal data object
+        */
+       public CTMapInfo getCTMapInfo(){
+               return mapInfo;
+               
+       }
+
+       /**
+        * Gets the CTSchema buy it's ID
+        * @param schemaId the schema ID
+        * @return 
+        */
+       public CTSchema getCTSchemaById(String schemaId){
+               CTSchema xmlSchema = null;
+
+               CTSchema[] schemas = mapInfo.getSchemaArray();
+               for(CTSchema schema: schemas){
+                       if(schema.getID().equals(schemaId)){
+                               xmlSchema = schema;
+                               break;
+                       }
+               }
+               return xmlSchema;
+       }
+       
+       
+       public XSSFMap getXSSFMapById(int id){
+               return maps.get(id);
+       }
+       
+       /**
+        * 
+        * @return all the mappings configured in this document
+        */
+       public Collection<XSSFMap> getAllXSSFMaps(){
+               return maps.values();
+       }
+
+       protected void writeTo(OutputStream out) throws IOException {
+               MapInfoDocument doc = MapInfoDocument.Factory.newInstance();
+               doc.setMapInfo(mapInfo);
+               doc.save(out, DEFAULT_XML_OPTIONS);
+       }
+
+       @Override
+       protected void commit() throws IOException {
+               PackagePart part = getPackagePart();
+               OutputStream out = part.getOutputStream();
+               writeTo(out);
+               out.close();
+       }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xssf/model/SingleXmlCells.java b/src/ooxml/java/org/apache/poi/xssf/model/SingleXmlCells.java
new file mode 100755 (executable)
index 0000000..9b118bf
--- /dev/null
@@ -0,0 +1,106 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.model;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.helpers.XSSFSingleXmlCell;
+import org.apache.xmlbeans.XmlException;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSingleXmlCell;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSingleXmlCells;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.SingleXmlCellsDocument;
+
+
+/**
+ * 
+ * This class implements the Single Cell Tables Part (Open Office XML Part 4:
+ * chapter 3.5.2)
+ * 
+ *
+ * @author Roberto Manicardi
+ */
+public class SingleXmlCells extends POIXMLDocumentPart {
+       
+       
+       private CTSingleXmlCells singleXMLCells;
+
+       public SingleXmlCells() {
+               super();
+               singleXMLCells = CTSingleXmlCells.Factory.newInstance();
+
+       }
+
+       public SingleXmlCells(PackagePart part, PackageRelationship rel)
+                       throws IOException {
+               super(part, rel);
+               readFrom(part.getInputStream());
+       }
+
+       public void readFrom(InputStream is) throws IOException {
+               try {
+                       SingleXmlCellsDocument doc = SingleXmlCellsDocument.Factory.parse(is);
+                       singleXMLCells = doc.getSingleXmlCells();
+               } catch (XmlException e) {
+                       throw new IOException(e.getLocalizedMessage());
+               }
+       }
+       
+       public XSSFSheet getXSSFSheet(){
+               return (XSSFSheet) getParent();
+       }
+
+       protected void writeTo(OutputStream out) throws IOException {
+               SingleXmlCellsDocument doc = SingleXmlCellsDocument.Factory.newInstance();
+               doc.setSingleXmlCells(singleXMLCells);
+               doc.save(out, DEFAULT_XML_OPTIONS);
+       }
+
+       @Override
+       protected void commit() throws IOException {
+               PackagePart part = getPackagePart();
+               OutputStream out = part.getOutputStream();
+               writeTo(out);
+               out.close();
+       }
+       
+       public CTSingleXmlCells getCTSingleXMLCells(){
+               return singleXMLCells;
+       }
+       
+       /**
+        * 
+        * @return all the SimpleXmlCell contained in this SingleXmlCells element
+        */
+       public List<XSSFSingleXmlCell> getAllSimpleXmlCell(){
+               List<XSSFSingleXmlCell> list = new Vector<XSSFSingleXmlCell>();
+               CTSingleXmlCell[] singleXMLCellArray = singleXMLCells.getSingleXmlCellArray();
+               
+               for(CTSingleXmlCell singleXmlCell: singleXMLCellArray){                 
+                       list.add(new XSSFSingleXmlCell(singleXmlCell,this));
+               }               
+               return list;
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xssf/model/Table.java b/src/ooxml/java/org/apache/poi/xssf/model/Table.java
new file mode 100755 (executable)
index 0000000..9ab24ac
--- /dev/null
@@ -0,0 +1,248 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.model;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr;
+import org.apache.xmlbeans.XmlException;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.TableDocument;
+
+/**
+ * 
+ * This class implements the Table Part (Open Office XML Part 4:
+ * chapter 3.5.1)
+ * 
+ * This implementation works under the assumption that a table contains mappings to a subtree of an XML.
+ * The root element of this subtree an occur multiple times (one for each row of the table). The child nodes
+ * of the root element can be only attributes or element with maxOccurs=1 property set
+ * 
+ *
+ * @author Roberto Manicardi
+ */
+public class Table extends POIXMLDocumentPart {
+       
+       private CTTable ctTable;
+       private List<XSSFXmlColumnPr> xmlColumnPr;
+       private CellReference startCellReference;
+       private CellReference endCellReference; 
+       private String commonXPath; 
+       
+       
+       public Table() {
+               super();
+               ctTable = CTTable.Factory.newInstance();
+
+       }
+
+       public Table(PackagePart part, PackageRelationship rel)
+                       throws IOException {
+               super(part, rel);
+               readFrom(part.getInputStream());
+       }
+
+       public void readFrom(InputStream is) throws IOException {
+               try {
+                       TableDocument doc = TableDocument.Factory.parse(is);
+                       ctTable = doc.getTable();
+               } catch (XmlException e) {
+                       throw new IOException(e.getLocalizedMessage());
+               }
+       }
+       
+       public XSSFSheet getXSSFSheet(){
+               return (XSSFSheet) getParent();
+       }
+
+       public void writeTo(OutputStream out) throws IOException {
+               TableDocument doc = TableDocument.Factory.newInstance();
+               doc.setTable(ctTable);
+               doc.save(out, DEFAULT_XML_OPTIONS);
+       }
+
+       @Override
+       protected void commit() throws IOException {
+               PackagePart part = getPackagePart();
+               OutputStream out = part.getOutputStream();
+               writeTo(out);
+               out.close();
+       }
+       
+       public CTTable getCTTable(){
+               return ctTable;
+       }
+       
+       /**
+        * Checks if this Table element contains even a single mapping to the map identified by id
+        * @param id the XSSFMap ID
+        * @return true if the Table element contain mappings
+        */
+       public boolean mapsTo(long id){
+               boolean maps =false;
+               
+               List<XSSFXmlColumnPr> pointers = getXmlColumnPrs();
+               
+               for(XSSFXmlColumnPr pointer: pointers){
+                       if(pointer.getMapId()==id){
+                               maps=true;
+                               break;
+                       }
+               }
+               
+               return maps;
+       }
+
+       
+       /**
+        * 
+        * Calculates the xpath of the root element for the table. This will be the common part
+        * of all the mapping's xpaths
+        * 
+        * @return the xpath of the table's root element
+        */
+       public String getCommonXpath() {
+               
+               if(commonXPath == null){
+               
+               String[] commonTokens ={};
+               
+               for(CTTableColumn column :ctTable.getTableColumns().getTableColumnArray()){
+                       if(column.getXmlColumnPr()!=null){
+                               String xpath = column.getXmlColumnPr().getXpath();
+                               String[] tokens =  xpath.split("/");
+                               if(commonTokens.length==0){
+                                       commonTokens = tokens;
+                                       
+                               }else{
+                                       int maxLenght = commonTokens.length>tokens.length? tokens.length:commonTokens.length;
+                                       for(int i =0; i<maxLenght;i++){
+                                               if(!commonTokens[i].equals(tokens[i])){
+                                                List<String> subCommonTokens = Arrays.asList(commonTokens).subList(0, i);
+                                                
+                                                String[] container = {};
+                                                
+                                                commonTokens = subCommonTokens.toArray(container);
+                                                break;
+                                                
+                                                
+                                               }
+                                       }
+                               }
+                               
+                       }
+               }
+               
+               
+               commonXPath ="";
+               
+               for(int i = 1 ; i< commonTokens.length;i++){
+                       commonXPath +="/"+commonTokens[i];
+               
+               }
+               }
+               
+               return commonXPath;
+       }
+
+       
+       public List<XSSFXmlColumnPr> getXmlColumnPrs() {
+               
+               if(xmlColumnPr==null){
+                       xmlColumnPr = new Vector<XSSFXmlColumnPr>();
+                       for(CTTableColumn column:ctTable.getTableColumns().getTableColumnArray()){
+                               if(column.getXmlColumnPr()!=null){
+                                       XSSFXmlColumnPr columnPr = new XSSFXmlColumnPr(this,column,column.getXmlColumnPr());
+                                       xmlColumnPr.add(columnPr);
+                               }
+                       }
+               }
+               return xmlColumnPr;
+       }
+
+       /**
+        *  the number of mapped table columns (see Open Office XML Part 4: chapter 3.5.1.4)
+        * @return 
+        */
+       public long getNumerOfMappedColumns(){
+               return ctTable.getTableColumns().getCount();
+       }
+       
+       
+       /**
+        * The reference for the cell in the top-left part of the table 
+        * (see Open Office XML Part 4: chapter 3.5.1.2, attribute ref) 
+        * @return
+        */
+       public CellReference getStartCellReference() {
+               
+               if(startCellReference==null){                   
+                               String ref = ctTable.getRef();
+                               String[] boundaries = ref.split(":");
+                               String from = boundaries[0];
+                               startCellReference = new CellReference(from);
+               }
+               return startCellReference;
+       }
+       
+       /**
+        * The reference for the cell in the bottom-right part of the table
+        * (see Open Office XML Part 4: chapter 3.5.1.2, attribute ref)
+        * @return
+        */
+       public CellReference getEndCellReference() {
+               
+               if(endCellReference==null){
+                       
+                               String ref = ctTable.getRef();
+                               String[] boundaries = ref.split(":");
+                               String from = boundaries[1];
+                               endCellReference = new CellReference(from);
+               }
+               return endCellReference;
+       }
+       
+       
+       /**
+        * Gets the total number of rows in the selection. (Note: in this version autofiltering is ignored)
+        * @return 
+        */
+       public int getRowCount(){
+               
+               
+               CellReference from = getStartCellReference();
+               CellReference to = getEndCellReference();
+               
+               int rowCount = -1;
+               if (from!=null && to!=null){
+                rowCount = to.getRow()-from.getRow();
+               }
+               return rowCount;
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFMap.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFMap.java
new file mode 100755 (executable)
index 0000000..33c742f
--- /dev/null
@@ -0,0 +1,120 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel;
+
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.xssf.model.MapInfo;
+import org.apache.poi.xssf.model.SingleXmlCells;
+import org.apache.poi.xssf.model.Table;
+import org.apache.poi.xssf.usermodel.helpers.XSSFSingleXmlCell;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTMap;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSchema;
+import org.w3c.dom.Node;
+
+/**
+ * This class implements the Map element (Open Office XML Part 4:
+ * chapter 3.16.2)
+ * <p/>
+ * This element contains all of the properties related to the XML map,
+ * and the behaviors expected during data refresh operations.
+ *
+ * @author Roberto Manicardi
+ */
+
+
+public class XSSFMap {
+
+    private CTMap ctMap;
+
+    private MapInfo mapInfo;
+
+
+    public XSSFMap(CTMap ctMap, MapInfo mapInfo) {
+        this.ctMap = ctMap;
+        this.mapInfo = mapInfo;
+    }
+
+
+    public CTMap getCtMap() {
+        return ctMap;
+    }
+
+
+    public CTSchema getCTSchema() {
+        String schemaId = ctMap.getSchemaID();
+        return mapInfo.getCTSchemaById(schemaId);
+    }
+
+    public Node getSchema() {
+        Node xmlSchema = null;
+
+        CTSchema schema = getCTSchema();
+        xmlSchema = schema.getDomNode().getFirstChild();
+
+        return xmlSchema;
+    }
+
+    /**
+     * @return the list of Single Xml Cells that provide a map rule to this mapping.
+     */
+    public List<XSSFSingleXmlCell> getRelatedSingleXMLCell() {
+        List<XSSFSingleXmlCell> relatedSimpleXmlCells = new Vector<XSSFSingleXmlCell>();
+
+        int sheetNumber = mapInfo.getWorkbook().getNumberOfSheets();
+        for (int i = 0; i < sheetNumber; i++) {
+            XSSFSheet sheet = mapInfo.getWorkbook().getSheetAt(i);
+            for (POIXMLDocumentPart p : sheet.getRelations()) {
+                if (p instanceof SingleXmlCells) {
+                    SingleXmlCells singleXMLCells = (SingleXmlCells) p;
+                    for (XSSFSingleXmlCell cell : singleXMLCells.getAllSimpleXmlCell()) {
+                        if (cell.getMapId() == ctMap.getID()) {
+                            relatedSimpleXmlCells.add(cell);
+                        }
+                    }
+                }
+            }
+        }
+        return relatedSimpleXmlCells;
+    }
+
+    /**
+     * @return the list of all Tables that provide a map rule to this mapping
+     */
+    public List<Table> getRelatedTables() {
+
+        List<Table> tables = new Vector<Table>();
+        int sheetNumber = mapInfo.getWorkbook().getNumberOfSheets();
+
+        for (int i = 0; i < sheetNumber; i++) {
+            XSSFSheet sheet = mapInfo.getWorkbook().getSheetAt(i);
+            for (POIXMLDocumentPart p : sheet.getRelations()) {
+                if (p.getPackageRelationship().getRelationshipType().equals(XSSFRelation.TABLE.getRelation())) {
+                    Table table = (Table) p;
+                    if (table.mapsTo(ctMap.getID())) {
+                        tables.add(table);
+                    }
+                }
+            }
+        }
+
+        return tables;
+    }
+}
index 73fe263a263b5106e0e8f83758ea0e1d1fe73954..c3b97c6c75012156dc4af1075d08cd75c3db48fa 100644 (file)
@@ -25,10 +25,13 @@ import java.util.HashMap;
 import org.apache.poi.POIXMLDocument;
 import org.apache.poi.POIXMLRelation;
 import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.xssf.model.MapInfo;
+import org.apache.poi.xssf.model.SingleXmlCells;
 import org.apache.poi.xssf.model.StylesTable;
 import org.apache.poi.xssf.model.SharedStringsTable;
 import org.apache.poi.xssf.model.CommentsTable;
 import org.apache.poi.xssf.model.CalculationChain;
+import org.apache.poi.xssf.model.Table;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
@@ -93,6 +96,28 @@ public final class XSSFRelation extends POIXMLRelation {
                        "/xl/drawings/vmlDrawing#.vml",
                        null
        );
+       
+       public static final XSSFRelation CUSTOM_XML_MAPPINGS = new XSSFRelation(
+                       "application/xml",
+                       "http://schemas.openxmlformats.org/officeDocument/2006/relationships/xmlMaps",
+                       "/xl/xmlMaps.xml",
+                       MapInfo.class
+       );
+       
+       public static final XSSFRelation SINGLE_XML_CELLS = new XSSFRelation(
+                       "application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml",
+                       "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableSingleCells",
+                       "/tables/tableSingleCells#.xml",
+                       SingleXmlCells.class
+       );
+       
+       public static final XSSFRelation TABLE = new XSSFRelation(
+                       "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml",
+                       "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table",
+                       "/tables/table#.xml",
+                       Table.class
+       );
+       
     public static final XSSFRelation IMAGES = new XSSFRelation(
             null,
                "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
index 5d509260e3d749b7a50daba69f7f84215be9c2aa..5453b33b14519d851ece0454f963e6d6b5615b48 100644 (file)
@@ -22,12 +22,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.regex.Pattern;
 
 import javax.xml.namespace.QName;
@@ -56,6 +51,8 @@ import org.apache.poi.util.PackageHelper;
 import org.apache.poi.xssf.model.CalculationChain;
 import org.apache.poi.xssf.model.SharedStringsTable;
 import org.apache.poi.xssf.model.StylesTable;
+import org.apache.poi.xssf.model.MapInfo;
+import org.apache.poi.xssf.extractor.XSSFExportToXml;
 import org.apache.xmlbeans.XmlException;
 import org.apache.xmlbeans.XmlObject;
 import org.apache.xmlbeans.XmlOptions;
@@ -106,6 +103,11 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X
      */
     private CalculationChain calcChain;
 
+    /**
+     * A collection of custom XML mappings
+     */
+    private MapInfo mapInfo;
+
     /**
      * Used to keep track of the data formatter so that all
      * createDataFormatter calls return the same one for a given
@@ -174,6 +176,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X
                 if(p instanceof SharedStringsTable) sharedStringSource = (SharedStringsTable)p;
                 else if(p instanceof StylesTable) stylesSource = (StylesTable)p;
                 else if(p instanceof CalculationChain) calcChain = (CalculationChain)p;
+                else if(p instanceof MapInfo) mapInfo = (MapInfo)p;
                 else if (p instanceof XSSFSheet) {
                     shIdMap.put(p.getPackageRelationship().getId(), (XSSFSheet)p);
                 }
@@ -1282,4 +1285,11 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X
         return calcChain;
     }
 
+    /**
+     * 
+     * @return a collection of custom XML mappings defined in this workbook
+     */
+    public Collection<XSSFMap> getCustomXMLMappings(){
+        return mapInfo == null ? new ArrayList<XSSFMap>() : mapInfo.getAllXSSFMaps();
+    }
 }
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFSingleXmlCell.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFSingleXmlCell.java
new file mode 100755 (executable)
index 0000000..055f2b8
--- /dev/null
@@ -0,0 +1,84 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.helpers;
+
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.xssf.model.SingleXmlCells;
+import org.apache.poi.xssf.usermodel.XSSFCell;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSingleXmlCell;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXmlCellPr;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXmlPr;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.STXmlDataType.Enum;
+
+/**
+ * 
+ * This class is a wrapper around the CTSingleXmlCell  (Open Office XML Part 4:
+ * chapter 3.5.2.1) 
+ * 
+
+ * 
+ * @author Roberto Manicardi
+ *
+ */
+public class XSSFSingleXmlCell {
+       
+       private CTSingleXmlCell singleXmlCell;
+       private SingleXmlCells parent;
+       
+       
+       public XSSFSingleXmlCell(CTSingleXmlCell singleXmlCell, SingleXmlCells parent){
+               this.singleXmlCell = singleXmlCell;
+               this.parent = parent;
+       }
+       
+       /**
+        * Gets the XSSFCell referenced by the R attribute
+        * @return the referenced XSSFCell, null if the cell reference is invalid
+        */
+       public XSSFCell getReferencedCell(){
+               XSSFCell cell = null;
+               
+               
+               CellReference cellReference =  new CellReference(singleXmlCell.getR()); 
+               
+               XSSFRow row = parent.getXSSFSheet().getRow(cellReference.getRow());
+               cell = row.getCell(cellReference.getCol());  
+               
+               
+               return cell;
+       }
+       
+       public String getXpath(){
+               CTXmlCellPr xmlCellPr = singleXmlCell.getXmlCellPr();
+               CTXmlPr xmlPr = xmlCellPr.getXmlPr();
+               String xpath = xmlPr.getXpath();
+               return xpath;
+       }
+       
+       public long getMapId(){
+               return singleXmlCell.getXmlCellPr().getXmlPr().getMapId();
+       }
+
+       public Enum getXmlDataType() {
+               CTXmlCellPr xmlCellPr = singleXmlCell.getXmlCellPr();
+               CTXmlPr xmlPr = xmlCellPr.getXmlPr();
+               return xmlPr.getXmlDataType();
+       }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFXmlColumnPr.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFXmlColumnPr.java
new file mode 100755 (executable)
index 0000000..c03b56e
--- /dev/null
@@ -0,0 +1,65 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.helpers;
+
+import org.apache.poi.xssf.model.Table;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXmlColumnPr;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.STXmlDataType.Enum;
+
+
+/**
+ * 
+ * This class is a wrapper around the CTXmlColumnPr (Open Office XML Part 4:
+ * chapter 3.5.1.7)
+ * 
+ *
+ * @author Roberto Manicardi
+ */
+public class XSSFXmlColumnPr {
+       
+       private Table table;
+       private CTTableColumn ctTableColumn;
+       private CTXmlColumnPr ctXmlColumnPr;
+       
+       public XSSFXmlColumnPr(Table table ,CTTableColumn ctTableColum,CTXmlColumnPr ctXmlColumnPr){
+               this.table = table;
+               this.ctTableColumn = ctTableColum;
+               this.ctXmlColumnPr = ctXmlColumnPr;
+       }
+       
+       public long getMapId(){
+               return ctXmlColumnPr.getMapId();
+       }
+       
+       public String getLocalXPath(){
+               String localXPath = "";
+               int numberOfCommonXPathAxis = table.getCommonXpath().split("/").length-1;
+               
+               String[] xPathTokens = ctXmlColumnPr.getXpath().split("/");
+               for(int i=numberOfCommonXPathAxis; i<xPathTokens.length;i++){
+                       localXPath += "/" +xPathTokens[i];
+               }
+               return localXPath;
+       }
+
+       public Enum getXmlDataType() {
+               return ctXmlColumnPr.getXmlDataType();
+       }
+
+}
diff --git a/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFExportToXML.java b/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFExportToXML.java
new file mode 100755 (executable)
index 0000000..960f078
--- /dev/null
@@ -0,0 +1,220 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.extractor;
+
+import java.io.ByteArrayOutputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.xssf.XSSFTestDataSamples;
+import org.apache.poi.xssf.model.MapInfo;
+import org.apache.poi.xssf.usermodel.XSSFMap;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * 
+ * @author Roberto Manicardi
+ *
+ */
+public class TestXSSFExportToXML extends TestCase{
+       
+       
+       
+       
+       
+       public void testExportToXML() throws Exception{
+       
+               
+                XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("CustomXmlMappings.xlsx");
+                 
+                
+                
+                 MapInfo mapInfo = null;
+                 
+                 for(POIXMLDocumentPart p : wb.getRelations()){
+                         
+                         
+                         if(p instanceof MapInfo){
+                                 mapInfo = (MapInfo) p;
+                                 
+                                 
+                                 XSSFMap map = mapInfo.getXSSFMapById(1);
+                                 XSSFExportToXml exporter = new XSSFExportToXml(map);
+                                 ByteArrayOutputStream os = new ByteArrayOutputStream();
+                                 exporter.exportToXML(os,true);
+                                 String xml = os.toString("UTF-8");
+                                 
+                                 assertNotNull(xml);
+                                 assertTrue(!xml.equals(""));
+                                 
+                                 
+                                 String docente = xml.split("<DOCENTE>")[1].split("</DOCENTE>")[0].trim();
+                                 String nome = xml.split("<NOME>")[1].split("</NOME>")[0].trim();
+                                 String tutor = xml.split("<TUTOR>")[1].split("</TUTOR>")[0].trim();
+                                 String cdl = xml.split("<CDL>")[1].split("</CDL>")[0].trim();
+                                 String durata = xml.split("<DURATA>")[1].split("</DURATA>")[0].trim();
+                                 String argomento = xml.split("<ARGOMENTO>")[1].split("</ARGOMENTO>")[0].trim();
+                                 String progetto = xml.split("<PROGETTO>")[1].split("</PROGETTO>")[0].trim();
+                                 String crediti = xml.split("<CREDITI>")[1].split("</CREDITI>")[0].trim();
+                                 
+                                 assertEquals("ro",docente);
+                                 assertEquals("ro",nome);
+                                 assertEquals("ds",tutor);
+                                 assertEquals("gs",cdl);
+                                 assertEquals("g",durata);
+                                 assertEquals("gvvv",argomento);
+                                 assertEquals("aaaa",progetto);
+                                 assertEquals("aa",crediti);
+                                 
+                         }
+                        
+                 }
+               
+       }
+       
+       
+       public void testExportToXMLInverseOrder() throws Exception{
+               
+       
+               
+                XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("CustomXmlMappings-inverse-order.xlsx");
+        
+                 MapInfo mapInfo = null;
+                 
+                 for(POIXMLDocumentPart p : wb.getRelations()){
+                         
+                         
+                         if(p instanceof MapInfo){
+                                 mapInfo = (MapInfo) p;
+                                 
+                                 
+                                 XSSFMap map = mapInfo.getXSSFMapById(1);
+                                 XSSFExportToXml exporter = new XSSFExportToXml(map);
+                                 ByteArrayOutputStream os = new ByteArrayOutputStream();
+                                 exporter.exportToXML(os,true);
+                                 String xml = os.toString("UTF-8");
+                                 
+                                 assertNotNull(xml);
+                                 assertTrue(!xml.equals(""));
+                                 
+                                 String docente = xml.split("<DOCENTE>")[1].split("</DOCENTE>")[0].trim();
+                                 String nome = xml.split("<NOME>")[1].split("</NOME>")[0].trim();
+                                 String tutor = xml.split("<TUTOR>")[1].split("</TUTOR>")[0].trim();
+                                 String cdl = xml.split("<CDL>")[1].split("</CDL>")[0].trim();
+                                 String durata = xml.split("<DURATA>")[1].split("</DURATA>")[0].trim();
+                                 String argomento = xml.split("<ARGOMENTO>")[1].split("</ARGOMENTO>")[0].trim();
+                                 String progetto = xml.split("<PROGETTO>")[1].split("</PROGETTO>")[0].trim();
+                                 String crediti = xml.split("<CREDITI>")[1].split("</CREDITI>")[0].trim();
+                                 
+                                 assertEquals("aa",nome);
+                                 assertEquals("aaaa",docente);
+                                 assertEquals("gvvv",tutor);
+                                 assertEquals("g",cdl);
+                                 assertEquals("gs",durata);
+                                 assertEquals("ds",argomento);
+                                 assertEquals("ro",progetto);
+                                 assertEquals("ro",crediti);
+                                 
+                         }
+                        
+                 }
+                 
+               
+               
+               
+               
+       }
+       
+       public void testXPathOrdering() throws Exception{
+               
+               XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("CustomXmlMappings-inverse-order.xlsx");
+               
+                MapInfo mapInfo = null;
+                 
+                 for(POIXMLDocumentPart p : wb.getRelations()){
+                         
+                         
+                         if(p instanceof MapInfo){
+                                 mapInfo = (MapInfo) p;
+                                 
+                                 XSSFMap map = mapInfo.getXSSFMapById(1);
+                                 XSSFExportToXml exporter = new XSSFExportToXml(map);
+                                 
+                                 assertEquals(1,exporter.compare("/CORSO/DOCENTE", "/CORSO/NOME"));
+                                 
+                                 assertEquals(-1,exporter.compare("/CORSO/NOME", "/CORSO/DOCENTE"));
+                                 
+                                 
+                         }
+                 }
+       }
+       
+       
+
+       
+       public void testMultiTable() throws Exception{
+               
+               
+               XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("CustomXMLMappings-complex-type.xlsx");         
+                 
+                 for(POIXMLDocumentPart p : wb.getRelations()){
+                         
+                         
+                         if(p instanceof MapInfo){
+                                 MapInfo mapInfo = (MapInfo) p;
+                                 
+                                 XSSFMap map = mapInfo.getXSSFMapById(2);
+                                 
+                                 assertNotNull(map);
+                                 
+                                 XSSFExportToXml exporter = new XSSFExportToXml(map);
+                                 ByteArrayOutputStream os = new ByteArrayOutputStream();
+                                 exporter.exportToXML(os,true);
+                                 String xml = os.toString("UTF-8");
+                                 
+                                 assertNotNull(xml);
+                                 
+                                 String[] regexConditions = {  "<MapInfo",
+                                                                                               "</MapInfo>",
+                                                                                               "<Schema ID=\"1\" Namespace=\"\" SchemaRef=\"\"/>",
+                                                                                               "<Schema ID=\"4\" Namespace=\"\" SchemaRef=\"\"/>",
+                                                                                               "DataBinding",
+                                                                                               "Map Append=\"false\" AutoFit=\"false\" ID=\"1\"",
+                                                                                               "Map Append=\"false\" AutoFit=\"false\" ID=\"5\""
+                                                                                               
+                                                                                    };
+                                 
+                                 for(String condition : regexConditions){
+                                         Pattern pattern = Pattern.compile(condition);
+                                         Matcher matcher = pattern.matcher(xml);
+                                         assertTrue(matcher.find());
+                                 }
+                                 
+                                 
+                         }
+                 }
+               
+               
+       }
+       
+       
+
+}
diff --git a/src/ooxml/testcases/org/apache/poi/xssf/model/TestMapInfo.java b/src/ooxml/testcases/org/apache/poi/xssf/model/TestMapInfo.java
new file mode 100755 (executable)
index 0000000..35c80f0
--- /dev/null
@@ -0,0 +1,84 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.model;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.xssf.XSSFTestDataSamples;
+import org.apache.poi.xssf.usermodel.XSSFMap;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSchema;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTMapInfo;
+import org.w3c.dom.Node;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Roberto Manicardi
+ */
+public class TestMapInfo extends TestCase {
+
+
+    public void testMapInfoExists() throws Exception {
+
+
+        XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("CustomXmlMappings.xlsx");
+
+        MapInfo mapInfo = null;
+        SingleXmlCells singleXMLCells = null;
+
+        for (POIXMLDocumentPart p : wb.getRelations()) {
+
+
+            if (p instanceof MapInfo) {
+                mapInfo = (MapInfo) p;
+
+
+                CTMapInfo ctMapInfo = mapInfo.getCTMapInfo();
+
+                assertNotNull(ctMapInfo);
+
+                CTSchema[] schemas = ctMapInfo.getSchemaArray();
+                assertEquals(1, schemas.length);
+
+
+                for (XSSFMap map : mapInfo) {
+                    Node xmlSchema = map.getSchema();
+                    assertNotNull(xmlSchema);
+                }
+            }
+        }
+
+        XSSFSheet sheet1 = wb.getSheetAt(0);
+
+        for (POIXMLDocumentPart p : sheet1.getRelations()) {
+
+            if (p instanceof SingleXmlCells) {
+                singleXMLCells = (SingleXmlCells) p;
+            }
+
+        }
+
+
+        assertNotNull(mapInfo);
+        assertNotNull(singleXMLCells);
+
+
+    }
+
+
+}
diff --git a/src/testcases/org/apache/poi/hssf/data/CustomXMLMappings-complex-type.xlsx b/src/testcases/org/apache/poi/hssf/data/CustomXMLMappings-complex-type.xlsx
new file mode 100755 (executable)
index 0000000..0d6d8d0
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/CustomXMLMappings-complex-type.xlsx differ
diff --git a/src/testcases/org/apache/poi/hssf/data/CustomXMLMappings.xlsx b/src/testcases/org/apache/poi/hssf/data/CustomXMLMappings.xlsx
new file mode 100755 (executable)
index 0000000..32a5573
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/CustomXMLMappings.xlsx differ
diff --git a/src/testcases/org/apache/poi/hssf/data/CustomXmlMappings-inverse-order.xlsx b/src/testcases/org/apache/poi/hssf/data/CustomXmlMappings-inverse-order.xlsx
new file mode 100755 (executable)
index 0000000..f3acef3
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/CustomXmlMappings-inverse-order.xlsx differ