diff --git a/pom.xml b/pom.xml index b96ea30393721ab5bdea9e3d41561dd550be937b..bc8e9d941fb93422ffd6ba09cb33e35abb1cc8b1 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,7 @@ <org.infinispan.version>6.0.2.Final</org.infinispan.version> <lucene.version>4.8.0</lucene.version> <version.selenium>2.43.1</version.selenium> + <qtiworks.version>1.0-SNAPSHOT</qtiworks.version> <!-- properties for testing and Q&A --> <!-- by default no tests are executed so far (April 2011). Use appropriate profiles and properties on the command line --> @@ -1793,6 +1794,39 @@ <artifactId>reload-moonunit</artifactId> <version>reload_dist255-moonunit.jar</version> </dependency> + <dependency> + <groupId>uk.ac.ed.ph.qtiworks</groupId> + <artifactId>qtiworks-jqtiplus</artifactId> + <version>${qtiworks.version}</version> + <exclusions> + <exclusion> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>uk.ac.ed.ph.qtiworks</groupId> + <artifactId>qtiworks-mathassess</artifactId> + <version>${qtiworks.version}</version> + <exclusions> + <exclusion> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>uk.ac.ed.ph.qtiworks</groupId> + <artifactId>qtiworks-mathassess-glue</artifactId> + <version>${qtiworks.version}</version> + <exclusions> + <exclusion> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </exclusion> + </exclusions> + </dependency> <dependency> <groupId>rome</groupId> <artifactId>rome</artifactId> diff --git a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties index eefa93270e9e9dd5bceaba0c07068802c6e34ead..298817839a15e6fe916d1938344dd791df3867f3 100644 --- a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties @@ -19,6 +19,7 @@ FileResource.FILE=Datei FileResource.GLOSSARY=Glossar FileResource.IMAGE=Bild FileResource.IMSCP=CP-Lerninhalt +FileResource.IMSQTI21=IMS QTI 2.1 FileResource.MOVIE=Film FileResource.PDF=PDF FileResource.PODCAST=Podcast diff --git a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties index 375a5a5a8b9a8e2566220cd3b9603db6e2cb7379..4492912e0cbacb0a7d2bce1efb2adddc1f0f8384 100644 --- a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties @@ -19,6 +19,7 @@ FileResource.FILE=File FileResource.GLOSSARY=Glossary FileResource.IMAGE=Image FileResource.IMSCP=CP learning content +FileResource.IMSQTI21=IMS QTI 2.1 FileResource.MOVIE=Movie FileResource.PDF=PDF FileResource.PODCAST=Podcast diff --git a/src/main/java/org/olat/core/commons/fullWebApp/_content/fullwebapplayout.html b/src/main/java/org/olat/core/commons/fullWebApp/_content/fullwebapplayout.html index d7b84fab82e5fbf859a94737f7123741004bdb49..02cf111b35595d6cc453911febb570ff635a42e4 100644 --- a/src/main/java/org/olat/core/commons/fullWebApp/_content/fullwebapplayout.html +++ b/src/main/java/org/olat/core/commons/fullWebApp/_content/fullwebapplayout.html @@ -98,6 +98,7 @@ function o_start(){ #else <link type="text/css" href='$r.staticLink("js/js.plugins.min.css")' rel="stylesheet" /> #end +<link type="text/css" href='$r.staticLink("assessment/rendering/css/assessment.css")' rel="stylesheet" /> <script type="text/javascript"> /* <![CDATA[ */ diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/Form.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/Form.java index 4ddd5c6f48981376972b3046fc158c00fbe2c42f..018c0c6e1fac654f29dae55b7e3f90b5b0c9c382 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/Form.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/Form.java @@ -154,6 +154,8 @@ public class Form extends LogDelegator { private String formName; private String dispatchFieldId; private String eventFieldId; + + private boolean standaloneRendering; // the real form private FormItemContainer formLayout; @@ -212,6 +214,14 @@ public class Form extends LogDelegator { return form; } + public boolean isStandaloneRendering() { + return standaloneRendering; + } + + public void setStandaloneRendering(boolean standaloneRendering) { + this.standaloneRendering = standaloneRendering; + } + /** * @param ureq */ @@ -403,6 +413,7 @@ public class Form extends LogDelegator { } else { // Get parameters the standard way logDebug("Dispatching non-multipart form", null); + Set<String> keys = ureq.getParameterSet(); for (String key : keys) { String[] values = ureq.getHttpReq().getParameterValues(key); diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/FormWrapperContainer.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/FormWrapperContainer.java index 07e4ebb555b4d149a4bb6e17ce377163ad6f02f8..ba4759e95166c81da46fb8dc1ec78bcf256e600b 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/FormWrapperContainer.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/FormWrapperContainer.java @@ -55,6 +55,10 @@ class FormWrapperContainer extends AbstractComponent implements ComponentCollect this.form = form; firstInit = false; } + + public boolean isStandaloneRendering() { + return form.isStandaloneRendering(); + } public String getDispatchFieldId() { return form.getDispatchFieldId(); diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/FormWrapperContainerRenderer.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/FormWrapperContainerRenderer.java index b07c33c9d1776f441b8ab5dffd77f35987e5620f..57ac59d475ddae892e6e838b3dc87b0f83c67014 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/FormWrapperContainerRenderer.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/FormWrapperContainerRenderer.java @@ -74,6 +74,10 @@ class FormWrapperContainerRenderer implements ComponentRenderer { boolean hasRenderInstr = (args != null && args.length > 0); if (toRender != null) { + if(formC.isStandaloneRendering()) { + renderer.render(sb, toRender, args); + } else { + AJAXFlags flags = renderer.getGlobalSettings().getAjaxFlags(); boolean iframePostEnabled = flags.isIframePostEnabled(); /* @@ -126,6 +130,7 @@ class FormWrapperContainerRenderer implements ComponentRenderer { * FORM SUBMIT on keypress enter */ sb.append(FormJSHelper.submitOnKeypressEnter(formC.getFormName())); + } } } diff --git a/src/main/java/org/olat/fileresource/types/ImsQTI21Resource.java b/src/main/java/org/olat/fileresource/types/ImsQTI21Resource.java new file mode 100644 index 0000000000000000000000000000000000000000..ec96be98e85aad4435a5b68ffec8343a4120452a --- /dev/null +++ b/src/main/java/org/olat/fileresource/types/ImsQTI21Resource.java @@ -0,0 +1,200 @@ +/** +* OLAT - Online Learning and Training<br> +* http://www.olat.org +* <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 +* <p> +* http://www.apache.org/licenses/LICENSE-2.0 +* <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> +* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> +* University of Zurich, Switzerland. +* <hr> +* <a href="http://www.openolat.org"> +* OpenOLAT - Online Learning and Training</a><br> +* This file has been modified by the OpenOLAT community. Changes are licensed +* under the Apache 2.0 license as the original file. +*/ +package org.olat.fileresource.types; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.PathUtils; +import org.olat.ims.qti21.QTI21ContentPackage; + +import uk.ac.ed.ph.jqtiplus.reading.QtiXmlReader; +import uk.ac.ed.ph.jqtiplus.xmlutils.XmlReadResult; +import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ChainedResourceLocator; +import uk.ac.ed.ph.jqtiplus.xmlutils.locators.NetworkHttpResourceLocator; +import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator; + +/** + * Description:<br> + * Try to validate the resource against QTI 2.1 + * + * <P> + * Initial Date: Jun 24, 2009 <br> + * @author matthai + */ +public class ImsQTI21Resource extends FileResource { + + private static final OLog log = Tracing.createLoggerFor(ImsQTI21Resource.class); + private static final String IMS_MANIFEST = "imsmanifest.xml"; + + /** + * IMS QTI 21 file resource identifier. + */ + public static final String TYPE_NAME = "FileResource.IMSQTI21"; + + public ImsQTI21Resource() { + super(TYPE_NAME); + } + + public static ResourceEvaluation evaluate(File file, String filename) { + ResourceEvaluation eval = new ResourceEvaluation(); + try { + ImsManifestFileFilter visitor = new ImsManifestFileFilter(); + Path fPath = PathUtils.visit(file, filename, visitor); + if(visitor.isValid()) { + Path realManifestPath = visitor.getManifestPath(); + Path manifestPath = fPath.resolve(realManifestPath); + + RootSearcher rootSearcher = new RootSearcher(); + Files.walkFileTree(fPath, rootSearcher); + if(rootSearcher.foundRoot()) { + manifestPath = rootSearcher.getRoot().resolve(IMS_MANIFEST); + } else { + manifestPath = fPath.resolve(IMS_MANIFEST); + } + + QTI21ContentPackage cp = new QTI21ContentPackage(manifestPath); + if(validateImsManifest(cp, new PathResourceLocator(manifestPath.getParent()))) { + eval.setValid(true); + } else { + eval.setValid(false); + } + } else { + eval.setValid(false); + } + } catch (IOException e) { + log.error("", e); + eval.setValid(false); + } + return eval; + } + + + public static boolean validateImsManifest(QTI21ContentPackage cp, ResourceLocator resourceLocator) { + try { + URI test = cp.getTest().toUri(); + ResourceLocator chainedResourceLocator = createResolvingResourceLocator(resourceLocator); + XmlReadResult result = new QtiXmlReader().read(chainedResourceLocator, test, true); + return result != null && result.isSchemaValid(); + } catch (Exception e) { + log.error("", e); + return false; + } + } + + public static boolean validate(File resource) { + try { + PathResourceLocator resourceLocator = new PathResourceLocator(resource.getParentFile().toPath()); + ResourceLocator chainedResourceLocator = createResolvingResourceLocator(resourceLocator); + XmlReadResult result = new QtiXmlReader().read(chainedResourceLocator, resource.toURI(), true); + return result != null && result.isSchemaValid(); + } catch (Exception e) { + log.error("", e); + return false; + } + } + + public static ResourceLocator createResolvingResourceLocator(ResourceLocator resourceLocator) { + final ResourceLocator result = new ChainedResourceLocator( + resourceLocator, + QtiXmlReader.JQTIPLUS_PARSER_RESOURCE_LOCATOR, /* (to resolve internal HTTP resources, e.g. RP templates) */ + new NetworkHttpResourceLocator() /* (to resolve external HTTP resources, e.g. RP templates, external items) */ + ); + return result; + } + + public static class PathResourceLocator implements ResourceLocator { + + private static final OLog log = Tracing.createLoggerFor(PathResourceLocator.class); + + private Path root; + + public PathResourceLocator(Path root) { + this.root = root; + } + + @Override + public InputStream findResource(URI systemId) { + if ("file".equals(systemId.getScheme())) { + try { + return new FileInputStream(new File(systemId)); + } catch (final Exception e) { + log.info("File {} does not exist:" + systemId); + return null; + } + } else if("jar".equals(systemId.getScheme())) { + try { + String toPath = systemId.toString(); + if(toPath.contains("!")) { + int index = toPath.indexOf('!'); + String relative = toPath.substring(index + 1); + Path newPath = root.resolve(relative); + return Files.newInputStream(newPath); + } + + } catch (Exception e) { + log.error("File {} does not exist:" + systemId, e); + return null; + } + } + return null; + } + } + + private static class ImsManifestFileFilter extends SimpleFileVisitor<Path> { + private boolean manifestFile; + private Path manifestPath; + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + + String filename = file.getFileName().toString(); + if(IMS_MANIFEST.equals(filename)) { + manifestFile = true; + manifestPath = file; + } + return manifestFile ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE; + } + + public boolean isValid() { + return manifestFile; + } + + public Path getManifestPath() { + return manifestPath; + } + } +} diff --git a/src/main/java/org/olat/ims/_spring/imsContext.xml b/src/main/java/org/olat/ims/_spring/imsContext.xml index adda71c27cc64e0c23cd432abfe185e714144d11..3f4cfc84f975ddf27b198230fd1fb3defa018011 100644 --- a/src/main/java/org/olat/ims/_spring/imsContext.xml +++ b/src/main/java/org/olat/ims/_spring/imsContext.xml @@ -11,6 +11,7 @@ <context:component-scan base-package="org.olat.ims.lti.manager,org.olat.ims.qti.qpool,org.olat.ims.qti.statistics" /> <import resource="classpath:/org/olat/ims/qti/_spring/qtiContext.xml"/> + <import resource="classpath:/org/olat/ims/qti21/_spring/qti21Context.xml"/> <bean id="org.olat.ims.cp.CPManager" class="org.olat.ims.cp.CPManagerImpl"/> diff --git a/src/main/java/org/olat/ims/cp/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/cp/ui/_i18n/LocalStrings_en.properties index 6099059bc25c3e0647bef338025bda56673717c6..f33de473c5e519b2bda7b1b8be97a4054c98d8cb 100644 --- a/src/main/java/org/olat/ims/cp/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/cp/ui/_i18n/LocalStrings_en.properties @@ -1,5 +1,6 @@ #Fri Feb 24 11:55:40 CET 2012 FileResource.IMSCP=CP learning content +FileResource.IMSQTI21=IMS QTI 2.1 chelp.cpeditorhelp.add=Select the page you want to add a sub-page to before clicking on the button "Add page." Your sub-page will be added right underneath the page selected, however, you can still move your sub-page. chelp.cpeditorhelp.add.title=Add page chelp.cpeditorhelp.copy=To copy a page along with its sub-pages just select that page and click on the button "Copy page" in the title bar. diff --git a/src/main/java/org/olat/ims/qti21/QTI21ContentPackage.java b/src/main/java/org/olat/ims/qti21/QTI21ContentPackage.java new file mode 100644 index 0000000000000000000000000000000000000000..660a06cbc89842821594720f146a4c19f8af0a89 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/QTI21ContentPackage.java @@ -0,0 +1,178 @@ +/* +<LICENCE> + +Copyright (c) 2008, University of Southampton +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 Southampton 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. + +</LICENCE> +*/ + +package org.olat.ims.qti21; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathException; +import javax.xml.xpath.XPathFactory; + +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +public class QTI21ContentPackage { + public static final String MANIFEST_FILE_NAME = "imsmanifest.xml"; + private static final String [] TEST_EXPRESSION = + { + "/imscp:manifest/imscp:resources/imscp:resource[@type='imsqti_test_xmlv2p1']/@href", + "/imscp:manifest/imscp:resources/imscp:resource[@type='imsqti_assessment_xmlv2p1']/@href" /* incorrect -- for backwards compatibility */ + }; + private static final String [] ITEM_EXPRESSION = + { + "/imscp:manifest/imscp:resources/imscp:resource[@type='imsqti_item_xmlv2p0']/@href", + "/imscp:manifest/imscp:resources/imscp:resource[@type='imsqti_item_xmlv2p1']/@href", + "/imscp:manifest/imscp:resources/imscp:resource[@type='imsqti_item_xml_v2p1']/@href" /* for aqurate */ + }; + + private class QTINamespaceContext implements NamespaceContext + { + private static final String DEFAULT_NAME_SPACE = "imscp"; + private static final String DEFAULT_NAME_SPACE_URI = "http://www.imsglobal.org/xsd/imscp_v1p1"; + + private String prefix; + private String uri; + + public QTINamespaceContext() + { + this(DEFAULT_NAME_SPACE, DEFAULT_NAME_SPACE_URI); + } + + public QTINamespaceContext(String prefix, String uri) + { + this.prefix = prefix; + this.uri = uri; + } + + public String getNamespaceURI(String prefix) + { + if (prefix.equals(this.prefix)) + return uri; + else if (prefix.equals(XMLConstants.XML_NS_PREFIX)) + return XMLConstants.XML_NS_URI; + else if (prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) + return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; + else + return ""; + } + + public String getPrefix(String uri) + { + if (uri.equals(this.uri)) + return prefix; + else if (uri.equals(XMLConstants.XML_NS_URI)) + return XMLConstants.XML_NS_PREFIX; + else if (uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) + return XMLConstants.XMLNS_ATTRIBUTE; + else + return null; + } + + public Iterator<String> getPrefixes(String namespaceURI) + { + Collection<String> collection = new ArrayList<String>(); + collection.add(getPrefix(namespaceURI)); + + return collection.iterator(); + } + } + + private Path manifestFile; + + public QTI21ContentPackage(Path file) { + this.manifestFile = file; + } + + public Path getManifest() { + return manifestFile; + } + + public Path getTest() throws IOException { + NodeList list = null; + for (String s : TEST_EXPRESSION) { + list = getNodeList(s); + if (list.getLength() > 0) + break; + } + + if (list == null) { + throw new IOException("Cannot find test."); + } + + return manifestFile.getParent().resolve(list.item(0).getNodeValue()); + } + + public Path[] getItems() { + List<NodeList> list = new ArrayList<NodeList>(); + int length=0; + for (String s : ITEM_EXPRESSION) { + NodeList l = getNodeList(s); + list.add(l); + length += l.getLength(); + } + + Path[] result = new Path[length]; + + int j=0; + for (NodeList l : list) { + for (int i=0; i < l.getLength(); i++, j++) + result[j] = manifestFile.getParent().resolve(l.item(i).getNodeValue()); + } + + return result; + } + + private NodeList getNodeList(String expression) { + try (InputStream input=Files.newInputStream(manifestFile)) { + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(new QTINamespaceContext()); + InputSource source = new InputSource(input); + + return (NodeList) xpath.evaluate(expression, source, XPathConstants.NODESET); + } catch (IOException | XPathException ex) { + throw new RuntimeException("", ex); + } + } +} diff --git a/src/main/java/org/olat/ims/qti21/QTI21Module.java b/src/main/java/org/olat/ims/qti21/QTI21Module.java new file mode 100644 index 0000000000000000000000000000000000000000..34c3470d56db8b9e05df9844c7bd64cd91d9d407 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/QTI21Module.java @@ -0,0 +1,58 @@ +/** + * <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 org.olat.core.configuration.AbstractSpringModule; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.ims.qti21.repository.handlers.QTI21AssessmentHandler; +import org.olat.repository.handlers.RepositoryHandlerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 11.12.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Service +public class QTI21Module extends AbstractSpringModule { + + @Autowired + private QTI21AssessmentHandler assessmentHandler; + + @Autowired + public QTI21Module(CoordinatorManager coordinatorManager) { + super(coordinatorManager); + } + + @Override + public void init() { + RepositoryHandlerFactory.registerHandler(assessmentHandler, 10); + } + + @Override + protected void initFromChangedProperties() { + // + } + + + +} diff --git a/src/main/java/org/olat/ims/qti21/RequestTimestampContext.java b/src/main/java/org/olat/ims/qti21/RequestTimestampContext.java new file mode 100644 index 0000000000000000000000000000000000000000..b1a27957e42d81311808d24fd2ba16c28cc2be49 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/RequestTimestampContext.java @@ -0,0 +1,41 @@ +/** + * <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 java.util.Date; + +/** + * + * Initial date: 11.12.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class RequestTimestampContext { + + private Date currentRequestTimestamp; + + public Date getCurrentRequestTimestamp() { + if(currentRequestTimestamp == null) { + currentRequestTimestamp = new Date(); + } + return currentRequestTimestamp; + } + +} diff --git a/src/main/java/org/olat/ims/qti21/_spring/qti21Context.xml b/src/main/java/org/olat/ims/qti21/_spring/qti21Context.xml new file mode 100644 index 0000000000000000000000000000000000000000..1cbe50ad4b1cb43ccb4cc0b46737a6787af0d731 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/_spring/qti21Context.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context.xsd"> + + <context:component-scan base-package="org.olat.ims.qti21,uk.ac.ed.ph.jqtiplus" /> + + +</beans> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentHandler.java b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..659d0a587d1a7e0f34a2ae5c72474459df18cd42 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentHandler.java @@ -0,0 +1,168 @@ +/** + * <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.repository.handlers; + +import java.io.File; +import java.util.Locale; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.persistence.DBFactory; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.layout.MainLayoutController; +import org.olat.core.gui.control.generic.wizard.StepsMainRunController; +import org.olat.core.id.Identity; +import org.olat.core.id.OLATResourceable; +import org.olat.core.util.coordinate.LockResult; +import org.olat.fileresource.FileResourceManager; +import org.olat.fileresource.types.FileResource; +import org.olat.fileresource.types.ImsQTI21Resource; +import org.olat.fileresource.types.ResourceEvaluation; +import org.olat.ims.qti21.ui.QTI21DisplayController; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryService; +import org.olat.repository.handlers.EditionSupport; +import org.olat.repository.handlers.FileHandler; +import org.olat.repository.model.RepositoryEntrySecurity; +import org.olat.repository.ui.RepositoryEntryRuntimeController; +import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; +import org.olat.resource.OLATResource; +import org.olat.resource.OLATResourceManager; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 08.12.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Service +public class QTI21AssessmentHandler extends FileHandler { + + @Override + public String getSupportedType() { + return ImsQTI21Resource.TYPE_NAME; + } + + @Override + public boolean isCreate() { + return false; + } + + @Override + public String getCreateLabelI18nKey() { + return "tools.add.qti21"; + } + + @Override + public RepositoryEntry createResource(Identity initialAuthor, String displayname, String description, + Object createObject, Locale locale) { + return null; + } + + @Override + public boolean isPostCreateWizardAvailable() { + return false; + } + + @Override + public ResourceEvaluation acceptImport(File file, String filename) { + return ImsQTI21Resource.evaluate(file, filename); + } + + @Override + public RepositoryEntry importResource(Identity initialAuthor, String initialAuthorAlt, String displayname, String description, + boolean withReferences, Locale locale, File file, String filename) { + ImsQTI21Resource ores = new ImsQTI21Resource(); + OLATResource resource = OLATResourceManager.getInstance().createAndPersistOLATResourceInstance(ores); + File fResourceFileroot = FileResourceManager.getInstance().getFileResourceRootImpl(resource).getBasefile(); + File zipDir = new File(fResourceFileroot, FileResourceManager.ZIPDIR); + FileResource.copyResource(file, filename, zipDir); + + RepositoryEntry re = CoreSpringFactory.getImpl(RepositoryService.class) + .create(initialAuthor, null, "", displayname, description, resource, RepositoryEntry.ACC_OWNERS); + DBFactory.getInstance().commit(); + return re; + } + + @Override + public RepositoryEntry copy(RepositoryEntry source, RepositoryEntry target) { + return null; + } + + @Override + public boolean supportsDownload() { + return true; + } + + @Override + public boolean supportsLaunch() { + return true; + } + + @Override + public EditionSupport supportsEdit(OLATResourceable resource) { + return EditionSupport.no; + } + + @Override + public MainLayoutController createLaunchController(RepositoryEntry re, RepositoryEntrySecurity reSecurity, + UserRequest ureq, WindowControl wControl) { + return new RepositoryEntryRuntimeController(ureq, wControl, re, reSecurity, + new RuntimeControllerCreator() { + @Override + public Controller create(UserRequest uureq, WindowControl wwControl, TooledStackedPanel toolbarPanel, RepositoryEntry entry, RepositoryEntrySecurity security) { + return new QTI21DisplayController(uureq, wwControl, entry); + } + }); + } + + @Override + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl, TooledStackedPanel toolbar) { + return null; + } + + @Override + public StepsMainRunController createWizardController(OLATResourceable res, UserRequest ureq, WindowControl wControl) { + return null; + } + + @Override + public LockResult acquireLock(OLATResourceable ores, Identity identity) { + return null; + } + + @Override + public void releaseLock(LockResult lockResult) { + // + } + + @Override + public boolean isLocked(OLATResourceable ores) { + return false; + } + + @Override + protected String getDeletedFilePrefix() { + return null; + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21Component.java b/src/main/java/org/olat/ims/qti21/ui/QTI21Component.java new file mode 100644 index 0000000000000000000000000000000000000000..dbeaf32489057d804bf7ebe47c8140281673b4d6 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/QTI21Component.java @@ -0,0 +1,106 @@ +/** + * <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; + +import java.net.URI; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.AbstractComponent; +import org.olat.core.gui.components.ComponentRenderer; +import org.olat.core.gui.render.ValidationResult; +import org.olat.ims.qti21.RequestTimestampContext; + +import uk.ac.ed.ph.jqtiplus.running.TestSessionController; +import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator; + +/** + * + * Initial date: 10.12.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21Component extends AbstractComponent { + + private static final QTI21ComponentRenderer RENDERER = new QTI21ComponentRenderer(); + + private URI assessmentObjectUri; + private ResourceLocator resourceLocator; + private TestSessionController testSessionController; + private RequestTimestampContext requestTimestampContext; + + private final QTI21FormItem qtiItem; + + public QTI21Component(String name, QTI21FormItem qtiItem) { + super(name); + this.qtiItem = qtiItem; + } + + public QTI21FormItem getQtiItem() { + return qtiItem; + } + + public ResourceLocator getResourceLocator() { + return resourceLocator; + } + + public void setResourceLocator(ResourceLocator resourceLocator) { + this.resourceLocator = resourceLocator; + } + + public TestSessionController getTestSessionController() { + return testSessionController; + } + + public void setTestSessionController(TestSessionController testSessionController) { + this.testSessionController = testSessionController; + } + + public RequestTimestampContext getRequestTimestampContext() { + return requestTimestampContext; + } + + public void setRequestTimestampContext(RequestTimestampContext requestTimestampContext) { + this.requestTimestampContext = requestTimestampContext; + } + + public URI getAssessmentObjectUri() { + return assessmentObjectUri; + } + + public void setAssessmentObjectUri(URI assessmentObjectUri) { + this.assessmentObjectUri = assessmentObjectUri; + } + + @Override + protected void doDispatchRequest(UserRequest ureq) { + // + } + + @Override + public void validate(UserRequest ureq, ValidationResult vr) { + super.validate(ureq, vr); + vr.getJsAndCSSAdder().addRequiredStaticJsFile("assessment/rendering/javascript/QtiWorksRendering.js"); + } + + @Override + public ComponentRenderer getHTMLRendererSingleton() { + return RENDERER; + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21ComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/QTI21ComponentRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..1e71f445bb8c9600df29d7554c0a4d661e0b9fab --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/QTI21ComponentRenderer.java @@ -0,0 +1,199 @@ +/** + * <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; + +import java.io.StringWriter; +import java.io.Writer; +import java.util.Date; + +import javax.xml.transform.stream.StreamResult; + +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.DefaultComponentRenderer; +import org.olat.core.gui.components.form.flexible.impl.Form; +import org.olat.core.gui.control.winmgr.AJAXFlags; +import org.olat.core.gui.render.RenderResult; +import org.olat.core.gui.render.Renderer; +import org.olat.core.gui.render.StringOutput; +import org.olat.core.gui.render.URLBuilder; +import org.olat.core.gui.translator.Translator; +import org.olat.ims.qti21.RequestTimestampContext; +import org.olat.ims.qti21.ui.rendering.AbstractRenderingOptions; +import org.olat.ims.qti21.ui.rendering.AbstractRenderingRequest; +import org.olat.ims.qti21.ui.rendering.AssessmentRenderer; +import org.olat.ims.qti21.ui.rendering.SerializationMethod; +import org.olat.ims.qti21.ui.rendering.TestRenderingOptions; +import org.olat.ims.qti21.ui.rendering.TestRenderingRequest; + +import uk.ac.ed.ph.jqtiplus.running.TestSessionController; +import uk.ac.ed.ph.jqtiplus.state.TestSessionState; + +/** + * + * Initial date: 10.12.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21ComponentRenderer extends DefaultComponentRenderer { + + + private AssessmentRenderer assessmentRenderer = new AssessmentRenderer(); + + @Override + public void render(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, + Translator translator, RenderResult renderResult, String[] args) { + + AJAXFlags flags = renderer.getGlobalSettings().getAjaxFlags(); + boolean iframePostEnabled = flags.isIframePostEnabled(); + + QTI21Component cmp = (QTI21Component)source; + TestSessionController testSessionController = cmp.getTestSessionController(); + QTI21FormItem item = cmp.getQtiItem(); + Component rootFormCmp = item.getRootForm().getInitialComponent(); + + URLBuilder formUbuBuilder = renderer.getUrlBuilder().createCopyFor(rootFormCmp); + StringOutput formUrl = new StringOutput(); + formUbuBuilder.buildURI(formUrl, + new String[] { Form.FORMID, "dispatchuri", "dispatchevent" }, + new String[] { Form.FORMCMD, item.getFormDispatchId(), "0" }, + iframePostEnabled ? AJAXFlags.MODE_TOBGIFRAME : AJAXFlags.MODE_NORMAL); + + /* Create appropriate options that link back to this controller */ + final String sessionBaseUrl = formUrl.toString(); + final TestRenderingOptions renderingOptions = new TestRenderingOptions(); + configureBaseRenderingOptions(sessionBaseUrl, renderingOptions); + renderingOptions.setTestPartNavigationUrl(sessionBaseUrl + "/test-part-navigation"); + renderingOptions.setSelectTestItemUrl(sessionBaseUrl + "/select-item"); + renderingOptions.setAdvanceTestItemUrl(sessionBaseUrl + "/finish-item"); + renderingOptions.setReviewTestPartUrl(sessionBaseUrl + "/review-test-part"); + renderingOptions.setReviewTestItemUrl(sessionBaseUrl + "/review-item"); + renderingOptions.setShowTestItemSolutionUrl(sessionBaseUrl + "/item-solution"); + renderingOptions.setEndTestPartUrl(sessionBaseUrl + "/end-test-part"); + renderingOptions.setAdvanceTestPartUrl(sessionBaseUrl + "/advance-test-part"); + renderingOptions.setExitTestUrl(sessionBaseUrl + "/exit-test"); + + Writer writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + renderCurrentCandidateTestSessionState(testSessionController, renderingOptions, result, cmp); + + String output = writer.toString(); + output = replaces(output); + sb.append(output); + } + + private String replaces(String result) { + int index = result.indexOf("<body"); + String output = result.substring(index + 5); + index = output.indexOf("</body>"); + output = output.substring(0, index); + output = output.replace("<form action", "<form target='oaa0' action"); + return "<div " + output + "</div>"; + } + + private void configureBaseRenderingOptions(final String sessionBaseUrl, final AbstractRenderingOptions renderingOptions) { + renderingOptions.setSerializationMethod(SerializationMethod.HTML5_MATHJAX); + renderingOptions.setSourceUrl(sessionBaseUrl + "/source"); + renderingOptions.setStateUrl(sessionBaseUrl + "/state"); + renderingOptions.setResultUrl(sessionBaseUrl + "/result"); + renderingOptions.setValidationUrl(sessionBaseUrl + "/validation"); + renderingOptions.setServeFileUrl(sessionBaseUrl + "/file"); + renderingOptions.setAuthorViewUrl(sessionBaseUrl + "/author-view"); + renderingOptions.setResponseUrl(sessionBaseUrl + "/response"); + } + + private void renderCurrentCandidateTestSessionState(TestSessionController testSessionController, + TestRenderingOptions renderingOptions, StreamResult result, QTI21Component component) { + TestSessionState testSessionState = testSessionController.getTestSessionState(); + /*final CandidateSession candidateSession = candidateSessionContext.getCandidateSession(); + if (candidateSession.isExploded()) { + renderExploded(candidateSessionContext, renderingOptions, result); + } + else if (candidateSession.isTerminated()) { + renderTerminated(candidateSessionContext, renderingOptions, result); + } + else {*/ + /* Look up most recent event */ + // final CandidateEvent latestEvent = assertSessionEntered(candidateSession); + + /* Load the TestSessionState and create a TestSessionController */ + //final TestSessionState testSessionState = candidateDataService.loadTestSessionState(latestEvent); + //final TestSessionController testSessionController = createTestSessionController(candidateSession, testSessionState); + + /* Touch the session's duration state if appropriate */ + if (testSessionState.isEntered() && !testSessionState.isEnded()) { + RequestTimestampContext requestTimestampContext = component.getRequestTimestampContext(); + final Date timestamp = requestTimestampContext.getCurrentRequestTimestamp(); + testSessionController.touchDurations(timestamp); + } + + /* Render event */ + renderTestEvent(testSessionController, renderingOptions, result, component); + // } + } + + private void renderTestEvent(final TestSessionController testSessionController, + final TestRenderingOptions renderingOptions, final StreamResult result, QTI21Component component) { + //final CandidateTestEventType testEventType = candidateEvent.getTestEventType(); + //final CandidateSession candidateSession = candidateEvent.getCandidateSession(); + + /* Create and partially configure rendering request */ + final TestRenderingRequest renderingRequest = new TestRenderingRequest(); + initRenderingRequest(renderingRequest, renderingOptions, component); + renderingRequest.setTestSessionController(testSessionController); + + /* If session has terminated, render appropriate state and exit */ + final TestSessionState testSessionState = testSessionController.getTestSessionState(); + if (/* candidateSession.isTerminated() ||*/ testSessionState.isExited()) { + //assessmentRenderer.renderTeminated(createTerminatedRenderingRequest(candidateSessionContext, renderingRequest.getRenderingOptions()), result); + return; + } + + /* Check for "modal" events first. These cause a particular rendering state to be + * displayed, which candidate will then leave. + */ + /*if (testEventType==CandidateTestEventType.REVIEW_ITEM) { + // Extract item to review + renderingRequest.setTestRenderingMode(TestRenderingMode.ITEM_REVIEW); + renderingRequest.setModalItemKey(extractTargetItemKey(candidateEvent)); + } + else if (testEventType==CandidateTestEventType.SOLUTION_ITEM) { + // Extract item to show solution + renderingRequest.setTestRenderingMode(TestRenderingMode.ITEM_SOLUTION); + renderingRequest.setModalItemKey(extractTargetItemKey(candidateEvent)); + }*/ + + /* Pass to rendering layer */ + assessmentRenderer.renderTest(renderingRequest, result); + } + + private <P extends AbstractRenderingOptions> void initRenderingRequest( + final AbstractRenderingRequest<P> renderingRequest, final P renderingOptions, QTI21Component component) { + + renderingRequest.setRenderingOptions(renderingOptions); + renderingRequest.setAssessmentResourceLocator(component.getResourceLocator()); + renderingRequest.setAssessmentResourceUri(component.getAssessmentObjectUri()); + renderingRequest.setAuthorMode(false); + renderingRequest.setValidated(true); + renderingRequest.setLaunchable(true); + renderingRequest.setErrorCount(0); + renderingRequest.setWarningCount(0); + renderingRequest.setValid(true); + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21DisplayController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21DisplayController.java new file mode 100644 index 0000000000000000000000000000000000000000..061b6221867867bf94715164a071bd192ab96022 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/QTI21DisplayController.java @@ -0,0 +1,276 @@ +/** + * <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; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Path; +import java.util.Date; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +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.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.logging.OLATRuntimeException; +import org.olat.fileresource.FileResourceManager; +import org.olat.fileresource.types.ImsQTI21Resource; +import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator; +import org.olat.ims.qti21.QTI21ContentPackage; +import org.olat.ims.qti21.RequestTimestampContext; +import org.olat.repository.RepositoryEntry; + +import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager; +import uk.ac.ed.ph.jqtiplus.JqtiPlus; +import uk.ac.ed.ph.jqtiplus.node.AssessmentObjectType; +import uk.ac.ed.ph.jqtiplus.node.result.AssessmentResult; +import uk.ac.ed.ph.jqtiplus.notification.NotificationLevel; +import uk.ac.ed.ph.jqtiplus.notification.NotificationRecorder; +import uk.ac.ed.ph.jqtiplus.provision.BadResourceException; +import uk.ac.ed.ph.jqtiplus.reading.AssessmentObjectXmlLoader; +import uk.ac.ed.ph.jqtiplus.reading.QtiModelBuildingError; +import uk.ac.ed.ph.jqtiplus.reading.QtiXmlInterpretationException; +import uk.ac.ed.ph.jqtiplus.reading.QtiXmlReader; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentObject; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentTest; +import uk.ac.ed.ph.jqtiplus.running.TestPlanner; +import uk.ac.ed.ph.jqtiplus.running.TestProcessingInitializer; +import uk.ac.ed.ph.jqtiplus.running.TestSessionController; +import uk.ac.ed.ph.jqtiplus.running.TestSessionControllerSettings; +import uk.ac.ed.ph.jqtiplus.state.TestPlan; +import uk.ac.ed.ph.jqtiplus.state.TestProcessingMap; +import uk.ac.ed.ph.jqtiplus.state.TestSessionState; +import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator; + +/** + * + * Initial date: 08.12.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21DisplayController extends FormBasicController { + + private File fUnzippedDirRoot; + + private QTI21FormItem qtiEl; + private TestSessionController testSessionController; + + private RequestTimestampContext requestTimestampContext = new RequestTimestampContext(); + private JqtiExtensionManager jqtiExtensionManager = new JqtiExtensionManager(); + + public QTI21DisplayController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry) { + super(ureq, wControl, "run"); + + FileResourceManager frm = FileResourceManager.getInstance(); + fUnzippedDirRoot = frm.unzipFileResource(entry.getOlatResource()); + + testSessionController = enterSession(); + + /* Handle immediate end of test session */ + if (testSessionController.getTestSessionState().isEnded()) { + //candidateSessionFinisher.finishCandidateSession(candidateSession, assessmentResult); + } + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + qtiEl = new QTI21FormItem("qtirun"); + formLayout.add("qtirun", qtiEl); + + mainForm.setStandaloneRendering(true); + + ResourceLocator fileResourceLocator = new PathResourceLocator(fUnzippedDirRoot.toPath()); + final ResourceLocator inputResourceLocator = + ImsQTI21Resource.createResolvingResourceLocator(fileResourceLocator); + qtiEl.setResourceLocator(inputResourceLocator); + qtiEl.setRequestTimestampContext(requestTimestampContext); + qtiEl.setTestSessionController(testSessionController); + qtiEl.setAssessmentObjectUri(createAssessmentObjectUri()); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formOK(UserRequest ureq) { + // + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source == qtiEl) { + if(event instanceof QTI21FormEvent) { + QTI21FormEvent qe = (QTI21FormEvent)event; + switch(qe.getCommand()) { + case "select-item": doSelectItem(qe.getSubCommand()); break; + default: {} + } + } + } + super.formInnerEvent(ureq, source, event); + } + + private void doSelectItem(String subCommand) { + System.out.println(subCommand); + } + + //private CandidateSession enterCandidateSession(final CandidateSession candidateSession) + private TestSessionController enterSession() { + /* Set up listener to record any notifications */ + final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO); + + /* Create fresh JQTI+ state & controller for it */ + TestSessionController testSessionController = createNewTestSessionStateAndController(notificationRecorder); + if (testSessionController==null) { + return null; + } + + /* Initialise test state and enter test */ + final TestSessionState testSessionState = testSessionController.getTestSessionState(); + final Date timestamp = requestTimestampContext.getCurrentRequestTimestamp(); + try { + testSessionController.initialize(timestamp); + final int testPartCount = testSessionController.enterTest(timestamp); + if (testPartCount==1) { + /* If there is only testPart, then enter this (if possible). + * (Note that this may cause the test to exit immediately if there is a failed + * preCondition on this part.) + */ + testSessionController.enterNextAvailableTestPart(timestamp); + } + else { + /* Don't enter first testPart yet - we shall tell candidate that + * there are multiple parts and let them enter manually. + */ + } + } + catch (final RuntimeException e) { + logError("", e); + return null; + } + + return testSessionController; + } + + + public AssessmentResult computeTestAssessmentResult(final String candidateSessionId, final TestSessionController testSessionController) { + String qtiWorksBaseUrl = null;//TODO + final URI sessionIdentifierSourceId = URI.create(qtiWorksBaseUrl); + final String sessionIdentifier = "testsession/" + candidateSessionId; + return testSessionController.computeAssessmentResult(requestTimestampContext.getCurrentRequestTimestamp(), + sessionIdentifier, sessionIdentifierSourceId); + } + + private TestSessionController createNewTestSessionStateAndController(NotificationRecorder notificationRecorder) { + TestProcessingMap testProcessingMap = getTestProcessingMap(); + /* Generate a test plan for this session */ + final TestPlanner testPlanner = new TestPlanner(testProcessingMap); + if (notificationRecorder!=null) { + testPlanner.addNotificationListener(notificationRecorder); + } + final TestPlan testPlan = testPlanner.generateTestPlan(); + + final TestSessionState testSessionState = new TestSessionState(testPlan); + + final TestSessionControllerSettings testSessionControllerSettings = new TestSessionControllerSettings(); + testSessionControllerSettings.setTemplateProcessingLimit(computeTemplateProcessingLimit()); + + /* Create controller and wire up notification recorder */ + final TestSessionController result = new TestSessionController(jqtiExtensionManager, + testSessionControllerSettings, testProcessingMap, testSessionState); + if (notificationRecorder!=null) { + result.addNotificationListener(notificationRecorder); + } + return result; + } + + private TestProcessingMap getTestProcessingMap() { + boolean assessmentPackageIsValid = true; + + final ResolvedAssessmentTest resolvedAssessmentTest = loadAndResolveAssessmentObject(); + BadResourceException ex = resolvedAssessmentTest.getTestLookup().getBadResourceException(); + if(ex instanceof QtiXmlInterpretationException) { + QtiXmlInterpretationException exml = (QtiXmlInterpretationException)ex; + System.out.println(exml.getInterpretationFailureReason()); + for(QtiModelBuildingError err :exml.getQtiModelBuildingErrors()) { + System.out.println(err); + } + } + + TestProcessingInitializer initializer = new TestProcessingInitializer(resolvedAssessmentTest, assessmentPackageIsValid); + TestProcessingMap result = initializer.initialize(); + return result; + } + + public <E extends ResolvedAssessmentObject<?>> E loadAndResolveAssessmentObject() { + + QtiXmlReader qtiXmlReader = new QtiXmlReader(jqtiExtensionManager); + + ResourceLocator fileResourceLocator = new PathResourceLocator(fUnzippedDirRoot.toPath()); + final ResourceLocator inputResourceLocator = + ImsQTI21Resource.createResolvingResourceLocator(fileResourceLocator); + final URI assessmentObjectSystemId = createAssessmentObjectUri(); + final AssessmentObjectXmlLoader assessmentObjectXmlLoader = new AssessmentObjectXmlLoader(qtiXmlReader, inputResourceLocator); + final AssessmentObjectType assessmentObjectType = AssessmentObjectType.ASSESSMENT_TEST; + E result; + if (assessmentObjectType==AssessmentObjectType.ASSESSMENT_ITEM) { + result = (E) assessmentObjectXmlLoader.loadAndResolveAssessmentItem(assessmentObjectSystemId); + } + else if (assessmentObjectType==AssessmentObjectType.ASSESSMENT_TEST) { + result = (E) assessmentObjectXmlLoader.loadAndResolveAssessmentTest(assessmentObjectSystemId); + } + else { + throw new OLATRuntimeException("Unexpected branch " + assessmentObjectType, null); + } + return result; + } + + public URI createAssessmentObjectUri() { + File manifestPath = new File(fUnzippedDirRoot, "imsmanifest.xml"); + QTI21ContentPackage cp = new QTI21ContentPackage(manifestPath.toPath()); + try { + Path testPath = cp.getTest(); + return testPath.toUri(); + } catch (IOException e) { + logError("", e); + } + return null; + } + + /** + * Request limit configured outer of the QTI 2.1 file. + * @return + */ + public int computeTemplateProcessingLimit() { + final Integer requestedLimit = null;// deliverySettings.getTemplateProcessingLimit(); + if (requestedLimit==null) { + /* Not specified, so use default */ + return JqtiPlus.DEFAULT_TEMPLATE_PROCESSING_LIMIT; + } + final int requestedLimitIntValue = requestedLimit.intValue(); + return requestedLimitIntValue > 0 ? requestedLimitIntValue : JqtiPlus.DEFAULT_TEMPLATE_PROCESSING_LIMIT; + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21FormEvent.java b/src/main/java/org/olat/ims/qti21/ui/QTI21FormEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..40fbde7b88e3df37f839b7b8f1fb4ff2bf091fe0 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/QTI21FormEvent.java @@ -0,0 +1,45 @@ +/** + * <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; + +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; + +/** + * + * Initial date: 11.12.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21FormEvent extends FormEvent { + + private static final long serialVersionUID = 5391738662661690060L; + + private final String subCommand; + + public QTI21FormEvent(String command, String subCommand, FormItem source) { + super(command, source); + this.subCommand = subCommand; + } + + public String getSubCommand() { + return subCommand; + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21FormItem.java b/src/main/java/org/olat/ims/qti21/ui/QTI21FormItem.java new file mode 100644 index 0000000000000000000000000000000000000000..1490d42d7d68db1f08fedf6d5527827c70646571 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/QTI21FormItem.java @@ -0,0 +1,105 @@ +/** + * <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; + +import java.net.URI; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.form.flexible.impl.FormItemImpl; +import org.olat.ims.qti21.RequestTimestampContext; + +import uk.ac.ed.ph.jqtiplus.running.TestSessionController; +import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator; + +/** + * + * Initial date: 11.12.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21FormItem extends FormItemImpl { + + private final QTI21Component component; + + public QTI21FormItem(String name) { + super(name); + component = new QTI21Component(name + "_cmp", this); + } + + public URI getAssessmentObjectUri() { + return component.getAssessmentObjectUri(); + } + + public void setAssessmentObjectUri(URI assessmentObjectUri) { + component.setAssessmentObjectUri(assessmentObjectUri); + } + + public TestSessionController getTestSessionController() { + return component.getTestSessionController(); + } + + public void setTestSessionController(TestSessionController testSessionController) { + component.setTestSessionController(testSessionController); + } + + public RequestTimestampContext getRequestTimestampContext() { + return component.getRequestTimestampContext(); + } + + public void setRequestTimestampContext(RequestTimestampContext requestTimestampContext) { + component.setRequestTimestampContext(requestTimestampContext); + } + + public ResourceLocator getResourceLocator() { + return component.getResourceLocator(); + } + + public void setResourceLocator(ResourceLocator resourceLocator) { + component.setResourceLocator(resourceLocator); + } + + @Override + protected Component getFormItemComponent() { + return component; + } + + @Override + protected void rootFormAvailable() { + // + } + + private static final String SELECT_ITEM = "/select-item/"; + + @Override + public void evalFormRequest(UserRequest ureq) { + String uri = ureq.getModuleURI(); + if(uri.startsWith(SELECT_ITEM)) { + String sub = uri.substring(SELECT_ITEM.length()); + QTI21FormEvent event = new QTI21FormEvent("select-item", sub, this); + getRootForm().fireFormEvent(ureq, event); + } + } + + @Override + public void reset() { + // + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/_content/run.html b/src/main/java/org/olat/ims/qti21/ui/_content/run.html new file mode 100644 index 0000000000000000000000000000000000000000..ac2ba9b1cf2a96d8c89efab780e20e579cddeda7 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/_content/run.html @@ -0,0 +1,2 @@ +<h1>Run</h1> +$r.render("qtirun") \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/AbstractRenderingOptions.java b/src/main/java/org/olat/ims/qti21/ui/rendering/AbstractRenderingOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..8c169295fe4b73e425a6a6bc5c2035dddc400ccd --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/AbstractRenderingOptions.java @@ -0,0 +1,158 @@ +/* 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 java.io.Serializable; + +import uk.ac.ed.ph.jqtiplus.internal.util.ObjectUtilities; + +/** + * General base options for passing to the {@link AssessmentRenderer}. + * + * @author David McKain + */ +public abstract class AbstractRenderingOptions implements Serializable { + + private static final long serialVersionUID = 971871443108075384L; + + /** Required {@link SerializationMethod} */ + + private SerializationMethod serializationMethod; + + /** Encoding to use (when sending the result to an OutputStream). UTF-8 will be used if not specified */ + private String encoding; + + private String responseUrl; + + private String serveFileUrl; + + private String authorViewUrl; + + private String sourceUrl; + + private String stateUrl; + + private String resultUrl; + + private String validationUrl; + + //---------------------------------------------------- + + public SerializationMethod getSerializationMethod() { + return serializationMethod; + } + + public void setSerializationMethod(final SerializationMethod serializationMethod) { + this.serializationMethod = serializationMethod; + } + + + public String getEncoding() { + return encoding; + } + + public void setEncoding(final String encoding) { + this.encoding = encoding; + } + + + public String getResponseUrl() { + return responseUrl; + } + + public void setResponseUrl(final String responseUrl) { + this.responseUrl = responseUrl; + } + + + public String getServeFileUrl() { + return serveFileUrl; + } + + public void setServeFileUrl(final String serveFileUrl) { + this.serveFileUrl = serveFileUrl; + } + + + public String getAuthorViewUrl() { + return authorViewUrl; + } + + public void setAuthorViewUrl(final String authorViewUrl) { + this.authorViewUrl = authorViewUrl; + } + + + public String getSourceUrl() { + return sourceUrl; + } + + public void setSourceUrl(final String sourceUrl) { + this.sourceUrl = sourceUrl; + } + + + public String getStateUrl() { + return stateUrl; + } + + public void setStateUrl(final String stateUrl) { + this.stateUrl = stateUrl; + } + + + public String getResultUrl() { + return resultUrl; + } + + public void setResultUrl(final String resultUrl) { + this.resultUrl = resultUrl; + } + + + public String getValidationUrl() { + return validationUrl; + } + + public void setValidationUrl(final String validationUrl) { + this.validationUrl = validationUrl; + } + + //---------------------------------------------------- + + @Override + public String toString() { + return ObjectUtilities.beanToString(this); + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/AbstractRenderingRequest.java b/src/main/java/org/olat/ims/qti21/ui/rendering/AbstractRenderingRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..715b9642e98ccf04342b146be5c36daf50953732 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/AbstractRenderingRequest.java @@ -0,0 +1,154 @@ +/* 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 java.net.URI; + +import uk.ac.ed.ph.jqtiplus.internal.util.ObjectUtilities; +import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator; + +/** + * Base for {@link ItemRenderingRequest} and {@link TestRenderingRequest} containing things + * that are required in both cases. + * + * @param <P> specific subtype of {@link AbstractRenderingOptions} attached to this request. + * + * @author David McKain + */ +public abstract class AbstractRenderingRequest<P extends AbstractRenderingOptions> { + + private P renderingOptions; + + private ResourceLocator assessmentResourceLocator; + + private URI assessmentResourceUri; + + private boolean authorMode; + + /* Validation information copied from AssessmentPackage */ + private boolean validated; + private boolean launchable; + private int errorCount; + private int warningCount; + private boolean valid; + + //---------------------------------------------------- + + public P getRenderingOptions() { + return renderingOptions; + } + + public void setRenderingOptions(final P renderingOptions) { + this.renderingOptions = renderingOptions; + } + + + public ResourceLocator getAssessmentResourceLocator() { + return assessmentResourceLocator; + } + + public void setAssessmentResourceLocator(final ResourceLocator assessmentResourceLocator) { + this.assessmentResourceLocator = assessmentResourceLocator; + } + + + public URI getAssessmentResourceUri() { + return assessmentResourceUri; + } + + public void setAssessmentResourceUri(final URI assessmentResourceUri) { + this.assessmentResourceUri = assessmentResourceUri; + } + + + public boolean isValidated() { + return validated; + } + + public void setValidated(final boolean validated) { + this.validated = validated; + } + + + public boolean isLaunchable() { + return launchable; + } + + public void setLaunchable(final boolean launchable) { + this.launchable = launchable; + } + + + public int getErrorCount() { + return errorCount; + } + + public void setErrorCount(final int errorCount) { + this.errorCount = errorCount; + } + + + public int getWarningCount() { + return warningCount; + } + + public void setWarningCount(final int warningCount) { + this.warningCount = warningCount; + } + + + public boolean isValid() { + return valid; + } + + public void setValid(final boolean valid) { + this.valid = valid; + } + + + public boolean isAuthorMode() { + return authorMode; + } + + public void setAuthorMode(final boolean authorMode) { + this.authorMode = authorMode; + } + + //---------------------------------------------------- + + @Override + public final String toString() { + return ObjectUtilities.beanToString(this); + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/AssessmentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/rendering/AssessmentRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..c2d634311a9a8cca2c45bdc24e1762a58b4a60ce --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/AssessmentRenderer.java @@ -0,0 +1,576 @@ +/* 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 java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringReader; +import java.io.Writer; +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.PostConstruct; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Transformer; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamResult; + +import org.olat.core.helpers.Settings; +import org.olat.core.util.WebappHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.validation.BeanPropertyBindingResult; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; + +import uk.ac.ed.ph.jqtiplus.internal.util.Assert; +import uk.ac.ed.ph.jqtiplus.node.test.NavigationMode; +import uk.ac.ed.ph.jqtiplus.node.test.TestPart; +import uk.ac.ed.ph.jqtiplus.running.TestSessionController; +import uk.ac.ed.ph.jqtiplus.state.EffectiveItemSessionControl; +import uk.ac.ed.ph.jqtiplus.state.ItemSessionState; +import uk.ac.ed.ph.jqtiplus.state.TestPartSessionState; +import uk.ac.ed.ph.jqtiplus.state.TestPlanNode; +import uk.ac.ed.ph.jqtiplus.state.TestPlanNodeKey; +import uk.ac.ed.ph.jqtiplus.state.TestSessionState; +import uk.ac.ed.ph.jqtiplus.state.marshalling.ItemSessionStateXmlMarshaller; +import uk.ac.ed.ph.jqtiplus.state.marshalling.TestSessionStateXmlMarshaller; +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.SimpleXsltStylesheetCache; +import uk.ac.ed.ph.jqtiplus.xmlutils.xslt.XsltStylesheetManager; + +/** + * This key service performs the actual rendering of items and tests, supporting the + * rendering of specific states and particular modal states (e.g. reviewing an item + * in a test during testPart review). + * <p> + * This stands separately from the main QTIWorks domain model and is potentially usable + * outside of the rest of the QTIWorks engine. I've chosen not to make it a separate module + * for now. + * + * <h2>Usage</h2> + * <ul> + * <li>An instance of this class is safe to use concurrently by multiple threads.</li> + * <li>If using outside QTIWorks engine, remember to set the necessary properties then call {@link #init()}</li> + * </ul> + * + * @author David McKain + */ +public class AssessmentRenderer { + + private static final Logger logger = LoggerFactory.getLogger(AssessmentRenderer.class); + + private static final URI serializeXsltUri = URI.create("classpath:/rendering-xslt/serialize.xsl"); + private static final URI ctopXsltUri = URI.create("classpath:/rendering-xslt/ctop.xsl"); + private static final URI itemStandaloneXsltUri = URI.create("classpath:/rendering-xslt/item-standalone.xsl"); + private static final URI testItemXsltUri = URI.create("classpath:/rendering-xslt/test-item.xsl"); + private static final URI testEntryXsltUri = URI.create("classpath:/rendering-xslt/test-entry.xsl"); + private static final URI testPartNavigationXsltUri = URI.create("classpath:/rendering-xslt/test-testpart-navigation.xsl"); + private static final URI testPartFeedbackXsltUri = URI.create("classpath:/rendering-xslt/test-testpart-feedback.xsl"); + private static final URI testFeedbackXsltUri = URI.create("classpath:/rendering-xslt/test-feedback.xsl"); + private static final URI itemAuthorViewXsltUri = URI.create("classpath:/rendering-xslt/item-author-view.xsl"); + private static final URI testAuthorViewXsltUri = URI.create("classpath:/rendering-xslt/test-author-view.xsl"); + private static final URI terminatedXsltUri = URI.create("classpath:/rendering-xslt/terminated.xsl"); + private static final URI explodedXsltUri = URI.create("classpath:/rendering-xslt/exploded.xsl"); + + + + /** Manager for the XSLT stylesheets, created during init. */ + private XsltStylesheetManager stylesheetManager; + + //---------------------------------------------------- + + + + + //---------------------------------------------------- + public AssessmentRenderer() { + this.stylesheetManager = new XsltStylesheetManager(new ClassPathResourceLocator(), new SimpleXsltStylesheetCache()); + } + + //---------------------------------------------------- + + /** + * Renders the given {@link ItemRenderingRequest}, sending the result to the provided JAXP {@link Result}. + * <p> + * The rendering shows the current state of the item, unless {@link ItemRenderingRequest#isSolutionMode()} + * returns true, in which case the model solution is rendered. + * <p> + * NB: If you're using a {@link StreamResult} then you probably want to wrap it around an + * {@link OutputStream} rather than a {@link Writer}. Remember that you are responsible for + * closing the {@link OutputStream} or {@link Writer} afterwards! + * The caller is responsible for closing this stream afterwards. + */ + public void renderItem(final ItemRenderingRequest request, final Result result) { + Assert.notNull(request, "request"); + Assert.notNull(result, "result"); + + /* Check request is valid */ + final BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "itemRenderingRequest"); + //jsr303Validator.validate(request, errors); + if (errors.hasErrors()) { + throw new IllegalArgumentException("Invalid " + request.getClass().getSimpleName() + + " Object: " + errors); + } + + /* Pass request info to XSLT as parameters */ + final Map<String, Object> xsltParameters = new HashMap<String, Object>(); + setBaseRenderingParameters(xsltParameters, request, null); + + /* Pass ItemSessionState (as DOM Document) */ + final ItemSessionState itemSessionState = request.getItemSessionState(); + xsltParameters.put("itemSessionState", ItemSessionStateXmlMarshaller.marshal(itemSessionState).getDocumentElement()); + + /* Set control parameters */ + xsltParameters.put("prompt", request.getPrompt()); + xsltParameters.put("solutionMode", Boolean.valueOf(request.isSolutionMode())); + xsltParameters.put("endAllowed", Boolean.valueOf(request.isEndAllowed())); + xsltParameters.put("softSoftResetAllowed", Boolean.valueOf(request.isSoftResetAllowed())); + xsltParameters.put("hardResetAllowed", Boolean.valueOf(request.isHardResetAllowed())); + xsltParameters.put("solutionAllowed", Boolean.valueOf(request.isSolutionAllowed())); + xsltParameters.put("candidateCommentAllowed", Boolean.valueOf(request.isCandidateCommentAllowed())); + + /* Set action URLs */ + final ItemRenderingOptions renderingOptions = request.getRenderingOptions(); + xsltParameters.put("endUrl", renderingOptions.getEndUrl()); + xsltParameters.put("softResetUrl", renderingOptions.getSoftResetUrl()); + xsltParameters.put("hardResetUrl", renderingOptions.getHardResetUrl()); + xsltParameters.put("exitUrl", renderingOptions.getExitUrl()); + xsltParameters.put("solutionUrl", renderingOptions.getSolutionUrl()); + + /* Perform transform */ + doTransform(request, itemStandaloneXsltUri, xsltParameters, result); + } + + /** + * Renders the given {@link TestRenderingRequest}, sending the result to the + * provided JAXP {@link Result}. + * <p> + * NB: If you're using a {@link StreamResult} then you probably want to wrap it around an + * {@link OutputStream} rather than a {@link Writer}. Remember that you are responsible for + * closing the {@link OutputStream} or {@link Writer} afterwards! + * + * @throws QtiWorksRenderingException if an unexpected Exception happens during rendering + */ + public void renderTest(final TestRenderingRequest request, final Result result) { + Assert.notNull(request, "renderingRequest"); + Assert.notNull(result, "result"); + + /* Check request is valid */ + final BeanPropertyBindingResult errors = new BeanPropertyBindingResult(request, "testRenderingRequest"); + //jsr303Validator.validate(request, errors); + if (errors.hasErrors()) { + throw new IllegalArgumentException("Invalid " + request.getClass().getSimpleName() + + " Object: " + errors); + } + + /* Set up general XSLT parameters */ + final Map<String, Object> xsltParameters = new HashMap<String, Object>(); + setBaseRenderingParameters(xsltParameters, request, null); + + final TestSessionController testSessionController = request.getTestSessionController(); + final TestSessionState testSessionState = testSessionController.getTestSessionState(); + xsltParameters.put("testSessionState", TestSessionStateXmlMarshaller.marshal(testSessionState).getDocumentElement()); + xsltParameters.put("testSystemId", request.getAssessmentResourceUri().toString()); + + /* Pass rendering options */ + final TestRenderingOptions renderingOptions = request.getRenderingOptions(); + xsltParameters.put("testPartNavigationUrl", renderingOptions.getTestPartNavigationUrl()); + xsltParameters.put("selectTestItemUrl", renderingOptions.getSelectTestItemUrl()); + xsltParameters.put("advanceTestItemUrl", renderingOptions.getAdvanceTestItemUrl()); + xsltParameters.put("endTestPartUrl", renderingOptions.getEndTestPartUrl()); + xsltParameters.put("reviewTestPartUrl", renderingOptions.getReviewTestPartUrl()); + xsltParameters.put("reviewTestItemUrl", renderingOptions.getReviewTestItemUrl()); + xsltParameters.put("showTestItemSolutionUrl", renderingOptions.getShowTestItemSolutionUrl()); + xsltParameters.put("advanceTestPartUrl", renderingOptions.getAdvanceTestPartUrl()); + xsltParameters.put("exitTestUrl", renderingOptions.getExitTestUrl()); + + final TestRenderingMode testRenderingMode = request.getTestRenderingMode(); + if (testRenderingMode==TestRenderingMode.ITEM_REVIEW) { + doRenderTestItemReview(request, xsltParameters, result); + } + else if (testRenderingMode==TestRenderingMode.ITEM_SOLUTION) { + doRenderTestItemSolution(request, xsltParameters, result); + } + else { + /* Render current state */ + final TestPlanNodeKey currentTestPartKey = testSessionState.getCurrentTestPartKey(); + if (testSessionState.isEnded()) { + /* At end of test, so show overall test feedback */ + doRenderTestFeedback(request, xsltParameters, result); + } + else if (currentTestPartKey!=null) { + final TestPartSessionState currentTestPartSessionState = testSessionState.getTestPartSessionStates().get(currentTestPartKey); + final TestPlanNodeKey currentItemKey = testSessionState.getCurrentItemKey(); + if (currentItemKey!=null) { + /* An item is selected, so render it in appropriate state */ + doRenderCurrentTestItem(request, xsltParameters, result); + } + else { + /* No item selected */ + if (currentTestPartSessionState.isEnded()) { + /* testPart has ended, so must be showing testPart feedback */ + doRenderTestPartFeedback(request, xsltParameters, result); + } + else { + /* testPart not ended, so we must be showing the navigation menu in nonlinear mode */ + doRenderTestPartNavigation(request, xsltParameters, result); + } + } + } + else { + /* No current testPart == start of multipart test */ + doRenderTestEntry(request, xsltParameters, result); + } + } + } + + private void doRenderTestEntry(final TestRenderingRequest request, + final Map<String, Object> xsltParameters, final Result result) { + doTransform(request, testEntryXsltUri, xsltParameters, result); + } + + private void doRenderTestPartNavigation(final TestRenderingRequest request, + final Map<String, Object> xsltParameters, final Result result) { + /* Determine whether candidate may exist testPart */ + final TestSessionController testSessionController = request.getTestSessionController(); + xsltParameters.put("endTestPartAllowed", Boolean.valueOf(testSessionController.mayEndCurrentTestPart())); + + doTransform(request, testPartNavigationXsltUri, xsltParameters, result); + } + + private void doRenderTestPartFeedback(final TestRenderingRequest request, + final Map<String, Object> xsltParameters, final Result result) { + doTransform(request, testPartFeedbackXsltUri, xsltParameters, result); + } + + private void doRenderTestFeedback(final TestRenderingRequest request, + final Map<String, Object> xsltParameters, final Result result) { + doTransform(request, testFeedbackXsltUri, xsltParameters, result); + } + + private void doRenderCurrentTestItem(final TestRenderingRequest request, + final Map<String, Object> xsltParameters, final Result result) { + /* Extract the item to be rendered */ + final TestSessionController testSessionController = request.getTestSessionController(); + final TestSessionState testSessionState = testSessionController.getTestSessionState(); + final TestPlanNodeKey itemKey = testSessionState.getCurrentItemKey(); + + /* Set item parameters */ + final URI itemSystemId = setTestItemParameters(request, itemKey, xsltParameters); + + /* Set specific parameters for this rendering */ + final TestPart currentTestPart = testSessionController.getCurrentTestPart(); + final NavigationMode navigationMode = currentTestPart.getNavigationMode(); + xsltParameters.put("reviewMode", Boolean.FALSE); + xsltParameters.put("solutionMode", Boolean.FALSE); + xsltParameters.put("advanceTestItemAllowed", Boolean.valueOf(navigationMode==NavigationMode.LINEAR && testSessionController.mayAdvanceItemLinear())); + xsltParameters.put("testPartNavigationAllowed", Boolean.valueOf(navigationMode==NavigationMode.NONLINEAR)); + xsltParameters.put("endTestPartAllowed", Boolean.valueOf(navigationMode==NavigationMode.LINEAR && testSessionController.mayEndCurrentTestPart())); + + /* We finally do the transform on the _item_ (NB!) */ + doTransform(request, itemSystemId, testItemXsltUri, xsltParameters, result); + } + + private void doRenderTestItemReview(final TestRenderingRequest request, + final Map<String, Object> xsltParameters, final Result result) { + /* Extract item to review */ + final TestPlanNodeKey reviewItemKey = request.getModalItemKey(); + + /* Set item parameters */ + final URI itemSystemId = setTestItemParameters(request, reviewItemKey, xsltParameters); + + /* Set specific parameters for this rendering */ + xsltParameters.put("reviewMode", Boolean.TRUE); + xsltParameters.put("solutionMode", Boolean.FALSE); + xsltParameters.put("testPartNavigationAllowed", Boolean.FALSE); + xsltParameters.put("advanceTestItemAllowed", Boolean.FALSE); + xsltParameters.put("endTestPartAllowed", Boolean.FALSE); + + /* We finally do the transform on the _item_ (NB!) */ + doTransform(request, itemSystemId, testItemXsltUri, xsltParameters, result); + } + + private void doRenderTestItemSolution(final TestRenderingRequest request, + final Map<String, Object> xsltParameters, final Result result) { + /* Extract item to review */ + final TestPlanNodeKey solutionItemKey = request.getModalItemKey(); + + /* Set item parameters */ + final URI itemSystemId = setTestItemParameters(request, solutionItemKey, xsltParameters); + + /* Set specific parameters for this rendering */ + xsltParameters.put("reviewMode", Boolean.TRUE); + xsltParameters.put("solutionMode", Boolean.TRUE); + xsltParameters.put("testPartNavigationAllowed", Boolean.FALSE); + xsltParameters.put("advanceTestItemAllowed", Boolean.FALSE); + xsltParameters.put("endTestPartAllowed", Boolean.FALSE); + + /* We finally do the transform on the _item_ (NB!) */ + doTransform(request, itemSystemId, testItemXsltUri, xsltParameters, result); + } + + /** + * Renders a terminated session, sending the result to the provided JAXP {@link Result}. + * <p> + * NB: If you're using a {@link StreamResult} then you probably want to wrap it around an + * {@link OutputStream} rather than a {@link Writer}. Remember that you are responsible for + * closing the {@link OutputStream} or {@link Writer} afterwards! + */ + public void renderTeminated(final TerminatedRenderingRequest request, final Result result) { + Assert.notNull(result, "result"); + + final Map<String, Object> xsltParameters = new HashMap<String, Object>(); + setBaseRenderingParameters(xsltParameters, request, null); + xsltParameters.put("exitSessionUrl", request.getExitSessionUrl()); + + doTransform(request, null, terminatedXsltUri, xsltParameters, result); + } + + + //---------------------------------------------------- + + private URI setTestItemParameters(final TestRenderingRequest request, final TestPlanNodeKey itemKey, + final Map<String, Object> xsltParameters) { + final TestSessionController testSessionController = request.getTestSessionController(); + final TestSessionState testSessionState = testSessionController.getTestSessionState(); + final TestPlanNode itemRefNode = testSessionState.getTestPlan().getNode(itemKey); + if (itemRefNode==null) { + throw new QtiWorksRenderingException("Failed to locate item with key " + itemKey + " in TestPlan"); + } + final ItemSessionState itemSessionState = testSessionState.getItemSessionStates().get(itemKey); + if (itemSessionState==null) { + throw new QtiWorksRenderingException("Failed to locate ItemSessionState for item with key " + itemKey); + } + + /* Add item-specific parameters */ + xsltParameters.put("itemSessionState", ItemSessionStateXmlMarshaller.marshal(itemSessionState).getDocumentElement()); + xsltParameters.put("itemKey", itemKey.toString()); + + /* Pass ItemSessionControl parameters */ + /* (Add any future additional itemSessionControl parameters here as required) */ + final EffectiveItemSessionControl effectiveItemSessionControl = itemRefNode.getEffectiveItemSessionControl(); + xsltParameters.put("allowComment", Boolean.valueOf(effectiveItemSessionControl.isAllowComment())); + xsltParameters.put("showFeedback", Boolean.valueOf(effectiveItemSessionControl.isShowFeedback())); + xsltParameters.put("showSolution", Boolean.valueOf(effectiveItemSessionControl.isShowSolution())); + + /* The caller should reset the following parameters to suit */ + xsltParameters.put("reviewMode", Boolean.FALSE); + xsltParameters.put("solutionMode", Boolean.FALSE); + xsltParameters.put("testPartNavigationAllowed", Boolean.FALSE); + xsltParameters.put("advanceTestItemAllowed", Boolean.FALSE); + xsltParameters.put("endTestPartAllowed", Boolean.FALSE); + + return itemRefNode.getItemSystemId(); + } + + private <P extends AbstractRenderingOptions> void setBaseRenderingParameters(final Map<String, Object> xsltParameters, + final AbstractRenderingRequest<P> request, final List<String> notifications) { + setBaseRenderingParameters(xsltParameters); + + /* Pass notifications */ + if (notifications!=null) { + //xsltParameters.put("notifications", new XsltParamBuilder().notificationsToElements(notifications)); + } + + /* Pass common control parameters */ + xsltParameters.put("validated", Boolean.valueOf(request.isValidated())); + xsltParameters.put("launchable", Boolean.valueOf(request.isLaunchable())); + xsltParameters.put("errorCount", Integer.valueOf(request.getErrorCount())); + xsltParameters.put("warningCount", Integer.valueOf(request.getWarningCount())); + xsltParameters.put("valid", Boolean.valueOf(request.isValid())); + xsltParameters.put("authorMode", Boolean.valueOf(request.isAuthorMode())); + + /* Pass common action URLs */ + final P renderingOptions = request.getRenderingOptions(); + xsltParameters.put("responseUrl", renderingOptions.getResponseUrl()); + xsltParameters.put("serveFileUrl", renderingOptions.getServeFileUrl()); + xsltParameters.put("authorViewUrl", renderingOptions.getAuthorViewUrl()); + xsltParameters.put("sourceUrl", renderingOptions.getSourceUrl()); + xsltParameters.put("stateUrl", renderingOptions.getStateUrl()); + xsltParameters.put("resultUrl", renderingOptions.getResultUrl()); + xsltParameters.put("validationUrl", renderingOptions.getValidationUrl()); + + } + + private void setBaseRenderingParameters(final Map<String, Object> xsltParameters) { + xsltParameters.put("qtiWorksVersion", "1.0-SNAPSH0T-OO"); + xsltParameters.put("webappContextPath", "");// WebappHelper.getServletContextPath()); + } + + //---------------------------------------------------- + + /** + * Invokes the transformation pipeline on the "main" assessment XML extracted from the + * given renderingRequest, using the XSLT at the given URI and specified parameters. The result + * is sent to the given {@link Result} Object. + * + * @param renderingRequest request to be rendered, must not be null + * @param rendererStylesheetUri XSLT URI, must not be null + * @param xsltParameters optional parameters + * @param result {@link Result} to generate, which must not be null + */ + private void doTransform(final AbstractRenderingRequest<?> renderingRequest, final URI rendererStylesheetUri, + final Map<String, Object> xsltParameters, final Result result) { + doTransform(renderingRequest, renderingRequest.getAssessmentResourceUri(), rendererStylesheetUri, + xsltParameters, result); + } + + /** + * Invokes the transformation pipeline on the XML resource at the given inputUri, loaded using + * the {@link ResourceLocator} specified by the given renderingRequest, using the XSLT at the + * given URI and specified parameters. The result is sent to the given {@link Result} Object. + * <p> + * + * @param renderingRequest request to be rendered, must not be null + * @param inputUri URI of the XML to pass to the XSLT pipeline. If null, a well-formed empty + * document will be passed, which is useful if the output doesn't depend on the input XML + * at all, or when generating error pages. + * @param rendererStylesheetUri XSLT URI, must not be null + * @param xsltParameters optional parameters + * @param result {@link Result} to generate, which must not be null. + */ + private void doTransform(final AbstractRenderingRequest<?> renderingRequest, final URI inputUri, + final URI rendererStylesheetUri, final Map<String, Object> xsltParameters, final Result result) { + Assert.notNull(renderingRequest); + Assert.notNull(rendererStylesheetUri); + Assert.notNull(result); + /* We do this as an XML pipeline: + * + * Input --> Rendering XSLT --> MathML C-to-P --> Serialization XSLT --> Result + * + * NB: I'm not bothering to set up LexicalHandlers, so comments and things like that won't + * be passed through the pipeline. If that becomes important, change the code below to + * support that. + */ + /* First obtain the required compiled stylesheets. */ + final TransformerHandler rendererTransformerHandler = stylesheetManager.getCompiledStylesheetHandler(rendererStylesheetUri, renderingRequest.getAssessmentResourceLocator()); + final TransformerHandler mathmlTransformerHandler = stylesheetManager.getCompiledStylesheetHandler(ctopXsltUri, null); + final TransformerHandler serializerTransformerHandler = stylesheetManager.getCompiledStylesheetHandler(serializeXsltUri, null); + + /* Pass necessary parameters to renderer */ + final Transformer rendererTransformer = rendererTransformerHandler.getTransformer(); + if (inputUri!=null) { + rendererTransformer.setParameter("systemId", inputUri); + } + if (xsltParameters!=null) { + for (final Entry<String, Object> paramEntry : xsltParameters.entrySet()) { + rendererTransformer.setParameter(paramEntry.getKey(), paramEntry.getValue()); + } + } + + /* Configure the serializer */ + final Transformer serializerTransformer = serializerTransformerHandler.getTransformer(); + final AbstractRenderingOptions renderingOptions = renderingRequest.getRenderingOptions(); + final SerializationMethod serializationMethod = renderingRequest.getRenderingOptions().getSerializationMethod(); + + serializerTransformer.setParameter("serializationMethod", serializationMethod.toString()); + serializerTransformer.setParameter("outputMethod", serializationMethod.getMethod()); + serializerTransformer.setParameter("contentType", serializationMethod.getContentType()); + serializerTransformer.setOutputProperty(OutputKeys.INDENT, "yes"); + serializerTransformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + serializerTransformer.setOutputProperty(OutputKeys.MEDIA_TYPE, serializationMethod.getContentType()); + serializerTransformer.setOutputProperty(OutputKeys.METHOD, serializationMethod.getMethod()); + serializerTransformer.setOutputProperty("include-content-type", "no"); + + /* If we're writing to an OutputStream, then select the encoding to use */ + final StreamResult streamResult = (result instanceof StreamResult) ? (StreamResult) result : null; + final boolean isOutputStreamResult = streamResult!=null && streamResult.getOutputStream()!=null; + final String outputStreamEncoding = renderingOptions.getEncoding()!=null ? renderingOptions.getEncoding() : "UTF-8"; + if (isOutputStreamResult) { + serializerTransformer.setOutputProperty(OutputKeys.ENCODING, outputStreamEncoding); + } + + /* If we're building HTML5, send its custom pseudo-DOCTYPE to the Result, as we can't generate this in XSLT. */ + if (streamResult!=null && serializationMethod==SerializationMethod.HTML5_MATHJAX) { + final String html5Doctype = "<!DOCTYPE html>\n"; + try { + if (isOutputStreamResult) { + /* Need to send doctype in correct encoding */ + streamResult.getOutputStream().write(html5Doctype.getBytes(outputStreamEncoding)); + } + else if (streamResult.getWriter()!=null) { + streamResult.getWriter().write(html5Doctype); + } + } + catch (final IOException e) { + throw new QtiWorksRenderingException("Could not write HTML5 prolog to result", e); + } + } + + /* Set up the XML source */ + final InputSource assessmentSaxSource; + if (inputUri!=null) { + final ResourceLocator assessmentResourceLocator = renderingRequest.getAssessmentResourceLocator(); + final InputStream assessmentStream = assessmentResourceLocator.findResource(inputUri); + assessmentSaxSource = new InputSource(assessmentStream); + assessmentSaxSource.setSystemId(inputUri.toString()); + } + else { + /* (null inputUri, so we'll pass an empty well-formed XML document) */ + assessmentSaxSource = new InputSource(new StringReader("<null/>")); + } + + + /* Now join the pipeline together (it's clearest to work backwards here) + * + * NB: I'm not bothering to set up LexicalHandlers, so comments and things like that won't + * be passed through the pipeline. If that becomes important, change the code below to + * support that. + */ + serializerTransformerHandler.setResult(result); + final SAXResult mathmlResult = new SAXResult(serializerTransformerHandler); + mathmlTransformerHandler.setResult(mathmlResult); + final SAXResult rendererResult = new SAXResult(mathmlTransformerHandler); + rendererTransformerHandler.setResult(rendererResult); + final XMLReader xmlReader = XmlUtilities.createNsAwareSaxReader(false); + xmlReader.setContentHandler(rendererTransformerHandler); + + /* Finally we run the pipeline */ + try { + xmlReader.parse(assessmentSaxSource); + } + catch (final Exception e) { + logger.error("Rendering XSLT pipeline failed for request {}", renderingRequest, e); + throw new QtiWorksRenderingException("Unexpected Exception running rendering XML pipeline", e); + } + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/ItemRenderingOptions.java b/src/main/java/org/olat/ims/qti21/ui/rendering/ItemRenderingOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..b4a6e191c4d8fb9a901eda83999b241c8f349412 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/ItemRenderingOptions.java @@ -0,0 +1,109 @@ +/* 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 uk.ac.ed.ph.jqtiplus.internal.util.ObjectUtilities; + +/** + * Rendering options used when rendering items in standalone mode. + * + * @author David McKain + */ +public final class ItemRenderingOptions extends AbstractRenderingOptions { + + private static final long serialVersionUID = -9121795157165098560L; + + private String endUrl; + + private String softResetUrl; + + private String hardResetUrl; + + private String solutionUrl; + + private String exitUrl; + + //---------------------------------------------------- + + public String getEndUrl() { + return endUrl; + } + + public void setEndUrl(final String endUrl) { + this.endUrl = endUrl; + } + + + public String getSoftResetUrl() { + return softResetUrl; + } + + public void setSoftResetUrl(final String softResetUrl) { + this.softResetUrl = softResetUrl; + } + + + public String getHardResetUrl() { + return hardResetUrl; + } + + public void setHardResetUrl(final String hardResetUrl) { + this.hardResetUrl = hardResetUrl; + } + + + public String getSolutionUrl() { + return solutionUrl; + } + + public void setSolutionUrl(final String solutionUrl) { + this.solutionUrl = solutionUrl; + } + + + public String getExitUrl() { + return exitUrl; + } + + public void setExitUrl(final String exitUrl) { + this.exitUrl = exitUrl; + } + + //---------------------------------------------------- + + @Override + public String toString() { + return ObjectUtilities.beanToString(this); + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/ItemRenderingRequest.java b/src/main/java/org/olat/ims/qti21/ui/rendering/ItemRenderingRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..4c02759daff40669e19fcdae1ee9d7ecccff27b6 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/ItemRenderingRequest.java @@ -0,0 +1,132 @@ +/* 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 uk.ac.ed.ph.jqtiplus.state.ItemSessionState; + +/** + * Encapsulates the required data for rendering the current state of a standalone + * item. + * + * @author David McKain + */ +public final class ItemRenderingRequest extends AbstractRenderingRequest<ItemRenderingOptions> { + + /** Required {@link ItemSessionState} to be rendered */ + + private ItemSessionState itemSessionState; + + /** Set to enable the modal solution mode */ + private boolean solutionMode; + + private String prompt; + private boolean endAllowed; + private boolean softSoftResetAllowed; + private boolean hardResetAllowed; + private boolean solutionAllowed; + private boolean candidateCommentAllowed; + + //---------------------------------------------------- + + public ItemSessionState getItemSessionState() { + return itemSessionState; + } + + public void setItemSessionState(final ItemSessionState itemSessionState) { + this.itemSessionState = itemSessionState; + } + + + public boolean isSolutionMode() { + return solutionMode; + } + + public void setSolutionMode(final boolean solutionMode) { + this.solutionMode = solutionMode; + } + + + public String getPrompt() { + return prompt; + } + + public void setPrompt(final String prompt) { + this.prompt = prompt; + } + + + public boolean isEndAllowed() { + return endAllowed; + } + + public void setEndAllowed(final boolean endAllowed) { + this.endAllowed = endAllowed; + } + + + public boolean isSoftResetAllowed() { + return softSoftResetAllowed; + } + + public void setSoftResetAllowed(final boolean softSoftResetAllowed) { + this.softSoftResetAllowed = softSoftResetAllowed; + } + + + public boolean isHardResetAllowed() { + return hardResetAllowed; + } + + public void setHardResetAllowed(final boolean hardResetAllowed) { + this.hardResetAllowed = hardResetAllowed; + } + + + public boolean isSolutionAllowed() { + return solutionAllowed; + } + + public void setSolutionAllowed(final boolean solutionAllowed) { + this.solutionAllowed = solutionAllowed; + } + + + public boolean isCandidateCommentAllowed() { + return candidateCommentAllowed; + } + + public void setCandidateCommentAllowed(final boolean candidateCommentAllowed) { + this.candidateCommentAllowed = candidateCommentAllowed; + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/QtiWorksRenderingException.java b/src/main/java/org/olat/ims/qti21/ui/rendering/QtiWorksRenderingException.java new file mode 100644 index 0000000000000000000000000000000000000000..b4cdb8aded90f95dc85307d348c01149d9447e20 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/QtiWorksRenderingException.java @@ -0,0 +1,54 @@ +/* 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; + + +/** + * Extension of {@link QtiWorksRuntimeException} used when an unexpected {@link Exception} + * occurs during rendering. + * + * @author David McKain + */ +public final class QtiWorksRenderingException extends RuntimeException { + + private static final long serialVersionUID = -2343145716203604174L; + + public QtiWorksRenderingException(final String message, final Throwable cause) { + super(message, cause); + } + + public QtiWorksRenderingException(final String message) { + super(message); + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/SerializationMethod.java b/src/main/java/org/olat/ims/qti21/ui/rendering/SerializationMethod.java new file mode 100644 index 0000000000000000000000000000000000000000..1916db283be2f5efff67a7e62a7f0d31e5869f4b --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/SerializationMethod.java @@ -0,0 +1,89 @@ +/* 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; + +/** + * Encapsulates the supported ways of serializing the results obtained + * from {@link AssessmentRenderer}. + * + * @author David McKain + */ +public enum SerializationMethod { + + /** + * XHTML + MathML, mis-delivered as text/html, using MathJax to display + * any MathML that is present. + */ + XHTML_MATHJAX("text/html", "xhtml"), + + /** + * HTML5 + MathML, using MathJax to display any MathML that is present. + */ + HTML5_MATHJAX("text/html", "html"), + + /** + * XHTML + MathML delivered as application/xhtml+xml. + * Perfect for MathML items on Mozilla. + */ + MOZILLA_MATHML("application/xhtml+xml", "xhtml"), + + /** + * XHTML mis-delivered as text/html containing gubbins to invoke MathPlayer + */ + IE_MATHPLAYER("text/html", "html"), + + /** + * XHTML traditionally mis-delivered as text/html. + * Perfect for non-MathML items, useless otherwise. + */ + TRADITIONAL_XHTML("text/html", "xhtml"), + + ; + + private final String contentType; + private final String method; + + private SerializationMethod(final String contentType, final String method) { + this.contentType = contentType; + this.method = method; + } + + public String getContentType() { + return contentType; + } + + public String getMethod() { + return method; + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/TerminatedRenderingRequest.java b/src/main/java/org/olat/ims/qti21/ui/rendering/TerminatedRenderingRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..55d96115ed6e57d30e857968748e0e7fcbb77765 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/TerminatedRenderingRequest.java @@ -0,0 +1,54 @@ +/* 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; + +/** + * Encapsulates an (explicit) rendering request for a terminated session. (It's fairly + * pointless - just required for class hierarchy purposes.) + * + * @author David McKain + */ +public final class TerminatedRenderingRequest extends AbstractRenderingRequest<AbstractRenderingOptions> { + + private String exitSessionUrl; + + public String getExitSessionUrl() { + return exitSessionUrl; + } + + public void setExitSessionUrl(final String exitSessionUrl) { + this.exitSessionUrl = exitSessionUrl; + } + +} diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/TestRenderingMode.java b/src/main/java/org/olat/ims/qti21/ui/rendering/TestRenderingMode.java new file mode 100644 index 0000000000000000000000000000000000000000..45b477cb43f19053c2e55fea5d5879176b2fb4ef --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/TestRenderingMode.java @@ -0,0 +1,47 @@ +/* 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; + +/** + * Enumerate the various modes of navigation through a test. + * + * @author David McKain + */ +public enum TestRenderingMode { + + ITEM_REVIEW, + ITEM_SOLUTION, + ; + +} diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/TestRenderingOptions.java b/src/main/java/org/olat/ims/qti21/ui/rendering/TestRenderingOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..03ddee30ef8faf14cc02756831eee614d1f6aa80 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/TestRenderingOptions.java @@ -0,0 +1,153 @@ +/* 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 uk.ac.ed.ph.jqtiplus.internal.util.ObjectUtilities; + +/** + * Rendering options used when rendering tests. + * + * @author David McKain + */ +public final class TestRenderingOptions extends AbstractRenderingOptions { + + private static final long serialVersionUID = -1121298920996208870L; + + private String testPartNavigationUrl; + + private String selectTestItemUrl; + + private String advanceTestItemUrl; + + private String endTestPartUrl; + + private String reviewTestPartUrl; + + private String reviewTestItemUrl; + + private String showTestItemSolutionUrl; + + private String advanceTestPartUrl; + + private String exitTestUrl; + + //---------------------------------------------------- + + public String getSelectTestItemUrl() { + return selectTestItemUrl; + } + + public void setSelectTestItemUrl(final String selectTestItemUrl) { + this.selectTestItemUrl = selectTestItemUrl; + } + + + public String getAdvanceTestItemUrl() { + return advanceTestItemUrl; + } + + public void setAdvanceTestItemUrl(final String advanceTestItemUrl) { + this.advanceTestItemUrl = advanceTestItemUrl; + } + + + public String getEndTestPartUrl() { + return endTestPartUrl; + } + + public void setEndTestPartUrl(final String endTestPartUrl) { + this.endTestPartUrl = endTestPartUrl; + } + + + public String getReviewTestPartUrl() { + return reviewTestPartUrl; + } + + public void setReviewTestPartUrl(final String reviewTestPartUrl) { + this.reviewTestPartUrl = reviewTestPartUrl; + } + + + public String getReviewTestItemUrl() { + return reviewTestItemUrl; + } + + public void setReviewTestItemUrl(final String reviewTestItemUrl) { + this.reviewTestItemUrl = reviewTestItemUrl; + } + + + public String getShowTestItemSolutionUrl() { + return showTestItemSolutionUrl; + } + + public void setShowTestItemSolutionUrl(final String showTestItemSolutionUrl) { + this.showTestItemSolutionUrl = showTestItemSolutionUrl; + } + + + public String getTestPartNavigationUrl() { + return testPartNavigationUrl; + } + + public void setTestPartNavigationUrl(final String testPartNavigationUrl) { + this.testPartNavigationUrl = testPartNavigationUrl; + } + + + public String getAdvanceTestPartUrl() { + return advanceTestPartUrl; + } + + public void setAdvanceTestPartUrl(final String advanceTestPartUrl) { + this.advanceTestPartUrl = advanceTestPartUrl; + } + + + public String getExitTestUrl() { + return exitTestUrl; + } + + public void setExitTestUrl(final String exitTestUrl) { + this.exitTestUrl = exitTestUrl; + } + + //---------------------------------------------------- + + @Override + public String toString() { + return ObjectUtilities.beanToString(this); + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/TestRenderingRequest.java b/src/main/java/org/olat/ims/qti21/ui/rendering/TestRenderingRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..d99555435c2384801b4ca27471a01fab2750edbd --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/TestRenderingRequest.java @@ -0,0 +1,83 @@ +/* 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 uk.ac.ed.ph.jqtiplus.running.TestSessionController; +import uk.ac.ed.ph.jqtiplus.state.TestPlanNodeKey; +import uk.ac.ed.ph.jqtiplus.state.TestSessionState; + +/** + * Request for rendering a particular test navigation screen. + * <p> + * The {@link TestRenderingMode} is used to determine what should be generated. + * + * @author David McKain + */ +public final class TestRenderingRequest extends AbstractRenderingRequest<TestRenderingOptions> { + + /** {@link TestSessionController} wrapped around the {@link TestSessionState} being rendered. */ + + private TestSessionController testSessionController; + + private TestRenderingMode testRenderingMode; + private TestPlanNodeKey modalItemKey; + + //---------------------------------------------------- + + public TestSessionController getTestSessionController() { + return testSessionController; + } + + public void setTestSessionController(final TestSessionController testSessionController) { + this.testSessionController = testSessionController; + } + + + public TestRenderingMode getTestRenderingMode() { + return testRenderingMode; + } + + public void setTestRenderingMode(final TestRenderingMode testRenderingMode) { + this.testRenderingMode = testRenderingMode; + } + + + public TestPlanNodeKey getModalItemKey() { + return modalItemKey; + } + + public void setModalItemKey(final TestPlanNodeKey modalItemKey) { + this.modalItemKey = modalItemKey; + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..b4c1f40c007e9502d3bb0ae89470aa8967c9b114 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/XmlUtilities.java @@ -0,0 +1,73 @@ +/* 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); + } + } + +} diff --git a/src/main/java/org/olat/ims/qti21/ui/rendering/XsltExtensionFunctions.java b/src/main/java/org/olat/ims/qti21/ui/rendering/XsltExtensionFunctions.java new file mode 100644 index 0000000000000000000000000000000000000000..485b23e36deb1704fb9e443f5d90600713c00c40 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/rendering/XsltExtensionFunctions.java @@ -0,0 +1,68 @@ +/* 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 java.util.IllegalFormatException; + +/** + * Contains XSLT extension functions used by the rendering process + * + * @author David McKain + */ +public final class XsltExtensionFunctions { + + /** + * This method is called by <code>printedVariable</code> to format a numeric value. + * + * FIXME: This implementation is highly deficient! See + * <a href="https://github.com/davemckain/qtiworks/issues/21">Issue #21</a> + * for details. + * + * @param format + * @param numberValue + * @return formatted String + */ + public static String format(final String format, final Double numberValue) { + try { + return String.format(format, numberValue); + } + catch (final IllegalFormatException e) { + /* Our implementation doesn't support don't support C- or QTI-specific formats. + * In this case, we'll simply display the number as-is. + */ + return numberValue.toString(); + } + } + +} diff --git a/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties index 74f61d41db571f1cea8f76202243aeff326bcb5a..e2a2ff7090d1539a5c1269a63f09d94ef87daab2 100644 --- a/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties @@ -9,6 +9,7 @@ FileResource.FILE=Andere Datei FileResource.GLOSSARY=Glossar FileResource.IMAGE=Bild FileResource.IMSCP=CP-Lerninhalt +FileResource.IMSQTI21=IMS QTI 2.1 FileResource.MOVIE=Film FileResource.PDF=PDF FileResource.PODCAST=Podcast diff --git a/src/main/java/org/olat/repository/manager/RepositoryEntryDocumentFactory.java b/src/main/java/org/olat/repository/manager/RepositoryEntryDocumentFactory.java index e3fc05f0060cf18e87036c2d9bfb758f32990f3e..ceee452c3b191fdf12ffbd7a6caaa58791d79194 100644 --- a/src/main/java/org/olat/repository/manager/RepositoryEntryDocumentFactory.java +++ b/src/main/java/org/olat/repository/manager/RepositoryEntryDocumentFactory.java @@ -118,7 +118,7 @@ public class RepositoryEntryDocumentFactory { icon = "o_podcast_icon"; else if (docType.equals("type.repository.entry.FileResource.BLOG")) icon = "o_blog_icon"; - else if (docType.equals("type.repository.entry.FileResource.TEST")) + else if (docType.equals("type.repository.entry.FileResource.TEST") || docType.equals("FileResource.IMSQTI21=IMS QTI 2.1")) icon = "o_iqtest_icon"; else if (docType.equals("type.repository.entry.FileResource.SURVEY")) icon = "o_iqsurv_icon"; diff --git a/src/main/resources/rendering-xslt/author-view-common.xsl b/src/main/resources/rendering-xslt/author-view-common.xsl new file mode 100644 index 0000000000000000000000000000000000000000..f3c9784ee33314ea906f8671e84de1430aead6b9 --- /dev/null +++ b/src/main/resources/rendering-xslt/author-view-common.xsl @@ -0,0 +1,419 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Common templates for item & test author views + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw m"> + + <!-- ************************************************************ --> + + <xsl:import href="qti-common.xsl"/> + + <!-- ************************************************************ --> + + <xsl:template name="errorStatusPanel" as="element(ul)?"> + <xsl:if test="exists($notifications) or ($validated and not($valid))"> + <xsl:variable name="errors" select="$notifications[@level='ERROR']" as="element(qw:notification)*"/> + <xsl:variable name="warnings" select="$notifications[@level='WARNING']" as="element(qw:notification)*"/> + <xsl:variable name="infos" select="$notifications[@level='INFO']" as="element(qw:notification)*"/> + <ul class="summary"> + <xsl:if test="exists($errors)"> + <li class="errorSummary"> + <a href="#notifications"><xsl:value-of select="count($errors)"/> Runtime Error<xsl:if test="count($errors)!=1">s</xsl:if></a> + </li> + </xsl:if> + <xsl:if test="exists($warnings)"> + <li class="warnSummary"> + <a href="#notifications"><xsl:value-of select="count($warnings)"/> Runtime Warning<xsl:if test="count($warnings)!=1">s</xsl:if></a> + </li> + </xsl:if> + <xsl:if test="exists($infos)"> + <li class="infoSummary"> + <a href="#notifications"><xsl:value-of select="count($infos)"/> Runtime Information Notification<xsl:if test="count($notifications)!=1">s</xsl:if></a> + </li> + </xsl:if> + <xsl:if test="$validated and not($valid)"> + <li class="errorSummary"> + <a href="{$webappContextPath}{$validationUrl}">This assessment has validation errors or warnings</a> + </li> + </xsl:if> + </ul> + </xsl:if> + </xsl:template> + + + <!-- ************************************************************ --> + + <xsl:template match="qw:itemSessionState" as="element(div)+"> + <xsl:param name="includeNotifications" as="xs:boolean" select="false()"/> + <div class="resultPanel info"> + <h4>Key item session status information</h4> + <div class="details"> + <ul> + <li>Entry time: <xsl:value-of select="qw:format-optional-date(@entryTime, '(Not Yet Entered)')"/></li> + <li>End time: <xsl:value-of select="qw:format-optional-date(@endTime, '(Not Yet Ended)')"/></li> + <li>Duration accumulated: <xsl:value-of select="@durationAccumulated div 1000.0"/> s</li> + <li>Initialized: <xsl:value-of select="@initialized"/></li> + <li>Responded: <xsl:value-of select="@responded"/></li> + <li><code>sessionStatus</code>: <xsl:value-of select="@sessionStatus"/></li> + <li><code>numAttempts</code>: <xsl:value-of select="@numAttempts"/></li> + <li><code>completionStatus</code>: <xsl:value-of select="@completionStatus"/></li> + </ul> + </div> + </div> + <div class="resultPanel info"> + <h4>Variable state</h4> + <div class="details"> + <p>The values of all variables are shown below.</p> + <xsl:apply-templates select="." mode="variableValuesPanel"/> + </div> + </div> + <xsl:if test="@responded='true'"> + <div class="resultPanel info"> + <h4>Response state</h4> + <div class="details"> + <xsl:apply-templates select="." mode="unboundResponsesPanel"/> + <xsl:apply-templates select="." mode="invalidResponsesPanel"/> + </div> + </div> + </xsl:if> + <xsl:apply-templates select="." mode="shuffleStatePanel"/> + <xsl:if test="$includeNotifications"> + <xsl:call-template name="notificationsPanel"/> + </xsl:if> + </xsl:template> + + <xsl:template match="qw:itemSessionState" mode="unboundResponsesPanel" as="element(div)"> + <div class="resultPanel {if (exists(@unboundResponseIdentifiers)) then 'failure' else 'success'}"> + <h4>Unbound responses (<xsl:value-of select="count(@unboundResponseIdentifiers)"/>)</h4> + <div class="details"> + <xsl:choose> + <xsl:when test="exists(@unboundResponseIdentifiers)"> + <p> + The responses listed below were not successfully bound to their corresponding variables. + This might happen, for example, if you bind a <code><textEntryInteraction></code> to + a numeric variable and the candidate enters something that is not a number. + </p> + <ul> + <xsl:for-each select="@unboundResponseIdentifiers"> + <li> + <span class="variableName"> + <xsl:value-of select="."/> + </span> + </li> + </xsl:for-each> + </ul> + </xsl:when> + <xsl:otherwise> + <p> + All responses were successfully bound to response variables. + </p> + </xsl:otherwise> + </xsl:choose> + </div> + </div> + </xsl:template> + + <xsl:template match="qw:itemSessionState" mode="invalidResponsesPanel" as="element(div)"> + <div class="resultPanel {if (exists(@invalidResponseIdentifiers)) then 'failure' else 'success'}"> + <h4>Invalid responses (<xsl:value-of select="count(@invalidResponseIdentifiers)"/>)</h4> + <div class="details"> + <xsl:choose> + <xsl:when test="exists(@invalidResponseIdentifiers)"> + <p> + The responses were successfully bound to their corresponding variables, + but failed to satisfy the constraints specified by their corresponding interactions: + </p> + <ul> + <xsl:for-each select="@invalidResponseIdentifiers"> + <li> + <span class="variableName"> + <xsl:value-of select="."/> + </span> + </li> + </xsl:for-each> + </ul> + </xsl:when> + <xsl:otherwise> + <p> + All responses satisfied the constraints specified by their correpsonding interactions. + </p> + </xsl:otherwise> + </xsl:choose> + </div> + </div> + </xsl:template> + + <xsl:template match="qw:itemSessionState" mode="variableValuesPanel" as="element(div)*"> + <xsl:if test="exists(qw:outcomeVariable)"> + <div class="resultPanel"> + <h4>Outcome values (<xsl:value-of select="count(qw:outcomeVariable)"/>)</h4> + <div class="details"> + <xsl:call-template name="dumpValues"> + <xsl:with-param name="valueHolders" select="qw:outcomeVariable"/> + </xsl:call-template> + </div> + </div> + </xsl:if> + <xsl:if test="exists(qw:responseVariable)"> + <div class="resultPanel"> + <h4>Response values (<xsl:value-of select="count(qw:responseVariable)"/>)</h4> + <div class="details"> + <xsl:call-template name="dumpValues"> + <xsl:with-param name="valueHolders" select="qw:responseVariable"/> + </xsl:call-template> + </div> + </div> + </xsl:if> + <xsl:if test="exists(qw:templateVariable)"> + <div class="resultPanel"> + <h4>Template values (<xsl:value-of select="count(qw:templateVariable)"/>)</h4> + <div class="details"> + <xsl:call-template name="dumpValues"> + <xsl:with-param name="valueHolders" select="qw:templateVariable"/> + </xsl:call-template> + </div> + </div> + </xsl:if> + </xsl:template> + + <xsl:template name="notificationsPanel" as="element(div)"> + <div class="resultPanel {if ($notifications[not(@level='INFO')]) then 'failure' else 'success'}"> + <h4><a name="notifications">Processing notifications (<xsl:value-of select="count($notifications)"/>)</a></h4> + <div class="details"> + <xsl:choose> + <xsl:when test="count($notifications) > 0"> + <p> + The following notifications were recorded during this processing run on this item. + These may indicate issues with your item that need fixed. + </p> + <xsl:call-template name="notificationsLevelPanel"> + <xsl:with-param name="level" select="'ERROR'"/> + <xsl:with-param name="title" select="'Errors'"/> + <xsl:with-param name="class" select="'failure'"/> + </xsl:call-template> + <xsl:call-template name="notificationsLevelPanel"> + <xsl:with-param name="level" select="'WARNING'"/> + <xsl:with-param name="title" select="'Warnings'"/> + <xsl:with-param name="class" select="'warnings'"/> + </xsl:call-template> + <xsl:call-template name="notificationsLevelPanel"> + <xsl:with-param name="level" select="'INFO'"/> + <xsl:with-param name="title" select="'Informational'"/> + <xsl:with-param name="class" select="'success'"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <p> + No notifications were recorded during this processing run on this item. + </p> + </xsl:otherwise> + </xsl:choose> + </div> + </div> + </xsl:template> + + <xsl:template name="notificationsLevelPanel" as="element(div)?"> + <xsl:param name="level" as="xs:string"/> + <xsl:param name="title" as="xs:string"/> + <xsl:param name="class" as="xs:string"/> + <xsl:variable name="notificationsAtLevel" select="$notifications[@level=$level]" as="element(qw:notification)*"/> + <xsl:if test="exists($notificationsAtLevel)"> + <div class="resultPanel{if (exists($notificationsAtLevel)) then concat(' ', $class) else ''}"> + <h4><xsl:value-of select="concat($title, ' (', count($notificationsAtLevel), ')')"/></h4> + <div class="details"> + <table class="notificationsTable"> + <thead> + <tr> + <th>Type</th> + <th>QTI Class</th> + <th>Attribute</th> + <th>Line Number</th> + <th>Column Number</th> + <th>Message</th> + </tr> + </thead> + <tbody> + <xsl:for-each select="$notificationsAtLevel"> + <tr> + <td><xsl:value-of select="@type"/></td> + <td><xsl:value-of select="if (exists(@nodeQtiClassName)) then @nodeQtiClassName else 'N/A'"/></td> + <td><xsl:value-of select="if (exists(@attrLocalName)) then @attrLocalName else 'N/A'"/></td> + <td><xsl:value-of select="if (exists(@lineNumber)) then @lineNumber else 'Unknown'"/></td> + <td><xsl:value-of select="if (exists(@columnNumber)) then @columnNumber else 'Unknown'"/></td> + <td><xsl:value-of select="."/></td> + </tr> + </xsl:for-each> + </tbody> + </table> + </div> + </div> + </xsl:if> + </xsl:template> + + <xsl:template match="qw:itemSessionState" mode="shuffleStatePanel" as="element(div)"> + <div class="resultPanel info"> + <h4>Interaction shuffle state</h4> + <div class="details"> + <xsl:choose> + <xsl:when test="exists(qw:shuffledInteractionChoiceOrder)"> + <ul> + <xsl:for-each select="qw:shuffledInteractionChoiceOrder"> + <li> + <span class="variableName"> + <xsl:value-of select="@responseIdentifier"/> + </span> + <xsl:text> = [</xsl:text> + <xsl:value-of select="tokenize(@choiceSequence, ' ')" separator=", "/> + <xsl:text>]</xsl:text> + </li> + </xsl:for-each> + </ul> + </xsl:when> + <xsl:otherwise> + <p>There are no shuffled interactions in this item.</p> + </xsl:otherwise> + </xsl:choose> + </div> + </div> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template name="dumpValues" as="element(ul)"> + <xsl:param name="valueHolders" as="element()*"/> + <ul> + <xsl:for-each select="$valueHolders"> + <xsl:call-template name="dumpValue"> + <xsl:with-param name="valueHolder" select="."/> + </xsl:call-template> + </xsl:for-each> + </ul> + </xsl:template> + + <xsl:template name="dumpValue" as="element(li)"> + <xsl:param name="valueHolder" as="element()"/> + <li> + <span class="variableName"> + <xsl:value-of select="@identifier"/> + </span> + <xsl:text> = </xsl:text> + <xsl:choose> + <xsl:when test="not(*)"> + <xsl:text>NULL</xsl:text> + </xsl:when> + <xsl:when test="qw:is-maths-content-value($valueHolder)"> + <!-- We'll handle MathsContent variables specially to help question authors --> + <span class="type">MathsContent :: </span> + <xsl:copy-of select="qw:extract-maths-content-pmathml($valueHolder)"/> + + <!-- Make the raw record fields available via a toggle --> + <xsl:text> </xsl:text> + <a id="qtiworks_id_toggle_debugMathsContent_{@identifier}" class="debugButton" + href="javascript:void(0)">Toggle Details</a> + <div id="qtiworks_id_debugMathsContent_{@identifier}" class="debugMathsContent"> + <xsl:call-template name="dumpRecordEntries"> + <xsl:with-param name="valueHolders" select="$valueHolder/qw:value"/> + </xsl:call-template> + </div> + <script> + $(document).ready(function() { + $('a#qtiworks_id_toggle_debugMathsContent_<xsl:value-of select="@identifier"/>').click(function() { + $('#qtiworks_id_debugMathsContent_<xsl:value-of select="@identifier"/>').toggle(); + }) + }); + </script> + </xsl:when> + <xsl:otherwise> + <!-- Other variables will be output in a fairly generic way --> + <span class="type"> + <xsl:value-of select="(@cardinality, @baseType, ':: ')" separator=" "/> + </span> + <xsl:choose> + <xsl:when test="@cardinality='single'"> + <xsl:variable name="singleValue" select="$valueHolder/qw:value" as="element(qw:value)"/> + <xsl:choose> + <xsl:when test="@baseType='file'"> + <xsl:value-of select="$singleValue/@fileName"/> + </xsl:when> + <xsl:otherwise> + <xsl:variable name="text" select="$singleValue" as="xs:string"/> + <xsl:choose> + <xsl:when test="contains($text, '
')"> + <pre><xsl:value-of select="$text"/></pre> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$text"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:when test="@cardinality='multiple'"> + <xsl:text>{</xsl:text> + <xsl:value-of select="$valueHolder/qw:value" separator=", "/> + <xsl:text>}</xsl:text> + </xsl:when> + <xsl:when test="@cardinality='ordered'"> + <xsl:text>[</xsl:text> + <xsl:value-of select="$valueHolder/qw:value" separator=", "/> + <xsl:text>]</xsl:text> + </xsl:when> + <xsl:when test="@cardinality='record'"> + <xsl:text>(</xsl:text> + <xsl:call-template name="dumpRecordEntries"> + <xsl:with-param name="valueHolders" select="$valueHolder/qw:value"/> + </xsl:call-template> + <xsl:text>)</xsl:text> + </xsl:when> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </li> + </xsl:template> + + <xsl:template name="dumpRecordEntries" as="element(ul)"> + <xsl:param name="valueHolders" as="element()*"/> + <ul> + <xsl:for-each select="$valueHolders"> + <li> + <span class="variableName"> + <xsl:value-of select="@fieldIdentifier"/> + </span> + <xsl:text> = </xsl:text> + <xsl:choose> + <xsl:when test="not(*)"> + <xsl:text>NULL</xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- Other variables will be output in a fairly generic way --> + <span class="type"> + <xsl:value-of select="(@baseType, ':: ')" separator=" "/> + </span> + <xsl:variable name="text" select="qw:value" as="xs:string"/> + <xsl:choose> + <xsl:when test="contains($text, '
')"> + <pre><xsl:value-of select="$text"/></pre> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$text"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </li> + </xsl:for-each> + </ul> + </xsl:template> + +</xsl:stylesheet> + diff --git a/src/main/resources/rendering-xslt/ctop.xsl b/src/main/resources/rendering-xslt/ctop.xsl new file mode 100644 index 0000000000000000000000000000000000000000..c5eabcc93bc756caad431deb79b4ea54650ca713 --- /dev/null +++ b/src/main/resources/rendering-xslt/ctop.xsl @@ -0,0 +1,1973 @@ +<xsl:stylesheet + version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:m="http://www.w3.org/1998/Math/MathML" + exclude-result-prefixes="m" +> + +<!-- + +Copyright David Carlisle 2001, 2002, 2008, 2009. + +Use and distribution of this code are permitted under the terms of the <a +href="http://www.w3.org/Consortium/Legal/copyright-software-19980720" +>W3C Software Notice and License</a>. Or the MIT or MPL 1.1 or MPL 2.0 licences. +2001-2002 MathML2 version +2008-2009 Updates for MathML3 +--> + +<xsl:output method="xml" /> + +<xsl:template match="/"> + <xsl:apply-templates mode="c2p"/> +</xsl:template> + +<xsl:template mode="c2p" match="*"> +<xsl:copy> + <xsl:copy-of select="@*"/> + <xsl:apply-templates mode="c2p"/> +</xsl:copy> +</xsl:template> + + +<!-- 4.4.1.1 cn --> + +<xsl:template mode="c2p" match="m:cn"> + <m:mn><xsl:apply-templates mode="c2p"/></m:mn> +</xsl:template> + +<xsl:template mode="c2p" match="m:cn[@type='complex-cartesian']"> + <m:mrow> + <m:mn><xsl:apply-templates mode="c2p" select="text()[1]"/></m:mn> + <m:mo>+</m:mo> + <m:mn><xsl:apply-templates mode="c2p" select="text()[2]"/></m:mn> + <m:mo>⁢<!--invisible times--></m:mo> + <m:mi>i<!-- imaginary i --></m:mi> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='complex_cartesian']]"> + <m:mrow> + <m:mn><xsl:apply-templates mode="c2p" select="*[2]"/></m:mn> + <m:mo>+</m:mo> + <m:mn><xsl:apply-templates mode="c2p" select="*[3]"/></m:mn> + <m:mo>⁢<!--invisible times--></m:mo> + <m:mi>i<!-- imaginary i --></m:mi> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:cn[@type='rational']"> + <m:mrow> + <m:mn><xsl:apply-templates mode="c2p" select="text()[1]"/></m:mn> + <m:mo>/</m:mo> + <m:mn><xsl:apply-templates mode="c2p" select="text()[2]"/></m:mn> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='rational']]"> + <m:mrow> + <m:mn><xsl:apply-templates mode="c2p" select="*[2]"/></m:mn> + <m:mo>/</m:mo> + <m:mn><xsl:apply-templates mode="c2p" select="*[3]"/></m:mn> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:cn[not(@type) or @type='integer']"> + <xsl:choose> + <xsl:when test="not(@base) or @base=10"> + <m:mn><xsl:apply-templates mode="c2p"/></m:mn> + </xsl:when> + <xsl:otherwise> + <m:msub> + <m:mn><xsl:apply-templates mode="c2p"/></m:mn> + <m:mn><xsl:value-of select="@base"/></m:mn> + </m:msub> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template mode="c2p" match="m:cn[@type='complex-polar']"> + <m:mrow> + <m:mn><xsl:apply-templates mode="c2p" select="text()[1]"/></m:mn> + <m:mo>⁢<!--invisible times--></m:mo> + <m:msup> + <m:mi>e<!-- exponential e--></m:mi> + <m:mrow> + <m:mi>i<!-- imaginary i--></m:mi> + <m:mo>⁢<!--invisible times--></m:mo> + <m:mn><xsl:apply-templates mode="c2p" select="text()[2]"/></m:mn> + </m:mrow> + </m:msup> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='complex_polar']]"> + <m:mrow> + <xsl:apply-templates mode="c2p" select="*[2]"/> + <m:mo>⁢<!--invisible times--></m:mo> + <m:msup> + <m:mi>e<!-- exponential e--></m:mi> + <m:mrow> + <m:mi>i<!-- imaginary i--></m:mi> + <m:mo>⁢<!--invisible times--></m:mo> + <xsl:apply-templates mode="c2p" select="*[3]"/> + </m:mrow> + </m:msup> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:cn[@type='e-notation']"> + <m:mn> + <xsl:apply-templates mode="c2p" select="m:sep/preceding-sibling::node()"/> + <xsl:text>E</xsl:text> + <xsl:apply-templates mode="c2p" select="m:sep/following-sibling::node()"/> + </m:mn> +</xsl:template> + +<xsl:template mode="c2p" match="m:cn[@type='hexdouble']"> + <m:mn> + <xsl:text>0x</xsl:text> + <xsl:apply-templates mode="c2p"/> + </m:mn> +</xsl:template> + +<!-- 4.4.1.1 ci --> + +<xsl:template mode="c2p" match="m:ci/text()"> + <m:mi><xsl:value-of select="."/></m:mi> +</xsl:template> + +<xsl:template mode="c2p" match="m:ci"> + <m:mrow><xsl:apply-templates mode="c2p"/></m:mrow> +</xsl:template> + +<!-- 4.4.1.2 csymbol --> + +<xsl:template mode="c2p" match="m:csymbol/text()"> + <m:mi><xsl:value-of select="."/></m:mi><!-- Robin Green r.d.greenATlancaster.ac.uk, Christoph Lange langecATweb.de--> +</xsl:template> + +<xsl:template mode="c2p" match="m:csymbol"> + <m:mrow><xsl:apply-templates mode="c2p"/></m:mrow> +</xsl:template> + +<!-- 4.4.2.1 apply 4.4.2.2 reln --> + +<xsl:template mode="c2p" match="m:apply|m:reln"> + <m:mrow> + <xsl:choose> + <xsl:when test="*[1]/*/*"> + <m:mfenced separators=""> + <xsl:apply-templates mode="c2p" select="*[1]"> + <xsl:with-param name="p" select="10"/> + </xsl:apply-templates> + </m:mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="c2p" select="*[1]"> + <xsl:with-param name="p" select="10"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> + <m:mo>⁡<!--function application--></m:mo> + <m:mfenced open="(" close=")" separators=","> + <xsl:apply-templates mode="c2p" select="*[position()>1]"/> + </m:mfenced> + </m:mrow> +</xsl:template> + + +<xsl:template mode="c2p" match="m:bind"> + <m:mrow> + <xsl:choose> + <xsl:when test="*[1]/*/*"> + <m:mfenced separators=""> + <xsl:apply-templates mode="c2p" select="*[1]"> + <xsl:with-param name="p" select="10"/> + </xsl:apply-templates> + </m:mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="c2p" select="*[1]"> + <xsl:with-param name="p" select="10"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> + <xsl:apply-templates select="bvar/*"/> + <m:mo>.</m:mo> + <xsl:apply-templates mode="c2p" select="*[position()>1][not(self::m:bvar)]"/> + </m:mrow> +</xsl:template> + +<!-- 4.4.2.3 fn --> +<xsl:template mode="c2p" match="m:fn"> + <m:mrow><xsl:apply-templates mode="c2p"/></m:mrow> +</xsl:template> + +<!-- 4.4.2.4 interval --> +<xsl:template mode="c2p" match="m:interval[*[2]]"> + <m:mfenced open="[" close="]"><xsl:apply-templates mode="c2p"/></m:mfenced> +</xsl:template> +<xsl:template mode="c2p" match="m:interval[*[2]][@closure='open']" priority="2"> + <m:mfenced open="(" close=")"><xsl:apply-templates mode="c2p"/></m:mfenced> +</xsl:template> +<xsl:template mode="c2p" match="m:interval[*[2]][@closure='open-closed']" priority="2"> + <m:mfenced open="(" close="]"><xsl:apply-templates mode="c2p"/></m:mfenced> +</xsl:template> +<xsl:template mode="c2p" match="m:interval[*[2]][@closure='closed-open']" priority="2"> + <m:mfenced open="[" close=")"><xsl:apply-templates mode="c2p"/></m:mfenced> +</xsl:template> + +<xsl:template mode="c2p" match="m:interval"> + <m:mfenced open="{{" close="}}"><xsl:apply-templates mode="c2p"/></m:mfenced> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='integer_interval']]"> + <m:mfenced open="[" close="]"><xsl:apply-templates mode="c2p" select="*[position()!=1]"/></m:mfenced> +</xsl:template> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='interval']]"> + <m:mfenced open="[" close="]"><xsl:apply-templates mode="c2p" select="*[position()!=1]"/></m:mfenced> +</xsl:template> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='interval-cc']]"> + <m:mfenced open="[" close="]"><xsl:apply-templates mode="c2p" select="*[position()!=1]"/></m:mfenced> +</xsl:template> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='interval-oo']]"> + <m:mfenced open="(" close=")"><xsl:apply-templates mode="c2p" select="*[position()!=1]"/></m:mfenced> +</xsl:template> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='oriented_interval']]"> + <m:mfenced open="(" close=")"><xsl:apply-templates mode="c2p" select="*[position()!=1]"/></m:mfenced> +</xsl:template> + +<!-- 4.4.2.5 inverse --> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:inverse]] + |m:apply[*[1][self::m:csymbol='inverse']]"> + <m:msup> + <xsl:apply-templates mode="c2p" select="*[2]"/> + <m:mrow><m:mo>(</m:mo><m:mn>-1</m:mn><m:mo>)</m:mo></m:mrow> + </m:msup> +</xsl:template> + +<!-- 4.4.2.6 sep --> + +<!-- 4.4.2.7 condition --> +<xsl:template mode="c2p" match="m:condition"> + <m:mrow><xsl:apply-templates mode="c2p"/></m:mrow> +</xsl:template> + +<!-- 4.4.2.8 declare --> +<xsl:template mode="c2p" match="m:declare"/> + +<!-- 4.4.2.9 lambda --> +<xsl:template mode="c2p" match="m:lambda + |m:apply[*[1][self::m:csymbol='lambda']] + |m:bind[*[1][self::m:csymbol='lambda']]"><!--dpc--> + <m:mrow> + <m:mi>λ<!--lambda--></m:mi> + <m:mrow><xsl:apply-templates mode="c2p" select="m:bvar/*"/></m:mrow> + <m:mo>.</m:mo> + <m:mfenced> + <xsl:apply-templates mode="c2p" select="*[last()]"/> + </m:mfenced> +</m:mrow> +</xsl:template> + + +<!-- 4.4.2.10 compose --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:compose]] + |m:apply[*[1][self::m:csymbol='left_compose']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="1"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>∘<!-- o --></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + + +<!-- 4.4.2.11` ident --> +<xsl:template mode="c2p" match="m:ident"> +<m:mi>id</m:mi> +</xsl:template> + +<!-- 4.4.2.12` domain --> +<xsl:template mode="c2p" match="m:domain"> +<m:mi>domain</m:mi> +</xsl:template> + +<!-- 4.4.2.13` codomain --> +<xsl:template mode="c2p" match="m:codomain"> +<m:mi>codomain</m:mi> +</xsl:template> + +<!-- 4.4.2.14` image --> +<xsl:template mode="c2p" match="m:image"> +<m:mi>image</m:mi> +</xsl:template> + +<!-- 4.4.2.15` domainofapplication --> +<xsl:template mode="c2p" match="m:domainofapplication"> + <m:merror><m:mtext>unexpected domainofapplication</m:mtext></m:merror> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[2][self::m:bvar]][m:domainofapplication]" priority="0.4"> + <m:mrow> + <m:munder> + <xsl:apply-templates mode="c2p" select="*[1]"/> + <m:mrow> + <xsl:apply-templates mode="c2p" select="m:bvar/*"/> + <m:mo>∈<!-- in --></m:mo> + <xsl:apply-templates mode="c2p" select="m:domainofapplication/*"/> + </m:mrow> + </m:munder> + <m:mfenced> + <xsl:apply-templates mode="c2p" select="m:domainofapplication/following-sibling::*"/> + </m:mfenced> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[m:domainofapplication]" priority="0.3"> + <m:mrow> + <m:mrow><m:mi>restriction</m:mi> + <m:mfenced> + <xsl:apply-templates mode="c2p" select="*[1]"/> + <xsl:apply-templates mode="c2p" select="m:domainofapplication/*"/> + </m:mfenced> + </m:mrow> + <m:mfenced> + <xsl:apply-templates mode="c2p" select="m:domainofapplication/following-sibling::*"/> + </m:mfenced> + </m:mrow> +</xsl:template> + +<!-- 4.4.2.16` piecewise --> +<xsl:template mode="c2p" match="m:piecewise"> + <m:mrow> + <m:mo>{</m:mo> + <m:mtable> + <xsl:for-each select="m:piece|m:otherwise"> + <m:mtr> + <m:mtd><xsl:apply-templates mode="c2p" select="*[1]"/></m:mtd> + <xsl:choose><!--dpc--> + <xsl:when test="self::m:piece"> + <m:mtd columnalign="left"><m:mtext>  if  </m:mtext></m:mtd> + <m:mtd><xsl:apply-templates mode="c2p" select="*[2]"/></m:mtd> + </xsl:when> + <xsl:otherwise> + <m:mtd colspan="2" columnalign="left"><m:mtext>  otherwise</m:mtext></m:mtd> + </xsl:otherwise> + </xsl:choose> + </m:mtr> + </xsl:for-each> + </m:mtable> + </m:mrow> +</xsl:template> + + +<!-- 4.4.3.1 quotient --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:quotient]] + |m:apply[*[1][self::m:csymbol='quotient']]"> +<m:mrow> +<m:mo>⌊<!-- lfloor--></m:mo> +<xsl:apply-templates mode="c2p" select="*[2]"/> +<m:mo>/</m:mo> +<xsl:apply-templates mode="c2p" select="*[3]"/> +<m:mo>⌋<!-- rfloor--></m:mo> +</m:mrow> +</xsl:template> + + + +<!-- 4.4.3.2 factorial --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:factorial]] + |m:apply[*[1][self::m:csymbol='factorial']]"> +<m:mrow> +<xsl:apply-templates mode="c2p" select="*[2]"> + <xsl:with-param name="p" select="7"/> +</xsl:apply-templates> +<m:mo>!</m:mo> +</m:mrow> +</xsl:template> + + +<!-- 4.4.3.3 divide --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:divide]] + |m:apply[*[1][self::m:csymbol='divide']]"> + <xsl:param name="p" select="0"/> +<xsl:call-template name="binary"> + <xsl:with-param name="mo"><m:mo>/</m:mo></xsl:with-param> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="this-p" select="3"/> +</xsl:call-template> +</xsl:template> + + +<!-- 4.4.3.4 max min--> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:max]] + |m:apply[*[1][self::m:csymbol='max']]"> +<m:mrow> + <m:mi>max</m:mi> + <xsl:call-template name="set"/> +</m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:min]]|m:reln[*[1][self::m:min]]"> +<m:mrow> + <m:mi>min</m:mi><!--dpc--> + <xsl:call-template name="set"/> +</m:mrow> +</xsl:template> + +<!-- 4.4.3.5 minus--> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:minus] and count(*)=2] + |m:apply[*[1][self::m:csymbol='unary_minus']]"> +<m:mrow> + <m:mo>−<!--minus--></m:mo> + <xsl:apply-templates mode="c2p" select="*[2]"> + <xsl:with-param name="p" select="5"/> + </xsl:apply-templates> +</m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:minus] and count(*)>2] + |m:apply[*[1][self::m:csymbol='minus']]"> + <xsl:param name="p" select="0"/> +<xsl:call-template name="binary"> + <xsl:with-param name="mo"><m:mo>−<!--minus--></m:mo></xsl:with-param> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="this-p" select="2"/> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.3.6 plus--> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:plus]] + |m:apply[*[1][self::m:csymbol='plus']]"> + <xsl:param name="p" select="0"/> + <m:mrow> + <xsl:if test="$p > 2"><m:mo>(</m:mo></xsl:if> + <xsl:for-each select="*[position()>1]"> + <xsl:if test="position() > 1"> + <m:mo> + <xsl:choose> + <xsl:when test="self::m:apply[*[1][self::m:times] and + *[2][self::m:apply/*[1][self::m:minus] or self::m:cn[not(m:sep) and + (number(.) < 0)]]]">−<!--minus--></xsl:when> + <xsl:otherwise>+</xsl:otherwise> + </xsl:choose> + </m:mo> + </xsl:if> + <xsl:choose> + <xsl:when test="self::m:apply[*[1][self::m:times] and + *[2][self::m:cn[not(m:sep) and (number(.) <0)]]]"> + <m:mrow> + <m:mn><xsl:value-of select="-(*[2])"/></m:mn> + <m:mo>⁢<!--invisible times--></m:mo> + <xsl:apply-templates mode="c2p" select="."> + <xsl:with-param name="first" select="2"/> + <xsl:with-param name="p" select="2"/> + </xsl:apply-templates> + </m:mrow> + </xsl:when> + <xsl:when test="self::m:apply[*[1][self::m:times] and + *[2][self::m:apply/*[1][self::m:minus]]]"> + <m:mrow> + <xsl:apply-templates mode="c2p" select="./*[2]/*[2]"/> + <xsl:apply-templates mode="c2p" select="."> + <xsl:with-param name="first" select="2"/> + <xsl:with-param name="p" select="2"/> + </xsl:apply-templates> + </m:mrow> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="c2p" select="."> + <xsl:with-param name="p" select="2"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:if test="$p > 2"><m:mo>)</m:mo></xsl:if> + </m:mrow> +</xsl:template> + + +<!-- 4.4.3.7 power --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:power]] + |m:apply[*[1][self::m:csymbol='power']]"> +<m:msup> +<xsl:apply-templates mode="c2p" select="*[2]"> + <xsl:with-param name="p" select="5"/> +</xsl:apply-templates> +<xsl:apply-templates mode="c2p" select="*[3]"> + <xsl:with-param name="p" select="5"/> +</xsl:apply-templates> +</m:msup> +</xsl:template> + +<!-- 4.4.3.8 remainder --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:rem]] + |m:apply[*[1][self::m:csymbol='rem']]"> + <xsl:param name="p" select="0"/> +<xsl:call-template name="binary"> + <xsl:with-param name="mo"><m:mo>mod</m:mo></xsl:with-param> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="this-p" select="3"/> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.3.9 times--> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:times]] + |m:apply[*[1][self::m:csymbol='times']]" + name="times"> + <xsl:param name="p" select="0"/> + <xsl:param name="first" select="1"/> + <m:mrow> + <xsl:if test="$p > 3"><m:mo>(</m:mo></xsl:if> + <xsl:for-each select="*[position()>1]"> + <xsl:if test="position() > 1"> + <m:mo> + <xsl:choose> + <xsl:when test="self::m:cn">×<!-- times --></xsl:when> + <xsl:otherwise>⁢<!--invisible times--></xsl:otherwise> + </xsl:choose> + </m:mo> + </xsl:if> + <xsl:if test="position()>= $first"> + <xsl:apply-templates mode="c2p" select="."> + <xsl:with-param name="p" select="3"/> + </xsl:apply-templates> + </xsl:if> + </xsl:for-each> + <xsl:if test="$p > 3"><m:mo>)</m:mo></xsl:if> + </m:mrow> +</xsl:template> + + +<!-- 4.4.3.10 root --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:root] and not(m:degree) or m:degree=2]" priority="4"> +<m:msqrt> +<xsl:apply-templates mode="c2p" select="*[position()>1]"/> +</m:msqrt> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:root]]"> +<m:mroot> +<xsl:apply-templates mode="c2p" select="*[position()>1 and not(self::m:degree)]"/> +<m:mrow><xsl:apply-templates mode="c2p" select="m:degree/*"/></m:mrow> +</m:mroot> +</xsl:template> + + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='root']]"> +<m:mroot> + <xsl:apply-templates mode="c2p" select="*[position()!=1]"/> +</m:mroot> +</xsl:template> + +<!-- 4.4.3.11 gcd --> +<xsl:template mode="c2p" match="m:gcd"> +<m:mi>gcd</m:mi> +</xsl:template> + +<!-- 4.4.3.12 and --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:and]] + |m:reln[*[1][self::m:and]] + |m:apply[*[1][self::m:csymbol='and']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="2"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>∧<!-- and --></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + + +<!-- 4.4.3.13 or --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:or]] + |m:apply[*[1][self::m:csymbol='or']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="3"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>∨<!-- or --></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.3.14 xor --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:xor]] + |m:apply[*[1][self::m:csymbol='xor']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="3"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>xor</m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + + +<!-- 4.4.3.15 not --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:not]] + |m:apply[*[1][self::m:csymbol='not']]"> +<m:mrow> +<m:mo>¬<!-- not --></m:mo> +<xsl:apply-templates mode="c2p" select="*[2]"> + <xsl:with-param name="p" select="7"/> +</xsl:apply-templates> +</m:mrow> +</xsl:template> + + + + +<!-- 4.4.3.16 implies --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:implies]] + |m:reln[*[1][self::m:implies]] + |m:apply[*[1][self::m:csymbol='implies']]"> + <xsl:param name="p" select="0"/> +<xsl:call-template name="binary"> + <xsl:with-param name="mo"><m:mo>⇒<!-- Rightarrow --></m:mo></xsl:with-param> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="this-p" select="3"/> +</xsl:call-template> +</xsl:template> + + +<!-- 4.4.3.17 forall --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:forall]] + |m:apply[*[1][self::m:csymbol='forall']] + |m:bind[*[1][self::m:forall]] + |m:bind[*[1][self::m:csymbol='forall']]"> + <m:mrow> + <m:mo>∀<!--forall--></m:mo> + <m:mrow><xsl:apply-templates mode="c2p" select="m:bvar[not(current()/m:condition)]/*|m:condition/*"/></m:mrow> + <m:mo>.</m:mo> + <m:mfenced> + <xsl:apply-templates mode="c2p" select="*[last()]"/> + </m:mfenced> +</m:mrow> +</xsl:template> + + + +<!-- 4.4.3.18 exists --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:exists]] + |m:apply[*[1][self::m:csymbol='exists']] + |m:bind[*[1][self::m:exists]] + |m:bind[*[1][self::m:csymbol='exists']]"> + <m:mrow> + <m:mo>∃<!--exists--></m:mo> + <m:mrow><xsl:apply-templates mode="c2p" select="m:bvar[not(current()/m:condition)]/*|m:condition/*"/></m:mrow> + <m:mo>.</m:mo> + <m:mfenced separators=""> + <xsl:choose> + <xsl:when test="m:condition"> + <xsl:apply-templates mode="c2p" select="m:condition/*"/> + <m:mo>∧<!-- and --></m:mo> + </xsl:when> + <xsl:when test="m:domainofapplication"> + <m:mrow> + <m:mrow> + <xsl:for-each select="m:bvar"> + <xsl:apply-templates mode="c2p"/> + <xsl:if test="position()!=last()"> + <m:mo>,</m:mo> + </xsl:if> + </xsl:for-each> + </m:mrow> + <m:mo>∈<!-- in --></m:mo> + <xsl:apply-templates mode="c2p" select="m:domainofapplication/*"/> + </m:mrow> + <m:mo>∧<!-- and --></m:mo> + </xsl:when> + </xsl:choose> + <xsl:apply-templates mode="c2p" select="*[last()]"/> + </m:mfenced> +</m:mrow> +</xsl:template> + + + +<!-- 4.4.3.19 abs --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:abs]] + |m:apply[*[1][self::m:csymbol='abs']]"> +<m:mrow> +<m:mo>|</m:mo> +<xsl:apply-templates mode="c2p" select="*[2]"/> +<m:mo>|</m:mo> +</m:mrow> +</xsl:template> + + + +<!-- 4.4.3.20 conjugate --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:conjugate]] + |m:apply[*[1][self::m:csymbol='conjugate']]"> +<m:mover> +<xsl:apply-templates mode="c2p" select="*[2]"/> +<m:mo>¯<!-- overline --></m:mo> +</m:mover> +</xsl:template> + +<!-- 4.4.3.21 arg --> +<xsl:template mode="c2p" match="m:arg"> + <m:mi>arg</m:mi> +</xsl:template> + + +<!-- 4.4.3.22 real --> +<xsl:template mode="c2p" match="m:real|m:csymbol[.='real']"> + <m:mo>ℛ<!-- real --></m:mo> +</xsl:template> + +<!-- 4.4.3.23 imaginary --> +<xsl:template mode="c2p" match="m:imaginary|m:csymbol[.='imaginary']"> + <m:mo>ℑ<!-- imaginary --></m:mo> +</xsl:template> + +<!-- 4.4.3.24 lcm --> +<xsl:template mode="c2p" match="m:lcm"> + <m:mi>lcm</m:mi> +</xsl:template> + + +<!-- 4.4.3.25 floor --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:floor]] + |m:apply[*[1][self::m:csymbol='floor']]"> +<m:mrow> +<m:mo>⌊<!-- lfloor--></m:mo> +<xsl:apply-templates mode="c2p" select="*[2]"/> +<m:mo>⌋<!-- rfloor--></m:mo> +</m:mrow> +</xsl:template> + + +<!-- 4.4.3.25 ceiling --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:ceiling]] + |m:apply[*[1][self::m:csymbol='ceiling']]"> +<m:mrow> +<m:mo>⌈<!-- lceil--></m:mo> +<xsl:apply-templates mode="c2p" select="*[2]"/> +<m:mo>⌉<!-- rceil--></m:mo> +</m:mrow> +</xsl:template> + +<!-- 4.4.4.1 eq --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:eq]] + |m:reln[*[1][self::m:eq]] + |m:apply[*[1][self::m:csymbol='eq']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="1"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>=</m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.4.2 neq --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:neq]] + |m:apply[*[1][self::m:csymbol='neq']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="1"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>≠<!-- neq --></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.4.3 eq --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:gt]] + |m:reln[*[1][self::m:gt]] + |m:apply[*[1][self::m:csymbol='gt']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="1"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.4.4 lt --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:lt]] + |m:reln[*[1][self::m:lt]] + |m:apply[*[1][self::m:csymbol='lt']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="1"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo><</m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.4.5 geq --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:geq]] + |m:apply[*[1][self::m:csymbol='geq']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="1"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>≥</m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.4.6 geq --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:leq]] + |m:apply[*[1][self::m:csymbol='leq']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="1"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>≤</m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.4.7 equivalent --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:equivalent]] + |m:apply[*[1][self::m:csymbol='equivalent']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="1"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>≡</m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.4.8 approx --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:approx]] + |m:apply[*[1][self::m:csymbol='approx']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="1"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>≃</m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + + +<!-- 4.4.4.9 factorof --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:factorof]] + |m:apply[*[1][self::m:csymbol='factorof']]"> + <xsl:param name="p" select="0"/> +<xsl:call-template name="binary"> + <xsl:with-param name="mo"><m:mo>|</m:mo></xsl:with-param> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="this-p" select="3"/> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.5.1 int --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:int]] + |m:apply[*[1][self::m:csymbol='int']] + |m:bind[*[1][self::m:int]] + |m:bind[*[1][self::m:csymbol='int']]"> + <m:mrow> + <m:msubsup> + <m:mi>∫<!--int--></m:mi> + <m:mrow><xsl:apply-templates mode="c2p" select="m:lowlimit/*|m:interval/*[1]|m:condition/*|m:domainofapplication/*"/></m:mrow> + <m:mrow><xsl:apply-templates mode="c2p" select="m:uplimit/*|m:interval/*[2]"/></m:mrow> + </m:msubsup> + <xsl:apply-templates mode="c2p" select="*[last()]"/> + <xsl:if test="m:bvar"> + <m:mi>d</m:mi><xsl:apply-templates mode="c2p" select="m:bvar"/> + </xsl:if> +</m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='defint']]"> +<m:mrow> +<m:munder><m:mi>∫<!--int--></m:mi> +<xsl:apply-templates mode="c2p" select="*[2]"/> +</m:munder> + <xsl:apply-templates mode="c2p" select="*[last()]"/> +</m:mrow> +</xsl:template> + +<!-- 4.4.5.2 diff --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:diff] and not(m:bvar)]| + m:apply[*[1][self::m:csymbol='diff']]" priority="2"> + <m:msup> + <m:mrow><xsl:apply-templates mode="c2p" select="*[2]"/></m:mrow> + <m:mo>′<!--prime--></m:mo> + </m:msup> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:diff]]" priority="1"> + <m:mfrac> + <xsl:choose> + <xsl:when test="m:bvar/m:degree"> + <m:mrow><m:msup><m:mi>d</m:mi><xsl:apply-templates mode="c2p" select="m:bvar/m:degree/node()"/></m:msup> + <xsl:apply-templates mode="c2p" select="*[last()]"/></m:mrow> + <m:mrow><m:mi>d</m:mi><m:msup><xsl:apply-templates mode="c2p" + select="m:bvar/node()"/><xsl:apply-templates mode="c2p" + select="m:bvar/m:degree/node()"/></m:msup> +</m:mrow> +</xsl:when> +<xsl:otherwise> + <m:mrow><m:mi>d</m:mi><xsl:apply-templates mode="c2p" select="*[last()]"/></m:mrow> + <m:mrow><m:mi>d</m:mi><xsl:apply-templates mode="c2p" select="m:bvar"/></m:mrow> +</xsl:otherwise> + </xsl:choose> + </m:mfrac> +</xsl:template> + + +<!-- 4.4.5.3 partialdiff --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:partialdiff] and m:list and m:ci and count(*)=3]" priority="2"> +<m:mrow> + <m:msub><m:mi>D</m:mi><m:mrow> +<xsl:for-each select="m:list[1]/*"> +<xsl:apply-templates mode="c2p" select="."/> +<xsl:if test="position()<last()"><m:mo>,</m:mo></xsl:if> +</xsl:for-each> +</m:mrow></m:msub> + <m:mrow><xsl:apply-templates mode="c2p" select="*[3]"/></m:mrow> +</m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:partialdiff]]" priority="1"> + <m:mfrac> + <m:mrow> + <xsl:choose><!--dpc--> + <xsl:when test="not(m:bvar/m:degree) and not(m:bvar[2])"> + <m:mo>∂<!-- partial --></m:mo> + </xsl:when> + <xsl:otherwise> + <m:msup><m:mo>∂<!-- partial --></m:mo> + <m:mrow> + <xsl:choose> + <xsl:when test="m:degree"> + <xsl:apply-templates mode="c2p" select="m:degree/node()"/> + </xsl:when> + <xsl:when test="m:bvar/m:degree[string(number(.))='NaN']"> + <xsl:for-each select="m:bvar/m:degree"> + <xsl:apply-templates mode="c2p" select="node()"/> + <xsl:if test="position()<last()"><m:mo>+</m:mo></xsl:if> + </xsl:for-each> + <xsl:if test="count(m:bvar[not(m:degree)])>0"> + <m:mo>+</m:mo><m:mn><xsl:value-of select="count(m:bvar[not(m:degree)])"/></m:mn> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <m:mn><xsl:value-of select="number(sum(m:bvar/m:degree))+count(m:bvar[not(m:degree)])"/></m:mn> + </xsl:otherwise> + </xsl:choose> + </m:mrow> + </m:msup> + </xsl:otherwise> + </xsl:choose> + <xsl:apply-templates mode="c2p" select="*[last()]"/></m:mrow> + <m:mrow> + <xsl:for-each select="m:bvar"> + <m:mrow> + <m:mo>∂<!-- partial --></m:mo><m:msup><xsl:apply-templates mode="c2p" select="node()"/> + <m:mrow><xsl:apply-templates mode="c2p" select="m:degree/node()"/></m:mrow> + </m:msup> + </m:mrow> + </xsl:for-each> + </m:mrow> + </m:mfrac> +</xsl:template> + + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='partialdiffdegree']]"> + <m:mrow> + <m:msub> + <m:mo>∂<!-- partial --></m:mo> + <m:mrow> + <xsl:apply-templates mode="c2p" select="*[2]"/> + </m:mrow> + </m:msub> + <m:mfenced> + <xsl:apply-templates mode="c2p" select="*[4]"/> + </m:mfenced> + </m:mrow> +</xsl:template> + + +<!-- 4.4.5.4 lowlimit--> +<xsl:template mode="c2p" match="m:lowlimit"/> + +<!-- 4.4.5.5 uplimit--> +<xsl:template mode="c2p" match="m:uplimit"/> + +<!-- 4.4.5.6 bvar--> +<xsl:template mode="c2p" match="m:bvar"> + <m:mi><xsl:apply-templates mode="c2p"/></m:mi> + <xsl:if test="following-sibling::m:bvar"><m:mo>,</m:mo></xsl:if> +</xsl:template> + +<!-- 4.4.5.7 degree--> +<xsl:template mode="c2p" match="m:degree"/> + +<!-- 4.4.5.8 divergence--> +<xsl:template mode="c2p" match="m:divergence"> +<m:mi>div</m:mi> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:divergence]and m:bvar and m:vector]"> +<xsl:variable name="v" select="m:bvar"/> +<m:mrow> +<m:mi>div</m:mi> +<m:mo>⁡<!--function application--></m:mo> +<m:mo>(</m:mo> +<m:mtable> +<xsl:for-each select="m:vector/*"> +<xsl:variable name="p" select="position()"/> +<m:mtr><m:mtd> +<xsl:apply-templates mode="c2p" select="$v[$p]/*"/> +<m:mo>↦<!-- map--></m:mo> +<xsl:apply-templates mode="c2p" select="."/> +</m:mtd></m:mtr> +</xsl:for-each> +</m:mtable> +<m:mo>)</m:mo> +</m:mrow> +</xsl:template> + +<!-- 4.4.5.9 grad--> +<xsl:template mode="c2p" match="m:grad"> +<m:mi>grad</m:mi> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:grad]and m:bvar]"> +<m:mrow> +<m:mi>grad</m:mi> +<m:mo>⁡<!--function application--></m:mo> +<m:mrow> +<m:mo>(</m:mo> +<m:mfenced> +<xsl:apply-templates mode="c2p" select="m:bvar/*"/> +</m:mfenced> +<m:mo>↦<!-- map--></m:mo> +<xsl:apply-templates mode="c2p" select="*[position()!=1][not(self::m:bvar)]"/> +<m:mo>)</m:mo> +</m:mrow> +</m:mrow> +</xsl:template> + +<!-- 4.4.5.10 curl --> +<xsl:template mode="c2p" match="m:curl"> +<m:mi>curl</m:mi> +</xsl:template> + + +<!-- 4.4.5.11 laplacian--> +<xsl:template mode="c2p" match="m:laplacian"> +<m:msup><m:mo>∇<!-- nabla --></m:mo><m:mn>2</m:mn></m:msup> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:laplacian]and m:bvar]"> +<m:mrow> +<xsl:apply-templates mode="c2p" select="*[1]"/> +<m:mo>⁡<!--function application--></m:mo> +<m:mrow> +<m:mo>(</m:mo> +<m:mfenced> +<xsl:apply-templates mode="c2p" select="m:bvar/*"/> +</m:mfenced> +<m:mo>↦<!-- map--></m:mo> +<xsl:apply-templates mode="c2p" select="*[position()!=1][not(self::m:bvar)]"/> +<m:mo>)</m:mo> +</m:mrow> +</m:mrow> +</xsl:template> + +<!-- 4.4.6.1 set --> + +<xsl:template mode="c2p" match="m:set"> + <xsl:call-template name="set"/> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='set']]"> +<m:mfenced open="{{" close="}}" separators=","> + <xsl:apply-templates mode="c2p" select="*[position()!=1]"/> +</m:mfenced> +</xsl:template> + +<!-- 4.4.6.2 list --> + +<xsl:template mode="c2p" match="m:list"> + <xsl:call-template name="set"> + <xsl:with-param name="o" select="'('"/> + <xsl:with-param name="c" select="')'"/> + </xsl:call-template> +</xsl:template> + + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='list']]"> +<m:mfenced open="(" close=")" separators=","> + <xsl:apply-templates mode="c2p" select="*[position()!=1]"/> +</m:mfenced> +</xsl:template> + +<!-- 4.4.6.3 union --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:union]] + |m:apply[*[1][self::m:csymbol='union']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="2"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>∪<!-- union --></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:union]][m:bvar] + |m:apply[*[1][self::m:csymbol='union']][m:bvar]" + priority="2" +> + <xsl:call-template name="sum"> + <xsl:with-param name="mo"><m:mo>⋃</m:mo></xsl:with-param> + </xsl:call-template> +</xsl:template> + +<!-- 4.4.6.4 intersect --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:intersect]] + |m:apply[*[1][self::m:csymbol='intersect']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="3"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>∩<!-- intersect --></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:intersect]][m:bvar] + |m:apply[*[1][self::m:csymbol='intersect']][m:bvar]" + priority="2" +> + <xsl:call-template name="sum"> + <xsl:with-param name="mo"><m:mo>⋂</m:mo></xsl:with-param> + </xsl:call-template> +</xsl:template> + + + +<!-- 4.4.6.5 in --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:in]] + |m:apply[*[1][self::m:csymbol='in']]"> + <xsl:param name="p" select="0"/> +<xsl:call-template name="binary"> + <xsl:with-param name="mo"><m:mo>∈<!-- in --></m:mo></xsl:with-param> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="this-p" select="3"/> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.6.5 notin --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:notin]]|m:reln[*[1][self::m:notin]] + |m:apply[*[1][self::m:csymbol='notin']]"> + <xsl:param name="p" select="0"/> +<xsl:call-template name="binary"> + <xsl:with-param name="mo"><m:mo>∉<!-- not in --></m:mo></xsl:with-param> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="this-p" select="3"/> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.6.7 subset --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:subset]] + |m:apply[*[1][self::m:csymbol='subset']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="2"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>⊆<!-- subseteq --></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.6.8 prsubset --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:prsubset]] + |m:apply[*[1][self::m:csymbol='prsubset']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="2"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>⊂<!-- prsubset --></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.6.9 notsubset --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:notsubset]] + |m:apply[*[1][self::m:csymbol='notsubset']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="binary"> + <xsl:with-param name="this-p" select="2"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>⊈<!-- notsubseteq --></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.6.10 notprsubset --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:notprsubset]] + |m:apply[*[1][self::m:csymbol='notprsubset']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="binary"> + <xsl:with-param name="this-p" select="2"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>⊄<!-- prsubset --></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.6.11 setdiff --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:setdiff]] + |m:apply[*[1][self::m:csymbol='setdiff']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="binary"> + <xsl:with-param name="this-p" select="2"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>∖<!-- setminus --></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.6.12 card --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:card]] + |m:apply[*[1][self::m:csymbol='card']]"> +<m:mrow> +<m:mo>|</m:mo> +<xsl:apply-templates mode="c2p" select="*[2]"/> +<m:mo>|</m:mo> +</m:mrow> +</xsl:template> + +<!-- 4.4.6.13 cartesianproduct --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:cartesianproduct or self::m:vectorproduct]] + |m:apply[*[1][self::m:csymbol[.='cartesian_product' or . = 'vectorproduct']]]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="2"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>×<!-- times --></m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<xsl:template +match="m:apply[*[1][self::m:cartesianproduct][count(following-sibling::m:reals)=count(following-sibling::*)]]" +priority="2"> +<m:msup> +<xsl:apply-templates mode="c2p" select="*[2]"> + <xsl:with-param name="p" select="5"/> +</xsl:apply-templates> +<m:mn><xsl:value-of select="count(*)-1"/></m:mn> +</m:msup> +</xsl:template> + + +<!-- 4.4.7.1 sum --> +<xsl:template name="sum" mode="c2p" match="m:apply[*[1][self::m:sum]]"> + <xsl:param name="mo"><m:mo>∑<!--sum--></m:mo></xsl:param> + <m:mrow> + <m:munderover> + <xsl:copy-of select="$mo"/> + <m:mrow><xsl:apply-templates mode="c2p" select="m:lowlimit|m:interval/*[1]|m:condition/*|m:domainofapplication/*"/></m:mrow><!-- Alexey Shamrin shamrinATmail.ru --> + <m:mrow><xsl:apply-templates mode="c2p" select="m:uplimit/*|m:interval/*[2]"/></m:mrow> + </m:munderover> + <xsl:apply-templates mode="c2p" select="*[last()]"/> +</m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='sum']]"> +<m:mrow> +<m:munder><m:mo>∑<!--sum--></m:mo> +<xsl:apply-templates mode="c2p" select="*[2]"/> +</m:munder> + <xsl:apply-templates mode="c2p" select="*[last()]"/> +</m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply/m:lowlimit" priority="3"> +<m:mrow> +<xsl:if test="../m:bvar"> + <xsl:apply-templates mode="c2p" select="../m:bvar/node()"/> + <m:mo>=</m:mo> +</xsl:if> +<xsl:apply-templates mode="c2p"/> +</m:mrow> +</xsl:template> + +<!-- 4.4.7.2 product --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:product]]"> + <xsl:call-template name="sum"> + <xsl:with-param name="mo"><m:mo>∏<!--product--></m:mo></xsl:with-param> + </xsl:call-template> +</xsl:template> + + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='product']]"> +<m:mrow> +<m:munder><m:mo>∏<!--product--></m:mo> +<xsl:apply-templates mode="c2p" select="*[2]"/> +</m:munder> + <xsl:apply-templates mode="c2p" select="*[last()]"/> +</m:mrow> +</xsl:template> + +<!-- 4.4.7.3 limit --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:limit]]"> + <m:mrow> + <m:munder> + <m:mi>lim</m:mi> <!-- Alexey Shamrin shamrinATmail.ru --> + <m:mrow><xsl:apply-templates mode="c2p" select="m:lowlimit|m:condition/*"/></m:mrow> + </m:munder> + <xsl:apply-templates mode="c2p" select="*[last()]"/> +</m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='limit']][m:bind]"> + <m:mrow> + <m:munder> + <m:mi>lim</m:mi> + <m:mrow> + <xsl:apply-templates mode="c2p" select="m:bind/m:bvar/*"/> + <m:mo> + <xsl:choose> + <xsl:when test="*[3]='above'">↘<!--searrow--></xsl:when> + <xsl:when test="*[3]='below'">↗<!--nearrow--></xsl:when> + <xsl:otherwise>→<!--rightarrow--></xsl:otherwise> + </xsl:choose> + </m:mo> + <xsl:apply-templates mode="c2p" select="*[2]"/> +</m:mrow> + </m:munder> + <xsl:apply-templates mode="c2p" select="m:bind/*[last()]"/> +</m:mrow> +</xsl:template> + + + +<xsl:template mode="c2p" match="m:apply[m:limit]/m:lowlimit" priority="4"> +<m:mrow> +<xsl:apply-templates mode="c2p" select="../m:bvar/node()"/> +<m:mo>→<!--rightarrow--></m:mo> +<xsl:apply-templates mode="c2p"/> +</m:mrow> +</xsl:template> + + +<!-- 4.4.7.4 tendsto --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:tendsto]]|m:reln[*[1][self::m:tendsto]]"> + <xsl:param name="p"/> +<xsl:call-template name="binary"> + <xsl:with-param name="this-p" select="2"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo> + <xsl:choose> + <xsl:when test="@type='above'">↘<!--searrow--></xsl:when> + <xsl:when test="@type='below'">↗<!--nearrow--></xsl:when> + <xsl:when test="@type='two-sided'">→<!--rightarrow--></xsl:when> + <xsl:otherwise>→<!--rightarrow--></xsl:otherwise> + </xsl:choose> + </m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='tendsto']]"> + <m:mrow> + <xsl:apply-templates mode="c2p" select="*[3]"/> + <m:mo> + <xsl:choose> + <xsl:when test="*[1][self::above]">↘<!--searrow--></xsl:when> + <xsl:when test="*[1][self::below]">↗<!--nearrow--></xsl:when> + <xsl:when test="*[1][self::two-sided]">→<!--rightarrow--></xsl:when> + <xsl:otherwise>→<!--rightarrow--></xsl:otherwise> + </xsl:choose> + </m:mo> + <xsl:apply-templates mode="c2p" select="*[4]"/> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:semantics/m:ci='tendsto']]"> + <m:mrow> + <xsl:apply-templates mode="c2p" select="*[2]"/> + <m:mo>→<!--rightarrow--></m:mo> + <xsl:apply-templates mode="c2p" select="*[3]"/> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:tendsto"> + <m:mi>tendsto</m:mi> +</xsl:template> + +<!-- 4.4.8.1 trig --> +<xsl:template mode="c2p" match="m:apply[*[1][ + self::m:sin or self::m:cos or self::m:tan or self::m:sec or + self::m:csc or self::m:cot or self::m:sinh or self::m:cosh or + self::m:tanh or self::m:sech or self::m:csch or self::m:coth or + self::m:arcsin or self::m:arccos or self::m:arctan or self::m:arccosh + or self::m:arccot or self::m:arccoth or self::m:arccsc or + self::m:arccsch or self::m:arcsec or self::m:arcsech or + self::m:arcsinh or self::m:arctanh or self::m:ln]]"> + <m:mrow> + <m:mi><xsl:value-of select="local-name(*[1])"/></m:mi> + <m:mo>⁡<!--function application--></m:mo> + <xsl:apply-templates mode="c2p" select="*[2]"> + <xsl:with-param name="p" select="7"/> + </xsl:apply-templates> + </m:mrow> +</xsl:template> + +<!-- Vasil I. Yaroshevich --> +<xsl:template mode="c2p" match=" + m:sin | m:cos | m:tan | m:sec | + m:csc | m:cot | m:sinh | m:cosh | + m:tanh | m:sech | m:csch | m:coth | + m:arcsin | m:arccos | m:arctan | m:arccosh + | m:arccot | m:arccoth | m:arccsc | + m:arccsch | m:arcsec | m:arcsech | + m:arcsinh | m:arctanh | m:ln|m:mean| + m:plus|m:minus"> +<m:mi><xsl:value-of select="local-name()"/></m:mi> +</xsl:template> + + + + +<!-- 4.4.8.2 exp --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:exp]] + |m:apply[*[1][self::m:csymbol='exp']]"> +<m:msup> +<m:mi>e<!-- exponential e--></m:mi> +<m:mrow><xsl:apply-templates mode="c2p" select="*[2]"/></m:mrow> +</m:msup> +</xsl:template> + +<!-- 4.4.8.3 ln --> +<!-- with trig --> + +<!-- 4.4.8.4 log --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:log]] + |m:apply[*[1][self::m:csymbol='log']]"> +<m:mrow> +<xsl:choose> +<xsl:when test="not(m:logbase) or m:logbase=10"> +<m:mi>log</m:mi> +</xsl:when> +<xsl:otherwise> +<m:msub> +<m:mi>log</m:mi> +<m:mrow><xsl:apply-templates mode="c2p" select="m:logbase/node()"/></m:mrow> +</m:msub> +</xsl:otherwise> +</xsl:choose> +<m:mo>⁡<!--function application--></m:mo> +<xsl:apply-templates mode="c2p" select="*[last()]"> + <xsl:with-param name="p" select="7"/> +</xsl:apply-templates> +</m:mrow> +</xsl:template> + + +<!-- 4.4.9.1 mean --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:mean]] + |m:apply[*[1][self::m:csymbol='mean']]"> +<m:mrow> + <m:mo>〈<!--langle--></m:mo> + <xsl:for-each select="*[position()>1]"> + <xsl:apply-templates mode="c2p" select="."/> + <xsl:if test="position() !=last()"><m:mo>,</m:mo></xsl:if> + </xsl:for-each> +<m:mo>〉<!--rangle--></m:mo> +</m:mrow> +</xsl:template> + + +<!-- 4.4.9.2 sdef --> +<xsl:template mode="c2p" match="m:sdev|m:csymbol[.='sdev']"> +<m:mo>σ<!--sigma--></m:mo> +</xsl:template> + +<!-- 4.4.9.3 variance --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:variance]] + |m:apply[*[1][self::m:csymbol='variance']]"> +<m:msup> +<m:mrow> +<m:mo>σ<!--sigma--></m:mo> + <m:mo>⁡<!--function application--></m:mo> +<m:mfenced> +<xsl:apply-templates mode="c2p" select="*[position()!=1]"/> +</m:mfenced> +</m:mrow> +<m:mn>2</m:mn> +</m:msup> +</xsl:template> + + +<!-- 4.4.9.4 median --> +<xsl:template mode="c2p" match="m:median"> +<m:mi>median</m:mi> +</xsl:template> + + +<!-- 4.4.9.5 mode --> +<xsl:template mode="c2p" match="m:mode"> +<m:mi>mode</m:mi> +</xsl:template> + +<!-- 4.4.9.5 moment --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:moment]]"> + <m:mrow> + <m:mo>〈<!--langle--></m:mo> + <m:msup> + <xsl:variable name="data" + select="*[not(position()=1)] + [not(self::m:degree or self::m:momentabout)]"/> + <xsl:choose> + <xsl:when test="$data[2]"> + <m:mfenced> + <xsl:apply-templates mode="c2p" select="$data"/> + </m:mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="c2p" select="$data"/> + </xsl:otherwise> + </xsl:choose> + <m:mrow><xsl:apply-templates mode="c2p" select="m:degree/node()"/></m:mrow> + </m:msup> + <m:mo>〉<!--rangle--></m:mo> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='moment']]"> +<m:msub> + <m:mrow> + <m:mo>〈<!--langle--></m:mo> + <m:msup> + <xsl:apply-templates mode="c2p" select="*[4]"/> + <xsl:apply-templates mode="c2p" select="*[2]"/> + </m:msup> + <m:mo>〉<!--rangle--></m:mo> + </m:mrow> + <xsl:apply-templates mode="c2p" select="*[3]"/> +</m:msub> +</xsl:template> + +<!-- 4.4.9.5 momentabout --> +<xsl:template mode="c2p" match="m:momentabout"/> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:moment]][m:momentabout]" priority="2"> + <m:msub> + <m:mrow> + <m:mo>〈<!--langle--></m:mo> + <m:msup> + <xsl:variable name="data" + select="*[not(position()=1)] + [not(self::m:degree or self::m:momentabout)]"/> + <xsl:choose> + <xsl:when test="$data[2]"> + <m:mfenced> + <xsl:apply-templates mode="c2p" select="$data"/> + </m:mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="c2p" select="$data"/> + </xsl:otherwise> + </xsl:choose> + <m:mrow><xsl:apply-templates mode="c2p" select="m:degree/node()"/></m:mrow> + </m:msup> + <m:mo>〉<!--rangle--></m:mo> + </m:mrow> + <m:mrow> + <xsl:apply-templates mode="c2p" select="m:momentabout/*"/> + </m:mrow> + </m:msub> +</xsl:template> + +<!-- 4.4.10.1 vector --> +<xsl:template mode="c2p" match="m:vector"> +<m:mrow> +<m:mo>(</m:mo> +<m:mtable> +<xsl:for-each select="*"> +<m:mtr><m:mtd><xsl:apply-templates mode="c2p" select="."/></m:mtd></m:mtr> +</xsl:for-each> +</m:mtable> +<m:mo>)</m:mo> +</m:mrow> +</xsl:template> + + +<xsl:template mode="c2p" match="m:vector[m:condition]"> + <m:mrow> + <m:mo>[</m:mo> + <xsl:apply-templates mode="c2p" select="*[last()]"/> + <m:mo>|</m:mo> + <xsl:apply-templates mode="c2p" select="m:condition"/> + <m:mo>]</m:mo> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:vector[m:domainofapplication]"> + <m:mrow> + <m:mo>[</m:mo> + <xsl:apply-templates mode="c2p" select="*[last()]"/> + <m:mo>|</m:mo> + <xsl:apply-templates mode="c2p" select="m:bvar/*"/> + <m:mo>∈</m:mo> + <xsl:apply-templates mode="c2p" select="m:domainofapplication/*"/> + <m:mo>]</m:mo> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='vector']]"> +<m:mrow> +<m:mo>(</m:mo> +<m:mtable> +<xsl:for-each select="*[position()!=1]"> +<m:mtr> + <m:mtd><xsl:apply-templates mode="c2p" select="."/></m:mtd> +</m:mtr> +</xsl:for-each> +</m:mtable> +<m:mo>)</m:mo> +</m:mrow> +</xsl:template> + +<!-- 4.4.10.2 matrix --> +<xsl:template mode="c2p" match="m:matrix"> +<m:mrow> +<m:mo>(</m:mo> +<m:mtable> +<xsl:apply-templates mode="c2p"/> +</m:mtable> +<m:mo>)</m:mo> +</m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:matrix[m:condition]"> + <m:mrow> + <m:mo>[</m:mo> + <m:msub> + <m:mi>m</m:mi> + <m:mrow> + <xsl:for-each select="m:bvar"> + <xsl:apply-templates mode="c2p"/> + <xsl:if test="position()!=last()"><m:mo>,</m:mo></xsl:if> + </xsl:for-each> + </m:mrow> + </m:msub> + <m:mo>|</m:mo> + <m:mrow> + <m:msub> + <m:mi>m</m:mi> + <m:mrow> + <xsl:for-each select="m:bvar"> + <xsl:apply-templates mode="c2p"/> + <xsl:if test="position()!=last()"><m:mo>,</m:mo></xsl:if> + </xsl:for-each> + </m:mrow> + </m:msub> + <m:mo>=</m:mo> + <xsl:apply-templates mode="c2p" select="*[last()]"/> + </m:mrow> + <m:mo>;</m:mo> + <xsl:apply-templates mode="c2p" select="m:condition"/> + <m:mo>]</m:mo> + </m:mrow> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol='matrix']]"> +<m:mrow> +<m:mo>(</m:mo> +<m:mtable> +<xsl:apply-templates mode="c2p" select="*[position()!=1]"/> +</m:mtable> +<m:mo>)</m:mo> +</m:mrow> +</xsl:template> + + +<!-- 4.4.10.3 matrixrow --> +<xsl:template mode="c2p" match="m:matrix/m:matrixrow"> +<m:mtr> +<xsl:for-each select="*"> +<m:mtd><xsl:apply-templates mode="c2p" select="."/></m:mtd> +</xsl:for-each> +</m:mtr> +</xsl:template> + +<xsl:template mode="c2p" match="m:matrixrow"> +<m:mtable> +<m:mtr> +<xsl:for-each select="*"> +<m:mtd><xsl:apply-templates mode="c2p" select="."/></m:mtd> +</xsl:for-each> +</m:mtr> +</m:mtable> +</xsl:template> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:csymbol.='matrixrow']]"> +<m:mtr> +<xsl:for-each select="*[position()!=1]"> +<m:mtd><xsl:apply-templates mode="c2p" select="."/></m:mtd> +</xsl:for-each> +</m:mtr> +</xsl:template> + +<!-- 4.4.10.4 determinant --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:determinant]] + |m:apply[*[1][self::m:csymbol='determinant']]"> +<m:mrow> +<m:mi>det</m:mi> + <m:mo>⁡<!--function application--></m:mo> +<xsl:apply-templates mode="c2p" select="*[2]"> + <xsl:with-param name="p" select="7"/> +</xsl:apply-templates> +</m:mrow> +</xsl:template> + +<xsl:template +match="m:apply[*[1][self::m:determinant]][*[2][self::m:matrix]]" priority="2"> +<m:mrow> +<m:mo>|</m:mo> +<m:mtable> +<xsl:apply-templates mode="c2p" select="m:matrix/*"/> +</m:mtable> +<m:mo>|</m:mo> +</m:mrow> +</xsl:template> + +<!-- 4.4.10.5 transpose --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:transpose]] + |m:apply[*[1][self::m:csymbol='transpose']]"> +<m:msup> +<xsl:apply-templates mode="c2p" select="*[2]"> + <xsl:with-param name="p" select="7"/> +</xsl:apply-templates> +<m:mi>T</m:mi> +</m:msup> +</xsl:template> + +<!-- 4.4.10.5 selector --> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:selector]] + |m:apply[*[1][self::m:csymbol='selector']]"> +<m:msub> +<xsl:apply-templates mode="c2p" select="*[2]"> + <xsl:with-param name="p" select="7"/> +</xsl:apply-templates> +<m:mrow> + <xsl:for-each select="*[position()>2]"> + <xsl:apply-templates mode="c2p" select="."/> + <xsl:if test="position() !=last()"><m:mo>,</m:mo></xsl:if> + </xsl:for-each> +</m:mrow> +</m:msub> +</xsl:template> + +<!-- *** --> +<!-- 4.4.10.6 vectorproduct see cartesianproduct --> + + +<!-- 4.4.10.7 scalarproduct--> +<xsl:template mode="c2p" match="m:apply[*[1][self::m:scalarproduct]] + |m:apply[*[1][self::m:csymbol='scalarproduct']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="2"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>.</m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.10.8 outerproduct--> + +<xsl:template mode="c2p" match="m:apply[*[1][self::m:outerproduct]] + |m:apply[*[1][self::m:csymbol='outerproduct']]"> +<xsl:param name="p" select="0"/> +<xsl:call-template name="infix"> + <xsl:with-param name="this-p" select="2"/> + <xsl:with-param name="p" select="$p"/> + <xsl:with-param name="mo"><m:mo>⊗</m:mo></xsl:with-param> +</xsl:call-template> +</xsl:template> + +<!-- 4.4.11.2 semantics --> +<xsl:template mode="c2p" match="m:semantics"> + <xsl:apply-templates mode="c2p" select="*[1]"/> +</xsl:template> +<xsl:template mode="c2p" match="m:semantics[m:annotation-xml/@encoding='MathML-Presentation']"> + <xsl:apply-templates mode="c2p" select="m:annotation-xml[@encoding='MathML-Presentation']/node()"/> +</xsl:template> + +<!-- 4.4.12.1 integers --> +<xsl:template mode="c2p" match="m:integers"> +<m:mi mathvariant="double-struck">Z</m:mi> +</xsl:template> + +<!-- 4.4.12.2 reals --> +<xsl:template mode="c2p" match="m:reals"> +<m:mi mathvariant="double-struck">R</m:mi> +</xsl:template> + +<!-- 4.4.12.3 rationals --> +<xsl:template mode="c2p" match="m:rationals"> +<m:mi mathvariant="double-struck">Q</m:mi> +</xsl:template> + +<!-- 4.4.12.4 naturalnumbers --> +<xsl:template mode="c2p" match="m:naturalnumbers"> +<m:mi mathvariant="double-struck">N</m:mi> +</xsl:template> + +<!-- 4.4.12.5 complexes --> +<xsl:template mode="c2p" match="m:complexes"> +<m:mi mathvariant="double-struck">C</m:mi> +</xsl:template> + +<!-- 4.4.12.6 primes --> +<xsl:template mode="c2p" match="m:primes"> +<m:mi mathvariant="double-struck">P</m:mi> +</xsl:template> + +<!-- 4.4.12.7 exponentiale --> +<xsl:template mode="c2p" match="m:exponentiale"> + <m:mi>e<!-- exponential e--></m:mi> +</xsl:template> + +<!-- 4.4.12.8 imaginaryi --> +<xsl:template mode="c2p" match="m:imaginaryi"> + <m:mi>i<!-- imaginary i--></m:mi> +</xsl:template> + +<!-- 4.4.12.9 notanumber --> +<xsl:template mode="c2p" match="m:notanumber"> + <m:mi>NaN</m:mi> +</xsl:template> + +<!-- 4.4.12.10 true --> +<xsl:template mode="c2p" match="m:true"> + <m:mi>true</m:mi> +</xsl:template> + +<!-- 4.4.12.11 false --> +<xsl:template mode="c2p" match="m:false"> + <m:mi>false</m:mi> +</xsl:template> + +<!-- 4.4.12.12 emptyset --> +<xsl:template mode="c2p" match="m:emptyset|m:csymbol[.='emptyset']"> + <m:mi>∅<!-- emptyset --></m:mi> +</xsl:template> + + +<!-- 4.4.12.13 pi --> +<xsl:template mode="c2p" match="m:pi|m:csymbol[.='pi']"> + <m:mi>π<!-- pi --></m:mi> +</xsl:template> + +<!-- 4.4.12.14 eulergamma --> +<xsl:template mode="c2p" match="m:eulergamma|m:csymbol[.='gamma']"> + <m:mi>γ<!-- gamma --></m:mi> +</xsl:template> + +<!-- 4.4.12.15 infinity --> +<xsl:template mode="c2p" match="m:infinity|m:csymbol[.='infinity']"> + <m:mi>∞<!-- infinity --></m:mi> +</xsl:template> + + +<!-- ****************************** --> +<xsl:template name="infix" > + <xsl:param name="mo"/> + <xsl:param name="p" select="0"/> + <xsl:param name="this-p" select="0"/> + <xsl:variable name="dmo"> + <xsl:choose> + <xsl:when test="m:domainofapplication"> + <m:munder> + <xsl:copy-of select="$mo"/> + <m:mrow> + <xsl:apply-templates mode="c2p" select="m:domainofapplication/*"/> + </m:mrow> + </m:munder> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$mo"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + <m:mrow> + <xsl:if test="$this-p < $p"><m:mo>(</m:mo></xsl:if> + <xsl:for-each select="*[not(self::m:domainofapplication)][position()>1]"> + <xsl:if test="position() > 1"> + <xsl:copy-of select="$dmo"/> + </xsl:if> + <xsl:apply-templates mode="c2p" select="."> + <xsl:with-param name="p" select="$this-p"/> + </xsl:apply-templates> + </xsl:for-each> + <xsl:if test="$this-p < $p"><m:mo>)</m:mo></xsl:if> + </m:mrow> +</xsl:template> + +<xsl:template name="binary" > + <xsl:param name="mo"/> + <xsl:param name="p" select="0"/> + <xsl:param name="this-p" select="0"/> + <m:mrow> + <xsl:if test="$this-p < $p"><m:mo>(</m:mo></xsl:if> + <xsl:apply-templates mode="c2p" select="*[2]"> + <xsl:with-param name="p" select="$this-p"/> + </xsl:apply-templates> + <xsl:copy-of select="$mo"/> + <xsl:apply-templates mode="c2p" select="*[3]"> + <xsl:with-param name="p" select="$this-p"/> + </xsl:apply-templates> + <xsl:if test="$this-p < $p"><m:mo>)</m:mo></xsl:if> + </m:mrow> +</xsl:template> + +<xsl:template name="set" > + <xsl:param name="o" select="'{'"/> + <xsl:param name="c" select="'}'"/> + <m:mrow> + <m:mo><xsl:value-of select="$o"/></m:mo> + <xsl:choose> + <xsl:when test="m:condition"> + <m:mrow><xsl:apply-templates mode="c2p" select="m:condition/following-sibling::*"/></m:mrow> + <m:mo>|</m:mo> + <m:mrow><xsl:apply-templates mode="c2p" select="m:condition/node()"/></m:mrow> + </xsl:when> + <xsl:when test="m:domainofapplication"> + <m:mrow><xsl:apply-templates mode="c2p" select="m:domainofapplication/following-sibling::*"/></m:mrow> + <m:mo>|</m:mo> + <m:mrow><xsl:apply-templates mode="c2p" select="m:bvar/node()"/></m:mrow> + <m:mo>∈<!-- in --></m:mo> + <m:mrow><xsl:apply-templates mode="c2p" select="m:domainofapplication/node()"/></m:mrow> + </xsl:when> + <xsl:otherwise> + <xsl:for-each select="*[not(position()=1 and parent::m:apply)]"> + <xsl:apply-templates mode="c2p" select="."/> + <xsl:if test="position() !=last()"><m:mo>,</m:mo></xsl:if> + </xsl:for-each> + </xsl:otherwise> + </xsl:choose> + <m:mo><xsl:value-of select="$c"/></m:mo> + </m:mrow> +</xsl:template> + + +<!-- mathml 3 addtitions --> + +<xsl:template mode="c2p" match="m:cs"> + <m:ms> + <xsl:value-of select=" + translate(., + '	  ', + '    ')"/> + </m:ms> +</xsl:template> + +<xsl:template mode="c2p" match="m:cbytes"> + <m:mrow/> +</xsl:template> + +<xsl:template mode="c2p" match="m:cerror"> + <m:merror> + <xsl:apply-templates mode="c2p"/> + </m:merror> +</xsl:template> + +<xsl:template mode="c2p" match="m:share" priority="4"> + <m:mi href="{@href}">share<xsl:value-of select="substring-after(@href,'#')"/></m:mi> +</xsl:template> + +</xsl:stylesheet> + diff --git a/src/main/resources/rendering-xslt/exploded.xsl b/src/main/resources/rendering-xslt/exploded.xsl new file mode 100644 index 0000000000000000000000000000000000000000..be7a1eb4ff8a6b42d353f1ecbde45eee112d530d --- /dev/null +++ b/src/main/resources/rendering-xslt/exploded.xsl @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Renders an "exploded" session. + +Input document: doesn't matter + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw m"> + + <!-- ************************************************************ --> + + <xsl:import href="qti-common.xsl"/> + + <!-- Optional URL for exiting session --> + <xsl:param name="exitSessionUrl" as="xs:string?" required="no"/> + + <!-- ************************************************************ --> + + <xsl:template match="/" as="element(html)"> + <html lang="en"> + <head> + <xsl:call-template name="includeQtiWorksJsAndCss"/> + <title>Assessment failure</title> + </head> + <body class="page exploded"> + <div class="container_12"> + <header class="pageHeader"> + <h1>QTIWorks</h1> + </header> + <xsl:choose> + <xsl:when test="$authorMode"> + <p> + This assessment has failed to run correctly: + </p> + <ul> + <li> + The most likely reasons for failure is bad or invalid assessment XML. + Please <a href="{$webappContextPath}{$validationUrl}">validate this assessment</a> to find and diagnose problems. + </li> + <li> + If your assessment is valid but is not working correctly, then there + may be a logic problem within QTIWorks. Please contact your QTIWorks + support contact for guidance, sending them this assessment for diagnosis. + </li> + </ul> + </xsl:when> + <xsl:otherwise> + <p> + Sorry, but this assessment is not working correctly. This problem has been logged. + </p> + <p> + Please contact your instructor for further help. + </p> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="exists($exitSessionUrlAbsolute)"> + <p> + <a href="{$exitSessionUrlAbsolute}">Exit and return</a> + </p> + </xsl:if> + </div> + </body> + </html> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/associateInteraction.xsl b/src/main/resources/rendering-xslt/interactions/associateInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..aea397f8cba9b4a25d58da50ad96c19ca3374bd6 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/associateInteraction.xsl @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti xs qw"> + + <xsl:template match="qti:associateInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <xsl:call-template name="qw:generic-bad-response-message"/> + </xsl:if> + <xsl:variable name="appletContainerId" select="concat('qtiworks_id_appletContainer_', @responseIdentifier)" as="xs:string"/> + <div id="{$appletContainerId}" class="appletContainer"> + <object type="application/x-java-applet" height="360" width="360"> + <param name="code" value="BoundedGraphicalApplet"/> + <param name="codebase" value="{$appletCodebase}"/> + <param name="identifier" value="{@responseIdentifier}"/> + <param name="baseType" value="pair" /> + <param name="operation_mode" value="graphic_associate_interaction" /> + <!-- (BoundedGraphicalApplet uses -1 to represent 'unlimited') --> + <param name="number_of_responses" value="{if (@maxAssociations > 0) then @maxAssocations else -1}"/> + + <xsl:variable name="choices" as="element(qti:simpleAssociableChoice)*" select="qw:filter-visible(qti:simpleAssociableChoice)"/> + <param name="hotspot_count" value="{count($choices)}"/> + <xsl:for-each select="$choices"> + <!-- (Content is flowStatic, but we can only show strings in labels) --> + <xsl:variable name="content" as="node()*"> + <xsl:apply-templates/> + </xsl:variable> + <param name="hotspot{position()-1}" value="{@identifier}::{normalize-space(string-join(for $n in $content return string($n), ''))}"/> + </xsl:for-each> + + <xsl:variable name="responseValue" select="qw:get-response-value(/, @responseIdentifier)" as="element(qw:responseVariable)?"/> + <xsl:if test="qw:is-not-null-value($responseValue)"> + <param name="feedback"> + <xsl:attribute name="value"> + <xsl:value-of select="$responseValue/qw:value" separator=","/> + </xsl:attribute> + </param> + </xsl:if> + </object> + <script type="text/javascript"> + $(document).ready(function() { + QtiWorksRendering.registerAppletBasedInteractionContainer('<xsl:value-of + select="$appletContainerId"/>', ['<xsl:value-of select="@responseIdentifier"/>']); + }); + </script> + </div> + </div> + </xsl:template> +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/choiceInteraction.xsl b/src/main/resources/rendering-xslt/interactions/choiceInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..9b8d868853c81c2695cd046c357ff85a15c2172a --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/choiceInteraction.xsl @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw"> + + <xsl:template match="qti:choiceInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <div class="badResponse"> + <xsl:choose> + <xsl:when test="@minChoices = @maxChoices and @minChoices > 0"> + You must select + <xsl:value-of select="@minChoices"/> + <xsl:text> </xsl:text> + <xsl:value-of select="if (@minChoices = 1) then 'choice' else 'choices'"/>. + </xsl:when> + <xsl:otherwise> + You must select + <xsl:if test="@minChoices > 0"> + at least <xsl:value-of select="@minChoices"/> + <xsl:if test="@maxChoices > 0"> and </xsl:if> + </xsl:if> + <xsl:if test="@maxChoices > 0"> + at most <xsl:value-of select="@maxChoices"/> + </xsl:if> + choices. + </xsl:otherwise> + </xsl:choose> + </div> + </xsl:if> + <table id="{if (@id) then @id else concat('choiceInteraction-', @responseIdentifier)}"> + <xsl:if test="qti:prompt"> + <tr class="prompt"> + <td colspan="2"> + <xsl:apply-templates select="qti:prompt"/> + </td> + </tr> + </xsl:if> + <xsl:if test="@label"> + <tr class="choiceInteractionLabelRow"> + <td class="leftTextLabel"> + <xsl:value-of select="substring-before(@label, '|')"/> + </td> + <td class="rightTextLabel"> + <xsl:value-of select="substring-after(@label, '|')"/> + </td> + </tr> + </xsl:if> + <xsl:apply-templates select="qw:get-visible-ordered-choices(., qti:simpleChoice)"/> + </table> + </div> + </xsl:template> + + <xsl:template match="qti:simpleChoice"> + <tr class="choiceinteraction"> + <xsl:if test="contains(../@class, 'choiceright')"> + <td class="choiceInteraction"> + <xsl:apply-templates/> + </td> + </xsl:if> + <td class="control"> + <input name="qtiworks_response_{../@responseIdentifier}" value="{@identifier}" type="{if (../@maxChoices=1) then 'radio' else 'checkbox'}"> + <xsl:if test="$isItemSessionEnded"> + <xsl:attribute name="disabled">disabled</xsl:attribute> + </xsl:if> + <xsl:if test="qw:value-contains(qw:get-response-value(/, ../@responseIdentifier), @identifier)"> + <xsl:attribute name="checked">checked</xsl:attribute> + </xsl:if> + </input> + </td> + <xsl:if test="not(contains(../@class, 'choiceright'))"> + <td class="choiceInteraction"> + <xsl:apply-templates/> + </td> + </xsl:if> + </tr> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/drawingInteraction.xsl b/src/main/resources/rendering-xslt/interactions/drawingInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..2199418ce9600037aa8635884c4503e3afb6adda --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/drawingInteraction.xsl @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti"> + + <xsl:template match="qti:drawingInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <div class="flash"> + Rendering of the QTI <tt>drawingInteraction</tt> isn't currently supported! + </div> + </div> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/endAttemptInteraction.xsl b/src/main/resources/rendering-xslt/interactions/endAttemptInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..fe9d12b77dd2a89b3d4fb25b37ab7beca975b68e --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/endAttemptInteraction.xsl @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti"> + + <xsl:template match="qti:endAttemptInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <span class="{local-name()}"> + <input type="submit" name="qtiworks_response_{@responseIdentifier}" value="{@title}"> + <xsl:if test="$isItemSessionEnded"> + <xsl:attribute name="disabled">disabled</xsl:attribute> + </xsl:if> + </input> + </span> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/extendedTextInteraction.xsl b/src/main/resources/rendering-xslt/interactions/extendedTextInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..9c4ca85c89c03bada44075598a04627568c34aec --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/extendedTextInteraction.xsl @@ -0,0 +1,154 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +DM: I don't have anything to test this out with! + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:extendedTextInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <xsl:variable name="responseDeclaration" select="qw:get-response-declaration(/, @responseIdentifier)" as="element(qti:responseDeclaration)?"/> + <xsl:variable name="responseValue" select="qw:get-response-value(/, @responseIdentifier)" as="element(qw:responseVariable)?"/> + <xsl:variable name="responseInput" select="qw:get-response-input(@responseIdentifier)" as="element(qw:responseInput)?"/> + <xsl:variable name="rawInput" select="qw:extract-single-cardinality-response-input($responseInput)" as="xs:string?"/> + + <!-- Create JavaScript to check each field --> + <xsl:variable name="checks" as="xs:string*"> + <xsl:choose> + <xsl:when test="$responseDeclaration/@baseType='float'"><xsl:sequence select="'float'"/></xsl:when> + <xsl:when test="$responseDeclaration/@baseType='integer'"><xsl:sequence select="'integer'"/></xsl:when> + </xsl:choose> + <xsl:if test="@patternMask"> + <xsl:sequence select="('regex', @patternMask)"/> + </xsl:if> + </xsl:variable> + <xsl:variable name="checkJavaScript" select="if (exists($checks)) + then concat('QtiWorksRendering.validateInput(this, ', + qw:to-javascript-arguments($checks), ')') + else ()" as="xs:string?"/> + + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <div class="badResponse"> + <!-- This will happen if either a pattern is wrong or the wrong number of choices + were made --> + <xsl:variable name="quantity" as="xs:string" + select="if (@minStrings=@maxStrings) then 'all' else concat('at least ', @minStrings)"/> + <xsl:choose> + <xsl:when test="@patternMask and @minStrings > 0"> + You must fill in <xsl:value-of select="$quantity"/> boxes + and use the correct format for your input in each box. + </xsl:when> + <xsl:when test="@minStrings > 0"> + You must fill in <xsl:value-of select="$quantity"/> boxes. + </xsl:when> + <xsl:when test="@patternMask"> + You must use the correct format for your input in each box. + </xsl:when> + </xsl:choose> + </div> + </xsl:if> + <xsl:choose> + <xsl:when test="$responseDeclaration/@cardinality='single'"> + <xsl:call-template name="singlebox"> + <xsl:with-param name="responseInput" select="$responseInput"/> + <xsl:with-param name="checkJavaScript" select="$checkJavaScript"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="@maxStrings"> + <xsl:call-template name="multibox"> + <xsl:with-param name="responseInput" select="$responseInput"/> + <xsl:with-param name="checkJavaScript" select="$checkJavaScript"/> + <xsl:with-param name="stringsCount" select="xs:integer(@maxStrings)"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="@minStrings"> + <xsl:call-template name="multibox"> + <xsl:with-param name="checkJavaScript" select="$checkJavaScript"/> + <xsl:with-param name="responseInput" select="$responseInput"/> + <xsl:with-param name="stringsCount" select="if (exists($responseValue)) then max((xs:integer(@minStrings), qw:get-cardinality-size($responseValue))) else xs:integer(@minStrings)"/> + <xsl:with-param name="allowCreate" select="true()"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="multibox"> + <xsl:with-param name="responseInput" select="$responseInput"/> + <xsl:with-param name="checkJavaScript" select="$checkJavaScript"/> + <xsl:with-param name="stringsCount" select="if (exists($responseValue)) then qw:get-cardinality-size($responseValue) else 1"/> + <xsl:with-param name="allowCreate" select="true()"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </div> + </xsl:template> + + <xsl:template name="singlebox"> + <xsl:param name="responseInput" as="element(qw:responseInput)?"/> + <xsl:param name="checkJavaScript" as="xs:string?"/> + <xsl:variable name="responseInputString" select="qw:extract-single-cardinality-response-input($responseInput)" as="xs:string?"/> + <textarea cols="40" rows="6" name="qtiworks_response_{@responseIdentifier}"> + <xsl:if test="$isItemSessionEnded"> + <xsl:attribute name="disabled">disabled</xsl:attribute> + </xsl:if> + <xsl:if test="@expectedLines"> + <xsl:attribute name="rows" select="@expectedLines"/> + </xsl:if> + <xsl:if test="@expectedLines and @expectedLength"> + <xsl:attribute name="cols" select="ceiling(@expectedLength div @expectedLines)"/> + </xsl:if> + <xsl:if test="$checkJavaScript"> + <xsl:attribute name="onchange" select="$checkJavaScript"/> + </xsl:if> + <xsl:value-of select="$responseInputString"/> + </textarea> + </xsl:template> + + <xsl:template name="multibox"> + <xsl:param name="responseInput" as="element(qw:responseInput)?"/> + <xsl:param name="checkJavaScript" as="xs:string?"/> + <xsl:param name="stringsCount" as="xs:integer"/> + <xsl:param name="allowCreate" select="false()" as="xs:boolean"/> + <xsl:variable name="interaction" select="." as="element(qti:extendedTextInteraction)"/> + <xsl:for-each select="1 to $stringsCount"> + <xsl:variable name="i" select="." as="xs:integer"/> + <xsl:variable name="responseInputString" select="$responseInput/qw:value[position()=$i]" as="xs:string?"/> + <input type="text" name="qtiworks_response_{$interaction/@responseIdentifier}"> + <xsl:if test="$interaction/@expectedLength"> + <xsl:attribute name="size" select="$interaction/@expectedLength"/> + </xsl:if> + <xsl:if test="exists($responseInputString)"> + <xsl:attribute name="value" select="$responseInputString"/> + </xsl:if> + <xsl:if test="$checkJavaScript"> + <xsl:attribute name="onchange" select="$checkJavaScript"/> + </xsl:if> + <xsl:if test="$allowCreate and $i=$stringsCount"> + <xsl:attribute name="onkeyup" select="'QtiWorksRendering.addNewTextBox(this)'"/> + </xsl:if> + </input> + <br/> + </xsl:for-each> + </xsl:template> + +</xsl:stylesheet> + diff --git a/src/main/resources/rendering-xslt/interactions/gapMatchInteraction.xsl b/src/main/resources/rendering-xslt/interactions/gapMatchInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..6595ca041ee19ba74685897ba986eca339baa4b7 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/gapMatchInteraction.xsl @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:gapMatchInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <xsl:variable name="orderedVisibleGapTexts" select="qw:get-visible-ordered-choices(., qti:gapText)"/> + <xsl:variable name="visibleGaps" select="qw:filter-visible(.//qti:gap)"/> + <div class="{local-name()}"> + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <xsl:call-template name="qw:generic-bad-response-message"/> + </xsl:if> + <xsl:apply-templates select="*[not(self::qti:gapText or self::qti:prompt)]"/> + <table> + <tr> + <td></td> + <xsl:for-each select="$orderedVisibleGapTexts"> + <td id="qtiworks_id_{../@responseIdentifier}_{@identifier}"> + <xsl:apply-templates/> + </td> + </xsl:for-each> + </tr> + <xsl:variable name="gmi" select="." as="element(qti:gapMatchInteraction)"/> + <xsl:for-each select="$visibleGaps"> + <xsl:variable name="gapIdentifier" select="@identifier" as="xs:string"/> + <tr> + <td> + GAP <xsl:value-of select="position()"/> + </td> + <xsl:for-each select="$orderedVisibleGapTexts"> + <td> + <xsl:variable name="responseValue" select="concat(@identifier, ' ', $gapIdentifier)" as="xs:string"/> + <input type="checkbox" name="qtiworks_response_{$gmi/@responseIdentifier}" value="{$responseValue}"> + <xsl:if test="$isItemSessionEnded"> + <xsl:attribute name="disabled">disabled</xsl:attribute> + </xsl:if> + <xsl:if test="qw:value-contains(qw:get-response-value(/, $gmi/@responseIdentifier), $responseValue)"> + <xsl:attribute name="checked" select="'checked'"/> + </xsl:if> + </input> + </td> + </xsl:for-each> + </tr> + </xsl:for-each> + </table> + <script type='text/javascript'> + QtiWorksRendering.registerGapMatchInteraction('<xsl:value-of select="@responseIdentifier"/>', + {<xsl:for-each select="$orderedVisibleGapTexts"><xsl:if test="position() > 1">, </xsl:if><xsl:value-of select="@identifier"/>:<xsl:value-of select="@matchMax"/></xsl:for-each>}, + {<xsl:for-each select="$visibleGaps"><xsl:if test="position() > 1">, </xsl:if><xsl:value-of select="@identifier"/>:<xsl:value-of select="boolean(@required)"/></xsl:for-each>}); + </script> + </div> + </xsl:template> + + <xsl:template match="qti:gap"> + <xsl:variable name="gmi" select="ancestor::qti:gapMatchInteraction" as="element(qti:gapMatchInteraction)"/> + <xsl:variable name="gaps" select="$gmi//qti:gap" as="element(qti:gap)+"/> + <xsl:variable name="thisGap" select="." as="element(qti:gap)"/> + <span class="gap" id="qtiworks_id_{$gmi/@responseIdentifier}_{@identifier}"> + <!-- (Print index of this gap wrt all gaps in the interaction) --> + GAP <xsl:value-of select="for $i in 1 to count($gaps) return + if ($gaps[$i]/@identifier = $thisGap/@identifier) then $i else ()"/> + </span> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/graphicAssociateInteraction.xsl b/src/main/resources/rendering-xslt/interactions/graphicAssociateInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..d1a3500358886fe3ff3f6f7107ddb4c16cb90c1c --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/graphicAssociateInteraction.xsl @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:graphicAssociateInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <xsl:call-template name="qw:generic-bad-response-message"/> + </xsl:if> + + <xsl:variable name="object" select="qti:object" as="element(qti:object)"/> + <xsl:variable name="appletContainerId" select="concat('qtiworks_id_appletContainer_', @responseIdentifier)" as="xs:string"/> + <div id="{$appletContainerId}" class="appletContainer"> + <object type="application/x-java-applet" height="{$object/@height + 40}" width="{$object/@width}"> + <param name="code" value="BoundedGraphicalApplet"/> + <param name="codebase" value="{$appletCodebase}"/> + <param name="identifier" value="{@responseIdentifier}"/> + <param name="baseType" value="pair"/> + <param name="operation_mode" value="graphic_associate_interaction"/> + <!-- (BoundedGraphicalApplet uses -1 to represent 'unlimited') --> + <param name="number_of_responses" value="{if (@maxAssociations > 0) then @maxAssocations else -1}"/> + <param name="background_image" value="{qw:convert-link($object/@data)}"/> + <xsl:variable name="hotspots" select="qw:filter-visible(qti:associableHotspot)" as="element(qti:associableHotspot)*"/> + <param name="hotspot_count" value="{count($hotspots)}"/> + <xsl:for-each select="$hotspots"> + <param name="hotspot{position()-1}"> + <xsl:attribute name="value"><xsl:value-of select="@identifier"/>::::<xsl:value-of select="@shape"/>::<xsl:value-of select="@coords"/><xsl:if test="@label">::hotSpotLabel:<xsl:value-of select="@label"/></xsl:if><xsl:if test="@matchGroup">::<xsl:value-of select="translate(normalize-space(@matchGroup), ' ', '::')"/></xsl:if><xsl:if test="@matchMax">::maxAssociations:<xsl:value-of select="@matchMax"/></xsl:if></xsl:attribute> + </param> + </xsl:for-each> + <xsl:variable name="responseValue" select="qw:get-response-value(/, @responseIdentifier)" as="element(qw:responseVariable)?"/> + <xsl:if test="qw:is-not-null-value($responseValue)"> + <param name="feedback"> + <xsl:attribute name="value"> + <xsl:value-of select="$responseValue/qw:value" separator=","/> + </xsl:attribute> + </param> + </xsl:if> + </object> + <script type="text/javascript"> + $(document).ready(function() { + QtiWorksRendering.registerAppletBasedInteractionContainer('<xsl:value-of + select="$appletContainerId"/>', ['<xsl:value-of select="@responseIdentifier"/>']); + }); + </script> + </div> + </div> + </xsl:template> +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/graphicGapMatchInteraction.xsl b/src/main/resources/rendering-xslt/interactions/graphicGapMatchInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..d3541be1afda3216a1fa6006d79c31e83a809fc7 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/graphicGapMatchInteraction.xsl @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:graphicGapMatchInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <xsl:call-template name="qw:generic-bad-response-message"/> + </xsl:if> + + <xsl:variable name="object" select="qti:object" as="element(qti:object)"/> + <xsl:variable name="appletContainerId" select="concat('qtiworks_id_appletContainer_', @responseIdentifier)" as="xs:string"/> + <div id="{$appletContainerId}" class="appletContainer"> + <object type="application/x-java-applet" height="{$object/@height + 40}" width="{$object/@width}"> + <param name="code" value="BoundedGraphicalApplet"/> + <param name="codebase" value="{$appletCodebase}"/> + <param name="identifier" value="{@responseIdentifier}"/> + <param name="baseType" value="directedPair"/> + <param name="operation_mode" value="gap_match_interaction"/> + <param name="number_of_responses" value="{count(qti:associableHotspot)}"/> + <param name="background_image" value="{qw:convert-link($object/@data)}"/> + <xsl:variable name="hotspots" select="qw:filter-visible(qti:associableHotspot)" as="element(qti:associableHotspot)*"/> + <param name="hotspot_count" value="{count($hotspots)}"/> + <xsl:for-each select="$hotspots"> + <param name="hotspot{position()-1}"> + <xsl:attribute name="value"><xsl:value-of select="@identifier"/>::::<xsl:value-of select="@shape"/>::<xsl:value-of select="@coords"/><xsl:if test="@label">::hotSpotLabel:<xsl:value-of select="@label"/></xsl:if><xsl:if test="@matchGroup">::<xsl:value-of select="translate(normalize-space(@matchGroup), ' ', '::')"/></xsl:if><xsl:if test="@matchMax">::maxAssociations:<xsl:value-of select="@matchMax"/></xsl:if></xsl:attribute> + </param> + </xsl:for-each> + <xsl:variable name="gapImgs" select="qw:filter-visible(qti:gapImg)" as="element(qti:gapImg)*"/> + <param name="movable_element_count" value="{count($gapImgs)}"/> + <xsl:for-each select="$gapImgs"> + <param name="movable_object{position()-1}"> + <xsl:attribute name="value"><xsl:value-of select="@identifier"/>::<xsl:value-of select="qw:convert-link(qti:object/@data)"/>::<xsl:if test="@label">::hotSpotLabel:<xsl:value-of select="@label"/></xsl:if><xsl:if test="@matchGroup">::<xsl:value-of select="translate(normalize-space(@matchGroup), ' ', '::')"/></xsl:if><xsl:if test="@matchMax">::maxAssociations:<xsl:value-of select="@matchMax"/></xsl:if></xsl:attribute> + </param> + </xsl:for-each> + <xsl:variable name="responseValue" select="qw:get-response-value(/, @responseIdentifier)" as="element(qw:responseVariable)?"/> + <xsl:if test="qw:is-not-null-value($responseValue)"> + <param name="feedback"> + <xsl:attribute name="value"> + <xsl:value-of select="$responseValue/qw:value" separator=","/> + </xsl:attribute> + </param> + </xsl:if> + </object> + <script type="text/javascript"> + $(document).ready(function() { + QtiWorksRendering.registerAppletBasedInteractionContainer('<xsl:value-of + select="$appletContainerId"/>', ['<xsl:value-of select="@responseIdentifier"/>']); + }); + </script> + </div> + </div> + </xsl:template> +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/graphicOrderInteraction.xsl b/src/main/resources/rendering-xslt/interactions/graphicOrderInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..bda449dc73bed9ccc1fad022b0d1ff54e27c955d --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/graphicOrderInteraction.xsl @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:graphicOrderInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <xsl:call-template name="qw:generic-bad-response-message"/> + </xsl:if> + + <xsl:variable name="object" select="qti:object" as="element(qti:object)"/> + <xsl:variable name="appletContainerId" select="concat('qtiworks_id_appletContainer_', @responseIdentifier)" as="xs:string"/> + <div id="{$appletContainerId}" class="appletContainer"> + <object type="application/x-java-applet" height="{$object/@height + 40}" width="{$object/@width}"> + <param name="code" value="BoundedGraphicalApplet"/> + <param name="codebase" value="{$appletCodebase}"/> + <param name="identifier" value="{@responseIdentifier}"/> + <param name="object_type" value="TEXT"/> + <param name="operation_mode" value="graphic_order_interaction"/> + <param name="number_of_responses" value="{count(qti:hotspotChoice)}"/> + <param name="background_image" value="{qw:convert-link($object/@data)}"/> + <xsl:variable name="hotspotChoices" select="qw:filter-visible(qti:hotspotChoice)" as="element(qti:hotspotChoice)*"/> + <param name="hotspot_count" value="{count($hotspotChoices)}"/> + <xsl:for-each select="$hotspotChoices"> + <param name="hotspot{position()-1}"> + <xsl:attribute name="value"><xsl:value-of select="@identifier"/>::::<xsl:value-of select="@shape"/>::<xsl:value-of select="@coords"/><xsl:if test="@label">::hotSpotLabel:<xsl:value-of select="@label"/></xsl:if><xsl:if test="@matchGroup">::<xsl:value-of select="translate(normalize-space(@matchGroup), ' ', '::')"/></xsl:if></xsl:attribute> + </param> + </xsl:for-each> + <param name="movable_element_count" value="{count($hotspotChoices)}"/> + <xsl:for-each select="$hotspotChoices"> + <param name="movable_object{position()-1}" value="{position()}::{position()}"/> + </xsl:for-each> + <xsl:variable name="responseValue" select="qw:get-response-value(/, @responseIdentifier)" as="element(qw:responseVariable)?"/> + <xsl:if test="qw:is-not-null-value($responseValue)"> + <param name="feedback"> + <xsl:attribute name="value"> + <xsl:value-of select="$responseValue/qw:value" separator=","/> + </xsl:attribute> + </param> + </xsl:if> + </object> + <script type="text/javascript"> + $(document).ready(function() { + QtiWorksRendering.registerAppletBasedInteractionContainer('<xsl:value-of + select="$appletContainerId"/>', ['<xsl:value-of select="@responseIdentifier"/>']); + }); + </script> + </div> + </div> + </xsl:template> +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/hotspotInteraction.xsl b/src/main/resources/rendering-xslt/interactions/hotspotInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..fdc1742f3e241f682274a5aaa60ab2d37499cac4 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/hotspotInteraction.xsl @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:hotspotInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <xsl:call-template name="qw:generic-bad-response-message"/> + </xsl:if> + + <xsl:variable name="object" select="qti:object" as="element(qti:object)"/> + <xsl:variable name="appletContainerId" select="concat('qtiworks_id_appletContainer_', @responseIdentifier)" as="xs:string"/> + <div id="{$appletContainerId}" class="appletContainer"> + <object type="application/x-java-applet" height="{$object/@height + 40}" width="{$object/@width}"> + <param name="code" value="BoundedGraphicalApplet"/> + <param name="codebase" value="{$appletCodebase}"/> + <param name="identifier" value="{@responseIdentifier}"/> + <param name="operation_mode" value="hotspot_interaction"/> + <!-- (BoundedGraphicalApplet uses -1 to represent 'unlimited') --> + <param name="number_of_responses" value="{if (@maxChoices > 0) then @maxChoices else -1}"/> + <param name="background_image" value="{qw:convert-link($object/@data)}"/> + <xsl:variable name="hotspotChoices" select="qw:filter-visible(qti:hotspotChoice)" as="element(qti:hotspotChoice)*"/> + <param name="hotspot_count" value="{count($hotspotChoices)}"/> + <xsl:for-each select="qti:hotspotChoice"> + <param name="hotspot{position()-1}" + value="{@identifier}::::{@shape}::{@coords}{if (@label) then concat('::hotSpotLabel',@label) else ''}{if (@matchGroup) then concat('::', translate(normalize-space(@matchGroup), ' ', '::')) else ''}"/> + </xsl:for-each> + + <xsl:variable name="responseValue" select="qw:get-response-value(/, @responseIdentifier)" as="element(qw:responseVariable)?"/> + <xsl:if test="qw:is-not-null-value($responseValue)"> + <param name="feedback"> + <xsl:attribute name="value"> + <xsl:value-of select="$responseValue/qw:value" separator=","/> + </xsl:attribute> + </param> + </xsl:if> + </object> + <script type="text/javascript"> + $(document).ready(function() { + QtiWorksRendering.registerAppletBasedInteractionContainer('<xsl:value-of + select="$appletContainerId"/>', ['<xsl:value-of select="@responseIdentifier"/>']); + }); + </script> + </div> + </div> + </xsl:template> +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/hottextInteraction.xsl b/src/main/resources/rendering-xslt/interactions/hottextInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..6385502306014e7956a3e705bcd50a6c630f11a7 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/hottextInteraction.xsl @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:hottextInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <div class="badResponse"> + You must select + <xsl:if test="@minChoices > 0"> + at least <xsl:value-of select="@minChoices"/> + <xsl:if test="@maxChoices > 0"> and </xsl:if> + </xsl:if> + <xsl:if test="@maxChoices > 0"> + at most <xsl:value-of select="@maxChoices"/> + </xsl:if> + options. + </div> + </xsl:if> + <xsl:apply-templates/> + </div> + </xsl:template> + + <xsl:template match="qti:hottext"> + <xsl:if test="qw:is-visible(.)"> + <xsl:variable name="hottextInteraction" select="ancestor::qti:hottextInteraction" as="element(qti:hottextInteraction)"/> + <xsl:variable name="responseIdentifier" select="$hottextInteraction/@responseIdentifier" as="xs:string"/> + <span class="hottext"> + <input type="{if ($hottextInteraction/@maxChoices=1) then 'radio' else 'checkbox'}" + name="qtiworks_response_{$responseIdentifier}" + value="{@identifier}"> + <xsl:if test="$isItemSessionEnded"> + <xsl:attribute name="disabled">disabled</xsl:attribute> + </xsl:if> + <xsl:if test="qw:value-contains(qw:get-response-value(/, $responseIdentifier), @identifier)"> + <xsl:attribute name="checked" select="'checked'"/> + </xsl:if> + </input> + <xsl:apply-templates/> + </span> + </xsl:if> + </xsl:template> +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/inlineChoiceInteraction.xsl b/src/main/resources/rendering-xslt/interactions/inlineChoiceInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..a849317a505ffb87439aa2cb11c6774343ef6823 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/inlineChoiceInteraction.xsl @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:inlineChoiceInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <span class="{local-name()}"> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <span class="badResponse"> + You must select one of the following options + </span> + </xsl:if> + <select name="qtiworks_response_{@responseIdentifier}"> + <option value="">(Select)</option> + <xsl:apply-templates select="qw:get-visible-ordered-choices(., qti:inlineChoice)"/> + </select> + </span> + </xsl:template> + + <xsl:template match="qti:inlineChoice"> + <option value="{@identifier}"> + <xsl:if test="$isItemSessionEnded"> + <xsl:attribute name="disabled">disabled</xsl:attribute> + </xsl:if> + <xsl:if test="qw:value-contains(qw:get-response-value(/, ../@responseIdentifier), @identifier)"> + <xsl:attribute name="selected" select="'selected'"/> + </xsl:if> + <!-- + Note that the content of <option/> is PCDATA so we need to filter the content. This isn't so + bad as the only HTML content we might get is added by substituting <printedVariable/>. + --> + <xsl:variable name="content" as="item()*"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="for $x in $content return string($x)"/> + </option> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/matchInteraction.xsl b/src/main/resources/rendering-xslt/interactions/matchInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..9f0430fd556544c96fb5b26a8487e375e097150d --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/matchInteraction.xsl @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:matchInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <xsl:variable name="orderedSet1" select="qw:get-visible-ordered-choices(., qti:simpleMatchSet[1]/qti:simpleAssociableChoice)" as="element(qti:simpleAssociableChoice)*"/> + <xsl:variable name="orderedSet2" select="qw:get-visible-ordered-choices(., qti:simpleMatchSet[2]/qti:simpleAssociableChoice)" as="element(qti:simpleAssociableChoice)*"/> + <div class="{local-name()}"> + <xsl:variable name="responseIdentifier" select="@responseIdentifier" as="xs:string"/> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <xsl:call-template name="qw:generic-bad-response-message"/> + </xsl:if> + + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + <table> + <thead> + <tr> + <th/> + <xsl:for-each select="$orderedSet2"> + <th> + <xsl:apply-templates/> + </th> + </xsl:for-each> + </tr> + </thead> + <tbody> + <xsl:for-each select="$orderedSet1"> + <xsl:variable name="set1Identifier" select="@identifier" as="xs:string"/> + <tr> + <th> + <xsl:apply-templates/> + </th> + <xsl:for-each select="$orderedSet2"> + <xsl:variable name="set2Identifier" select="@identifier" as="xs:string"/> + <td align="center"> + <xsl:variable name="responseValue" select="concat($set1Identifier, ' ', $set2Identifier)" as="xs:string"/> + <input type="checkbox" name="qtiworks_response_{$responseIdentifier}" value="{$responseValue}"> + <xsl:if test="$isItemSessionEnded"> + <xsl:attribute name="disabled">disabled</xsl:attribute> + </xsl:if> + <xsl:if test="qw:value-contains(qw:get-response-value(/, $responseIdentifier), $responseValue)"> + <xsl:attribute name="checked">checked</xsl:attribute> + </xsl:if> + </input> + </td> + </xsl:for-each> + </tr> + </xsl:for-each> + </tbody> + </table> + <xsl:if test="$isItemSessionOpen"> + <script type='text/javascript'> + QtiWorksRendering.registerMatchInteraction('<xsl:value-of select="@responseIdentifier"/>', + <xsl:value-of select="@maxAssociations"/>, + {<xsl:for-each select="$orderedSet1"> + <xsl:if test="position() > 1">,</xsl:if> + <xsl:value-of select="@identifier"/>:<xsl:value-of select="@matchMax"/> + </xsl:for-each>}, + {<xsl:for-each select="$orderedSet2"> + <xsl:if test="position() > 1">,</xsl:if> + <xsl:value-of select="@identifier"/>:<xsl:value-of select="@matchMax"/> + </xsl:for-each>}); + </script> + </xsl:if> + </div> + </xsl:template> +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/mathEntryInteraction.xsl b/src/main/resources/rendering-xslt/interactions/mathEntryInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..7a0cba93eaee68975b131e00fa9a73bccca2faa9 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/mathEntryInteraction.xsl @@ -0,0 +1,72 @@ +<?xml version="1.0"?> + +<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:ma="http://mathassess.qtitools.org/xsd/mathassess" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti ma qw m"> + + <xsl:template match="qti:customInteraction[@class='org.qtitools.mathassess.MathEntryInteraction']"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <xsl:variable name="responseInput" select="qw:get-response-input(@responseIdentifier)" as="element(qw:responseInput)?"/> + <xsl:variable name="responseValue" select="qw:get-response-value(/, @responseIdentifier)" as="element(qw:responseVariable)?"/> + <xsl:variable name="asciiMathInput" select="qw:extract-single-cardinality-response-input($responseInput)" as="xs:string?"/> + <xsl:variable name="orientation" select="if (../self::qti:td or count(../*)!=1) then 'vertical' else 'horizontal'" as="xs:string"/> + <div class="mathEntryInteraction {$orientation}"> + <div class="inputPanel"> + <a href="{$webappContextPath}/rendering/mathEntryInteractionHelp.html" target="_blank" id="qtiworks_id_mathEntryHelp_{@responseIdentifier}"></a> + <input id="qtiworks_id_mathEntryInput_{@responseIdentifier}" name="qtiworks_response_{@responseIdentifier}" type="text" + size="{if (exists(@ma:expectedLength)) then @ma:expectedLength else '10'}"> + <xsl:if test="$isItemSessionEnded"> + <xsl:attribute name="disabled">disabled</xsl:attribute> + </xsl:if> + <xsl:if test="exists($asciiMathInput)"> + <xsl:attribute name="value"> + <xsl:value-of select="$asciiMathInput"/> + </xsl:attribute> + </xsl:if> + </input> + </div> + <div class="previewPanel"> + <div id="qtiworks_id_mathEntryMessages_{@responseIdentifier}"></div> + <div id="qtiworks_id_mathEntryPreview_{@responseIdentifier}"> + <!-- Keep this in --> + <math xmlns="http://www.w3.org/1998/Math/MathML"></math> + </div> + </div> + <script type="text/javascript"> + QtiWorksRendering.registerReadyCallback(function() { + var inputControlId = 'qtiworks_id_mathEntryInput_<xsl:value-of select="@responseIdentifier"/>'; + var messageContainerId = 'qtiworks_id_mathEntryMessages_<xsl:value-of select="@responseIdentifier"/>'; + var previewContainerId = 'qtiworks_id_mathEntryPreview_<xsl:value-of select="@responseIdentifier"/>'; + var helpContainerId = 'qtiworks_id_mathEntryHelp_<xsl:value-of select="@responseIdentifier"/>'; + + var upConversionAjaxControl = UpConversionAjaxController.createUpConversionAjaxControl(messageContainerId, previewContainerId); + var widget = AsciiMathInputController.bindInputWidget(inputControlId, upConversionAjaxControl); + widget.setHelpButtonId(helpContainerId); + widget.init(); + <xsl:choose> + <xsl:when test="qw:is-bad-response(@responseIdentifier)"> + widget.show('<xsl:value-of select="$asciiMathInput"/>', { + cmathFailures: {} + }); + </xsl:when> + <xsl:when test="qw:is-null-value($responseValue)"> + widget.syncWithInput(); + </xsl:when> + <xsl:otherwise> + widget.show('<xsl:value-of select="$asciiMathInput"/>', { + cmath: '<xsl:value-of select="qw:escape-for-javascript-string($responseValue/qw:value[@fieldIdentifier='CMathML'])"/>', + pmathBracketed: '<xsl:value-of select="qw:escape-for-javascript-string($responseValue/qw:value[@fieldIdentifier='PMathMLBracketed'])"/>', + }); + </xsl:otherwise> + </xsl:choose> + }); + </script> + </div> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/mediaInteraction.xsl b/src/main/resources/rendering-xslt/interactions/mediaInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..27ca9bb65dda9cfe4b96fa79ed7016eb6f30ae81 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/mediaInteraction.xsl @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti"> + + <xsl:template match="qti:mediaInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="flash"> + Rendering of the QTI <tt>mediaInteraction</tt> isn't currently supported! + </div> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/orderInteraction.xsl b/src/main/resources/rendering-xslt/interactions/orderInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..411bc2da9cdde71e710f3c07dcf076bf7516ae70 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/orderInteraction.xsl @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:orderInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <xsl:call-template name="qw:generic-bad-response-message"/> + </xsl:if> + + <div id="qtiworks_response_{@responseIdentifier}"> + <!-- Create holder for hidden form fields that will contain the actual data to pass back --> + <div class="hiddenInputContainer"></div> + + <!-- Filter out the choice identifiers that are visible and split into those which haven't + been selected and those which have --> + <xsl:variable name="thisInteraction" select="." as="element(qti:orderInteraction)"/> + <xsl:variable name="visibleOrderedChoices" as="element(qti:simpleChoice)*" select="qw:get-visible-ordered-choices(., qti:simpleChoice)"/> + <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> + + <!-- Now generate selection widget --> + <xsl:variable name="orientation" select="if (@orientation) then @orientation else 'horizontal'" as="xs:string"/> + <div class="source box {$orientation}"> + <xsl:if test="$isItemSessionOpen"> + <span class="info">Drag unused items from here...</span> + </xsl:if> + <ul class="{$orientation}"> + <xsl:apply-templates select="$unselectedVisibleChoices"/> + </ul> + <br/> + </div> + <div class="target box {$orientation}"> + <xsl:if test="$isItemSessionOpen"> + <span class="info">Drop and order your selected items here...</span> + </xsl:if> + <ul class="{$orientation}"> + <xsl:apply-templates select="$respondedVisibleChoices"/> + </ul> + <br/> + </div> + <br/> + + <!-- Register with JavaScript --> + <script type="text/javascript"> + $(document).ready(function() { + QtiWorksRendering.registerOrderInteraction('<xsl:value-of + select="@responseIdentifier"/>', [<xsl:value-of + select="qw:to-javascript-arguments(for $s in $unselectedVisibleChoices return $s/@identifier)"/>], [<xsl:value-of + select="qw:to-javascript-arguments(for $s in $respondedVisibleChoices return $s/@identifier)"/>], <xsl:value-of + select="if (@minChoices) then @minChoices else 'null'"/>, <xsl:value-of + select="if (@maxChoices) then @maxChoices else 'null'"/>); + }); + </script> + </div> + </div> + </xsl:template> + + <xsl:template match="qti:orderInteraction/qti:simpleChoice"> + <li id="qtiworks_response_{@identifier}" class="ui-state-default"> + <span class="ui-icon ui-icon-arrowthick-2-n-s"></span> + <xsl:apply-templates/> + </li> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/positionObjectInteraction.xsl b/src/main/resources/rendering-xslt/interactions/positionObjectInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..4fb381e9272e59829c0759a2aa5a70ee711edfb8 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/positionObjectInteraction.xsl @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw"> + + <xsl:template match="qti:positionObjectStage"> + <xsl:for-each select="qti:positionObjectInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + </xsl:for-each> + <div class="{local-name()}"> + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <!-- TODO: This probably looks awful! --> + <xsl:for-each select="qti:positionObjectInteraction"> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <xsl:call-template name="qw:generic-bad-response-message"/> + </xsl:if> + </xsl:for-each> + + <xsl:variable name="object" select="qti:object"/> + <xsl:variable name="appletContainerId" select="concat('qtiworks_id_appletContainer_', qti:positionObjectInteraction[1]/@responseIdentifier)" as="xs:string"/> + <div id="{$appletContainerId}" class="appletContainer"> + <object type="application/x-java-applet" height="{$object/@height + 40}" width="{$object/@width}"> + <param name="code" value="rhotspotV2"/> + <param name="codebase" value="{$appletCodebase}"/> + <param name="NoOfMainImages" value="1"/> + <param name="background_image" value="{qw:convert-link($object/@data)}"/> + <param name="Mainimageno1" value="{qw:convert-link($object/@data)}::0::0::{$object/@width}::{$object/@height}"/> + <param name="baseType" value="point"/> + <param name="noOfTargets" value="0"/> + <param name="identifiedTargets" value="FALSE"/> + <param name="interactions" value="{string-join(for $i in qti:positionObjectInteraction return $i/@responseIdentifier, '::')}"/> + + <xsl:for-each select="qti:positionObjectInteraction"> + <xsl:variable name="interaction" select="." as="element(qti:positionObjectInteraction)"/> + <param name="maxChoices:{@responseIdentifier}" value="{@maxChoices}"/> + <xsl:for-each select="1 to @maxChoices"> + <param name="labelNo{.}:{$interaction/@responseIdentifier}" + value="::{$interaction/qti:object/@type}::{qw:convert-link($interaction/qti:object/@data)}::{$interaction/qti:object/@width}::{$interaction/qti:object/@height}::{$interaction/@maxChoices}"/> + </xsl:for-each> + + <xsl:variable name="responseValue" select="qw:get-response-value(/, @responseIdentifier)" as="element(qw:responseVariable)?"/> + <param name="feedbackState:{@responseIdentifier}"> + <xsl:attribute name="value"> + <xsl:choose> + <xsl:when test="qw:is-not-null-value($responseValue)"> + <xsl:text>Yes:</xsl:text> + <xsl:value-of select="$responseValue/qw:value" separator=":"/> + </xsl:when> + <xsl:otherwise> + <xsl:text>No</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:attribute> + </param> + </xsl:for-each> + + <!--param name="noOfMarkers" value="3"/> + <xsl:attribute name="value"><xsl:value-of select="@maxChoices"/></xsl:attribute> + </param --> + <param name="markerType" value="LABELS"/> + </object> + <script type="text/javascript"> + $(document).ready(function() { + QtiWorksRendering.registerAppletBasedInteractionContainer('<xsl:value-of + select="$appletContainerId"/>', [<xsl:value-of + select="qw:to-javascript-arguments(for $i in qti:positionObjectInteraction return $i/@responseIdentifier)"/>]); + }); + </script> + </div> + </div> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/selectPointInteraction.xsl b/src/main/resources/rendering-xslt/interactions/selectPointInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..ffb1e847cf4eb1da50798ca67e4d4613c8e9a189 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/selectPointInteraction.xsl @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:selectPointInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <xsl:call-template name="qw:generic-bad-response-message"/> + </xsl:if> + + <xsl:variable name="object" select="qti:object" as="element(qti:object)"/> + <xsl:variable name="appletContainerId" select="concat('qtiworks_id_appletContainer_', @responseIdentifier)" as="xs:string"/> + <div id="{$appletContainerId}" class="appletContainer"> + <object type="application/x-java-applet" height="{$object/@height + 40}" width="{$object/@width}"> + <param name="code" value="rhotspotV2"/> + <param name="codebase" value="{$appletCodebase}"/> + <param name="identifier" value="{@responseIdentifier}"/> + <param name="NoOfMainImages" value="1"/> + <param name="Mainimageno1" value="{qw:convert-link($object/@data)}::0::0::{$object/@width}::{$object/@height}"/> + <param name="markerName" value="images/marker.gif"/> + <param name="baseType" value="point"/> + <param name="noOfTargets" value="0"/> + <param name="identifiedTargets" value="FALSE"/> + <param name="noOfMarkers" value="{@maxChoices}"/> + <param name="markerType" value="STANDARD"/> + + <xsl:variable name="responseValue" select="qw:get-response-value(/, @responseIdentifier)" as="element(qw:responseVariable)?"/> + <param name="feedbackState"> + <xsl:attribute name="value"> + <xsl:choose> + <xsl:when test="qw:is-not-null-value($responseValue)"> + <xsl:text>Yes:</xsl:text> + <xsl:value-of select="$responseValue/qw:value" separator=":"/> + </xsl:when> + <xsl:otherwise> + <xsl:text>No</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:attribute> + </param> + </object> + <script type="text/javascript"> + $(document).ready(function() { + QtiWorksRendering.registerAppletBasedInteractionContainer('<xsl:value-of + select="$appletContainerId"/>', ['<xsl:value-of select="@responseIdentifier"/>']); + }); + </script> + </div> + </div> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/sliderInteraction.xsl b/src/main/resources/rendering-xslt/interactions/sliderInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..c47f60808f677d18b66f5397a417ff6c84714fa6 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/sliderInteraction.xsl @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:sliderInteraction"> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <xsl:if test="qw:is-invalid-response(@responseIdentifier)"> + <xsl:call-template name="qw:generic-bad-response-message"/> + </xsl:if> + + <xsl:variable name="value" select="qw:get-response-value(/, @responseIdentifier)"/> + <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:variable name="orientation" select="if (@orientation) then @orientation else 'horizontal'"/> + + <div class="sliderInteraction"> + <xsl:choose> + <xsl:when test="$orientation='horizontal'"> + <div class="sliderHorizontal"> + <div class="sliderWidget" id="qtiworks_id_slider_{@responseIdentifier}"></div> + <div class="sliderValue"><span id="qtiworks_id_slidervalue_{@responseIdentifier}"><xsl:value-of select="$value"/></span></div> + </div> + </xsl:when> + <xsl:otherwise> + <div class="sliderVertical"> + <div class="sliderValue"><span id="qtiworks_id_slidervalue_{@responseIdentifier}"><xsl:value-of select="$value"/></span></div> + <div class="sliderWidget" id="qtiworks_id_slider_{@responseIdentifier}"></div> + </div> + </xsl:otherwise> + </xsl:choose> + <input type="hidden" name="qtiworks_response_{@responseIdentifier}" value="{$value}"/> + <script type="text/javascript"> + $(document).ready(function() { + QtiWorksRendering.registerSliderInteraction('<xsl:value-of + select="@responseIdentifier"/>', { + min: <xsl:value-of select="$min"/>, + max: <xsl:value-of select="$max"/>, + step: <xsl:value-of select="$step"/>, + orientation: '<xsl:value-of select="if (@orientation) then @orientation else 'horizontal'"/>', + isReversed: <xsl:value-of select="if (@reverse) then @reverse else 'false'"/>, + isDiscrete: <xsl:value-of select="$is-discrete"/> + }); + }); + </script> + </div> + </div> + </xsl:template> +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/textEntryInteraction.xsl b/src/main/resources/rendering-xslt/interactions/textEntryInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..e4e7aa1de4238a9ed91bb2cdece6198269545900 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/textEntryInteraction.xsl @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw"> + + <xsl:template match="qti:textEntryInteraction"> + <xsl:variable name="is-bad-response" select="qw:is-bad-response(@responseIdentifier)" as="xs:boolean"/> + <xsl:variable name="is-invalid-response" select="qw:is-invalid-response(@responseIdentifier)" as="xs:boolean"/> + <input name="qtiworks_presented_{@responseIdentifier}" type="hidden" value="1"/> + <span class="{local-name()}"> + <xsl:variable name="responseDeclaration" select="qw:get-response-declaration(/, @responseIdentifier)" as="element(qti:responseDeclaration)?"/> + <xsl:variable name="responseValue" select="qw:get-response-value(/, @responseIdentifier)" as="element(qw:responseVariable)?"/> + <xsl:variable name="responseInput" select="qw:get-response-input(@responseIdentifier)" as="element(qw:responseInput)?"/> + <xsl:variable name="responseInputString" select="qw:extract-single-cardinality-response-input($responseInput)" as="xs:string?"/> + <xsl:variable name="checks" as="xs:string*"> + <xsl:choose> + <xsl:when test="$responseDeclaration/@baseType='float'"><xsl:sequence select="'float'"/></xsl:when> + <xsl:when test="$responseDeclaration/@baseType='integer'"><xsl:sequence select="'integer'"/></xsl:when> + </xsl:choose> + <xsl:if test="@patternMask"> + <xsl:sequence select="('regex', @patternMask)"/> + </xsl:if> + </xsl:variable> + <xsl:variable name="checkJavaScript" select="concat('QtiWorksRendering.validateInput(this, ', + qw:to-javascript-arguments($checks), + ')')" as="xs:string"/> + + <input type="text" name="qtiworks_response_{@responseIdentifier}"> + <xsl:if test="$isItemSessionEnded"> + <xsl:attribute name="disabled">disabled</xsl:attribute> + </xsl:if> + <xsl:if test="$is-bad-response or $is-invalid-response"> + <xsl:attribute name="class" select="'badResponse'"/> + </xsl:if> + <xsl:if test="@expectedLength"> + <xsl:attribute name="size" select="@expectedLength"/> + </xsl:if> + <xsl:choose> + <xsl:when test="$is-bad-response"> + <!-- Response won't have been bound to variable, so show raw input --> + <xsl:attribute name="value" select="$responseInputString"/> + </xsl:when> + <xsl:when test="exists($responseValue)"> + <!-- Response has been bound, so show current variable value --> + <xsl:attribute name="value" select="qw:extract-single-cardinality-value($responseValue)"/> + </xsl:when> + </xsl:choose> + <xsl:if test="exists($checks)"> + <xsl:attribute name="onchange" select="$checkJavaScript"/> + </xsl:if> + </input> + <xsl:if test="$is-bad-response"> + <span class="badResponse"> + You must enter a valid <xsl:value-of select="$responseDeclaration/@baseType"/>! + </span> + </xsl:if> + <xsl:if test="$is-invalid-response"> + <!-- (This must be a regex issue) --> + <span class="badResponse"> + Your input is not of the required format! + </span> + </xsl:if> + </span> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/interactions/uploadInteraction.xsl b/src/main/resources/rendering-xslt/interactions/uploadInteraction.xsl new file mode 100644 index 0000000000000000000000000000000000000000..47052c474f572f9b3b251d61aa330e4092375848 --- /dev/null +++ b/src/main/resources/rendering-xslt/interactions/uploadInteraction.xsl @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti qw xs"> + + <xsl:template match="qti:uploadInteraction"> + <input name="qtiworks_uploadpresented_{@responseIdentifier}" type="hidden" value="1"/> + <div class="{local-name()}"> + <xsl:if test="qti:prompt"> + <div class="prompt"> + <xsl:apply-templates select="qti:prompt"/> + </div> + </xsl:if> + <xsl:variable name="responseValue" select="qw:get-response-value(/, @responseIdentifier)" as="element(qw:responseVariable)?"/> + <xsl:choose> + <xsl:when test="not(empty($responseValue))"> + <!-- Already uploaded something, so show file and ability to replace it --> + <div class="fileUploadStatus"> + Uploaded: <xsl:value-of select="$responseValue/qw:value/@fileName"/> + </div> + <xsl:choose> + <xsl:when test="$isItemSessionOpen"> + <div class="fileUploadInstruction"> + Upload New File + </div> + <input type="file" name="qtiworks_uploadresponse_{@responseIdentifier}"/> + </xsl:when> + <xsl:otherwise> + <input type="file" name="qtiworks_uploadresponse_{@responseIdentifier}" disabled="disabled"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <!-- Nothing uploaded yet --> + <xsl:choose> + <xsl:when test="$isItemSessionOpen"> + <div class="fileUploadInstruction"> + Upload File + </div> + <input type="file" name="qtiworks_uploadresponse_{@responseIdentifier}"/> + </xsl:when> + <xsl:otherwise> + <input type="file" name="qtiworks_uploadresponse_{@responseIdentifier}" disabled="disabled"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </div> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/item-author-view.xsl b/src/main/resources/rendering-xslt/item-author-view.xsl new file mode 100644 index 0000000000000000000000000000000000000000..e327a31c3d12e16ca88464192d5a55ace8f9f9f4 --- /dev/null +++ b/src/main/resources/rendering-xslt/item-author-view.xsl @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Renders the author/debug view of a standalone assessmentItem + +Input document: doesn't matter + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw m"> + + <!-- ************************************************************ --> + + <xsl:import href="author-view-common.xsl"/> + + <!-- State of item being rendered --> + <xsl:param name="itemSessionState" as="element(qw:itemSessionState)"/> + + <!-- ************************************************************ --> + + <xsl:template match="/" as="element(html)"> + <html> + <head> + <title>Author Debug View</title> + <xsl:call-template name="includeQtiWorksJsAndCss"/> + </head> + <body class="page authorInfo"> + <div class="container_12"> + <header class="pageHeader"> + <h1>QTIWorks</h1> + </header> + <h2>QTI standalone item author's feedback</h2> + + <xsl:call-template name="errorStatusPanel"/> + <xsl:call-template name="buttonBar"/> + + <xsl:apply-templates select="$itemSessionState"> + <xsl:with-param name="includeNotifications" select="true()"/> + </xsl:apply-templates> + </div> + </body> + </html> + </xsl:template> + + <xsl:template name="buttonBar"> + <div class="buttonBar"> + <ul class="controls"> + <li> + <form action="{$webappContextPath}{$sourceUrl}" method="get" class="showXmlInDialog" title="Item Source XML"> + <input type="submit" value="View Item source XML"/> + </form> + </li> + <li> + <form action="{$webappContextPath}{$stateUrl}" method="get" class="showXmlInDialog" title="Item State XML"> + <input type="submit" value="View Item state XML"/> + </form> + </li> + <li> + <form action="{$webappContextPath}{$resultUrl}" method="get" class="showXmlInDialog" title="Item Result XML"> + <input type="submit" value="View Item <assessmentResult> XML"/> + </form> + </li> + </ul> + </div> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/item-common.xsl b/src/main/resources/rendering-xslt/item-common.xsl new file mode 100644 index 0000000000000000000000000000000000000000..dda87d903eb7fb6296f7e0def92bf384bce553cf --- /dev/null +++ b/src/main/resources/rendering-xslt/item-common.xsl @@ -0,0 +1,473 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Common templates for QTI flow elements, used in both item and test +rendering. + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns:saxon="http://saxon.sf.net/" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti xs qw saxon m"> + + <!-- ************************************************************ --> + + <xsl:import href="qti-common.xsl"/> + + <!-- State of item being rendered --> + <xsl:param name="itemSessionState" as="element(qw:itemSessionState)"/> + + <!-- Flag to enable modal rendering of model solution for this item --> + <xsl:param name="solutionMode" as="xs:boolean" required="yes"/> + + <!-- Extract information from the <itemSessionState> --> + <xsl:variable name="shuffledChoiceOrders" select="$itemSessionState/qw:shuffledInteractionChoiceOrder" + as="element(qw:shuffledInteractionChoiceOrder)*"/> + <xsl:variable name="templateValues" select="$itemSessionState/qw:templateVariable" as="element(qw:templateVariable)*"/> + <xsl:variable name="responseValues" select="$itemSessionState/qw:responseVariable" as="element(qw:responseVariable)*"/> + <xsl:variable name="outcomeValues" select="$itemSessionState/qw:outcomeVariable" as="element(qw:outcomeVariable)*"/> + <xsl:variable name="overriddenCorrectResponses" select="$itemSessionState/qw:overriddenCorrectResponse" as="element(qw:overriddenCorrectResponse)*"/> + <xsl:variable name="sessionStatus" select="$itemSessionState/@sessionStatus" as="xs:string"/> + <xsl:variable name="isItemSessionEnded" as="xs:boolean" select="$itemSessionState/@endTime!='' or $solutionMode"/> + <xsl:variable name="isItemSessionOpen" as="xs:boolean" select="$itemSessionState/@entryTime!='' and not($isItemSessionEnded)"/> + <xsl:variable name="isItemSessionExited" as="xs:boolean" select="$itemSessionState/@exitTime!=''"/> + + <!-- Raw response inputs --> + <xsl:variable name="responseInputs" select="$itemSessionState/qw:responseInput" as="element(qw:responseInput)*"/> + + <!-- Uncommitted response values --> + <xsl:variable name="uncommittedResponseValues" select="$itemSessionState/qw:uncommittedResponseValue" as="element(qw:uncommittedResponseValue)*"/> + + <!-- Bad/invalid responses --> + <xsl:variable name="unboundResponseIdentifiers" select="$itemSessionState/@unboundResponseIdentifiers" as="xs:string*"/> + <xsl:variable name="invalidResponseIdentifiers" select="$itemSessionState/@invalidResponseIdentifiers" as="xs:string*"/> + + <!-- Is a model solution provided? --> + <xsl:variable name="hasModelSolution" as="xs:boolean" select="exists(/qti:assessmentItem/qti:responseDeclaration/qti:correctResponse) or exists($overriddenCorrectResponses)"/> + + <!-- Is there templateProcessing or responseProcessing? --> + <xsl:variable name="hasTemplateProcessing" as="xs:boolean" select="exists(/qti:assessmentItem/qti:templateProcessing)"/> + <xsl:variable name="hasResponseProcessing" as="xs:boolean" select="exists(/qti:assessmentItem/qti:responseProcessing)"/> + + <!-- Include stylesheets handling each type of interaction --> + <xsl:include href="interactions/associateInteraction.xsl"/> + <xsl:include href="interactions/choiceInteraction.xsl"/> + <xsl:include href="interactions/drawingInteraction.xsl"/> + <xsl:include href="interactions/endAttemptInteraction.xsl"/> + <xsl:include href="interactions/extendedTextInteraction.xsl"/> + <xsl:include href="interactions/gapMatchInteraction.xsl"/> + <xsl:include href="interactions/graphicAssociateInteraction.xsl"/> + <xsl:include href="interactions/graphicGapMatchInteraction.xsl"/> + <xsl:include href="interactions/graphicOrderInteraction.xsl"/> + <xsl:include href="interactions/hotspotInteraction.xsl"/> + <xsl:include href="interactions/hottextInteraction.xsl"/> + <xsl:include href="interactions/inlineChoiceInteraction.xsl"/> + <xsl:include href="interactions/matchInteraction.xsl"/> + <xsl:include href="interactions/mediaInteraction.xsl"/> + <xsl:include href="interactions/orderInteraction.xsl"/> + <xsl:include href="interactions/positionObjectInteraction.xsl"/> + <xsl:include href="interactions/selectPointInteraction.xsl"/> + <xsl:include href="interactions/sliderInteraction.xsl"/> + <xsl:include href="interactions/textEntryInteraction.xsl"/> + <xsl:include href="interactions/uploadInteraction.xsl"/> + <xsl:include href="interactions/mathEntryInteraction.xsl"/> + + <!-- ************************************************************ --> + + <xsl:function name="qw:get-response-input" as="element(qw:responseInput)?"> + <xsl:param name="identifier" as="xs:string"/> + <xsl:sequence select="$responseInputs[@identifier=$identifier]"/> + </xsl:function> + + <xsl:function name="qw:is-bad-response" as="xs:boolean"> + <xsl:param name="identifier" as="xs:string"/> + <xsl:sequence select="$unboundResponseIdentifiers=$identifier"/> + </xsl:function> + + <xsl:function name="qw:is-invalid-response" as="xs:boolean"> + <xsl:param name="identifier" as="xs:string"/> + <xsl:sequence select="$invalidResponseIdentifiers=$identifier"/> + </xsl:function> + + <xsl:function name="qw:extract-single-cardinality-response-input" as="xs:string"> + <xsl:param name="responseInput" as="element(qw:responseInput)?"/> + <xsl:choose> + <xsl:when test="$responseInput/qw:file"> + <xsl:message terminate="yes">This function does not support file responses</xsl:message> + </xsl:when> + <xsl:otherwise> + <xsl:variable name="strings" select="$responseInput/qw:string" as="element(qw:string)*"/> + <xsl:choose> + <xsl:when test="not(exists($strings))"> + <xsl:sequence select="''"/> + </xsl:when> + <xsl:when test="count($strings)=1"> + <xsl:sequence select="$strings[1]"/> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes"> + Expected response input <xsl:copy-of select="$responseInput"/> to contain one string value only + </xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </xsl:function> + + <xsl:function name="qw:get-template-value" as="element(qw:templateVariable)?"> + <xsl:param name="identifier" as="xs:string"/> + <xsl:sequence select="$templateValues[@identifier=$identifier]"/> + </xsl:function> + + <xsl:function name="qw:get-outcome-value" as="element(qw:outcomeVariable)?"> + <xsl:param name="identifier" as="xs:string"/> + <xsl:sequence select="$outcomeValues[@identifier=$identifier]"/> + </xsl:function> + + <xsl:function name="qw:get-response-declaration" as="element(qti:responseDeclaration)?"> + <xsl:param name="document" as="document-node()"/> + <xsl:param name="identifier" as="xs:string"/> + <xsl:sequence select="$document/qti:assessmentItem/qti:responseDeclaration[@identifier=$identifier]"/> + </xsl:function> + + <!-- NB: This now checks *uncommitted* responses first, then *committed* responses --> + <xsl:function name="qw:get-response-value" as="element(qw:responseVariable)?"> + <xsl:param name="document" as="document-node()"/> + <xsl:param name="identifier" as="xs:string"/> + <xsl:variable name="responseDeclaration" select="qw:get-response-declaration($document, $identifier)" as="element(qti:responseDeclaration)?"/> + <xsl:choose> + <xsl:when test="$solutionMode and $overriddenCorrectResponses[@identifier=$identifier]"> + <!-- Correct response has been set during template processing --> + <xsl:for-each select="$overriddenCorrectResponses[@identifier=$identifier]"> + <qw:responseVariable> + <xsl:copy-of select="@cardinality, @baseType"/> + <xsl:copy-of select="qw:value"/> + </qw:responseVariable> + </xsl:for-each> + </xsl:when> + <xsl:when test="$solutionMode and $responseDeclaration/qti:correctResponse"> + <!-- <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> + </xsl:when> + <xsl:when test="$uncommittedResponseValues[@identifier=$identifier]"> + <!-- There's an uncommitted value here. We don't distinguish between uncommitted and committed during rendering --> + <xsl:for-each select="$uncommittedResponseValues[@identifier=$identifier]"> + <qw:responseVariable> + <xsl:copy-of select="@cardinality, @baseType"/> + <xsl:copy-of select="qw:value"/> + </qw:responseVariable> + </xsl:for-each> + </xsl:when> + <xsl:otherwise> + <!-- This is a committed value, which is already in a <qw:responseVariable/> --> + <xsl:sequence select="$responseValues[@identifier=$identifier]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:function> + + <xsl:function name="qw:get-template-declaration" as="element(qti:templateDeclaration)?"> + <xsl:param name="document" as="document-node()"/> + <xsl:param name="identifier" as="xs:string"/> + <xsl:sequence select="$document/qti:assessmentItem/qti:templateDeclaration[@identifier=$identifier]"/> + </xsl:function> + + <xsl:function name="qw:get-outcome-declaration" as="element(qti:outcomeDeclaration)?"> + <xsl:param name="document" as="document-node()"/> + <xsl:param name="identifier" as="xs:string"/> + <xsl:sequence select="$document/qti:assessmentItem/qti:outcomeDeclaration[@identifier=$identifier]"/> + </xsl:function> + + <!-- ************************************************************ --> + + <!-- Tests the @showHide and @templateIdentifier attributes of the given (choice) element to determine whether it + should be shown or not --> + <xsl:function name="qw:is-visible" as="xs:boolean"> + <xsl:param name="element" as="element()"/> + <xsl:sequence select="boolean($element[$overrideTemplate + or not(@templateIdentifier) + or (qw:value-contains(qw:get-template-value(@templateIdentifier), @identifier) and not(@showHide='hide'))])"/> + </xsl:function> + + <!-- Filters out the elements in the given sequence having @showHide and @templateIdentifier attributes to return + the ones that will actually be visible --> + <xsl:function name="qw:filter-visible" as="element()*"> + <xsl:param name="elements" as="element()*"/> + <xsl:sequence select="$elements[qw:is-visible(.)]"/> + </xsl:function> + + <xsl:function name="qw:get-shuffled-choice-order" as="xs:string*"> + <xsl:param name="interaction" as="element()"/> + <xsl:variable name="choiceSequence" as="xs:string?" + select="$shuffledChoiceOrders[@responseIdentifier=$interaction/@responseIdentifier]/@choiceSequence"/> + <xsl:sequence select="tokenize($choiceSequence, ' ')"/> + </xsl:function> + + <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> + + <!-- ************************************************************ --> + + <xsl:template name="qw:generic-bad-response-message"> + <div class="badResponse"> + Please complete this interaction as directed. + </div> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template match="qti:infoControl" as="element(div)"> + <div class="infoControl"> + <input type="submit" onclick="return QtiWorksRendering.showInfoControlContent(this)" value="{@title}"/> + <div class="infoControlContent"> + <xsl:apply-templates/> + </div> + </div> + </xsl:template> + + <!-- Stylesheet link --> + <xsl:template match="qti:stylesheet" as="element(link)"> + <link rel="stylesheet"> + <xsl:copy-of select="@* except @href"/> + <xsl:if test="exists(@href)"> + <xsl:attribute name="href" select="qw:convert-link(@href)"/> + </xsl:if> + </link> + </xsl:template> + + <!-- prompt --> + <xsl:template match="qti:prompt"> + <xsl:apply-templates/> + </xsl:template> + + <!-- param (override to handle template variables) --> + <xsl:template match="qti:param"> + <xsl:variable name="templateValue" select="qw:get-template-value(@value)" as="element(qw:templateVariable)?"/> + <!-- Note: spec is not explicit in that we really only allow single cardinality param substitution --> + <param data-dave="yes" name="{@name}" value="{if (exists($templateValue) + and qw:is-single-cardinality-value($templateValue) + and qw:get-template-declaration(/, @value)[@paramVariable='true']) + then qw:extract-single-cardinality-value($templateValue) else @value}"/> + </xsl:template> + + <xsl:template match="qti:rubricBlock" as="element(div)"> + <div class="rubric {@view}"> + <xsl:if test="not($view) or ($view = @view)"> + <xsl:apply-templates/> + </xsl:if> + </div> + </xsl:template> + + <!-- printedVariable. Numeric output currently only supports Java String.format formatting. --> + <xsl:template match="qti:assessmentItem//qti:printedVariable" as="element(span)"> + <xsl:variable name="identifier" select="@identifier" as="xs:string"/> + <xsl:variable name="templateValue" select="qw:get-template-value(@identifier)" as="element(qw:templateVariable)?"/> + <xsl:variable name="outcomeValue" select="qw:get-outcome-value(@identifier)" as="element(qw:outcomeVariable)?"/> + <span class="printedVariable"> + <xsl:choose> + <xsl:when test="exists($outcomeValue)"> + <xsl:call-template name="printedVariable"> + <xsl:with-param name="source" select="."/> + <xsl:with-param name="valueHolder" select="$outcomeValue"/> + <xsl:with-param name="valueDeclaration" select="qw:get-outcome-declaration(/, @identifier)"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="exists($templateValue)"> + <xsl:call-template name="printedVariable"> + <xsl:with-param name="source" select="."/> + <xsl:with-param name="valueHolder" select="$templateValue"/> + <xsl:with-param name="valueDeclaration" select="qw:get-template-declaration(/, @identifier)"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + (variable <xsl:value-of select="$identifier"/> was not found) + </xsl:otherwise> + </xsl:choose> + </span> + </xsl:template> + + <!-- Keep MathML by default --> + <xsl:template match="m:*" as="element()"> + <xsl:element name="{local-name()}" namespace="http://www.w3.org/1998/Math/MathML"> + <xsl:copy-of select="@*"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:template> + + <!-- MathML parallel markup containers: we'll remove any non-XML annotations, which may + result in the container also being removed as it's no longer required in that case. --> + <xsl:template match="m:semantics" as="element()*"> + <xsl:choose> + <xsl:when test="not(*[position()!=1 and self::m:annotation-xml])"> + <!-- All annotations are non-XML so remove this wrapper completely (and unwrap a container mrow if required) --> + <xsl:apply-templates select="if (*[1][self::m:mrow]) then *[1]/* else *[1]"/> + </xsl:when> + <xsl:otherwise> + <!-- Keep non-XML annotations --> + <xsl:element name="semantics" namespace="http://www.w3.org/1998/Math/MathML"> + <xsl:apply-templates select="* except m:annotation"/> + </xsl:element> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="m:*/text()" as="text()"> + <!-- NOTE: The XML input is produced using JQTI's toXmlString() method, which has + the unfortunate effect of indenting MathML, so we'll renormalise --> + <xsl:value-of select="normalize-space(.)"/> + </xsl:template> + + <!-- mathml (mi) --> + <!-- + We are extending the spec here in 2 ways: + 1. Allowing MathsContent variables to be substituted + 2. Allowing arbitrary response and outcome variables to be substituted. + --> + <xsl:template match="qti:assessmentItem//m:mi" as="element()"> + <xsl:variable name="content" select="normalize-space(text())" as="xs:string"/> + <xsl:variable name="templateValue" select="qw:get-template-value($content)" as="element(qw:templateVariable)?"/> + <xsl:variable name="responseValue" select="qw:get-response-value(/, $content)" as="element(qw:responseVariable)?"/> + <xsl:variable name="outcomeValue" select="qw:get-outcome-value($content)" as="element(qw:outcomeVariable)?"/> + <xsl:choose> + <xsl:when test="exists($templateValue) and qw:get-template-declaration(/, $content)[@mathVariable='true']"> + <xsl:call-template name="substitute-mi"> + <xsl:with-param name="identifier" select="$content"/> + <xsl:with-param name="value" select="$templateValue"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="exists($responseValue)"> + <xsl:call-template name="substitute-mi"> + <xsl:with-param name="identifier" select="$content"/> + <xsl:with-param name="value" select="$responseValue"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="exists($outcomeValue)"> + <xsl:call-template name="substitute-mi"> + <xsl:with-param name="identifier" select="$content"/> + <xsl:with-param name="value" select="$outcomeValue"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:element name="mi" namespace="http://www.w3.org/1998/Math/MathML"> + <xsl:copy-of select="@*"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- mathml (ci) --> + <!-- + We are extending the spec here in 2 ways: + 1. Allowing MathsContent variables to be substituted + 2. Allowing arbitrary response and outcome variables to be substituted. + --> + <xsl:template match="qti:assessmentItem//m:ci" as="element()*"> + <xsl:variable name="content" select="normalize-space(text())" as="xs:string"/> + <xsl:variable name="templateValue" select="qw:get-template-value($content)" as="element(qw:templateVariable)?"/> + <xsl:variable name="responseValue" select="qw:get-response-value(/, $content)" as="element(qw:responseVariable)?"/> + <xsl:variable name="outcomeValue" select="qw:get-outcome-value($content)" as="element(qw:outcomeVariable)?"/> + <xsl:choose> + <xsl:when test="exists($templateValue) and qw:get-template-declaration(/, $content)[@mathVariable='true']"> + <xsl:call-template name="substitute-ci"> + <xsl:with-param name="identifier" select="$content"/> + <xsl:with-param name="value" select="$templateValue"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="exists($responseValue)"> + <xsl:call-template name="substitute-ci"> + <xsl:with-param name="identifier" select="$content"/> + <xsl:with-param name="value" select="$responseValue"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="exists($outcomeValue)"> + <xsl:call-template name="substitute-ci"> + <xsl:with-param name="identifier" select="$content"/> + <xsl:with-param name="value" select="$outcomeValue"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:element name="ci" namespace="http://www.w3.org/1998/Math/MathML"> + <xsl:copy-of select="@*"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- ************************************************************ --> + + <!-- feedback (block and inline) --> + <xsl:template name="feedback" as="node()*"> + <xsl:choose> + <xsl:when test="$overrideFeedback"> + <xsl:apply-templates/> + </xsl:when> + <xsl:otherwise> + <xsl:variable name="identifierMatch" select="boolean(qw:value-contains(qw:get-outcome-value(@outcomeIdentifier), @identifier))" as="xs:boolean"/> + <xsl:if test="($identifierMatch and @showHide='show') or (not($identifierMatch) and @showHide='hide')"> + <xsl:apply-templates/> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- ************************************************************ --> + + <!-- templateBlock --> + <xsl:template match="qti:templateBlock" as="node()*"> + <xsl:call-template name="template"/> + </xsl:template> + + <!-- templateInline --> + <xsl:template match="qti:templateInline" as="node()*"> + <xsl:call-template name="template"/> + </xsl:template> + + <!-- template (block and feedback) --> + <xsl:template name="template" as="node()*"> + <xsl:choose> + <xsl:when test="$overrideTemplate"> + <xsl:apply-templates/> + </xsl:when> + <xsl:otherwise> + <xsl:variable name="identifierMatch" select="boolean(qw:value-contains(qw:get-template-value(@templateIdentifier),@identifier))" as="xs:boolean"/> + <xsl:if test="($identifierMatch and @showHide='show') or (not($identifierMatch) and @showHide='hide')"> + <xsl:apply-templates/> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/item-standalone.xsl b/src/main/resources/rendering-xslt/item-standalone.xsl new file mode 100644 index 0000000000000000000000000000000000000000..7b884b1db4825331b5d18ac8615f7fda860486b3 --- /dev/null +++ b/src/main/resources/rendering-xslt/item-standalone.xsl @@ -0,0 +1,210 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Renders a standalone assessmentItem + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw m"> + + <!-- ************************************************************ --> + + <xsl:import href="qti-fallback.xsl"/> + <xsl:import href="item-common.xsl"/> + <xsl:import href="utils.xsl"/> + + <!-- Item prompt --> + <xsl:param name="prompt" select="()" as="xs:string?"/> + + <!-- Action permissions --> + <xsl:param name="endAllowed" as="xs:boolean" required="yes"/> + <xsl:param name="solutionAllowed" as="xs:boolean" required="yes"/> + <xsl:param name="softSoftResetAllowed" as="xs:boolean" required="yes"/> + <xsl:param name="hardResetAllowed" as="xs:boolean" required="yes"/> + <xsl:param name="candidateCommentAllowed" as="xs:boolean" required="yes"/> + + <!-- Action URLs --> + <xsl:param name="softResetUrl" as="xs:string" required="yes"/> + <xsl:param name="hardResetUrl" as="xs:string" required="yes"/> + <xsl:param name="endUrl" as="xs:string" required="yes"/> + <xsl:param name="solutionUrl" as="xs:string" required="yes"/> + <xsl:param name="exitUrl" as="xs:string" required="yes"/> + + <!-- ************************************************************ --> + + <!-- Item may be QTI 2.0 or 2.1, so we'll put a template in here to fix namespaces to QTI 2.1 --> + <xsl:template match="/"> + <xsl:apply-templates select="qw:to-qti21(/)/*"/> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template match="qti:assessmentItem" as="element(html)"> + <xsl:variable name="containsMathEntryInteraction" + select="exists(qti:itemBody//qti:customInteraction[@class='org.qtitools.mathassess.MathEntryInteraction'])" + as="xs:boolean"/> + <html> + <xsl:if test="@lang"> + <xsl:copy-of select="@lang"/> + <xsl:attribute name="xml:lang" select="@lang"/> + </xsl:if> + <head> + <title><xsl:value-of select="@title"/></title> + <xsl:call-template name="includeAssessmentJsAndCss"/> + + <!-- + Import ASCIIMathML stuff if there are any MathEntryInteractions in the question. + (It would be quite nice if we could allow each interaction to hook into this + part of the result generation directly.) + --> + <xsl:if test="$containsMathEntryInteraction"> + <script src="{$webappContextPath}/rendering/javascript/UpConversionAjaxController.js?{$qtiWorksVersion}"/> + <script src="{$webappContextPath}/rendering/javascript/AsciiMathInputController.js?{$qtiWorksVersion}"/> + <script> + UpConversionAjaxController.setUpConversionServiceUrl('<xsl:value-of select="$webappContextPath"/>/candidate/verifyAsciiMath'); + UpConversionAjaxController.setDelay(300); + </script> + </xsl:if> + + <!-- Include stylesheet declared within item --> + <xsl:apply-templates select="qti:stylesheet"/> + </head> + <body class="qtiworks assessmentItem"> + <xsl:call-template name="maybeAddAuthoringLink"/> + + <!-- Item title --> + <h1 class="itemTitle"> + <xsl:apply-templates select="$itemSessionState" mode="item-status"/> + <xsl:value-of select="@title"/> + </h1> + + <!-- Delivery prompt --> + <xsl:if test="$prompt"> + <div class="itemPrompt"> + <xsl:value-of select="$prompt"/> + </div> + </xsl:if> + + <!-- Item body --> + <xsl:apply-templates select="qti:itemBody"/> + + <!-- Display active modal feedback (only after responseProcessing) --> + <xsl:if test="$sessionStatus='final'"> + <xsl:variable name="modalFeedback" as="element()*"> + <xsl:for-each select="qti:modalFeedback"> + <xsl:variable name="feedback" as="node()*"> + <xsl:call-template name="feedback"/> + </xsl:variable> + <xsl:if test="$feedback"> + <div class="modalFeedbackItem"> + <xsl:if test="@title"><h3><xsl:value-of select="@title"/></h3></xsl:if> + <xsl:sequence select="$feedback"/> + </div> + </xsl:if> + </xsl:for-each> + </xsl:variable> + <xsl:if test="exists($modalFeedback)"> + <div class="modalFeedback"> + <h2>Feedback</h2> + <xsl:sequence select="$modalFeedback"/> + </div> + </xsl:if> + </xsl:if> + + <!-- Session control --> + <xsl:call-template name="qw:item-controls"/> + </body> + </html> + </xsl:template> + + <xsl:template name="qw:item-controls"> + <ul class="sessionControl"> + <xsl:if test="$softSoftResetAllowed"> + <li> + <form action="{$webappContextPath}{$softResetUrl}" method="post"> + <input type="submit" value="Reset{if ($isItemSessionEnded) then ' and play again' else ''}"/> + </form> + </li> + </xsl:if> + <xsl:if test="$hardResetAllowed and $hasTemplateProcessing"> + <li> + <form action="{$webappContextPath}{$hardResetUrl}" method="post"> + <input type="submit" value="Reinitialise{if ($isItemSessionEnded) then ' and play again' else ''}"/> + </form> + </li> + </xsl:if> + <xsl:if test="$endAllowed and $hasResponseProcessing"> + <li> + <form action="{$webappContextPath}{$endUrl}" method="post"> + <input type="submit" value="Finish and review"/> + </form> + </li> + </xsl:if> + <xsl:if test="$solutionAllowed and $hasModelSolution"> + <li> + <form action="{$webappContextPath}{$solutionUrl}" method="post"> + <input type="submit" value="Show model solution"> + <xsl:if test="$solutionMode"> + <!-- Already in solution mode --> + <xsl:attribute name="disabled" select="'disabled'"/> + </xsl:if> + </input> + </form> + </li> + </xsl:if> + <li> + <form action="{$webappContextPath}{$exitUrl}" method="post"> + <input type="submit" value="Exit"/> + </form> + </li> + </ul> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template match="qti:itemBody"> + <div id="itemBody"> + <form method="post" action="{$webappContextPath}{$responseUrl}" + enctype="multipart/form-data" accept-charset="UTF-8" + onsubmit="return QtiWorksRendering.maySubmit()" + onreset="QtiWorksRendering.reset()" autocomplete="off"> + + <xsl:apply-templates/> + + <xsl:if test="$candidateCommentAllowed"> + <fieldset class="candidateComment"> + <legend>Please use the following text box if you need to provide any additional information, comments or feedback during this test:</legend> + <input name="qtiworks_comment_presented" type="hidden" value="true"/> + <textarea name="qtiworks_comment"><xsl:value-of select="$itemSessionState/qw:candidateComment"/></textarea> + </fieldset> + </xsl:if> + + <xsl:if test="$isItemSessionOpen"> + <div class="controls"> + <input id="submit_button" name="submit" type="submit" value="SUBMIT RESPONSE"/> + </div> + </xsl:if> + </form> + </div> + </xsl:template> + + <!-- Overridden to add support for solution state --> + <xsl:template match="qw:itemSessionState" mode="item-status"> + <xsl:choose> + <xsl:when test="$solutionMode"> + <span class="itemStatus review">Model Solution</span> + </xsl:when> + <xsl:otherwise> + <xsl:apply-imports/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/mathmlc2p.xsl b/src/main/resources/rendering-xslt/mathmlc2p.xsl new file mode 100644 index 0000000000000000000000000000000000000000..f0388d7b50d64b28e1b67087666094ad25bf44ec --- /dev/null +++ b/src/main/resources/rendering-xslt/mathmlc2p.xsl @@ -0,0 +1,3307 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xd="http://www.pnp-software.com/XSLTdoc" + xmlns:h="http://www.w3.org/1999/xhtml" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:x="data:,x" + version="1.0" + exclude-result-prefixes="x h xd"> + <xsl:output method="xml" indent="yes" omit-xml-declaration="no"/> + <xsl:param name="And">∧</xsl:param> + <xsl:param name="ApplyFunction">⁡</xsl:param> + <xsl:param name="Backslash">∖</xsl:param> + <xsl:param name="DoubleRightArrow">⇒</xsl:param> + <xsl:param name="DownArrow">↓</xsl:param> + <xsl:param name="ee">ⅇ</xsl:param> + <xsl:param name="empty">∅</xsl:param> + <xsl:param name="equiv">≡</xsl:param> + <xsl:param name="Exists">∃</xsl:param> + <xsl:param name="ExponentialE">ⅇ</xsl:param> + <xsl:param name="ForAll">∀</xsl:param> + <xsl:param name="gamma">γ</xsl:param> + <xsl:param name="GreaterEqual">≥</xsl:param> + <xsl:param name="gt">></xsl:param> + <xsl:param name="ImaginaryI">ⅈ</xsl:param> + <xsl:param name="infin">∞</xsl:param> + <xsl:param name="Integral">∫</xsl:param> + <xsl:param name="Intersection">⋂</xsl:param> + <xsl:param name="InvisibleComma">⁣</xsl:param> + <xsl:param name="InvisibleTimes">⁢</xsl:param> + <xsl:param name="isin">∈</xsl:param> + <xsl:param name="lambda">λ</xsl:param> + <xsl:param name="lang">〈</xsl:param> + <xsl:param name="LeftCeiling">⌈</xsl:param> + <xsl:param name="LeftFloor">⌊</xsl:param> + <xsl:param name="LessEqual">≦</xsl:param> + <xsl:param name="lt"><</xsl:param> + <xsl:param name="Not">¬</xsl:param> + <xsl:param name="NotEqual">≠</xsl:param> + <xsl:param name="notin">∉</xsl:param> + <xsl:param name="NotSubset">⊂⃒</xsl:param> + <xsl:param name="NotSubsetEqual">⊈</xsl:param> + <xsl:param name="Or">∨</xsl:param> + <xsl:param name="ovbar">⌽</xsl:param> + <xsl:param name="PartialD">∂</xsl:param> + <xsl:param name="pi">π</xsl:param> + <xsl:param name="Product">∏</xsl:param> + <xsl:param name="rang">〉</xsl:param> + <xsl:param name="RightArrow">→</xsl:param> + <xsl:param name="RightFloor">⌋</xsl:param> + <xsl:param name="RightCeiling">⌉</xsl:param> + <xsl:param name="sigma">σ</xsl:param> + <xsl:param name="SmallCircle">∘</xsl:param> + <xsl:param name="Subset">⋐</xsl:param> + <xsl:param name="SubsetEqual">⊆</xsl:param> + <xsl:param name="Sum">∑</xsl:param> + <xsl:param name="times">×</xsl:param> + <xsl:param name="Union">⋃</xsl:param> + <xsl:param name="UpArrow">↑</xsl:param> + <xsl:template match="/"> + <xsl:apply-templates/> + </xsl:template> + <xsl:template match="text()|@*"> + <xsl:value-of disable-output-escaping="no" select="."/> + </xsl:template> + <xsl:template match="m:cn"> + <xsl:choose> + <xsl:when test="@base and @base!=10"> + <msub> + <mrow> + <xsl:choose> + <xsl:when test="./@type='complex-cartesian' or ./@type='complex'"> + <mn> + <xsl:value-of select="text()[1]"/> + </mn> + <xsl:choose> + <xsl:when test="contains(text()[2],'-')"> + <mo>-</mo> + <mn> + <xsl:value-of select="substring-after(text()[2],'-')"/> + </mn> + </xsl:when> + <xsl:otherwise> + <mo>+</mo> + <mn> + <xsl:value-of select="text()[2]"/> + </mn> + </xsl:otherwise> + </xsl:choose> + <mo> + <xsl:value-of select="$InvisibleTimes"/> + </mo> + <mi> + <xsl:value-of select="$ImaginaryI"/> + </mi> + </xsl:when> + <xsl:when test="./@type='complex-polar'"> Polar<mfenced> + <mn> + <xsl:value-of select="text()[1]"/> + </mn> + <mn> + <xsl:value-of select="text()[2]"/> + </mn> + </mfenced> + </xsl:when> + <xsl:when test="./@type='e-notation'"> + <mrow> + <mn> + <xsl:value-of select="text()[1]"/> + </mn> + <mo>e</mo> + <mn> + <xsl:value-of select="text()[2]"/> + </mn> + </mrow> + </xsl:when> + <xsl:when test="./@type='rational'"> + <mn> + <xsl:value-of select="text()[1]"/> + </mn> + <mo>/</mo> + <mn> + <xsl:value-of select="text()[2]"/> + </mn> + </xsl:when> + <xsl:otherwise> + <mn> + <xsl:value-of select="."/> + </mn> + </xsl:otherwise> + </xsl:choose> + </mrow> + <mn> + <xsl:value-of select="@base"/> + </mn> + </msub> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="./@type='complex-cartesian' or ./@type='complex'"> + <mrow> + <mn> + <xsl:value-of select="text()[1]"/> + </mn> + <xsl:choose> + <xsl:when test="contains(text()[2],'-')"> + <mo>-</mo> + <mn> + <xsl:value-of select="substring(text()[2],2)"/> + </mn> + <mo> + <xsl:value-of select="$InvisibleTimes"/> + </mo> + <mi> + <xsl:value-of select="$ImaginaryI"/> + </mi> + </xsl:when> + <xsl:otherwise> + <mo>+</mo> + <mn> + <xsl:value-of select="text()[2]"/> + </mn> + <mo> + <xsl:value-of select="$InvisibleTimes"/> + </mo> + <mi> + <xsl:value-of select="$ImaginaryI"/> + </mi> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:when> + <xsl:when test="./@type='complex-polar'"> + <mrow> + <mi>Polar</mi> + <mfenced> + <mn> + <xsl:value-of select="text()[1]"/> + </mn> + <mn> + <xsl:value-of select="text()[2]"/> + </mn> + </mfenced> + </mrow> + </xsl:when> + <xsl:when test="./@type='e-notation'"> + <mrow> + <mn> + <xsl:value-of select="text()[1]"/> + </mn> + <mo>e</mo> + <mn> + <xsl:value-of select="text()[2]"/> + </mn> + </mrow> + </xsl:when> + <xsl:when test="./@type='rational'"> + <mrow> + <mn> + <xsl:value-of select="text()[1]"/> + </mn> + <mo>/</mo> + <mn> + <xsl:value-of select="text()[2]"/> + </mn> + </mrow> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="*"> + <mrow> + <xsl:copy-of select="*"/> + </mrow> + </xsl:when> + <xsl:otherwise> + <mn> + <xsl:value-of select="."/> + </mn> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:ci"> + <xsl:choose> + <xsl:when test="./@type='complex-cartesian' or ./@type='complex'"> + <xsl:choose> + <xsl:when test="count(*)>0"> + <mrow> + <mi> + <xsl:value-of select="text()[1]"/> + </mi> + <xsl:choose> + <xsl:when test="contains(text()[preceding-sibling::*[1][self::m:sep]],'-')"> + <mo>-</mo> + <mi> + <xsl:value-of select="substring-after(text()[preceding-sibling::*[1][self::m:sep]],'-')"/> + </mi> + <mo> + <xsl:value-of select="$InvisibleTimes"/> + </mo> + <mi> + <xsl:value-of select="$ImaginaryI"/> + </mi> + </xsl:when> + <xsl:otherwise> + <mo>+</mo> + <mi> + <xsl:value-of select="text()[preceding-sibling::*[1][self::m:sep]]"/> + </mi> + <mo> + <xsl:value-of select="$InvisibleTimes"/> + </mo> + <mi> + <xsl:value-of select="$ImaginaryI"/> + </mi> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:when> + <xsl:otherwise> + <mi> + <xsl:value-of select="."/> + </mi> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:when test="./@type='complex-polar'"> + <xsl:choose> + <xsl:when test="count(*)>0"> + <mrow> + <mi>Polar</mi> + <mfenced> + <mi> + <xsl:value-of select="text()[following-sibling::*[self::m:sep]]"/> + </mi> + <mi> + <xsl:value-of select="text()[preceding-sibling::*[self::m:sep]]"/> + </mi> + </mfenced> + </mrow> + </xsl:when> + <xsl:otherwise> + <mi> + <xsl:value-of select="."/> + </mi> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:when test="./@type='rational'"> + <xsl:choose> + <xsl:when test="count(*)>0"> + <mrow> + <mi> + <xsl:value-of select="text()[following-sibling::*[self::m:sep]]"/> + </mi> + <mo>/</mo> + <mi> + <xsl:value-of select="text()[preceding-sibling::*[self::m:sep]]"/> + </mi> + </mrow> + </xsl:when> + <xsl:otherwise> + <mi> + <xsl:value-of select="."/> + </mi> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:when test="./@type='vector'"> + <xsl:choose> + <xsl:when test="*"> + <xsl:choose> + <xsl:when test="*[1][self::m:msub]"> + <msub> + <mrow> + <mstyle fontweight="bold"> + <mrow> + <xsl:apply-templates select="m:msub/*[1]"/> + </mrow> + </mstyle> + </mrow> + <mrow> + <xsl:apply-templates select="m:msub/*[2]"/> + </mrow> + </msub> + </xsl:when> + <xsl:when test="*[1][self::m:msup]"> + <msup> + <mrow> + <mstyle fontweight="bold"> + <mrow> + <xsl:apply-templates select="m:msup/*[1]"/> + </mrow> + </mstyle> + </mrow> + <mrow> + <xsl:apply-templates select="msup/*[2]"/> + </mrow> + </msup> + </xsl:when> + <xsl:when test="*[1][self::m:msubsup]"> + <msubsup> + <mrow> + <mstyle fontweight="bold"> + <mrow> + <xsl:apply-templates select="m:msubsup/*[1]"/> + </mrow> + </mstyle> + </mrow> + <mrow> + <xsl:apply-templates select="m:msubsup/*[2]"/> + </mrow> + <mrow> + <xsl:apply-templates select="m:msubsup/*[3]"/> + </mrow> + </msubsup> + </xsl:when> + <xsl:otherwise> + <mrow> + <xsl:copy-of select="*"/> + </mrow> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <mi fontweight="bold"> + <xsl:value-of select="text()"/> + </mi> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="*"> + <mrow> + <xsl:copy-of select="*"/> + </mrow> + </xsl:when> + <xsl:otherwise> + <mi> + <xsl:value-of select="."/> + </mi> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:csymbol]]"> + <mrow> + <xsl:apply-templates select="m:csymbol[1]"/> + <mfenced> + <xsl:for-each select="*[position()!=1]"> + <xsl:apply-templates select="."/> + </xsl:for-each> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:csymbol"> + <xsl:choose> + <xsl:when test="count(node()) != count(text())"> + <mrow> + <xsl:copy-of select="*"/> + </mrow> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="."/> + </mo> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:mtext"> + <xsl:copy-of select="."/> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:apply]]"> + <xsl:choose> + <xsl:when test="count(*)>=2"> + <mrow> + <mfenced separators=""> + <xsl:apply-templates select="*[1]"/> + </mfenced> + <mfenced> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + </mrow> + </xsl:when> + <xsl:otherwise> + <mfenced separators=""> + <xsl:apply-templates select="*"/> + </mfenced> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:fn]]"> + <mrow> + <xsl:choose> + <xsl:when test="m:fn/*[1][self::m:apply]"> + <mfenced separators=""> + <mrow> + <xsl:apply-templates select="m:fn/*"/> + </mrow> + </mfenced> + </xsl:when> + <xsl:otherwise> + <mi> + <xsl:apply-templates select="m:fn/*"/> + </mi> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="count(*)>1"> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <mfenced> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + </xsl:if> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:ci]]"> + <mrow> + <xsl:apply-templates select="m:ci[1]"/> + <xsl:if test="count(*)>1"> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <mfenced> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + </xsl:if> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:mo]]"> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <mrow> + <xsl:for-each select="*[position()!=last() and position()!=1]"> + <xsl:apply-templates select="."/> + <xsl:copy-of select="preceding-sibling::m:mo"/> + </xsl:for-each> + <xsl:apply-templates select="*[position()!=1 and position()=last()]"/> + </mrow> + </xsl:when> + <xsl:when test="count(*)=2"> + <mrow> + <xsl:copy-of select="m:mo[1]/*"/> + <xsl:apply-templates select="*[2]"/> + </mrow> + </xsl:when> + </xsl:choose> + </xsl:template> + <xsl:template match="m:interval"> + <xsl:choose> + <xsl:when test="count(*)=2"> + <xsl:choose> + <xsl:when test="@closure and @closure='open-closed'"> + <mfenced open="(" close="]"> + <xsl:apply-templates select="*[1]"/> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:when test="@closure and @closure='closed-open'"> + <mfenced open="[" close=")"> + <xsl:apply-templates select="*[1]"/> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:when test="@closure and @closure='closed'"> + <mfenced open="[" close="]"> + <xsl:apply-templates select="*[1]"/> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:when test="@closure and @closure='open'"> + <mfenced open="(" close=")"> + <xsl:apply-templates select="*[1]"/> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <mfenced open="[" close="]"> + <xsl:apply-templates select="*[1]"/> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <mrow> + <xsl:apply-templates select="m:condition"/> + </mrow> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:apply]/m:inverse]"> + <mrow> + <msup> + <mrow> + <xsl:apply-templates select="m:apply[1]/*[2]"/> + </mrow> + <mfenced> + <mn>-1</mn> + </mfenced> + </msup> + <xsl:if test="count(*)>=2"> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <mfenced> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + </xsl:if> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:inverse]]"> + <msup> + <mrow> + <xsl:apply-templates select="*[2]"/> + </mrow> + <mfenced> + <mn>-1</mn> + </mfenced> + </msup> + </xsl:template> + <xsl:template match="m:condition"> + <mrow> + <xsl:apply-templates select="*"/> + </mrow> + </xsl:template> + <xsl:template match="m:domainofapplication"> + <mrow> + <xsl:apply-templates select="*"/> + </mrow> + </xsl:template> + <xsl:template match="m:declare"> </xsl:template> + <xsl:template match="m:lambda"> + <mrow> + <mo> + <xsl:value-of select="$lambda"/> + </mo> + <mrow> + <mo>(</mo> + <xsl:for-each select="m:bvar"> + <xsl:apply-templates select="."/> + <mo>,</mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + <mo>)</mo> + </mrow> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:apply]/m:compose]"> + <mrow> + <xsl:choose> + <xsl:when test="count(*)>=2"> + <mfenced> + <mrow> + <xsl:for-each select="m:apply[1]/*[position()!=1 and position()!=last()]"> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$SmallCircle"/> + </mo> + </xsl:for-each> + <xsl:apply-templates select="m:apply[1]/*[position()=last()]"/> + </mrow> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:for-each select="m:apply[1]/*[position()!=1 and position()!=last()]"> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$SmallCircle"/> + </mo> + </xsl:for-each> + <xsl:apply-templates select="m:apply[1]/*[position()=last()]"/> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="count(*)>=2"> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <mrow> + <mo>(</mo> + <xsl:for-each select="*[position()!=1 and position()!=last()]"> + <xsl:apply-templates select="."/> + <mo>,</mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + <mo>)</mo> + </mrow> + </xsl:if> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:compose]]"> + <xsl:for-each select="*[position()!=1 and position()!=last()]"> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$SmallCircle"/> + </mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:template> + <xsl:template match="m:ident"> + <mi>id</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:domain]]"> + <mrow> + <mi>domain</mi> + <mfenced open="(" close=")"> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:codomain]]"> + <mrow> + <mi>codomain</mi> + <mfenced open="(" close=")"> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:image]]"> + <mrow> + <mi>image</mi> + <mfenced open="(" close=")"> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:piecewise"> + <mrow> + <mfenced open="{{" close=""> + <mtable> + <xsl:for-each select="m:piece"> + <mtr> + <mtd> + <xsl:apply-templates select="*[1]"/> + <mspace width="0.3em"/> + <m:mtext>if</m:mtext> + <mspace width="0.3em"/> + <xsl:apply-templates select="*[2]"/> + </mtd> + </mtr> + </xsl:for-each> + <xsl:if test="m:otherwise"> + <mtr> + <mtd> + <xsl:apply-templates select="m:otherwise/*"/> + <mspace width="0.3em"/> + <m:mtext>otherwise</m:mtext> + </mtd> + </mtr> + </xsl:if> + </mtable> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:quotient]]"> + <mrow> + <mo>integer part of</mo> + <mrow> + <xsl:choose> + <xsl:when test="*[2] and self::m:apply"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + <mo>/</mo> + <xsl:choose> + <xsl:when test="*[3] and self::m:apply"> + <mfenced separators=""> + <xsl:apply-templates select="*[3]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[3]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:factorial]]"> + <mrow> + <xsl:choose> + <xsl:when test="*[2][self::m:apply]"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + <mo>!</mo> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:divide]]"> + <mrow> + <xsl:choose> + <xsl:when test="contains(@other,'scriptstyle')"> + <mfrac bevelled="true"> + <mrow> + <xsl:apply-templates select="*[2]"/> + </mrow> + <mrow> + <xsl:apply-templates select="*[3]"/> + </mrow> + </mfrac> + </xsl:when> + <xsl:otherwise> + <mfrac> + <mrow> + <xsl:apply-templates select="*[2]"/> + </mrow> + <mrow> + <xsl:apply-templates select="*[3]"/> + </mrow> + </mfrac> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:min]]"> + <mrow> + <xsl:choose> + <xsl:when test="m:bvar"> + <msub> + <mi>min</mi> + <mrow> + <xsl:for-each select="m:bvar[position()!=last()]"> + <xsl:apply-templates select="."/> + <mo>,</mo> + </xsl:for-each> + <xsl:apply-templates select="m:bvar[position()=last()]"/> + </mrow> + </msub> + <mrow> + <mo>{</mo> + <xsl:apply-templates select="*[not(self::m:condition) and not(self::m:bvar)]"/> + <xsl:if test="m:condition"> + <mo>|</mo> + <xsl:apply-templates select="m:condition"/> + </xsl:if> + <mo>}</mo> + </mrow> + </xsl:when> + <xsl:otherwise> + <mo>min</mo> + <mrow> + <mo>{</mo> + <mfenced open="" close=""> + <xsl:apply-templates select="*[not(self::m:condition) and not(self::m:min)]"/> + </mfenced> + <xsl:if test="m:condition"> + <mo>|</mo> + <xsl:apply-templates select="m:condition"/> + </xsl:if> + <mo>}</mo> + </mrow> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:min] and m:domainofapplication]"> + <mrow> + <xsl:choose> + <xsl:when test="m:bvar"> + <msub> + <munder> + <mi>min</mi> + <xsl:apply-templates select="m:domainofapplication"/> + </munder> + <mrow> + <xsl:for-each select="m:bvar[position()!=last()]"> + <xsl:apply-templates select="."/> + <mo>,</mo> + </xsl:for-each> + <xsl:apply-templates select="m:bvar[position()=last()]"/> + </mrow> + </msub> + </xsl:when> + <xsl:otherwise> + <munder> + <mi>min</mi> + <xsl:apply-templates select="m:domainofapplication"/> + </munder> + </xsl:otherwise> + </xsl:choose> + <mo>{</mo> + <xsl:apply-templates select="*[not(self::m:condition or self::m:domainofapplication or self::m:bvar)]"/> + <xsl:if test="m:condition"> + <mo>|</mo> + <xsl:apply-templates select="m:condition"/> + </xsl:if> + <mo>}</mo> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:max]]"> + <mrow> + <xsl:choose> + <xsl:when test="m:bvar"> + <msub> + <mi>max</mi> + <mrow> + <xsl:for-each select="m:bvar[position()!=last()]"> + <xsl:apply-templates select="."/> + <mo>,</mo> + </xsl:for-each> + <xsl:apply-templates select="m:bvar[position()=last()]"/> + </mrow> + </msub> + <mrow> + <mo>{</mo> + <xsl:apply-templates select="*[not(self::m:condition) and not(self::m:bvar)]"/> + <xsl:if test="m:condition"> + <mo>|</mo> + <xsl:apply-templates select="m:condition"/> + </xsl:if> + <mo>}</mo> + </mrow> + </xsl:when> + <xsl:otherwise> + <mo>max</mo> + <mrow> + <mo>{</mo> + <mfenced open="" close=""> + <xsl:apply-templates select="*[not(self::m:condition) and not(self::m:max)]"/> + </mfenced> + <xsl:if test="m:condition"> + <mo>|</mo> + <xsl:apply-templates select="m:condition"/> + </xsl:if> + <mo>}</mo> + </mrow> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:max] and m:domainofapplication]"> + <mrow> + <xsl:choose> + <xsl:when test="m:bvar"> + <msub> + <munder> + <mi>max</mi> + <xsl:apply-templates select="m:domainofapplication"/> + </munder> + <mrow> + <xsl:for-each select="m:bvar[position()!=last()]"> + <xsl:apply-templates select="."/> + <mo>,</mo> + </xsl:for-each> + <xsl:apply-templates select="m:bvar[position()=last()]"/> + </mrow> + </msub> + </xsl:when> + <xsl:otherwise> + <munder> + <mi>max</mi> + <xsl:apply-templates select="m:domainofapplication"/> + </munder> + </xsl:otherwise> + </xsl:choose> + <mo>{</mo> + <xsl:apply-templates select="*[not(self::m:condition or self::m:domainofapplication or self::m:bvar)]"/> + <xsl:if test="m:condition"> + <mo>|</mo> + <xsl:apply-templates select="m:condition"/> + </xsl:if> + <mo>}</mo> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:minus]]"> + <mrow> + <xsl:choose> + <xsl:when test="count(*)=3"> + <xsl:apply-templates select="*[2]"/> + <mo>-</mo> + <xsl:choose> + <xsl:when test="(*[3][self::m:ci or self::m:cn] and contains(*[3]/text(),'-')) or *[3][self::m:apply]"> + <mfenced separators=""> + <xsl:apply-templates select="*[3]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[3]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <mo>-</mo> + <xsl:choose> + <xsl:when test="(*[2][self::m:ci or self::m:cn] and contains(*[2]/text(),'-')) or *[2][self::m:apply]"> + <mfenced separators=""> + <xsl:apply-templates select="*[position()=last()]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:plus]]"> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <mrow> + <xsl:choose> + <xsl:when test="(*[2][self::m:ci or self::m:cn] and contains(*[2]/text(),'-')) or (*[2][self::m:apply and child::m:minus])"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + <xsl:for-each select="*[position()!=1 and position()!=2]"> + <xsl:choose> + <xsl:when test="((self::m:ci or self::m:cn) and contains(./text(),'-')) or (self::m:apply and child::m:minus)"> + <mo>+</mo> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <mo>+</mo> + <xsl:apply-templates select="."/> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </mrow> + </xsl:when> + <xsl:when test="count(*)=2"> + <mrow> + <mo>+</mo> + <xsl:apply-templates select="*[2]"/> + </mrow> + </xsl:when> + <xsl:otherwise> + <mo>+</mo> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:power]]"> + <msup> + <xsl:choose> + <xsl:when test="*[2][self::m:apply]"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + <xsl:apply-templates select="*[3]"/> + </msup> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:rem]]"> + <mrow> + <xsl:choose> + <xsl:when test="*[2][self::m:apply]"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + <mo>mod</mo> + <xsl:choose> + <xsl:when test="*[3][self::m:apply]"> + <mfenced separators=""> + <xsl:apply-templates select="*[3]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[3]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:times]]"> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <mrow> + <xsl:for-each select="*[position()!=last() and position()!=1]"> + <xsl:choose> + <xsl:when test="m:plus"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + <mo> + <xsl:value-of select="$InvisibleTimes"/> + </mo> + </xsl:when> + <xsl:when test="m:minus"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + <mo> + <xsl:value-of select="$InvisibleTimes"/> + </mo> + </xsl:when> + <xsl:when test="(self::m:ci or self::m:cn) and contains(text(),'-')"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + <mo> + <xsl:value-of select="$InvisibleTimes"/> + </mo> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$InvisibleTimes"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:for-each select="*[position()=last()]"> + <xsl:choose> + <xsl:when test="m:plus"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + </xsl:when> + <xsl:when test="m:minus"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + </xsl:when> + <xsl:when test="(self::m:ci or self::m:cn) and contains(text(),'-')"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="."/> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </mrow> + </xsl:when> + <xsl:when test="count(*)=2"> + <mrow> + <mo> + <xsl:value-of select="$InvisibleTimes"/> + </mo> + <xsl:choose> + <xsl:when test="m:plus"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:when test="m:minus"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:when test="*[2][self::m:ci or self::m:cn] and contains(*[2]/text(),'-')"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$InvisibleTimes"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:root]]"> + <xsl:choose> + <xsl:when test="m:degree"> + <xsl:choose> + <xsl:when test="m:degree/m:cn/text()='2'"> + <msqrt> + <xsl:apply-templates select="*[3]"/> + </msqrt> + </xsl:when> + <xsl:otherwise> + <mroot> + <xsl:apply-templates select="*[3]"/> + <mrow> + <xsl:apply-templates select="m:degree/*"/> + </mrow> + </mroot> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <msqrt> + <xsl:apply-templates select="*[2]"/> + </msqrt> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:gcd]]"> + <mrow> + <mi>gcd</mi> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <mfenced> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:and]]"> + <mrow> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <xsl:for-each select="*[position()!=last() and position()!=1]"> + <xsl:choose> + <xsl:when test="m:or"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + <mo> + <xsl:value-of select="$And"/> + </mo> + </xsl:when> + <xsl:when test="m:xor"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + <mo> + <xsl:value-of select="$And"/> + </mo> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$And"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:for-each select="*[position()=last()]"> + <xsl:choose> + <xsl:when test="m:or"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + </xsl:when> + <xsl:when test="m:xor"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="."/> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </xsl:when> + <xsl:when test="count(*)=2"> + <mo> + <xsl:value-of select="$And"/> + </mo> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$And"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:or]]"> + <mrow> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <xsl:for-each select="*[position()!=last() and position()!=1]"> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$Or"/> + </mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:when test="count(*)=2"> + <mo> + <xsl:value-of select="$Or"/> + </mo> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$Or"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:xor]]"> + <mrow> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <xsl:for-each select="*[position()!=last() and position()!=1]"> + <xsl:apply-templates select="."/> + <mo>xor</mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:when test="count(*)=2"> + <mo>xor</mo> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:otherwise> + <mo>xor</mo> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:not]]"> + <mrow> + <mo> + <xsl:value-of select="$Not"/> + </mo> + <xsl:choose> + <xsl:when test="m:apply"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:implies]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$DoubleRightArrow"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:implies]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$DoubleRightArrow"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:forall]]"> + <mrow> + <mo> + <xsl:value-of select="$ForAll"/> + </mo> + <mrow> + <xsl:for-each select="m:bvar[position()!=last()]"> + <xsl:apply-templates select="."/> + <mo>,</mo> + </xsl:for-each> + <xsl:apply-templates select="m:bvar[position()=last()]"/> + </mrow> + <xsl:if test="m:condition"> + <mrow> + <mo>,</mo> + <xsl:apply-templates select="m:condition"/> + </mrow> + </xsl:if> + <xsl:choose> + <xsl:when test="m:apply"> + <mo>:</mo> + <xsl:apply-templates select="m:apply"/> + </xsl:when> + <xsl:when test="m:reln"> + <mo>:</mo> + <xsl:apply-templates select="m:reln"/> + </xsl:when> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:exists]]"> + <mrow> + <mo> + <xsl:value-of select="$Exists"/> + </mo> + <mrow> + <xsl:for-each select="m:bvar[position()!=last()]"> + <xsl:apply-templates select="."/> + <mo>,</mo> + </xsl:for-each> + <xsl:apply-templates select="m:bvar[position()=last()]"/> + </mrow> + <xsl:if test="m:condition"> + <mrow> + <mo>,</mo> + <xsl:apply-templates select="m:condition"/> + </mrow> + </xsl:if> + <xsl:choose> + <xsl:when test="m:apply"> + <mo>:</mo> + <xsl:apply-templates select="m:apply"/> + </xsl:when> + <xsl:when test="m:reln"> + <mo>:</mo> + <xsl:apply-templates select="m:reln"/> + </xsl:when> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:abs]]"> + <mrow> + <mo>|</mo> + <xsl:apply-templates select="*[position()=last()]"/> + <mo>|</mo> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:conjugate]]"> + <mover> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$ovbar"/> + </mo> + </mover> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arg]]"> + <mrow> + <mi>arg</mi> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:real]]"> + <mrow> + <mi> + <xsl:text disable-output-escaping="yes">ℜ</xsl:text> + </mi> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:imaginary]]"> + <mrow> + <mi> + <xsl:text disable-output-escaping="yes">ℑ</xsl:text> + </mi> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:lcm]]"> + <mrow> + <mi>lcm</mi> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <mfenced> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:floor]]"> + <mrow> + <mo> + <xsl:value-of select="$LeftFloor"/> + </mo> + <xsl:apply-templates select="*[position()=last()]"/> + <mo> + <xsl:value-of select="$RightFloor"/> + </mo> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:ceiling]]"> + <mrow> + <mo> + <xsl:value-of select="$LeftCeiling"/> + </mo> + <xsl:apply-templates select="*[position()=last()]"/> + <mo> + <xsl:value-of select="$RightCeiling"/> + </mo> + </mrow> + </xsl:template> + <xsl:template name="eqRel"> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <mrow> + <xsl:for-each select="*[position()!=1 and position()!=last()]"> + <xsl:apply-templates select="."/> + <mo>=</mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </mrow> + </xsl:when> + <xsl:when test="count(*)=2"> + <mrow> + <mo>=</mo> + <xsl:apply-templates select="*[2]"/> + </mrow> + </xsl:when> + <xsl:otherwise> + <mo>=</mo> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:eq]]"> + <xsl:call-template name="eqRel"/> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:eq]]"> + <xsl:call-template name="eqRel"/> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:neq]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$NotEqual"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:neq]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$NotEqual"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template name="gtRel"> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <mrow> + <xsl:for-each select="*[position()!=1 and position()!=last()]"> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$gt"/> + </mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </mrow> + </xsl:when> + <xsl:when test="count(*)=2"> + <mrow> + <mo> + <xsl:value-of select="$gt"/> + </mo> + <xsl:apply-templates select="*[2]"/> + </mrow> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$gt"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:gt]]"> + <xsl:call-template name="gtRel"/> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:gt]]"> + <xsl:call-template name="gtRel"/> + </xsl:template> + <xsl:template name="ltRel"> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <mrow> + <xsl:for-each select="*[position()!=1 and position()!=last()]"> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$lt"/> + </mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </mrow> + </xsl:when> + <xsl:when test="count(*)=2"> + <mrow> + <mo> + <xsl:value-of select="$lt"/> + </mo> + <xsl:apply-templates select="*[2]"/> + </mrow> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$lt"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:lt]]"> + <xsl:call-template name="ltRel"/> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:lt]]"> + <xsl:call-template name="ltRel"/> + </xsl:template> + <xsl:template name="geqRel"> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <mrow> + <xsl:for-each select="*[position()!=1 and position()!=last()]"> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$GreaterEqual"/> + </mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </mrow> + </xsl:when> + <xsl:when test="count(*)=2"> + <mrow> + <mo> + <xsl:value-of select="$GreaterEqual"/> + </mo> + <xsl:apply-templates select="*[2]"/> + </mrow> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$GreaterEqual"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:geq]]"> + <xsl:call-template name="geqRel"/> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:geq]]"> + <xsl:call-template name="geqRel"/> + </xsl:template> + <xsl:template name="leqRel"> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <mrow> + <xsl:for-each select="*[position()!=1 and position()!=last()]"> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$LessEqual"/> + </mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </mrow> + </xsl:when> + <xsl:when test="count(*)=2"> + <mrow> + <mo> + <xsl:value-of select="$LessEqual"/> + </mo> + <xsl:apply-templates select="*[2]"/> + </mrow> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$LessEqual"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:leq]]"> + <xsl:call-template name="leqRel"/> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:leq]]"> + <xsl:call-template name="leqRel"/> + </xsl:template> + <xsl:template name="equivRel"> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <mrow> + <xsl:for-each select="*[position()!=1 and position()!=last()]"> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$equiv"/> + </mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </mrow> + </xsl:when> + <xsl:when test="count(*)=2"> + <mrow> + <mo> + <xsl:value-of select="$equiv"/> + </mo> + <xsl:apply-templates select="*[2]"/> + </mrow> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$equiv"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:equivalent]]"> + <xsl:call-template name="equivRel"/> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:equivalent]]"> + <xsl:call-template name="equivRel"/> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:approx]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:text disable-output-escaping="yes">≈</xsl:text> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:approx]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:text disable-output-escaping="yes">≈</xsl:text> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:factorof]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo>|</mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:int]]"> + <mrow> + <xsl:choose> + <xsl:when test="m:condition"> + <msub> + <mo> + <xsl:value-of select="$Integral"/> + </mo> + <xsl:apply-templates select="m:condition"/> + </msub> + <mrow> + <xsl:apply-templates select="*[position()=last()]"/> + </mrow> + <mrow> + <mo>d</mo> + <xsl:apply-templates select="m:bvar"/> + </mrow> + </xsl:when> + <xsl:when test="m:domainofapplication"> + <msub> + <mo> + <xsl:value-of select="$Integral"/> + </mo> + <xsl:apply-templates select="m:domainofapplication"/> + </msub> + <mrow> + <xsl:apply-templates select="*[position()=last()]"/> + </mrow> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="m:interval"> + <msubsup> + <mo> + <xsl:value-of select="$Integral"/> + </mo> + <xsl:apply-templates select="m:interval/*[1]"/> + <xsl:apply-templates select="m:interval/*[2]"/> + </msubsup> + <xsl:apply-templates select="*[position()=last()]"/> + <mo>d</mo> + <xsl:apply-templates select="m:bvar"/> + </xsl:when> + <xsl:when test="m:lowlimit"> + <msubsup> + <mo> + <xsl:value-of select="$Integral"/> + </mo> + <mrow> + <xsl:apply-templates select="m:lowlimit"/> + </mrow> + <mrow> + <xsl:apply-templates select="m:uplimit"/> + </mrow> + </msubsup> + <xsl:apply-templates select="*[position()=last()]"/> + <mo>d</mo> + <xsl:apply-templates select="m:bvar"/> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$Integral"/> + </mo> + <xsl:apply-templates select="*[position()=last()]"/> + <mo>d</mo> + <xsl:apply-templates select="m:bvar"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:diff]]"> + <mrow> + <xsl:choose> + <xsl:when test="m:bvar/m:degree"> + <xsl:choose> + <xsl:when test="contains(m:bvar/m:degree/m:cn/text(),'1') and string-length(normalize-space(m:bvar/m:degree/m:cn/text()))=1"> + <mfrac> + <mo>d</mo> + <mrow> + <mo>d</mo> + <xsl:apply-templates select="m:bvar/*[not(self::m:degree)]"/> + </mrow> + </mfrac> + <mrow> + <xsl:choose> + <xsl:when test="m:apply[position()=last()]/m:fn[1]"> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:otherwise> + <mfenced separators=""> + <xsl:apply-templates select="*[position()=last()]"/> + </mfenced> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:when> + <xsl:otherwise> + <mfrac> + <mrow> + <msup> + <mo>d</mo> + <mrow> + <xsl:apply-templates select="m:bvar/m:degree"/> + </mrow> + </msup> + </mrow> + <mrow> + <mo>d</mo> + <msup> + <mrow> + <xsl:apply-templates select="m:bvar/*[not(self::m:degree)]"/> + </mrow> + <mrow> + <xsl:apply-templates select="m:bvar/m:degree"/> + </mrow> + </msup> + </mrow> + </mfrac> + <mrow> + <xsl:choose> + <xsl:when test="m:apply[position()=last()]/m:fn[1]"> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:otherwise> + <mfenced separators=""> + <xsl:apply-templates select="*[position()=last()]"/> + </mfenced> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="count(*)<=2"> + <xsl:apply-templates select="*[2]"/>' </xsl:when> + <xsl:otherwise> + <mfrac> + <mo>d</mo> + <mrow> + <mo>d</mo> + <xsl:apply-templates select="m:bvar"/> + </mrow> + </mfrac> + <mrow> + <xsl:choose> + <xsl:when test="m:apply[position()=last()]/m:fn[1]"> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:otherwise> + <mfenced separators=""> + <xsl:apply-templates select="*[position()=last()]"/> + </mfenced> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:partialdiff]]"> + <mrow> + <xsl:choose> + <xsl:when test="m:list"> + <msub> + <mo>D</mo> + <mfenced separators="," open="" close=""> + <xsl:apply-templates select="m:list/*"/> + </mfenced> + </msub> + <mfenced open="(" close=")"> + <xsl:apply-templates select="*[not(self::m:list)]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:for-each select="m:bvar"> + <xsl:choose> + <xsl:when test="m:degree"> + <xsl:choose> + <xsl:when test="contains(m:degree/m:cn/text(),'1') and string-length(normalize-space(m:degree/m:cn/text()))=1"> + <mfrac> + <mrow> + <mo> + <xsl:value-of select="$PartialD"/> + </mo> + </mrow> + <mrow> + <mo> + <xsl:value-of select="$PartialD"/> + </mo> + <xsl:apply-templates select="*[not(self::m:degree)]"/> + </mrow> + </mfrac> + </xsl:when> + <xsl:otherwise> + <mfrac> + <mrow> + <msup> + <mrow> + <mo> + <xsl:value-of select="$PartialD"/> + </mo> + </mrow> + <mrow> + <xsl:apply-templates select="m:degree"/> + </mrow> + </msup> + </mrow> + <mrow> + <mrow> + <mo> + <xsl:value-of select="$PartialD"/> + </mo> + </mrow> + <msup> + <mrow> + <xsl:apply-templates select="*[not(self::m:degree)]"/> + </mrow> + <mrow> + <xsl:apply-templates select="m:degree"/> + </mrow> + </msup> + </mrow> + </mfrac> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <mfrac> + <mrow> + <mo> + <xsl:value-of select="$PartialD"/> + </mo> + </mrow> + <mrow> + <mo> + <xsl:value-of select="$PartialD"/> + </mo> + <xsl:apply-templates select="."/> + </mrow> + </mfrac> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <mrow> + <xsl:choose> + <xsl:when test="m:apply[position()=last()]/m:fn[1]"> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:otherwise> + <mfenced separators=""> + <xsl:apply-templates select="*[position()=last()]"/> + </mfenced> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:lowlimit"> + <xsl:apply-templates select="*"/> + </xsl:template> + <xsl:template match="m:uplimit"> + <xsl:apply-templates select="*"/> + </xsl:template> + <xsl:template match="m:bvar"> + <xsl:apply-templates select="*"/> + </xsl:template> + <xsl:template match="m:degree"> + <xsl:apply-templates select="*"/> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:divergence]]"> + <mrow> + <mi>div</mi> + <xsl:choose> + <xsl:when test="*[2][self::m:apply] or (*[2][self::m:ci or self::m:cn] and contains(*[2]/text(),'-'))"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:grad]]"> + <mrow> + <mi>grad</mi> + <xsl:choose> + <xsl:when test="*[2][self::m:apply] or (*[2][self::m:ci or self::m:cn] and contains(*[2]/text(),'-'))"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:curl]]"> + <mrow> + <mi>curl</mi> + <xsl:choose> + <xsl:when test="*[2][self::m:apply] or (*[2][self::m:ci or self::m:cn] and contains(*[2]/text(),'-'))"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:laplacian]]"> + <mrow> + <msup> + <mo> + <xsl:text disable-output-escaping="yes">∇</xsl:text> + </mo> + <mn>2</mn> + </msup> + <xsl:choose> + <xsl:when test="*[2][self::m:apply] or (*[2][self::m:ci or self::m:cn] and contains(*[2]/text(),'-'))"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:set"> + <mrow> + <xsl:choose> + <xsl:when test="m:condition"> + <mo>{</mo> + <mrow> + <mfenced open="" close=""> + <xsl:apply-templates select="m:bvar"/> + </mfenced> + <mo>|</mo> + <xsl:apply-templates select="m:condition"/> + </mrow> + <mo>}</mo> + </xsl:when> + <xsl:otherwise> + <mfenced open="{{" close="}}" separators=","> + <xsl:apply-templates select="*"/> + </mfenced> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:list"> + <mrow> + <xsl:choose> + <xsl:when test="m:condition"> + <mo>[</mo> + <mrow> + <mfenced open="" close=""> + <xsl:apply-templates select="m:bvar"/> + </mfenced> + <mo>|</mo> + <xsl:apply-templates select="m:condition"/> + </mrow> + <mo>]</mo> + </xsl:when> + <xsl:otherwise> + <mfenced open="[" close="]"> + <xsl:apply-templates select="*"/> + </mfenced> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:union]]"> + <mrow> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <xsl:for-each select="*[position()!=last() and position()!=1]"> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$Union"/> + </mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:when test="count(*)=2"> + <mo> + <xsl:value-of select="$Union"/> + </mo> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$Union"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:intersect]]"> + <mrow> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <xsl:for-each select="*[position()!=last() and position()!=1]"> + <xsl:choose> + <xsl:when test="m:union"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + <mo> + <xsl:value-of select="$Intersection"/> + </mo> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$Intersection"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:when test="count(*)=2"> + <mo> + <xsl:value-of select="$Intersection"/> + </mo> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$Intersection"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:in]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$isin"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:in]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$isin"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:notin]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$notin"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:notin]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$notin"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template name="subsetRel"> + <mrow> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <xsl:for-each select="*[position()!=last() and position()!=1]"> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$SubsetEqual"/> + </mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:when test="count(*)=2"> + <mo> + <xsl:value-of select="$SubsetEqual"/> + </mo> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$SubsetEqual"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:subset]]"> + <xsl:call-template name="subsetRel"/> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:subset]]"> + <xsl:call-template name="subsetRel"/> + </xsl:template> + <xsl:template name="prsubsetRel"> + <mrow> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <xsl:for-each select="*[position()!=last() and position()!=1]"> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$Subset"/> + </mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:when test="count(*)=2"> + <mo> + <xsl:value-of select="$Subset"/> + </mo> + <xsl:apply-templates select="*[position()=last()]"/> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$Subset"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:prsubset]]"> + <xsl:call-template name="prsubsetRel"/> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:prsubset]]"> + <xsl:call-template name="prsubsetRel"/> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:notsubset]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$NotSubset"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:notsubset]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$NotSubset"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:notprsubset]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$NotSubsetEqual"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:notprsubset]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$NotSubsetEqual"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:setdiff]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$Backslash"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:card]]"> + <mrow> + <mo>|</mo> + <xsl:apply-templates select="*[position()=last()]"/> + <mo>|</mo> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:cartesianproduct]]"> + <xsl:choose> + <xsl:when test="count(*)>=3"> + <mrow> + <xsl:for-each select="*[position()!=last() and position()!=1]"> + <xsl:choose> + <xsl:when test="m:plus"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + <mo> + <xsl:value-of select="$times"/> + </mo> + </xsl:when> + <xsl:when test="m:minus"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + <mo> + <xsl:value-of select="$times"/> + </mo> + </xsl:when> + <xsl:when test="(self::m:ci or self::m:cn) and contains(text(),'-')"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + <mo> + <xsl:value-of select="$times"/> + </mo> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="."/> + <mo> + <xsl:value-of select="$times"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:for-each select="*[position()=last()]"> + <xsl:choose> + <xsl:when test="m:plus"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + </xsl:when> + <xsl:when test="m:minus"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + </xsl:when> + <xsl:when test="(self::m:ci or self::m:cn) and contains(text(),'-')"> + <mfenced separators=""> + <xsl:apply-templates select="."/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="."/> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </mrow> + </xsl:when> + <xsl:when test="count(*)=2"> + <mrow> + <mo> + <xsl:value-of select="$times"/> + </mo> + <xsl:choose> + <xsl:when test="m:plus"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:when test="m:minus"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:when test="*[2][self::m:ci or self::m:cn] and contains(*[2]/text(),'-')"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$InvisibleTimes"/> + </mo> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:sum]]"> + <mrow> + <xsl:choose> + <xsl:when test="m:condition and m:domainofapplication"> + <munder> + <mo> + <xsl:value-of select="$Sum"/> + </mo> + <mrow> + <munder> + <mrow> + <xsl:apply-templates select="m:domainofapplication"/> + </mrow> + <mrow> + <xsl:apply-templates select="m:condition"/> + </mrow> + </munder> + </mrow> + </munder> + </xsl:when> + <xsl:when test="m:condition and m:lowlimit and m:uplimit"> + <munderover> + <mo> + <xsl:value-of select="$Sum"/> + </mo> + <mrow> + <munder> + <mrow> + <xsl:apply-templates select="m:bvar"/> + <mo>=</mo> + <xsl:apply-templates select="m:lowlimit"/> + </mrow> + <mrow> + <xsl:apply-templates select="m:condition"/> + </mrow> + </munder> + </mrow> + <mrow> + <xsl:apply-templates select="m:uplimit"/> + </mrow> + </munderover> + </xsl:when> + <xsl:when test="m:condition"> + <munder> + <mo> + <xsl:value-of select="$Sum"/> + </mo> + <xsl:apply-templates select="m:condition"/> + </munder> + </xsl:when> + <xsl:when test="m:domainofapplication"> + <munder> + <mo> + <xsl:value-of select="$Sum"/> + </mo> + <xsl:apply-templates select="m:domainofapplication"/> + </munder> + </xsl:when> + <xsl:when test="m:lowlimit and m:uplimit"> + <munderover> + <mo> + <xsl:value-of select="$Sum"/> + </mo> + <mrow> + <xsl:apply-templates select="m:bvar"/> + <mo>=</mo> + <xsl:apply-templates select="m:lowlimit"/> + </mrow> + <mrow> + <xsl:apply-templates select="m:uplimit"/> + </mrow> + </munderover> + </xsl:when> + <xsl:otherwise> + <mo> + <xsl:value-of select="$Sum"/> + </mo> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="*[position()=last() and self::m:apply]"> + <mfenced separators=""> + <xsl:apply-templates select="*[position()=last()]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <mrow> + <xsl:apply-templates select="*[position()=last()]"/> + </mrow> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:product]]"> + <mrow> + <xsl:choose> + <xsl:when test="m:condition and m:domainofapplication"> + <munder> + <mo> + <xsl:value-of select="$Product"/> + </mo> + <mrow> + <munder> + <mrow> + <xsl:apply-templates select="m:domainofapplication"/> + </mrow> + <mrow> + <xsl:apply-templates select="m:condition"/> + </mrow> + </munder> + </mrow> + </munder> + </xsl:when> + <xsl:when test="m:condition and m:lowlimit and m:uplimit"> + <munderover> + <mo> + <xsl:value-of select="$Product"/> + </mo> + <mrow> + <munder> + <mrow> + <xsl:apply-templates select="m:bvar"/> + <mo>=</mo> + <xsl:apply-templates select="m:lowlimit"/> + </mrow> + <mrow> + <xsl:apply-templates select="m:condition"/> + </mrow> + </munder> + </mrow> + <mrow> + <xsl:apply-templates select="m:uplimit"/> + </mrow> + </munderover> + </xsl:when> + <xsl:when test="m:condition"> + <munder> + <mo> + <xsl:value-of select="$Product"/> + </mo> + <xsl:apply-templates select="m:condition"/> + </munder> + </xsl:when> + <xsl:when test="m:domainofapplication"> + <munder> + <mo> + <xsl:value-of select="$Product"/> + </mo> + <xsl:apply-templates select="m:domainofapplication"/> + </munder> + </xsl:when> + <xsl:otherwise> + <munderover> + <mo> + <xsl:value-of select="$Product"/> + </mo> + <mrow> + <xsl:apply-templates select="m:bvar"/> + <mo>=</mo> + <xsl:apply-templates select="m:lowlimit"/> + </mrow> + <mrow> + <xsl:apply-templates select="m:uplimit"/> + </mrow> + </munderover> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="*[position()=last() and self::m:apply]"> + <mfenced separators=""> + <xsl:apply-templates select="*[position()=last()]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <mrow> + <xsl:apply-templates select="*[position()=last()]"/> + </mrow> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:limit]]"> + <mrow> + <xsl:choose> + <xsl:when test="m:condition"> + <munder> + <mo>lim</mo> + <xsl:apply-templates select="m:condition"/> + </munder> + </xsl:when> + <xsl:otherwise> + <munder> + <mo>lim</mo> + <mrow> + <xsl:apply-templates select="m:bvar"/> + <mo> + <xsl:value-of select="$RightArrow"/> + </mo> + <xsl:apply-templates select="m:lowlimit"/> + </mrow> + </munder> + </xsl:otherwise> + </xsl:choose> + <mrow> + <xsl:apply-templates select="*[position()=last()]"/> + </mrow> + </mrow> + </xsl:template> + <xsl:template name="tendstoRel"> + <mrow> + <xsl:choose> + <xsl:when test="m:tendsto/@type"> + <xsl:choose> + <xsl:when test="m:tendsto/@type='above'"> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$DownArrow"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </xsl:when> + <xsl:when test="m:tendsto/@type='below'"> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$UpArrow"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </xsl:when> + <xsl:when test="m:tendsto/@type='two-sided'"> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$RightArrow"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </xsl:when> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$RightArrow"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:tendsto]]"> + <xsl:call-template name="tendstoRel"/> + </xsl:template> + <xsl:template match="m:reln[*[1][self::m:tendsto]]"> + <xsl:call-template name="tendstoRel"/> + </xsl:template> + <xsl:template name="trigo"> + <xsl:param name="func">sin</xsl:param> + <mrow> + <mi> + <xsl:value-of select="$func"/> + </mi> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <xsl:choose> + <xsl:when test="*[2][self::m:apply] or (*[2][self::m:ci or self::m:cn] and contains(*[2]/text(),'-'))"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <mrow> + <xsl:apply-templates select="*[2]"/> + </mrow> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:sin]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">sin</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:sin[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>sin</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:cos]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">cos</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:cos[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>cos</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:tan]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">tan</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:tan[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>tan</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:sec]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">sec</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:sec[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>sec</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:csc]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">csc</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:csc[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>csc</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:cot]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">cot</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:cot[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>cot</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:sinh]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">sinh</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:sinh[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>sinh</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:cosh]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">cosh</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:cosh[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>cosh</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:tanh]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">tanh</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:tanh[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>tanh</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:sech]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">sech</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:sech[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>sech</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:csch]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">csch</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:csch[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>csch</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:coth]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">coth</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:coth[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>coth</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arcsin]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">arcsin</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:arcsin[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>arcsin</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arccos]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">arccos</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:arccos[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>arccos</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arctan]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">arctan</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:arctan[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>arctan</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arcsec]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">arcsec</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:arcsec[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>arcsec</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arccsc]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">arccsc</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:arccsc[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>arccsc</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arccot]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">arccot</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:arccot[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>arccot</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arcsinh]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">arcsinh</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:arcsinh[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>arcsinh</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arccosh]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">arccosh</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:arccosh[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>arccosh</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arctanh]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">arctanh</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:arctanh[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>arctanh</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arcsech]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">arcsech</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:arcsech[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>arcsech</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arccsch]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">arccsch</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:arccsch[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>arccsch</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:arccoth]]"> + <xsl:call-template name="trigo"> + <xsl:with-param name="func">arccoth</xsl:with-param> + </xsl:call-template> + </xsl:template> + <xsl:template match="m:arccoth[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>arccoth</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:exp]]"> + <msup> + <mi> + <xsl:value-of select="$ee"/> + </mi> + <xsl:apply-templates select="*[2]"/> + </msup> + </xsl:template> + <xsl:template match="m:exp[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi> + <xsl:value-of select="$ExponentialE"/> + </mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:ln]]"> + <mrow> + <mi>ln</mi> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <xsl:choose> + <xsl:when test="*[2][self::m:apply] or (*[2][self::m:ci or self::m:cn] and contains(*[2]/text(),'-'))"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:ln[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mi>ln</mi> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:log]]"> + <mrow> + <xsl:choose> + <xsl:when test="m:logbase"> + <msub> + <mi>log</mi> + <xsl:apply-templates select="m:logbase"/> + </msub> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <xsl:choose> + <xsl:when test="*[3][self::m:apply] or (*[3][self::m:ci or self::m:cn] and contains(*[3]/text(),'-'))"> + <mfenced separators=""> + <xsl:apply-templates select="*[3]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[3]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <msub> + <mi>log</mi> + <mn>10</mn> + </msub> + <mo> + <xsl:value-of select="$ApplyFunction"/> + </mo> + <xsl:choose> + <xsl:when test="*[2][self::m:apply] or (*[2][self::m:ci or self::m:cn] and contains(*[2]/text(),'-'))"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:log[preceding-sibling::*[position()=last() and (self::m:compose or self::m:inverse)]]"> + <mrow> + <xsl:choose> + <xsl:when test="m:logbase"> + <msub> + <mi>log</mi> + <xsl:apply-templates select="m:logbase"/> + </msub> + </xsl:when> + <xsl:otherwise> + <msub> + <mi>log</mi> + <mn>10</mn> + </msub> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:logbase"> + <xsl:apply-templates select="*"/> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:mean]]"> + <mrow> + <xsl:choose> + <xsl:when test="count(*)>2"> + <mo> + <xsl:value-of select="$lang"/> + </mo> + <xsl:for-each select="*[position()!=1 and position()!=last()]"> + <xsl:apply-templates select="."/> + <mo>,</mo> + </xsl:for-each> + <xsl:apply-templates select="*[position()=last()]"/> + <mo> + <xsl:value-of select="$rang"/> + </mo> + </xsl:when> + <xsl:otherwise> + <mover> + <mrow> + <xsl:apply-templates select="*[position()=last()]"/> + </mrow> + <mo> + <xsl:value-of select="$ovbar"/> + </mo> + </mover> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:sdev]]"> + <mrow> + <mi> + <xsl:value-of select="$sigma"/> + </mi> + <mfenced> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:variance]]"> + <mrow> + <mi> + <xsl:value-of select="$sigma"/> + </mi> + <msup> + <mfenced> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + <mn>2</mn> + </msup> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:median]]"> + <mrow> + <mi>median</mi> + <mfenced> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:mode]]"> + <mrow> + <mi>mode</mi> + <mfenced> + <xsl:apply-templates select="*[position()!=1]"/> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:moment]]"> + <mrow> + <mo> + <xsl:value-of select="$lang"/> + </mo> + <xsl:for-each select="*[position()!=1 and position()!=2 and position()!=last() and not(self::m:momentabout)]"> + <msup> + <xsl:apply-templates select="."/> + <xsl:apply-templates select="../m:degree"/> + </msup> + <mo>,</mo> + </xsl:for-each> + <msup> + <xsl:apply-templates select="*[position()=last()]"/> + <xsl:apply-templates select="m:degree"/> + </msup> + <mo> + <xsl:value-of select="$rang"/> + </mo> + </mrow> + </xsl:template> + <xsl:template match="m:momentabout"> </xsl:template> + <xsl:template match="m:vector"> + <xsl:choose> + <xsl:when test="(preceding-sibling::*[1][self::m:matrix] and preceding-sibling::*[position()=last() and self::m:times])"> + <mfenced> + <mtable> + <xsl:for-each select="*"> + <mtr> + <mtd> + <xsl:apply-templates select="."/> + </mtd> + </mtr> + </xsl:for-each> + </mtable> + </mfenced> + </xsl:when> + <xsl:otherwise> + <mfenced> + <xsl:apply-templates select="*"/> + </mfenced> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template match="m:matrix"> + <mrow> + <mfenced> + <mtable> + <xsl:apply-templates select="*"/> + </mtable> + </mfenced> + </mrow> + </xsl:template> + <xsl:template match="m:matrixrow"> + <mtr> + <xsl:for-each select="*"> + <mtd> + <mpadded width="+0.3em" lspace="+0.3em"> + <xsl:apply-templates select="."/> + </mpadded> + </mtd> + </xsl:for-each> + </mtr> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:determinant]]"> + <mrow> + <mo>det</mo> + <xsl:choose> + <xsl:when test="m:apply"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:transpose]]"> + <msup> + <xsl:choose> + <xsl:when test="m:apply"> + <mfenced separators=""> + <xsl:apply-templates select="*[2]"/> + </mfenced> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + <mo>T</mo> + </msup> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:selector]]"> + <mrow> + <xsl:choose> + <xsl:when test="*[2][self::m:matrix]"> + <xsl:choose> + <xsl:when test="count(*)=4"> + <xsl:variable name="i"> + <xsl:value-of select="*[3]"/> + </xsl:variable> + <xsl:variable name="j"> + <xsl:value-of select="*[4]"/> + </xsl:variable> + <xsl:apply-templates select="*[2]/*[position()=number($i)]/*[position()=number($j)]"/> + </xsl:when> + <xsl:when test="count(*)=3"> + <xsl:variable name="i"> + <xsl:value-of select="*[3]"/> + </xsl:variable> + <mtable> + <xsl:apply-templates select="*[2]/*[position()=number($i)]"/> + </mtable> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:when test="*[2][(self::m:vector or self::m:list)]"> + <xsl:choose> + <xsl:when test="count(*)=3"> + <xsl:variable name="i"> + <xsl:value-of select="*[3]"/> + </xsl:variable> + <xsl:apply-templates select="*[2]/*[position()=number($i)]"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="count(*)=4"> + <msub> + <xsl:apply-templates select="*[2]"/> + <mrow> + <xsl:apply-templates select="*[3]"/> + <mo> + <xsl:value-of select="$InvisibleComma"/> + </mo> + <xsl:apply-templates select="*[4]"/> + </mrow> + </msub> + </xsl:when> + <xsl:when test="count(*)=3"> + <msub> + <xsl:apply-templates select="*[2]"/> + <xsl:apply-templates select="*[3]"/> + </msub> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[2]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:vectorproduct]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo> + <xsl:value-of select="$times"/> + </mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:scalarproduct]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo>.</mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:apply[*[1][self::m:outerproduct]]"> + <mrow> + <xsl:apply-templates select="*[2]"/> + <mo>.</mo> + <xsl:apply-templates select="*[3]"/> + </mrow> + </xsl:template> + <xsl:template match="m:annotation"> </xsl:template> + <xsl:template match="m:semantics"> + <mrow> + <xsl:choose> + <xsl:when test="contains(m:annotation-xml/@encoding,'MathML-Presentation')"> + <xsl:apply-templates select="annotation-xml[contains(@encoding,'MathML-Presentation')]"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*[1]"/> + </xsl:otherwise> + </xsl:choose> + </mrow> + </xsl:template> + <xsl:template match="m:annotation-xml[contains(@encoding,'MathML-Presentation')]"> + <mrow> + <xsl:copy-of select="*"/> + </mrow> + </xsl:template> + <xsl:template match="m:integers"> + <mi> + <xsl:text disable-output-escaping="yes">ℤ</xsl:text> + </mi> + </xsl:template> + <xsl:template match="m:reals"> + <mi> + <xsl:text disable-output-escaping="yes">ℝ</xsl:text> + </mi> + </xsl:template> + <xsl:template match="m:rationals"> + <mi> + <xsl:text disable-output-escaping="yes">ℚ</xsl:text> + </mi> + </xsl:template> + <xsl:template match="m:naturalnumbers"> + <mi> + <xsl:text disable-output-escaping="yes">ℕ</xsl:text> + </mi> + </xsl:template> + <xsl:template match="m:complexes"> + <mi> + <xsl:text disable-output-escaping="yes">ℂ</xsl:text> + </mi> + </xsl:template> + <xsl:template match="m:primes"> + <mi> + <xsl:text disable-output-escaping="yes">ℙ</xsl:text> + </mi> + </xsl:template> + <xsl:template match="m:exponentiale"> + <mi> + <xsl:value-of select="$ee"/> + </mi> + </xsl:template> + <xsl:template match="m:imaginaryi"> + <mi> + <xsl:value-of select="$ImaginaryI"/> + </mi> + </xsl:template> + <xsl:template match="m:notanumber"> + <mi>NaN</mi> + </xsl:template> + <xsl:template match="m:true"> + <mi>true</mi> + </xsl:template> + <xsl:template match="m:false"> + <mi>false</mi> + </xsl:template> + <xsl:template match="m:emptyset"> + <mi> + <xsl:value-of select="$empty"/> + </mi> + </xsl:template> + <xsl:template match="m:pi"> + <mi> + <xsl:value-of select="$pi"/> + </mi> + </xsl:template> + <xsl:template match="m:eulergamma"> + <mi> + <xsl:value-of select="$gamma"/> + </mi> + </xsl:template> + <xsl:template match="m:infinity"> + <mi> + <xsl:value-of select="$infin"/> + </mi> + </xsl:template> + <xsl:template match="*"> + <xsl:copy> + <xsl:for-each select="@*"> + <xsl:copy/> + </xsl:for-each> + <xsl:apply-templates/> + </xsl:copy> + </xsl:template> +</xsl:stylesheet> \ No newline at end of file diff --git a/src/main/resources/rendering-xslt/qti-common.xsl b/src/main/resources/rendering-xslt/qti-common.xsl new file mode 100644 index 0000000000000000000000000000000000000000..501efc62ac7d25e4899e82715e77aa916d0f4eeb --- /dev/null +++ b/src/main/resources/rendering-xslt/qti-common.xsl @@ -0,0 +1,566 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Contains QTI-related templates common to both item and test +rendering. + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns:saxon="http://saxon.sf.net/" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti xs qw saxon m"> + + <!-- ************************************************************ --> + + <!-- Web Application contextPath. Starts with a '/' --> + <xsl:param name="webappContextPath" as="xs:string" required="yes"/> + + <!-- QTIWorks version number --> + <xsl:param name="qtiWorksVersion" as="xs:string" required="yes"/> + + <!-- Global action URLs --> + <xsl:param name="responseUrl" as="xs:string" required="yes"/> + <xsl:param name="serveFileUrl" as="xs:string" required="yes"/> + <xsl:param name="authorViewUrl" as="xs:string" required="yes"/> + <xsl:param name="sourceUrl" as="xs:string" required="yes"/> + <xsl:param name="stateUrl" as="xs:string" required="yes"/> + <xsl:param name="resultUrl" as="xs:string" required="yes"/> + <xsl:param name="validationUrl" as="xs:string" required="yes"/> + + <!-- + URI of the Item or Test being rendered. + Will be passed during 'proper' rendering only; will not be passed when + rendering exploded & terminated states. + --> + <xsl:param name="systemId" as="xs:string?"/> + + <!-- Set to true to include author debug information --> + <xsl:param name="authorMode" as="xs:boolean" required="yes"/> + + <!-- Notifications produced during the event being rendered --> + <xsl:param name="notifications" as="element(qw:notification)*"/> + + <!-- Validation information --> + <xsl:param name="validated" as="xs:boolean"/> + <xsl:param name="launchable" as="xs:boolean"/> + <xsl:param name="errorCount" as="xs:integer"/> + <xsl:param name="warningCount" as="xs:integer"/> + <xsl:param name="valid" as="xs:boolean"/> + + <!-- FIXME: This is not used at the moment --> + <xsl:param name="view" select="false()" as="xs:boolean"/> + + <!-- Debugging Params --> + <!-- FIXME: These are not currently used! --> + <xsl:param name="overrideFeedback" select="false()" as="xs:boolean"/> <!-- enable all feedback --> + <xsl:param name="overrideTemplate" select="false()" as="xs:boolean"/> <!-- enable all templates --> + + <!-- Codebase URL for engine-provided applets --> + <xsl:variable name="appletCodebase" select="concat($webappContextPath, '/rendering/applets')" as="xs:string"/> + + <!-- Optional URL for exiting session (NB: may be relative to context) --> + <xsl:param name="exitSessionUrl" as="xs:string?" required="no"/> + + <!-- + Absolute version of exitSessionUrl (if specified) + NB: This will have been sanitised in advance, and will either be relative or http:// or https://. + --> + <xsl:variable name="exitSessionUrlAbsolute" as="xs:string?" + select="if (exists($exitSessionUrl)) then ( + if (matches($exitSessionUrl, '^https?://')) then $exitSessionUrl else concat($webappContextPath, $exitSessionUrl) + ) else ()"/> + + <!-- ************************************************************ --> + + <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> + + <!-- ************************************************************ --> + + <xsl:function name="qw:format-optional-date" as="xs:string?"> + <xsl:param name="date" as="xs:string?"/> + <xsl:param name="default" as="xs:string?"/> + <xsl:sequence select="if ($date!='') then $date else $default"/> + </xsl:function> + + <xsl:function name="qw:format-number" as="xs:string"> + <xsl:param name="format" as="xs:string"/> + <xsl:param name="number" as="xs:double"/> + <xsl:sequence select="fmt:format($format, $number)" xmlns:fmt="org.olat.ims.qti21.ui.rendering.XsltExtensionFunctions"/> + </xsl:function> + + <xsl:function name="qw:value-contains" as="xs:boolean"> + <xsl:param name="valueHolder" as="element()?"/> + <xsl:param name="test" as="xs:string"/> + <xsl:sequence select="boolean($valueHolder/qw:value[string(.)=$test])"/> + </xsl:function> + + <xsl:function name="qw:is-not-null-value" as="xs:boolean"> + <xsl:param name="valueHolder" as="element()"/> + <xsl:sequence select="exists($valueHolder/*)"/> + </xsl:function> + + <xsl:function name="qw:is-null-value" as="xs:boolean"> + <xsl:param name="valueHolder" as="element()"/> + <xsl:sequence select="not(exists($valueHolder/*))"/> + </xsl:function> + + <xsl:function name="qw:is-single-cardinality-value" as="xs:boolean"> + <xsl:param name="valueHolder" as="element()"/> + <xsl:sequence select="boolean($valueHolder[@cardinality='single'])"/> + </xsl:function> + + <xsl:function name="qw:extract-single-cardinality-value" as="xs:string"> + <xsl:param name="valueHolder" as="element()"/> + <xsl:choose> + <xsl:when test="qw:is-null-value($valueHolder)"> + <xsl:sequence select="''"/> + </xsl:when> + <xsl:when test="qw:is-single-cardinality-value($valueHolder)"> + <xsl:sequence select="string($valueHolder/qw:value)"/> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes"> + Expected value <xsl:copy-of select="$valueHolder"/> to have single cardinality + </xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:function> + + <xsl:function name="qw:is-multiple-cardinality-value" as="xs:boolean"> + <xsl:param name="valueHolder" as="element()"/> + <xsl:sequence select="boolean($valueHolder[@cardinality='multiple'])"/> + </xsl:function> + + <xsl:function name="qw:is-ordered-cardinality-value" as="xs:boolean"> + <xsl:param name="valueHolder" as="element()"/> + <xsl:sequence select="boolean($valueHolder[@cardinality='ordered'])"/> + </xsl:function> + + <xsl:function name="qw:get-cardinality-size" as="xs:integer"> + <xsl:param name="valueHolder" as="element()"/> + <xsl:sequence select="count($valueHolder/qw:value)"/> + </xsl:function> + + <!-- NB: This works for both ordered and multiple cardinalities so as to allow iteration --> + <!-- (NB: The term 'iterable' is not defined in the spec.) --> + <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> + + <xsl:function name="qw:extract-iterable-elements" as="xs:string*"> + <xsl:param name="valueHolder" as="element()"/> + <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="for $v in $valueHolder/qw:value return string($v)"/> + </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> + + <xsl:function name="qw:is-record-cardinality-value" as="xs:boolean"> + <xsl:param name="valueHolder" as="element()"/> + <xsl:sequence select="boolean($valueHolder[@cardinality='record'])"/> + </xsl:function> + + <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> + + <xsl:function name="qw:is-maths-content-value" as="xs:boolean"> + <xsl:param name="valueHolder" as="element()"/> + <xsl:sequence select="boolean($valueHolder[@cardinality='record' + and qw:value[@baseType='string' and @fieldIdentifier='MathsContentClass' + and string(qw:value)='org.qtitools.mathassess']])"/> + </xsl:function> + + <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> + + <xsl:function name="qw:extract-maths-content-cmathml" as="element(m:math)"> + <xsl:param name="valueHolder" as="element()"/> + <xsl:choose> + <xsl:when test="qw:is-maths-content-value($valueHolder)"> + <xsl:variable name="cmathmlString" select="$valueHolder/qw:value[@fieldIdentifier='CMathML']" as="xs:string"/> + <xsl:variable name="cmathmlDocNode" select="saxon:parse($cmathmlString)" as="document-node()"/> + <xsl:copy-of select="$cmathmlDocNode/*"/> + </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> + + <xsl:template name="maybeAddAuthoringLink"> + <!-- Authoring console link (maybe) --> + <xsl:if test="$authorMode"> + <div class="authorModePanel"> + <div class="authoringInvoker"><a href="{$webappContextPath}{$authorViewUrl}" target="_blank">Open Author's Feedback</a></div> + <xsl:call-template name="errorStatusPanel"/> + </div> + </xsl:if> + </xsl:template> + + <!-- ************************************************************ --> + <!-- Variable substitution --> + <!-- ************************************************************ --> + + <xsl:template name="printedVariable" as="node()?"> + <xsl:param name="source" as="element(qti:printedVariable)"/> + <xsl:param name="valueHolder" as="element()"/> + <xsl:param name="valueDeclaration" as="element()"/> + <!-- + + The QTI spec says that this variable must have single cardinality. + + For convenience, we also accept multiple, ordered and record cardinality variables here, + printing them out in a hard-coded form that probably won't make sense to test + candidates but might be useful for debugging. + + Our implementation additionally adds support for "printing" MathsContent variables + used in MathAssess, outputting an inline Presentation MathML element, as documented + in the MathAssses spec. + + --> + <xsl:choose> + <xsl:when test="qw:is-null-value($valueHolder)"> + <!-- (Spec says to output nothing in this case) --> + </xsl:when> + <xsl:when test="qw:is-single-cardinality-value($valueHolder)"> + <xsl:variable name="singleValue" select="qw:extract-single-cardinality-value($valueHolder)" as="xs:string"/> + <xsl:choose> + <xsl:when test="@format and $valueDeclaration[@baseType='float' or @baseType='integer']"> + <xsl:value-of select="qw:format-number(@format, number($singleValue))"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$singleValue"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:when test="qw:is-maths-content-value($valueHolder)"> + <!-- MathAssess math variable --> + <xsl:copy-of select="qw:extract-maths-content-pmathml($valueHolder)"/> + </xsl:when> + <xsl:when test="qw:is-multiple-cardinality-value($valueHolder)"> + <!-- Multiple cardinality --> + <xsl:variable name="delimiter" select="if (exists($source/@delimiter)) then $source/@delimiter else ';'"/> + <xsl:value-of select="qw:extract-iterable-elements($valueHolder)" separator="{$delimiter}"/> + </xsl:when> + <xsl:when test="qw:is-ordered-cardinality-value($valueHolder)"> + <!-- Ordered cardinality --> + <xsl:choose> + <xsl:when test="exists($source/@index)"> + <xsl:value-of select="qw:extract-iterable-element($valueHolder, $source/@index)"/> + </xsl:when> + <xsl:otherwise> + <xsl:variable name="delimiter" select="if (exists($source/@delimiter)) then $source/@delimiter else ';'"/> + <xsl:value-of select="qw:extract-iterable-elements($valueHolder)" separator="{$delimiter}"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:when test="qw:is-record-cardinality-value($valueHolder)"> + <xsl:choose> + <xsl:when test="exists($source/@field)"> + <!-- Display single field --> + <xsl:value-of select="qw:extract-record-field-value($valueHolder, $source/@field)"/> + </xsl:when> + <xsl:otherwise> + <!-- Dump whole record --> + <xsl:variable name="delimiter" select="if (exists($source/@delimiter)) then $source/@delimiter else ';'"/> + <xsl:variable name="mappingIndicator" select="if ($source/@mappingIndicator) then $source/@mappingIndicator else '='"/> + <xsl:variable name="to-print" as="xs:string*" + select="for $v in $valueHolder/qw:value return concat($v/@identifier, $mappingIndicator, $v/qw:value)"/> + <xsl:value-of select="$to-print" separator="{$delimiter}"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes"> + <printedVariable> may not be applied to value + <xsl:copy-of select="$valueHolder"/> + </xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- MathML substitution (mi) --> + <xsl:template name="substitute-mi" as="element()"> + <xsl:param name="identifier" as="xs:string"/> + <xsl:param name="value" as="element()"/> + <xsl:choose> + <xsl:when test="qw:is-null-value($value)"> + <!-- We shall represent null as an empty mrow --> + <xsl:element name="mrow" namespace="http://www.w3.org/1998/Math/MathML"/> + </xsl:when> + <xsl:when test="qw:is-single-cardinality-value($value)"> + <!-- Single cardinality template variables are substituted according to Section 6.3.1 of the + spec. Note that it does not define what should be done with multiple and ordered + cardinality variables. --> + <xsl:element name="mn" namespace="http://www.w3.org/1998/Math/MathML"> + <xsl:copy-of select="@*"/> + <xsl:value-of select="qw:extract-single-cardinality-value($value)"/> + </xsl:element> + </xsl:when> + <xsl:when test="qw:is-maths-content-value($value)"> + <!-- This is a MathAssess MathsContent variable. What we do here is + replace the matched MathML element with the child(ren) of the <math/> PMathML field + in this record, wrapping in an <mrow/> if required so as to ensure that we have a + single replacement element --> + <xsl:variable name="pmathml" select="qw:extract-maths-content-pmathml($value)" as="element(m:math)"/> + <xsl:choose> + <xsl:when test="count($pmathml/*)=1"> + <xsl:copy-of select="$pmathml/*"/> + </xsl:when> + <xsl:otherwise> + <xsl:element name="mrow" namespace="http://www.w3.org/1998/Math/MathML"> + <xsl:copy-of select="$pmathml/*"/> + </xsl:element> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <!-- Unsupported substitution --> + <xsl:message> + Substituting the variable <xsl:value-of select="$identifier"/> with value + <xsl:copy-of select="$value"/> + within MathML is not currently supported. + </xsl:message> + <xsl:element name="mtext" namespace="http://www.w3.org/1998/Math/MathML">(Unsupported variable substitution)</xsl:element> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- MathML substitution (ci) --> + <xsl:template name="substitute-ci" as="element()*"> + <xsl:param name="identifier" as="xs:string"/> + <xsl:param name="value" as="element()"/> + <xsl:choose> + <xsl:when test="qw:is-null-value($value)"> + <!-- We shall omit nulls --> + </xsl:when> + <xsl:when test="qw:is-single-cardinality-value($value)"> + <!-- Single cardinality template variables are substituted according to Section 6.3.1 of the + spec. Note that it does not define what should be done with multiple and ordered + cardinality variables. --> + <xsl:element name="cn" namespace="http://www.w3.org/1998/Math/MathML"> + <xsl:copy-of select="@*"/> + <xsl:value-of select="qw:extract-single-cardinality-value($value)"/> + </xsl:element> + </xsl:when> + <xsl:when test="qw:is-maths-content-value($value)"> + <!-- This is a MathAssess MathsContent variable. What we do here is + replace the matched MathML element with the child(ren) of the <math/> PMathML field + in this record, wrapping in an <mrow/> if required so as to ensure that we have a + single replacement element --> + <xsl:variable name="cmathml" select="qw:extract-maths-content-cmathml($value)" as="element(m:math)"/> + <xsl:copy-of select="$cmathml/*"/> + </xsl:when> + <xsl:otherwise> + <!-- Unsupported substitution --> + <xsl:message> + Substituting the variable <xsl:value-of select="$identifier"/> with value + <xsl:copy-of select="$value"/> + within MathML is not currently supported. + </xsl:message> + <xsl:element name="mtext" namespace="http://www.w3.org/1998/Math/MathML">(Unsupported variable substitution)</xsl:element> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + + <!-- ************************************************************ --> + <!-- QTI flow --> + <!-- ************************************************************ --> + + <!-- feedbackInline --> + <xsl:template match="qti:feedbackInline" as="element(span)?"> + <xsl:variable name="feedback" as="node()*"> + <xsl:call-template name="feedback"/> + </xsl:variable> + <xsl:if test="exists($feedback)"> + <span class="{string-join(('feedbackInline', @class), ' ')}"> + <xsl:sequence select="$feedback"/> + </span> + </xsl:if> + </xsl:template> + + <!-- feedbackBlock --> + <xsl:template match="qti:feedbackBlock" as="element(div)?"> + <xsl:variable name="feedback" as="node()*"> + <xsl:call-template name="feedback"/> + </xsl:variable> + <xsl:if test="exists($feedback)"> + <div class="{string-join(('feedbackBlock', @class), ' ')}"> + <xsl:sequence select="$feedback"/> + </div> + </xsl:if> + </xsl:template> + + <xsl:template name="feedback" as="node()*"> + <xsl:message terminate="yes"> + This must be overridden by including stylesheet + </xsl:message> + </xsl:template> + + <!-- Convert XHTML elements that have been "imported" into QTI --> + <xsl:template match="qti:abbr|qti:acronym|qti:address|qti:blockquote|qti:br|qti:cite|qti:code| + qti:dfn|qti:div|qti:em|qti:h1|qti:h2|qti:h3|qti:h4|qti:h5|qti:h6|qti:kbd| + qti:p|qti:pre|qti:q|qti:samp|qti:span|qti:strong|qti:var| + qti:dl|qti:dt|qti:dd|qti:ol|qti:ul|qti:li| + qti:object|qti:param|qti:b|qti:big|qti:hr|qti:i|qti:small|qti:sub|qti:sup|qti:tt| + qti:caption|qti:col|qti:colgroup|qti:table|qti:tbody|qti:td|qti:th|qti:tfoot|qti:tr|qti:thead| + qti:img|qti:a"> + <xsl:element name="{local-name()}"> + <xsl:apply-templates select="@*" mode="qti-to-xhtml"/> + <xsl:apply-templates select="node()"/> + </xsl:element> + </xsl:template> + + <!-- Handle path attributes carefully so that relative paths get fixed up --> + <xsl:template match="qti:img/@src|@href|qti:object/@data" mode="qti-to-xhtml"> + <xsl:attribute name="{local-name()}" select="qw:convert-link(string(.))"/> + </xsl:template> + + <!-- Copy other attributes as-is --> + <xsl:template match="@*" mode="qti-to-xhtml"> + <xsl:copy-of select="."/> + </xsl:template> + + <!-- ************************************************************ --> + + <!-- Basic item states. NB: Some templates override this --> + <xsl:template match="qw:itemSessionState" mode="item-status"> + <xsl:choose> + <xsl:when test="@endTime!=''"> + <span class="itemStatus ended">Finished</span> + </xsl:when> + <xsl:when test="not(empty(@unboundResponseIdentifiers) and empty(@invalidResponseIdentifiers))"> + <span class="itemStatus invalid">Needs Attention</span> + </xsl:when> + <xsl:when test="@responded='true' or exists(qw:uncommittedResponseValue)"> + <span class="itemStatus answered">Answered</span> + </xsl:when> + <xsl:when test="@entryTime!=''"> + <span class="itemStatus notAnswered">Not Answered</span> + </xsl:when> + <xsl:otherwise> + <span class="itemStatus notPresented">Not Seen</span> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template name="errorStatusPanel" as="element(ul)?"> + <xsl:if test="exists($notifications) or ($validated and not($valid))"> + <xsl:variable name="errors" select="$notifications[@level='ERROR']" as="element(qw:notification)*"/> + <xsl:variable name="warnings" select="$notifications[@level='WARNING']" as="element(qw:notification)*"/> + <xsl:variable name="infos" select="$notifications[@level='INFO']" as="element(qw:notification)*"/> + <ul class="summary"> + <xsl:if test="exists($errors)"> + <li class="errorSummary"><xsl:value-of select="count($errors)"/> Runtime Error<xsl:if test="count($errors)!=1">s</xsl:if></li> + </xsl:if> + <xsl:if test="exists($warnings)"> + <li class="warnSummary"><xsl:value-of select="count($warnings)"/> Runtime Warning<xsl:if test="count($warnings)!=1">s</xsl:if></li> + </xsl:if> + <xsl:if test="exists($infos)"> + <li class="infoSummary"><xsl:value-of select="count($infos)"/> Runtime Information Notification<xsl:if test="count($notifications)!=1">s</xsl:if></li> + </xsl:if> + <xsl:if test="$validated and not($valid)"> + <li class="errorSummary">This assessment has validation errors or warnings</li> + </xsl:if> + </ul> + </xsl:if> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template name="includeJquery"> + <link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/themes/smoothness/jquery-ui.min.css"/> + <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"/> + <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js"/> + </xsl:template> + + <xsl:template name="includeAssessmentJsAndCss"> + <xsl:call-template name="includeJquery"/> + <link rel="stylesheet" href="{$webappContextPath}/rendering/css/assessment.css?{$qtiWorksVersion}" type="text/css" media="screen"/> + <script src="{$webappContextPath}/rendering/javascript/QtiWorksRendering.js?{$qtiWorksVersion}"/> + </xsl:template> + + <xsl:template name="includeQtiWorksJsAndCss"> + <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Open+Sans:400,400italic,700,700italic|Ubuntu:500"/> + <link rel="stylesheet" href="{$webappContextPath}/lib/960/reset.css"/> + <link rel="stylesheet" href="{$webappContextPath}/lib/960/text.css"/> + <link rel="stylesheet" href="{$webappContextPath}/lib/fluid960gs/grid.css"/> + <link rel="stylesheet" href="{$webappContextPath}/includes/qtiworks.css?{$qtiWorksVersion}"/> + <xsl:call-template name="includeJquery"/> + <script src="{$webappContextPath}/includes/qtiworks.js?{$qtiWorksVersion}"/> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/qti-fallback.xsl b/src/main/resources/rendering-xslt/qti-fallback.xsl new file mode 100644 index 0000000000000000000000000000000000000000..b0f482a4f6cbe7c8e7d9a9767b62704453a9bbc0 --- /dev/null +++ b/src/main/resources/rendering-xslt/qti-fallback.xsl @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Contains QTI-related templates common to both item and test +rendering. + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti"> + + <!-- Catch-all for QTI elements not handled elsewhere. --> + <xsl:template match="qti:*" priority="-10"> + <xsl:message terminate="yes"> + QTI element <xsl:value-of select="local-name()"/> was not handled by a template + </xsl:message> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/serialize.xsl b/src/main/resources/rendering-xslt/serialize.xsl new file mode 100644 index 0000000000000000000000000000000000000000..629be0200f17a458b3417c600bee9cffc127fddd --- /dev/null +++ b/src/main/resources/rendering-xslt/serialize.xsl @@ -0,0 +1,197 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +This stylesheet tidies up the XHTML generated by our rendering +process to add in any required extra stuff for the selected +serialization method (e.g. add in MathPlayer gubbins). It also +does a bit of tidying of the results to make it look nicer and +hence slightly easier to debug. + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:xhtml="http://www.w3.org/1999/xhtml" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs xhtml m qw"> + + <xsl:param name="serializationMethod" as="xs:string"/> + <xsl:param name="contentType" as="xs:string"/> + <xsl:param name="outputMethod" as="xs:string"/> + + <!-- New MathJax CDN URL (September 2014) now works over HTTP and HTTPS --> + <!-- FIXME: Allow MathJax URL to be overridden in qtiworks-deployment.properties --> + <xsl:param name="mathJaxUrl" select="'//cdn.mathjax.org/mathjax/2.1-latest/MathJax.js?config=MML_HTMLorMML'" as="xs:string"/> + <xsl:param name="mathJaxConfig" as="xs:string?"/> + + <!-- ************************************************************ --> + + <xsl:template match="xhtml:html" as="element()"> + <xsl:variable name="containsMathML" select="exists(xhtml:body//m:*)" as="xs:boolean"/> + <!-- Generate XHTML tree in usual namespace --> + <xsl:variable name="html" as="element(xhtml:html)"> + <html> + <xsl:copy-of select="@*"/> + <xsl:if test="$serializationMethod='IE_MATHPLAYER' and $containsMathML"> + <xsl:namespace name="m" select="'http://www.w3.org/1998/Math/MathML'"/> + </xsl:if> + <head> + <xsl:choose> + <xsl:when test="$serializationMethod='HTML5_MATHJAX'"> + <!-- Add in HTML5 Content Type meta --> + <meta charset="UTF-8"/> + </xsl:when> + <xsl:otherwise> + <!-- Add traditional HTML Content Type meta --> + <meta http-equiv="Content-Type" content="{$contentType}; charset=UTF-8"/> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="$serializationMethod='IE_MATHPLAYER' and $containsMathML"> + <object id="MathPlayer" classid="clsid:32F66A20-7614-11D4-BD11-00104BD3F987"/> + <xsl:processing-instruction name="import"> + <xsl:text>namespace="m" implementation="#MathPlayer"</xsl:text> + </xsl:processing-instruction> + </xsl:if> + <!-- Pull in <head/> stuff added by other stylesheets --> + <xsl:for-each select="xhtml:head/*"> + <xsl:apply-templates select="."/> + <xsl:text>
</xsl:text> + </xsl:for-each> + <!-- Finally pull in MathJax if required --> + <xsl:if test="$containsMathML and $serializationMethod=('XHTML_MATHJAX','HTML5_MATHJAX')"> + <xsl:if test="string($mathJaxConfig)"> + <script type="text/x-mathjax-config"> + <xsl:value-of select="$mathJaxConfig"/> + </script> + </xsl:if> + <xsl:text>
</xsl:text> + <script src="{$mathJaxUrl}"> + <xsl:if test="not($serializationMethod='XHTML5_MATHJAX')"> + <xsl:attribute name="type" select="'text/javascript'"/> + </xsl:if> + </script> + <xsl:text>
</xsl:text> + </xsl:if> + </head> + <xsl:apply-templates select="xhtml:body"/> + </html> + </xsl:variable> + <!-- + For strict HTML outputs, move HTML elements into no namespace, + otherwise output tree as-is. + --> + <xsl:choose> + <xsl:when test="$outputMethod='html'"> + <xsl:apply-templates select="$html" mode="no-namespace"/> + </xsl:when> + <xsl:otherwise> + <xsl:sequence select="$html"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- Add @type attribute to <script> if we're not generating HTML5 --> + <xsl:template match="xhtml:script[not($serializationMethod='HTML5_MATHJAX') and not(@type)]"> + <xsl:copy> + <xsl:copy-of select="@*"/> + <xsl:attribute name="type" select="'text/javascript'"/> + </xsl:copy> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template match="xhtml:*"> + <xsl:copy> + <xsl:copy-of select="@*"/> + <xsl:apply-templates/> + </xsl:copy> + </xsl:template> + + <xsl:template match="m:*"> + <xsl:choose> + <xsl:when test="$serializationMethod='IE_MATHPLAYER'"> + <xsl:element name="m:{local-name()}" namespace="http://www.w3.org/1998/Math/MathML"> + <xsl:copy-of select="@*"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:when> + <xsl:otherwise> + <xsl:element name="{local-name()}" namespace="http://www.w3.org/1998/Math/MathML"> + <xsl:copy-of select="@*"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="*"> + <xsl:copy> + <xsl:copy-of select="@*"/> + <xsl:apply-templates/> + </xsl:copy> + </xsl:template> + + <xsl:function name="qw:is-xhtml-block-element" as="xs:boolean"> + <xsl:param name="element" as="node()?"/> + <xsl:sequence select="boolean($element[self::xhtml:* and local-name()=('p','table','div','tbody','tr','td','form','ul','li')])"/> + </xsl:function> + + <xsl:template match="m:*/text()"> + <xsl:copy-of select="."/> + </xsl:template> + + <xsl:template match="text()"> + <xsl:choose> + <xsl:when test="normalize-space(.)='' and (qw:is-xhtml-block-element(following-sibling::node()[1]) or qw:is-xhtml-block-element(preceding-sibling::node()[1]))"> + <!-- Whitespace Nodes before/after block elements are ignorable --> + <xsl:sequence select="''"/> + </xsl:when> + <xsl:when test="not(preceding-sibling::node()[1])"> + <!-- Strip leading whitespace on first child node, collapse trailing whitespace down --> + <xsl:sequence select="replace(replace(., '^\s+', ''), '\s+$', ' ')"/> + </xsl:when> + <xsl:when test="not(following-sibling::node()[1])"> + <!-- Strip trailing whitespace on last child node, collapse leading whitespace down --> + <xsl:sequence select="replace(replace(., '\s+$', ''), '^\s+', ' ')"/> + </xsl:when> + <xsl:otherwise> + <!-- Collapse leading and trailing whitespace down --> + <xsl:sequence select="replace(replace(., '^\s+', ' '), '\s+$', ' ')"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template match="xhtml:html" mode="no-namespace"> + <xsl:element name="html" namespace=""> + <xsl:copy-of select="@*"/> + <!-- (Need to re-copy any explicit xmlns:m added previously) --> + <xsl:if test="$serializationMethod='IE_MATHPLAYER' and in-scope-prefixes(.)='m'"> + <xsl:namespace name="m" select="'http://www.w3.org/1998/Math/MathML'"/> + </xsl:if> + <xsl:apply-templates mode="no-namespace"/> + </xsl:element> + </xsl:template> + + <xsl:template match="xhtml:*" mode="no-namespace"> + <xsl:element name="{local-name()}" namespace=""> + <xsl:copy-of select="@*"/> + <xsl:apply-templates mode="no-namespace"/> + </xsl:element> + </xsl:template> + + <xsl:template match="*" mode="no-namespace"> + <xsl:copy> + <xsl:copy-of select="@*"/> + <xsl:apply-templates mode="no-namespace"/> + </xsl:copy> + </xsl:template> + + <xsl:template match="text()|comment()|processing-instruction()" mode="no-namespace"> + <xsl:copy-of select="."/> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/terminated.xsl b/src/main/resources/rendering-xslt/terminated.xsl new file mode 100644 index 0000000000000000000000000000000000000000..a2087f93a3a05accc6674da656d93a611468fcf7 --- /dev/null +++ b/src/main/resources/rendering-xslt/terminated.xsl @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Renders a terminated assessment + +Input document: doesn't matter + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qw"> + + <!-- ************************************************************ --> + + <xsl:import href="qti-common.xsl"/> + + <!-- Optional URL for exiting session --> + <xsl:param name="exitSessionUrl" as="xs:string?" required="no"/> + + <!-- ************************************************************ --> + + <xsl:template match="/" as="element(html)"> + <html lang="en"> + <head> + <title>Assessment Completed</title> + <link rel="stylesheet" href="{$webappContextPath}/rendering/css/assessment.css" type="text/css" media="screen"/> + </head> + <body class="qtiworks"> + <p> + This assessment is now closed and you can no longer interact with it. + </p> + <xsl:if test="exists($exitSessionUrlAbsolute)"> + <p> + <a href="{$exitSessionUrlAbsolute}">Exit and return</a> + </p> + </xsl:if> + </body> + </html> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/test-author-view.xsl b/src/main/resources/rendering-xslt/test-author-view.xsl new file mode 100644 index 0000000000000000000000000000000000000000..d06d1b99a0f6849227dfd31cdfc1643523b7b19a --- /dev/null +++ b/src/main/resources/rendering-xslt/test-author-view.xsl @@ -0,0 +1,244 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Renders the author/debug view of a standalone assessmentItem + +Input document: doesn't matter + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw m"> + + <!-- ************************************************************ --> + + <xsl:import href="author-view-common.xsl"/> + + <!-- State of test being rendered --> + <xsl:param name="testSessionState" as="element(qw:testSessionState)"/> + + <xsl:function name="qw:formatNodeType" as="xs:string"> + <xsl:param name="testPlanNode" as="element(qw:node)"/> + <xsl:choose> + <xsl:when test="$testPlanNode/@type='TEST_PART'"> + <xsl:sequence select="'testPart'"/> + </xsl:when> + <xsl:when test="$testPlanNode/@type='ASSESSMENT_SECTION'"> + <xsl:sequence select="'assessmentSection'"/> + </xsl:when> + <xsl:otherwise> + <xsl:sequence select="'assessmentItemRef'"/> + </xsl:otherwise> + </xsl:choose> + </xsl:function> + + <xsl:function name="qw:formatNodeKey" as="xs:string"> + <xsl:param name="testPlanNode" as="element(qw:node)"/> + <xsl:variable name="components" select="tokenize($testPlanNode/@key, ':')" as="xs:string*"/> + <xsl:variable name="identifier" select="$components[1]" as="xs:string"/> + <xsl:variable name="globalIndex" select="$components[2]" as="xs:string"/> + <xsl:variable name="instance" select="$components[3]" as="xs:string"/> + <xsl:sequence select="concat($identifier, + if ($instance!='1') then concat(' (Instance ', $instance, ')') else ())"/> + </xsl:function> + + <!-- ************************************************************ --> + + <xsl:template match="/" as="element(html)"> + <html> + <head> + <title>Author Debug View</title> + <xsl:call-template name="includeQtiWorksJsAndCss"/> + </head> + <body class="page authorInfo"> + <div class="container_12"> + <header class="pageHeader"> + <h1>QTIWorks</h1> + </header> + <h2>QTI test author's feedback</h2> + + <xsl:call-template name="errorStatusPanel"/> + <xsl:call-template name="buttonBar"/> + <xsl:apply-templates select="$testSessionState"/> + </div> + </body> + </html> + </xsl:template> + + <xsl:template name="buttonBar"> + <div class="buttonBar"> + <ul class="controls"> + <li> + <form action="{$webappContextPath}{$sourceUrl}" method="get" class="showXmlInDialog" title="Test Source XML"> + <input type="submit" value="View Test source XML"/> + </form> + </li> + <li> + <form action="{$webappContextPath}{$stateUrl}" method="get" class="showXmlInDialog" title="Test State XML"> + <input type="submit" value="View Test state XML"/> + </form> + </li> + <li> + <form action="{$webappContextPath}{$resultUrl}" method="get" class="showXmlInDialog" title="Test Result XML"> + <input type="submit" value="View Test <assessmentResult> XML"/> + </form> + </li> + </ul> + </div> + </xsl:template> + + <xsl:template match="qw:testSessionState"> + <div class="resultPanel info"> + <h4>Key status information</h4> + <div class="details"> + <ul> + <li>Entry time: <xsl:value-of select="qw:format-optional-date(@entryTime, '(Not Yet Entered)')"/></li> + <li>End time: <xsl:value-of select="qw:format-optional-date(@endTime, '(Not Yet Ended)')"/></li> + <li>Duration accumulated: <xsl:value-of select="@durationAccumulated div 1000.0"/> s</li> + <li>Initialized: <xsl:value-of select="@initialized"/></li> + <li>Current testPart key: <xsl:value-of select="if (exists(@currentTestPartKey)) then @currentTestPartKey else '(Not in a testPart)'"/></li> + <li>Current item key: <xsl:value-of select="if (exists(@currentItemKey)) then @currentItemKey else '(No item selected)'"/></li> + </ul> + </div> + </div> + <xsl:apply-templates select="." mode="variableValuesPanel"/> + <xsl:apply-templates select="qw:testPlan" mode="testPlan"/> + <xsl:apply-templates select="qw:testPlan" mode="drillDown"/> + <xsl:call-template name="notificationsPanel"/> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template match="qw:testSessionState" mode="variableValuesPanel" as="element(div)*"> + <div class="resultPanel info"> + <h4>Outcome values (<xsl:value-of select="count(qw:outcomeVariable)"/>)</h4> + <div class="details"> + <xsl:choose> + <xsl:when test="exists(qw:outcomeVariable)"> + <xsl:call-template name="dumpValues"> + <xsl:with-param name="valueHolders" select="qw:outcomeVariable"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + (None) + </xsl:otherwise> + </xsl:choose> + </div> + </div> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template match="qw:testPlan" mode="testPlan"> + <div class="resultPanel info"> + <h4>Test plan</h4> + <div class="details"> + <p> + This shows the shape of the test being delivered, after any selection and ordering rules have been applied. + It also shows the effective values of itemSessionControl for each selected testPart, assessmentSection + and assessmentItemRef. + </p> + <table> + <thead> + <tr> + <th>Node</th> + <th>Type</th> + <th>max<br/>Attempts</th> + <th>validate<br/>Responses</th> + <th>allow<br/>Comment</th> + <th>allow<br/>Skipping</th> + <th>show<br/>Solution</th> + <th>show<br/>Feedback</th> + <th>allow<br/>Review</th> + </tr> + </thead> + <tbody> + <xsl:apply-templates select="qw:node" mode="testPlan"/> + </tbody> + </table> + </div> + </div> + </xsl:template> + + <xsl:template match="qw:testPlan//qw:node" mode="testPlan"> + <tr> + <td> + <xsl:for-each select="ancestor::qw:node">↳</xsl:for-each> + <xsl:value-of select="qw:formatNodeKey(.)"/> + </td> + <td align="center"><xsl:value-of select="qw:formatNodeType(.)"/></td> + <td align="center"><xsl:value-of select="@maxAttempts"/></td> + <td align="center"><xsl:value-of select="@validateResponses"/></td> + <td align="center"><xsl:value-of select="@allowComment"/></td> + <td align="center"><xsl:value-of select="@allowSkipping"/></td> + <td align="center"><xsl:value-of select="@showSolution"/></td> + <td align="center"><xsl:value-of select="@showFeedback"/></td> + <td align="center"><xsl:value-of select="@allowReview"/></td> + </tr> + <xsl:apply-templates select="qw:node" mode="testPlan"/> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template match="qw:testPlan" mode="drillDown"> + <div class="resultPanel info"> + <h4>Node state drilldown</h4> + <div class="details"> + <p> + This shows the state of each testPart, assessmentSection and assessmentItemRef instance. + </p> + <xsl:apply-templates select="qw:node" mode="drillDown"/> + </div> + </div> + </xsl:template> + + <xsl:template match="qw:testPlan//qw:node" mode="drillDown"> + <div class="resultPanel expandable"> + <h4> + <xsl:sequence select="qw:formatNodeType(.)"/> + <xsl:sequence select="qw:formatNodeKey(.)"/> + </h4> + <div class="details"> + <xsl:choose> + <xsl:when test="@type='TEST_PART'"> + <xsl:apply-templates select="//qw:testPart[@key=current()/@key]/qw:testPartSessionState"/> + </xsl:when> + <xsl:when test="@type='ASSESSMENT_SECTION'"> + <xsl:apply-templates select="//qw:assessmentSection[@key=current()/@key]/qw:assessmentSectionSessionState"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="//qw:item[@key=current()/@key]/qw:itemSessionState"> + <xsl:with-param name="includeNotifications" select="false()"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> + <xsl:apply-templates select="qw:node" mode="drillDown"/> + </div> + </div> + </xsl:template> + + <xsl:template match="qw:assessmentSectionSessionState | qw:testPartSessionState"> + <ul> + <li>Entry time: <xsl:value-of select="qw:format-optional-date(@entryTime, '(Not Yet Entered)')"/></li> + <xsl:if test="exists(@entryTime)"> + <li>End time: <xsl:value-of select="qw:format-optional-date(@endTime, '(Not Yet Ended)')"/></li> + <xsl:if test="exists(@exitTime)"> + <li>Exit time: <xsl:value-of select="qw:format-optional-date(@endTime, '(Not Yet Exited)')"/></li> + </xsl:if> + </xsl:if> + <li>Duration accumulated: <xsl:value-of select="@durationAccumulated div 1000.0"/> s</li> + <li>preCondition failed?: <xsl:value-of select="@preConditionFailed"/></li> + <li>Jumped by branchRule?: <xsl:value-of select="@jumpedByBranchRule"/></li> + <xsl:if test="@branchRuleTarget"> + <li>branchRule target: <xsl:value-of select="@branchRuleTarget"/></li> + </xsl:if> + </ul> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/test-common.xsl b/src/main/resources/rendering-xslt/test-common.xsl new file mode 100644 index 0000000000000000000000000000000000000000..7ce2bcffb93545249d243ac12d0e999ec98fa935 --- /dev/null +++ b/src/main/resources/rendering-xslt/test-common.xsl @@ -0,0 +1,198 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Base templates used in test rendering + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns:saxon="http://saxon.sf.net/" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="qti xs qw saxon m"> + + <!-- ************************************************************ --> + + <xsl:import href="qti-common.xsl"/> + + <!-- URI of the Test being rendered --> + <xsl:param name="testSystemId" as="xs:string" required="yes"/> + + <!-- State of test being rendered --> + <xsl:param name="testSessionState" as="element(qw:testSessionState)" required="yes"/> + + <!-- Outcome declarations in test --> + <xsl:param name="testOutcomeDeclarations" select="()" as="element(qti:outcomeDeclaration)*"/> + + <!-- Action URLs --> + <xsl:param name="testPartNavigationUrl" as="xs:string" required="yes"/> + <xsl:param name="selectTestItemUrl" as="xs:string" required="yes"/> + <xsl:param name="advanceTestItemUrl" as="xs:string" required="yes"/> + <xsl:param name="endTestPartUrl" as="xs:string" required="yes"/> + <xsl:param name="reviewTestPartUrl" as="xs:string" required="yes"/> + <xsl:param name="reviewTestItemUrl" as="xs:string" required="yes"/> + <xsl:param name="showTestItemSolutionUrl" as="xs:string" required="yes"/> + <xsl:param name="advanceTestPartUrl" as="xs:string" required="yes"/> + <xsl:param name="exitTestUrl" as="xs:string" required="yes"/> + + <!-- ************************************************************ --> + + <!-- Current TestPart details in the TestPlan --> + <xsl:variable name="currentTestPartKey" select="$testSessionState/@currentTestPartKey" as="xs:string"/> + <xsl:variable name="currentTestPartNode" select="$testSessionState/qw:testPlan/qw:node[@key=$currentTestPartKey]" as="element(qw:node)"/> + + <!-- assesssmentTest details --> + <xsl:variable name="assessmentTest" select="document($testSystemId)/*[1]" as="element(qti:assessmentTest)"/> + <xsl:variable name="currentTestPart" select="$assessmentTest/qti:testPart[@identifier=qw:extract-identifier($currentTestPartNode)]" as="element(qti:testPart)"/> + <xsl:variable name="hasMultipleTestParts" select="count($assessmentTest/qti:testPart) > 1" as="xs:boolean"/> + + <!-- Test outcome values --> + <xsl:variable name="testOutcomeValues" select="$testSessionState/qw:outcomeVariable" as="element(qw:outcomeVariable)*"/> + + <!-- ************************************************************ --> + + <xsl:variable name="testOrTestPart" as="xs:string" + select="if ($hasMultipleTestParts) then 'Test Part' else 'Test'"/> + + <xsl:variable name="endTestPartAlertMessage" as="xs:string" + select="concat('Are you sure? This will commit your answers for this ', $testOrTestPart, '.')"/> + + <xsl:variable name="exitTestPartAlertMessage" as="xs:string" + select="concat('Are you sure? This will leave this ', $testOrTestPart, ' and you can''t go back in.')"/> + + <xsl:variable name="exitTestAlertMessage" as="xs:string" + select="'Are you sure? This will leave ths Test and you can''t go back in.'"/> + + <!-- ************************************************************ --> + + <xsl:function name="qw:get-test-outcome-value" as="element(qw:outcomeVariable)?"> + <xsl:param name="identifier" as="xs:string"/> + <xsl:sequence select="$testOutcomeValues[@identifier=$identifier]"/> + </xsl:function> + + <xsl:function name="qw:get-test-outcome-declaration" as="element(qti:outcomeDeclaration)?"> + <xsl:param name="identifier" as="xs:string"/> + <xsl:sequence select="$assessmentTest/qti:outcomeDeclaration[@identifier=$identifier]"/> + </xsl:function> + + <xsl:function name="qw:extract-identifier" as="xs:string"> + <xsl:param name="testPlanNode" as="element(qw:node)"/> + <xsl:sequence select="substring-before($testPlanNode/@key, ':')"/> + </xsl:function> + + <!-- ************************************************************ --> + + <xsl:template match="qti:rubricBlock" as="element(div)"> + <div class="rubric {@view}"> + <xsl:if test="not($view) or ($view = @view)"> + <xsl:apply-templates/> + </xsl:if> + </div> + </xsl:template> + + <!-- ************************************************************ --> + + <!-- printedVariable. Numeric output currently only supports Java String.format formatting. --> + <xsl:template match="qti:assessmentTest//qti:printedVariable" as="element(span)"> + <xsl:variable name="identifier" select="@identifier" as="xs:string"/> + <xsl:variable name="testOutcomeValue" select="qw:get-test-outcome-value(@identifier)" as="element(qw:outcomeVariable)?"/> + <span class="printedVariable"> + <xsl:choose> + <xsl:when test="exists($testOutcomeValue)"> + <xsl:call-template name="printedVariable"> + <xsl:with-param name="source" select="."/> + <xsl:with-param name="valueHolder" select="$testOutcomeValue"/> + <xsl:with-param name="valueDeclaration" select="qw:get-test-outcome-declaration(@identifier)"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + (variable <xsl:value-of select="$identifier"/> was not found) + </xsl:otherwise> + </xsl:choose> + </span> + </xsl:template> + + <!-- Keep MathML by default --> + <xsl:template match="m:*" as="element()"> + <xsl:element name="{local-name()}" namespace="http://www.w3.org/1998/Math/MathML"> + <xsl:copy-of select="@*"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:template> + + <!-- MathML parallel markup containers: we'll remove any non-XML annotations, which may + result in the container also being removed as it's no longer required in that case. --> + <xsl:template match="m:semantics" as="element()*"> + <xsl:choose> + <xsl:when test="not(*[position()!=1 and self::m:annotation-xml])"> + <!-- All annotations are non-XML so remove this wrapper completely (and unwrap a container mrow if required) --> + <xsl:apply-templates select="if (*[1][self::m:mrow]) then *[1]/* else *[1]"/> + </xsl:when> + <xsl:otherwise> + <!-- Keep non-XML annotations --> + <xsl:element name="semantics" namespace="http://www.w3.org/1998/Math/MathML"> + <xsl:apply-templates select="* except m:annotation"/> + </xsl:element> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="m:*/text()" as="text()"> + <!-- NOTE: The XML input is produced using JQTI's toXmlString() method, which has + the unfortunate effect of indenting MathML, so we'll renormalise --> + <xsl:value-of select="normalize-space(.)"/> + </xsl:template> + + <!-- mathml (mi) --> + <!-- + We are extending the spec here in 2 ways: + 1. Allowing MathsContent variables to be substituted + 2. Allowing arbitrary response and outcome variables to be substituted. + --> + <xsl:template match="qti:assessmentTest//m:mi" as="element()"> + <xsl:variable name="content" select="normalize-space(text())" as="xs:string"/> + <xsl:variable name="testOutcomeValue" select="qw:get-test-outcome-value(@identifier)" as="element(qw:outcomeVariable)?"/> + <xsl:choose> + <xsl:when test="exists($testOutcomeValue)"> + <xsl:call-template name="substitute-mi"> + <xsl:with-param name="identifier" select="$content"/> + <xsl:with-param name="value" select="$testOutcomeValue"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:element name="mi" namespace="http://www.w3.org/1998/Math/MathML"> + <xsl:copy-of select="@*"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- ************************************************************ --> + + <!-- test feedback --> + <xsl:template match="qti:testFeedback"> + <xsl:variable name="feedback-content" as="node()*"> + <xsl:choose> + <xsl:when test="$overrideFeedback"> + <xsl:apply-templates/> + </xsl:when> + <xsl:otherwise> + <xsl:variable name="identifierMatch" select="boolean(qw:value-contains(qw:get-test-outcome-value(@outcomeIdentifier), @identifier))" as="xs:boolean"/> + <xsl:if test="($identifierMatch and @showHide='show') or (not($identifierMatch) and @showHide='hide')"> + <xsl:apply-templates/> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="exists($feedback-content)"> + <h2>Feedback</h2> + <xsl:copy-of select="$feedback-content"/> + </xsl:if> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/test-entry.xsl b/src/main/resources/rendering-xslt/test-entry.xsl new file mode 100644 index 0000000000000000000000000000000000000000..9db7e55dc72d00dac9aaeee7bd2c5b1775c4d6bf --- /dev/null +++ b/src/main/resources/rendering-xslt/test-entry.xsl @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Test Entry page (shown when there are multiple testParts) + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw m"> + + <!-- ************************************************************ --> + + <xsl:import href="qti-fallback.xsl"/> + <xsl:import href="test-common.xsl"/> + <xsl:import href="utils.xsl"/> + + <!-- ************************************************************ --> + + <xsl:template match="qti:assessmentTest" as="element(html)"> + <html> + <xsl:if test="@lang"> + <xsl:copy-of select="@lang"/> + <xsl:attribute name="xml:lang" select="@lang"/> + </xsl:if> + <head> + <title><xsl:value-of select="@title"/></title> + <xsl:call-template name="includeAssessmentJsAndCss"/> + </head> + <body class="qtiworks assessmentTest testEntry"> + <xsl:call-template name="maybeAddAuthoringLink"/> + + <h2>Test Entry Page</h2> + + <p> + This test consists of + <xsl:if test="exists(.//qti:preCondition)"><xsl:text> up to </xsl:text></xsl:if> + <xsl:value-of select="count(qti:testPart)"/> + parts. + </p> + + <!-- Test session control --> + <xsl:call-template name="qw:test-controls"/> + </body> + </html> + </xsl:template> + + <xsl:template name="qw:test-controls"> + <ul class="sessionControl"> + <li> + <form action="{$webappContextPath}{$advanceTestPartUrl}" method="post"> + <input type="submit" value="Enter Test"/> + </form> + </li> + </ul> + </xsl:template> + +</xsl:stylesheet> + diff --git a/src/main/resources/rendering-xslt/test-feedback.xsl b/src/main/resources/rendering-xslt/test-feedback.xsl new file mode 100644 index 0000000000000000000000000000000000000000..8d5dc8ba5afc12b845aa3d8f7397dd9e9e97c530 --- /dev/null +++ b/src/main/resources/rendering-xslt/test-feedback.xsl @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Renders the final test feedback. + +(This is currently only shown in multi-part tests. Single part tests +combine the feedback for the test and the testPart.) + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw m"> + + <!-- ************************************************************ --> + + <xsl:import href="qti-fallback.xsl"/> + <xsl:import href="test-common.xsl"/> + <xsl:import href="utils.xsl"/> + + <!-- This test --> + <xsl:variable name="assessmentTest" select="/*[1]" as="element(qti:assessmentTest)"/> + + <!-- ************************************************************ --> + + <xsl:template match="qti:assessmentTest" as="element(html)"> + <html> + <xsl:if test="@lang"> + <xsl:copy-of select="@lang"/> + <xsl:attribute name="xml:lang" select="@lang"/> + </xsl:if> + <head> + <title><xsl:value-of select="@title"/></title> + <xsl:call-template name="includeAssessmentJsAndCss"/> + </head> + <body class="qtiworks assessmentTest testFeedback"> + <xsl:call-template name="maybeAddAuthoringLink"/> + + <h1>Test Complete</h1> + + <!-- Show 'atEnd' feedback for the test --> + <xsl:apply-templates select="qti:testFeedback[@access='atEnd']"/> + + <!-- Test session control --> + <xsl:call-template name="qw:test-controls"/> + </body> + </html> + </xsl:template> + + <xsl:template name="qw:test-controls"> + <ul class="sessionControl"> + <li> + <form action="{$webappContextPath}{$exitTestUrl}" method="post" + onsubmit="return confirm({qw:to-javascript-string($exitTestAlertMessage)})"> + <input type="submit" value="Exit Test"/> + </form> + </li> + </ul> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/test-item.xsl b/src/main/resources/rendering-xslt/test-item.xsl new file mode 100644 index 0000000000000000000000000000000000000000..98b2af74b4c2e8c3c4d3880c37a51f6964254f2f --- /dev/null +++ b/src/main/resources/rendering-xslt/test-item.xsl @@ -0,0 +1,335 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Renders an AssessmentItem within an AssessmentTest, as seen by candidates. + +NB: This is used both while being presented, and during review. + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw m"> + + <!-- ************************************************************ --> + + <xsl:import href="qti-fallback.xsl"/> + <xsl:import href="test-common.xsl"/> + <xsl:import href="item-common.xsl"/> + <xsl:import href="utils.xsl"/> + + <!-- + Flag to indicate that item is being modally rendered in review mode at the end + of the current testPart. When unset, we will be rendering the current state of + the current item. + --> + <xsl:param name="reviewMode" as="xs:boolean" required="yes"/> + + <!-- + Key for item being rendered is passed here. + NB: Can't simply extract $testSessionState/@currentItemKey as this will be null + when *reviewing* an item. + --> + <xsl:param name="itemKey" as="xs:string"/> + + <!-- Action permissions --> + <xsl:param name="advanceTestItemAllowed" as="xs:boolean" required="yes"/> + <xsl:param name="endTestPartAllowed" as="xs:boolean" required="yes"/> + <xsl:param name="testPartNavigationAllowed" as="xs:boolean" required="yes"/> + + <!-- Relevant properties of EffectiveItemSessionControl for this item --> + <xsl:param name="showFeedback" as="xs:boolean" required="yes"/> + <xsl:param name="allowComment" as="xs:boolean" required="yes"/> + <xsl:param name="showSolution" as="xs:boolean" required="yes"/> + + <!-- + Keep reference to assesssmentItem element as the processing chain goes off on a tangent + at one point. + --> + <xsl:variable name="assessmentItem" select="/*[1]" as="element(qti:assessmentItem)"/> + + <xsl:variable name="itemFeedbackAllowed" as="xs:boolean" + select="if ($reviewMode) + then (/qti:assessentItem/@adaptive='true' or $showFeedback) + else (not($solutionMode))"/> + + <xsl:variable name="provideItemSolutionButton" as="xs:boolean" + select="$reviewMode and $showSolution and not($solutionMode)"/> + + <!-- Text to use on submit button, which depends on submissionMode --> + <xsl:variable name="submitButtonText" as="xs:string" + select="if ($currentTestPart/@submissionMode='individual') then 'Submit Answer' else 'Save Answer'"/> + + <!-- ************************************************************ --> + + <!-- Item may be QTI 2.0 or 2.1, so we'll put a template in here to fix namespaces to QTI 2.1 --> + <xsl:template match="/"> + <xsl:apply-templates select="qw:to-qti21(/)/*"/> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template match="qti:assessmentItem" as="element(html)"> + <xsl:variable name="containsMathEntryInteraction" + select="exists(qti:itemBody//qti:customInteraction[@class='org.qtitools.mathassess.MathEntryInteraction'])" + as="xs:boolean"/> + <html> + <xsl:if test="@lang"> + <xsl:copy-of select="@lang"/> + <xsl:attribute name="xml:lang" select="@lang"/> + </xsl:if> + <head> + <title><xsl:value-of select="@title"/></title> + <xsl:call-template name="includeAssessmentJsAndCss"/> + + <!-- + Import ASCIIMathML stuff if there are any MathEntryInteractions in the question. + (It would be quite nice if we could allow each interaction to hook into this + part of the result generation directly.) + --> + <xsl:if test="$containsMathEntryInteraction"> + <script src="{$webappContextPath}/rendering/javascript/UpConversionAjaxController.js?{$qtiWorksVersion}"/> + <script src="{$webappContextPath}/rendering/javascript/AsciiMathInputController.js?{$qtiWorksVersion}"/> + <script> + UpConversionAjaxController.setUpConversionServiceUrl('<xsl:value-of select="$webappContextPath"/>/candidate/verifyAsciiMath'); + UpConversionAjaxController.setDelay(300); + </script> + </xsl:if> + + <!-- Include stylesheet declared within item --> + <xsl:apply-templates select="qti:stylesheet"/> + </head> + <body class="qtiworks assessmentItem assessmentTest"> + <xsl:call-template name="maybeAddAuthoringLink"/> + + <!-- + Show 'during' tetFeedback for the current testPart and/or the test itself. + The info model says this should be shown directly after outcome processing. + This is equivalent in this case to the item's sessionStatus='final' + --> + <xsl:if test="$sessionStatus='final'"> + <!-- Show any 'during' testFeedback for the current testPart --> + <xsl:apply-templates select="$currentTestPart/qti:testFeedback[@access='during']"/> + + <!-- Show any 'during' testFeedback for the test --> + <xsl:apply-templates select="$assessmentTest/qti:testFeedback[@access='during']"/> + </xsl:if> + + <!-- Drill down into current item via current testPart structure --> + <xsl:apply-templates select="$currentTestPartNode" mode="testPart-drilldown"/> + </body> + </html> + </xsl:template> + + <xsl:template name="qw:test-controls"> + <ul class="sessionControl"> + <!-- Interacting state --> + <xsl:if test="$advanceTestItemAllowed"> + <li> + <form action="{$webappContextPath}{$advanceTestItemUrl}" method="post"> + <input type="submit" value="Next Question"/> + </form> + </li> + </xsl:if> + <xsl:if test="$testPartNavigationAllowed"> + <li> + <form action="{$webappContextPath}{$testPartNavigationUrl}" method="post"> + <input type="submit" value="Test Question Menu"/> + </form> + </li> + </xsl:if> + <xsl:if test="$endTestPartAllowed"> + <li> + <form action="{$webappContextPath}{$endTestPartUrl}" method="post" + onsubmit="return confirm({qw:to-javascript-string($endTestPartAlertMessage)})"> + <input type="submit" value="End {$testOrTestPart}"/> + </form> + </li> + </xsl:if> + <!-- Review state --> + <xsl:if test="$reviewMode"> + <li> + <form action="{$webappContextPath}{$reviewTestPartUrl}" method="post"> + <input type="submit" value="Back to Test Feedback"/> + </form> + </li> + </xsl:if> + <xsl:if test="$provideItemSolutionButton"> + <li> + <form action="{$webappContextPath}{$showTestItemSolutionUrl}/{$itemKey}" method="post"> + <input type="submit" value="Show Solution"/> + </form> + </li> + </xsl:if> + <xsl:if test="$reviewMode and $solutionMode"> + <!-- Allow return to item review state --> + <li> + <form action="{$webappContextPath}{$reviewTestItemUrl}/{$itemKey}" method="post"> + <input type="submit" value="Hide Solution"/> + </form> + </li> + </xsl:if> + </ul> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template match="qw:node[@type='TEST_PART']" mode="testPart-drilldown"> + <ul class="testPartDrilldown"> + <xsl:apply-templates mode="testPart-drilldown"/> + </ul> + </xsl:template> + + <xsl:template match="qw:node[@type='ASSESSMENT_SECTION']" mode="testPart-drilldown"> + <xsl:if test=".//qw:node[@key=$itemKey]"> + <!-- Only show sections that ancestors of current item --> + <li class="assessmentSection"> + <header> + <!-- Section title --> + <h2><xsl:value-of select="@sectionPartTitle"/></h2> + <!-- Handle rubrics --> + <xsl:variable name="sectionIdentifier" select="qw:extract-identifier(.)" as="xs:string"/> + <xsl:variable name="assessmentSection" select="$assessmentTest//qti:assessmentSection[@identifier=$sectionIdentifier]" as="element(qti:assessmentSection)*"/> + <xsl:apply-templates select="$assessmentSection/qti:rubricBlock"/> + </header> + <!-- Descend --> + <ul class="testPartDrilldownInner"> + <xsl:apply-templates mode="testPart-drilldown"/> + </ul> + </li> + </xsl:if> + </xsl:template> + + <xsl:template match="qw:node[@type='ASSESSMENT_ITEM_REF']" mode="testPart-drilldown"> + <xsl:if test="@key=$itemKey"> + <!-- We've reached the current item --> + <li class="currentItem"> + + <!-- Render item --> + <xsl:apply-templates select="$assessmentItem" mode="render-item"/> + + <!-- Put session controls here --> + <xsl:call-template name="qw:test-controls"/> + </li> + </xsl:if> + </xsl:template> + + <!-- ************************************************************ --> + + <xsl:template match="qti:assessmentItem" mode="render-item"> + <!-- Item title --> + <h1 class="itemTitle"> + <xsl:apply-templates select="$itemSessionState" mode="item-status"/> + <xsl:value-of select="@title"/> + </h1> + + <!-- Render item body --> + <xsl:apply-templates select="qti:itemBody"/> + + <!-- Display active modal feedback (only after responseProcessing) --> + <xsl:if test="$itemFeedbackAllowed and $sessionStatus='final'"> + <xsl:variable name="modalFeedback" as="element()*"> + <xsl:for-each select="qti:modalFeedback"> + <xsl:variable name="feedback" as="node()*"> + <xsl:call-template name="feedback"/> + </xsl:variable> + <xsl:if test="$feedback"> + <div class="modalFeedback"> + <xsl:if test="@title"><h3><xsl:value-of select="@title"/></h3></xsl:if> + <xsl:sequence select="$feedback"/> + </div> + </xsl:if> + </xsl:for-each> + </xsl:variable> + <xsl:if test="exists($modalFeedback)"> + <div class="modalFeedback"> + <h2>Feedback</h2> + <xsl:sequence select="$modalFeedback"/> + </div> + </xsl:if> + </xsl:if> + </xsl:template> + + <xsl:template match="qti:itemBody"> + <div id="itemBody"> + <form method="post" action="{$webappContextPath}{$responseUrl}" + enctype="multipart/form-data" accept-charset="UTF-8" + onsubmit="return QtiWorksRendering.maySubmit()" + onreset="QtiWorksRendering.reset()" autocomplete="off"> + + <xsl:apply-templates/> + + <xsl:choose> + <xsl:when test="$allowComment and $isItemSessionOpen"> + <fieldset class="candidateComment"> + <legend>Please use the following text box if you need to provide any additional information, comments or feedback during this test:</legend> + <input name="qtiworks_comment_presented" type="hidden" value="true"/> + <textarea name="qtiworks_comment"><xsl:value-of select="$itemSessionState/qw:candidateComment"/></textarea> + </fieldset> + </xsl:when> + <xsl:when test="$allowComment and $isItemSessionEnded and exists($itemSessionState/qw:candidateComment)"> + <fieldset class="candidateComment"> + <legend>You submitted the folllowing comment with this item:</legend> + <input name="qtiworks_comment_presented" type="hidden" value="true"/> + <textarea name="qtiworks_comments" disabled="disabled"><xsl:value-of select="$itemSessionState/qw:candidateComment"/></textarea> + </fieldset> + </xsl:when> + </xsl:choose> + + <xsl:if test="$isItemSessionOpen"> + <div class="testItemControl"> + <input id="submit_button" name="submit" type="submit" value="{$submitButtonText}"/> + </div> + </xsl:if> + </form> + </div> + </xsl:template> + + <!-- Override using 'showFeedback' --> + <xsl:template match="qti:feedbackInline | qti:feedbackBlock"> + <xsl:if test="$itemFeedbackAllowed"> + <xsl:apply-imports/> + </xsl:if> + </xsl:template> + + <!-- Disable any buttons in the question (from endAttemptInteraction) if not in interacting state --> + <xsl:template match="qti:endAttemptInteraction[not($isItemSessionOpen)]"> + <input type="submit" name="{@responseIdentifier}" value="{@title}" disabled="disabled"/> + </xsl:template> + + <!-- ************************************************************ --> + + <!-- Overridden to add support for review state --> + <xsl:template match="qw:itemSessionState" mode="item-status"> + <xsl:choose> + <xsl:when test="$solutionMode"> + <span class="itemStatus review">Model Solution</span> + </xsl:when> + <xsl:when test="$reviewMode"> + <xsl:choose> + <xsl:when test="not(empty(@unboundResponseIdentifiers) and empty(@invalidResponseIdentifiers))"> + <span class="itemStatus reviewInvalid">Review (Invalid Answer)</span> + </xsl:when> + <xsl:when test="@responded='true'"> + <span class="itemStatus review">Review</span> + </xsl:when> + <xsl:when test="@entryTime!=''"> + <span class="itemStatus reviewNotAnswered">Review (Not Answered)</span> + </xsl:when> + <xsl:otherwise> + <span class="itemStatus reviewNotSeen">Review (Not Seen)</span> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:apply-imports/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/test-testpart-feedback.xsl b/src/main/resources/rendering-xslt/test-testpart-feedback.xsl new file mode 100644 index 0000000000000000000000000000000000000000..ea740719b71feba7fda7dd0fc07628793c3c7c57 --- /dev/null +++ b/src/main/resources/rendering-xslt/test-testpart-feedback.xsl @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Renders the test(Part) feedback + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw m"> + + <!-- ************************************************************ --> + + <xsl:import href="qti-fallback.xsl"/> + <xsl:import href="test-common.xsl"/> + <xsl:import href="utils.xsl"/> + + <!-- This test --> + <xsl:variable name="assessmentTest" select="/*[1]" as="element(qti:assessmentTest)"/> + + <!-- ************************************************************ --> + + <xsl:template match="qti:assessmentTest" as="element(html)"> + <html> + <xsl:if test="@lang"> + <xsl:copy-of select="@lang"/> + <xsl:attribute name="xml:lang" select="@lang"/> + </xsl:if> + <head> + <title><xsl:value-of select="@title"/></title> + <xsl:call-template name="includeAssessmentJsAndCss"/> + </head> + <body class="qtiworks assessmentTest testFeedback"> + <xsl:call-template name="maybeAddAuthoringLink"/> + + <h1><xsl:value-of select="$testOrTestPart"/> Complete</h1> + + <!-- Show 'atEnd' testPart feedback --> + <xsl:apply-templates select="$currentTestPart/qti:testFeedback[@access='atEnd']"/> + + <!-- Show 'atEnd' test feedback f there's only 1 testPart --> + <xsl:if test="not($hasMultipleTestParts)"> + <xsl:apply-templates select="qti:testFeedback[@access='atEnd']"/> + </xsl:if> + + <!-- Review --> + <xsl:apply-templates select="$currentTestPartNode" mode="testPart-review"/> + + <!-- Test session control --> + <xsl:call-template name="qw:test-controls"/> + </body> + </html> + </xsl:template> + + <xsl:template name="qw:test-controls"> + <ul class="sessionControl"> + <li> + <form action="{$webappContextPath}{$advanceTestPartUrl}" method="post" + onsubmit="return confirm({qw:to-javascript-string($exitTestPartAlertMessage)})"> + <input type="submit" value="Exit {$testOrTestPart}"/> + </form> + </li> + </ul> + </xsl:template> + + <xsl:template match="qw:node[@type='TEST_PART']" mode="testPart-review"> + <xsl:variable name="reviewable-items" select=".//qw:node[@type='ASSESSMENT_ITEM_REF' and (@allowReview='true' or @showFeedback='true')]" as="element(qw:node)*"/> + <xsl:if test="exists($reviewable-items)"> + <h2>Review your responses</h2> + <p> + You may review your responses to some (or all) questions. These are listed below. + </p> + <ul class="testPartNavigation"> + <xsl:apply-templates mode="testPart-review"/> + </ul> + </xsl:if> + </xsl:template> + + <xsl:template match="qw:node[@type='ASSESSMENT_SECTION']" mode="testPart-review"> + <xsl:variable name="assessmentSessionSessionState" select="$testSessionState/qw:assessmentSection[@key=current()/@key]/qw:assessmentSectionSessionState" + as="element(qw:assessmentSectionSessionState)"/> + <xsl:if test="$currentTestPart/@navigationMode='nonlinear' or exists($assessmentSessionSessionState/@entryTime)"> + <li class="assessmentSection"> + <header> + <!-- Section title --> + <h2><xsl:value-of select="@sectionPartTitle"/></h2> + <!-- Handle rubrics --> + <xsl:variable name="sectionIdentifier" select="qw:extract-identifier(.)" as="xs:string"/> + <xsl:variable name="assessmentSection" select="$assessmentTest//qti:assessmentSection[@identifier=$sectionIdentifier]" as="element(qti:assessmentSection)*"/> + <xsl:apply-templates select="$assessmentSection/qti:rubricBlock"/> + </header> + <!-- Descend --> + <ul class="testPartNavigationInner"> + <xsl:apply-templates mode="testPart-review"/> + </ul> + </li> + </xsl:if> + </xsl:template> + + <xsl:template match="qw:node[@type='ASSESSMENT_ITEM_REF']" mode="testPart-review"> + <xsl:variable name="reviewable" select="@allowReview='true' or @showFeedback='true'" as="xs:boolean"/> + <xsl:variable name="itemSessionState" select="$testSessionState/qw:item[@key=current()/@key]/qw:itemSessionState" as="element(qw:itemSessionState)"/> + <xsl:if test="$currentTestPart/@navigationMode='nonlinear' or exists($itemSessionState/@entryTime)"> + <li class="assessmentItem"> + <form action="{$webappContextPath}{$reviewTestItemUrl}/{@key}" method="post"> + <button type="submit"> + <xsl:if test="not($reviewable)"> + <xsl:attribute name="disabled" select="'disabled'"/> + </xsl:if> + <span class="questionTitle"><xsl:value-of select="@sectionPartTitle"/></span> + <xsl:choose> + <xsl:when test="not($reviewable)"> + <span class="itemStatus reviewNotAllowed">Not Reviewable</span> + </xsl:when> + <xsl:when test="not(empty($itemSessionState/@unboundResponseIdentifiers) and empty($itemSessionState/@invalidResponseIdentifiers))"> + <span class="itemStatus reviewInvalid">Review (Invalid Answer)</span> + </xsl:when> + <xsl:when test="$itemSessionState/@responded='true'"> + <span class="itemStatus review">Review</span> + </xsl:when> + <xsl:when test="$itemSessionState/@entryTime!=''"> + <span class="itemStatus reviewNotAnswered">Review (Not Answered)</span> + </xsl:when> + <xsl:otherwise> + <span class="itemStatus reviewNotSeen">Review (Not Seen)</span> + </xsl:otherwise> + </xsl:choose> + </button> + </form> + </li> + </xsl:if> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/resources/rendering-xslt/test-testpart-navigation.xsl b/src/main/resources/rendering-xslt/test-testpart-navigation.xsl new file mode 100644 index 0000000000000000000000000000000000000000..61a7057a7edd3a9d220a257e2bc808b41e5dafd6 --- /dev/null +++ b/src/main/resources/rendering-xslt/test-testpart-navigation.xsl @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +Renders the navigation for the current testPart + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:m="http://www.w3.org/1998/Math/MathML" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw m"> + + <!-- ************************************************************ --> + + <xsl:import href="qti-fallback.xsl"/> + <xsl:import href="test-common.xsl"/> + <xsl:import href="utils.xsl"/> + + <!-- Action permissions --> + <xsl:param name="endTestPartAllowed" as="xs:boolean" required="yes"/> + + <!-- ************************************************************ --> + + <xsl:template match="qti:assessmentTest" as="element(html)"> + <html> + <xsl:if test="@lang"> + <xsl:copy-of select="@lang"/> + <xsl:attribute name="xml:lang" select="@lang"/> + </xsl:if> + <head> + <title><xsl:value-of select="@title"/></title> + <xsl:call-template name="includeAssessmentJsAndCss"/> + </head> + <body class="qtiworks assessmentTest testPartNavigation"> + <xsl:call-template name="maybeAddAuthoringLink"/> + + <h1><xsl:value-of select="$testOrTestPart"/> Question Menu</h1> + <xsl:apply-templates select="$currentTestPartNode" mode="testPart-navigation"/> + + <!-- Test session control --> + <xsl:call-template name="qw:test-controls"/> + </body> + </html> + </xsl:template> + + <xsl:template name="qw:test-controls"> + <ul class="sessionControl"> + <li> + <form action="{$webappContextPath}{$endTestPartUrl}" method="post" + onsubmit="return confirm('{$endTestPartAlertMessage}')"> + <input type="submit" value="End {$testOrTestPart}"> + <xsl:if test="not($endTestPartAllowed)"> + <xsl:attribute name="disabled" select="'disabled'"/> + </xsl:if> + </input> + </form> + </li> + </ul> + </xsl:template> + + <xsl:template match="qw:node" mode="testPart-navigation"> + <ul class="testPartNavigation"> + <xsl:apply-templates mode="testPart-navigation"/> + </ul> + </xsl:template> + + <xsl:template match="qw:node[@type='ASSESSMENT_SECTION']" mode="testPart-navigation"> + <li class="assessmentSection"> + <header> + <!-- Section title --> + <h2><xsl:value-of select="@sectionPartTitle"/></h2> + <!-- Handle rubrics --> + <xsl:variable name="sectionIdentifier" select="qw:extract-identifier(.)" as="xs:string"/> + <xsl:variable name="assessmentSection" select="$assessmentTest//qti:assessmentSection[@identifier=$sectionIdentifier]" as="element(qti:assessmentSection)*"/> + <xsl:apply-templates select="$assessmentSection/qti:rubricBlock"/> + </header> + <!-- Descend --> + <ul class="testPartNavigationInner"> + <xsl:apply-templates mode="testPart-navigation"/> + </ul> + </li> + </xsl:template> + + <xsl:template match="qw:node[@type='ASSESSMENT_ITEM_REF']" mode="testPart-navigation"> + <xsl:variable name="itemSessionState" select="$testSessionState/qw:item[@key=current()/@key]/qw:itemSessionState" as="element(qw:itemSessionState)"/> + <li class="assessmentItem"> + <form action="{$webappContextPath}{$selectTestItemUrl}/{@key}" method="post"> + <button type="submit"> + <span class="questionTitle"><xsl:value-of select="@sectionPartTitle"/></span> + <xsl:apply-templates select="$itemSessionState" mode="item-status"/> + </button> + </form> + </li> + </xsl:template> + +</xsl:stylesheet> + diff --git a/src/main/resources/rendering-xslt/utils.xsl b/src/main/resources/rendering-xslt/utils.xsl new file mode 100644 index 0000000000000000000000000000000000000000..f5a1d2b094231fd38b17c5368952d1d4b0ceccb4 --- /dev/null +++ b/src/main/resources/rendering-xslt/utils.xsl @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + +General utility templates + +--> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p1" + xmlns:qw="http://www.ph.ed.ac.uk/qtiworks" + xmlns="http://www.w3.org/1999/xhtml" + xpath-default-namespace="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="xs qti qw"> + + <!-- ************************************************************ --> + <!-- JavaScript string helpers --> + + <xsl:function name="qw:escape-for-javascript-string" as="xs:string"> + <xsl:param name="input" as="xs:string?"/> + <xsl:sequence select="replace(replace($input, '[
]', ''), '('')', '\\$1')"/> + </xsl:function> + + <xsl:function name="qw:to-javascript-string" as="xs:string"> + <xsl:param name="input" as="xs:string"/> + <xsl:sequence select="concat('''', qw:escape-for-javascript-string($input), '''')"/> + </xsl:function> + + <xsl:function name="qw:to-javascript-arguments" as="xs:string"> + <xsl:param name="inputs" as="xs:string*"/> + <xsl:sequence select="string-join(for $string in $inputs return qw:to-javascript-string($string), ', ')"/> + </xsl:function> + + <!-- ************************************************************ --> + <!-- QTI 2.0 to 2.1 --> + + <xsl:function name="qw:to-qti21" as="document-node()"> + <xsl:param name="input" as="document-node()"/> + <xsl:variable name="root-element" select="$input/*[1]" as="element()"/> + <xsl:variable name="root-namespace" select="namespace-uri($root-element)" as="xs:anyURI"/> + <xsl:choose> + <xsl:when test="$root-namespace='http://www.imsglobal.org/xsd/imsqti_v2p1'"> + <xsl:sequence select="$input"/> + </xsl:when> + <xsl:when test="$root-namespace='http://www.imsglobal.org/xsd/imsqti_v2p0'"> + <xsl:sequence select="()"/> + <!-- Convert QTI 2.0 to QTI 2.1 --> + <xsl:document> + <xsl:apply-templates select="$root-element" mode="qti20-to-21"/> + </xsl:document> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes"> + Unexpected namespace URI '<xsl:value-of select="$root-namespace"/>' for root element + </xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:function> + + <xsl:template match="*" mode="qti20-to-21"> + <xsl:element name="{local-name()}" namespace="http://www.imsglobal.org/xsd/imsqti_v2p1"> + <xsl:copy-of select="@*"/> + <xsl:apply-templates mode="qti20-to-21"/> + </xsl:element> + </xsl:template> + + <xsl:template match="text()" mode="qti20-to-21"> + <xsl:copy/> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/main/webapp/static/assessment/rendering/css/assessment.css b/src/main/webapp/static/assessment/rendering/css/assessment.css new file mode 100644 index 0000000000000000000000000000000000000000..a3d8c650369dfb700da29f9659e2269a2866ace8 --- /dev/null +++ b/src/main/webapp/static/assessment/rendering/css/assessment.css @@ -0,0 +1,524 @@ +/* QTIworks assessment rendering */ +/************************************************************/ +/* Launcher */ + +#launchBox { + width: 50em; + margin: 5em auto; + text-align: center; + display: none; +} + +#launchBox #progressBar { + margin: 1em; + height: 1.8em; + border: 1px solid #b7b7b7; + border-radius: 1em; + background-color: #b7b7b7; + background-image: url(data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==); /* Image as URL was not always loading in Chrome... never worked out why! */ + opacity: 0.3; +} + +#launchBox #launchButton { + background: none; + border: none; +} + +#launchBox #launchButton:hover { + text-decoration: underline; +} + +/************************************************************/ +/* Session control */ + +ul.sessionControl { + list-style: none; + margin: 1em; + text-align: center; +} + +ul.sessionControl li { + display: inline; + padding: 0.2em; +} + +ul.sessionControl li form { + display: inline; +} + +/************************************************************/ +/* Assessment Item */ +/************************************************************/ + +.assessmentItem { +} + +.assessmentItem h1 { + margin-bottom: 0; +} + +.assessmentItem .itemStatus { + float: right; + display: block; + padding: 0.3em; + border-radius: 0.5em; + color: white; + font-size: 0.8em; +} + +.itemStatus.ended { + background-color: #999999; +} + +.itemStatus.invalid { + background-color: red; +} + +.itemStatus.answered { + background-color: blue; +} + +.itemStatus.notAnswered { + background-color: #ff9900; +} + +.itemStatus.notPresented { + background-color: green; +} + +.itemStatus.review { + background-color: #f52887; +} + +.itemStatus.reviewNotAllowed, +.itemStatus.reviewInvalid, +.itemStatus.reviewNotAnswered, +.itemStatus.reviewNotSeen { + background-color: #f52887; + opacity: 0.5; +} + +.itemPrompt { + margin: 1.5em 0; + font-style: italic; + color: #666666; +} + +#itemBody { + margin: 1em 0; +} + +/************************************************************/ + +.assessmentItem div.badResponse, +.assessmentItem span.badResponse { + color: red; + font-weight: bold; +} + +.assessmentItem input.badResponse { + border: 1px solid red; +} + +/************************************************************/ + +.assessmentItem .infoControl input { + margin-right: 0.5em; +} + +.assessmentItem .infoControl .infoControlContent { + display: none; +} + +.assessmentItem .sliderInteraction { + margin: 1em; +} + +.assessmentItem .sliderInteraction .sliderVertical { +} + +.assessmentItem .sliderInteraction .sliderVertical .sliderValue { + margin: 1em 0; +} + +.assessmentItem .sliderInteraction .sliderVertical .sliderWidget { + height: 200px; +} + +.assessmentItem .sliderInteraction .sliderHorizontal .sliderValue { + text-align: center; +} + +.assessmentItem div.orderInteraction div.highlight { + border: 1px solid red; +} + +.assessmentItem div.orderInteraction div.box { + background-color: #eeeeee; +} + +.assessmentItem div.orderInteraction div.box span.info { + margin-left: 1em; + color: #666666; + font-style: italic; + font-size: smaller; +} + +.assessmentItem div.orderInteraction div.box.horizontal { + margin: 0.5em 0; + width: 100%; +} + +.assessmentItem div.orderInteraction ul.horizontal { + float: left; + margin: 0 1em; + padding: 1em 0; + width: 100%; +} + +.assessmentItem div.orderInteraction div.box.vertical { + margin: 1em 0; + width: 49%; +} + +.assessmentItem div.orderInteraction div.box.vertical.source { + float: left; +} + +.assessmentItem div.orderInteraction div.box.vertical.target { + float: right; +} + +.assessmentItem div.orderInteraction ul.vertical { + margin: 0 1em; + padding: 1em 0; + width: auto; +} + +.assessmentItem div.orderInteraction ul { + list-style-type: none; + margin: 0; + padding: 0; + width: 60%; +} + +.assessmentItem div.orderInteraction ul li { + margin: 0 3px 3px 3px; + padding: 0.4em; + padding-left: 1.5em; + font-size: 1em; +} + +.assessmentItem div.orderInteraction ul li span.ui-icon { + position: absolute; + margin-left: -1.3em; +} + +.assessmentItem div.orderInteraction ul.horizontal li { + float: left; + width: auto; +} + +.assessmentItem div.orderInteraction br { + clear: both; +} + +.assessmentItem .hottext { + font-weight: bold; +} + +.assessmentItem .gap { + font-weight: bold; + border: 1px dashed #000; +} + +/* MathEntryInteraction styling for MathAssess */ +.assessmentItem .mathEntryInteraction { + border: 1px solid #ddedfc; + background-color: #edf1f6; + background: linear-gradient(to top, #edf1f6 0%, #f6f9fb 100%); + border-radius: 0.4em; + padding: 1em; + margin: 0.5em 0; +} + +.assessmentItem .mathEntryInteraction.horizontal { + min-height: 5em; + width: 40em; +} + +.assessmentItem .mathEntryInteraction.vertical { + min-height: 6em; +} + +.assessmentItem .mathEntryInteraction .inputPanel { + line-height: 1em; + text-align: left; +} + +.assessmentItem .mathEntryInteraction.horizontal .inputPanel { + width: 45%; + float: left; + margin: 2em 0; +} + +.assessmentItem .mathEntryInteraction.vertical .inputPanel { + padding: 0 5em; +} + +.assessmentItem .mathEntryInteraction.vertical .inputPanel:before { + content: 'Input Maths: '; +} + +.assessmentItem .mathEntryInteraction .inputPanel a { + display: block; + float: left; + padding: 0; + margin: 2px 0; + height: 1em; + width: 18px; + background: url(../images/help.png) center no-repeat; +} + +.assessmentItem .mathEntryInteraction .inputPanel a:hover { + cursor: help; + background-color: transparent !important; +} + +.assessmentItem .mathEntryInteraction .inputPanel input { + margin: 0; + padding: 0; +} + +.assessmentItem .mathEntryInteraction .previewPanel { + text-align: center; +} + +.assessmentItem .mathEntryInteraction.horizontal .previewPanel { + width: 50%; + margin-left: 40%; +} + +.assessmentItem .mathEntryInteraction.vertical .previewPanel { + margin-top: 2em; + min-height: 4em; +} + +.assessmentItem div.upConversionAjaxControlMessage { + width: auto; + text-align: center; + display: inline; + padding: 0.5em 0 0.5em 20px; +} + +.assessmentItem div.waiting { + background: url(../images/spinner.gif) no-repeat center left; +} + +.assessmentItem div.success { + background: url(../images/accept.png) no-repeat center left; +} + +.assessmentItem div.failure { + background: url(../images/cancel.png) no-repeat center left; +} + +.assessmentItem div.error { + background: url(../images/exclamation.png) no-repeat center left; +} + +.assessmentItem div.upConversionAjaxControlError { +} + +.assessmentItem div.upConversionAjaxControlPreview { + margin: 0.5em 0; + font-size: 110%; +} + +/* Help for Math Input */ + +.assessmentItem table.inputHelp { + border-collapse: collapse; + width: 100%; + font-size: 90%; +} + +.assessmentItem table.inputHelp th { + border: 1px solid #999999; + padding: 0.2em 0.5em; + background-color: #cad8e5; +} + +.assessmentItem table.inputHelp td { + color: #999999; + border: 1px solid #999999; + padding: 0.2em 0.5em; +} + +.assessmentItem table.inputHelp kbd { + color: black; + font-size: 100%; + line-height: 100%; +} + +.assessmentItem table.inputHelp .longComma { + margin-right: 0.5em; +} + +/* Candidate comment */ + +.candidateComment { + padding: 0; + margin: 2em 0; + border: none; +} + +.candidateComment legend { +} + +.candidateComment textarea { + display: block; +} + +/************************************************************/ +/* Test stuff */ +/************************************************************/ + +/* Test Navigation */ + +ul.testPartNavigation { + list-style: none; + padding: 0; + margin: 0; +} + +ul.testPartNavigation li.assessmentSection { + border: 2px solid #eeeeee; + border-radius: 0.5em; + padding: 0 1em 0.5em 1em; + margin: 0.5em 0; +} + +ul.testPartNavigation li.assessmentSection header { + margin: 0 0 1em 0; +} + +ul.testPartNavigation li.assessmentSection header div.rubric { + font-style: italic; +} + +ul.testPartNavigation li.assessmentSection ul.testPartNavigationInner { + list-style: none; + padding: 0; + margin: 0; +} + +ul.testPartNavigation li.assessmentItem { + padding: 0.1em 0; +} + +ul.testPartNavigation li.assessmentItem button { + padding: 0.5em; +} + +ul.testPartNavigation li.assessmentItem span { + vertical-align: middle; +} + +ul.testPartNavigation li.assessmentItem .questionTitle { +} + +ul.testPartNavigation li.assessmentItem .itemStatus { + display: inline; + float: none; + margin: 0.2em; + padding: 0.4em; + border-radius: 0.3em; + margin-left: 1em; +} + +/* Test Item Presentation */ + +ul.testPartDrilldown { + list-style: none; + padding: 0; + margin: 0; +} + +ul.testPartDrilldown li.assessmentSection { + border: 2px solid #eeeeee; + border-radius: 0.5em; + padding: 0 1em 0.5em 1em; +} + +ul.testPartDrilldown li.assessmentSection header { + margin: 0 0 1em 0; +} + +ul.testPartDrilldown li.assessmentSection header div.rubric { + font-style: italic; +} + +ul.testPartDrilldown li.assessmentSection ul.testPartDrilldownInner { + list-style: none; + padding: 0; + margin: 0; +} + +ul.testPartDrilldown li.currentItem { + border: 2px solid #eeeeee; + border-radius: 0.5em; + padding: 0 1em; + margin-top: 1em; +} + +.testItemControl { + margin-top: 0.5em; +} + +/************************************************************/ +/* Author invoker */ + +.authorModePanel { + position: fixed; + bottom: 0; + left: 0; + width: auto; + z-index: 127; + background: white url(../../includes/images/bluegradient.png) left top repeat-x; + border: 1px solid #ccdbf0; + border-top-right-radius: 1em; + padding: 1em; + color: #666666; + font-size: smaller; +} + +.authorModePanel div { + text-align: center; +} + +.authorModePanel ul.summary { + list-style: none; + margin: 0; + padding: 0; +} + +.authorModePanel ul.summary li { + margin: 0; + padding: 0.3em 0 0.3em 24px; +} + +.authorModePanel li.errorSummary { + background: url(../../includes/images/exclamation.png) 5px center no-repeat; +} + +.authorModePanel li.warnSummary { + background: url(../../includes/images/error.png) 5px center no-repeat; +} + +.authorModePanel li.infoSummary { + background: url(../../includes/images/information.png) 5px center no-repeat; +} + + + diff --git a/src/main/webapp/static/assessment/rendering/images/accept.png b/src/main/webapp/static/assessment/rendering/images/accept.png new file mode 100644 index 0000000000000000000000000000000000000000..89c8129a490b329f3165f32fa0781701aab417ea Binary files /dev/null and b/src/main/webapp/static/assessment/rendering/images/accept.png differ diff --git a/src/main/webapp/static/assessment/rendering/images/cancel.png b/src/main/webapp/static/assessment/rendering/images/cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..c149c2bc017d5ce5a8ae9330dd7dbd012482e0f4 Binary files /dev/null and b/src/main/webapp/static/assessment/rendering/images/cancel.png differ diff --git a/src/main/webapp/static/assessment/rendering/images/exclamation.png b/src/main/webapp/static/assessment/rendering/images/exclamation.png new file mode 100644 index 0000000000000000000000000000000000000000..c37bd062e60c3b38fc82e4d1f236a8ac2fae9d8c Binary files /dev/null and b/src/main/webapp/static/assessment/rendering/images/exclamation.png differ diff --git a/src/main/webapp/static/assessment/rendering/images/help.png b/src/main/webapp/static/assessment/rendering/images/help.png new file mode 100644 index 0000000000000000000000000000000000000000..5c870176d4dea68aab9e51166cc3d7a582f326d6 Binary files /dev/null and b/src/main/webapp/static/assessment/rendering/images/help.png differ diff --git a/src/main/webapp/static/assessment/rendering/images/spinner.gif b/src/main/webapp/static/assessment/rendering/images/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..d0bce1542342e912da81a2c260562df172f30d73 Binary files /dev/null and b/src/main/webapp/static/assessment/rendering/images/spinner.gif differ diff --git a/src/main/webapp/static/assessment/rendering/javascript/AsciiMathInputController.js b/src/main/webapp/static/assessment/rendering/javascript/AsciiMathInputController.js new file mode 100644 index 0000000000000000000000000000000000000000..aa33923f24c82adf4c991732cb3c13598f84f9fb --- /dev/null +++ b/src/main/webapp/static/assessment/rendering/javascript/AsciiMathInputController.js @@ -0,0 +1,225 @@ +/* + * + * Requirements: + * + * jquery.js (at least version 1.5.0) + * UpConversionAjaxController.js + * AsciiMathParser.js (optional - only needed for geek previews) + * + * Author: David McKain + * + * Copyright (c) 2008-2011, The University of Edinburgh + * All Rights Reserved + */ + +/************************************************************/ + +var AsciiMathInputController = (function() { + + var helpDialog = null; /* (Created on first use) */ + + /* See if AsciiMathParser.js was loaded in order to provide live raw + * previews ACIIMath input. + */ + var asciiMathParserLoaded = false; + var asciiMathParser = null; + try { + asciiMathParser = new AsciiMathParser(AsciiMathParserBrowserUtilities.createXmlDocument()); + asciiMathParserLoaded = true; + } + catch (e) { + } + + /************************************************************/ + + var callAsciiMath = function(mathModeInput) { + var mathElement = asciiMathParser.parseAsciiMathInput(mathModeInput, { + displayMode: true, + addSourceAnnotation: true + }); + var mathml = AsciiMathParserBrowserUtilities.serializeXmlNode(mathElement); + return AsciiMathParserBrowserUtilities.indentMathmlString(mathml); + }; + + var showHelpDialog = function(helpAElement) { + if (helpDialog==null) { + var helpPanel = jQuery('<div></div>'); + helpPanel.load(helpAElement.href); + helpDialog = helpPanel.dialog({ + autoOpen: false, + draggable: true, + resizable: true, + title: 'Input Hints', + width: '70%' + }); + } + if (helpDialog.dialog('isOpen')) { + helpDialog.dialog('close'); + } + else { + var buttonPosition = jQuery(helpAElement).position(); + helpDialog.dialog('option', 'position', [ buttonPosition.left, buttonPosition.top + 70 ]); + helpDialog.dialog('open'); + } + }; + + /************************************************************/ + + var Widget = function(_asciiMathInputId, _verifierControl) { + this.asciiMathInputControlId = _asciiMathInputId; + this.verifierControl = _verifierControl; + this.rawRenderingContainerId = null; + this.rawSourceContainerId = null; + this.helpButtonId = null; + var lastInput = null; + var currentXHR = null; + var currentTimeoutId = null; + var widget = this; + + /* Binds event handlers to the input widget to make it responsd to user input */ + this._init = function() { + /* Bind help button */ + if (this.helpButtonId!=null) { + var helpButton = jQuery("#" + this.helpButtonId); + helpButton.click(function() { + showHelpDialog(this); + return false; + }); + } + + /* Set up handler to update preview when required */ + var inputSelector = jQuery("#" + this.asciiMathInputControlId); + inputSelector.bind("change keyup keydown", function() { + widget._userInputChanged(); + }); + }; + + /** + * Called after the user causes a change in input. This checks the input + * to see if it is different from the last input and, if so, schedules + * verification. + */ + this._userInputChanged = function() { + var asciiMathInput = this._getAsciiMathInput(); + if (lastInput==null || asciiMathInput!=lastInput) { + lastInput = asciiMathInput; + this._processInput(asciiMathInput); + } + }; + + this._getAsciiMathInput = function() { + var inputSelector = jQuery("#" + this.asciiMathInputControlId); + return inputSelector.get(0).value; + }; + + this._setAsciiMathInput = function(asciiMathInput) { + var inputSelector = jQuery("#" + this.asciiMathInputControlId); + inputSelector.get(0).value = asciiMathInput || ''; + lastInput = asciiMathInput; + this._processInput(asciiMathInput); + }; + + this._show = function(asciiMathInput, jsonData) { + var inputSelector = jQuery("#" + this.asciiMathInputControlId); + inputSelector.get(0).value = asciiMathInput || ''; + lastInput = asciiMathInput; + this.verifierControl.showVerificationResult(jsonData); + }; + + this._processInput = function(asciiMathInput) { + /* Update live ASCIIMath preview (if used) */ + var asciiMathInput = widget._updateAsciiMathPreview(asciiMathInput); + + /* Call up verifier (if used) */ + if (this.verifierControl!=null) { + if (asciiMathInput!=null && asciiMathInput.length>0) { + this.verifierControl.verifyLater(asciiMathInput); + } + else { + this.verifierControl.clear(); + } + } + }; + + this._updateAsciiMathPreview = function(asciiMathInput) { + /* Get ASCIIMathML to generate a <math> element */ + var mathmlSource = null; + var message = null; + if (asciiMathInput.match(/\S/)) { + if (asciiMathParserLoaded) { + mathmlSource = callAsciiMath(this.getAsciiMathInput()); + } + else { + message = "(AsciiMathParser.js not loaded)"; + } + } + else { + message = "(Blank input)"; + } + /* Update preview elements */ + if (this.rawRenderingContainerId!=null) { + if (mathmlSource!=null) { + UpConversionAjaxController.replaceContainerMathMLContent(jQuery("#" + this.rawRenderingContainerId), mathmlSource); + } + else { + UpConversionAjaxController.replaceContainerMathMLContent(jQuery("#" + this.rawRenderingContainerId), + "<math><mtext>" + message + "</mtext></math>"); + } + } + if (this.rawSourceContainerId!=null) { + UpConversionAjaxController.replaceContainerPreformattedText(jQuery("#" + this.rawSourceContainerId), mathmlSource || message); + } + return asciiMathInput; + }; + + }; + + Widget.prototype.setHelpButtonId = function(id) { + this.helpButtonId = id; + }; + + Widget.prototype.setRawRenderingContainerId = function(id) { + this.rawRenderingContainerId = id; + }; + + Widget.prototype.setRawSourceContainerId = function(id) { + this.rawSourceContainerId = id; + }; + + Widget.prototype.init = function() { + this._init(); + }; + + Widget.prototype.syncWithInput = function() { + this._processInput(this._getAsciiMathInput()); + }; + + Widget.prototype.getAsciiMathInput = function() { + return this._getAsciiMathInput(); + }; + + Widget.prototype.setAsciiMathInput = function(asciiMathInput) { + this._setAsciiMathInput(asciiMathInput); + }; + + Widget.prototype.show = function(asciiMathInput, jsonData) { + this._show(asciiMathInput, jsonData); + }; + + Widget.prototype.reset = function() { + this._setAsciiMathInput(null); + }; + + return { + bindInputWidget: function(asciiMathInputId, verifierControl) { + if (asciiMathInputId==null) { + throw new Error("asciiMathInputId must not be null"); + } + if (verifierControl==null) { + throw new Error("verifierControl must not be null"); + } + return new Widget(asciiMathInputId, verifierControl); + } + }; + +})(); diff --git a/src/main/webapp/static/assessment/rendering/javascript/QtiWorksRendering.js b/src/main/webapp/static/assessment/rendering/javascript/QtiWorksRendering.js new file mode 100644 index 0000000000000000000000000000000000000000..2e1d897ba3eddf11a81383992220e86400af3945 --- /dev/null +++ b/src/main/webapp/static/assessment/rendering/javascript/QtiWorksRendering.js @@ -0,0 +1,623 @@ +/* + * + * Requirements: + * + * jquery.js + * jquery-ui.js (incl. Draggable, Resizable, Sortable, Dialog, Slider) + * + * Author: David McKain + * + * Copyright (c) 2012-2013, The University of Edinburgh + * All Rights Reserved + */ + +/************************************************************/ + +var QtiWorksRendering = (function() { + + var submitCallbacks = []; + var resetCallbacks = []; + + var registerSubmitCallback = function(callback) { + submitCallbacks.push(callback); + }; + + var registerResetCallback = function(callback) { + resetCallbacks.push(callback); + }; + + var queryInputElements = function(responseIdentifier) { + return $('input[name=qtiworks_response_' + responseIdentifier + ']'); + }; + + /************************************************************/ + /* sliderInteraction */ + + var SliderInteraction = function(responseIdentifier, configData) { + this.responseIdentifier = responseIdentifier; + this.sliderQuery = $('#qtiworks_id_slider_' + responseIdentifier); + this.feedbackQuery = $('#qtiworks_id_slidervalue_' + responseIdentifier); + this.inputElementQuery = $('input[name="qtiworks_response_' + responseIdentifier + '"]'); + this.min = configData.min; + this.max = configData.max; + this.step = configData.step; + this.orientation = configData.orientation; + this.isReversed = configData.isReversed; + this.isDiscrete = configData.isDiscrete; + this.initialValue = this.inputElementQuery.get(0).value || this.min; + var interaction = this; + + this.init = function() { + this.sliderQuery.slider({ + value: interaction.initialValue, + step: interaction.step, + orientation: interaction.orientation, + /* (To handle 'reverse', we simply negate and swap min/max when mapping to/from the slider itself) */ + min: interaction.isReversed ? -interaction.max : interaction.min, + max: interaction.isReversed ? -interaction.min : interaction.max, + slide: function(event, ui) { + var value = interaction.isReversed ? -ui.value : ui.value; + interaction.setValue(value); + } + }); + this.reset(); + }; + + this.setValue = function(value) { + this.inputElementQuery.get(0).value = value; + this.feedbackQuery.text(value); + this.sliderQuery.slider('value', this.isReversed ? -value : value); + }; + + this.reset = function() { + this.setValue(this.initialValue); + }; + + registerResetCallback(function() { + interaction.reset(); + }); + }; + + /************************************************************/ + /* matchInteraction */ + + var MatchInteraction = function(responseIdentifier, maxAssociations, leftData, rightData) { + this.responseIdentifier = responseIdentifier; + this.maxAssociations = maxAssociations; + this.matchCount = 0; + this.leftMap = {}; + this.rightMap = {}; + this.matched = []; + var interaction = this; + + for(var key in leftData){ + this.leftMap[key] = { + matchMax: leftData[key], + matchCount: 0 + }; + } + for(var key in rightData){ + this.rightMap[key] = { + matchMax: rightData[key], + matchCount: 0 + }; + } + + this.withCheckbox = function(inputElement, callback) { + var directedPair = inputElement.value; + var splitPair = directedPair.split(" "); + var left = interaction.leftMap[splitPair[0]]; + var right = interaction.rightMap[splitPair[1]]; + callback(inputElement, directedPair, left, right); + }; + + this.init = function() { + queryInputElements(this.responseIdentifier).bind('click', function() { + interaction.checkMatch(this); + }); + this.recalculate(); + this.updateDisabledStates(); + }; + + this.resetChecks = function() { + queryInputElements(this.responseIdentifier).each(function() { + if (interaction.matched[this.value]) { + this.checked = true; + } + else { + this.checked = false; + } + }); + this.recalculate(); + this.updateDisabledStates(); + }; + + this.recalculate = function() { + this.matchCount = 0; + this.matched = {}; + for(var key in this.leftMap) { + this.leftMap[key].matchCount = 0; + } + for(var key in this.rightMap) { + this.rightMap[key].matchCount = 0; + } + + queryInputElements(this.responseIdentifier).each(function() { + interaction.withCheckbox(this, function(inputElement, directedPair, left, right) { + if (inputElement.checked) { + interaction.matchCount++; + left.matchCount++; + right.matchCount++; + interaction.matched[directedPair] = true; + } + }); + }); + }; + + this.updateDisabledStates = function() { + queryInputElements(this.responseIdentifier).each(function() { + interaction.withCheckbox(this, function(inputElement, directedPair, left, right) { + if (inputElement.checked) { + inputElement.disabled = false; + } + else if ((interaction.maxAssociations!=0 && interaction.matchCount >= interaction.maxAssociations) + || (left.matchMax!=0 && left.matchCount >= left.matchMax) + || (right.matchMax!=0 && right.matchCount >= right.matchMax)) { + inputElement.disabled = true; + } + else { + inputElement.disabled = false; + } + }); + }); + }; + + this.checkMatch = function(inputElement) { + interaction.withCheckbox(inputElement, function(inputElement, directedPair, left, right) { + if (inputElement.checked){ + var incremented = false; + if (left.matchMax != 0 && left.matchMax <= left.matchCount) { + inputElement.checked = false; + } + else { + left.matchCount++; + interaction.matchCount++; + incremented = true; + } + + if (right.matchMax != 0 && right.matchMax <= right.matchCount) { + inputElement.checked = false; + } + else { + right.matchCount++; + if (!incremented) { + interaction.matchCount++; + } + } + } + else { + interaction.matchCount--; + left.matchCount--; + right.matchCount--; + } + interaction.updateDisabledStates(responseIdentifier); + }); + }; + + registerResetCallback(function() { + interaction.resetChecks(); + }); + }; + + /************************************************************/ + /* gapMatchInteraction (NB: no JS validation of matchMin/required here) */ + + var GapMatchInteraction = function(responseIdentifier, gapChoiceData, gapData) { + this.responseIdentifier = responseIdentifier; + this.gapChoiceMap = {}; + this.gapMap = {}; + this.matched = []; + var interaction = this; + + for(var key in gapChoiceData){ + var query = $('#qtiworks_id_' + this.responseIdentifier + '_' + key); + this.gapChoiceMap[key] = { + matchMax: gapChoiceData[key], + matchCount: 0, + query: query, + text: query.text() + }; + } + for(var key in gapData){ + var query = $('#qtiworks_id_' + this.responseIdentifier + '_' + key); + this.gapMap[key] = { + required: gapData[key], /* NB: This is not currently used in the JS */ + matched: false, + matchedGapChoice: null, + query: query, + label: query.text() + }; + } + + this.withCheckbox = function(inputElement, callback) { + var directedPair = inputElement.value; + var splitPair = directedPair.split(" "); + var gapChoice = interaction.gapChoiceMap[splitPair[0]]; + var gap = interaction.gapMap[splitPair[1]]; + callback(inputElement, directedPair, gapChoice, gap); + }; + + + this.init = function() { + var checkboxes = queryInputElements(this.responseIdentifier); + checkboxes.bind('click', function() { + interaction.checkMatch(this); + }); + this.recalculate(); + this.updateDisabledStates(); + }; + + this.reset = function() { + queryInputElements(this.responseIdentifier).each(function() { + if (interaction.matched[this.value]) { + this.checked = true; + } + else { + this.checked = false; + } + }); + this.recalculate(); + this.updateDisabledStates(); + }; + + this.recalculate = function() { + this.matchCount = 0; + for (var key in this.gapChoiceMap) { + this.gapChoiceMap[key].matchCount = 0; + } + for (var key in this.gapMap) { + this.gapMap[key].matched = false; + this.gapMap[key].matchedGapChoice = null; + } + + queryInputElements(this.responseIdentifier).each(function() { + interaction.withCheckbox(this, function(inputElement, directedPair, gapChoice, gap) { + if (inputElement.checked) { + gapChoice.matchCount++; + gap.matched = true; + gap.matchedGapChoice = gapChoice; + interaction.matched[directedPair] = true; + } + }); + }); + + for (var key in this.gapMap) { + var gap = this.gapMap[key]; + var gapText; + if (gap.matched) { + gapText = gap.matchedGapChoice.text; + } + else { + gapText = gap.label; + } + gap.query.text(gapText); + } + }; + + this.updateDisabledStates = function() { + queryInputElements(this.responseIdentifier).each(function() { + interaction.withCheckbox(this, function(inputElement, directedPair, gapChoice, gap) { + if (inputElement.checked) { + inputElement.disabled = false; + } + else if (gap.matched || (gapChoice.matchMax!=0 && gapChoice.matchCount >= gapChoice.matchMax)) { + inputElement.disabled = true; + } + else { + inputElement.disabled = false; + } + }); + }); + }; + + this.checkMatch = function(inputElement) { + this.withCheckbox(inputElement, function(inputElement, directedPair, gapChoice, gap) { + if (inputElement.checked){ + if (gap.matched || (gapChoice.matchMax != 0 && gapChoice.matchMax <= gapChoice.matchCount)) { + inputElement.checked = false; + } + else { + gapChoice.matchCount++; + gap.matched = true; + gap.matchedGapChoice = gapChoice; + } + gap.query.text(gapChoice.text); + } + else{ + gapChoice.matchCount--; + gap.matched = false; + gap.matchedGapChoice = null; + gap.query.text(gap.label); + } + interaction.updateDisabledStates(responseIdentifier); + }); + }; + + registerResetCallback(function() { + interaction.reset(); + }); + }; + + /************************************************************/ + /* orderInteraction */ + + var OrderInteraction = function(responseIdentifier, initialSourceOrder, initialTargetOrder, minChoices, maxChoices) { + this.responseIdentifier = responseIdentifier; + this.initialSourceOrder = initialSourceOrder; + this.initialTargetOrder = initialTargetOrder; + this.minChoices = minChoices; + this.maxChoices = maxChoices; + this.containerQuery = $('#qtiworks_response_' + responseIdentifier); + this.targetBox = $('#qtiworks_response_' + responseIdentifier + ' div.target'); + this.sourceList = $('#qtiworks_response_' + responseIdentifier + ' div.source ul'); + this.targetList = $('#qtiworks_response_' + responseIdentifier + ' div.target ul'); + this.hiddenInputContainer = $('#qtiworks_response_' + responseIdentifier + ' div.hiddenInputContainer'); + var interaction = this; + + this.reset = function() { + /* Record items by their HTML ID */ + var itemsById = {}; + var sourceItems = this.sourceList.children('li'); + sourceItems.each(function() { + itemsById[this.id] = this; + }); + var targetItems = this.targetList.children('li'); + targetItems.each(function() { + itemsById[this.id] = this; + }); + + /* Detach items from the page */ + sourceItems.detach(); + targetItems.detach(); + + /* Then re-add them in the initial order */ + $.each(interaction.initialSourceOrder, function(index, responseIdentifier) { + var item = itemsById['qtiworks_response_' + responseIdentifier]; + interaction.sourceList.append(item); + }); + $.each(interaction.initialTargetOrder, function(index, responseIdentifier) { + var item = itemsById['qtiworks_response_' + responseIdentifier]; + interaction.targetList.append(item); + }); + }; + + this.syncHiddenFormFields = function() { + /* Store the current selected orders in the hidden inputs */ + interaction.hiddenInputContainer.empty(); + interaction.targetList.children('li').each(function(index) { + var choiceId = this.id.substring('qtiworks_response_'.length); // Trim leading 'qtiworks_response_' + var inputElement = $('<input type="hidden">'); + inputElement.attr('name', 'qtiworks_response_' + interaction.responseIdentifier); + inputElement.attr('value', choiceId); + interaction.hiddenInputContainer.append(inputElement); + }); + }; + + this.highlight = function(state) { + this.targetBox.toggleClass('highlight', state); + }; + + this.init = function() { + /* Add jQuery UI Sortable effect to sourceList */ + var listSelector = '#qtiworks_response_' + this.responseIdentifier + ' ul'; + this.sourceList.sortable({ + connectWith: listSelector + }); + this.sourceList.disableSelection(); + this.targetList.sortable({ + connectWith: listSelector + }); + this.targetList.disableSelection(); + + /* Register callback to reset things when requested */ + registerResetCallback(function() { + interaction.reset(); + }); + + /* Sync selection into hidden form fields on submit */ + registerSubmitCallback(function() { + var selectedCount = interaction.targetList.children('li').size(); + if (minChoices!=null && maxChoices!=null) { + if (selectedCount < minChoices || selectedCount > maxChoices) { + if (minChoices!=maxChoices) { + alert("You must select and order between " + minChoices + " and " + maxChoices + " items"); + } + else { + alert("You must select and order exactly " + minChoices + " item" + + (minChoices>1 ? "s" : "")); + } + interaction.highlight(true); + return false; + } + else { + interaction.highlight(false); + } + } + interaction.syncHiddenFormFields(); + return true; + }); + }; + }; + + /************************************************************/ + /* Interactions using Applets. + * (Recall that PositionObjectInteraction currently uses an applet for its stage, + * so this class needs to be able to associate a single applet with multiple interactions) + */ + + var AppletBasedInteractionContainer = function(containerId, responseIdentifiers) { + this.responseIdentifiers = responseIdentifiers; + this.divContainerQuery = $('#' + containerId); + this.appletQuery = this.divContainerQuery.find('object[type="application/x-java-applet"]'); + var interaction = this; + + this.reset = function() { + this.appletQuery.each(function() { + /* (Annoyingly, the reset() function in some of the applets is called reSet()!) */ + if (this.reset) { + this.reset(); + } + else if (this.reSet) { + this.reSet(); + } + interaction.setResponseData(); + }); + }; + + this.extractResponseData = function() { + this.appletQuery.each(function() { + for (i in interaction.responseIdentifiers) { + var responseIdentifier = interaction.responseIdentifiers[i]; + /* (NB: The following code portion includes JS->Java calls) */ + var valuesVector = this.getValues(responseIdentifier); + var values = []; + if (valuesVector!=null) { + var valuesEnum = valuesVector.elements(); + while (valuesEnum.hasMoreElements()) { + values.push(valuesEnum.nextElement()); + } + } + /* (Back to JS only now) */ + interaction.setResponseData(responseIdentifier, values); + } + }); + }; + + this.setResponseData = function(responseIdentifier, values) { + this.divContainerQuery.find('input').remove(); + for (var i in values) { + var inputElement = $('<input type="hidden">'); + inputElement.attr('name', 'qtiworks_response_' + responseIdentifier); + inputElement.attr('value', values[i]); + this.divContainerQuery.append(inputElement); + } + }; + + this.init = function() { + registerSubmitCallback(function() { + interaction.extractResponseData(); + return true; + }); + registerResetCallback(function() { + interaction.reset(); + }); + }; + }; + + /************************************************************/ + /* Public methods */ + + return { + maySubmit: function() { + var allowSubmit = true; + for (var i in submitCallbacks) { + allowSubmit = submitCallbacks[i](); + if (!allowSubmit) { + break; + } + } + return allowSubmit; + }, + + reset: function() { + for (var i in resetCallbacks) { + resetCallbacks[i](); + } + }, + + showInfoControlContent: function(inputElement) { + $(inputElement).next('div').show(); + inputElement.disabled = true; + return false; + }, + + registerSliderInteraction: function(responseIdentifier, configData) { + new SliderInteraction(responseIdentifier, configData).init(); + }, + + registerOrderInteraction: function(responseIdentifier, initialSourceOrder, initialTargetOrder, minChoices, maxChoices) { + new OrderInteraction(responseIdentifier, initialSourceOrder, initialTargetOrder, minChoices, maxChoices).init(); + }, + + registerMatchInteraction: function(responseIdentifier, maxAssociations, matchSet1, matchSet2) { + new MatchInteraction(responseIdentifier, maxAssociations, matchSet1, matchSet2).init(); + }, + + registerGapMatchInteraction: function(responseIdentifier, gapChoiceData, gapData) { + new GapMatchInteraction(responseIdentifier, gapChoiceData, gapData).init(); + }, + + registerAppletBasedInteractionContainer: function(containerId, responseIdentifiers) { + new AppletBasedInteractionContainer(containerId, responseIdentifiers).init(); + }, + + registerReadyCallback: function(callback) { + $(document).ready(function() { + if (typeof(MathJax) !== "undefined") { + MathJax.Hub.Queue(callback); + } + else { + callback(); + } + }); + }, + + validateInput: function(obj) { + var errorMessage = ''; + var value = obj.value; + for (var i=1; i<arguments.length; i++) { + switch (arguments[i]) { + case 'float': + if (!value.match(/^-?[\d\.]+$/)){ + errorMessage += 'This input must be a number!\n'; + } + break; + + case 'integer': + if (!value.match(/^-?\d+$/)){ + errorMessage += 'This input must be an integer!\n'; + } + break; + + case 'regex': + var regex = arguments[++i]; + if (!value.match(regex)) { + errorMessage += 'This input is not valid!\n'; + } + break; + } + } + if (errorMessage.length!=0) { + alert(errorMessage); + $(obj).addClass("badResponse"); + return false; + } + else { + $(obj).removeClass("badResponse"); + return true; + } + }, + + /* Used for <extendedTextInteraction/> */ + addNewTextBox: function(inputElement) { + var input = $(inputElement); + var newInput = input.clone(true); + input.removeAttr('onkeyup'); + newInput.attr('value', ''); + + var br = $("<br>"); + input.after(br); + br.after(newInput); + } + }; +})(); diff --git a/src/main/webapp/static/assessment/rendering/javascript/UpConversionAjaxController.js b/src/main/webapp/static/assessment/rendering/javascript/UpConversionAjaxController.js new file mode 100644 index 0000000000000000000000000000000000000000..a7f2c845ae69a62bdf3572a685b69e9e2db46bd4 --- /dev/null +++ b/src/main/webapp/static/assessment/rendering/javascript/UpConversionAjaxController.js @@ -0,0 +1,322 @@ +/* Controller for AJAX Up-Conversion / Semantic Enrichment functionality + * + * Requirements: + * + * jquery.js (at least version 1.5.0) + * + * Author: David McKain + * + * $Id$ + * + * Copyright (c) 2008-2011, The University of Edinburgh + * All Rights Reserved + */ + +/************************************************************/ + +var UpConversionAjaxController = (function() { + + var upConversionServiceUrl = null; /* Caller must fill in */ + var delay = 500; + + var STATUS_EMPTY = 0; + var STATUS_WAITING_CLIENT = 1; + var STATUS_WAITING_SERVER = 2; + var STATUS_SUCCESS = 3; + var STATUS_PARSE_ERROR = 4; + var STATUS_UPCONVERSION_FAILED = 5; + var STATUS_UNKNOWN_ERROR = 6; + var STATUS_AJAX_ERROR = 7; + + var EMPTY_INPUT = "(Empty Input)"; + + /************************************************************/ + + var UpConversionAjaxControl = function(_messageContainerId, _bracketedRenderingContainerId) { + this.messageContainerId = _messageContainerId; + this.bracketedRenderingContainerId = _bracketedRenderingContainerId; + this.pmathSemanticSourceContainerId = null; + this.pmathBracketedSourceContainerId = null; + this.cmathSourceContainerId = null; + this.maximaSourceContainerId = null; + var currentXhr = null; + var currentTimeoutId = null; + var thisControl = this; + + /* Schedules verification on the given data after a short delay + * that batches requests together for efficiency. + * + * Use null or blank input to signify "empty input". The UI will be updated accordingly. + */ + this._verifyLater = function(verifyInputData) { + if (upConversionServiceUrl==null) { + throw new Error("upConversionServiceUrl is null - no verification can be done"); + } + this._resetTimeout(); + if (verifyInputData!=null && verifyInputData.length>0) { + this._updateUpConversionContainer(STATUS_WAITING_SERVER); + currentTimeoutId = window.setTimeout(function() { + thisControl._callVerifier(verifyInputData); + currentTimeoutId = null; + }, delay); + } + else { + this._clearVerificationResult(); + } + }; + + this._clear = function() { + this._resetTimeout(); + this._clearVerificationResult(); + }; + + this._resetTimeout = function() { + if (currentTimeoutId!=null) { + window.clearTimeout(currentTimeoutId); + currentTimeoutId = null; + } + }; + + /* Calls up the AJAX verification service on the given data, causing a UI update once + * the results are returned. + * + * Use null input to signify "empty input". The UI will be updated instantly. + */ + this._callVerifier = function(verifyInputData) { + currentXhr = jQuery.ajax({ + type: 'POST', + url: upConversionServiceUrl, + dataType: 'json', + data: {input: verifyInputData }, + success: function(data, textStatus, jqXhr) { + if (currentXhr==jqXhr) { + currentXhr = null; + thisControl._showVerificationResult(data); + } + }, + error: function(jqXhr, textStatus, error) { + thisControl._updateUpConversionContainer(STATUS_AJAX_ERROR, error); + } + }); + }; + + this._showVerificationResult = function(jsonData) { + /* We consider "valid" to mean "getting as far as CMathML" here */ + var cmath = jsonData['cmath']; + if (cmath!=null) { + var bracketedMathML = jsonData['pmathBracketed']; + this._updateUpConversionContainer(STATUS_SUCCESS, bracketedMathML); + } + else if (jsonData['cmathFailures']!=null) { + this._updateUpConversionContainer(STATUS_UPCONVERSION_FAILED); + } + else if (jsonData['errors']!=null) { + var html = '<ul>'; + for (var i in jsonData['errors']) { + html += '<li>' + jsonData['errors'][i] + '</li>'; + } + html += '</ul>'; + this._updateUpConversionContainer(STATUS_PARSE_ERROR, null, html); + } + else { + this._updateUpConversionContainer(STATUS_UNKNOWN_ERROR); + } + + /* Maybe show various sources, if requested */ + if (this.pmathSemanticSourceContainerId!=null) { + this._showPreformatted(jQuery("#" + this.pmathSemanticSourceContainerId), + jsonData['pmathSemantic'] || 'Could not generate Semantic Presentation MathML'); + } + if (this.pmathBracketedSourceContainerId!=null) { + this._showPreformatted(jQuery("#" + this.pmathBracketedSourceContainerId), + jsonData['pmathBracketed'] || 'Could not generate Bracketed Presentation MathML'); + } + if (this.cmathSourceContainerId!=null) { + this._showPreformatted(jQuery("#" + this.cmathSourceContainerId), + cmath || 'Could not generate Content MathML'); + } + if (this.maximaSourceContainerId!=null) { + this._showPreformatted(jQuery("#" + this.maximaSourceContainerId), + jsonData['maxima'] || 'Could not generate Maxima syntax'); + } + }; + + this._clearVerificationResult = function() { + this._updateUpConversionContainer(STATUS_EMPTY); + if (this.pmathSemanticSourceContainerId!=null) { + this._showPreformatted(jQuery("#" + this.pmathSemanticSourceContainerId), EMPTY_INPUT); + } + if (this.pmathBracketedSourceContainerId!=null) { + this._showPreformatted(jQuery("#" + this.pmathBracketedSourceContainerId), EMPTY_INPUT); + } + if (this.cmathSourceContainerId!=null) { + this._showPreformatted(jQuery("#" + this.cmathSourceContainerId), EMPTY_INPUT); + } + if (this.maximaSourceContainerId!=null) { + this._showPreformatted(jQuery("#" + this.maximaSourceContainerId), EMPTY_INPUT); + } + }; + + this._updateUpConversionContainer = function(status, mathElementString, errorContent) { + var bracketedRenderingContainer = jQuery("#" + this.bracketedRenderingContainerId); + var messageContainer = jQuery("#" + this.messageContainerId); + /* Set up if not done already */ + if (messageContainer.children().size()==0) { + messageContainer.html("<div class='upConversionAjaxControlMessage'></div>" + + "<div class='upConversionAjaxControlError'></div>"); + bracketedRenderingContainer.attr('class', 'upConversionAjaxControlPreview'); + } + var statusContainer = messageContainer.children().first(); + var errorContainer = statusContainer.next(); + switch(status) { + case STATUS_EMPTY: + errorContainer.hide(); + bracketedRenderingContainer.hide(); + statusContainer.hide(); + statusContainer.attr('class', 'upConversionAjaxControlMessage'); + this._showMessage(statusContainer, '\xa0'); + break; + + case STATUS_WAITING_CLIENT: + case STATUS_WAITING_SERVER: + errorContainer.hide(); + bracketedRenderingContainer.hide(); + statusContainer.attr('class', 'upConversionAjaxControlMessage waiting'); + this._showMessage(statusContainer, 'Verifying your input...'); + statusContainer.show(); + break; + + case STATUS_SUCCESS: + errorContainer.hide(); + statusContainer.attr('class', 'upConversionAjaxControlMessage success'); + this._showMessage(statusContainer, 'I have interpreted your input as:'); + this._showMathML(bracketedRenderingContainer, mathElementString); + statusContainer.show(); + bracketedRenderingContainer.show(); + break; + + case STATUS_PARSE_ERROR: + bracketedRenderingContainer.hide(); + statusContainer.attr('class', 'upConversionAjaxControlMessage failure'); + this._showMessage(statusContainer, 'SnuggleTeX could not parse your input:'); + this._showMessage(errorContainer, errorContent); + statusContainer.show(); + errorContainer.show(); + break; + + case STATUS_UPCONVERSION_FAILED: + errorContainer.hide(); + bracketedRenderingContainer.hide(); + statusContainer.attr('class', 'upConversionAjaxControlMessage failure'); + this._showMessage(statusContainer, 'Sorry, I could not make sense of your input'); + this._showMessage(errorContainer, null); + statusContainer.show(); + break; + + case STATUS_UNKNOWN_ERROR: + errorContainer.hide(); + bracketedRenderingContainer.hide(); + statusContainer.attr('class', 'upConversionAjaxControlMessage error'); + this._showMessage(statusContainer, 'Unexpected error'); + this._showMessage(errorContainer, null); + statusContainer.show(); + break; + + case STATUS_AJAX_ERROR: + bracketedRenderingContainer.hide(); + statusContainer.attr('class', 'upConversionAjaxControlMessage error'); + this._showMessage(statusContainer, 'Communication error'); + this._showMessage(errorContainer, errorContent); + statusContainer.show(); + errorContainer.show(); + break; + } + }; + + this._showMessage = function(containerQuery, html) { + UpConversionAjaxController.replaceContainerContent(containerQuery, html || "\xa0"); + }; + + this._showMathML = function(containerQuery, mathmlString) { + UpConversionAjaxController.replaceContainerMathMLContent(containerQuery, mathmlString); + }; + + this._showPreformatted = function(containerQuery, text) { + UpConversionAjaxController.replaceContainerPreformattedText(containerQuery, text); + }; + }; + + UpConversionAjaxControl.prototype.setPMathSemanticSourceContainerId = function(id) { + this.pmathSemanticSourceContainerId = id; + }; + + UpConversionAjaxControl.prototype.setPMathBracketedSourceContainerId = function(id) { + this.pmathBracketedSourceContainerId = id; + }; + + UpConversionAjaxControl.prototype.setCMathSourceContainerId = function(id) { + this.cmathSourceContainerId = id; + }; + + UpConversionAjaxControl.prototype.setMaximaSourceContainerId = function(id) { + this.maximaSourceContainerId = id; + }; + + UpConversionAjaxControl.prototype.showVerificationResult = function(jsonData) { + this._showVerificationResult(jsonData); + }; + + UpConversionAjaxControl.prototype.verifyLater = function(verifyInputData) { + this._verifyLater(verifyInputData); + }; + + UpConversionAjaxControl.prototype.clear = function() { + this._clear(); + }; + + return { + replaceContainerContent: function(containerQuery, content) { + containerQuery.empty(); + if (content!=null) { + containerQuery.append(content); + } + }, + + replaceContainerPreformattedText: function(containerQuery, content) { + if (navigator.platform.substr(0,3)=='Win') { /* Convert to Windows line endings first */ + content = content.replace(/\n/g, "\r\n"); + } + containerQuery.text(content); + }, + + replaceContainerMathMLContent: function(containerQuery, mathmlString) { + containerQuery.each(function() { + var mathJax = MathJax.Hub.getAllJax(this.id); + if (mathJax.length==1) { + MathJax.Hub.Queue(["Text", mathJax[0], mathmlString]); + } + else { + throw new Error("Expected 1 MathJax element, but got " + mathJax.length); + } + }); + }, + + createUpConversionAjaxControl: function(messageContainerId, bracketedRenderingContainerId) { + if (messageContainerId==null) { + throw new Error("messageContainerId must not be null"); + } + if (bracketedRenderingContainerId==null) { + throw new Error("bracketedRenderingContainerId must not be null"); + } + return new UpConversionAjaxControl(messageContainerId, bracketedRenderingContainerId); + }, + + getUpConversionServiceUrl: function() { return upConversionServiceUrl; }, + setUpConversionServiceUrl: function(url) { upConversionServiceUrl = url; }, + + getDelay: function() { return delay; }, + setDelay: function(newDelay) { delay = newDelay; } + }; + +})(); + diff --git a/src/main/webapp/static/themes/light/modules/_icons.scss b/src/main/webapp/static/themes/light/modules/_icons.scss index 9258c7237c29140d9a1ddcf5f02b656ab7834e25..cec1f50392207e5487493da0281694b1f790849d 100644 --- a/src/main/webapp/static/themes/light/modules/_icons.scss +++ b/src/main/webapp/static/themes/light/modules/_icons.scss @@ -234,6 +234,7 @@ $fa-css-prefix: "o_icon" !default; .o_FileResource-SCORMCP_icon:before { content: $fa-var-archive; } .o_FileResource-SURVEY_icon:before { content: $fa-var-meh-o; } .o_FileResource-TEST_icon:before { content: $fa-var-pencil-square-o; } +.o_FileResource-IMSQTI21_icon:before { content: $fa-var-pencil-square-o; } .o_FileResource-WIKI_icon:before { content: $fa-var-globe; } .o_FileResource-SHAREDFOLDER_icon:before { content: $fa-var-folder-open-o; } .o_FileResource-GLOSSARY_icon:before { content: $fa-var-graduation-cap; }