package fi.otavanopisto.muikku.plugins.dnm.parser.content; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.transform.TransformerException; import javax.xml.xpath.XPathExpressionException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; 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.material.fieldmeta.MultiSelectFieldOptionMeta; public class DeusNexContentParser { public DeusNexContentParser() { } public DeusNexContentParser setEmbeddedItemElementHandler( DeusNexEmbeddedItemElementHandler deusNexEmbeddedItemElementHandler) { this.embeddedItemElementHandler = deusNexEmbeddedItemElementHandler; return this; } public DeusNexContentParser setFieldElementHandler(DeusNexFieldElementHandler fieldElementHandler) { this.fieldElementHandler = fieldElementHandler; return this; } private Map<String, String> parseStyleDocument(Element documentElement) throws DeusNexException { Map<String, String> contents = new HashMap<>(); Document ownerDocument = documentElement.getOwnerDocument(); try { NodeList embeddedNodeList = documentElement.getElementsByTagName("ix:embedded"); for (int i = embeddedNodeList.getLength() - 1; i >= 0; i--) { Element embeddedElement = (Element) embeddedNodeList.item(i); Node replacement = handleEmbedded(ownerDocument, embeddedElement); replaceElement(ownerDocument, embeddedElement, replacement); } NodeList ixImageNodeList = documentElement.getElementsByTagName("ix:image"); for (int i = ixImageNodeList.getLength() - 1; i >= 0; i--) { Element ixImageElement = (Element) ixImageNodeList.item(i); Node replacement = handleIxImage(ownerDocument, ixImageElement); replaceElement(ownerDocument, ixImageElement, replacement); } NodeList embeddedItemNodeList = documentElement.getElementsByTagName("ix:embeddeditem"); for (int i = embeddedItemNodeList.getLength() - 1; i >= 0; i--) { Element embeddedItemElement = (Element) embeddedItemNodeList.item(i); Node replacement = handleEmbeddedItem(ownerDocument, embeddedItemElement); replaceElement(ownerDocument, embeddedItemElement, replacement); } NodeList textFieldNodeList = documentElement.getElementsByTagName("ixf:textfield"); for (int i = textFieldNodeList.getLength() - 1; i >= 0; i--) { Element element = (Element) textFieldNodeList.item(i); Node replacement = handleTextField(ownerDocument, element); replaceElement(ownerDocument, element, replacement); } NodeList memoFieldNodeList = documentElement.getElementsByTagName("ixf:memofield"); for (int i = memoFieldNodeList.getLength() - 1; i >= 0; i--) { Element element = (Element) memoFieldNodeList.item(i); Node replacement = handleMemoField(ownerDocument, element); replaceElement(ownerDocument, element, replacement); } NodeList optionListNodeList = documentElement.getElementsByTagName("ixf:optionlist"); for (int i = optionListNodeList.getLength() - 1; i >= 0; i--) { Element element = (Element) optionListNodeList.item(i); Node replacement = handleOptionListField(ownerDocument, element); replaceElement(ownerDocument, element, replacement); } NodeList connectFieldNodeList = documentElement.getElementsByTagName("ixf:connectfield"); for (int i = connectFieldNodeList.getLength() - 1; i >= 0; i--) { Element element = (Element) connectFieldNodeList.item(i); Node replacement = handleConnectField(ownerDocument, element); replaceElement(ownerDocument, element, replacement); } // ixf:uploadfilefield NodeList fileFieldNodeList = documentElement.getElementsByTagName("ixf:uploadfilefield"); for (int i = fileFieldNodeList.getLength() - 1; i >= 0; i--) { Element element = (Element) fileFieldNodeList.item(i); Node replacement = handleFileField(ownerDocument, element); replaceElement(ownerDocument, element, replacement); } Element htmlElement = ownerDocument.createElement("html"); Element bodyElement = ownerDocument.createElement("body"); htmlElement.appendChild(bodyElement); NodeList childNodes = documentElement.getChildNodes(); for (int i = childNodes.getLength() - 1; i >= 0; i--) { if (bodyElement.getFirstChild() != null) { bodyElement.insertBefore(childNodes.item(i), bodyElement.getFirstChild()); } else { bodyElement.appendChild(childNodes.item(i)); } } contents.put("fi", DeusNexXmlUtils.serializeElement(htmlElement, true, false, "xml")); } catch (XPathExpressionException | TransformerException e) { throw new DeusNexInternalException("Internal Error occurred while processing document", e); } return contents; } public Map<String, String> parseContent(Element documentElement) throws DeusNexException { if ("styledocument".equals(documentElement.getTagName())) { return parseStyleDocument(documentElement); } if (!"document".equals(documentElement.getTagName())) { throw new DeusNexSyntaxException("Invalid content document"); } Map<String, String> contents = new HashMap<>(); Document ownerDocument = documentElement.getOwnerDocument(); try { List<Element> localeDocuments = DeusNexXmlUtils.getElementsByXPath(documentElement, "fckdocument"); for (Element localeDocument : localeDocuments) { String lang = localeDocument.getAttribute("lang"); if (StringUtils.isBlank(lang)) { throw new DeusNexSyntaxException("Locale document does not specify lang"); } // TODO: Sometimes document and fckdocument nodes are duplicated, when // and why? NodeList embeddedNodeList = documentElement.getElementsByTagName("ix:embedded"); for (int i = embeddedNodeList.getLength() - 1; i >= 0; i--) { Element embeddedElement = (Element) embeddedNodeList.item(i); Node replacement = handleEmbedded(ownerDocument, embeddedElement); replaceElement(ownerDocument, embeddedElement, replacement); } NodeList ixImageNodeList = documentElement.getElementsByTagName("ix:image"); for (int i = ixImageNodeList.getLength() - 1; i >= 0; i--) { Element ixImageElement = (Element) ixImageNodeList.item(i); Node replacement = handleIxImage(ownerDocument, ixImageElement); replaceElement(ownerDocument, ixImageElement, replacement); } NodeList embeddedItemNodeList = localeDocument.getElementsByTagName("ix:embeddeditem"); for (int i = embeddedItemNodeList.getLength() - 1; i >= 0; i--) { Element embeddedItemElement = (Element) embeddedItemNodeList.item(i); Node replacement = handleEmbeddedItem(ownerDocument, embeddedItemElement); replaceElement(ownerDocument, embeddedItemElement, replacement); } NodeList textFieldNodeList = localeDocument.getElementsByTagName("ixf:textfield"); for (int i = textFieldNodeList.getLength() - 1; i >= 0; i--) { Element element = (Element) textFieldNodeList.item(i); Node replacement = handleTextField(ownerDocument, element); replaceElement(ownerDocument, element, replacement); } NodeList memoFieldNodeList = localeDocument.getElementsByTagName("ixf:memofield"); for (int i = memoFieldNodeList.getLength() - 1; i >= 0; i--) { Element element = (Element) memoFieldNodeList.item(i); Node replacement = handleMemoField(ownerDocument, element); replaceElement(ownerDocument, element, replacement); } NodeList optionListNodeList = localeDocument.getElementsByTagName("ixf:optionlist"); for (int i = optionListNodeList.getLength() - 1; i >= 0; i--) { Element element = (Element) optionListNodeList.item(i); Node replacement = handleOptionListField(ownerDocument, element); replaceElement(ownerDocument, element, replacement); } NodeList connectFieldNodeList = localeDocument.getElementsByTagName("ixf:connectfield"); for (int i = connectFieldNodeList.getLength() - 1; i >= 0; i--) { Element element = (Element) connectFieldNodeList.item(i); Node replacement = handleConnectField(ownerDocument, element); replaceElement(ownerDocument, element, replacement); } // ixf:uploadfilefield NodeList fileFieldNodeList = localeDocument.getElementsByTagName("ixf:uploadfilefield"); for (int i = fileFieldNodeList.getLength() - 1; i >= 0; i--) { Element element = (Element) fileFieldNodeList.item(i); Node replacement = handleFileField(ownerDocument, element); replaceElement(ownerDocument, element, replacement); } Element htmlElement = ownerDocument.createElement("html"); Element bodyElement = ownerDocument.createElement("body"); htmlElement.appendChild(bodyElement); NodeList childNodes = localeDocument.getChildNodes(); for (int i = childNodes.getLength() - 1; i >= 0; i--) { if (bodyElement.getFirstChild() != null) { bodyElement.insertBefore(childNodes.item(i), bodyElement.getFirstChild()); } else { bodyElement.appendChild(childNodes.item(i)); } } contents.put(lang, DeusNexXmlUtils.serializeElement(htmlElement, true, false, "xml")); } } catch (XPathExpressionException | TransformerException e) { throw new DeusNexInternalException("Internal Error occurred while processing document", e); } return contents; } private void replaceElement(Document ownerDocument, Element element, Node replacement) throws XPathExpressionException, DeusNexInternalException { Node nextSibling = element.getNextSibling(); Node parent = element.getParentNode(); parent.removeChild(element); if (replacement != null) { if (nextSibling != null) { parent.insertBefore(replacement, nextSibling); } else { parent.appendChild(replacement); } } } private Node handleEmbeddedItem(Document ownerDocument, Element embeddedItemElement) throws XPathExpressionException, DeusNexInternalException { String type = DeusNexXmlUtils.getChildValue(embeddedItemElement, "type"); // String id = embeddedItemElement.getAttribute("id"); switch (type) { case "image": return handleEmbeddedItemImage(ownerDocument, embeddedItemElement); case "document": return handleEmbeddedItemDocument(ownerDocument, embeddedItemElement); case "audio": return handleEmbeddedItemAudio(ownerDocument, embeddedItemElement); case "hyperlink": return handleEmbeddedItemHyperLink(ownerDocument, embeddedItemElement); case "flash": return handleEmbedded(ownerDocument, embeddedItemElement); } throw new DeusNexInternalException("Unknown ix:embeddeditem type '" + type + "'"); } private Node handleEmbedded(Document ownerDocument, Element embeddedElement) throws XPathExpressionException, DeusNexInternalException { Integer resRef = DeusNexXmlUtils.getChildValueInteger(embeddedElement, "res_ref"); Boolean functionalRef = DeusNexXmlUtils.getChildValueBoolean(embeddedElement, "functionalRef"); Boolean visible = DeusNexXmlUtils.getChildValueBoolean(embeddedElement, "visible"); Boolean autoStart = DeusNexXmlUtils.getChildValueBoolean(embeddedElement, "autoStart"); Integer width = DeusNexXmlUtils.getChildValueInteger(embeddedElement, "width"); Integer height = DeusNexXmlUtils.getChildValueInteger(embeddedElement, "height"); Boolean showAsLink = DeusNexXmlUtils.getChildValueBoolean(embeddedElement, "showAsLink"); Boolean showControls = DeusNexXmlUtils.getChildValueBoolean(embeddedElement, "showControls"); Boolean loop = DeusNexXmlUtils.getChildValueBoolean(embeddedElement, "loop"); Integer queryType = DeusNexXmlUtils.getChildValueInteger(embeddedElement, "queryType"); if (embeddedItemElementHandler != null) { return embeddedItemElementHandler.handleEmbedded(ownerDocument, resRef, functionalRef, visible, autoStart, width, height, showAsLink, showControls, loop, queryType); } else { return null; } } private Node handleIxImage(Document ownerDocument, Element ixImageElement) throws XPathExpressionException { Attr resRefAttr = ixImageElement.getAttributeNode("res_ref"); if (resRefAttr == null) { return null; } Attr widthAttr = ixImageElement.getAttributeNode("width"); Attr heightAttr = ixImageElement.getAttributeNode("height"); Integer resRef = NumberUtils.createInteger(resRefAttr.getValue()); Integer width = widthAttr == null ? null : NumberUtils.createInteger(widthAttr.getValue()); Integer height = heightAttr == null ? null : NumberUtils.createInteger(heightAttr.getValue()); if (embeddedItemElementHandler != null) { return embeddedItemElementHandler.handleEmbeddedImage(ownerDocument, null, null, width, height, 0, null, resRef); } else { return null; } } private Node handleEmbeddedItemHyperLink(Document ownerDocument, Element embeddedItemElement) throws XPathExpressionException { Integer resourceNo = DeusNexXmlUtils.getChildValueInteger(embeddedItemElement, "parameters/resourceno"); String fileName = DeusNexXmlUtils.getChildValue(embeddedItemElement, "parameters/filename"); String linkText = DeusNexXmlUtils.getChildValue(embeddedItemElement, "parameters/linktext"); String target = DeusNexXmlUtils.getChildValue(embeddedItemElement, "parameters/target"); if (embeddedItemElementHandler != null) { return embeddedItemElementHandler.handleEmbeddedHyperlink(ownerDocument, resourceNo, target, fileName, linkText); } else { return null; } } private Node handleEmbeddedItemAudio(Document ownerDocument, Element embeddedItemElement) throws XPathExpressionException { Integer resourceNo = DeusNexXmlUtils.getChildValueInteger(embeddedItemElement, "parameters/resourceno"); Boolean showAsLink = "1".equals(DeusNexXmlUtils.getChildValue(embeddedItemElement, "parameters/showaslink")); String fileName = DeusNexXmlUtils.getChildValue(embeddedItemElement, "parameters/filename"); String linkText = DeusNexXmlUtils.getChildValue(embeddedItemElement, "parameters/linktext"); Boolean autoStart = "1".equals(DeusNexXmlUtils.getChildValue(embeddedItemElement, "parameters/autostart")); Boolean loop = "1".equals(DeusNexXmlUtils.getChildValue(embeddedItemElement, "parameters/loop")); if (embeddedItemElementHandler != null) { return embeddedItemElementHandler.handleEmbeddedAudio(ownerDocument, resourceNo, showAsLink, fileName, linkText, autoStart, loop); } else { return null; } } private Node handleEmbeddedItemDocument(Document ownerDocument, Element embeddedItemElement) throws XPathExpressionException { String title = DeusNexXmlUtils.getChildValue(embeddedItemElement, "parameters/title"); Integer queryType = DeusNexXmlUtils.getChildValueInteger(embeddedItemElement, "parameters/querytype"); Integer resourceNo = DeusNexXmlUtils.getChildValueInteger(embeddedItemElement, "parameters/resourceno"); Integer embeddedResourceNo = DeusNexXmlUtils.getChildValueInteger(embeddedItemElement, "parameters/embeddedresourceno"); if (embeddedItemElementHandler != null) { return embeddedItemElementHandler.handleEmbeddedDocument(ownerDocument, title, queryType, resourceNo, embeddedResourceNo); } else { return null; } } private Node handleEmbeddedItemImage(Document ownerDocument, Element embeddedItemElement) throws XPathExpressionException { String title = DeusNexXmlUtils.getChildValue(embeddedItemElement, "parameters/title"); String alt = DeusNexXmlUtils.getChildValue(embeddedItemElement, "parameters/alt"); Integer width = DeusNexXmlUtils.getChildValueInteger(embeddedItemElement, "parameters/width"); Integer height = DeusNexXmlUtils.getChildValueInteger(embeddedItemElement, "parameters/height"); Integer hspace = DeusNexXmlUtils.getChildValueInteger(embeddedItemElement, "parameters/hspace"); String align = DeusNexXmlUtils.getChildValue(embeddedItemElement, "parameters/align"); Integer resourceno = DeusNexXmlUtils.getChildValueInteger(embeddedItemElement, "parameters/resourceno"); if (embeddedItemElementHandler != null) { return embeddedItemElementHandler.handleEmbeddedImage(ownerDocument, title, alt, width, height, hspace, align, resourceno); } else { return null; } } private Node handleOptionListField(Document ownerDocument, Element fieldElement) throws XPathExpressionException, DeusNexException { String paramName = DeusNexXmlUtils.getChildValue(fieldElement, "paramname"); String type = DeusNexXmlUtils.getChildValue(fieldElement, "type"); if ("checklist".equals(type)) { return handleCheckListField(ownerDocument, fieldElement, paramName); } else { return handleSingleOptionListField(ownerDocument, fieldElement, paramName, type); } } private Node handleCheckListField(Document ownerDocument, Element fieldElement, String paramName) throws XPathExpressionException { List<MultiSelectFieldOptionMeta> options = new ArrayList<>(); List<Element> optionElements = DeusNexXmlUtils.getElementsByXPath(fieldElement, "option"); for (Element optionElement : optionElements) { String optionName = optionElement.getAttribute("name"); String optionText = optionElement.getTextContent(); // Points to correct conversion String pointsStr = optionElement.getAttribute("points"); Boolean correct = StringUtils.isNotBlank(pointsStr) ? NumberUtils.createDouble(pointsStr) > 0 : Boolean.FALSE; options.add(new MultiSelectFieldOptionMeta(optionName, optionText, correct)); } if (fieldElementHandler != null) { return fieldElementHandler.handleChecklistField(ownerDocument, paramName, options, helpOf(fieldElement), hintOf(fieldElement)); } return null; } private Node handleSingleOptionListField(Document ownerDocument, Element fieldElement, String paramName, String type) throws XPathExpressionException, DeusNexException { Integer size = DeusNexXmlUtils.getChildValueInteger(fieldElement, "fieldsvisible"); List<OptionListOption> options = new ArrayList<>(); List<Element> optionElements = DeusNexXmlUtils.getElementsByXPath(fieldElement, "option"); for (Element optionElement : optionElements) { String optionName = optionElement.getAttribute("name"); String pointsStr = optionElement.getAttribute("points"); Double optionPoints = StringUtils.isNotBlank(pointsStr) ? NumberUtils.createDouble(pointsStr) : null; String optionText = optionElement.getTextContent(); options.add(new OptionListOption(optionName, optionPoints, optionText)); } if (fieldElementHandler != null) { return fieldElementHandler.handleOptionList(ownerDocument, paramName, type, options, size, helpOf(fieldElement), hintOf(fieldElement)); } return null; } private String hintOf(Element fieldElement) throws XPathExpressionException { List<Element> hintElements = DeusNexXmlUtils.getElementsByXPath(fieldElement, "hint"); String hint = ""; if (!hintElements.isEmpty()) { hint = hintElements.get(0).getTextContent(); } return hint; } private String helpOf(Element fieldElement) throws XPathExpressionException { List<Element> helpElements = DeusNexXmlUtils.getElementsByXPath(fieldElement, "help"); String help = ""; if (!helpElements.isEmpty()) { help = helpElements.get(0).getTextContent(); } return help; } private Node handleTextField(Document ownerDocument, Element fieldElement) throws XPathExpressionException, DeusNexException { String paramName = DeusNexXmlUtils.getChildValue(fieldElement, "paramname"); Integer columns = DeusNexXmlUtils.getChildValueInteger(fieldElement, "columns"); List<RightAnswer> rightAnswers = new ArrayList<>(); List<Element> answerElements = DeusNexXmlUtils.getElementsByXPath(fieldElement, "rightanswers/answer"); for (Element answerElement : answerElements) { String answerText = DeusNexXmlUtils.getChildValue(answerElement, "text"); Double answerPoints = DeusNexXmlUtils.getChildValueDouble(answerElement, "points"); rightAnswers.add(new RightAnswer(answerPoints, answerText)); } if (fieldElementHandler != null) { return fieldElementHandler.handleTextField(ownerDocument, paramName, columns, Boolean.TRUE, rightAnswers, helpOf(fieldElement), hintOf(fieldElement)); } return null; } private Node handleMemoField(Document ownerDocument, Element embeddedItemElement) throws XPathExpressionException, DeusNexException { String paramName = DeusNexXmlUtils.getChildValue(embeddedItemElement, "paramname"); Integer columns = DeusNexXmlUtils.getChildValueInteger(embeddedItemElement, "columns"); Integer rows = DeusNexXmlUtils.getChildValueInteger(embeddedItemElement, "rows"); String help = DeusNexXmlUtils.getChildValue(embeddedItemElement, "help"); String hint = DeusNexXmlUtils.getChildValue(embeddedItemElement, "hint"); if (fieldElementHandler != null) { return fieldElementHandler.handleMemoField(ownerDocument, paramName, columns, rows, help, hint); } return null; } private Node handleFileField(Document ownerDocument, Element embeddedItemElement) throws XPathExpressionException, DeusNexException { String paramName = DeusNexXmlUtils.getChildValue(embeddedItemElement, "paramname"); String help = DeusNexXmlUtils.getChildValue(embeddedItemElement, "help"); String hint = DeusNexXmlUtils.getChildValue(embeddedItemElement, "hint"); if (fieldElementHandler != null) { return fieldElementHandler.handleFileField(ownerDocument, paramName, help, hint); } return null; } private Node handleConnectField(Document ownerDocument, Element fieldElement) throws XPathExpressionException, DeusNexException { String paramName = DeusNexXmlUtils.getChildValue(fieldElement, "paramname"); List<ConnectFieldOption> options = new ArrayList<>(); List<Element> optionElements = DeusNexXmlUtils.getElementsByXPath(fieldElement, "option"); for (Element answerElement : optionElements) { String optionAnswer = answerElement.getAttribute("answer"); String optionEquivalent = answerElement.getAttribute("equivalent"); String optionTerm = answerElement.getAttribute("term"); String optionPointsStr = answerElement.getAttribute("points"); Double optionPoints = StringUtils.isNotBlank(optionPointsStr) ? NumberUtils.createDouble(optionPointsStr) : null; options.add(new ConnectFieldOption(optionAnswer, optionEquivalent, optionTerm, optionPoints)); } if (fieldElementHandler != null) { return fieldElementHandler.handleConnectField(ownerDocument, paramName, options, helpOf(fieldElement), hintOf(fieldElement)); } return null; } private DeusNexEmbeddedItemElementHandler embeddedItemElementHandler; private DeusNexFieldElementHandler fieldElementHandler; }