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