package util; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * This utility creates an XML document. * * The use of attributes is avoided in this utility because they are more difficult to read * and to maintain. (Reference: w3schools.com) * * @author Yoshida */ public class XMLTool { private static final String XML_PARAM_YES = "yes"; private static final String DOC_CREATION_EXCEPTION = "Could not create a new instance of a Document."; private static final String INDENT_REFERENCE = "{http://xml.apache.org/xslt}indent-amount"; private static final String CLOSE_EXCEPTION = "The writer could not be closed"; private static final String CONVERSION_EXCEPTION = "The document could not be converted into a string"; private static final String WRITING_EXCEPTION = "File could not be written."; private static final String USER_DIR = System.getProperty("user.dir"); private Document myDoc; /** * The constructor of this XML file builder automatically creates a buffered * document with the destination of the argument path, ready to receive elements. */ public XMLTool() { makeDoc(); } /** * This constructor reads in an XML file from an XMLFilePath * * @param xmlFilePath The relative path with "filename.xml". * For example, if path = "/src/example.xml", * the file example.xml will be saved in the source folder. */ public XMLTool(String xmlFilePath) { readDoc(xmlFilePath); } /** * Creates a new document. */ public void makeDoc () { DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); try { myDoc = dbFactory.newDocumentBuilder().newDocument(); } catch (ParserConfigurationException e) { throw new RuntimeException(DOC_CREATION_EXCEPTION, e); } } /** * Reads a new document from an XML file. * * @param path The relative path with "filename.xml". For example, if path = "/src/example.xml", * the file example.xml will be saved in the source folder. */ public void readDoc (String path) { File file = new File(USER_DIR + path); DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); try { myDoc = dbFactory.newDocumentBuilder().parse(file); } catch (IOException e) { throw new RuntimeException("Could not open file.", e); } catch (ParserConfigurationException e) { throw new RuntimeException(DOC_CREATION_EXCEPTION, e); } catch (SAXException e) { throw new RuntimeException("XML document is corrupted.", e); } } /** * Gets the document being written in this XML Builder. * * @return A document with an XML format. */ public Document getDoc () { return myDoc; } /* * Creating new elements */ /** * Creates the root of this document. * * @param tag This parameter is the tag of the root * @return the root */ public Element makeRoot (String tag) { Element root = myDoc.createElement(tag); myDoc.appendChild(root); return root; } /** * Creates a new empty element in the Doc. * * @param tag The tag of the element * @return The recently created element. */ public Element makeElement (String tag) { Element element = myDoc.createElement(tag); return element; } /** * Creates a new element in the Doc with a content. * * @param tag The tag of the element * @param content The content of the element. * @return The recently created element. */ public Element makeElement (String tag, String content) { Element element = makeElement(tag); element.setTextContent(content); return element; } /** * Creates a new elements tree from a Map. * * @param parentTag The tag of the parent * @param elementsMap The keys of this map will be converted into tags and the values of the map * into its values * @return the parent element with its respective tag. */ public Element makeElementsFromMap (String parentTag, Map<String, String> elementsMap) { Element parent = makeElement(parentTag); return addChildrenFromMap(parent, elementsMap); } /* * Adding elements to parent element */ /** * Adds a element child to an specific parent element * * @param parent The parent element to which this method is going to add a child * @param child The element to be added as a child * @return The recently modified parent. */ public Element addChild (Element parent, Element child) { parent.appendChild(child); return parent; } /** * Creates a new child element from a tag and adds it to an specific parent element * * @param parent The parent element to which this method is going to add a child * @param tag The tag of the child node * @return The recently modified parent. */ public Element addChild (Element parent, String tag) { Element child = makeElement(tag); return addChild(parent, child); } /** * Creates a new child element from a tag, adds a value to it and adds it to a parent element * * @param parent The parent element to which this method is going to add a child * @param tag The tag of the child * @param value The value of the child * @return The recently modified parent. */ public Element addChild (Element parent, String tag, String value) { Element child = makeElement(tag, value); return addChild(parent, child); } /** * Creates children and appends/adds them to a particular * parent element (passed in as a parameter) * * @param parent The parent element to which this method is going to add children * @param elementsMap The keys of this map will be converted into tags and the values of the map * into its values * @return The parent element. */ public Element addChildrenFromMap (Element parent, Map<String, String> elementsMap) { for (String name : elementsMap.keySet()) { String value = elementsMap.get(name); addChild(parent, name, value); } return parent; } /* * Getter methods. */ /** * This method returns the first element in the document with an specific tag. * Be careful with this method! If you have many instances of the same tag, use * getElementsListByTagName, which will return a list of elements with that same tag, in the * top-down reading order. * * @param tag The name of the tag to match on. The special value "*" matches all tags. For XML, * the tag parameter is case-sensitive, otherwise it depends on the case-sensitivity * of the mark up language in use. * @return The FIRST element with the tag. */ public Element getElement (String tag) { return (Element) myDoc.getElementsByTagName(tag).item(0); } /** * Returns a list of elements with the tag from an specific element. * * @param parent The parent element from which the element * @param tag The name of the tag to match on. The special value "*" matches all tags. For XML, * the tag parameter is case-sensitive, otherwise it depends on the case-sensitivity * of the mark up language in use. * * @return A list of elements with the tag. */ public List<Element> getElementList (Element parent, String tag) { NodeList nodes = parent.getElementsByTagName(tag); return convertNodeList(nodes); } /** * Returns a list of elements with the tag from the XML document. * * @param tag The name of the tag to match on. The special value "*" matches all tags. For XML, * the tag parameter is case-sensitive, otherwise it depends on the case-sensitivity * of the mark up language in use. * @return A list of elements with the tag. */ public List<Element> getElementList (String tag) { NodeList nodes = myDoc.getElementsByTagName(tag); return convertNodeList(nodes); } /** * This method return the tag name of an element. * * @param element The element containing a tag * @return the tag of the element as a string */ public String getTagName (Element element) { return element.getTagName(); } /** * This method returns the value of an element as a String. * * @param element The element from which the content is being extracted. * @return A string stores in the element. */ public String getContent (Element element) { return element.getTextContent(); } /** * Gets the content of the FIRST element associated with the referent tag. * If the tag refers to a non-leaf node, it appends all the values from its children. * * @param tag A string with the tag of the element. * @return the content(value) of the element. */ public String getContent (String tag) { return getContent(getElement(tag)); } /** * Get a list of children element nodes from a parent in the order they appear in the XML file. * * @param parent The parent element from which the children elements are going to be retrieved. * @return A list of children elements from the parent element. */ public List<Element> getChildrenList (Element parent) { NodeList nodes = parent.getChildNodes(); return convertNodeList(nodes); } /** * Creates a map with the child tag (as a map key) and a child element (as a map value) of all * the * children elements of a particular parent element. * If the parent element does not contain children, this method returns an empty map. * * @param parent The parent element node from which the children elements will be created. * @return a map with the child tag (as a map key) and a child element (as a map value) of all * the children elements of a particular parent element. */ public Map<String, Element> getChildrenElementMap (Element parent) { Map<String, Element> map = new HashMap<String, Element>(); NodeList nodes = parent.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { if (nodes.item(i).getNodeType() == Node.ELEMENT_NODE) { Element node = (Element) nodes.item(i); map.put(node.getTagName(), node); } } return map; } /** * Creates a map with the child tag (as a map key) and a child element (as a map value) of all * the children elements of a particular parent element tag. * If the parent element does not contain children, this method returns an empty map. * * @param parentTag The tag of the parent element node. * @return a map with the child tag (as a map key) and a child element (as a map value) of all * the children elements of a particular parent element. */ public Map<String, Element> getChildrenElementMap (String parentTag) { Element parent = getElement(parentTag); return getChildrenElementMap(parent); } /** * Creates a map with the tag (as a map key) and the content (as a map value) of all the * children elements of a particular node. * If the parent element does not contain children, this method returns an empty map. * * @param parent The parent element node. * @return a map with the tag (as a map key) and the content (as a map value) of all the * children elements of a particular node. */ public Map<String, String> getChildrenStringMap (Element parent) { Map<String, String> stringMap = new HashMap<String, String>(); Map<String, Element> elementMap = getChildrenElementMap(parent); for (String key : elementMap.keySet()) { stringMap.put(key, elementMap.get(key).getTextContent()); } return stringMap; } /** * Creates a map with the tag (as a map key) and the content (as a map value) of all the * children elements of the first node with a particular tag. * If the parent element does not contain children, this method returns an empty map. * * @param parentTag The tag of the parent element node. * @return a map with the tag (as a map key) and the content (as a map value) of all the * children elements of a particular node. */ public Map<String, String> getChildrenStringMap (String parentTag) { Element parent = getElement(parentTag); return getChildrenStringMap(parent); } /** * Searches for all parent elements with the same tag and then generates a list containing maps * of their respective children. * Creates maps with the tag (as a map key) and the content (as a map value) of all the * children elements of a particular node. * The list preserves the top-down numerical order in which the tag is found in the XML * document. * * @param parentsTag The tag of the parent elements to be searched. * @return a list of maps. The map contains the tag of the children elements as a key and the * children content as a value. Map<key, value> = Map<childTag, childValue> = * Map<String, String>; */ public List<Map<String, Element>> getMapList (String parentsTag) { List<Element> nodeList = getElementList(parentsTag); List<Map<String, Element>> listOfMaps = new ArrayList<Map<String, Element>>(nodeList.size()); for (int i = 0; i < nodeList.size(); i++) { listOfMaps.add(getChildrenElementMap(nodeList.get(i))); } return listOfMaps; } /* * Other functionalities */ /** * Converts a NodeList into a list of Elements. * * @param nodeList NodeList containing elements * @return A list with the elements of nodeList. */ public List<Element> convertNodeList (NodeList nodeList) { List<Element> elementList = new ArrayList<Element>(); for (int i = 0; i < nodeList.getLength(); i++) { if (nodeList.item(i).getNodeType() == Node.ELEMENT_NODE) { elementList.add((Element) nodeList.item(i)); } } return elementList; } /* * Writing to file, will be replaced by the secretary program */ /** * Translates a document into an XML formatted String. * * @param doc document with its elements. * @return String with XML formatting * @throws TransformerException This exception is thrown when a Document object cannot be * converted into a String */ public String translateToXMLString (Document doc) throws TransformerException { DOMSource source = new DOMSource(doc); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, XML_PARAM_YES); transformer.setOutputProperty(OutputKeys.INDENT, XML_PARAM_YES); transformer.setOutputProperty(INDENT_REFERENCE, "4"); StringWriter sw = new StringWriter(); StreamResult result = new StreamResult(sw); transformer.transform(source, result); String xmlString = sw.toString(); return xmlString; } /** * This <code>FileWriter</code> creates the document in an specific location, * form an String. * * @param path The relative path with "filename.xml". For example, if path = "/src/example.xml", * the file example.xml will be saved in the source folder. */ public void writeFile (String path) { FileWriter writer = null; try { writer = new FileWriter(USER_DIR + path); writer.write(translateToXMLString(myDoc)); writer.close(); } catch (TransformerException e) { try { writer.close(); } catch (IOException e1) { throw new RuntimeException(CLOSE_EXCEPTION, e); } throw new RuntimeException(CONVERSION_EXCEPTION, e); } catch (IOException e) { throw new RuntimeException(WRITING_EXCEPTION, e); } } }