001package org.hl7.fhir.utilities.xls; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import java.io.File; 035import java.io.FileInputStream; 036import java.io.FileOutputStream; 037import java.io.IOException; 038import java.io.InputStream; 039 040import javax.xml.parsers.DocumentBuilder; 041import javax.xml.parsers.DocumentBuilderFactory; 042import javax.xml.parsers.ParserConfigurationException; 043import javax.xml.transform.Result; 044import javax.xml.transform.Source; 045import javax.xml.transform.Transformer; 046import javax.xml.transform.TransformerException; 047import javax.xml.transform.TransformerFactory; 048import javax.xml.transform.dom.DOMSource; 049import javax.xml.transform.stream.StreamResult; 050 051import org.hl7.fhir.exceptions.FHIRException; 052import org.hl7.fhir.utilities.TextFile; 053import org.hl7.fhir.utilities.Utilities; 054import org.hl7.fhir.utilities.xml.XMLUtil; 055import org.w3c.dom.Document; 056import org.w3c.dom.Element; 057import org.w3c.dom.Node; 058import org.xml.sax.SAXException; 059 060public class XLSXmlNormaliser { 061 062 private static final String XLS_NS = "urn:schemas-microsoft-com:office:spreadsheet"; 063 064 private Document xml; 065 066 private String source; 067 private String dest; 068 private boolean exceptionIfExcelNotNormalised; 069 070 public XLSXmlNormaliser(String source, String dest, boolean exceptionIfExcelNotNormalised) { 071 super(); 072 this.source = source; 073 this.dest = dest; 074 this.exceptionIfExcelNotNormalised = exceptionIfExcelNotNormalised; 075 } 076 077 public XLSXmlNormaliser(String source, boolean exceptionIfExcelNotNormalised) { 078 super(); 079 this.source = source; 080 this.dest = source; 081 this.exceptionIfExcelNotNormalised = exceptionIfExcelNotNormalised; 082 } 083 084 public void go() throws FHIRException, TransformerException, ParserConfigurationException, SAXException, IOException { 085 File inp = new File(source); 086 long time = inp.lastModified(); 087 xml = parseXml(new FileInputStream(inp)); 088 089 Element root = xml.getDocumentElement(); 090 091 boolean hasComment = false; 092 Node n = root.getFirstChild(); 093 while (n != null) { 094 if (n.getNodeType() == Node.COMMENT_NODE && "canonicalized".equals(n.getTextContent())) { 095 hasComment = true; 096 break; 097 } 098 n = n.getNextSibling(); 099 } 100 if (hasComment) 101 return; 102 if (exceptionIfExcelNotNormalised) 103 throw new FHIRException("The spreadsheet "+dest+" was committed after editing in excel, but before the build could run *after Excel was closed*"); 104 105 System.out.println("normalise: "+source); 106 107 XMLUtil.deleteByName(root, "ActiveSheet"); 108 Element xw = XMLUtil.getNamedChild(root, "ExcelWorkbook"); 109 XMLUtil.deleteByName(xw, "WindowHeight"); 110 XMLUtil.deleteByName(xw, "WindowWidth"); 111 XMLUtil.deleteByName(xw, "WindowTopX"); 112 XMLUtil.deleteByName(xw, "WindowTopY"); 113 114 for (Element wk : XMLUtil.getNamedChildren(root, "Worksheet")) 115 processWorksheet(wk); 116 117 if (!hasComment) 118 root.appendChild(xml.createComment("canonicalized")); 119 try { 120 saveXml(new FileOutputStream(dest)); 121 String s = TextFile.fileToString(dest); 122 s = s.replaceAll("\r\n","\n"); 123 s = replaceSignificantEoln(s); 124 TextFile.stringToFile(s, dest, false); 125 new File(dest).setLastModified(time); 126 } catch (Exception e) { 127 System.out.println("The file "+dest+" is still open in Excel, and you will have to run the build after closing Excel before committing"); 128 } 129 } 130 131 private String replaceSignificantEoln(String s) { 132 StringBuilder b = new StringBuilder(); 133 boolean hasText = false; 134 for (char c : s.toCharArray()) { 135 if (c == '>' || c == '<' ) { 136 hasText = false; 137 b.append(c); 138 } else if (c == '\n') { 139 if (hasText) { 140 b.append(" "); 141 } else 142 b.append(c); 143 144 } else if (!Character.isWhitespace(c)) { 145 b.append(c); 146 hasText = true; 147 } else 148 b.append(c); 149 } 150 151 return b.toString(); 152 } 153 154 private void processWorksheet(Element wk) throws FHIRException { 155 Element tbl = XMLUtil.getNamedChild(wk, "Table"); 156 processTable(tbl); 157 for (Element row : XMLUtil.getNamedChildren(tbl, "Row")) 158 processRow(row); 159 for (Element col : XMLUtil.getNamedChildren(tbl, "Column")) 160 processCol(col); 161 for (Element wo : XMLUtil.getNamedChildren(wk, "WorksheetOptions")) 162 processOptions(wo); 163 } 164 165 private void processOptions(Element wo) { 166 XMLUtil.deleteByName(wo, "Unsynced"); 167 XMLUtil.deleteByName(wo, "Panes"); 168 for (Element panes : XMLUtil.getNamedChildren(wo, "Panes")) 169 processPanes(panes); 170 } 171 172 private void processPanes(Element panes) { 173 for (Element pane : XMLUtil.getNamedChildren(panes, "Pane")) 174 processPane(pane); 175 } 176 177 private void processPane(Element pane) { 178 XMLUtil.deleteByName(pane, "ActiveRow"); 179 XMLUtil.deleteByName(pane, "ActiveCol"); 180 } 181 182// private void setTextElement(Element e, String name, String text) { 183// Element te = XMLUtil.getNamedChild(e, name); 184// if (te != null) 185// te.setTextContent(text); 186// } 187 188 private void processTable(Element col) { 189 XMLUtil.deleteAttr(col, "urn:schemas-microsoft-com:office:spreadsheet", "DefaultColumnWidth"); 190 XMLUtil.deleteAttr(col, "urn:schemas-microsoft-com:office:spreadsheet", "DefaultRowHeight"); 191 } 192 193 194 private void processCol(Element col) { 195 String width = col.getAttributeNS("urn:schemas-microsoft-com:office:spreadsheet", "Width"); 196 if (!Utilities.noString(width)) { 197 Double d = Double.valueOf(width); 198 width = Double.toString(Math.round(d*2)/2); 199 col.setAttributeNS("urn:schemas-microsoft-com:office:spreadsheet", "ss:Width", width); 200 } 201 } 202 203 private void processRow(Element row) { 204 String height = row.getAttributeNS("urn:schemas-microsoft-com:office:spreadsheet", "Height"); 205 if (!Utilities.noString(height) && height.contains(".")) { 206 Double d = Double.valueOf(height); 207 row.setAttributeNS("urn:schemas-microsoft-com:office:spreadsheet", "ss:Height", Long.toString(Math.round(d))); 208 } 209 } 210 211 private void check(boolean test, String message) throws FHIRException { 212 if (!test) 213 throw new FHIRException(message+" in "+getLocation()); 214 } 215 216 217 private Document parseXml(InputStream in) throws FHIRException, ParserConfigurationException, SAXException, IOException { 218 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 219 factory.setNamespaceAware(true); 220 DocumentBuilder builder = factory.newDocumentBuilder(); 221 return builder.parse(in); 222 } 223 224 private void saveXml(FileOutputStream stream) throws TransformerException, IOException { 225 226 TransformerFactory factory = TransformerFactory.newInstance(); 227 Transformer transformer = factory.newTransformer(); 228 Result result = new StreamResult(stream); 229 Source source = new DOMSource(xml); 230 transformer.transform(source, result); 231 stream.flush(); 232 } 233 234 private String getLocation() { 235 return source; //+", row "+rowIndex.toString(); 236 } 237 238 239}