Index: lams_build/3rdParty.userlibraries =================================================================== diff -u -rbdadb88084e9f69dce20ea8d0094121174832db2 -r7060fa5b595fa324e138924099faf98e52cb5ca1 --- lams_build/3rdParty.userlibraries (.../3rdParty.userlibraries) (revision bdadb88084e9f69dce20ea8d0094121174832db2) +++ lams_build/3rdParty.userlibraries (.../3rdParty.userlibraries) (revision 7060fa5b595fa324e138924099faf98e52cb5ca1) @@ -5,7 +5,10 @@ + + + Index: lams_build/build.xml =================================================================== diff -u -r49dd0e4536336f2f5250264924badee6329cef1d -r7060fa5b595fa324e138924099faf98e52cb5ca1 --- lams_build/build.xml (.../build.xml) (revision 49dd0e4536336f2f5250264924badee6329cef1d) +++ lams_build/build.xml (.../build.xml) (revision 7060fa5b595fa324e138924099faf98e52cb5ca1) @@ -389,7 +389,6 @@ - ${ant.project.name}: Deploying libraries as WildFly modules @@ -410,6 +409,14 @@ + + + + + + + Index: lams_build/conf/j2ee/jboss-deployment-structure.xml =================================================================== diff -u -rb6d0779192887979b5d3d244dec3506d12a7e401 -r7060fa5b595fa324e138924099faf98e52cb5ca1 --- lams_build/conf/j2ee/jboss-deployment-structure.xml (.../jboss-deployment-structure.xml) (revision b6d0779192887979b5d3d244dec3506d12a7e401) +++ lams_build/conf/j2ee/jboss-deployment-structure.xml (.../jboss-deployment-structure.xml) (revision 7060fa5b595fa324e138924099faf98e52cb5ca1) @@ -19,6 +19,7 @@ + Index: lams_build/lib/apache-poi/poi.module.xml =================================================================== diff -u -ra7136940932826c124abfb4a8e39a412a82fe1a6 -r7060fa5b595fa324e138924099faf98e52cb5ca1 --- lams_build/lib/apache-poi/poi.module.xml (.../poi.module.xml) (revision a7136940932826c124abfb4a8e39a412a82fe1a6) +++ lams_build/lib/apache-poi/poi.module.xml (.../poi.module.xml) (revision 7060fa5b595fa324e138924099faf98e52cb5ca1) @@ -27,6 +27,7 @@ + Index: lams_build/liblist.txt =================================================================== diff -u -rc42f68a09110f060149fba432df9ff60ccc84751 -r7060fa5b595fa324e138924099faf98e52cb5ca1 --- lams_build/liblist.txt (.../liblist.txt) (revision c42f68a09110f060149fba432df9ff60ccc84751) +++ lams_build/liblist.txt (.../liblist.txt) (revision 7060fa5b595fa324e138924099faf98e52cb5ca1) @@ -5,8 +5,12 @@ apache-poi poi-4.0.1.jar 4.0.1 Apache License 2.0 Apache the Java API for Microsoft Documents poi-ooxml-4.0.1.jar poi-ooxml-schemas-4.0.1.jar + poi-scratchpad-4.0.1.jar xmlbeans-3.0.2.jar 3.0.2 +apache-tika tika-core-1.24.jar 1.24 Apache License 2.0 Apache a content analysis toolkit that detects and extracts metadata and text from over a thousand different file types + tika-parsers-1.24.jar + autopatch autopatch-1.4.2-lams.jar 1.4.2-lams Apache License Tacit Knowledge automated Java patching system discovery-1.0.5-lams.jar 1.0.5-lams Index: lams_central/conf/language/lams/ApplicationResources_en_AU.properties =================================================================== diff -u -r625a1082501027cf4b35af4cebcf34178d0bec67 -r7060fa5b595fa324e138924099faf98e52cb5ca1 --- lams_central/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 625a1082501027cf4b35af4cebcf34178d0bec67) +++ lams_central/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 7060fa5b595fa324e138924099faf98e52cb5ca1) @@ -321,6 +321,7 @@ label.tab.conditions.timelimit.individual = Is this time limit for each individual? label.tab.conditions.enable = Enable label.questions.file.title = Choose IMS QTI file +label.choose.word.document =Choose Word file label.questions.file.missing = Please select a ZIP or XML file with questions in IMS QTIformat. label.questions.choice.title = Choose questions label.questions.choice.select.all = Select all @@ -915,6 +916,7 @@ label.search.question.bank = Search question bank label.question.type = Type: label.question.successfully.imported = Question successfully imported +label.import.word =Import questions from Word document label.import.qti = Import questions in IMS QTI format label.export.qti = Export questions in IMS QTI format label.import.xml = Import questions in XML format @@ -978,6 +980,7 @@ label.qb.collection.remove = Remove collection label.qb.collection.xml = XML label.qb.collection.qti = QTI +label.qb.collection.word =Word label.qb.collection.questions.none = There are no questions in this collection label.qb.collection.share.title = Share collection with courses label.qb.collection.shared = Shared Index: lams_central/src/java/org/lamsfoundation/lams/web/QuestionsController.java =================================================================== diff -u -rf92b1383638a766c5d8496c6f607322dd2af8329 -r7060fa5b595fa324e138924099faf98e52cb5ca1 --- lams_central/src/java/org/lamsfoundation/lams/web/QuestionsController.java (.../QuestionsController.java) (revision f92b1383638a766c5d8496c6f607322dd2af8329) +++ lams_central/src/java/org/lamsfoundation/lams/web/QuestionsController.java (.../QuestionsController.java) (revision 7060fa5b595fa324e138924099faf98e52cb5ca1) @@ -14,6 +14,7 @@ import org.lamsfoundation.lams.qb.service.IQbService; import org.lamsfoundation.lams.questions.Question; import org.lamsfoundation.lams.questions.QuestionParser; +import org.lamsfoundation.lams.questions.QuestionWordParser; import org.lamsfoundation.lams.usermanagement.dto.UserDTO; import org.lamsfoundation.lams.util.Configuration; import org.lamsfoundation.lams.util.ConfigurationKeys; @@ -30,7 +31,8 @@ import org.springframework.web.bind.annotation.RequestParam; /** - * Runs extraction of chosen IMS QTI zip file and prepares form for user to manually choose interesting question. + * Runs extraction of the chosen IMS QTI zip file or .docx file. Prepares form for user to manually choose interesting + * question. */ @Controller public class QuestionsController { @@ -44,8 +46,9 @@ @RequestMapping("/questions") public String execute(@RequestParam String tmpFileUploadId, @RequestParam String returnURL, - @RequestParam("limitType") String limitTypeParam, @RequestParam String callerID, - @RequestParam(required = false) Boolean collectionChoice, HttpServletRequest request) throws Exception { + @RequestParam("limitType") String limitTypeParam, @RequestParam(required = false) String importType, + @RequestParam String callerID, @RequestParam(required = false) Boolean collectionChoice, + HttpServletRequest request) throws Exception { MultiValueMap errorMap = new LinkedMultiValueMap<>(); String packageName = null; @@ -71,13 +74,17 @@ // show only chosen types of questions request.setAttribute("limitType", limitTypeParam); + boolean isWordInput = "word".equals(importType); + request.setAttribute("importType", importType); + if (collectionChoice != null && collectionChoice) { // in the view a drop down with collections will be displayed request.setAttribute("collections", qbService.getUserCollections(QuestionsController.getUserId())); } // user did not choose a file - if (file == null || !(packageName.endsWith(".zip") || packageName.endsWith(".xml"))) { + if (file == null || (isWordInput ? !packageName.endsWith(".docx") + : !(packageName.endsWith(".zip") || packageName.endsWith(".xml")))) { errorMap.add("GLOBAL", messageService.getMessage("label.questions.file.missing")); } @@ -102,11 +109,18 @@ InputStream uploadedFileStream = new FileInputStream(file); - Question[] questions = packageName.endsWith(".xml") - ? QuestionParser.parseQTIFile(uploadedFileStream, null, limitType) - : QuestionParser.parseQTIPackage(uploadedFileStream, limitType); + Question[] questions; + if (packageName.endsWith(".xml")) { + questions = QuestionParser.parseQTIFile(uploadedFileStream, null, limitType); + + } else if (packageName.endsWith(".docx")) { + questions = QuestionWordParser.parseWordFile(uploadedFileStream, packageName); + + } else { + questions = QuestionParser.parseQTIPackage(uploadedFileStream, limitType); + } request.setAttribute("questions", questions); - + FileUtil.deleteTmpFileUploadDir(tmpFileUploadId); return "questions/questionChoice"; Index: lams_central/web/qb/collection.jsp =================================================================== diff -u -radd24141d43ff78942c251760e1614925db89464 -r7060fa5b595fa324e138924099faf98e52cb5ca1 --- lams_central/web/qb/collection.jsp (.../collection.jsp) (revision add24141d43ff78942c251760e1614925db89464) +++ lams_central/web/qb/collection.jsp (.../collection.jsp) (revision 7060fa5b595fa324e138924099faf98e52cb5ca1) @@ -370,6 +370,11 @@ }); } + function importWordQuestions(){ + window.open('questions/questionFile.jsp?importType=word', + 'QuestionFile','width=500,height=240,scrollbars=yes'); + } + function importQTI(){ window.open('questions/questionFile.jsp', 'QuestionFile','width=500,height=370,scrollbars=yes'); @@ -489,6 +494,16 @@ + +
+ + + + + + + +
+ Index: lams_common/src/java/org/lamsfoundation/lams/questions/Answer.java =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r7060fa5b595fa324e138924099faf98e52cb5ca1 --- lams_common/src/java/org/lamsfoundation/lams/questions/Answer.java (.../Answer.java) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_common/src/java/org/lamsfoundation/lams/questions/Answer.java (.../Answer.java) (revision 7060fa5b595fa324e138924099faf98e52cb5ca1) @@ -29,6 +29,7 @@ private String text; private String feedback; private Float score; + private int displayOrder = 1; public String getText() { return text; @@ -53,7 +54,15 @@ public void setScore(Float score) { this.score = score; } + + public int getDisplayOrder() { + return displayOrder; + } + public void setDisplayOrder(int displayOrder) { + this.displayOrder = displayOrder; + } + @Override public int hashCode() { return new HashCodeBuilder().append(text).toHashCode(); Index: lams_common/src/java/org/lamsfoundation/lams/questions/QuestionWordParser.java =================================================================== diff -u -rced545c66bb69bcc44131468672f09da3d34eeb3 -r7060fa5b595fa324e138924099faf98e52cb5ca1 --- lams_common/src/java/org/lamsfoundation/lams/questions/QuestionWordParser.java (.../QuestionWordParser.java) (revision ced545c66bb69bcc44131468672f09da3d34eeb3) +++ lams_common/src/java/org/lamsfoundation/lams/questions/QuestionWordParser.java (.../QuestionWordParser.java) (revision 7060fa5b595fa324e138924099faf98e52cb5ca1) @@ -16,6 +16,7 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; @@ -29,7 +30,9 @@ import org.apache.log4j.Logger; import org.apache.tika.exception.TikaException; import org.apache.tika.io.TikaInputStream; +import org.apache.tika.metadata.HttpHeaders; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.TikaMetadataKeys; import org.apache.tika.mime.MediaType; import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.Parser; @@ -56,9 +59,8 @@ */ public class QuestionWordParser { private static Logger log = Logger.getLogger(QuestionWordParser.class); - + private final static String QUESTION_BREAK = "{question}"; - private static final String CUSTOM_IMAGE_TAG_REGEX = "\\[IMAGE: .*?]"; /** * Extracts questions from IMS QTI zip file. @@ -71,7 +73,7 @@ OOXMLParser tikaParser = new OOXMLParser(); ByteArrayOutputStream out = new ByteArrayOutputStream(); - SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); + SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance(); TransformerHandler handler = factory.newTransformerHandler(); handler.getTransformer().setOutputProperty(OutputKeys.METHOD, "xml"); handler.getTransformer().setOutputProperty(OutputKeys.INDENT, "no"); @@ -86,15 +88,15 @@ String xml = new String(out.toByteArray(), "UTF-8"); //Tika has this bug of putting b and u tags in the wrong order xml = xml.replaceAll("", ""); - + //parse resulted xml DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = docBuilder.parse(new InputSource(new StringReader(xml))); DOMImplementationLS domImplLS = (DOMImplementationLS) doc.getImplementation(); LSSerializer serializer = domImplLS.createLSSerializer(); serializer.getDomConfig().setParameter("xml-declaration", false); - XPath xPath = XPathFactory.newInstance().newXPath(); - + XPath xPath = XPathFactory.newInstance().newXPath(); + //convert all tags into {IMAGE} for further processing by QuestionParser.processHTMLField(..) NodeList images = (NodeList) xPath.evaluate("//img[@src]", doc, XPathConstants.NODESET); // Set the node content @@ -104,29 +106,29 @@ Text updatedImageTag = doc.createTextNode("[IMAGE: " + src + "]"); image.getParentNode().insertBefore(updatedImageTag, image); image.getParentNode().removeChild(image); - } - - //get root-level

and - String expression = "/html/body/p|/html/body/table"; + } + + //get root-level

and

+ String expression = "/html/body/p|/html/body/table"; NodeList nodes = (NodeList) xPath.compile(expression).evaluate(doc, XPathConstants.NODESET); - + //go through all paragraphs and search for questions divided by QUESTION_BREAK List questions = new LinkedList<>(); int counter = 0; String line = QuestionWordParser.readNextLine(serializer, nodes, counter++); while (counter < nodes.getLength()) { - + //start processing question if (line.contains(QUESTION_BREAK)) { //read next line line = QuestionWordParser.readNextLine(serializer, nodes, counter++); - + //iterate through the next paragraphs until we meet QUESTION_BREAK or the end of file List questionParagraphs = new ArrayList<>(); while (!line.contains(QUESTION_BREAK) && counter < nodes.getLength()) { Node lineNode = nodes.item(counter - 1); questionParagraphs.add(lineNode); - + line = QuestionWordParser.readNextLine(serializer, nodes, counter++); } @@ -178,8 +180,8 @@ } } } - - //add question feedback that goes after all options and correct answer + + //add question feedback that goes after all options and correct answer } else { String feedback = question.getFeedback() == null ? formattedText : question.getFeedback() + formattedText; @@ -188,13 +190,9 @@ } else { if (StringUtils.isBlank(question.getTitle())) { - //remove "[IMAGE: ]" tags - String title = text.replaceAll(QuestionWordParser.CUSTOM_IMAGE_TAG_REGEX, ""); - //trim to 200 characters while preserving the last full word - title = title.replaceAll("(?<=.{200})\\b.*", "..."); - question.setTitle(title); + question.setTitle(formattedText); } - + //add question description that goes before all options String description = question.getText() == null ? formattedText : question.getText() + formattedText; @@ -211,23 +209,23 @@ //skip all lines before {question} line = QuestionWordParser.readNextLine(serializer, nodes, counter++); } - } - + } + return questions.toArray(Question.QUESTION_ARRAY_TYPE); } - + private static String readNextLine(LSSerializer serializer, NodeList nodes, int counter) { //check it's not the end of file if (counter >= nodes.getLength()) { return ""; } - + Node node = nodes.item(counter); String htmlText = serializer.writeToString(node); log.debug("Reading the next line from word document: " + htmlText); return htmlText; } - + // private static String parseUsingAutoDetect(String filename, TikaConfig tikaConfig, Metadata metadata) // throws Exception { // System.out.println("Handling using AutoDetectParser: [" + filename + "]"); @@ -272,7 +270,7 @@ // // return handler.toString(); // } - + /** * A nested Tika parser which extracts out any images as they come along. */ @@ -285,14 +283,14 @@ private TikaImageExtractingParser(String imgFolder) { // Our expected types - types = new HashSet(); + types = new HashSet<>(); types.add(MediaType.image("bmp")); types.add(MediaType.image("gif")); types.add(MediaType.image("jpg")); types.add(MediaType.image("jpeg")); types.add(MediaType.image("png")); types.add(MediaType.image("tiff")); - + this.imgFolder = imgFolder; } @@ -305,8 +303,8 @@ public void parse(InputStream stream, ContentHandler handler, Metadata metadata, ParseContext context) throws IOException, SAXException, TikaException { // Is it a supported image? - String filename = metadata.get(Metadata.RESOURCE_NAME_KEY); - String type = metadata.get(Metadata.CONTENT_TYPE); + String filename = metadata.get(TikaMetadataKeys.RESOURCE_NAME_KEY); + String type = metadata.get(HttpHeaders.CONTENT_TYPE); boolean accept = false; if (type != null) { @@ -325,8 +323,9 @@ } } - if (!accept) + if (!accept) { return; + } count++; @@ -372,7 +371,7 @@ } } super.startElement(uri, localName, qName, attrs); - + } else if ("table".equals(localName)) { AttributesImpl attributes; if (origAttrs instanceof AttributesImpl) { @@ -392,7 +391,7 @@ } if (!classAttributeMet) { attributes = new AttributesImpl(origAttrs); - attributes.addAttribute("", "class", "class", "CDATA", "table-striped"); + attributes.addAttribute("", "class", "class", "CDATA", "table-striped"); } super.startElement(uri, localName, qName, attributes); Index: lams_common/src/java/org/lamsfoundation/lams/util/zipfile/ZipFileUtil.java =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r7060fa5b595fa324e138924099faf98e52cb5ca1 --- lams_common/src/java/org/lamsfoundation/lams/util/zipfile/ZipFileUtil.java (.../ZipFileUtil.java) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_common/src/java/org/lamsfoundation/lams/util/zipfile/ZipFileUtil.java (.../ZipFileUtil.java) (revision 7060fa5b595fa324e138924099faf98e52cb5ca1) @@ -76,16 +76,15 @@ } /** - * Create a temporary directory in which the zip file contents will go. This method is protected (rather than - * private) so that it may be called by the junit tests for this class. + * Create a temporary directory in which the zip file contents will go. * * @param zipFileName * @return name of the new directory * @throws ZipFileUtilException * if the java io temp directory is not defined, or we are unable to calculate a unique name for the * expanded directory, or an IOException occurs. */ - protected static String prepareTempDirectory(String zipFileName) throws ZipFileUtilException { + public static String prepareTempDirectory(String zipFileName) throws ZipFileUtilException { int dotIndex = zipFileName.indexOf("."); String shortZipName = dotIndex > -1 ? zipFileName.substring(0, dotIndex) : zipFileName;