You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

XSSFExportToXml.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.xssf.extractor;
  16. import java.io.IOException;
  17. import java.io.OutputStream;
  18. import java.util.Collections;
  19. import java.util.Comparator;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Vector;
  24. import javax.xml.parsers.DocumentBuilder;
  25. import javax.xml.parsers.DocumentBuilderFactory;
  26. import javax.xml.parsers.ParserConfigurationException;
  27. import javax.xml.transform.OutputKeys;
  28. import javax.xml.transform.Source;
  29. import javax.xml.transform.Transformer;
  30. import javax.xml.transform.TransformerException;
  31. import javax.xml.transform.TransformerFactory;
  32. import javax.xml.transform.dom.DOMSource;
  33. import javax.xml.transform.stream.StreamResult;
  34. import javax.xml.validation.Schema;
  35. import javax.xml.validation.SchemaFactory;
  36. import javax.xml.validation.Validator;
  37. import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
  38. import org.apache.poi.xssf.usermodel.XSSFCell;
  39. import org.apache.poi.xssf.usermodel.XSSFMap;
  40. import org.apache.poi.xssf.usermodel.XSSFRow;
  41. import org.apache.poi.xssf.usermodel.XSSFSheet;
  42. import org.apache.poi.xssf.usermodel.XSSFTable;
  43. import org.apache.poi.xssf.usermodel.helpers.XSSFSingleXmlCell;
  44. import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr;
  45. import org.openxmlformats.schemas.spreadsheetml.x2006.main.STXmlDataType;
  46. import org.w3c.dom.Document;
  47. import org.w3c.dom.Element;
  48. import org.w3c.dom.NamedNodeMap;
  49. import org.w3c.dom.Node;
  50. import org.w3c.dom.NodeList;
  51. import org.xml.sax.SAXException;
  52. /**
  53. *
  54. * Maps an XLSX to an XML according to one of the mapping defined.
  55. *
  56. *
  57. * The output XML Schema must respect this limitations:
  58. *
  59. * <ul>
  60. * <li> all mandatory elements and attributes must be mapped (enable validation to check this)</li>
  61. *
  62. * <li> no &lt;any&gt; in complex type/element declaration </li>
  63. * <li> no &lt;anyAttribute&gt; attributes declaration </li>
  64. * <li> no recursive structures: recursive structures can't be nested more than one level </li>
  65. * <li> no abstract elements: abstract complex types can be declared but must not be used in elements. </li>
  66. * <li> no mixed content: an element can't contain simple text and child element(s) together </li>
  67. * <li> no &lt;substitutionGroup&gt; in complex type/element declaration </li>
  68. * </ul>
  69. */
  70. public class XSSFExportToXml implements Comparator<String>{
  71. private XSSFMap map;
  72. /**
  73. * Creates a new exporter and sets the mapping to be used when generating the XML output document
  74. *
  75. * @param map the mapping rule to be used
  76. */
  77. public XSSFExportToXml(XSSFMap map) {
  78. this.map = map;
  79. }
  80. /**
  81. *
  82. * Exports the data in an XML stream
  83. *
  84. * @param os OutputStream in which will contain the output XML
  85. * @param validate if true, validates the XML againts the XML Schema
  86. * @throws SAXException
  87. * @throws TransformerException
  88. * @throws ParserConfigurationException
  89. */
  90. public void exportToXML(OutputStream os, boolean validate) throws SAXException, ParserConfigurationException, TransformerException {
  91. exportToXML(os, "UTF-8", validate);
  92. }
  93. private Document getEmptyDocument() throws ParserConfigurationException{
  94. DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
  95. DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
  96. Document doc = docBuilder.newDocument();
  97. return doc;
  98. }
  99. /**
  100. * Exports the data in an XML stream
  101. *
  102. * @param os OutputStream in which will contain the output XML
  103. * @param encoding the output charset encoding
  104. * @param validate if true, validates the XML againts the XML Schema
  105. * @throws SAXException
  106. * @throws ParserConfigurationException
  107. * @throws TransformerException
  108. * @throws InvalidFormatException
  109. */
  110. public void exportToXML(OutputStream os, String encoding, boolean validate) throws SAXException, ParserConfigurationException, TransformerException{
  111. List<XSSFSingleXmlCell> singleXMLCells = map.getRelatedSingleXMLCell();
  112. List<XSSFTable> tables = map.getRelatedTables();
  113. String rootElement = map.getCtMap().getRootElement();
  114. Document doc = getEmptyDocument();
  115. Element root = null;
  116. if (isNamespaceDeclared()) {
  117. root=doc.createElementNS(getNamespace(),rootElement);
  118. } else {
  119. root = doc.createElementNS("", rootElement);
  120. }
  121. doc.appendChild(root);
  122. List<String> xpaths = new Vector<String>();
  123. Map<String,XSSFSingleXmlCell> singleXmlCellsMappings = new HashMap<String,XSSFSingleXmlCell>();
  124. Map<String,XSSFTable> tableMappings = new HashMap<String,XSSFTable>();
  125. for(XSSFSingleXmlCell simpleXmlCell : singleXMLCells) {
  126. xpaths.add(simpleXmlCell.getXpath());
  127. singleXmlCellsMappings.put(simpleXmlCell.getXpath(), simpleXmlCell);
  128. }
  129. for(XSSFTable table : tables) {
  130. String commonXPath = table.getCommonXpath();
  131. xpaths.add(commonXPath);
  132. tableMappings.put(commonXPath, table);
  133. }
  134. Collections.sort(xpaths,this);
  135. for(String xpath : xpaths) {
  136. XSSFSingleXmlCell simpleXmlCell = singleXmlCellsMappings.get(xpath);
  137. XSSFTable table = tableMappings.get(xpath);
  138. if (!xpath.matches(".*\\[.*")) {
  139. // Exports elements and attributes mapped with simpleXmlCell
  140. if (simpleXmlCell!=null) {
  141. XSSFCell cell = simpleXmlCell.getReferencedCell();
  142. if (cell!=null) {
  143. Node currentNode = getNodeByXPath(xpath,doc.getFirstChild(),doc,false);
  144. STXmlDataType.Enum dataType = simpleXmlCell.getXmlDataType();
  145. mapCellOnNode(cell,currentNode,dataType);
  146. }
  147. }
  148. // Exports elements and attributes mapped with tables
  149. if (table!=null) {
  150. List<XSSFXmlColumnPr> tableColumns = table.getXmlColumnPrs();
  151. XSSFSheet sheet = table.getXSSFSheet();
  152. int startRow = table.getStartCellReference().getRow();
  153. // In mappings created with Microsoft Excel the first row contains the table header and must be skipped
  154. startRow +=1;
  155. int endRow = table.getEndCellReference().getRow();
  156. for(int i = startRow; i<= endRow; i++) {
  157. XSSFRow row = sheet.getRow(i);
  158. Node tableRootNode = getNodeByXPath(table.getCommonXpath(),doc.getFirstChild(),doc,true);
  159. short startColumnIndex = table.getStartCellReference().getCol();
  160. for(int j = startColumnIndex; j<= table.getEndCellReference().getCol();j++) {
  161. XSSFCell cell = row.getCell(j);
  162. if (cell!=null) {
  163. XSSFXmlColumnPr pointer = tableColumns.get(j-startColumnIndex);
  164. String localXPath = pointer.getLocalXPath();
  165. Node currentNode = getNodeByXPath(localXPath,tableRootNode,doc,false);
  166. STXmlDataType.Enum dataType = pointer.getXmlDataType();
  167. mapCellOnNode(cell,currentNode,dataType);
  168. }
  169. }
  170. }
  171. }
  172. } else {
  173. // TODO: implement filtering management in xpath
  174. }
  175. }
  176. boolean isValid = true;
  177. if (validate) {
  178. isValid =isValid(doc);
  179. }
  180. if (isValid) {
  181. /////////////////
  182. //Output the XML
  183. //set up a transformer
  184. TransformerFactory transfac = TransformerFactory.newInstance();
  185. Transformer trans = transfac.newTransformer();
  186. trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
  187. trans.setOutputProperty(OutputKeys.INDENT, "yes");
  188. trans.setOutputProperty(OutputKeys.ENCODING, encoding);
  189. //create string from xml tree
  190. StreamResult result = new StreamResult(os);
  191. DOMSource source = new DOMSource(doc);
  192. trans.transform(source, result);
  193. }
  194. }
  195. /**
  196. * Validate the generated XML against the XML Schema associated with the XSSFMap
  197. *
  198. * @param xml the XML to validate
  199. * @return
  200. */
  201. private boolean isValid(Document xml) throws SAXException{
  202. boolean isValid = false;
  203. try{
  204. String language = "http://www.w3.org/2001/XMLSchema";
  205. SchemaFactory factory = SchemaFactory.newInstance(language);
  206. Source source = new DOMSource(map.getSchema());
  207. Schema schema = factory.newSchema(source);
  208. Validator validator = schema.newValidator();
  209. validator.validate(new DOMSource(xml));
  210. //if no exceptions where raised, the document is valid
  211. isValid=true;
  212. } catch(IOException e) {
  213. e.printStackTrace();
  214. }
  215. return isValid;
  216. }
  217. private void mapCellOnNode(XSSFCell cell, Node node, STXmlDataType.Enum outputDataType) {
  218. String value ="";
  219. switch (cell.getCellType()) {
  220. case XSSFCell.CELL_TYPE_STRING: value = cell.getStringCellValue(); break;
  221. case XSSFCell.CELL_TYPE_BOOLEAN: value += cell.getBooleanCellValue(); break;
  222. case XSSFCell.CELL_TYPE_ERROR: value = cell.getErrorCellString(); break;
  223. case XSSFCell.CELL_TYPE_FORMULA: value = cell.getStringCellValue(); break;
  224. case XSSFCell.CELL_TYPE_NUMERIC: value += cell.getRawValue(); break;
  225. default: ;
  226. }
  227. if (node instanceof Element) {
  228. Element currentElement = (Element) node;
  229. currentElement.setTextContent(value);
  230. } else {
  231. node.setNodeValue(value);
  232. }
  233. }
  234. private String removeNamespace(String elementName) {
  235. return elementName.matches(".*:.*")?elementName.split(":")[1]:elementName;
  236. }
  237. private Node getNodeByXPath(String xpath,Node rootNode,Document doc,boolean createMultipleInstances) {
  238. String[] xpathTokens = xpath.split("/");
  239. Node currentNode =rootNode;
  240. // The first token is empty, the second is the root node
  241. for(int i =2; i<xpathTokens.length;i++) {
  242. String axisName = removeNamespace(xpathTokens[i]);
  243. if (!axisName.startsWith("@")) {
  244. NodeList list =currentNode.getChildNodes();
  245. Node selectedNode = null;
  246. if (!(createMultipleInstances && i==xpathTokens.length-1) ) {
  247. // select the last child node only if we need to map to a single cell
  248. selectedNode = selectNode(axisName, list);
  249. }
  250. if (selectedNode==null) {
  251. selectedNode = createElement(doc, currentNode, axisName);
  252. }
  253. currentNode = selectedNode;
  254. } else {
  255. Node attribute = createAttribute(doc, currentNode, axisName);
  256. currentNode = attribute;
  257. }
  258. }
  259. return currentNode;
  260. }
  261. private Node createAttribute(Document doc, Node currentNode, String axisName) {
  262. String attributeName = axisName.substring(1);
  263. NamedNodeMap attributesMap = currentNode.getAttributes();
  264. Node attribute = attributesMap.getNamedItem(attributeName);
  265. if (attribute==null) {
  266. attribute = doc.createAttributeNS("", attributeName);
  267. attributesMap.setNamedItem(attribute);
  268. }
  269. return attribute;
  270. }
  271. private Node createElement(Document doc, Node currentNode, String axisName) {
  272. Node selectedNode;
  273. if (isNamespaceDeclared()) {
  274. selectedNode =doc.createElementNS(getNamespace(),axisName);
  275. } else {
  276. selectedNode = doc.createElementNS("", axisName);
  277. }
  278. currentNode.appendChild(selectedNode);
  279. return selectedNode;
  280. }
  281. private Node selectNode(String axisName, NodeList list) {
  282. Node selectedNode = null;
  283. for(int j=0;j<list.getLength();j++) {
  284. Node node = list.item(j);
  285. if (node.getNodeName().equals(axisName)) {
  286. selectedNode=node;
  287. break;
  288. }
  289. }
  290. return selectedNode;
  291. }
  292. private boolean isNamespaceDeclared() {
  293. String schemaNamespace = getNamespace();
  294. return schemaNamespace!=null && !schemaNamespace.equals("");
  295. }
  296. private String getNamespace() {
  297. return map.getCTSchema().getNamespace();
  298. }
  299. /**
  300. * Compares two xpaths to define an ordering according to the XML Schema
  301. *
  302. */
  303. public int compare(String leftXpath, String rightXpath) {
  304. int result = 0;
  305. Node xmlSchema = map.getSchema();
  306. String[] leftTokens = leftXpath.split("/");
  307. String[] rightTokens = rightXpath.split("/");
  308. int minLenght = leftTokens.length< rightTokens.length? leftTokens.length : rightTokens.length;
  309. Node localComplexTypeRootNode = xmlSchema;
  310. for(int i =1;i <minLenght; i++) {
  311. String leftElementName =leftTokens[i];
  312. String rightElementName = rightTokens[i];
  313. if (leftElementName.equals(rightElementName)) {
  314. Node complexType = getComplexTypeForElement(leftElementName, xmlSchema,localComplexTypeRootNode);
  315. localComplexTypeRootNode = complexType;
  316. } else {
  317. int leftIndex = indexOfElementInComplexType(leftElementName,localComplexTypeRootNode);
  318. int rightIndex = indexOfElementInComplexType(rightElementName,localComplexTypeRootNode);
  319. if (leftIndex!=-1 && rightIndex!=-1) {
  320. if ( leftIndex < rightIndex) {
  321. result = -1;
  322. }if ( leftIndex > rightIndex) {
  323. result = 1;
  324. }
  325. } else {
  326. // NOTE: the xpath doesn't match correctly in the schema
  327. }
  328. }
  329. }
  330. return result;
  331. }
  332. private int indexOfElementInComplexType(String elementName,Node complexType) {
  333. NodeList list = complexType.getChildNodes();
  334. int indexOf = -1;
  335. for(int i=0; i< list.getLength();i++) {
  336. Node node = list.item(i);
  337. if (node instanceof Element) {
  338. if (node.getLocalName().equals("element")) {
  339. Node nameAttribute = node.getAttributes().getNamedItem("name");
  340. if (nameAttribute.getNodeValue().equals(removeNamespace(elementName))) {
  341. indexOf = i;
  342. break;
  343. }
  344. }
  345. }
  346. }
  347. return indexOf;
  348. }
  349. private Node getComplexTypeForElement(String elementName,Node xmlSchema,Node localComplexTypeRootNode) {
  350. Node complexTypeNode = null;
  351. String elementNameWithoutNamespace = removeNamespace(elementName);
  352. NodeList list = localComplexTypeRootNode.getChildNodes();
  353. String complexTypeName = "";
  354. for(int i=0; i< list.getLength();i++) {
  355. Node node = list.item(i);
  356. if ( node instanceof Element) {
  357. if (node.getLocalName().equals("element")) {
  358. Node nameAttribute = node.getAttributes().getNamedItem("name");
  359. if (nameAttribute.getNodeValue().equals(elementNameWithoutNamespace)) {
  360. Node complexTypeAttribute = node.getAttributes().getNamedItem("type");
  361. if (complexTypeAttribute!=null) {
  362. complexTypeName = complexTypeAttribute.getNodeValue();
  363. break;
  364. }
  365. }
  366. }
  367. }
  368. }
  369. // Note: we expect that all the complex types are defined at root level
  370. if (!"".equals(complexTypeName)) {
  371. NodeList complexTypeList = xmlSchema.getChildNodes();
  372. for(int i=0; i< complexTypeList.getLength();i++) {
  373. Node node = complexTypeList.item(i);
  374. if ( node instanceof Element) {
  375. if (node.getLocalName().equals("complexType")) {
  376. Node nameAttribute = node.getAttributes().getNamedItem("name");
  377. if (nameAttribute.getNodeValue().equals(complexTypeName)) {
  378. NodeList complexTypeChildList =node.getChildNodes();
  379. for(int j=0; j<complexTypeChildList.getLength();j++) {
  380. Node sequence = complexTypeChildList.item(j);
  381. if ( sequence instanceof Element) {
  382. if (sequence.getLocalName().equals("sequence")) {
  383. complexTypeNode = sequence;
  384. break;
  385. }
  386. }
  387. }
  388. if (complexTypeNode!=null) {
  389. break;
  390. }
  391. }
  392. }
  393. }
  394. }
  395. }
  396. return complexTypeNode;
  397. }
  398. }