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("&#10;");
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}