diff --git a/src/main/java/org/olat/ims/qti21/XmlUtilities.java b/src/main/java/org/olat/ims/qti21/XmlUtilities.java new file mode 100644 index 0000000000000000000000000000000000000000..a61fc5384135944c31ab720f9e786e85be9dc31b --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/XmlUtilities.java @@ -0,0 +1,50 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti21; + +import javax.xml.parsers.SAXParserFactory; + +import org.xml.sax.XMLReader; + +/** + * + * Initial date: 10.03.2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public final class XmlUtilities { + + private static final SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + static { + parserFactory.setNamespaceAware(true); + parserFactory.setValidating(false); + } + + /** + * @return XMLReader namespace aware but not validating. + */ + public static final XMLReader createNsAwareSaxReader() { + try { + return parserFactory.newSAXParser().getXMLReader(); + } catch (final Exception e) { + throw new RuntimeException("Could not create NS-aware SAXParser with validating=false. Check deployment/runtime ClassPath", e); + } + } +} diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java index d97c259af677b5e1c05d228a105c63cbd3890e8e..18746c7be85592a9f79b28d27914cb8a2a7c349e 100644 --- a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java +++ b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java @@ -69,7 +69,6 @@ import org.olat.ims.qti21.model.CandidateItemEventType; import org.olat.ims.qti21.model.CandidateTestEventType; import org.olat.ims.qti21.model.ResponseLegality; import org.olat.ims.qti21.model.jpa.CandidateEvent; -import org.olat.ims.qti21.ui.rendering.XmlUtilities; import org.olat.modules.assessment.AssessmentEntry; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; @@ -110,6 +109,7 @@ import uk.ac.ed.ph.jqtiplus.value.NumberValue; import uk.ac.ed.ph.jqtiplus.value.RecordValue; import uk.ac.ed.ph.jqtiplus.value.SingleValue; import uk.ac.ed.ph.jqtiplus.value.Value; +import uk.ac.ed.ph.jqtiplus.xmlutils.XmlFactories; import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ClassPathResourceLocator; import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator; import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.XsltSerializationOptions; @@ -362,8 +362,8 @@ public class QTI21ServiceImpl implements QTI21Service, InitializingBean, Disposa private Document loadStateDocument(AssessmentTestSession candidateSession) { File sessionFile = getTestSessionStateFile(candidateSession); if(sessionFile.exists()) { - DocumentBuilder documentBuilder = XmlUtilities.createNsAwareDocumentBuilder(); try { + DocumentBuilder documentBuilder = XmlFactories.newDocumentBuilder(); return documentBuilder.parse(sessionFile); } catch (final Exception e) { throw new OLATRuntimeException("Could not parse serailized state XML. This is an internal error as we currently don't expose this data to clients", e); diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21EditorController.java b/src/main/java/org/olat/ims/qti21/pool/QTI21EditorController.java index db32dc6dd560a767b8d9b184453b1bb8f7ba5129..626fe86b14add2f05cc286f63bdd70493512f456 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21EditorController.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21EditorController.java @@ -29,6 +29,7 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.util.vfs.VFSContainer; import org.olat.ims.qti21.QTI21Service; import org.olat.ims.qti21.ui.editor.AssessmentItemEditorController; import org.olat.ims.qti21.ui.editor.events.AssessmentItemEvent; @@ -65,6 +66,7 @@ public class QTI21EditorController extends BasicController implements QPoolItemE mainVC = createVelocityContainer("pool_editor"); File resourceDirectory = qpoolService.getRootDirectory(questionItem); + VFSContainer resourceContainer = qpoolService.getRootContainer(questionItem); resourceFile = qpoolService.getRootFile(questionItem); URI assessmentItemUri = resourceFile.toURI(); @@ -72,7 +74,7 @@ public class QTI21EditorController extends BasicController implements QPoolItemE .loadAndResolveAssessmentItem(assessmentItemUri, resourceDirectory); editorCtrl = new AssessmentItemEditorController(ureq, wControl, - resolvedAssessmentItem, resourceDirectory, resourceFile); + resolvedAssessmentItem, resourceDirectory, resourceContainer, resourceFile); listenTo(editorCtrl); mainVC.put("editor", editorCtrl.getInitialComponent()); diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponent.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponent.java index d4bfb037a327579c77a3d1143b8e36df31aae05f..01573645b1c5d5f0f28bbdffaa674c9c2c58d12f 100644 --- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponent.java +++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponent.java @@ -43,6 +43,11 @@ public class AssessmentItemComponent extends AssessmentObjectComponent { this.qtiItem = qtiItem; } + @Override + public String relativePathTo(ResolvedAssessmentItem item) { + return ""; + } + public ResolvedAssessmentItem getResolvedAssessmentItem() { return resolvedAssessmentItem; } @@ -51,6 +56,7 @@ public class AssessmentItemComponent extends AssessmentObjectComponent { this.resolvedAssessmentItem = resolvedAssessmentItem; } + @Override public AssessmentItemFormItem getQtiItem() { return qtiItem; } @@ -68,7 +74,6 @@ public class AssessmentItemComponent extends AssessmentObjectComponent { this.itemSessionController = itemSessionController; } - @Override public AssessmentItemComponentRenderer getHTMLRendererSingleton() { return RENDERER; diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java index 0ffba5d9273c9a144ba2c1904d78e7f3cf59ce7b..ce0440a58042a2191c2eb1293c678a0b7b01088e 100644 --- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java +++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java @@ -40,6 +40,7 @@ import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.template.declaration.TemplateDeclaration; import uk.ac.ed.ph.jqtiplus.node.outcome.declaration.OutcomeDeclaration; import uk.ac.ed.ph.jqtiplus.node.result.SessionStatus; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; import uk.ac.ed.ph.jqtiplus.running.ItemSessionController; import uk.ac.ed.ph.jqtiplus.state.ItemSessionState; import uk.ac.ed.ph.jqtiplus.types.Identifier; @@ -162,19 +163,20 @@ public class AssessmentItemComponentRenderer extends AssessmentObjectComponentRe URLBuilder ubu, Translator translator) { final AssessmentItem assessmentItem = component.getAssessmentItem(); + final ResolvedAssessmentItem resolvedAssessmentItem = component.getResolvedAssessmentItem(); //title + status sb.append("<h1 class='itemTitle'>"); renderItemStatus(renderer, sb, itemSessionState, translator); - sb.append(assessmentItem.getTitle()).append("</h1>"); - sb.append("<div id='itemBody'>"); + sb.append(assessmentItem.getTitle()).append("</h1>") + .append("<div id='itemBody'>"); //TODO prompt //render itemBody assessmentItem.getItemBody().getBlocks().forEach((block) - -> renderBlock(renderer, sb, component, assessmentItem, itemSessionState, block, ubu, translator)); + -> renderBlock(renderer, sb, component, resolvedAssessmentItem, itemSessionState, block, ubu, translator)); //comment renderComment(renderer, sb, component, itemSessionState, translator); @@ -190,7 +192,7 @@ public class AssessmentItemComponentRenderer extends AssessmentObjectComponentRe // Display active modal feedback (only after responseProcessing) if(itemSessionState.getSessionStatus() == SessionStatus.FINAL) { - renderTestItemModalFeedback(renderer, sb, component, assessmentItem, itemSessionState, ubu, translator); + renderTestItemModalFeedback(renderer, sb, component, resolvedAssessmentItem, itemSessionState, ubu, translator); } } @@ -203,8 +205,8 @@ public class AssessmentItemComponentRenderer extends AssessmentObjectComponentRe } @Override - protected void renderPrintedVariable(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, AssessmentItem assessmentItem, ItemSessionState itemSessionState, - PrintedVariable printedVar) { + protected void renderPrintedVariable(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, + ItemSessionState itemSessionState, PrintedVariable printedVar) { Identifier identifier = printedVar.getIdentifier(); Value templateValue = itemSessionState.getTemplateValues().get(identifier); @@ -212,10 +214,12 @@ public class AssessmentItemComponentRenderer extends AssessmentObjectComponentRe sb.append("<span class='printedVariable'>"); if(outcomeValue != null) { - OutcomeDeclaration outcomeDeclaration = assessmentItem.getOutcomeDeclaration(identifier); + OutcomeDeclaration outcomeDeclaration = resolvedAssessmentItem.getRootNodeLookup() + .extractIfSuccessful().getOutcomeDeclaration(identifier); renderPrintedVariable(renderer, sb, printedVar, outcomeDeclaration, outcomeValue); } else if(templateValue != null) { - TemplateDeclaration templateDeclaration = assessmentItem.getTemplateDeclaration(identifier); + TemplateDeclaration templateDeclaration = resolvedAssessmentItem.getRootNodeLookup() + .extractIfSuccessful().getTemplateDeclaration(identifier); renderPrintedVariable(renderer, sb, printedVar, templateDeclaration, templateValue); } else { sb.append("(variable ").append(identifier.toString()).append(" was not found)"); diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponent.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponent.java index ce4d0b30f02600a05446c2ddebc880174af54b76..782b20a3d145aae06986ff65ed1f615b44b33533 100644 --- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponent.java +++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponent.java @@ -36,6 +36,7 @@ import org.olat.ims.qti21.ui.CandidateSessionContext; import uk.ac.ed.ph.jqtiplus.node.content.variable.FeedbackElement; import uk.ac.ed.ph.jqtiplus.node.item.ModalFeedback; import uk.ac.ed.ph.jqtiplus.node.test.VisibilityMode; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; import uk.ac.ed.ph.jqtiplus.state.ItemSessionState; import uk.ac.ed.ph.jqtiplus.types.Identifier; import uk.ac.ed.ph.jqtiplus.value.Value; @@ -78,6 +79,8 @@ public abstract class AssessmentObjectComponent extends AbstractComponent implem this.assessmentObjectUri = assessmentObjectUri; } + public abstract String relativePathTo(ResolvedAssessmentItem resolvedAssessmentItem); + public ResourceLocator getResourceLocator() { return resourceLocator; } diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java index 9dc2c59d57de16c5b8236f418b6865a6bd53fdf1..5e9b225fdfd9a57e953cb067d9923a564dc52d91 100644 --- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java +++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java @@ -81,7 +81,7 @@ import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.ims.qti21.QTI21Service; -import org.olat.ims.qti21.ui.rendering.XmlUtilities; +import org.olat.ims.qti21.XmlUtilities; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; @@ -138,6 +138,7 @@ import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration; import uk.ac.ed.ph.jqtiplus.node.shared.VariableDeclaration; import uk.ac.ed.ph.jqtiplus.node.test.NavigationMode; import uk.ac.ed.ph.jqtiplus.node.test.TestPart; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; import uk.ac.ed.ph.jqtiplus.running.TestSessionController; import uk.ac.ed.ph.jqtiplus.state.ItemSessionState; import uk.ac.ed.ph.jqtiplus.types.Identifier; @@ -193,8 +194,9 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent } protected void renderTestItemModalFeedback(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, URLBuilder ubu, Translator translator) { + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, URLBuilder ubu, Translator translator) { List<ModalFeedback> modalFeedbacks = new ArrayList<>(); + AssessmentItem assessmentItem = resolvedAssessmentItem.getRootNodeLookup().extractIfSuccessful(); for(ModalFeedback modalFeedback:assessmentItem.getModalFeedbacks()) { if(component.isFeedback(modalFeedback, itemSessionState)) { modalFeedbacks.add(modalFeedback); @@ -212,7 +214,7 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent } modalFeedback.getFlowStatics().forEach((flow) - -> renderFlow(renderer, sb, component, assessmentItem, itemSessionState, flow, ubu, translator)); + -> renderFlow(renderer, sb, component, resolvedAssessmentItem, itemSessionState, flow, ubu, translator)); sb.append("</div>"); } @@ -222,21 +224,21 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent } public void renderFlow(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, Flow flow, URLBuilder ubu, Translator translator) { + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, Flow flow, URLBuilder ubu, Translator translator) { if(flow instanceof Block) { - renderBlock(renderer, sb, component, assessmentItem, itemSessionState, (Block)flow, ubu, translator); + renderBlock(renderer, sb, component, resolvedAssessmentItem, itemSessionState, (Block)flow, ubu, translator); } else if(flow instanceof Inline) { - renderInline(renderer, sb, component, assessmentItem, itemSessionState, (Inline)flow, ubu, translator); + renderInline(renderer, sb, component, resolvedAssessmentItem, itemSessionState, (Inline)flow, ubu, translator); } else { log.error("What is it for a flow static object: " + flow); } } public void renderTextOrVariable(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, TextOrVariable textOrVariable) { + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, TextOrVariable textOrVariable) { if(textOrVariable instanceof PrintedVariable) { - renderPrintedVariable(renderer, sb, component, assessmentItem, itemSessionState, (PrintedVariable)textOrVariable); + renderPrintedVariable(renderer, sb, component, resolvedAssessmentItem, itemSessionState, (PrintedVariable)textOrVariable); } else if(textOrVariable instanceof TextRun) { sb.append(((TextRun)textOrVariable).getTextContent()); } else { @@ -245,7 +247,7 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent } public void renderBlock(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, Block block, URLBuilder ubu, Translator translator) { + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, Block block, URLBuilder ubu, Translator translator) { switch(block.getQtiClassName()) { case AssociateInteraction.QTI_CLASS_NAME: @@ -265,20 +267,20 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent case PositionObjectInteraction.QTI_CLASS_NAME: case SliderInteraction.QTI_CLASS_NAME: case UploadInteraction.QTI_CLASS_NAME: { - renderInteraction(renderer, sb, (FlowInteraction)block, assessmentItem, itemSessionState, component, ubu, translator); + renderInteraction(renderer, sb, (FlowInteraction)block, resolvedAssessmentItem, itemSessionState, component, ubu, translator); break; } case CustomInteraction.QTI_CLASS_NAME: { - renderCustomInteraction(renderer, sb, (CustomInteraction<?>)block, assessmentItem, itemSessionState, component, ubu, translator); + renderCustomInteraction(renderer, sb, (CustomInteraction<?>)block, resolvedAssessmentItem, itemSessionState, component, ubu, translator); break; } case PositionObjectStage.QTI_CLASS_NAME: { - renderPositionObjectStage(renderer, sb, (PositionObjectStage)block, assessmentItem, itemSessionState, component, ubu, translator); + renderPositionObjectStage(renderer, sb, (PositionObjectStage)block, resolvedAssessmentItem, itemSessionState, component, ubu, translator); break; } case TemplateBlock.QTI_CLASS_NAME: break;//never rendered case InfoControl.QTI_CLASS_NAME: { - renderInfoControl(renderer, sb, component, assessmentItem, itemSessionState, (InfoControl)block, ubu, translator); + renderInfoControl(renderer, sb, component, resolvedAssessmentItem, itemSessionState, (InfoControl)block, ubu, translator); break; } case FeedbackBlock.QTI_CLASS_NAME: { @@ -286,40 +288,40 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent if(component.isFeedback(feedbackBlock, itemSessionState)) { sb.append("<div class='o_info feedbackBlock '").append(getAtClass(feedbackBlock)).append(">"); feedbackBlock.getBlocks().forEach((child) - -> renderBlock(renderer, sb, component, assessmentItem, itemSessionState, child, ubu, translator)); + -> renderBlock(renderer, sb, component, resolvedAssessmentItem, itemSessionState, child, ubu, translator)); sb.append("</div>"); } break; } case RubricBlock.QTI_CLASS_NAME: break; //never rendered automatically case Math.QTI_CLASS_NAME: { - renderMath(renderer, sb, component, assessmentItem, itemSessionState, (Math)block); + renderMath(renderer, sb, component, resolvedAssessmentItem, itemSessionState, (Math)block); break; } case Div.QTI_CLASS_NAME: - renderStartHtmlTag(sb, component, block, null); + renderStartHtmlTag(sb, component, resolvedAssessmentItem, block, null); ((Div)block).getFlows().forEach((flow) - -> renderFlow(renderer, sb, component, assessmentItem, itemSessionState, flow, ubu, translator)); + -> renderFlow(renderer, sb, component, resolvedAssessmentItem, itemSessionState, flow, ubu, translator)); renderEndTag(sb, block); break; default: { - renderStartHtmlTag(sb, component, block, null); + renderStartHtmlTag(sb, component, resolvedAssessmentItem, block, null); if(block instanceof AtomicBlock) { AtomicBlock atomicBlock = (AtomicBlock)block; atomicBlock.getInlines().forEach((child) - -> renderInline(renderer, sb, component, assessmentItem, itemSessionState, child, ubu, translator)); + -> renderInline(renderer, sb, component, resolvedAssessmentItem, itemSessionState, child, ubu, translator)); } else if(block instanceof SimpleBlock) { SimpleBlock simpleBlock = (SimpleBlock)block; simpleBlock.getBlocks().forEach((child) - -> renderBlock(renderer, sb, component, assessmentItem, itemSessionState, child, ubu, translator)); + -> renderBlock(renderer, sb, component, resolvedAssessmentItem, itemSessionState, child, ubu, translator)); } renderEndTag(sb, block); } } } - public void renderInline(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, AssessmentItem assessmentItem, + public void renderInline(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, Inline inline, URLBuilder ubu, Translator translator) { switch(inline.getQtiClassName()) { @@ -329,11 +331,11 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent } case InlineChoiceInteraction.QTI_CLASS_NAME: case TextEntryInteraction.QTI_CLASS_NAME: { - renderInteraction(renderer, sb, (FlowInteraction)inline, assessmentItem, itemSessionState, component, ubu, translator); + renderInteraction(renderer, sb, (FlowInteraction)inline, resolvedAssessmentItem, itemSessionState, component, ubu, translator); break; } case Hottext.QTI_CLASS_NAME: { - renderHottext(renderer, sb, assessmentItem, itemSessionState, (Hottext)inline, component, ubu, translator); + renderHottext(renderer, sb, resolvedAssessmentItem, itemSessionState, (Hottext)inline, component, ubu, translator); break; } case Gap.QTI_CLASS_NAME: { @@ -341,7 +343,7 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent break; } case PrintedVariable.QTI_CLASS_NAME: { - renderPrintedVariable(renderer, sb, component, assessmentItem, itemSessionState, (PrintedVariable)inline); + renderPrintedVariable(renderer, sb, component, resolvedAssessmentItem, itemSessionState, (PrintedVariable)inline); break; } case TemplateInline.QTI_CLASS_NAME: break; //not part of the item body @@ -350,7 +352,7 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent if(component.isFeedback(feedbackInline, itemSessionState)) { sb.append("<span class='feedbackInline ").append(getAtClass(feedbackInline)).append("'>"); feedbackInline.getInlines().forEach((child) - -> renderInline(renderer, sb, component, assessmentItem, itemSessionState, child, ubu, translator)); + -> renderInline(renderer, sb, component, resolvedAssessmentItem, itemSessionState, child, ubu, translator)); sb.append("</span>"); } break; @@ -360,11 +362,11 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent break; } case Math.QTI_CLASS_NAME: { - renderMath(renderer, sb, component, assessmentItem, itemSessionState, (Math)inline); + renderMath(renderer, sb, component, resolvedAssessmentItem, itemSessionState, (Math)inline); break; } case Img.QTI_CLASS_NAME: { - renderHtmlTag(sb, component, inline, null); + renderHtmlTag(sb, component, resolvedAssessmentItem, inline, null); break; } case Br.QTI_CLASS_NAME: { @@ -372,11 +374,11 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent break; } default: { - renderStartHtmlTag(sb, component, inline, null); + renderStartHtmlTag(sb, component, resolvedAssessmentItem, inline, null); if(inline instanceof SimpleInline) { SimpleInline simpleInline = (SimpleInline)inline; simpleInline.getInlines().forEach((child) - -> renderInline(renderer, sb, component, assessmentItem, itemSessionState, child, ubu, translator)); + -> renderInline(renderer, sb, component, resolvedAssessmentItem, itemSessionState, child, ubu, translator)); } renderEndTag(sb, inline); } @@ -384,20 +386,20 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent } protected final void renderInfoControl(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, InfoControl infoControl, URLBuilder ubu, Translator translator) { + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, InfoControl infoControl, URLBuilder ubu, Translator translator) { sb.append("<div class=\"infoControl\">") .append("<button type='button' onclick=\"return QtiWorksRendering.showInfoControlContent(this)\" class='btn btn-default'>") .append("<span>").append(infoControl.getTitle()).append("</span></button>") .append("<div class='infoControlContent o_info'>"); infoControl.getChildren().forEach((flow) - -> renderFlow(renderer, sb, component, assessmentItem, itemSessionState, flow, ubu, translator)); + -> renderFlow(renderer, sb, component, resolvedAssessmentItem, itemSessionState, flow, ubu, translator)); sb.append("</div></div>"); } - protected final void renderHtmlTag(StringOutput sb, AssessmentObjectComponent component, QtiNode node, String cssClass) { + protected final void renderHtmlTag(StringOutput sb, AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, QtiNode node, String cssClass) { sb.append("<").append(node.getQtiClassName()); for(Attribute<?> attribute:node.getAttributes()) { - String value = getHtmlAttributeValue(component, attribute); + String value = getHtmlAttributeValue(component, resolvedAssessmentItem, attribute); if(StringHelper.containsNonWhitespace(value)) { String name = attribute.getLocalName(); sb.append(" ").append(name).append("=\"").append(value); @@ -410,10 +412,10 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent sb.append(" />"); } - protected final void renderStartHtmlTag(StringOutput sb, AssessmentObjectComponent component, QtiNode node, String cssClass) { + protected final void renderStartHtmlTag(StringOutput sb, AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, QtiNode node, String cssClass) { sb.append("<").append(node.getQtiClassName()); for(Attribute<?> attribute:node.getAttributes()) { - String value = getHtmlAttributeValue(component, attribute); + String value = getHtmlAttributeValue(component, resolvedAssessmentItem, attribute); if(StringHelper.containsNonWhitespace(value)) { String name = attribute.getLocalName(); sb.append(" ").append(name).append("=\"").append(value); @@ -496,12 +498,12 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent } private void renderPositionObjectStage(AssessmentRenderer renderer, StringOutput sb, PositionObjectStage positionObjectStage, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, AssessmentObjectComponent component, + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, AssessmentObjectComponent component, URLBuilder ubu, Translator translator) { Context ctx = new VelocityContext(); ctx.put("positionObjectStage", positionObjectStage); String page = getInteractionTemplate(positionObjectStage); - renderVelocity(renderer, sb, positionObjectStage, ctx, page, assessmentItem, itemSessionState, component, ubu, translator); + renderVelocity(renderer, sb, positionObjectStage, ctx, page, resolvedAssessmentItem, itemSessionState, component, ubu, translator); } /** @@ -516,7 +518,7 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent * @param translator */ private void renderCustomInteraction(AssessmentRenderer renderer, StringOutput sb, CustomInteraction<?> interaction, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, AssessmentObjectComponent component, + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, AssessmentObjectComponent component, URLBuilder ubu, Translator translator) { Context ctx = new VelocityContext(); ctx.put("interaction", interaction); @@ -527,7 +529,7 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent } else { page = velocity_root + "/unsupportedCustomInteraction.html"; } - renderVelocity(renderer, sb, interaction, ctx, page, assessmentItem, itemSessionState, component, ubu, translator); + renderVelocity(renderer, sb, interaction, ctx, page, resolvedAssessmentItem, itemSessionState, component, ubu, translator); } /** @@ -542,27 +544,28 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent * @param translator */ private void renderInteraction(AssessmentRenderer renderer, StringOutput sb, FlowInteraction interaction, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, AssessmentObjectComponent component, + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, AssessmentObjectComponent component, URLBuilder ubu, Translator translator) { Context ctx = new VelocityContext(); ctx.put("interaction", interaction); String page = getInteractionTemplate(interaction); - renderVelocity(renderer, sb, interaction, ctx, page, assessmentItem, itemSessionState, component, ubu, translator); + renderVelocity(renderer, sb, interaction, ctx, page, resolvedAssessmentItem, itemSessionState, component, ubu, translator); } private void renderVelocity(AssessmentRenderer renderer, StringOutput sb, QtiNode interaction, Context ctx, String page, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, AssessmentObjectComponent component, + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, AssessmentObjectComponent component, URLBuilder ubu, Translator translator) { ctx.put("localName", interaction.getQtiClassName()); - ctx.put("assessmentItem", assessmentItem); + ctx.put("assessmentItem", resolvedAssessmentItem.getRootNodeLookup().extractIfSuccessful()); ctx.put("itemSessionState", itemSessionState); ctx.put("isItemSessionOpen", component.isItemSessionOpen(itemSessionState, renderer.isSolutionMode())); ctx.put("isItemSessionEnded", component.isItemSessionEnded(itemSessionState, renderer.isSolutionMode())); Renderer fr = Renderer.getInstance(component, translator, ubu, new RenderResult(), renderer.getGlobalSettings()); AssessmentRenderer fHints = renderer.newHints(fr); - AssessmentObjectVelocityRenderDecorator vrdec = new AssessmentObjectVelocityRenderDecorator(fHints, sb, component, assessmentItem, itemSessionState, ubu, translator); + AssessmentObjectVelocityRenderDecorator vrdec + = new AssessmentObjectVelocityRenderDecorator(fHints, sb, component, resolvedAssessmentItem, itemSessionState, ubu, translator); ctx.put("r", vrdec); VelocityHelper vh = VelocityHelper.getInstance(); vh.mergeContent(page, ctx, sb, null); @@ -640,7 +643,7 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent </xsl:if> </xsl:template> */ - private void renderHottext(AssessmentRenderer renderer, StringOutput sb, AssessmentItem assessmentItem, ItemSessionState itemSessionState, Hottext hottext, + private void renderHottext(AssessmentRenderer renderer, StringOutput sb, ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, Hottext hottext, AssessmentObjectComponent component, URLBuilder ubu, Translator translator) { if(!isVisible(hottext, itemSessionState)) return; @@ -664,13 +667,14 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent if(component.isItemSessionEnded(itemSessionState, renderer.isSolutionMode())) { sb.append(" disabled"); } + AssessmentItem assessmentItem = resolvedAssessmentItem.getRootNodeLookup().extractIfSuccessful(); Value responseValue = getResponseValue(assessmentItem, itemSessionState, interaction.getResponseIdentifier(), renderer.isSolutionMode()); if(valueContains(responseValue, hottext.getIdentifier())) { sb.append(" checked"); } sb.append(" />"); hottext.getInlineStatics().forEach((inline) - -> renderInline(renderer, sb, component, assessmentItem, itemSessionState, inline, ubu, translator)); + -> renderInline(renderer, sb, component, resolvedAssessmentItem, itemSessionState, inline, ubu, translator)); sb.append("</span>"); } } @@ -797,7 +801,7 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent } protected abstract void renderPrintedVariable(AssessmentRenderer renderer, StringOutput sb, - AssessmentObjectComponent component, AssessmentItem assessmentItem, ItemSessionState itemSessionState, + AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, PrintedVariable printedVar); /** @@ -875,13 +879,13 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent } protected void renderMath(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, Math math) { + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, Math math) { renderer.setMathXsltDisabled(true); StringOutput mathOutput = StringOutputPool.allocStringBuilder(2048); mathOutput.append("<math xmlns=\"http://www.w3.org/1998/Math/MathML\">"); math.getContent().forEach((foreignElement) - -> renderMath(renderer, mathOutput, component, assessmentItem, itemSessionState, foreignElement)); + -> renderMath(renderer, mathOutput, component, resolvedAssessmentItem, itemSessionState, foreignElement)); mathOutput.append("</math>"); String enrichedMathML = StringOutputPool.freePop(mathOutput); renderer.setMathXsltDisabled(false); @@ -889,19 +893,20 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent } protected void renderMath(AssessmentRenderer renderer, StringOutput out, AssessmentObjectComponent component, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, QtiNode mathElement) { + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, QtiNode mathElement) { if(mathElement instanceof ForeignElement) { ForeignElement fElement = (ForeignElement)mathElement; boolean mi = fElement.getQtiClassName().equals("mi"); boolean ci = fElement.getQtiClassName().equals("ci"); if(ci || mi) { + AssessmentItem assessmentItem = resolvedAssessmentItem.getRootNodeLookup().extractIfSuccessful(); + String text = contentAsString(fElement); Identifier identifier = Identifier.assumedLegal(text); Value templateValue = getTemplateValue(itemSessionState, text); Value outcomeValue = getOutcomeValue(itemSessionState, text); Value responseValue = getResponseValue(assessmentItem, itemSessionState, identifier, renderer.isSolutionMode()); - if(templateValue != null && isTemplateDeclarationAMathVariable(assessmentItem, text)) { if(ci) { substituteCi(out, templateValue); @@ -921,15 +926,15 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent substituteMi(out, responseValue); } } else { - renderStartHtmlTag(out, component, fElement, null); + renderStartHtmlTag(out, component, resolvedAssessmentItem, fElement, null); fElement.getChildren().forEach((child) - -> renderMath(renderer, out, component, assessmentItem, itemSessionState, child)); + -> renderMath(renderer, out, component, resolvedAssessmentItem, itemSessionState, child)); renderEndTag(out, fElement); } } else { - renderStartHtmlTag(out, component, fElement, null); + renderStartHtmlTag(out, component, resolvedAssessmentItem, fElement, null); fElement.getChildren().forEach((child) - -> renderMath(renderer, out, component, assessmentItem, itemSessionState, child)); + -> renderMath(renderer, out, component, resolvedAssessmentItem, itemSessionState, child)); renderEndTag(out, fElement); } } else if(mathElement instanceof TextRun) { @@ -1061,7 +1066,7 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent try { mathmlTransformerHandler.setResult(new StreamResult(sb)); - final XMLReader xmlReader = XmlUtilities.createNsAwareSaxReader(false); + final XMLReader xmlReader = XmlUtilities.createNsAwareSaxReader(); xmlReader.setContentHandler(mathmlTransformerHandler); Reader mathStream = new StringReader(mathmlAsString); diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectVelocityRenderDecorator.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectVelocityRenderDecorator.java index f3591f131301e4a81966cde59c49520a1c36e607..dd49cb9075e6ed190a13830fc421530ad336049c 100644 --- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectVelocityRenderDecorator.java +++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectVelocityRenderDecorator.java @@ -67,6 +67,7 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.content.Gap; import uk.ac.ed.ph.jqtiplus.node.item.interaction.graphic.AssociableHotspot; import uk.ac.ed.ph.jqtiplus.node.item.interaction.graphic.HotspotChoice; import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; import uk.ac.ed.ph.jqtiplus.state.ItemSessionState; import uk.ac.ed.ph.jqtiplus.types.Identifier; import uk.ac.ed.ph.jqtiplus.types.ResponseData; @@ -96,20 +97,22 @@ public class AssessmentObjectVelocityRenderDecorator extends VelocityRenderDecor private final AssessmentItem assessmentItem; private final ItemSessionState itemSessionState; + private final ResolvedAssessmentItem resolvedAssessmentItem; private final AssessmentObjectComponent avc; public AssessmentObjectVelocityRenderDecorator(AssessmentRenderer renderer, StringOutput target, AssessmentObjectComponent vc, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, URLBuilder ubu, Translator translator) { + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, URLBuilder ubu, Translator translator) { super(renderer.getRenderer(), vc, target); this.avc = vc; this.ubu = ubu; this.target = target; this.renderer = renderer; this.translator = translator; - this.assessmentItem = assessmentItem; this.itemSessionState = itemSessionState; + this.resolvedAssessmentItem = resolvedAssessmentItem; + this.assessmentItem = resolvedAssessmentItem.getRootNodeLookup().extractIfSuccessful(); } public boolean isSolutionMode() { @@ -126,11 +129,11 @@ public class AssessmentObjectVelocityRenderDecorator extends VelocityRenderDecor } public String convertLink(String uri) { - return AssessmentRenderFunctions.convertLink(avc, uri); + return AssessmentRenderFunctions.convertLink(avc, resolvedAssessmentItem, uri); } public String convertLinkFull(String uri) { - return AssessmentRenderFunctions.convertLink(avc, uri); + return AssessmentRenderFunctions.convertLink(avc, resolvedAssessmentItem, uri); } public boolean isNullValue(Value value) { @@ -477,14 +480,14 @@ public class AssessmentObjectVelocityRenderDecorator extends VelocityRenderDecor public String renderPrompt(Prompt prompt) { if(prompt != null) { prompt.getInlineStatics().forEach((inline) - -> avc.getHTMLRendererSingleton().renderInline(renderer, target, avc, assessmentItem, itemSessionState, inline, ubu, translator)); + -> avc.getHTMLRendererSingleton().renderInline(renderer, target, avc, resolvedAssessmentItem, itemSessionState, inline, ubu, translator)); } return ""; } public String renderBlock(Block block) { if(block != null) { - avc.getHTMLRendererSingleton().renderBlock(renderer, target, avc, assessmentItem, itemSessionState, block, ubu, translator); + avc.getHTMLRendererSingleton().renderBlock(renderer, target, avc, resolvedAssessmentItem, itemSessionState, block, ubu, translator); } return ""; } @@ -492,7 +495,7 @@ public class AssessmentObjectVelocityRenderDecorator extends VelocityRenderDecor public String renderBlockStatics(List<BlockStatic> blockStaticList) { if(blockStaticList != null && blockStaticList.size() > 0) { blockStaticList.forEach((block) - -> avc.getHTMLRendererSingleton().renderBlock(renderer, target, avc, assessmentItem, itemSessionState, block, ubu, translator)); + -> avc.getHTMLRendererSingleton().renderBlock(renderer, target, avc, resolvedAssessmentItem, itemSessionState, block, ubu, translator)); } return ""; } @@ -500,7 +503,7 @@ public class AssessmentObjectVelocityRenderDecorator extends VelocityRenderDecor public String renderFlowStatics(List<FlowStatic> flowStaticList) { if(flowStaticList != null && flowStaticList.size() > 0) { flowStaticList.forEach((flow) - -> avc.getHTMLRendererSingleton().renderFlow(renderer, target, avc, assessmentItem, itemSessionState, flow, ubu, translator)); + -> avc.getHTMLRendererSingleton().renderFlow(renderer, target, avc, resolvedAssessmentItem, itemSessionState, flow, ubu, translator)); } return ""; } @@ -508,14 +511,14 @@ public class AssessmentObjectVelocityRenderDecorator extends VelocityRenderDecor public String renderTextOrVariables(List<TextOrVariable> textOrVariables) { if(textOrVariables != null && textOrVariables.size() > 0) { textOrVariables.forEach((textOrVariable) - -> avc.getHTMLRendererSingleton().renderTextOrVariable(renderer, target, avc, assessmentItem, itemSessionState, textOrVariable)); + -> avc.getHTMLRendererSingleton().renderTextOrVariable(renderer, target, avc, resolvedAssessmentItem, itemSessionState, textOrVariable)); } return ""; } public String renderFlow(Flow flow) { if(flow != null) { - avc.getHTMLRendererSingleton().renderFlow(renderer, target, avc, assessmentItem, itemSessionState, flow, ubu, translator); + avc.getHTMLRendererSingleton().renderFlow(renderer, target, avc, resolvedAssessmentItem, itemSessionState, flow, ubu, translator); } return ""; } diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentRenderFunctions.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentRenderFunctions.java index dc60e93ac12248ad951c0a734b0d14967edf52d3..cea6bf8d3fb0cc5a83460b71707407683f9e45dd 100644 --- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentRenderFunctions.java +++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentRenderFunctions.java @@ -40,6 +40,7 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.Choice; import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration; import uk.ac.ed.ph.jqtiplus.node.item.template.declaration.TemplateDeclaration; import uk.ac.ed.ph.jqtiplus.node.test.VisibilityMode; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; import uk.ac.ed.ph.jqtiplus.state.ItemSessionState; import uk.ac.ed.ph.jqtiplus.types.Identifier; import uk.ac.ed.ph.jqtiplus.types.ResponseData; @@ -517,7 +518,7 @@ public class AssessmentRenderFunctions { * @return */ @SuppressWarnings({ "rawtypes" }) - public static final String getHtmlAttributeValue(AssessmentObjectComponent component, Attribute attribute) { + public static final String getHtmlAttributeValue(AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, Attribute attribute) { String value; String name = attribute.getLocalName(); switch(name) { @@ -530,7 +531,7 @@ public class AssessmentRenderFunctions { case "href": case "src": String uri = getDomAttributeValue(attribute); - value = convertLink(component, uri); + value = convertLink(component, resolvedAssessmentItem, uri); break; default: value = null; @@ -564,11 +565,14 @@ public class AssessmentRenderFunctions { </xsl:choose> </xsl:function> */ - public static final String convertLink(AssessmentObjectComponent component, String uri) { + public static final String convertLink(AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, String uri) { if(uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("mailto:")) { return uri; } - uri = component.getMapperUri() + "/file?href=" + uri; + + String relativePath = component.relativePathTo(resolvedAssessmentItem); + uri = component.getMapperUri() + "/file?href=" + relativePath + uri; + System.out.println("Rewrite: " + uri); return uri; } diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponent.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponent.java index 76104c2ba4ee525a2003f41d32cca803dacc0f0e..7c6cff210041dea8aa82f55c8f1d46f6fce76d54 100644 --- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponent.java +++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponent.java @@ -19,6 +19,9 @@ */ package org.olat.ims.qti21.ui.components; +import java.io.File; +import java.net.URI; +import java.nio.file.Path; import java.util.List; import org.olat.ims.qti21.ui.components.AssessmentObjectComponentRenderer.RenderingRequest; @@ -28,6 +31,7 @@ import uk.ac.ed.ph.jqtiplus.node.result.SessionStatus; import uk.ac.ed.ph.jqtiplus.node.test.AssessmentSection; import uk.ac.ed.ph.jqtiplus.node.test.AssessmentTest; import uk.ac.ed.ph.jqtiplus.node.test.TestPart; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentTest; import uk.ac.ed.ph.jqtiplus.running.TestSessionController; import uk.ac.ed.ph.jqtiplus.state.ItemSessionState; @@ -102,6 +106,22 @@ public class AssessmentTestComponent extends AssessmentObjectComponent { public void setResolvedAssessmentTest(ResolvedAssessmentTest resolvedAssessmentTest) { this.resolvedAssessmentTest = resolvedAssessmentTest; } + + @Override + public String relativePathTo(ResolvedAssessmentItem resolvedAssessmentItem) { + URI itemUri = resolvedAssessmentItem.getItemLookup().getSystemId(); + File itemFile = new File(itemUri); + URI testUri = resolvedAssessmentTest.getTestLookup().getSystemId(); + File testFile = new File(testUri); + Path relativePath = testFile.toPath().getParent().relativize(itemFile.toPath().getParent()); + String relativePathString = relativePath.toString(); + if(relativePathString.isEmpty()) { + return relativePathString; + } else if(relativePathString.endsWith("/")) { + return relativePathString; + } + return relativePathString.concat("/"); + } public AssessmentTest getAssessmentTest() { return getResolvedAssessmentTest().getRootNodeLookup().extractIfSuccessful(); diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java index 0b970f4f6acf693bf8f6b6e3a46abbbe10ccbbf9..ade51a4e1e0b35e16b84810298ac6bc736519847 100644 --- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java +++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java @@ -344,7 +344,7 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe //render itemBody assessmentItem.getItemBody().getBlocks().forEach((block) - -> renderBlock(renderer, sb, component, assessmentItem, itemSessionState, block, ubu, translator)); + -> renderBlock(renderer, sb, component, resolvedAssessmentItem, itemSessionState, block, ubu, translator)); //comment renderComment(renderer, sb, component, itemSessionState, translator); @@ -359,12 +359,12 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe // Display active modal feedback (only after responseProcessing) if(component.isItemFeedbackAllowed(itemNode, assessmentItem, options)) { - renderTestItemModalFeedback(renderer, sb, component, assessmentItem, itemSessionState, ubu, translator); + renderTestItemModalFeedback(renderer, sb, component, resolvedAssessmentItem, itemSessionState, ubu, translator); } } @Override - protected void renderPrintedVariable(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, AssessmentItem assessmentItem, + protected void renderPrintedVariable(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, PrintedVariable printedVar) { AssessmentTestComponent testCmp = (AssessmentTestComponent)component; @@ -380,10 +380,12 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe Value templateValue = itemSessionState.getTemplateValues().get(identifier); Value outcomeValue = itemSessionState.getOutcomeValues().get(identifier); if(outcomeValue != null) { - OutcomeDeclaration outcomeDeclaration = assessmentItem.getOutcomeDeclaration(identifier); + OutcomeDeclaration outcomeDeclaration = resolvedAssessmentItem.getRootNodeLookup() + .extractIfSuccessful().getOutcomeDeclaration(identifier); renderPrintedVariable(renderer, sb, printedVar, outcomeDeclaration, outcomeValue); } else if(templateValue != null) { - TemplateDeclaration templateDeclaration = assessmentItem.getTemplateDeclaration(identifier); + TemplateDeclaration templateDeclaration = resolvedAssessmentItem.getRootNodeLookup() + .extractIfSuccessful().getTemplateDeclaration(identifier); renderPrintedVariable(renderer, sb, printedVar, templateDeclaration, templateValue); } else { sb.append("(variable ").append(identifier.toString()).append(" was not found)"); @@ -394,9 +396,9 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe @Override protected void renderMath(AssessmentRenderer renderer, StringOutput out, AssessmentObjectComponent component, - AssessmentItem assessmentItem, ItemSessionState itemSessionState, QtiNode mathElement) { - if(assessmentItem != null) { - super.renderMath(renderer, out, component, assessmentItem, itemSessionState, mathElement); + ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, QtiNode mathElement) { + if(resolvedAssessmentItem != null) { + super.renderMath(renderer, out, component, resolvedAssessmentItem, itemSessionState, mathElement); } else if(mathElement instanceof ForeignElement) { ForeignElement fElement = (ForeignElement)mathElement; boolean mi = fElement.getQtiClassName().equals("mi"); @@ -414,15 +416,15 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe substituteMi(out, outcomeValue); } } else { - renderStartHtmlTag(out, component, fElement, null); + renderStartHtmlTag(out, component, resolvedAssessmentItem, fElement, null); fElement.getChildren().forEach((child) - -> renderMath(renderer, out, component, assessmentItem, itemSessionState, child)); + -> renderMath(renderer, out, component, resolvedAssessmentItem, itemSessionState, child)); renderEndTag(out, fElement); } } else { - renderStartHtmlTag(out, component, fElement, null); + renderStartHtmlTag(out, component, resolvedAssessmentItem, fElement, null); fElement.getChildren().forEach((child) - -> renderMath(renderer, out, component, assessmentItem, itemSessionState, child)); + -> renderMath(renderer, out, component, resolvedAssessmentItem, itemSessionState, child)); renderEndTag(out, fElement); } } else if(mathElement instanceof TextRun) { diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTreeComponent.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTreeComponent.java index ee314a6339377a0642bc30c7b5162952ec6214cb..9d74759ca172e39a7f897462a32b1dd197d6deb5 100644 --- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTreeComponent.java +++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTreeComponent.java @@ -22,6 +22,7 @@ package org.olat.ims.qti21.ui.components; import org.olat.core.gui.UserRequest; import uk.ac.ed.ph.jqtiplus.node.test.AssessmentTest; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentTest; import uk.ac.ed.ph.jqtiplus.running.ItemProcessingContext; import uk.ac.ed.ph.jqtiplus.running.TestSessionController; @@ -50,6 +51,11 @@ public class AssessmentTreeComponent extends AssessmentObjectComponent { this.qtiItem = qtiItem; } + @Override + public String relativePathTo(ResolvedAssessmentItem resolvedAssessmentItem) { + return ""; + } + @Override public AssessmentTreeFormItem getQtiItem() { return qtiItem; diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTreeComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTreeComponentRenderer.java index b1581d5d5383604d7e56cd644907354dcaec889e..dfb91253ffc04cb9a7ca6dc87ee46012decd1016 100644 --- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTreeComponentRenderer.java +++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTreeComponentRenderer.java @@ -36,9 +36,9 @@ import org.olat.ims.qti21.ui.CandidateSessionContext; import org.olat.ims.qti21.ui.QTIWorksAssessmentTestEvent.Event; import uk.ac.ed.ph.jqtiplus.node.content.variable.PrintedVariable; -import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.test.NavigationMode; import uk.ac.ed.ph.jqtiplus.node.test.TestPart; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; import uk.ac.ed.ph.jqtiplus.running.ItemProcessingContext; import uk.ac.ed.ph.jqtiplus.running.ItemSessionController; import uk.ac.ed.ph.jqtiplus.running.TestSessionController; @@ -249,8 +249,8 @@ public class AssessmentTreeComponentRenderer extends AssessmentObjectComponentRe @Override protected void renderPrintedVariable(AssessmentRenderer renderer, StringOutput sb, - AssessmentObjectComponent component, AssessmentItem assessmentItem, ItemSessionState itemSessionState, + AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, PrintedVariable printedVar) { - + // } } \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java index ffc9f5f8875094fc6570e40841442bed9d4d0c15..04006d618d8614d283791c82b378bbb77c81ada1 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java @@ -20,7 +20,6 @@ package org.olat.ims.qti21.ui.editor; import java.io.File; -import java.net.URI; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; @@ -30,6 +29,7 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.util.vfs.VFSContainer; import org.olat.ims.qti21.QTI21Service; import org.olat.ims.qti21.model.QTI21QuestionType; import org.olat.ims.qti21.model.QTI21QuestionTypeDetector; @@ -77,6 +77,10 @@ public class AssessmentItemEditorController extends BasicController { private AssessmentItemDisplayController displayCtrl; private Controller itemEditor, scoreEditor, feedbackEditor; + private final File itemFile; + private final File rootDirectory; + private final VFSContainer rootContainer; + private AssessmentItemBuilder itemBuilder; private ManifestMetadataBuilder metadataBuilder; @@ -87,9 +91,13 @@ public class AssessmentItemEditorController extends BasicController { public AssessmentItemEditorController(UserRequest ureq, WindowControl wControl, - ResolvedAssessmentItem resolvedAssessmentItem, File unzippedDirectory, File itemFile) { + ResolvedAssessmentItem resolvedAssessmentItem, + File rootDirectory, VFSContainer rootContainer, File itemFile) { super(ureq, wControl); this.itemRef = null; + this.itemFile = itemFile; + this.rootDirectory = rootDirectory; + this.rootContainer = rootContainer; this.resolvedAssessmentItem = resolvedAssessmentItem; mainVC = createVelocityContainer("assessment_item_editor"); @@ -99,7 +107,7 @@ public class AssessmentItemEditorController extends BasicController { initItemEditor(ureq); - displayCtrl = new AssessmentItemDisplayController(ureq, getWindowControl(), resolvedAssessmentItem, unzippedDirectory, itemFile); + displayCtrl = new AssessmentItemDisplayController(ureq, getWindowControl(), resolvedAssessmentItem, rootDirectory, itemFile); listenTo(displayCtrl); tabbedPane.addTab("Preview", displayCtrl.getInitialComponent()); @@ -108,10 +116,13 @@ public class AssessmentItemEditorController extends BasicController { public AssessmentItemEditorController(UserRequest ureq, WindowControl wControl, RepositoryEntry testEntry, ResolvedAssessmentItem resolvedAssessmentItem, AssessmentItemRef itemRef, ManifestMetadataBuilder metadataBuilder, - File unzippedDirectory) { + File rootDirectory, VFSContainer rootContainer, File itemFile) { super(ureq, wControl); this.itemRef = itemRef; this.metadataBuilder = metadataBuilder; + this.itemFile = itemFile; + this.rootDirectory = rootDirectory; + this.rootContainer = rootContainer; this.resolvedAssessmentItem = resolvedAssessmentItem; mainVC = createVelocityContainer("assessment_item_editor"); @@ -123,7 +134,7 @@ public class AssessmentItemEditorController extends BasicController { AssessmentEntry assessmentEntry = assessmentService.getOrCreateAssessmentEntry(getIdentity(), testEntry, null, testEntry); displayCtrl = new AssessmentItemDisplayController(ureq, getWindowControl(), - testEntry, assessmentEntry, true, resolvedAssessmentItem, itemRef, unzippedDirectory); + testEntry, assessmentEntry, true, resolvedAssessmentItem, itemRef, rootDirectory); listenTo(displayCtrl); tabbedPane.addTab("Preview", displayCtrl.getInitialComponent()); @@ -167,7 +178,8 @@ public class AssessmentItemEditorController extends BasicController { private AssessmentItemBuilder initSingleChoiceEditors(UserRequest ureq, AssessmentItem item) { SingleChoiceAssessmentItemBuilder scItemBuilder = new SingleChoiceAssessmentItemBuilder(item, qtiService.qtiSerializer()); - itemEditor = new SingleChoiceEditorController(ureq, getWindowControl(), scItemBuilder); + itemEditor = new SingleChoiceEditorController(ureq, getWindowControl(), scItemBuilder, + rootDirectory, rootContainer, itemFile); listenTo(itemEditor); scoreEditor = new ChoiceScoreController(ureq, getWindowControl(), scItemBuilder, itemRef); listenTo(scoreEditor); @@ -182,7 +194,8 @@ public class AssessmentItemEditorController extends BasicController { private AssessmentItemBuilder initMultipleChoiceEditors(UserRequest ureq, AssessmentItem item) { MultipleChoiceAssessmentItemBuilder mcItemBuilder = new MultipleChoiceAssessmentItemBuilder(item, qtiService.qtiSerializer()); - itemEditor = new MultipleChoiceEditorController(ureq, getWindowControl(), mcItemBuilder); + itemEditor = new MultipleChoiceEditorController(ureq, getWindowControl(), mcItemBuilder, + rootDirectory, rootContainer, itemFile); listenTo(itemEditor); scoreEditor = new ChoiceScoreController(ureq, getWindowControl(), mcItemBuilder, itemRef); listenTo(scoreEditor); @@ -197,7 +210,8 @@ public class AssessmentItemEditorController extends BasicController { private AssessmentItemBuilder initKPrimChoiceEditors(UserRequest ureq, AssessmentItem item) { KPrimAssessmentItemBuilder kprimItemBuilder = new KPrimAssessmentItemBuilder(item, qtiService.qtiSerializer()); - itemEditor = new KPrimEditorController(ureq, getWindowControl(), kprimItemBuilder); + itemEditor = new KPrimEditorController(ureq, getWindowControl(), kprimItemBuilder, + rootDirectory, rootContainer, itemFile); listenTo(itemEditor); scoreEditor = new MinimalScoreController(ureq, getWindowControl(), kprimItemBuilder, itemRef); listenTo(scoreEditor); @@ -212,7 +226,8 @@ public class AssessmentItemEditorController extends BasicController { private AssessmentItemBuilder initFIBEditors(UserRequest ureq, AssessmentItem item) { FIBAssessmentItemBuilder kprimItemBuilder = new FIBAssessmentItemBuilder(item, qtiService.qtiSerializer()); - itemEditor = new FIBEditorController(ureq, getWindowControl(), kprimItemBuilder); + itemEditor = new FIBEditorController(ureq, getWindowControl(), kprimItemBuilder, + rootDirectory, rootContainer, itemFile); listenTo(itemEditor); scoreEditor = new FIBScoreController(ureq, getWindowControl(), kprimItemBuilder, itemRef); listenTo(scoreEditor); @@ -227,7 +242,8 @@ public class AssessmentItemEditorController extends BasicController { private AssessmentItemBuilder initEssayEditors(UserRequest ureq, AssessmentItem item) { EssayAssessmentItemBuilder essayItemBuilder = new EssayAssessmentItemBuilder(item, qtiService.qtiSerializer()); - itemEditor = new EssayEditorController(ureq, getWindowControl(), essayItemBuilder); + itemEditor = new EssayEditorController(ureq, getWindowControl(), essayItemBuilder, + rootDirectory, rootContainer, itemFile); listenTo(itemEditor); scoreEditor = new MinimalScoreController(ureq, getWindowControl(), essayItemBuilder, itemRef); listenTo(scoreEditor); @@ -271,8 +287,6 @@ public class AssessmentItemEditorController extends BasicController { if(itemBuilder != null) { itemBuilder.build(); } - URI itemUri = resolvedAssessmentItem.getItemLookup().getSystemId(); - File itemFile = new File(itemUri); qtiService.updateAssesmentObject(itemFile, resolvedAssessmentItem); } diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java index ca7310d77428ff376bee0c9e7bb693a53e3aa07e..899b3df5d074800c14a022e26ca2dc0a1faa0b31 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java @@ -55,6 +55,7 @@ import org.olat.core.gui.control.generic.wizard.StepRunnerCallback; import org.olat.core.gui.control.generic.wizard.StepsMainRunController; import org.olat.core.gui.control.generic.wizard.StepsRunContext; import org.olat.core.util.Util; +import org.olat.core.util.vfs.VFSContainer; import org.olat.fileresource.FileResourceManager; import org.olat.ims.qti21.QTI21Constants; import org.olat.ims.qti21.QTI21Service; @@ -128,6 +129,8 @@ public class AssessmentTestComposerController extends MainLayoutBasicController private final LayoutMain3ColsController columnLayoutCtr; private final File unzippedDirRoot; + private final VFSContainer unzippedContRoot; + private final RepositoryEntry testEntry; private ManifestBuilder manifestBuilder; private ResolvedAssessmentTest resolvedAssessmentTest; @@ -161,6 +164,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController FileResourceManager frm = FileResourceManager.getInstance(); unzippedDirRoot = frm.unzipFileResource(testEntry.getOlatResource()); + unzippedContRoot = frm.unzipContainerResource(testEntry.getOlatResource()); updateTreeModel(); manifestBuilder = ManifestBuilder.read(new File(unzippedDirRoot, "imsmanifest.xml")); @@ -536,8 +540,8 @@ public class AssessmentTestComposerController extends MainLayoutBasicController } //persist metadata - manifestBuilder.write(new File(unzippedDirRoot, "imsmanifest.xml")); + doSaveManifest(); updateTreeModel(); TreeNode newItemNode = menuTree.getTreeModel().getNodeById(firstItemId); @@ -562,7 +566,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController qtiService.updateAssesmentObject(testFile, resolvedAssessmentTest); manifestBuilder.appendAssessmentItem(itemFile.getName()); - manifestBuilder.write(new File(unzippedDirRoot, "imsmanifest.xml")); + doSaveManifest(); return itemRef; } @@ -651,7 +655,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController qtiService.updateAssesmentObject(testFile, resolvedAssessmentTest); manifestBuilder.appendAssessmentItem(itemFile.getName()); - manifestBuilder.write(new File(unzippedDirRoot, "imsmanifest.xml")); + doSaveManifest(); updateTreeModel(); @@ -685,7 +689,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController } private void doSaveManifest() { - this.manifestBuilder.write(new File(unzippedDirRoot, "imsmanifest.xml")); + manifestBuilder.write(new File(unzippedDirRoot, "imsmanifest.xml")); } private void doUpdate(Identifier identifier, String newTitle) { @@ -730,9 +734,11 @@ public class AssessmentTestComposerController extends MainLayoutBasicController currentEditorCtrl = new BadResourceController(ureq, getWindowControl(), item.getItemLookup().getBadResourceException(), unzippedDirRoot, itemRef.getHref()); } else { + URI itemUri = resolvedAssessmentTest.getSystemIdByItemRefMap().get(itemRef); + File itemFile = new File(itemUri); ManifestMetadataBuilder metadata = getMetadataBuilder(itemRef); currentEditorCtrl = new AssessmentItemEditorController(ureq, getWindowControl(), testEntry, - item, itemRef, metadata, unzippedDirRoot); + item, itemRef, metadata, unzippedDirRoot, unzippedContRoot, itemFile); } } @@ -765,8 +771,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController itemRef.setIdentifier(Identifier.parseString(itemId)); itemFile = new File(unzippedDirRoot, itemId + ".xml"); itemRef.setHref(new URI(itemFile.getName())); - - + try(OutputStream out = new FileOutputStream(itemFile)) { //make the copy qtiService.qtiSerializer().serializeJqtiObject(originalAssessmentItem, out); @@ -786,7 +791,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController qtiService.updateAssesmentObject(testFile, resolvedAssessmentTest); manifestBuilder.appendAssessmentItem(itemFile.getName()); - manifestBuilder.write(new File(unzippedDirRoot, "imsmanifest.xml")); + doSaveManifest(); } catch (Exception e) { logError("", e); } @@ -840,7 +845,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController URI testUri = resolvedAssessmentTest.getTestLookup().getSystemId(); File testFile = new File(testUri); qtiService.updateAssesmentObject(testFile, resolvedAssessmentTest); - manifestBuilder.write(new File(unzippedDirRoot, "imsmanifest.xml")); + doSaveManifest(); updateTreeModel(); if(selectedNode != null && selectedNode.getParent() != null) { diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/EssayEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/EssayEditorController.java index 84084426b6c87468fb8cc82fa8385f609f04124c..d5eab12e939cc788bb91fc3da405609773cba4d6 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/EssayEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/EssayEditorController.java @@ -19,17 +19,19 @@ */ package org.olat.ims.qti21.ui.editor.interactions; +import java.io.File; + import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItemContainer; import org.olat.core.gui.components.form.flexible.elements.RichTextElement; import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; -import org.olat.core.gui.components.form.flexible.impl.elements.richText.RichTextConfiguration; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; +import org.olat.core.util.vfs.VFSContainer; import org.olat.ims.qti21.model.QTI21QuestionType; import org.olat.ims.qti21.model.xml.interactions.EssayAssessmentItemBuilder; import org.olat.ims.qti21.ui.editor.AssessmentTestEditorController; @@ -47,13 +49,20 @@ public class EssayEditorController extends FormBasicController { private TextElement placeholderEl; private TextElement widthEl, heightEl, minWordsEl, maxWordsEl; private RichTextElement textEl; - + + private final File itemFile; + private final File rootDirectory; + private final VFSContainer rootContainer; private final EssayAssessmentItemBuilder itemBuilder; - public EssayEditorController(UserRequest ureq, WindowControl wControl, EssayAssessmentItemBuilder itemBuilder) { + public EssayEditorController(UserRequest ureq, WindowControl wControl, EssayAssessmentItemBuilder itemBuilder, + File rootDirectory, VFSContainer rootContainer, File itemFile) { super(ureq, wControl); setTranslator(Util.createPackageTranslator(AssessmentTestEditorController.class, getLocale())); + this.itemFile = itemFile; this.itemBuilder = itemBuilder; + this.rootDirectory = rootDirectory; + this.rootContainer = rootContainer; initForm(ureq); } @@ -62,11 +71,12 @@ public class EssayEditorController extends FormBasicController { titleEl = uifactory.addTextElement("title", "form.imd.title", -1, itemBuilder.getTitle(), formLayout); titleEl.setMandatory(true); + String relativePath = rootDirectory.toPath().relativize(itemFile.toPath().getParent()).toString(); + VFSContainer itemContainer = (VFSContainer)rootContainer.resolve(relativePath); + String description = itemBuilder.getQuestion(); - textEl = uifactory.addRichTextElementForStringData("desc", "form.imd.descr", description, 8, -1, true, null, null, + textEl = uifactory.addRichTextElementForStringData("desc", "form.imd.descr", description, 8, -1, true, itemContainer, null, formLayout, ureq.getUserSession(), getWindowControl()); - RichTextConfiguration richTextConfig = textEl.getEditorConfiguration(); - richTextConfig.setFileBrowserUploadRelPath("media");// set upload dir to the media dir String placeholder = itemBuilder.getPlaceholder(); placeholderEl = uifactory.addTextElement("placeholder", "fib.placeholder", 256, placeholder, formLayout); diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBEditorController.java index da70fef6d09530523d5a8df52cb448941954cfc0..d27efac5c959005239d709654bc62aca63db1197 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBEditorController.java @@ -19,6 +19,8 @@ */ package org.olat.ims.qti21.ui.editor.interactions; +import java.io.File; + import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; @@ -33,6 +35,7 @@ import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.util.Util; +import org.olat.core.util.vfs.VFSContainer; import org.olat.ims.qti21.model.QTI21QuestionType; import org.olat.ims.qti21.model.xml.interactions.FIBAssessmentItemBuilder; import org.olat.ims.qti21.model.xml.interactions.FIBAssessmentItemBuilder.TextEntry; @@ -52,13 +55,20 @@ public class FIBEditorController extends FormBasicController { private CloseableModalController cmc; private FIBTextEntrySettingsController gapEntrySettingsCtrl; - + + private final File itemFile; + private final File rootDirectory; + private final VFSContainer rootContainer; private final FIBAssessmentItemBuilder itemBuilder; - public FIBEditorController(UserRequest ureq, WindowControl wControl, FIBAssessmentItemBuilder itemBuilder) { + public FIBEditorController(UserRequest ureq, WindowControl wControl, FIBAssessmentItemBuilder itemBuilder, + File rootDirectory, VFSContainer rootContainer, File itemFile) { super(ureq, wControl, "fib"); setTranslator(Util.createPackageTranslator(AssessmentTestEditorController.class, getLocale())); + this.itemFile = itemFile; this.itemBuilder = itemBuilder; + this.rootDirectory = rootDirectory; + this.rootContainer = rootContainer; initForm(ureq); } @@ -72,13 +82,15 @@ public class FIBEditorController extends FormBasicController { titleEl = uifactory.addTextElement("title", "form.imd.title", -1, itemBuilder.getTitle(), metadata); titleEl.setMandatory(true); + String relativePath = rootDirectory.toPath().relativize(itemFile.toPath().getParent()).toString(); + VFSContainer itemContainer = (VFSContainer)rootContainer.resolve(relativePath); + String description = itemBuilder.getQuestion(); - textEl = uifactory.addRichTextElementForStringData("desc", "form.imd.descr", description, 8, -1, true, null, null, + textEl = uifactory.addRichTextElementForStringData("desc", "form.imd.descr", description, 8, -1, true, itemContainer, null, metadata, ureq.getUserSession(), getWindowControl()); textEl.addActionListener(FormEvent.ONCLICK); RichTextConfiguration richTextConfig = textEl.getEditorConfiguration(); richTextConfig.enableQTITools(); - richTextConfig.setFileBrowserUploadRelPath("media");// set upload dir to the media dir // Submit Button FormLayoutContainer buttonsContainer = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/KPrimEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/KPrimEditorController.java index d0329b8cd8f1a7a426f78d73e27da702b841aa59..90919df195cd66560604a58fb0f2f2e8eb600d48 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/KPrimEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/KPrimEditorController.java @@ -19,6 +19,7 @@ */ package org.olat.ims.qti21.ui.editor.interactions; +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -32,11 +33,11 @@ import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; -import org.olat.core.gui.components.form.flexible.impl.elements.richText.RichTextConfiguration; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.util.Util; +import org.olat.core.util.vfs.VFSContainer; import org.olat.ims.qti21.QTI21Constants; import org.olat.ims.qti21.model.QTI21QuestionType; import org.olat.ims.qti21.model.xml.interactions.KPrimAssessmentItemBuilder; @@ -65,12 +66,19 @@ public class KPrimEditorController extends FormBasicController { private final List<KprimWrapper> choiceWrappers = new ArrayList<>(); private int count = 0; + private final File itemFile; + private final File rootDirectory; + private final VFSContainer rootContainer; private final KPrimAssessmentItemBuilder itemBuilder; - public KPrimEditorController(UserRequest ureq, WindowControl wControl, KPrimAssessmentItemBuilder itemBuilder) { + public KPrimEditorController(UserRequest ureq, WindowControl wControl, KPrimAssessmentItemBuilder itemBuilder, + File rootDirectory, VFSContainer rootContainer, File itemFile) { super(ureq, wControl, "simple_choices_editor"); setTranslator(Util.createPackageTranslator(AssessmentTestEditorController.class, getLocale())); + this.itemFile = itemFile; this.itemBuilder = itemBuilder; + this.rootDirectory = rootDirectory; + this.rootContainer = rootContainer; initForm(ureq); } @@ -84,13 +92,12 @@ public class KPrimEditorController extends FormBasicController { titleEl = uifactory.addTextElement("title", "form.imd.title", -1, itemBuilder.getTitle(), metadata); titleEl.setMandatory(true); + String relativePath = rootDirectory.toPath().relativize(itemFile.toPath().getParent()).toString(); + VFSContainer itemContainer = (VFSContainer)rootContainer.resolve(relativePath); + String description = itemBuilder.getQuestion(); - textEl = uifactory.addRichTextElementForStringData("desc", "form.imd.descr", description, 8, -1, true, null, null, + textEl = uifactory.addRichTextElementForStringData("desc", "form.imd.descr", description, 8, -1, true, itemContainer, null, metadata, ureq.getUserSession(), getWindowControl()); - RichTextConfiguration richTextConfig = textEl.getEditorConfiguration(); - richTextConfig.setFileBrowserUploadRelPath("media");// set upload dir to the media dir - - //points -> in other controller //shuffle String[] yesnoValues = new String[]{ translate("yes"), translate("no") }; diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MultipleChoiceEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MultipleChoiceEditorController.java index 25599278e68081e8d91f3c2dd61eb1cc19e838c2..3699b3975ca35486e9c29341ebf0aa2130e4e89b 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MultipleChoiceEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MultipleChoiceEditorController.java @@ -19,6 +19,7 @@ */ package org.olat.ims.qti21.ui.editor.interactions; +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -32,12 +33,12 @@ import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; -import org.olat.core.gui.components.form.flexible.impl.elements.richText.RichTextConfiguration; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; +import org.olat.core.util.vfs.VFSContainer; import org.olat.ims.qti21.model.IdentifierGenerator; import org.olat.ims.qti21.model.QTI21QuestionType; import org.olat.ims.qti21.model.xml.AssessmentItemFactory; @@ -64,15 +65,24 @@ public class MultipleChoiceEditorController extends FormBasicController { private FormLayoutContainer answersCont; private final List<SimpleChoiceWrapper> choiceWrappers = new ArrayList<>(); + private final File itemFile; + private final File rootDirectory; + private final VFSContainer rootContainer; + private int count = 0; private final MultipleChoiceAssessmentItemBuilder itemBuilder; private static final String[] yesnoKeys = new String[]{ "y", "n"}; - public MultipleChoiceEditorController(UserRequest ureq, WindowControl wControl, MultipleChoiceAssessmentItemBuilder itemBuilder) { + public MultipleChoiceEditorController(UserRequest ureq, WindowControl wControl, + MultipleChoiceAssessmentItemBuilder itemBuilder, + File rootDirectory, VFSContainer rootContainer, File itemFile) { super(ureq, wControl, "simple_choices_editor"); setTranslator(Util.createPackageTranslator(AssessmentTestEditorController.class, getLocale())); this.itemBuilder = itemBuilder; + this.itemFile = itemFile; + this.rootDirectory = rootDirectory; + this.rootContainer = rootContainer; initForm(ureq); } @@ -88,13 +98,12 @@ public class MultipleChoiceEditorController extends FormBasicController { titleEl = uifactory.addTextElement("title", "form.imd.title", -1, itemBuilder.getTitle(), metadata); titleEl.setMandatory(true); + String relativePath = rootDirectory.toPath().relativize(itemFile.toPath().getParent()).toString(); + VFSContainer itemContainer = (VFSContainer)rootContainer.resolve(relativePath); + String description = itemBuilder.getQuestion(); - textEl = uifactory.addRichTextElementForStringData("desc", "form.imd.descr", description, 8, -1, true, null, null, + textEl = uifactory.addRichTextElementForStringData("desc", "form.imd.descr", description, 8, -1, true, itemContainer, null, metadata, ureq.getUserSession(), getWindowControl()); - RichTextConfiguration richTextConfig = textEl.getEditorConfiguration(); - richTextConfig.setFileBrowserUploadRelPath("media");// set upload dir to the media dir - - //points -> in other controller //shuffle String[] yesnoValues = new String[]{ translate("yes"), translate("no") }; diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/SingleChoiceEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/SingleChoiceEditorController.java index 1c764748636967d5accf72777fc94f1cfc5958a5..dcccb3c478a39e64662ef6d2f30339cbfa7fa59a 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/SingleChoiceEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/SingleChoiceEditorController.java @@ -19,6 +19,7 @@ */ package org.olat.ims.qti21.ui.editor.interactions; +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -32,12 +33,12 @@ import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; -import org.olat.core.gui.components.form.flexible.impl.elements.richText.RichTextConfiguration; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; +import org.olat.core.util.vfs.VFSContainer; import org.olat.ims.qti21.model.QTI21QuestionType; import org.olat.ims.qti21.model.xml.AssessmentItemFactory; import org.olat.ims.qti21.model.xml.interactions.SingleChoiceAssessmentItemBuilder; @@ -63,14 +64,22 @@ public class SingleChoiceEditorController extends FormBasicController { private final List<SimpleChoiceWrapper> choiceWrappers = new ArrayList<>(); private int count = 0; + private final File itemFile; + private final File rootDirectory; + private final VFSContainer rootContainer; private final SingleChoiceAssessmentItemBuilder itemBuilder; private static final String[] yesnoKeys = new String[]{ "y", "n"}; - public SingleChoiceEditorController(UserRequest ureq, WindowControl wControl, SingleChoiceAssessmentItemBuilder itemBuilder) { + public SingleChoiceEditorController(UserRequest ureq, WindowControl wControl, + SingleChoiceAssessmentItemBuilder itemBuilder, + File rootDirectory, VFSContainer rootContainer, File itemFile) { super(ureq, wControl, "simple_choices_editor"); setTranslator(Util.createPackageTranslator(AssessmentTestEditorController.class, getLocale())); this.itemBuilder = itemBuilder; + this.itemFile = itemFile; + this.rootDirectory = rootDirectory; + this.rootContainer = rootContainer; initForm(ureq); } @@ -83,14 +92,13 @@ public class SingleChoiceEditorController extends FormBasicController { titleEl = uifactory.addTextElement("title", "form.imd.title", -1, itemBuilder.getTitle(), metadata); titleEl.setMandatory(true); + + String relativePath = rootDirectory.toPath().relativize(itemFile.toPath().getParent()).toString(); + VFSContainer itemContainer = (VFSContainer)rootContainer.resolve(relativePath); String description = itemBuilder.getQuestion(); - textEl = uifactory.addRichTextElementForStringData("desc", "form.imd.descr", description, 8, -1, true, null, null, + textEl = uifactory.addRichTextElementForStringData("desc", "form.imd.descr", description, 8, -1, true, itemContainer, null, metadata, ureq.getUserSession(), getWindowControl()); - RichTextConfiguration richTextConfig = textEl.getEditorConfiguration(); - richTextConfig.setFileBrowserUploadRelPath("media");// set upload dir to the media dir - - //points -> in other controller //shuffle String[] yesnoValues = new String[]{ translate("yes"), translate("no") }; diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/XmlUtilities.java b/src/main/java/org/olat/ims/qti21/ui/rendering/XmlUtilities.java deleted file mode 100644 index b4c1f40c007e9502d3bb0ae89470aa8967c9b114..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/ims/qti21/ui/rendering/XmlUtilities.java +++ /dev/null @@ -1,73 +0,0 @@ -/* Copyright (c) 2012-2013, University of Edinburgh. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * * Neither the name of the University of Edinburgh nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * - * This software is derived from (and contains code from) QTItools and MathAssessEngine. - * QTItools is (c) 2008, University of Southampton. - * MathAssessEngine is (c) 2010, University of Edinburgh. - */ -package org.olat.ims.qti21.ui.rendering; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParserFactory; - -import org.xml.sax.XMLReader; - -/** - * Some generic XML-related utilities. - * - * @author David McKain - */ -public final class XmlUtilities { - - public static final DocumentBuilder createNsAwareDocumentBuilder() { - try { - final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); - documentBuilderFactory.setNamespaceAware(true); - return documentBuilderFactory.newDocumentBuilder(); - } - catch (final ParserConfigurationException e) { - throw new RuntimeException("Could not create NS-aware DocumentBuilder. Check deployment/runtime ClassPath", e); - } - } - - public static final XMLReader createNsAwareSaxReader(final boolean validating) { - try { - final SAXParserFactory parserFactory = SAXParserFactory.newInstance(); - parserFactory.setNamespaceAware(true); - parserFactory.setValidating(validating); - return parserFactory.newSAXParser().getXMLReader(); - } - catch (final Exception e) { - throw new RuntimeException("Could not create NS-aware SAXParser with validating=" + validating + ". Check deployment/runtime ClassPath", e); - } - } - -}