1 package sourceforge.org.qmc2.options.editor.model; 2 3 import java.io.File; 4 import java.io.FileOutputStream; 5 import java.io.StringWriter; 6 import java.util.ArrayList; 7 import java.util.Collections; 8 import java.util.Comparator; 9 import java.util.HashMap; 10 import java.util.HashSet; 11 import java.util.List; 12 import java.util.Map; 13 import java.util.Set; 14 15 import javax.xml.parsers.DocumentBuilder; 16 import javax.xml.parsers.DocumentBuilderFactory; 17 import javax.xml.transform.OutputKeys; 18 import javax.xml.transform.Transformer; 19 import javax.xml.transform.TransformerFactory; 20 import javax.xml.transform.dom.DOMSource; 21 import javax.xml.transform.stream.StreamResult; 22 23 import org.w3c.dom.Document; 24 import org.w3c.dom.Element; 25 import org.w3c.dom.Node; 26 import org.w3c.dom.NodeList; 27 28 public class QMC2TemplateFile { 29 30 private final List<Section> sections = new ArrayList<Section>(); 31 32 private final Map<String, Section> sectionMap = new HashMap<String, Section>(); 33 34 private final List<Section> addedSections = new ArrayList<Section>(); 35 36 private final List<Section> removedSections = new ArrayList<Section>(); 37 38 // we should not rewrite whole xml to keep comments and other possible formatting 39 private final Document document; 40 private final Node rootNode; 41 42 private final String emulator; 43 44 private final String version; 45 46 private final String format; 47 48 private final static String TAG_TEMPLATE = "template"; 49 50 private final static String ATTRIBUTE_EMULATOR = "emulator"; 51 52 private final static String ATTRIBUTE_VERSION = "version"; 53 54 private final static String ATTRIBUTE_FORMAT = "format"; 55 QMC2TemplateFile(Document document, String emulator, String version, String format)56 public QMC2TemplateFile(Document document, String emulator, String version, String format) { 57 this.document = document; 58 this.rootNode = document.getElementsByTagName(TAG_TEMPLATE).item(0); 59 this.emulator = emulator; 60 this.version = version; 61 this.format = format; 62 } 63 addSectionInternal(Section section)64 public void addSectionInternal(Section section) { 65 section.setIndex(sections.size()); 66 sections.add(section); 67 sectionMap.put(section.getName(), section); 68 } 69 addSection(Section section)70 public void addSection(Section section) { 71 addSectionInternal(section); 72 if (!removedSections.remove(section)) { 73 addedSections.add(section); 74 } 75 } 76 addSection(Section section, int index)77 public void addSection(Section section, int index) { 78 sections.add(index, section); 79 for (int i = index + 1; i < sections.size(); i++) { 80 sections.get(i).setIndex(i); 81 } 82 sectionMap.put(section.getName(), section); 83 if (!removedSections.remove(section)) { 84 addedSections.add(section); 85 } 86 } 87 removeSection(String sectionName)88 public Section removeSection(String sectionName) { 89 Section s = sectionMap.remove(sectionName); 90 sections.remove(s); 91 for (int i = 0; i < sections.size(); i++) { 92 sections.get(i).setIndex(i); 93 } 94 if (!addedSections.remove(s)) { 95 removedSections.add(s); 96 } 97 return s; 98 } 99 getSections()100 public List<Section> getSections() { 101 return sections; 102 } 103 parse(File file)104 public static QMC2TemplateFile parse(File file) throws Exception { 105 106 QMC2TemplateFile templateFile = null; 107 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 108 DocumentBuilder builder = factory.newDocumentBuilder(); 109 Document document = builder.parse(file); 110 111 Node templateNode = document.getElementsByTagName(TAG_TEMPLATE).item(0); 112 113 String emulator = templateNode.getAttributes() 114 .getNamedItem(ATTRIBUTE_EMULATOR).getNodeValue(); 115 String version = templateNode.getAttributes() 116 .getNamedItem(ATTRIBUTE_VERSION).getNodeValue(); 117 String format = templateNode.getAttributes() 118 .getNamedItem(ATTRIBUTE_FORMAT).getNodeValue(); 119 120 templateFile = new QMC2TemplateFile(document, emulator, version, format); 121 122 NodeList sections = document.getElementsByTagName(Section.TAG_SECTION); 123 124 for (int i = 0; i < sections.getLength(); i++) { 125 Section s = Section.parseSection(sections.item(i)); 126 s.setParent(templateFile); 127 templateFile.addSectionInternal(s); 128 } 129 return templateFile; 130 } 131 getLanguages()132 public Set<String> getLanguages() { 133 134 Set<String> languages = new HashSet<String>(); 135 for (Section s : sections) { 136 languages.addAll(s.getLanguages()); 137 } 138 return languages; 139 } 140 toXML()141 private Document toXML() throws Exception { 142 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory 143 .newInstance(); 144 DocumentBuilder documentBuilder; 145 Document document = null; 146 147 documentBuilder = documentBuilderFactory.newDocumentBuilder(); 148 document = documentBuilder.newDocument(); 149 Element rootElement = document.createElement(TAG_TEMPLATE); 150 rootElement.setAttribute(ATTRIBUTE_EMULATOR, emulator); 151 rootElement.setAttribute(ATTRIBUTE_VERSION, version); 152 rootElement.setAttribute(ATTRIBUTE_FORMAT, format); 153 154 for (Section s : sections) { 155 rootElement.appendChild(s.toXML(document)); 156 } 157 document.appendChild(rootElement); 158 return document; 159 } 160 findSectionInDocument(String name)161 private Node findSectionInDocument(String name) { 162 Node section = null; 163 NodeList sections = document.getElementsByTagName(Section.TAG_SECTION); 164 int i = 0; 165 while (i < sections.getLength() && section == null) { 166 Node candidate = sections.item(i); 167 String sectionName = Section.parseSectionName(candidate); 168 if (name.equals(sectionName)) { 169 section = candidate; 170 } 171 i++; 172 } 173 174 return section; 175 } 176 getCommentNodeForSection(Node node)177 private Node getCommentNodeForSection(Node node) { 178 Node commentNode = null; 179 Node previousNode = node.getPreviousSibling(); 180 181 while (previousNode != null && 182 !previousNode.getNodeName().equals(Section.TAG_SECTION)) 183 { 184 if (previousNode.getNodeType() == Node.COMMENT_NODE) 185 { 186 commentNode = previousNode; 187 } 188 previousNode = previousNode.getPreviousSibling(); 189 } 190 191 return commentNode; 192 } 193 processRemovedSections()194 private void processRemovedSections() { 195 for (Section section : removedSections) { 196 Node sectionElement = findSectionInDocument(section.getName()); 197 if (sectionElement != null) { 198 Node commentNode = getCommentNodeForSection(sectionElement); 199 List<Node> nodesToRemove = new ArrayList<Node>(); 200 nodesToRemove.add(sectionElement); 201 if (commentNode != null) { 202 Node aux = sectionElement.getPreviousSibling(); 203 while (aux != commentNode) { 204 nodesToRemove.add(aux); 205 aux = aux.getPreviousSibling(); 206 } 207 nodesToRemove.add(aux); 208 } 209 210 for (Node node: nodesToRemove) { 211 rootNode.removeChild(node); 212 } 213 } 214 } 215 } 216 processAddedSections()217 private void processAddedSections() { 218 Collections.sort(addedSections, new Comparator<Section>() { 219 220 @Override 221 public int compare(Section o1, Section o2) { 222 //sort descending 223 if (o1.getIndex() == o2.getIndex()) { 224 return 0; 225 } 226 else if (o1.getIndex() > o2.getIndex()) { 227 return -1; 228 } 229 else { 230 return 1; 231 } 232 } 233 234 }); 235 236 for (Section section : addedSections) { 237 NodeList sectionNodes = document.getElementsByTagName(Section.TAG_SECTION); 238 Node newChild = section.toXML(document); 239 240 if (section.getIndex() < sectionNodes.getLength()) { 241 Node refChild = sectionNodes.item(section.getIndex()); 242 Node commentNode = getCommentNodeForSection(refChild); 243 rootNode.insertBefore(newChild, commentNode != null ? commentNode : refChild); 244 } 245 else { 246 rootNode.appendChild(newChild); 247 } 248 249 } 250 } 251 processChangedSections()252 private void processChangedSections() { 253 NodeList sectionNodes = document.getElementsByTagName(Section.TAG_SECTION); 254 for (int i = 0; i < sectionNodes.getLength(); i++) { 255 Node node = sectionNodes.item(i); 256 String sectionName = Section.parseSectionName(node); 257 Section section = sectionMap.get(sectionName); 258 if (section != null) { 259 Node newChild = section.toXML(document); 260 rootNode.replaceChild(newChild, node); 261 } 262 } 263 } 264 save(File f)265 public void save(File f) throws Exception { 266 TransformerFactory transformerFactory = TransformerFactory 267 .newInstance(); 268 Transformer transformer = transformerFactory.newTransformer(); 269 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 270 transformer.setOutputProperty( 271 "{http://xml.apache.org/xslt}indent-amount", "5"); 272 transformer.setOutputProperty(OutputKeys.METHOD, "xml"); 273 274 processRemovedSections(); 275 processAddedSections(); 276 processChangedSections(); 277 278 DOMSource source = new DOMSource(document); 279 StringWriter output = new StringWriter(); 280 281 StreamResult result = new StreamResult(output); 282 transformer.transform(source, result); 283 284 String outputString = output.toString().replace(" ", "\t"); 285 FileOutputStream fos = new FileOutputStream(f); 286 fos.write(outputString.getBytes()); 287 fos.close(); 288 } 289 290 } 291