diff --git a/src/main/java/org/olat/fileresource/types/FileResource.java b/src/main/java/org/olat/fileresource/types/FileResource.java index aecac21735826a95b2fe019b2722924161812be6..a1d3f8dbcd8b794495732f57f7297455937c9772 100644 --- a/src/main/java/org/olat/fileresource/types/FileResource.java +++ b/src/main/java/org/olat/fileresource/types/FileResource.java @@ -27,6 +27,8 @@ package org.olat.fileresource.types; import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.Files; @@ -34,6 +36,11 @@ import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.spi.FileSystemProvider; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.OLog; @@ -126,6 +133,63 @@ public class FileResource implements OLATResourceable { return fPath; } + public static Path getResource(File file, String filename, String fallbackEncoding) + throws IOException { + if(!StringHelper.containsNonWhitespace(filename)) { + filename = file.getName(); + } + + if(!StringHelper.containsNonWhitespace(fallbackEncoding)) { + return getResource(file, filename); + } + + Path fPath = null; + if(file.isDirectory()) { + fPath = file.toPath(); + } else if(filename != null && filename.toLowerCase().endsWith(".zip")) { + //perhaps find root folder and return it + Map<String,String> env = new HashMap<>(); + if(isEncodingOk(file, "UTF-8")) { + env.put("encoding", "UTF-8"); + } else if(isEncodingOk(file, fallbackEncoding)) { + env.put("encoding", fallbackEncoding); + } + + fPath = newFileSystem(file.toPath(), env).getPath("/"); + RootSearcher rootSearcher = searchRootDirectory(fPath); + if(rootSearcher.foundRoot()) { + Path rootPath = rootSearcher.getRoot(); + fPath = fPath.resolve(rootPath); + } + } else { + fPath = file.toPath(); + } + return fPath; + } + + private static FileSystem newFileSystem(Path path, Map<String,String> env) throws IOException { + for (FileSystemProvider provider: FileSystemProvider.installedProviders()) { + try { + return provider.newFileSystem(path, env); + } catch (UnsupportedOperationException uoe) { + // + } + } + return null; + } + + private static boolean isEncodingOk(File file, String encoding) { + boolean ok = false; + try(ZipFile zFile = new ZipFile(file, Charset.forName(encoding))) { + zFile.stream().forEach(ZipEntry::toString); + ok = true; + } catch (IOException | IllegalArgumentException e) { + //this is what we check + } + + return ok; + } + protected static RootSearcher searchRootDirectory(Path fPath) throws IOException { RootSearcher rootSearcher = new RootSearcher(); diff --git a/src/main/java/org/olat/ims/qti21/QTI21Module.java b/src/main/java/org/olat/ims/qti21/QTI21Module.java index e937a4bf4d695a190fae186cf26d2fd2db20ede2..164b17e42db57bd2abc4ec8017f8f02f2c69a6a7 100644 --- a/src/main/java/org/olat/ims/qti21/QTI21Module.java +++ b/src/main/java/org/olat/ims/qti21/QTI21Module.java @@ -62,6 +62,8 @@ public class QTI21Module extends AbstractSpringModule { private String digitalSignatureCertificatePassword; @Value("${qti21.correction.workflow:anonymous}") private String correctionWorkflow; + @Value("${qti21.import.encoding.fallback:}") + private String importEncodingFallback; @Autowired public QTI21Module(CoordinatorManager coordinatorManager) { @@ -170,6 +172,10 @@ public class QTI21Module extends AbstractSpringModule { setStringProperty("qti21.digital.signature.certificate.password", digitalSignatureCertificatePassword, true); } + public String getImportEncodingFallback() { + return importEncodingFallback; + } + public enum CorrectionWorkflow { anonymous, named diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentHtmlBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentHtmlBuilder.java index 5573badde5d5b003ed8097f833b83d395ceac463..6b4026e32d7eff9c3a08e0e90fb932b4b6c0ec88 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentHtmlBuilder.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentHtmlBuilder.java @@ -135,8 +135,10 @@ public class AssessmentHtmlBuilder { content = content.replace(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", ""); content = content.replace("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", ""); content = content.replace("\n xmlns=\"http://www.imsglobal.org/xsd/imsqti_v2p1\"", ""); + content = content.replace(" xmlns=\"http://www.imsglobal.org/xsd/imsqti_v2p1\"", ""); content = content.replace("xmlns=\"http://www.imsglobal.org/xsd/imsqti_v2p1\"", ""); content = content.replace("\n xsi:schemaLocation=\"http://www.imsglobal.org/xsd/imsqti_v2p1 http://www.imsglobal.org/xsd/imsqti_v2p1.xsd\"", ""); + content = content.replace(" xsi:schemaLocation=\"http://www.imsglobal.org/xsd/imsqti_v2p1 http://www.imsglobal.org/xsd/imsqti_v2p1.xsd\"", ""); content = content.replace("xsi:schemaLocation=\"http://www.imsglobal.org/xsd/imsqti_v2p1 http://www.imsglobal.org/xsd/imsqti_v2p1.xsd\"", ""); return content.trim(); } diff --git a/src/main/java/org/olat/ims/qti21/model/xml/BadRessourceHelper.java b/src/main/java/org/olat/ims/qti21/model/xml/BadRessourceHelper.java index 0d891268701cd162bc8c9acc06d08768919c59b5..cfb83252b571f72249e0d68bbe12d5af11196e4e 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/BadRessourceHelper.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/BadRessourceHelper.java @@ -19,6 +19,8 @@ */ package org.olat.ims.qti21.model.xml; +import java.util.List; + import org.xml.sax.SAXParseException; import uk.ac.ed.ph.jqtiplus.provision.BadResourceException; @@ -95,6 +97,13 @@ public class BadRessourceHelper { out.append("Fatal: " + lineNumber + ":" + columnNumber + " :: " + msg + "\n"); } } + + if(result.getUnsupportedSchemaNamespaces() != null) { + List<String> unsupportedSchemaNamespaces = result.getUnsupportedSchemaNamespaces(); + for(String unsupportedSchemaNamespace : unsupportedSchemaNamespaces) { + out.append("Error unsupported namespace: " + unsupportedSchemaNamespace + "\n"); + } + } } } } diff --git a/src/main/java/org/olat/ims/qti21/model/xml/Onyx38ToQtiWorksHandler.java b/src/main/java/org/olat/ims/qti21/model/xml/Onyx38ToQtiWorksHandler.java index 64d74d99aef864da13e7e96f03a1090ee03519e4..0ebeda205e6a792363b5ba431e2481f002837fad 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/Onyx38ToQtiWorksHandler.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/Onyx38ToQtiWorksHandler.java @@ -54,7 +54,7 @@ public class Onyx38ToQtiWorksHandler extends DefaultHandler2 { private int pLevel = -1; private int liLevel = -1; private int itemBodySubLevel = -1; - private Deque<String> skipTags = new ArrayDeque<String>(); + private Deque<String> skipTags = new ArrayDeque<>(); private boolean envelopP = false; @@ -81,7 +81,7 @@ public class Onyx38ToQtiWorksHandler extends DefaultHandler2 { throws SAXException { try{ String comment = new String(ch, start, length); - if(comment != null && comment.contains("Onyx Editor")) { + if(comment.contains("Onyx Editor")) { int versionIndex = comment.indexOf(VERSION_MARKER); if(versionIndex > 0) { int offset = VERSION_MARKER.length(); @@ -133,7 +133,7 @@ public class Onyx38ToQtiWorksHandler extends DefaultHandler2 { writeAssessmentElement(qName, attributes); } else if("mapTolResponse".equals(qName)) { writeMapTo1ResponseElement(attributes); - }else { + } else { if(itemBodySubLevel == 0 && !envelopP && !isBlock(qName)) { xtw.writeStartElement("p"); envelopP = true; @@ -212,6 +212,9 @@ public class Onyx38ToQtiWorksHandler extends DefaultHandler2 { for(int i=0;i<numOfAttributes; i++) { String attrQName = attributes.getQName(i); String attrValue = attributes.getValue(i); + if("xsi:schemaLocation".equals(attrQName)) { + attrValue = attrValue.replace("http://www.w3.org/1998/Math/MathML http://www.w3.org/Math/XMLSchema/mathml2/mathml2.xsd", ""); + } xtw.writeAttribute(attrQName, attrValue); if("toolName".equals(attrQName)) { hasToolName = true; diff --git a/src/main/java/org/olat/ims/qti21/model/xml/OnyxToQtiWorksHandler.java b/src/main/java/org/olat/ims/qti21/model/xml/OnyxToQtiWorksHandler.java index ca6c0e895aea6fb4991bb49c0f55d0f1dc6aff03..950a39208c51224e85441b0530bd6ca36cc6eb1c 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/OnyxToQtiWorksHandler.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/OnyxToQtiWorksHandler.java @@ -134,7 +134,9 @@ public class OnyxToQtiWorksHandler extends DefaultHandler2 { writeImgElementAttributes(attributes); } else if("customOperator".equals(qName)) { writeCustomOperatorAttributes(attributes); - } else { + } else if("mapTolResponse".equals(qName)) { + writeMapTo1ResponseElement(attributes); + } else { int numOfAttributes = attributes.getLength(); for(int i=0;i<numOfAttributes; i++) { String attrQName = attributes.getQName(i); @@ -197,6 +199,10 @@ public class OnyxToQtiWorksHandler extends DefaultHandler2 { for(int i=0;i<numOfAttributes; i++) { String attrQName = attributes.getQName(i); String attrValue = attributes.getValue(i); + if("xsi:schemaLocation".equals(attrQName)) { + attrValue = attrValue.replace("http://www.w3.org/1998/Math/MathML http://www.w3.org/Math/XMLSchema/mathml2/mathml2.xsd", ""); + } + xtw.writeAttribute(attrQName, attrValue); if("toolName".equals(attrQName)) { hasToolName = true; @@ -213,6 +219,19 @@ public class OnyxToQtiWorksHandler extends DefaultHandler2 { } } + private void writeMapTo1ResponseElement(Attributes attributes) + throws XMLStreamException { + xtw.writeStartElement("mapResponse"); + int numOfAttributes = attributes.getLength(); + for(int i=0;i<numOfAttributes; i++) { + String attrQName = attributes.getQName(i); + if(!"tolerance".equals(attrQName) && !"toleranceMode".equals(attrQName) && !"xmlns".equals(attrQName)) { + String attrValue = attributes.getValue(i); + xtw.writeAttribute(attrQName, attrValue); + } + } + } + /** * The customOperator accept the class attribute or the definition attribute but not * both at the same time. @@ -268,7 +287,7 @@ public class OnyxToQtiWorksHandler extends DefaultHandler2 { private boolean latexDollarOpen = false; private void processLatexDollar(String text) - throws XMLStreamException, SAXException { + throws XMLStreamException { for(int i=100; i-->0; ) { int index = text.indexOf("$$"); @@ -297,7 +316,7 @@ public class OnyxToQtiWorksHandler extends DefaultHandler2 { } private void processLatexParenthesis(String text) - throws XMLStreamException, SAXException { + throws XMLStreamException { for(int i=100; i-->0; ) { int indexOpen = text.indexOf("\\("); diff --git a/src/main/java/org/olat/ims/qti21/model/xml/QTI21ExplorerHandler.java b/src/main/java/org/olat/ims/qti21/model/xml/QTI21ExplorerHandler.java index d742171f494395907995e2dac01ba95389ea5935..02bea0c089212d7d872d72401bb51198123feb68 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/QTI21ExplorerHandler.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/QTI21ExplorerHandler.java @@ -28,6 +28,11 @@ import org.xml.sax.SAXException; import org.xml.sax.ext.DefaultHandler2; /** + * The handler search the version and the editor in a comment + * at the beginning of the file, in the VCARD of imsmanifest, + * it will react to some non-standard features like mapTolResponse or + * some HTML code erros like <p> in <p>. + * * * Initial date: 1 févr. 2017<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com @@ -39,6 +44,7 @@ public class QTI21ExplorerHandler extends DefaultHandler2 { private static final String PRODID_MARKER = "PRODID:"; private StringBuilder collector; + private int pLevel = -1; private final QTI21Infos infos = new QTI21Infos(); public QTI21Infos getInfos() { @@ -49,7 +55,7 @@ public class QTI21ExplorerHandler extends DefaultHandler2 { public void comment(char[] ch, int start, int length) throws SAXException { String comment = new String(ch, start, length); - if(comment != null && comment.contains("Onyx Editor")) { + if(comment.contains("Onyx Editor")) { infos.setEditor("Onyx Editor"); int versionIndex = comment.indexOf(VERSION_MARKER); if(versionIndex > 0) { @@ -73,6 +79,17 @@ public class QTI21ExplorerHandler extends DefaultHandler2 { collector = new StringBuilder(); } else if("entity".equals(qName)) { collector = new StringBuilder(); + } else if("mapTolResponse".equals(qName)) { + if(!StringHelper.containsNonWhitespace(infos.getEditor())) { + infos.setEditor("Onyx Editor"); + infos.setVersion("3.8.1"); + } + } else if("p".equals(qName)) { + pLevel++; + if(pLevel == 1 && !StringHelper.containsNonWhitespace(infos.getEditor())) { + infos.setEditor("Onyx Editor"); + infos.setVersion("3.8.1"); + } } } @@ -121,6 +138,8 @@ public class QTI21ExplorerHandler extends DefaultHandler2 { } collector = null; + } else if("p".equals(qName)) { + pLevel--; } } } \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/repository/handlers/CopyAndConvertVisitor.java b/src/main/java/org/olat/ims/qti21/repository/handlers/CopyAndConvertVisitor.java index 388a144d3efad0a5c4f20b7539679471e3b90d7d..19aa0dc79251023c19ce2d9a0f99b45c2365a19c 100644 --- a/src/main/java/org/olat/ims/qti21/repository/handlers/CopyAndConvertVisitor.java +++ b/src/main/java/org/olat/ims/qti21/repository/handlers/CopyAndConvertVisitor.java @@ -50,9 +50,9 @@ import org.olat.core.util.WebappHelper; import org.olat.fileresource.types.ImsQTI21Resource; import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator; import org.olat.ims.qti21.QTI21Service; +import org.olat.ims.qti21.model.xml.AssessmentItemChecker; import org.olat.ims.qti21.model.xml.BadRessourceHelper; import org.olat.ims.qti21.model.xml.Onyx38ToQtiWorksHandler; -import org.olat.ims.qti21.model.xml.AssessmentItemChecker; import org.olat.ims.qti21.model.xml.OnyxToQtiWorksHandler; import org.olat.ims.qti21.model.xml.QTI21ExplorerHandler; import org.olat.ims.qti21.model.xml.QTI21Infos; @@ -108,17 +108,17 @@ class CopyAndConvertVisitor extends SimpleFileVisitor<Path> { public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Path relativeFile = source.relativize(file); - final Path destFile = Paths.get(destDir.toString(), relativeFile.toString()); - if(filter.matches(file)) { - String filename = file.getFileName().toString(); - if(filename.startsWith(".")) { - //ignore - } else if(filename != null && filename.endsWith("xml") && !filename.equals("imsmanifest.xml")) { - convertXmlFile(file, destFile); - } else { - Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING); - } - } + final Path destFile = Paths.get(destDir.toString(), relativeFile.toString()); + if(filter.matches(file)) { + String filename = file.getFileName().toString(); + if(filename.startsWith(".")) { + //ignore + } else if(filename.endsWith("xml") && !filename.equals("imsmanifest.xml")) { + convertXmlFile(file, destFile); + } else { + Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING); + } + } return FileVisitResult.CONTINUE; } @@ -127,7 +127,7 @@ class CopyAndConvertVisitor extends SimpleFileVisitor<Path> { throws IOException { Path relativeDir = source.relativize(dir); final Path dirToCreate = Paths.get(destDir.toString(), relativeDir.toString()); - if(Files.notExists(dirToCreate)){ + if(!dirToCreate.toFile().exists()) { Files.createDirectory(dirToCreate); } return FileVisitResult.CONTINUE; @@ -149,18 +149,12 @@ class CopyAndConvertVisitor extends SimpleFileVisitor<Path> { fileInfos.setVersion(infos.getVersion()); } if(onyx38Family(fileInfos)) { - validated = convertXmlFile(inputFile, outputFile, fileInfos.getType(), new HandlerProvider() { - @Override - public DefaultHandler2 create(XMLStreamWriter xtw) { - return new Onyx38ToQtiWorksHandler(xtw); - } + validated = convertXmlFile(inputFile, outputFile, fileInfos.getType(), xtw -> { + return new Onyx38ToQtiWorksHandler(xtw); }); } else if(onyxWebFamily(fileInfos)) { - validated = convertXmlFile(inputFile, outputFile, fileInfos.getType(), new HandlerProvider() { - @Override - public DefaultHandler2 create(XMLStreamWriter xtw) { - return new OnyxToQtiWorksHandler(xtw, infos); - } + validated = convertXmlFile(inputFile, outputFile, fileInfos.getType(), xtw -> { + return new OnyxToQtiWorksHandler(xtw, infos); }); if(validated && fileInfos.getType() == InputType.assessmentItem) { @@ -179,14 +173,14 @@ class CopyAndConvertVisitor extends SimpleFileVisitor<Path> { private boolean onyx38Family(QTI21Infos fileInfos) { if(fileInfos == null || fileInfos.getEditor() == null) return false; - String version = infos.getVersion(); - return "Onyx Editor".equals(infos.getEditor()) && version != null && + String version = fileInfos.getVersion(); + return "Onyx Editor".equals(fileInfos.getEditor()) && version != null && (version.startsWith("2.") || version.startsWith("3.")); } private boolean onyxWebFamily(QTI21Infos fileInfos) { if(fileInfos == null || fileInfos.getEditor() == null) return false; - return "ONYX Editor".equals(infos.getEditor()); + return "ONYX Editor".equals(fileInfos.getEditor()); } private QTI21Infos scanFile(Path inputFile) { @@ -215,6 +209,9 @@ class CopyAndConvertVisitor extends SimpleFileVisitor<Path> { boolean valid = validate(tmpFile.toPath(), type, true); if(valid) { + if(!outputFile.getParent().toFile().exists()) { + outputFile.getParent().toFile().mkdirs(); + } Files.copy(tmpFile.toPath(), outputFile, StandardCopyOption.REPLACE_EXISTING); } return valid; diff --git a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java index 6848439425bbbfebe02c18c3d6e99ea16b1c4980..9209f9cebcc45b126fb57421928158f5eb8e4774 100644 --- a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java +++ b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java @@ -61,6 +61,7 @@ import org.olat.fileresource.types.ResourceEvaluation; import org.olat.ims.qti.editor.QTIEditorPackage; import org.olat.ims.qti.fileresource.TestFileResource; import org.olat.ims.qti21.QTI21DeliveryOptions; +import org.olat.ims.qti21.QTI21Module; import org.olat.ims.qti21.QTI21Service; import org.olat.ims.qti21.manager.AssessmentTestSessionDAO; import org.olat.ims.qti21.model.IdentifierGenerator; @@ -107,6 +108,8 @@ public class QTI21AssessmentTestHandler extends FileHandler { @Autowired private DB dbInstance; @Autowired + private QTI21Module qtiModule; + @Autowired private QTI21Service qtiService; @Autowired private RepositoryService repositoryService; @@ -292,7 +295,8 @@ public class QTI21AssessmentTestHandler extends FileHandler { private boolean copyResource(File file, String filename, File targetDirectory) { try { - Path path = FileResource.getResource(file, filename); + String fallbackEncoding = qtiModule.getImportEncodingFallback(); + Path path = FileResource.getResource(file, filename, fallbackEncoding); if(path == null) { return false; } diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties index 3510aaf91fb179636f862637a52b6b45c36f9fcd..79a2cb5a8246d730f5bd97524c38131b35089e50 100644 --- a/src/main/resources/serviceconfig/olat.properties +++ b/src/main/resources/serviceconfig/olat.properties @@ -318,6 +318,9 @@ qti21.digital.signature.enabled=false #Path to a PFX certificate (with X509 certificate, private and public key) qti21.digital.signature.certificate= +#Try an other encoding to open the ZIP files during import of tests +qti21.import.encoding.fallback= + ######################################################################## # Certificates ######################################################################## diff --git a/src/test/java/org/olat/ims/qti21/model/xml/Onyx38ToQtiWorksAssessementItemsTest.java b/src/test/java/org/olat/ims/qti21/model/xml/Onyx38ToQtiWorksAssessementItemsTest.java index 63618431bcb7e3fb63814fb248019f9645eb39e3..0d20f32fd360a055ccd23882faf29b2f5b46d780 100644 --- a/src/test/java/org/olat/ims/qti21/model/xml/Onyx38ToQtiWorksAssessementItemsTest.java +++ b/src/test/java/org/olat/ims/qti21/model/xml/Onyx38ToQtiWorksAssessementItemsTest.java @@ -85,7 +85,8 @@ public class Onyx38ToQtiWorksAssessementItemsTest { { "extended-text-c-3-7.xml" }, { "extended-text-3-7.xml" }, { "text-entry-3-8.xml" }, - { "extended-text-d-3-7.xml" } + { "extended-text-d-3-7.xml" }, + { "paragraphs-rec.xml" } }); } diff --git a/src/test/java/org/olat/ims/qti21/model/xml/QTI21ExplorerHandlerTest.java b/src/test/java/org/olat/ims/qti21/model/xml/QTI21ExplorerHandlerTest.java index 068bad69e69bf481f39e7f6bc9f77541232ab04e..154be025929980aac2bfb1d796566e9eed242fb4 100644 --- a/src/test/java/org/olat/ims/qti21/model/xml/QTI21ExplorerHandlerTest.java +++ b/src/test/java/org/olat/ims/qti21/model/xml/QTI21ExplorerHandlerTest.java @@ -57,6 +57,7 @@ public class QTI21ExplorerHandlerTest { { "resources/onyx/extended-text-b-3-7.xml", "Onyx Editor", "3.7.2" }, { "resources/onyx/extended-text-c-3-7.xml", "Onyx Editor", "3.7.2" }, { "resources/onyx/extended-text-e-3-7.xml", "Onyx Editor", "3.7.2" }, + { "resources/onyx/paragraphs-rec.xml", "Onyx Editor", "3.8.1" }, { "resources/onyx/imsmanifest-5-1.xml", "ONYX Editor", "5.10.3" }, { "resources/onyx/imsmanifest-test-5-11.xml", "ONYX Editor", "5.11.1a" }, { "resources/openolat/essay3c2454b4c4dbd64347ea9df54cd.xml", "OpenOLAT", "11.3a" }, diff --git a/src/test/java/org/olat/ims/qti21/model/xml/resources/onyx/paragraphs-rec.xml b/src/test/java/org/olat/ims/qti21/model/xml/resources/onyx/paragraphs-rec.xml new file mode 100644 index 0000000000000000000000000000000000000000..693b9d153ee2e77e2634d33817d464f88cb92239 --- /dev/null +++ b/src/test/java/org/olat/ims/qti21/model/xml/resources/onyx/paragraphs-rec.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<assessmentItem + xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p1 http://www.imsglobal.org/xsd/qti/qtiv2p1/imsqti_v2p1p1.xsd http://www.w3.org/1998/Math/MathML http://www.w3.org/Math/XMLSchema/mathml2/mathml2.xsd" + identifier="id260dc4c6-5a55-4c2c-b90b-e32f28e2f4db" title="Rechnung" + adaptive="false" timeDependent="false"> + <responseDeclaration + identifier="id7e187f0f-409b-414e-99d2-49ba0110e62d" + cardinality="single" baseType="float"> + <correctResponse> + <value>0.99948</value> + </correctResponse> + </responseDeclaration> + <outcomeDeclaration identifier="SCORE" + cardinality="single" baseType="float"> + <defaultValue> + <value>0</value> + </defaultValue> + </outcomeDeclaration> + <outcomeDeclaration identifier="MAXSCORE" + cardinality="single" baseType="float"> + <defaultValue> + <value>1</value> + </defaultValue> + </outcomeDeclaration> + <outcomeDeclaration identifier="FEEDBACKBASIC" + cardinality="single" baseType="identifier" view="testConstructor"> + <defaultValue> + <value>empty</value> + </defaultValue> + </outcomeDeclaration> + <itemBody> + <p> + <p> + <p> + <p> + Do some calculation with 6 + <sub>3</sub> + to make some funny result. + </p> + <p> + Der Normalfaktor beträgt f =  + <textEntryInteraction + responseIdentifier="id7e187f0f-409b-414e-99d2-49ba0110e62d" /> + </p> + </p> + </p> + </p> + </itemBody> + <responseProcessing> + <responseCondition> + <responseIf> + <equal toleranceMode="absolute" tolerance="0.00005 0.00005" + includeLowerBound="true" includeUpperBound="true"> + <variable + identifier="id7e187f0f-409b-414e-99d2-49ba0110e62d" /> + <correct + identifier="id7e187f0f-409b-414e-99d2-49ba0110e62d" /> + </equal> + <setOutcomeValue identifier="SCORE"> + <sum> + <variable identifier="SCORE" /> + <baseValue baseType="float">1</baseValue> + </sum> + </setOutcomeValue> + </responseIf> + </responseCondition> + <responseCondition> + <responseIf> + <not> + <isNull> + <variable + identifier="id7e187f0f-409b-414e-99d2-49ba0110e62d" /> + </isNull> + </not> + <setOutcomeValue identifier="FEEDBACKBASIC"> + <baseValue baseType="identifier">incorrect</baseValue> + </setOutcomeValue> + </responseIf> + </responseCondition> + <responseCondition> + <responseIf> + <and> + <not> + <match> + <variable identifier="FEEDBACKBASIC" /> + <baseValue baseType="identifier">empty</baseValue> + </match> + </not> + <equal toleranceMode="exact"> + <variable identifier="SCORE" /> + <variable identifier="MAXSCORE" /> + </equal> + </and> + <setOutcomeValue identifier="FEEDBACKBASIC"> + <baseValue baseType="identifier">correct</baseValue> + </setOutcomeValue> + </responseIf> + </responseCondition> + </responseProcessing> +</assessmentItem>