001package org.hl7.fhir.utilities.xls; 002 003import java.io.File; 004import java.io.FileInputStream; 005import java.io.FileOutputStream; 006import java.io.IOException; 007import java.io.InputStream; 008 009import javax.xml.parsers.DocumentBuilder; 010import javax.xml.parsers.DocumentBuilderFactory; 011import javax.xml.parsers.ParserConfigurationException; 012import javax.xml.transform.Result; 013import javax.xml.transform.Source; 014import javax.xml.transform.Transformer; 015import javax.xml.transform.TransformerException; 016import javax.xml.transform.TransformerFactory; 017import javax.xml.transform.dom.DOMSource; 018import javax.xml.transform.stream.StreamResult; 019 020import org.hl7.fhir.exceptions.FHIRException; 021import org.hl7.fhir.utilities.TextFile; 022import org.hl7.fhir.utilities.Utilities; 023import org.hl7.fhir.utilities.xml.XMLUtil; 024import org.w3c.dom.Document; 025import org.w3c.dom.Element; 026import org.w3c.dom.Node; 027import org.xml.sax.SAXException; 028 029public class XLSXmlNormaliser { 030 031 private static final String XLS_NS = "urn:schemas-microsoft-com:office:spreadsheet"; 032 033 private Document xml; 034 035 private String source; 036 private String dest; 037 private boolean exceptionIfExcelNotNormalised; 038 039 public XLSXmlNormaliser(String source, String dest, boolean exceptionIfExcelNotNormalised) { 040 super(); 041 this.source = source; 042 this.dest = dest; 043 this.exceptionIfExcelNotNormalised = exceptionIfExcelNotNormalised; 044 } 045 046 public XLSXmlNormaliser(String source, boolean exceptionIfExcelNotNormalised) { 047 super(); 048 this.source = source; 049 this.dest = source; 050 this.exceptionIfExcelNotNormalised = exceptionIfExcelNotNormalised; 051 } 052 053 public void go() throws FHIRException, TransformerException, ParserConfigurationException, SAXException, IOException { 054 File inp = new File(source); 055 long time = inp.lastModified(); 056 xml = parseXml(new FileInputStream(inp)); 057 058 Element root = xml.getDocumentElement(); 059 060 boolean hasComment = false; 061 Node n = root.getFirstChild(); 062 while (n != null) { 063 if (n.getNodeType() == Node.COMMENT_NODE && "canonicalized".equals(n.getTextContent())) { 064 hasComment = true; 065 break; 066 } 067 n = n.getNextSibling(); 068 } 069 if (hasComment) 070 return; 071 if (exceptionIfExcelNotNormalised) 072 throw new FHIRException("The spreadsheet "+dest+" was committed after editing in excel, but before the build could run *after Excel was closed*"); 073 074 System.out.println("normalise: "+source); 075 076 XMLUtil.deleteByName(root, "ActiveSheet"); 077 Element xw = XMLUtil.getNamedChild(root, "ExcelWorkbook"); 078 XMLUtil.deleteByName(xw, "WindowHeight"); 079 XMLUtil.deleteByName(xw, "WindowWidth"); 080 XMLUtil.deleteByName(xw, "WindowTopX"); 081 XMLUtil.deleteByName(xw, "WindowTopY"); 082 083 for (Element wk : XMLUtil.getNamedChildren(root, "Worksheet")) 084 processWorksheet(wk); 085 086 if (!hasComment) 087 root.appendChild(xml.createComment("canonicalized")); 088 try { 089 saveXml(new FileOutputStream(dest)); 090 String s = TextFile.fileToString(dest); 091 s = s.replaceAll("\r\n","\n"); 092 s = replaceSignificantEoln(s); 093 TextFile.stringToFile(s, dest, false); 094 new File(dest).setLastModified(time); 095 } catch (Exception e) { 096 System.out.println("The file "+dest+" is still open in Excel, and you will have to run the build after closing Excel before committing"); 097 } 098 } 099 100 private String replaceSignificantEoln(String s) { 101 StringBuilder b = new StringBuilder(); 102 boolean hasText = false; 103 for (char c : s.toCharArray()) { 104 if (c == '>' || c == '<' ) { 105 hasText = false; 106 b.append(c); 107 } else if (c == '\n') { 108 if (hasText) { 109 b.append(" "); 110 } else 111 b.append(c); 112 113 } else if (!Character.isWhitespace(c)) { 114 b.append(c); 115 hasText = true; 116 } else 117 b.append(c); 118 } 119 120 return b.toString(); 121 } 122 123 private void processWorksheet(Element wk) throws FHIRException { 124 Element tbl = XMLUtil.getNamedChild(wk, "Table"); 125 processTable(tbl); 126 for (Element row : XMLUtil.getNamedChildren(tbl, "Row")) 127 processRow(row); 128 for (Element col : XMLUtil.getNamedChildren(tbl, "Column")) 129 processCol(col); 130 for (Element wo : XMLUtil.getNamedChildren(wk, "WorksheetOptions")) 131 processOptions(wo); 132 } 133 134 private void processOptions(Element wo) { 135 XMLUtil.deleteByName(wo, "Unsynced"); 136 XMLUtil.deleteByName(wo, "Panes"); 137 for (Element panes : XMLUtil.getNamedChildren(wo, "Panes")) 138 processPanes(panes); 139 } 140 141 private void processPanes(Element panes) { 142 for (Element pane : XMLUtil.getNamedChildren(panes, "Pane")) 143 processPane(pane); 144 } 145 146 private void processPane(Element pane) { 147 XMLUtil.deleteByName(pane, "ActiveRow"); 148 XMLUtil.deleteByName(pane, "ActiveCol"); 149 } 150 151// private void setTextElement(Element e, String name, String text) { 152// Element te = XMLUtil.getNamedChild(e, name); 153// if (te != null) 154// te.setTextContent(text); 155// } 156 157 private void processTable(Element col) { 158 XMLUtil.deleteAttr(col, "urn:schemas-microsoft-com:office:spreadsheet", "DefaultColumnWidth"); 159 XMLUtil.deleteAttr(col, "urn:schemas-microsoft-com:office:spreadsheet", "DefaultRowHeight"); 160 } 161 162 163 private void processCol(Element col) { 164 String width = col.getAttributeNS("urn:schemas-microsoft-com:office:spreadsheet", "Width"); 165 if (!Utilities.noString(width)) { 166 Double d = Double.valueOf(width); 167 width = Double.toString(Math.round(d*2)/2); 168 col.setAttributeNS("urn:schemas-microsoft-com:office:spreadsheet", "ss:Width", width); 169 } 170 } 171 172 private void processRow(Element row) { 173 String height = row.getAttributeNS("urn:schemas-microsoft-com:office:spreadsheet", "Height"); 174 if (!Utilities.noString(height) && height.contains(".")) { 175 Double d = Double.valueOf(height); 176 row.setAttributeNS("urn:schemas-microsoft-com:office:spreadsheet", "ss:Height", Long.toString(Math.round(d))); 177 } 178 } 179 180 private void check(boolean test, String message) throws FHIRException { 181 if (!test) 182 throw new FHIRException(message+" in "+getLocation()); 183 } 184 185 186 private Document parseXml(InputStream in) throws FHIRException, ParserConfigurationException, SAXException, IOException { 187 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 188 factory.setNamespaceAware(true); 189 DocumentBuilder builder = factory.newDocumentBuilder(); 190 return builder.parse(in); 191 } 192 193 private void saveXml(FileOutputStream stream) throws TransformerException, IOException { 194 195 TransformerFactory factory = TransformerFactory.newInstance(); 196 Transformer transformer = factory.newTransformer(); 197 Result result = new StreamResult(stream); 198 Source source = new DOMSource(xml); 199 transformer.transform(source, result); 200 stream.flush(); 201 } 202 203 private String getLocation() { 204 return source; //+", row "+rowIndex.toString(); 205 } 206 207 208}