/** * <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.ui.components; import static org.olat.ims.qti21.ui.components.AssessmentRenderFunctions.renderValue; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.olat.core.gui.components.form.flexible.impl.FormJSHelper; import org.olat.core.gui.render.StringOutput; import org.olat.core.gui.render.URLBuilder; import org.olat.core.gui.render.velocity.VelocityRenderDecorator; import org.olat.core.gui.translator.Translator; import org.olat.core.helpers.Settings; import org.olat.core.util.StringHelper; import org.olat.ims.qti21.AssessmentTestSession; import org.olat.ims.qti21.ui.CandidateSessionContext; import uk.ac.ed.ph.jqtiplus.attribute.value.StringMultipleAttribute; import uk.ac.ed.ph.jqtiplus.node.QtiNode; import uk.ac.ed.ph.jqtiplus.node.content.BodyElement; import uk.ac.ed.ph.jqtiplus.node.content.basic.Block; import uk.ac.ed.ph.jqtiplus.node.content.basic.BlockStatic; import uk.ac.ed.ph.jqtiplus.node.content.basic.Flow; import uk.ac.ed.ph.jqtiplus.node.content.basic.FlowStatic; import uk.ac.ed.ph.jqtiplus.node.content.variable.TextOrVariable; import uk.ac.ed.ph.jqtiplus.node.expression.operator.Shape; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.interaction.AssociateInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.ChoiceInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.ExtendedTextInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.GapMatchInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.GraphicAssociateInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.GraphicGapMatchInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.GraphicOrderInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.InlineChoiceInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.MatchInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.OrderInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Prompt; import uk.ac.ed.ph.jqtiplus.node.item.interaction.SliderInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.StringInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.Choice; import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.GapChoice; import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.GapImg; import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.InlineChoice; import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.SimpleAssociableChoice; import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.SimpleChoice; 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; import uk.ac.ed.ph.jqtiplus.utils.QueryUtils; import uk.ac.ed.ph.jqtiplus.value.BaseType; import uk.ac.ed.ph.jqtiplus.value.Cardinality; import uk.ac.ed.ph.jqtiplus.value.FileValue; import uk.ac.ed.ph.jqtiplus.value.ListValue; import uk.ac.ed.ph.jqtiplus.value.NullValue; import uk.ac.ed.ph.jqtiplus.value.Orientation; import uk.ac.ed.ph.jqtiplus.value.RecordValue; import uk.ac.ed.ph.jqtiplus.value.SingleValue; import uk.ac.ed.ph.jqtiplus.value.Value; /** * * Initial date: 14.09.2015<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class AssessmentObjectVelocityRenderDecorator extends VelocityRenderDecorator { private final URLBuilder ubu; private final AssessmentRenderer renderer; private final StringOutput target; private final Translator translator; private final AssessmentItem assessmentItem; private final ItemSessionState itemSessionState; private final ResolvedAssessmentItem resolvedAssessmentItem; private final AssessmentObjectComponent avc; public AssessmentObjectVelocityRenderDecorator(AssessmentRenderer renderer, StringOutput target, AssessmentObjectComponent vc, 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.itemSessionState = itemSessionState; this.resolvedAssessmentItem = resolvedAssessmentItem; this.assessmentItem = resolvedAssessmentItem.getRootNodeLookup().extractIfSuccessful(); } public boolean isSolutionMode() { return renderer.isSolutionMode(); } public boolean isItemSessionOpen() { return avc.isItemSessionOpen(itemSessionState, isSolutionMode()); } //isItemSessionEnded" as="xs:boolean" select="$itemSessionState/@endTime!='' or $solutionMode public boolean isItemSessionEnded() { return avc.isItemSessionEnded(itemSessionState, isSolutionMode()); } public String getAssessmentTestSessionKey() { CandidateSessionContext ctx = avc.getCandidateSessionContext(); if(ctx != null) { AssessmentTestSession candidateSession = ctx.getCandidateSession(); return candidateSession == null ? "" : candidateSession.getKey().toString(); } return ""; } public String appendFlexiFormDirty(String id) { FormJSHelper.appendFlexiFormDirty(target, avc.getQtiItem().getRootForm(), id); return ""; } public String appendFlexiFormDirtyForCheckbox(String id) { FormJSHelper.appendFlexiFormDirtyForCheckbox(target, avc.getQtiItem().getRootForm(), id); return ""; } public String appendFlexiFormDirtyForClick(String id) { FormJSHelper.appendFlexiFormDirtyForClick(target, avc.getQtiItem().getRootForm(), id); return ""; } public String appendFlexiFormDirtyOn(String id, String events) { FormJSHelper.appendFlexiFormDirtyOn(target, avc.getQtiItem().getRootForm(), events, id); return ""; } public String convertLink(String uri) { return AssessmentRenderFunctions.convertLink(avc, resolvedAssessmentItem, uri); } public String convertLinkFull(String uri) { return AssessmentRenderFunctions.convertLink(avc, resolvedAssessmentItem, uri); } public String convertLinkAbsolut(String uri) { String url = AssessmentRenderFunctions.convertLink(avc, resolvedAssessmentItem, uri); String path = Settings.getServerContextPathURI(); return path.concat(url); } public String convertSubmissionLinkFull(String uri) { return AssessmentRenderFunctions.convertSubmissionLink(avc, resolvedAssessmentItem, uri); } public String getFormDispatchFieldId() { return avc.getQtiItem().getRootForm().getDispatchFieldId(); } public boolean isNullValue(Value value) { return value == null || value.isNull(); } public boolean isNotNullValue(Value value) { return value != null && !value.isNull(); } /** * Generate a unique ID * @return */ public String responseUniqueId(Interaction interaction) { return avc.getResponseUniqueIdentifier(itemSessionState, interaction); } /** * For upload interaction * * @param value * @return */ public boolean notEmpty(Value value) { if(value instanceof FileValue) { FileValue fValue = (FileValue)value; return fValue.getFile() != null; } return value != null && !value.isNull(); } //<xsl:if test="qw:is-invalid-response(@responseIdentifier)"> public boolean isInvalidResponse(Identifier identifier) { //$itemSessionState/@invalidResponseIdentifiers return AssessmentRenderFunctions.isInvalidResponse(itemSessionState, identifier); } //<xsl:sequence select="$unboundResponseIdentifiers=$identifier"/> public boolean isBadResponse(Identifier identifier) { return AssessmentRenderFunctions.isBadResponse(itemSessionState, identifier); } /** * Check the maxChoices and the cardinality * @param interaction * @return */ public boolean isSingleChoice(Interaction interaction) { if(interaction instanceof ChoiceInteraction) { ChoiceInteraction choiceInteraction = (ChoiceInteraction)interaction; boolean sc = choiceInteraction.getMaxChoices() == 1; ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(choiceInteraction.getResponseIdentifier()); if(responseDeclaration != null && responseDeclaration.hasCardinality(Cardinality.MULTIPLE)) { return false; } return sc; } return false; } public String getOrientation(Orientation orientation) { if(orientation == null) { return Orientation.VERTICAL.toQtiString(); } return orientation.toQtiString(); } //<xsl:variable name="minStrings" select="if (@minStrings) then xs:integer(@minStrings) else 1" as="xs:integer"/> public String getMinStrings(ExtendedTextInteraction interaction) { int minStrings = interaction.getMinStrings(); return Integer.toString(minStrings); } public String getMaxStrings(ExtendedTextInteraction interaction) { Integer maxStrings = interaction.getMaxStrings(); return maxStrings == null ? "()" : Integer.toString(maxStrings); } public List<SimpleAssociableChoice> getVisibleAssociableChoices(AssociateInteraction interaction) { return interaction.getSimpleAssociableChoices().stream() .filter((choice) -> isVisible(choice, itemSessionState)) .collect(Collectors.toList()); } public List<HotspotChoice> getVisibleHotspotChoices(GraphicOrderInteraction interaction) { return interaction.getHotspotChoices().stream() .filter((hotspot) -> isVisible(hotspot, itemSessionState)) .collect(Collectors.toList()); } public List<AssociableHotspot> getVisibleAssociableHotspots(GraphicGapMatchInteraction interaction) { return interaction.getAssociableHotspots().stream() .filter((hotspot) -> isVisible(hotspot, itemSessionState)) .collect(Collectors.toList()); } public List<AssociableHotspot> getVisibleAssociableHotspots(GraphicAssociateInteraction interaction) { return interaction.getAssociableHotspots().stream() .filter((hotspot) -> isVisible(hotspot, itemSessionState)) .collect(Collectors.toList()); } public List<GapImg> getVisibleGapImgs(GraphicGapMatchInteraction interaction) { return interaction.getGapImgs().stream() .filter((gapImg) -> isVisible(gapImg, itemSessionState)) .collect(Collectors.toList()); } public List<SimpleAssociableChoice> getVisibleOrderedChoices(MatchInteraction interaction, int pos) { try { List<SimpleAssociableChoice> choices; if(interaction.getShuffle()) { List<Identifier> choiceOrders = itemSessionState.getShuffledInteractionChoiceOrder(interaction.getResponseIdentifier()); Map<Identifier,SimpleAssociableChoice> idTochoice = new HashMap<>(); List<SimpleAssociableChoice> allChoices = interaction.getSimpleMatchSets().get(pos).getSimpleAssociableChoices(); for(SimpleAssociableChoice allChoice:allChoices) { idTochoice.put(allChoice.getIdentifier(), allChoice); } choices = new ArrayList<>(); for(Identifier choiceOrder:choiceOrders) { SimpleAssociableChoice choice = idTochoice.get(choiceOrder); if(choice != null) { choices.add(choice); } } } else { choices = interaction.getSimpleMatchSets().get(pos).getSimpleAssociableChoices(); } return choices.stream() .filter((choice) -> isVisible(choice, itemSessionState)) .collect(Collectors.toList()); } catch (Exception e) { e.printStackTrace(); return null; } } //<xsl:apply-templates select="qw:get-visible-ordered-choices(., qti:simpleChoice)"/> public List<SimpleChoice> getVisibleOrderedSimpleChoices(ChoiceInteraction interaction) { List<SimpleChoice> choices; if(interaction.getShuffle()) { // <xsl:variable name="shuffledChoiceOrders" select="$itemSessionState/qw:shuffledInteractionChoiceOrder" // as="element(qw:shuffledInteractionChoiceOrder)*"/> //<xsl:variable name="choiceSequence" as="xs:string?" // select="$shuffledChoiceOrders[@responseIdentifier=$interaction/@responseIdentifier]/@choiceSequence"/> List<Identifier> choiceOrders = itemSessionState.getShuffledInteractionChoiceOrder(interaction.getResponseIdentifier()); choices = new ArrayList<>(); choiceOrders.forEach((choiceIdentifier) -> choices.add(interaction.getSimpleChoice(choiceIdentifier))); } else { choices = interaction.getSimpleChoices(); } List<SimpleChoice> visibleChoices = choices.stream() .filter((choice) -> isVisible(choice, itemSessionState)) .collect(Collectors.toList()); return visibleChoices; } public List<SimpleChoice> getVisibleOrderedSimpleChoices(OrderInteraction interaction) { List<SimpleChoice> choices; if(interaction.getShuffle()) { // <xsl:variable name="shuffledChoiceOrders" select="$itemSessionState/qw:shuffledInteractionChoiceOrder" // as="element(qw:shuffledInteractionChoiceOrder)*"/> //<xsl:variable name="choiceSequence" as="xs:string?" // select="$shuffledChoiceOrders[@responseIdentifier=$interaction/@responseIdentifier]/@choiceSequence"/> List<Identifier> choiceOrders = itemSessionState.getShuffledInteractionChoiceOrder(interaction.getResponseIdentifier()); choices = new ArrayList<>(); for(Identifier choiceOrder:choiceOrders) { choices.add(interaction.getSimpleChoice(choiceOrder)); } } else { choices = interaction.getSimpleChoices(); } return choices.stream() .filter((choice) -> isVisible(choice, itemSessionState)) .collect(Collectors.toList()); } /* <xsl:function name="qw:get-visible-ordered-choices" as="element()*"> <xsl:param name="interaction" as="element()"/> <xsl:param name="choices" as="element()*"/> <xsl:variable name="orderedChoices" as="element()*"> <xsl:choose> <xsl:when test="$interaction/@shuffle='true'"> <xsl:for-each select="qw:get-shuffled-choice-order($interaction)"> <xsl:sequence select="$choices[@identifier=current()]"/> </xsl:for-each> </xsl:when> <xsl:otherwise> <xsl:sequence select="$choices"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:sequence select="qw:filter-visible($orderedChoices)"/> </xsl:function> */ public List<InlineChoice> getVisibleOrderedChoices(InlineChoiceInteraction interaction) { List<InlineChoice> choices; if(interaction.getShuffle()) { // <xsl:variable name="shuffledChoiceOrders" select="$itemSessionState/qw:shuffledInteractionChoiceOrder" // as="element(qw:shuffledInteractionChoiceOrder)*"/> //<xsl:variable name="choiceSequence" as="xs:string?" // select="$shuffledChoiceOrders[@responseIdentifier=$interaction/@responseIdentifier]/@choiceSequence"/> List<Identifier> choiceOrders = itemSessionState.getShuffledInteractionChoiceOrder(interaction.getResponseIdentifier()); choices = new ArrayList<>(); for(Identifier choiceOrder:choiceOrders) { choices.add(interaction.getInlineChoice(choiceOrder)); } } else { choices = interaction.getInlineChoices(); } return choices.stream() .filter((choice) -> isVisible(choice, itemSessionState)) .collect(Collectors.toList()); } public List<GapChoice> getVisibleOrderedChoices(GapMatchInteraction interaction) { List<GapChoice> choices; if(interaction.getShuffle()) { // <xsl:variable name="shuffledChoiceOrders" select="$itemSessionState/qw:shuffledInteractionChoiceOrder" // as="element(qw:shuffledInteractionChoiceOrder)*"/> //<xsl:variable name="choiceSequence" as="xs:string?" // select="$shuffledChoiceOrders[@responseIdentifier=$interaction/@responseIdentifier]/@choiceSequence"/> List<Identifier> choiceOrders = itemSessionState.getShuffledInteractionChoiceOrder(interaction.getResponseIdentifier()); choices = new ArrayList<>(); for(Identifier choiceOrder:choiceOrders) { choices.add(interaction.getGapChoice(choiceOrder)); } } else { choices = new ArrayList<>(interaction.getGapChoices()); } return choices.stream() .filter((choice) -> isVisible(choice, itemSessionState)) .collect(Collectors.toList()); } public List<Gap> findGaps(GapMatchInteraction interaction) { return QueryUtils.search(Gap.class, interaction.getBlockStatics()); } public List<Gap> filterVisible(List<Gap> gaps) { if(gaps == null) return new ArrayList<>(0); return gaps.stream() .filter((gap) -> isVisible(gap, itemSessionState)) .collect(Collectors.toList()); } /* <xsl:variable name="respondedChoiceIdentifiers" select="qw:extract-iterable-elements(qw:get-response-value(/, @responseIdentifier))" as="xs:string*"/> <xsl:variable name="unselectedVisibleChoices" select="$visibleOrderedChoices[not(@identifier = $respondedChoiceIdentifiers)]" as="element(qti:simpleChoice)*"/> <xsl:variable name="respondedVisibleChoices" as="element(qti:simpleChoice)*"> <xsl:for-each select="$respondedChoiceIdentifiers"> <xsl:sequence select="$thisInteraction/qti:simpleChoice[@identifier=current() and qw:is-visible(.)]"/> </xsl:for-each> </xsl:variable> */ public OrderChoices getRespondedVisibleChoices(OrderInteraction interaction) { List<SimpleChoice> visibleChoices = getVisibleOrderedSimpleChoices(interaction); Value responseValue = getResponseValue(interaction.getResponseIdentifier()); List<String> responseIdentifiers = new ArrayList<>(); if(responseValue instanceof ListValue) { for(SingleValue singleValue: (ListValue)responseValue) { responseIdentifiers.add(singleValue.toQtiString()); } } List<SimpleChoice> unselectedVisibleChoices = new ArrayList<>(visibleChoices); for(Iterator<SimpleChoice> it=unselectedVisibleChoices.iterator(); it.hasNext(); ) { SimpleChoice choice = it.next(); if(responseIdentifiers.contains(choice.getIdentifier().toString())) { it.remove(); } } List<SimpleChoice> respondedVisibleChoices = new ArrayList<>(); for(String responseIdentifier:responseIdentifiers) { for(SimpleChoice visibleChoice:visibleChoices) { if(responseIdentifier.equals(visibleChoice.getIdentifier().toString())) { respondedVisibleChoices.add(visibleChoice); } } } return new OrderChoices(respondedVisibleChoices, unselectedVisibleChoices); } /* <xsl:variable name="is-discrete" select="qw:get-response-declaration(/, @responseIdentifier)/@baseType='integer'" as="xs:boolean"/> <xsl:variable name="min" select="if ($is-discrete) then string(floor(@lowerBound)) else string(@lowerBound)" as="xs:string"/> <xsl:variable name="max" select="if ($is-discrete) then string(ceiling(@upperBound)) else string(@upperBound)" as="xs:string"/> <xsl:variable name="step" select="if (@step) then @step else if ($is-discrete) then '1' else '0.01'" as="xs:string"/> <xsl:value-of select="if (@reverse) then @reverse else 'false'"/> */ public SliderOptions getSliderOptions(SliderInteraction interaction) { ResponseDeclaration responseDeclaration = getResponseDeclaration(interaction.getResponseIdentifier()); boolean discrete = responseDeclaration.hasBaseType(BaseType.INTEGER); boolean reverse = interaction.getReverse() == null ? false : interaction.getReverse().booleanValue(); String step; if(interaction.getStep() != null) { step = Integer.toString(interaction.getStep().intValue()); } else { step = discrete ? "1" : "0.01"; } String min; String max; if(discrete) { min = Long.toString(java.lang.Math.round(java.lang.Math.floor(interaction.getLowerBound()))); max = Long.toString(java.lang.Math.round(java.lang.Math.ceil(interaction.getUpperBound()))); } else { min = Long.toString(java.lang.Math.round(interaction.getLowerBound())); max = Long.toString(java.lang.Math.round(interaction.getUpperBound())); } return new SliderOptions(discrete, reverse, min, max, step); } public boolean isVisible(Choice choice, ItemSessionState iSessionState) { return AssessmentRenderFunctions.isVisible(choice, iSessionState); } public boolean valueContains(Value value, Identifier identifier) { return AssessmentRenderFunctions.valueContains(value, identifier); } public boolean valueContains(Value value, String string) { return AssessmentRenderFunctions.valueContains(value, string); } public ResponseData getResponseInput(Identifier identifier) { return AssessmentRenderFunctions.getResponseInput(itemSessionState, identifier); } public String extractSingleCardinalityResponseInput(ResponseData data) { return AssessmentRenderFunctions.extractSingleCardinalityResponseInput(data); } public String getResponseValueForField(Value value, String field) { String responseValue; //for math entry interaction if(value instanceof RecordValue) { Identifier fieldIdentifier = Identifier.assumedLegal(field); RecordValue recordValue = (RecordValue)value; SingleValue sValue = recordValue.get(fieldIdentifier); responseValue = sValue == null ? null : sValue.toQtiString(); } else { responseValue = null; } return responseValue; } public Value getResponseValue(Identifier identifier) { return AssessmentRenderFunctions.getResponseValue(assessmentItem, itemSessionState, identifier, isSolutionMode()); } public String getResponseValueAsBase64(Identifier identifier) { AssessmentTestSession assessmentTestSession = avc.getCandidateSessionContext().getCandidateSession(); return AssessmentRenderFunctions.getResponseValueAsBase64(assessmentItem, assessmentTestSession, itemSessionState, identifier, isSolutionMode()); } public ResponseDeclaration getResponseDeclaration(Identifier identifier) { return AssessmentRenderFunctions.getResponseDeclaration(assessmentItem, identifier); } public String renderClassAttr(BodyElement block) { List<String> classAttr = block.getClassAttr(); if(classAttr != null && classAttr.size() > 0) { for(String attr:classAttr) { if(target.getLastChar() != ' ') target.append(" "); target.append(attr); } } return ""; } public String renderPrompt(Prompt prompt) { if(prompt != null) { prompt.getInlineStatics().forEach((inline) -> 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, resolvedAssessmentItem, itemSessionState, block, ubu, translator); } return ""; } public String renderBlockStatics(List<BlockStatic> blockStaticList) { if(blockStaticList != null && blockStaticList.size() > 0) { blockStaticList.forEach((block) -> avc.getHTMLRendererSingleton().renderBlock(renderer, target, avc, resolvedAssessmentItem, itemSessionState, block, ubu, translator)); } return ""; } public String renderFlowStatics(List<FlowStatic> flowStaticList) { if(flowStaticList != null && flowStaticList.size() > 0) { flowStaticList.forEach((flow) -> avc.getHTMLRendererSingleton().renderFlow(renderer, target, avc, resolvedAssessmentItem, itemSessionState, flow, ubu, translator)); } return ""; } public String renderKprimSpecialFlowStatics(List<FlowStatic> flowStaticList) { StringOutput sb = new StringOutput(); if(flowStaticList != null && flowStaticList.size() > 0) { flowStaticList.forEach((flow) -> avc.getHTMLRendererSingleton().renderFlow(renderer, sb, avc, resolvedAssessmentItem, itemSessionState, flow, ubu, translator)); } String specialKprim = sb.toString(); if("+".equals(specialKprim)) { if(translator != null) { specialKprim = translator.translate("kprim.plus"); } else { specialKprim = "True"; } } else if("-".equals(specialKprim)) { if(translator != null) { specialKprim = translator.translate("kprim.minus"); } else { specialKprim = "False"; } } return specialKprim; } public String renderTextOrVariables(List<TextOrVariable> textOrVariables) { if(textOrVariables != null && textOrVariables.size() > 0) { textOrVariables.forEach((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, resolvedAssessmentItem, itemSessionState, flow, ubu, translator); } return ""; } public String renderExtendedTextBox(ExtendedTextInteraction interaction) { avc.getHTMLRendererSingleton() .renderExtendedTextBox(renderer, target, avc, assessmentItem, itemSessionState, interaction); return ""; } public String placeholder(Interaction interaction) { if(interaction instanceof StringInteraction) { StringInteraction tei = (StringInteraction)interaction; if(StringHelper.containsNonWhitespace(tei.getPlaceholderText())) { target.append(" placeholder=\"").append(StringHelper.escapeHtml(tei.getPlaceholderText())).append("\""); } } return ""; } public String escapeForJavascriptString(String text) { return escapeJavaScript(text); } public String toString(Identifier identifier) { return identifier == null ? "" : identifier.toString(); } public String toString(Value value) { return toString(value, ",", " "); } public String toString(Value value, String delimiter) { return toString(value, delimiter, " "); } public String toString(NullValue value, String delimiter) { return toString(value, delimiter, " "); } public String toString(Value value, String delimiter, String mappingIndicator) { StringOutput out = new StringOutput(32); renderValue(out, value, delimiter, mappingIndicator); return out.toString(); } public String toJavascriptArguments(List<? extends Choice> choices) { if(choices == null || choices.isEmpty()) return ""; StringBuilder out = new StringBuilder(32); for(Choice choice:choices) { if(out.length() > 0) out.append(","); out.append("'").append(choice.getIdentifier().toString()).append("'"); } return out.toString(); } public String checkJavaScript(ResponseDeclaration declaration, String patternMask) { return AssessmentRenderFunctions.checkJavaScript(declaration, patternMask); } public String shapeToString(Shape value) { return value.name().toLowerCase(); } public String coordsToString(List<Integer> coords) { StringBuilder out = new StringBuilder(); for(Integer coord:coords) { if(out.length() > 0) out.append(","); out.append(coord.intValue()); } return out.toString(); } public List<Integer> maxToList(int max) { List<Integer> list = new ArrayList<>(max); for(int i=1; i<=max; i++) { list.add(i); } return list; } public String subStringBefore(String text, String separator) { if(StringHelper.containsNonWhitespace(text)) { int index = text.indexOf(separator); if(index > 0) { target.append(text.substring(0, index)); } } return ""; } public String subStringAfter(String text, String separator) { if(StringHelper.containsNonWhitespace(text)) { int index = text.indexOf(separator); if(index == 0) { target.append(text); } else if(index > 0 && index < text.length()) { target.append(text.substring(index, text.length())); } } return ""; } public boolean classContains(QtiNode element, String marker) { StringMultipleAttribute css = element.getAttributes().getStringMultipleAttribute("class"); return css != null && css.getValue() != null && css.getValue().contains(marker); } public static class SliderOptions { private final boolean isDiscrete; private final boolean reverse; private final String min; private final String max; private final String step; public SliderOptions(boolean isDiscrete, boolean reverse, String min, String max, String step) { this.isDiscrete = isDiscrete; this.reverse = reverse; this.max = max; this.min = min; this.step = step; } public boolean isDiscrete() { return isDiscrete; } public boolean isReverse() { return reverse; } public String getMin() { return min; } public String getMax() { return max; } public String getStep() { return step; } } public static class OrderChoices { private final List<SimpleChoice> respondedVisibleChoices; private final List<SimpleChoice> unselectedVisibleChoices; public OrderChoices(List<SimpleChoice> respondedVisibleChoices, List<SimpleChoice> unselectedVisibleChoices) { this.respondedVisibleChoices = respondedVisibleChoices; this.unselectedVisibleChoices = unselectedVisibleChoices; } public List<SimpleChoice> getRespondedVisibleChoices() { return respondedVisibleChoices; } public List<SimpleChoice> getUnselectedVisibleChoices() { return unselectedVisibleChoices; } } }