diff --git a/src/main/java/org/olat/core/gui/control/generic/iframe/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/gui/control/generic/iframe/_i18n/LocalStrings_de.properties index ee28d32b19988ec234cf349d566bae00ddfee1f8..d75cdc3cbdd699ae4c6c7db8e76c6ae54fd33dad 100644 --- a/src/main/java/org/olat/core/gui/control/generic/iframe/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/core/gui/control/generic/iframe/_i18n/LocalStrings_de.properties @@ -21,7 +21,7 @@ encoding.same=Gleich wie Inhalt height.auto=Automatisch height.label=H\u00F6he Anzeigefl\u00E4che inherit.label=Standardwerte übernehmen -inherit=Aus Lernressourcenverwaltung übernehmen +inherit=Aus Layouteinstellungen der Lernressource \u00FCbernehmen custom=Anpassen glossary.need.jQuery=Glossar braucht jQuery automatic.need.js=Automatic braucht javascript diff --git a/src/main/java/org/olat/core/gui/control/generic/iframe/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/gui/control/generic/iframe/_i18n/LocalStrings_en.properties index f7fc02748a7945bb1fead76b796f555b787675fb..c7faf0818746b42443eb2dff3cbdbc9a9422f6d0 100644 --- a/src/main/java/org/olat/core/gui/control/generic/iframe/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/core/gui/control/generic/iframe/_i18n/LocalStrings_en.properties @@ -21,7 +21,7 @@ automatic.need.js=Automatic needs javascript height.auto=Automatic height.label=Display height iframe.content=Page content within iframe -inherit=Use from learning resource configuration +inherit=Use from learning resource layout configuration inherit.label=Use standard configuration mode=Display mode mode.configured=Optimized for OpenOLAT diff --git a/src/main/java/org/olat/ims/qti/qpool/ItemFileResourceValidator.java b/src/main/java/org/olat/ims/qti/qpool/ItemFileResourceValidator.java index 74e09c085289564caab668ed823d534d503a80c1..d156d3217abde345e12c6f751266bfa01024b000 100644 --- a/src/main/java/org/olat/ims/qti/qpool/ItemFileResourceValidator.java +++ b/src/main/java/org/olat/ims/qti/qpool/ItemFileResourceValidator.java @@ -31,6 +31,10 @@ import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.apache.commons.io.IOUtils; +import org.dom4j.Document; +import org.dom4j.DocumentType; +import org.dom4j.io.SAXReader; +import org.dom4j.io.SAXValidator; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.vfs.VFSLeaf; @@ -38,7 +42,6 @@ import org.olat.ims.resources.IMSEntityResolver; import org.olat.search.service.document.file.utils.ShieldInputStream; import org.xml.sax.Attributes; import org.xml.sax.ErrorHandler; -import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; @@ -114,6 +117,30 @@ public class ItemFileResourceValidator { } private boolean validateXml(InputStream in) { + boolean valid = false; + Document doc = readDocument(in); + if(doc != null) { + DocumentType docType = doc.getDocType(); + if(docType == null) { + doc.addDocType("questestinterop", null, "ims_qtiasiv1p2p1.dtd"); + } + valid = validateDocument(doc); + } + return valid; + } + + private Document readDocument(InputStream in) { + try { + SAXReader reader = new SAXReader(); + reader.setEntityResolver(new IMSEntityResolver()); + reader.setValidation(false); + return reader.read(in, ""); + } catch (Exception e) { + return null; + } + } + + private boolean validateDocument(Document in) { try { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setValidating(true); @@ -127,7 +154,10 @@ public class ItemFileResourceValidator { reader.setEntityResolver(new IMSEntityResolver()); reader.setErrorHandler(errorHandler); reader.setContentHandler(contentHandler); - reader.parse(new InputSource(in)); + + SAXValidator validator = new SAXValidator(reader); + validator.validate(in); + return errorHandler.isValid() && contentHandler.isItem(); } catch (ParserConfigurationException e) { return false; @@ -176,7 +206,7 @@ public class ItemFileResourceValidator { if("questestinterop".equals(qName)) { interop = true; - } else if("item".equals(localName)) { + } else if("item".equals(localName) || "item".equals(qName)) { if(interop) { item = true; } diff --git a/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java b/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java index 4f9e934a59ad62405ee22525377c9a90715aece6..6106024213e9cc9bad9dc4ad69545c1e79a83faa 100644 --- a/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java +++ b/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java @@ -636,11 +636,12 @@ class QTIImportProcessor { } private boolean processSidecarMetadata(QuestionItemImpl item, DocInfos docInfos) { + InputStream metadataIn = null; try { Path path = docInfos.root; - if(path != null) { + if(path != null && path.getFileName() != null) { Path metadata = path.resolve(path.getFileName().toString() + "_metadata.xml"); - InputStream metadataIn = Files.newInputStream(metadata); + metadataIn = Files.newInputStream(metadata); SAXReader reader = new SAXReader(); Document document = reader.read(metadataIn); Element rootElement = document.getRootElement(); @@ -654,6 +655,8 @@ class QTIImportProcessor { } catch (Exception e) { log.error("", e); return false; + } finally { + IOUtils.closeQuietly(metadataIn); } } diff --git a/src/test/java/org/olat/ims/qti/qpool/ItemFileResourceValidatorTest.java b/src/test/java/org/olat/ims/qti/qpool/ItemFileResourceValidatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..77bd7cd1b0deb65f04916dbf8f187c6459f46331 --- /dev/null +++ b/src/test/java/org/olat/ims/qti/qpool/ItemFileResourceValidatorTest.java @@ -0,0 +1,55 @@ +package org.olat.ims.qti.qpool; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; + +import junit.framework.Assert; + +import org.junit.Test; + +/** + * + * Initial date: 20.11.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ItemFileResourceValidatorTest { + + @Test + public void validate() throws URISyntaxException { + URL itemUrl = ItemFileResourceValidatorTest.class.getResource("fibi_i_001.xml"); + Assert.assertNotNull(itemUrl); + File itemFile = new File(itemUrl.toURI()); + + ItemFileResourceValidator validator = new ItemFileResourceValidator(); + boolean validate = validator.validate("fibi_i_001.xml", itemFile); + Assert.assertTrue(validate); + } + + @Test + public void validate_missingDoctype() throws URISyntaxException { + URL itemUrl = ItemFileResourceValidatorTest.class.getResource("oo_item_without_doctype.xml"); + Assert.assertNotNull(itemUrl); + File itemFile = new File(itemUrl.toURI()); + + ItemFileResourceValidator validator = new ItemFileResourceValidator(); + boolean validate = validator.validate("oo_item_without_doctype.xml", itemFile); + Assert.assertTrue(validate); + } + + @Test + public void validate_missingDoctype_invalid() throws URISyntaxException { + URL itemUrl = ItemFileResourceValidatorTest.class.getResource("oo_item_without_doctype_invalid.xml"); + Assert.assertNotNull(itemUrl); + File itemFile = new File(itemUrl.toURI()); + + ItemFileResourceValidator validator = new ItemFileResourceValidator(); + boolean validate = validator.validate("oo_item_without_doctype_invalid.xml", itemFile); + Assert.assertFalse(validate); + } + + + + +} diff --git a/src/test/java/org/olat/ims/qti/qpool/oo_item_without_doctype.xml b/src/test/java/org/olat/ims/qti/qpool/oo_item_without_doctype.xml new file mode 100644 index 0000000000000000000000000000000000000000..3353d8f1289c5511a1fc69ed20265521c5baa905 --- /dev/null +++ b/src/test/java/org/olat/ims/qti/qpool/oo_item_without_doctype.xml @@ -0,0 +1,49 @@ +<questestinterop> + <item ident="QTIEDIT:MCQ:1000002286" title="New question"> + <presentation label="New question"> + <material> + <mattext texttype="text/html"><![CDATA[New question]]></mattext> + </material> + <response_lid ident="1000002288" rcardinality="Multiple" rtiming="No"> + <render_choice shuffle="No" minnumber="0" maxnumber="1"> + <flow_label class="List"> + <response_label ident="1000002291" rshuffle="Yes"> + <material> + <mattext texttype="text/html"><![CDATA[New answer]]></mattext> + </material> + </response_label> + </flow_label> + </render_choice> + </response_lid> + </presentation> + <resprocessing> + <outcomes> + <decvar varname="SCORE" vartype="Decimal" defaultval="0" minvalue="0.0" maxvalue="1.0" cutvalue="1.0"/> + </outcomes> + <respcondition title="Mastery" continue="Yes"> + <conditionvar> + <and> + <varequal respident="1000002288" case="Yes">1000002291</varequal> + </and> + </conditionvar> + <setvar varname="SCORE" action="Set">1.0</setvar> + <displayfeedback feedbacktype="Response" linkrefid="Mastery"/> + </respcondition> + <respcondition title="_olat_resp_feedback" continue="Yes"> + <conditionvar> + <varequal respident="1000002288" case="Yes">1000002291</varequal> + </conditionvar> + <displayfeedback feedbacktype="Response" linkrefid="1000002291"/> + </respcondition> + <respcondition title="Fail" continue="Yes"> + <conditionvar> + <other/> + </conditionvar> + <setvar varname="SCORE" action="Set">0</setvar> + <displayfeedback feedbacktype="Response" linkrefid="Fail"/> + <displayfeedback feedbacktype="Solution" linkrefid="Solution"/> + <displayfeedback feedbacktype="Hint" linkrefid="Hint"/> + </respcondition> + </resprocessing> + </item> +</questestinterop> diff --git a/src/test/java/org/olat/ims/qti/qpool/oo_item_without_doctype_invalid.xml b/src/test/java/org/olat/ims/qti/qpool/oo_item_without_doctype_invalid.xml new file mode 100644 index 0000000000000000000000000000000000000000..451de66caa59d30b745bab1acd2b7228d76eacc0 --- /dev/null +++ b/src/test/java/org/olat/ims/qti/qpool/oo_item_without_doctype_invalid.xml @@ -0,0 +1,49 @@ +<questestinterop> + <itemop ident="QTIEDIT:MCQ:1000002286" title="New question"> + <presentation label="New question"> + <material> + <mattext texttype="text/html"><![CDATA[New question]]></mattext> + </material> + <response_lid ident="1000002288" rcardinality="Multiple" rtiming="No"> + <render_choice shuffle="No" minnumber="0" maxnumber="1"> + <flow_label class="List"> + <response_label ident="1000002291" rshuffle="Yes"> + <material> + <mattext texttype="text/html"><![CDATA[New answer]]></mattext> + </material> + </response_label> + </flow_label> + </render_choice> + </response_lid> + </presentation> + <resprocessing> + <outcomes> + <decvar varname="SCORE" vartype="Decimal" defaultval="0" minvalue="0.0" maxvalue="1.0" cutvalue="1.0"/> + </outcomes> + <respcondition title="Mastery" continue="Yes"> + <conditionvar> + <and> + <varequal respident="1000002288" case="Yes">1000002291</varequal> + </and> + </conditionvar> + <setvar varname="SCORE" action="Set">1.0</setvar> + <displayfeedback feedbacktype="Response" linkrefid="Mastery"/> + </respcondition> + <respcondition title="_olat_resp_feedback" continue="Yes"> + <conditionvar> + <varequal respident="1000002288" case="Yes">1000002291</varequal> + </conditionvar> + <displayfeedback feedbacktype="Response" linkrefid="1000002291"/> + </respcondition> + <respcondition title="Fail" continue="Yes"> + <conditionvar> + <other/> + </conditionvar> + <setvar varname="SCORE" action="Set">0</setvar> + <displayfeedback feedbacktype="Response" linkrefid="Fail"/> + <displayfeedback feedbacktype="Solution" linkrefid="Solution"/> + <displayfeedback feedbacktype="Hint" linkrefid="Hint"/> + </respcondition> + </resprocessing> + </itemop> +</questestinterop> diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index fd37817518dde7ce9048b010e8c207f8aafaa916..a5461ddced0f68d026b6d9485d7dbea646c46848 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -145,6 +145,7 @@ import org.junit.runners.Suite; org.olat.ims.qti.QTIResultManagerTest.class, org.olat.ims.qti.qpool.QTIImportProcessorTest.class, org.olat.ims.qti.qpool.QTIExportProcessorTest.class, + org.olat.ims.qti.qpool.ItemFileResourceValidatorTest.class, org.olat.ims.qti.questionimport.CSVToQuestionConverterTest.class, org.olat.ims.qti.statistics.manager.QTIStatisticsManagerLargeTest.class, org.olat.ims.qti.statistics.manager.QTIStatisticsManagerTest.class,