/****************************************************************************** * * Copyright 2014 Paphus Solutions Inc. * * Licensed under the Eclipse Public License, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ package org.botlibre.knowledge.xml; import java.io.File; import java.io.FileWriter; import java.io.StringWriter; import java.io.Writer; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.botlibre.BotException; import org.botlibre.api.knowledge.Network; import org.botlibre.api.knowledge.Relationship; import org.botlibre.api.knowledge.Vertex; import org.botlibre.knowledge.BasicNetwork; import org.botlibre.knowledge.BasicVertex; import org.botlibre.knowledge.Primitive; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; /** * Utility class that converts a network to add from xml. * The xml schema is defined in network.xsd. * A basic network and vertices are used for instantiation from xml. */ public class NetworkXMLParser { // toDo: use xsi:type for data private static NetworkXMLParser instance; public static NetworkXMLParser instance() { if (instance == null) { instance = new NetworkXMLParser(); } return instance; } /** * Parse a new network from an xml file. * This makes use of the xerces parser. */ public Network parse(File file) { try { return parse(new URL("file:///" + file.getAbsolutePath())); } catch (Exception exception) { throw new BotException("Parsing error while parsing network xml file.", exception); } } /** * Parse a new network from an xml uri. * This makes use of the xerces parser. */ @SuppressWarnings("unchecked") public Network parse(URL uri) { try { // Parse xml document with JAXP. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder parser = factory.newDocumentBuilder(); Document document = parser.parse(uri.toString()); Element root = document.getDocumentElement(); BasicNetwork network = new BasicNetwork(); SimpleDateFormat formater = new SimpleDateFormat(); // Parse all vertices and index by id. NodeList vertexElements = ((Element) root.getElementsByTagName("vertices").item(0)).getElementsByTagName("vertex"); Map<Number, Vertex> verticiesById = new HashMap<Number, Vertex>(); for (int index = 0; index < vertexElements.getLength(); index++) { Element vertexElement = (Element) vertexElements.item(index); BasicVertex vertex = new BasicVertex(); Long id = new Long(vertexElement.getAttribute("id")); vertex.setId(id); NodeList creationDateElements = vertexElement.getElementsByTagName("creation-date"); if (creationDateElements != null && creationDateElements.getLength() > 0) { String date = (String) creationDateElements.item(0).getFirstChild().getNodeValue(); vertex.setCreationDate(formater.parse(date)); } else { // Missing creation date, set to current date. (importing old files) vertex.setCreationDate(new Date()); } NodeList accessDateElements = vertexElement.getElementsByTagName("access-date"); if (accessDateElements != null && accessDateElements.getLength() > 0) { String date = (String) accessDateElements.item(0).getFirstChild().getNodeValue(); vertex.setAccessDate(formater.parse(date)); } else { // Missing access date, set to current date. (importing old files) vertex.setAccessDate(new Date()); } String count = vertexElement.getAttribute("access-count"); if (count != "") { vertex.setAccessCount(Integer.parseInt(count)); } else { // Missing count. (importing old files) vertex.setAccessCount(1); } NodeList dataElements = vertexElement.getElementsByTagName("data"); if (dataElements != null && dataElements.getLength() > 0) { Element dataNode = (Element)dataElements.item(0); String type = dataNode.getAttribute("type"); String value = dataNode.getTextContent(); Object data = null; if (type.equals("Primitive")) { data = new Primitive(value); } else if (type.equals("String")) { data = value; } else if (type.equals("Date")) { data = Calendar.getInstance(); ((Calendar)data).setTime(formater.parse(value)); } else { try { Class<Object> typeClass = (Class<Object>)Class.forName(type); data = typeClass.getConstructor(String.class).newInstance(value); } catch (Exception error) { System.out.println(error); data = value; } } vertex.setData(data); } // Check for existing vertex in case of corrupted file. Vertex existingVertex = network.findByData(vertex.getData()); if (existingVertex == null) { network.addVertex(vertex); verticiesById.put(id, vertex); } else { verticiesById.put(id, existingVertex); } } // Parse all relations and add to source vertex. NodeList relationElements = ((Element) root.getElementsByTagName("relations").item(0)).getElementsByTagName("relationship"); for (int index = 0; index < relationElements.getLength(); index++) { Element relationElement = (Element) relationElements.item(index); Long sourceId = new Long(relationElement.getAttribute("source-id")); Long targetId = new Long(relationElement.getAttribute("target-id")); Long typeId = new Long(relationElement.getAttribute("type-id")); Float correctness = 0.5f; try { correctness = new Float(relationElement.getAttribute("correctness")); } catch (Exception backwardCompatibility) { // Ignore. } Vertex source = (Vertex) verticiesById.get(sourceId); Vertex target = (Vertex) verticiesById.get(targetId); Vertex type = (Vertex) verticiesById.get(typeId); Relationship relationship = source.addRelationship(type, target); relationship.setCorrectness(correctness); } return network; } catch (Exception exception) { throw new BotException("Parsing error while parsing network xml file.", exception); } } /** * Covert the network into xml. */ public String toXML(Network network) { StringWriter writer = new StringWriter(); writeXML(network, writer); writer.flush(); return writer.toString(); } /** * Covert the network into xml and write to the file. */ public void toXML(Network network, File file) { Writer writer = null; try { writer = new FileWriter(file); writeXML(network, writer); writer.flush(); } catch (Exception exception) { throw new BotException("IO error while writing a network to xml.", exception); } finally { try { writer.close(); } catch (Exception ignore) {} } } /** * Covert the network into xml using the writer. */ public void writeXML(Network network, Writer writer) { try { // Write xml document with JAXP. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder parser = factory.newDocumentBuilder(); Document document = parser.newDocument(); Element root = document.createElement("network"); document.appendChild(root); Element verticesNode = document.createElement("vertices"); root.appendChild(verticesNode); // Vertices. SimpleDateFormat formater = new SimpleDateFormat(); for (Vertex vertex : network.findAll()) { Element vertexNode = document.createElement("vertex"); verticesNode.appendChild(vertexNode); vertexNode.setAttribute("id", vertex.getId().toString()); Element creationDate = document.createElement("creation-date"); creationDate.setTextContent(formater.format(vertex.getCreationDate().getTime())); vertexNode.appendChild(creationDate); Element accessDate = document.createElement("access-date"); accessDate.setTextContent(formater.format(vertex.getAccessDate().getTime())); vertexNode.appendChild(accessDate); vertexNode.setAttribute("access-count", String.valueOf(vertex.getAccessCount())); if (vertex.hasData()) { Element dataNode = document.createElement("data"); vertexNode.appendChild(dataNode); // Type Object data = vertex.getData(); String type = ""; if (data instanceof String) { type = "String"; } else if (data instanceof Primitive) { type = "Primitive"; } else if (data instanceof Calendar) { type = "Date"; } else { type = vertex.getData().getClass().getName(); } dataNode.setAttribute("type", type); // Value String value = ""; if (data instanceof String) { value = (String) data; } else if (data instanceof Primitive) { value = ((Primitive) data).getIdentity(); } else if (data instanceof Calendar) { value = formater.format(((Calendar)data).getTime()); } else { value = data.toString(); } dataNode.setTextContent(new String(value.getBytes("UTF8"))); } } // Relations. Element relationsNode = document.createElement("relations"); root.appendChild(relationsNode); for (Vertex vertex : network.findAll()) { for (Iterator<Relationship> relations = vertex.orderedAllRelationships(); relations.hasNext(); ) { Relationship relationship = (Relationship) relations.next(); Element relationshipNode = document.createElement("relationship"); relationsNode.appendChild(relationshipNode); relationshipNode.setAttribute("source-id", relationship.getSource().getId().toString()); relationshipNode.setAttribute("target-id", relationship.getTarget().getId().toString()); relationshipNode.setAttribute("type-id", relationship.getType().getId().toString()); relationshipNode.setAttribute("correctness", String.valueOf(relationship.getCorrectness())); } } // Transform to writer. Source source = new DOMSource(document); Result result = new StreamResult(writer); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); transformer.transform(source, result); } catch (Exception exception) { throw new BotException("IO error while writing a network to xml.", exception); } } }