package fi.otavanopisto.muikku.plugins.dnm.parser.structure; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.xpath.XPathExpressionException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.text.translate.NumericEntityUnescaper; import org.w3c.dom.CDATASection; import org.w3c.dom.DOMException; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.Text; import org.xml.sax.SAXException; import fi.otavanopisto.muikku.plugins.dnm.parser.DeusNexException; import fi.otavanopisto.muikku.plugins.dnm.parser.DeusNexInternalException; import fi.otavanopisto.muikku.plugins.dnm.parser.DeusNexSyntaxException; import fi.otavanopisto.muikku.plugins.dnm.parser.DeusNexXmlUtils; import fi.otavanopisto.muikku.plugins.dnm.parser.structure.model.Binary; import fi.otavanopisto.muikku.plugins.dnm.parser.structure.model.Document; import fi.otavanopisto.muikku.plugins.dnm.parser.structure.model.Folder; import fi.otavanopisto.muikku.plugins.dnm.parser.structure.model.Query; import fi.otavanopisto.muikku.plugins.dnm.parser.structure.model.Resource; public class DeusNexStructureParser { private static Logger logger = Logger.getLogger(DeusNexStructureParser.class.getName()); public DeusNexDocument parseDocument(InputStream inputStream) throws DeusNexException { DeusNexDocumentImpl deusNexDocument = new DeusNexDocumentImpl(); DocumentBuilder builder = DeusNexXmlUtils.createDocumentBuilder(); org.w3c.dom.Document domDocument; try { domDocument = builder.parse(inputStream); } catch (SAXException | IOException e1) { throw new DeusNexInternalException("XML Parsing failed", e1); } if (!"doc".equals(domDocument.getDocumentElement().getTagName())) { throw new DeusNexSyntaxException("Invalid root element"); } try { deusNexDocument.setRootFolder(parseFolder(domDocument.getDocumentElement())); } catch (XPathExpressionException e) { throw new DeusNexInternalException("Invalid XPath expression", e); } return deusNexDocument; } private Resource parseResource(Element resourceElement) throws DeusNexSyntaxException, XPathExpressionException, DeusNexInternalException { int type = DeusNexXmlUtils.getChildValueInt(resourceElement, "type"); switch (type) { case 2: case 3: return parseBinary(resourceElement); case 4: return parseFolder(resourceElement); case 7: return parseStyleDocument(resourceElement); case 34: return parseQueryDocument(resourceElement); case 37: return parseFCKDocument(resourceElement); case 38: return parseFCKQuery(resourceElement); case 11: return parseLink(resourceElement); default: throw new DeusNexInternalException("Unimplemented resource type " + type); } } private Resource parseLink(Element resourceElement) throws DeusNexInternalException { // TODO: Add support for proper link materials Document document = new Document(); Element linkElement = resourceElement.getOwnerDocument().createElement("a"); try { linkElement.setAttribute("href", DeusNexXmlUtils.getChildValue(resourceElement, "path")); linkElement.setTextContent(DeusNexXmlUtils.getChildValue(resourceElement, "title")); parseBasicResourceProperties(resourceElement, document); Element documentElement = resourceElement.getOwnerDocument().createElement("document"); Element fckDocumentElement = resourceElement.getOwnerDocument().createElement("fckdocument"); fckDocumentElement.setAttribute("lang", "fi"); fckDocumentElement.appendChild(linkElement); documentElement.appendChild(fckDocumentElement); document.setDocument(documentElement); } catch (DOMException | XPathExpressionException | DeusNexSyntaxException e) { throw new DeusNexInternalException("Link parsing failed", e); } return document; } private Binary parseBinary(Element resourceElement) throws DeusNexSyntaxException, XPathExpressionException, DeusNexInternalException { Binary binary = new Binary(); parseBasicResourceProperties(resourceElement, binary); Element contentElement = (Element) DeusNexXmlUtils.findNodeByXPath(resourceElement, "content"); Node contentChild = contentElement.getFirstChild(); String textContent = null; if (contentChild instanceof CDATASection) { textContent = ((CDATASection) contentChild).getTextContent(); } else if (contentChild instanceof Text) { textContent = ((Text) contentChild).getTextContent(); } byte[] content; try { content = decodeDeusNexBinary(textContent); } catch (UnsupportedEncodingException | DOMException e) { throw new DeusNexInternalException("Could not retrieve binary content", e); } binary.setContent(content); binary.setContentType(DeusNexXmlUtils.getChildValue(resourceElement, "contentType")); return binary; } private byte[] decodeDeusNexBinary(String binaryData) throws UnsupportedEncodingException { // First we need to decode base64 encoding byte[] decodedBytes = Base64.decodeBase64(binaryData); // Convert bytes into ISO-8859-1 string String decodedString = new String(decodedBytes, "ISO-8859-1"); // Translate escaped xml entities into characters String translatedString = new NumericEntityUnescaper().translate(decodedString); // Convert string into byte array using ISO-8859-1 encoding byte[] translatedBytes = translatedString.getBytes("ISO-8859-1"); // And voilĂ  we have a working binary return translatedBytes; } private Query parseFCKQuery(Element resourceElement) throws DeusNexSyntaxException, XPathExpressionException, DeusNexInternalException { Element documentElement = (Element) DeusNexXmlUtils.findNodeByXPath(resourceElement, "document"); if (documentElement == null) { logger.severe("Missing data/document node"); return null; } List<Resource> embeddedElements = new ArrayList<>(); List<Element> embeddedResourceElements = DeusNexXmlUtils.getElementsByXPath(resourceElement, "embedded/res"); for (Element embeddedResourceElement : embeddedResourceElements) { Resource resource = parseResource(embeddedResourceElement); if (resource != null) { embeddedElements.add(resource); } } Query query = new Query(); parseBasicResourceProperties(resourceElement, query); query.setDocument(documentElement); query.setResources(embeddedElements); query.setQueryIdType(DeusNexXmlUtils.getChildValue(resourceElement, "queryIdType")); query.setQueryStorageType(DeusNexXmlUtils.getChildValue(resourceElement, "queryStorageType")); query.setQueryState(DeusNexXmlUtils.getChildValue(resourceElement, "queryState")); query.setQueryType(DeusNexXmlUtils.getChildValue(resourceElement, "queryType")); return query; } private Document parseStyleDocument(Element resourceElement) throws DeusNexSyntaxException, XPathExpressionException, DeusNexInternalException { Element documentElement = (Element) DeusNexXmlUtils.findNodeByXPath(resourceElement, "document/styledocument"); if (documentElement == null) { logger.severe("Missing data/document node"); return null; } List<Resource> embeddedElements = new ArrayList<>(); List<Element> embeddedResourceElements = DeusNexXmlUtils.getElementsByXPath(resourceElement, "embedded/res"); for (Element embeddedResourceElement : embeddedResourceElements) { Resource resource = parseResource(embeddedResourceElement); if (resource != null) { embeddedElements.add(resource); } } Document document = new Document(); parseBasicResourceProperties(resourceElement, document); document.setDocument(documentElement); document.setResources(embeddedElements); return document; } private Query parseQueryDocument(Element resourceElement) throws DeusNexSyntaxException, XPathExpressionException, DeusNexInternalException { Element documentElement = (Element) DeusNexXmlUtils.findNodeByXPath(resourceElement, "styledocument"); List<Resource> embeddedElements = new ArrayList<>(); if (documentElement == null) { logger.severe("Missing data/document node"); return null; } List<Element> embeddedResourceElements = DeusNexXmlUtils.getElementsByXPath(resourceElement, "embedded/res"); for (Element embeddedResourceElement : embeddedResourceElements) { Resource resource = parseResource(embeddedResourceElement); if (resource != null) { embeddedElements.add(resource); } } Query query = new Query(); parseBasicResourceProperties(resourceElement, query); query.setDocument(documentElement); query.setResources(embeddedElements); query.setQueryIdType(DeusNexXmlUtils.getChildValue(resourceElement, "queryIdType")); query.setQueryStorageType(DeusNexXmlUtils.getChildValue(resourceElement, "queryStorageType")); query.setQueryState(DeusNexXmlUtils.getChildValue(resourceElement, "queryState")); query.setQueryType(DeusNexXmlUtils.getChildValue(resourceElement, "queryType")); return query; } private Document parseFCKDocument(Element resourceElement) throws DeusNexSyntaxException, XPathExpressionException, DeusNexInternalException { Element documentElement = (Element) DeusNexXmlUtils.findNodeByXPath(resourceElement, "document"); if (documentElement == null) { logger.severe("Missing data/document node"); return null; } List<Resource> embeddedElements = new ArrayList<>(); List<Element> embeddedResourceElements = DeusNexXmlUtils.getElementsByXPath(resourceElement, "embedded/res"); for (Element embeddedResourceElement : embeddedResourceElements) { Resource resource = parseResource(embeddedResourceElement); if (resource != null) { embeddedElements.add(resource); } } Document document = new Document(); parseBasicResourceProperties(resourceElement, document); document.setDocument(documentElement); document.setResources(embeddedElements); return document; } private Folder parseFolder(Element resourceElement) throws DeusNexSyntaxException, XPathExpressionException, DeusNexInternalException { List<Element> childResources = DeusNexXmlUtils.getElementsByXPath(resourceElement, "res"); List<Resource> resources = new ArrayList<>(); for (Element childResource : childResources) { Resource resource = parseResource(childResource); if (resource != null) { resources.add(resource); } } Folder folder = new Folder(); parseBasicResourceProperties(resourceElement, folder); folder.setResources(resources); return folder; } private void parseBasicResourceProperties(Element resourceElement, Resource resource) throws XPathExpressionException, DeusNexSyntaxException { Integer no = DeusNexXmlUtils.getChildValueInteger(resourceElement, "no"); if (no == null) { throw new DeusNexSyntaxException("Missing no node"); } resource.setNo(no); resource.setName(DeusNexXmlUtils.getChildValue(resourceElement, "name")); resource.setPath(DeusNexXmlUtils.getChildValue(resourceElement, "path")); resource.setTitle(DeusNexXmlUtils.getChildValue(resourceElement, "title")); resource.setHidden(DeusNexXmlUtils.getChildValueInt(resourceElement, "hidden") == 1 ? Boolean.TRUE : Boolean.FALSE); } }