diff options
author | Yegor Kozlov <yegor@apache.org> | 2009-07-16 05:46:14 +0000 |
---|---|---|
committer | Yegor Kozlov <yegor@apache.org> | 2009-07-16 05:46:14 +0000 |
commit | 35906116edf297fbe512765cc4728acfca3b20eb (patch) | |
tree | b57959b63798fc8ae04b63ea330539537d19ed8a /src/ooxml/java | |
parent | 27832ad35675cab8492cb702b952e2636560ea31 (diff) | |
download | poi-35906116edf297fbe512765cc4728acfca3b20eb.tar.gz poi-35906116edf297fbe512765cc4728acfca3b20eb.zip |
support for custom XML mappings in XSSF, see Bugzilla 47520
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@794539 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/ooxml/java')
9 files changed, 1356 insertions, 6 deletions
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 index 0000000000..01d96832b2 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java @@ -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 <any> in complex type/element declaration </li> + * <li> no <anyAttribute> 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 <substitutionGroup> 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 index 0000000000..d301f18a16 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/model/MapInfo.java @@ -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 index 0000000000..9b118bfdf8 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/model/SingleXmlCells.java @@ -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 index 0000000000..9ab24ac57a --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/model/Table.java @@ -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 index 0000000000..33c742f6e7 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFMap.java @@ -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; + } +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java index 73fe263a26..c3b97c6c75 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java @@ -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", diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index 5d509260e3..5453b33b14 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -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; @@ -107,6 +104,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 * book. This ensures that updates from one places is visible @@ -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 index 0000000000..055f2b8145 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFSingleXmlCell.java @@ -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 index 0000000000..c03b56e250 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFXmlColumnPr.java @@ -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(); + } + +} |