/**
* <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 uk.ac.ed.ph.qtiworks.mathassess.MathAssessConstants.FIELD_PMATHML_IDENTIFIER;
import static uk.ac.ed.ph.qtiworks.mathassess.MathAssessConstants.MATHS_CONTENT_RECORD_VARIABLE_IDENTIFIER;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.jcodec.common.IOUtils;
import org.olat.core.CoreSpringFactory;
import org.olat.core.gui.render.StringOutput;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.ims.qti21.AssessmentTestSession;
import org.olat.ims.qti21.manager.AssessmentTestSessionDAO;
import uk.ac.ed.ph.jqtiplus.attribute.Attribute;
import uk.ac.ed.ph.jqtiplus.node.ForeignElement;
import uk.ac.ed.ph.jqtiplus.node.QtiNode;
import uk.ac.ed.ph.jqtiplus.node.content.basic.TextRun;
import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem;
import uk.ac.ed.ph.jqtiplus.node.item.CorrectResponse;
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.TestFeedback;
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.state.TestSessionState;
import uk.ac.ed.ph.jqtiplus.types.Identifier;
import uk.ac.ed.ph.jqtiplus.types.ResponseData;
import uk.ac.ed.ph.jqtiplus.types.StringResponseData;
import uk.ac.ed.ph.jqtiplus.value.BaseType;
import uk.ac.ed.ph.jqtiplus.value.BooleanValue;
import uk.ac.ed.ph.jqtiplus.value.Cardinality;
import uk.ac.ed.ph.jqtiplus.value.DurationValue;
import uk.ac.ed.ph.jqtiplus.value.FileValue;
import uk.ac.ed.ph.jqtiplus.value.FloatValue;
import uk.ac.ed.ph.jqtiplus.value.IntegerValue;
import uk.ac.ed.ph.jqtiplus.value.ListValue;
import uk.ac.ed.ph.jqtiplus.value.MultipleValue;
import uk.ac.ed.ph.jqtiplus.value.NullValue;
import uk.ac.ed.ph.jqtiplus.value.OrderedValue;
import uk.ac.ed.ph.jqtiplus.value.RecordValue;
import uk.ac.ed.ph.jqtiplus.value.SingleValue;
import uk.ac.ed.ph.jqtiplus.value.StringValue;
import uk.ac.ed.ph.jqtiplus.value.Value;
/**
*
* Initial date: 18.09.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class AssessmentRenderFunctions {
private static final OLog log = Tracing.createLoggerFor(AssessmentRenderFunctions.class);
public static boolean exists(Value value) {
return value != null && !value.isNull();
}
public static boolean isNullValue(Value value) {
return value == null || value.isNull();
}
public static boolean isSingleCardinalityValue(Value value) {
return value != null && value.hasCardinality(Cardinality.SINGLE);
}
//<xsl:sequence select="boolean($valueHolder[@cardinality='multiple'])"/>
public static boolean isMultipleCardinalityValue(Value value) {
return value != null && value.hasCardinality(Cardinality.MULTIPLE);
}
// <xsl:sequence select="boolean($valueHolder[@cardinality='ordered'])"/>
public static boolean isOrderedCardinalityValue(Value value) {
return value != null && value.hasCardinality(Cardinality.ORDERED);
}
// <xsl:sequence select="boolean($valueHolder[@cardinality='record'])"/>
public static boolean isRecordCardinalityValue(Value value) {
return value != null && value.hasCardinality(Cardinality.RECORD);
}
//<xsl:sequence select="boolean($valueHolder[@cardinality='record'
// and qw:value[@baseType='string' and @fieldIdentifier='MathsContentClass'
// and string(qw:value)='org.qtitools.mathassess']])"/>
public static boolean isMathsContentValue(Value value) {
if(value.hasCardinality(Cardinality.RECORD)) {
RecordValue recordValue = (RecordValue)value;
for(Map.Entry<Identifier, SingleValue> entry:recordValue.entrySet()) {
final Identifier itemIdentifier = entry.getKey();
final SingleValue itemValue = entry.getValue();
if(itemValue.hasBaseType(BaseType.STRING)
&& MATHS_CONTENT_RECORD_VARIABLE_IDENTIFIER.equals(itemIdentifier)) {
return true;
}
}
}
return false;
}
// <xsl:sequence select="boolean($element[$overrideTemplate
// or not(@templateIdentifier)
// or (qw:value-contains(qw:get-template-value(@templateIdentifier), @identifier) and not(@showHide='hide'))])"/>
public static boolean isVisible(Choice choice, ItemSessionState iSessionState) {
Value templateValue = choice.getTemplateIdentifier() == null ? null : iSessionState.getTemplateValue(choice.getTemplateIdentifier());
return choice.getTemplateIdentifier() != null
//TODO the check must be checked
|| (templateValue != null && templateValue.toString().equals(choice.getIdentifier().toString()))
|| choice.getVisibilityMode() != VisibilityMode.HIDE_IF_MATCH;
}
//<xsl:if test="qw:is-invalid-response(@responseIdentifier)">
public static boolean isInvalidResponse(ItemSessionState itemSessionState, Identifier identifier) {
//$itemSessionState/@invalidResponseIdentifiers
return itemSessionState.getInvalidResponseIdentifiers().contains(identifier);
}
//<xsl:sequence select="$unboundResponseIdentifiers=$identifier"/>
public static boolean isBadResponse(ItemSessionState itemSessionState, Identifier identifier) {
return itemSessionState.getUnboundResponseIdentifiers().contains(identifier);
}
public static final Value getTemplateValue(ItemSessionState itemSessionState, String identifierAsString) {
Identifier identifier = Identifier.assumedLegal(identifierAsString);
return itemSessionState.getTemplateValues().get(identifier);
}
public static final boolean isTemplateDeclarationAMathVariable(AssessmentItem assessmentItem, String identifierString) {
Identifier identifier = Identifier.assumedLegal(identifierString);
TemplateDeclaration templateDeclaration = assessmentItem.getTemplateDeclaration(identifier);
return templateDeclaration == null ? false : templateDeclaration.getMathVariable();
}
public static final Value getOutcomeValue(ItemSessionState itemSessionState, String identifierAsString) {
Identifier identifier = Identifier.assumedLegal(identifierAsString);
return itemSessionState.getOutcomeValues().get(identifier);
}
public static String getResponseValueAsBase64(AssessmentItem assessmentItem, AssessmentTestSession candidateSession, ItemSessionState itemSessionState,
Identifier identifier, boolean solutionMode) {
Value val = getResponseValue(assessmentItem, itemSessionState, identifier, solutionMode);
String encodedString = null;
if(val instanceof FileValue) {
FileValue fileValue = (FileValue)val;
File myStore = CoreSpringFactory.getImpl(AssessmentTestSessionDAO.class).getSessionStorage(candidateSession);
File submissionDir = new File(myStore, "submissions");
File submittedFile = new File(submissionDir, fileValue.getFileName());
try(InputStream inStream = new FileInputStream(submittedFile)) {
byte[] binaryData = IOUtils.toByteArray(inStream);
encodedString = new String(Base64.encodeBase64(binaryData), "UTF8");
} catch(Exception e) {
log.error("", e);
}
}
return encodedString;
}
public static Value getResponseValue(AssessmentItem assessmentItem, ItemSessionState itemSessionState, Identifier identifier, boolean solutionMode) {
Value responseValue = null;
//<xsl:when test="$solutionMode and $overriddenCorrectResponses[@identifier=$identifier]">
if(solutionMode && itemSessionState.getOverriddenCorrectResponseValue(identifier) != null) {
responseValue = itemSessionState.getOverriddenCorrectResponseValue(identifier);
}
//<xsl:when test="$solutionMode and $responseDeclaration/qti:correctResponse">
else if(solutionMode && getResponseDeclaration(assessmentItem, identifier) != null) {
/* <!-- <correctResponse> has been set in the QTI -->
<!-- (We need to convert QTI <qti:correctResponse/> to <qw:responseVariable/>) -->
<xsl:for-each select="$responseDeclaration/qti:correctResponse">
<qw:responseVariable>
<xsl:copy-of select="../@cardinality, ../@baseType"/>
<xsl:for-each select="qti:value">
<qw:value>
<xsl:copy-of select="@fieldIdentifier, @baseType"/>
<xsl:copy-of select="text()"/>
</qw:value>
</xsl:for-each>
</qw:responseVariable>
</xsl:for-each>
*/
ResponseDeclaration responseDeclaration = getResponseDeclaration(assessmentItem, identifier);
CorrectResponse correctResponse = responseDeclaration.getCorrectResponse();
if(correctResponse != null) {
responseValue = correctResponse.evaluate();
}
}
//<xsl:when test="$uncommittedResponseValues[@identifier=$identifier]">
else if(itemSessionState.getUncommittedResponseValue(identifier) != null) {
responseValue = itemSessionState.getUncommittedResponseValue(identifier);
} else {
responseValue = itemSessionState.getResponseValue(identifier);
}
return responseValue;
}
//<xsl:sequence select="$document/qti:assessmentItem/qti:responseDeclaration[@identifier=$identifier]"/>
public static ResponseDeclaration getResponseDeclaration(AssessmentItem assessmentItem, Identifier identifier) {
return assessmentItem.getResponseDeclaration(identifier);
}
public static ResponseData getResponseInput(ItemSessionState itemSessionState, Identifier identifier) {
ResponseData responseInput = itemSessionState.getRawResponseDataMap().get(identifier);
return responseInput;
}
public static String extractSingleCardinalityResponseInput(ResponseData data) {
if(data instanceof StringResponseData) {
StringResponseData stringData = (StringResponseData)data;
List<String> dataList = stringData.getResponseData();
if(dataList != null && dataList.size() == 1) {
return dataList.get(0);
}
}
return null;
}
public static int getCardinalitySize(Value data) {
int size = 0;
if(data.getCardinality() != null) {
switch(data.getCardinality()) {
case SINGLE:
case RECORD:
size = 1;
break;
case MULTIPLE:
case ORDERED:
size = ((ListValue)data).size();
break;
}
}
return size;
}
public static String extractResponseInputAt(ResponseData data, int index) {
if(data instanceof StringResponseData) {
StringResponseData stringData = (StringResponseData)data;
List<String> dataList = stringData.getResponseData();
if(dataList != null && index < 0 && dataList.size() > index) {
return dataList.get(index);
}
}
return null;
}
public static void renderValue(StringOutput sb, Value valueHolder, String delimiter, String mappingIndicator) {
if(isNullValue(valueHolder)) {
//
} else if(isSingleCardinalityValue(valueHolder)) {
renderSingleCardinalityValue(sb, valueHolder);
} else if (isMathsContentValue(valueHolder)) {
//TODO qti renderMathmlAsString(sb, extractMathsContentPmathml(valueHolder));
sb.append(extractMathsContentPmathml(valueHolder));
} else if(isMultipleCardinalityValue(valueHolder)) {
renderMultipleCardinalityValue(sb, valueHolder, delimiter);
} else if(isOrderedCardinalityValue(valueHolder)) {
renderOrderedCardinalityValue(sb, valueHolder, delimiter);
} else if(isRecordCardinalityValue(valueHolder)) {
renderRecordCardinalityValue(sb, valueHolder, delimiter, mappingIndicator);
} else {
sb.append("printedVariable may not be applied to value ").append(valueHolder.toString());
}
}
public static void renderSingleCardinalityValue(StringOutput sb, Value value) {
if(value != null && !value.isNull() && value.hasCardinality(Cardinality.SINGLE)) {
switch(value.getBaseType()) {
case STRING: sb.append(((StringValue)value).stringValue()); break;
case INTEGER: sb.append(((IntegerValue)value).intValue()); break;
case FLOAT: sb.append(((FloatValue)value).doubleValue()); break;//TODO qti format
case BOOLEAN: sb.append(((BooleanValue)value).booleanValue()); break;
//TODO qti Duration in seconds
case DURATION: sb.append(((DurationValue)value).doubleValue()); break;
//TODO qti File value ???
case FILE: sb.append(((FileValue)value).toQtiString()); break;
case DIRECTED_PAIR:
case PAIR:
case IDENTIFIER:
case POINT:
case URI: sb.append(value.toQtiString()); break;
}
}
}
public static void renderMultipleCardinalityValue(StringOutput sb, Value value, String delimiter) {
if(value != null && value.hasCardinality(Cardinality.MULTIPLE)) {
MultipleValue mValue = (MultipleValue)value;
if(StringHelper.containsNonWhitespace(delimiter)) {
int numOfValues = mValue.size();
for(int i=0; i<numOfValues; i++) {
if(i > 0) sb.append(delimiter);
renderSingleCardinalityValue(sb, mValue.get(i));
}
} else {
mValue.forEach((singleValue) -> renderSingleCardinalityValue(sb, singleValue));
}
}
}
public static void renderOrderedCardinalityValue(StringOutput sb, Value value, String delimiter) {
if(value != null && value.hasCardinality(Cardinality.ORDERED)) {
OrderedValue oValue = (OrderedValue)value;
if(StringHelper.containsNonWhitespace(delimiter)) {
int numOfValues = oValue.size();
for(int i=0; i<numOfValues; i++) {
if(i > 0) sb.append(delimiter);
renderSingleCardinalityValue(sb, oValue.get(i));
}
} else {
oValue.forEach((singleValue) -> renderSingleCardinalityValue(sb, singleValue));
}
}
}
public static void renderRecordCardinalityValue(StringOutput sb, Value value, String delimiter, String mappingIndicator) {
if(value != null && value.hasCardinality(Cardinality.RECORD)) {
RecordValue oValue = (RecordValue)value;
boolean hasDelimiter = StringHelper.containsNonWhitespace(delimiter);
boolean hasMappingIndicator = StringHelper.containsNonWhitespace(mappingIndicator);
int count = 0;
for(Map.Entry<Identifier, SingleValue>entry:oValue.entrySet()) {
if(hasDelimiter && count++ > 0) sb.append(delimiter);
String identifierString = entry.getKey().toString();
sb.append(identifierString);
if(hasMappingIndicator) {
sb.append(mappingIndicator);
}
renderSingleCardinalityValue(sb, entry.getValue());
}
}
}
/*
<xsl:function name="qw:extract-record-field-value" as="xs:string?">
<xsl:param name="valueHolder" as="element()"/>
<xsl:param name="fieldName" as="xs:string"/>
<xsl:choose>
<xsl:when test="qw:is-record-cardinality-value($valueHolder)">
<xsl:value-of select="$valueHolder/qw:value[@fieldIdentifier=$fieldName]"/>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">
Expected value <xsl:copy-of select="$valueHolder"/> to have record
cardinalty.
</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
*/
public static SingleValue extractRecordFieldValue(Value value, Identifier identifier) {
SingleValue mappedValue = null;
if(value != null && identifier != null && value.hasCardinality(Cardinality.RECORD) ) {
RecordValue recordValue = (RecordValue)value;
mappedValue = recordValue.get(identifier);
}
return mappedValue;
}
/*
<xsl:function name="qw:extract-maths-content-pmathml" as="element(m:math)">
<xsl:param name="valueHolder" as="element()"/>
<xsl:choose>
<xsl:when test="qw:is-maths-content-value($valueHolder)">
<xsl:variable name="pmathmlString" select="$valueHolder/qw:value[@fieldIdentifier='PMathML']" as="xs:string"/>
<xsl:variable name="pmathmlDocNode" select="saxon:parse($pmathmlString)" as="document-node()"/>
<xsl:copy-of select="$pmathmlDocNode/*"/>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">
Expected value <xsl:copy-of select="$valueHolder"/> to be a MathsContent value
</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
*/
public static String extractMathsContentPmathml(Value value) {
if(value.hasCardinality(Cardinality.RECORD)) {
RecordValue recordValue = (RecordValue)value;
for(Map.Entry<Identifier, SingleValue> entry:recordValue.entrySet()) {
final Identifier itemIdentifier = entry.getKey();
final SingleValue itemValue = entry.getValue();
if(itemValue.hasBaseType(BaseType.STRING) && FIELD_PMATHML_IDENTIFIER.equals(itemIdentifier)) {
return ((StringValue)itemValue).stringValue();
}
}
}
return "";
}
/*
<xsl:function name="qw:extract-iterable-element" as="xs:string">
<xsl:param name="valueHolder" as="element()"/>
<xsl:param name="index" as="xs:integer"/>
<xsl:choose>
<xsl:when test="qw:is-null-value($valueHolder)">
<xsl:sequence select="''"/>
</xsl:when>
<xsl:when test="qw:is-ordered-cardinality-value($valueHolder) or qw:is-multiple-cardinality-value($valueHolder)">
<xsl:sequence select="string($valueHolder/qw:value[position()=$index])"/>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">
Expected value <xsl:copy-of select="$valueHolder"/> to have ordered
or multiple cardinality
</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
*/
public static final SingleValue extractIterableElement(Value valueHolder, int index) {
SingleValue indexedValue = null;
if(valueHolder != null && !valueHolder.isNull()) {
if(valueHolder.hasCardinality(Cardinality.ORDERED)) {
OrderedValue oValue = (OrderedValue)valueHolder;
if(index >= 0 && index < oValue.size()) {
indexedValue = oValue.get(index);
}
} else if(valueHolder.hasCardinality(Cardinality.MULTIPLE)) {
MultipleValue mValue = (MultipleValue)valueHolder;
if(index >= 0 && index < mValue.size()) {
indexedValue = mValue.get(index);
}
}
}
return indexedValue;
}
/**
* The method only collect text from ForeignElement, but
* recursively.
*
* @param fElement
* @return
*/
public static final String contentAsString(ForeignElement fElement) {
StringBuilder out = new StringBuilder(255);
contentAsString(out, fElement);
return out.toString();
}
private static final void contentAsString(StringBuilder out, ForeignElement fElement) {
for(QtiNode child:fElement.getChildren()) {
switch(child.getQtiClassName()) {
case TextRun.DISPLAY_NAME:
out.append(((TextRun)child).getTextContent());
break;
default: {
if(child instanceof ForeignElement) {
ForeignElement fChild = (ForeignElement)child;
contentAsString(out, fChild);
}
}
}
}
}
public static String checkJavaScript(ResponseDeclaration declaration, String patternMask) {
List<String> checks = new ArrayList<>(3);
// NB: We don't presently do any JS checks for numeric values bound to records, as the JS isn't currently
// clever enough to handle all numeric formats (e.g. 4e12)
if(declaration != null) {
if(declaration.getBaseType().isFloat()) {
checks.add("float");
}
if(declaration.getBaseType().isInteger()) {
checks.add("integer");
}
}
if(StringHelper.containsNonWhitespace(patternMask)) {
checks.add("regex");
checks.add(patternMask);
}
if(checks == null || checks.isEmpty()) {
return null;
}
StringBuilder out = new StringBuilder(128);
out.append("QtiWorksRendering.validateInput(this");
for(String check:checks) {
out.append(",'").append(check).append("'");
}
out.append(");");
return out.toString();
}
//value-contains
public static final boolean valueContains(Value value, Identifier identifier) {
if(value != null && !value.isNull()) {
//TODO mimic the XSLT
return value.toQtiString().contains(identifier.toString());
/*
if(value.hasBaseType(BaseType.IDENTIFIER)) {
IdentifierValue identifierValue = (IdentifierValue)value;
return identifierValue.identifierValue().equals(identifier);
}
*/
}
return false;
}
public static boolean valueContains(Value value, String string) {
if(value == null || value.isNull() || value instanceof NullValue) {
return false;
} else {
return value.toQtiString().contains(string);//TODO qti perhaps must match closer for MultipleValue
}
}
/**
* Mimic the @class
* @param node
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static final String getAtClass(QtiNode node) {
Attribute classAttribute = node.getAttributes().get("class");
return classAttribute.toDomAttributeValue(classAttribute.getValue());
}
/**
* Return only value if the attribute is a legal html attribute. the list doesn't
* include some HTML 5 like contenteditable (content is never editable but by QTI runtime).
*
* @param attribute
* @return
*/
@SuppressWarnings({ "rawtypes" })
public static final String getHtmlAttributeValue(AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, Attribute attribute) {
String value;
String name = attribute.getLocalName();
switch(name) {
case "accesskey":
case "alt":
case "class":
case "contextmenu":
case "dir":
case "hidden":
case "name":
case "id":
case "lang":
case "encoding":
case "tabindex":
case "title":
case "style":
case "width":
case "height":
value = getDomAttributeValue(attribute);
break;
case "href":
case "src":
String uri = getDomAttributeValue(attribute);
value = convertLink(component, resolvedAssessmentItem, uri);
break;
default:
value = null;
}
return value;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private static final String getDomAttributeValue(Attribute attribute) {
String value;
if(attribute.isSet()) {
value = attribute.toDomAttributeValue(attribute.getValue());
} else {
value = null;
}
return value;
}
/*
<xsl:function name="qw:convert-link" as="xs:string">
<xsl:param name="uri" as="xs:string"/>
<xsl:choose>
<xsl:when test="starts-with($uri, 'http:') or starts-with($uri, 'https:') or starts-with($uri, 'mailto:')">
<xsl:sequence select="$uri"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="resolved" as="xs:string" select="string(resolve-uri($uri, $systemId))"/>
<xsl:sequence select="concat($webappContextPath, $serveFileUrl, '?href=', encode-for-uri($resolved))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
*/
public static final String convertLink(AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, String uri) {
if(uri != null && (uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("mailto:"))) {
return uri;
}
String filename = getLinkFilename(uri);
String relativePath = component.relativePathTo(resolvedAssessmentItem);
return component.getMapperUri() + "/" + filename + "?href=" + relativePath + (uri == null ? "" : uri);
}
public static final String convertSubmissionLink(AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, String uri) {
String filename = getLinkFilename(uri);
String relativePath = component.relativePathTo(resolvedAssessmentItem);
return component.getSubmissionMapperUri() + "/submissions/" + filename + "?href=" + relativePath + (uri == null ? "" : uri);
}
private static final String getLinkFilename(String uri) {
String filename = "file";
try {
if(StringHelper.containsNonWhitespace(uri)) {
int lastIndex = uri.lastIndexOf('/');
if(lastIndex >= 0 && lastIndex + 1 < uri.length()) {
filename = uri.substring(lastIndex + 1, uri.length());
} else {
filename = uri;
}
}
} catch (Exception e) {
log.error("", e);
}
return filename;
}
public static final boolean testFeedbackVisible(TestFeedback testFeedback, TestSessionState testSessionState) {
//<xsl:variable name="identifierMatch" select="boolean(qw:value-contains(qw:get-test-outcome-value(@outcomeIdentifier), @identifier))" as="xs:boolean"/>
Identifier outcomeIdentifier = testFeedback.getOutcomeIdentifier();
Value outcomeValue = testSessionState.getOutcomeValue(outcomeIdentifier);
boolean identifierMatch = valueContains(outcomeValue, testFeedback.getOutcomeValue());
//<xsl:if test="($identifierMatch and @showHide='show') or (not($identifierMatch) and @showHide='hide')">
if((identifierMatch && testFeedback.getVisibilityMode() == VisibilityMode.SHOW_IF_MATCH)
|| (!identifierMatch && testFeedback.getVisibilityMode() == VisibilityMode.HIDE_IF_MATCH)) {
return true;
}
return false;
}
}