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