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>&lt;textEntryInteraction&gt;</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, '&#x0a;')">
+                      <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, '&#x0a;')">
+                  <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>&#8290;<!--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>&#8290;<!--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>&#8290;<!--invisible times--></m:mo>
+    <m:msup>
+    <m:mi>e<!-- exponential e--></m:mi>
+    <m:mrow>
+     <m:mi>i<!-- imaginary i--></m:mi>
+     <m:mo>&#8290;<!--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>&#8290;<!--invisible times--></m:mo>
+    <m:msup>
+    <m:mi>e<!-- exponential e--></m:mi>
+    <m:mrow>
+     <m:mi>i<!-- imaginary i--></m:mi>
+     <m:mo>&#8290;<!--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>&#8289;<!--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>&#955;<!--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>&#8728;<!-- 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>&#8712;<!-- 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>&#160; if &#160;</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>&#160; 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>&#8970;<!-- lfloor--></m:mo>
+<xsl:apply-templates mode="c2p" select="*[2]"/>
+<m:mo>/</m:mo>
+<xsl:apply-templates mode="c2p" select="*[3]"/>
+<m:mo>&#8971;<!-- 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>&#8722;<!--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(*)&gt;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>&#8722;<!--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 &gt; 2"><m:mo>(</m:mo></xsl:if>
+  <xsl:for-each select="*[position()&gt;1]">
+   <xsl:if test="position() &gt; 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(.) &lt; 0)]]]">&#8722;<!--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(.) &lt;0)]]]">
+     <m:mrow>
+     <m:mn><xsl:value-of select="-(*[2])"/></m:mn>
+      <m:mo>&#8290;<!--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 &gt; 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 &gt; 3"><m:mo>(</m:mo></xsl:if>
+  <xsl:for-each select="*[position()&gt;1]">
+   <xsl:if test="position() &gt; 1">
+    <m:mo>
+    <xsl:choose>
+      <xsl:when test="self::m:cn">&#215;<!-- times --></xsl:when>
+      <xsl:otherwise>&#8290;<!--invisible times--></xsl:otherwise>
+    </xsl:choose>
+    </m:mo>
+   </xsl:if> 
+   <xsl:if test="position()&gt;= $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 &gt; 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()&gt;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()&gt;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>&#8743;<!-- 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>&#8744;<!-- 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>&#172;<!-- 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>&#8658;<!-- 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>&#8704;<!--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>&#8707;<!--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>&#8743;<!-- 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>&#8712;<!-- in --></m:mo>
+       <xsl:apply-templates mode="c2p" select="m:domainofapplication/*"/>
+       </m:mrow>
+       <m:mo>&#8743;<!-- 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>&#175;<!-- 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>&#8475;<!-- real --></m:mo>
+</xsl:template>
+
+<!-- 4.4.3.23 imaginary -->
+<xsl:template mode="c2p" match="m:imaginary|m:csymbol[.='imaginary']">
+ <m:mo>&#8465;<!-- 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>&#8970;<!-- lfloor--></m:mo>
+<xsl:apply-templates mode="c2p" select="*[2]"/>
+<m:mo>&#8971;<!-- 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>&#8968;<!-- lceil--></m:mo>
+<xsl:apply-templates mode="c2p" select="*[2]"/>
+<m:mo>&#8969;<!-- 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>&#8800;<!-- 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>&gt;</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>&lt;</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>&#8805;</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>&#8804;</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>&#8801;</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>&#8771;</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>&#8747;<!--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>&#8747;<!--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>&#8242;<!--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()&lt;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>&#8706;<!-- partial --></m:mo>
+	</xsl:when>
+	<xsl:otherwise>
+	  <m:msup><m:mo>&#8706;<!-- 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()&lt;last()"><m:mo>+</m:mo></xsl:if>
+		</xsl:for-each>
+		<xsl:if test="count(m:bvar[not(m:degree)])&gt;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>&#8706;<!-- 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>&#8706;<!-- 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>&#8289;<!--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>&#x21a6;<!-- 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>&#8289;<!--function application--></m:mo>
+<m:mrow>
+<m:mo>(</m:mo>
+<m:mfenced>
+<xsl:apply-templates mode="c2p" select="m:bvar/*"/>
+</m:mfenced>
+<m:mo>&#x21a6;<!-- 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>&#8711;<!-- 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>&#8289;<!--function application--></m:mo>
+<m:mrow>
+<m:mo>(</m:mo>
+<m:mfenced>
+<xsl:apply-templates mode="c2p" select="m:bvar/*"/>
+</m:mfenced>
+<m:mo>&#x21a6;<!-- 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>&#8746;<!-- 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>&#x22C3;</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>&#8745;<!-- 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>&#x22C2;</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>&#8712;<!-- 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>&#8713;<!-- 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>&#8838;<!-- 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>&#8834;<!-- 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>&#8840;<!-- 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>&#8836;<!-- 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>&#8726;<!-- 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>&#215;<!-- 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>&#8721;<!--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>&#8721;<!--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>&#8719;<!--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>&#8719;<!--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'">&#8600;<!--searrow--></xsl:when>
+	<xsl:when test="*[3]='below'">&#8599;<!--nearrow--></xsl:when>
+	<xsl:otherwise>&#8594;<!--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>&#8594;<!--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'">&#8600;<!--searrow--></xsl:when>
+   <xsl:when test="@type='below'">&#8599;<!--nearrow--></xsl:when>
+   <xsl:when test="@type='two-sided'">&#8594;<!--rightarrow--></xsl:when>
+   <xsl:otherwise>&#8594;<!--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]">&#8600;<!--searrow--></xsl:when>
+	<xsl:when test="*[1][self::below]">&#8599;<!--nearrow--></xsl:when>
+	<xsl:when test="*[1][self::two-sided]">&#8594;<!--rightarrow--></xsl:when>
+	<xsl:otherwise>&#8594;<!--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>&#8594;<!--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>&#8289;<!--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>&#8289;<!--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>&#9001;<!--langle--></m:mo>
+    <xsl:for-each select="*[position()&gt;1]">
+      <xsl:apply-templates mode="c2p" select="."/>
+      <xsl:if test="position() !=last()"><m:mo>,</m:mo></xsl:if>
+    </xsl:for-each>
+<m:mo>&#9002;<!--rangle--></m:mo>
+</m:mrow>
+</xsl:template>
+
+
+<!-- 4.4.9.2 sdef -->
+<xsl:template mode="c2p" match="m:sdev|m:csymbol[.='sdev']">
+<m:mo>&#963;<!--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>&#963;<!--sigma--></m:mo>
+ <m:mo>&#8289;<!--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>&#9001;<!--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>&#9002;<!--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>&#9001;<!--langle--></m:mo>
+    <m:msup>
+	  <xsl:apply-templates mode="c2p" select="*[4]"/>
+	  <xsl:apply-templates mode="c2p" select="*[2]"/>
+    </m:msup>
+    <m:mo>&#9002;<!--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>&#9001;<!--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>&#9002;<!--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>&#x2208;</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>&#8289;<!--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()&gt;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>&#x2297;</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>&#8709;<!-- emptyset --></m:mi>
+</xsl:template>
+
+
+<!-- 4.4.12.13 pi -->
+<xsl:template mode="c2p" match="m:pi|m:csymbol[.='pi']">
+  <m:mi>&#960;<!-- pi --></m:mi>
+</xsl:template>
+
+<!-- 4.4.12.14 eulergamma -->
+<xsl:template mode="c2p" match="m:eulergamma|m:csymbol[.='gamma']">
+  <m:mi>&#947;<!-- gamma --></m:mi>
+</xsl:template>
+
+<!-- 4.4.12.15 infinity -->
+<xsl:template mode="c2p" match="m:infinity|m:csymbol[.='infinity']">
+  <m:mi>&#8734;<!-- 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 &lt; $p"><m:mo>(</m:mo></xsl:if>
+  <xsl:for-each select="*[not(self::m:domainofapplication)][position()&gt;1]">
+   <xsl:if test="position() &gt; 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 &lt; $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 &lt; $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 &lt; $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>&#8712;<!-- 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(.,
+			 '&#9;&#10;&#13;&#32;',
+			 '&#160;&#160;&#160;&#160;')"/>
+ </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 &gt; 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 &gt; 0">
+                at least <xsl:value-of select="@minChoices"/>
+                <xsl:if test="@maxChoices &gt; 0"> and </xsl:if>
+              </xsl:if>
+              <xsl:if test="@maxChoices &gt; 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 &gt; 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 &gt; 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 &gt; 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 &gt; 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 &gt; 0">
+            at least <xsl:value-of select="@minChoices"/>
+            <xsl:if test="@maxChoices &gt; 0"> and </xsl:if>
+          </xsl:if>
+          <xsl:if test="@maxChoices &gt; 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 &lt;assessmentResult&gt; 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">&#8743;</xsl:param>   
+   <xsl:param name="ApplyFunction">&#8289;</xsl:param>   
+   <xsl:param name="Backslash">&#8726;</xsl:param>   
+   <xsl:param name="DoubleRightArrow">&#8658;</xsl:param>   
+   <xsl:param name="DownArrow">&#8595;</xsl:param>   
+   <xsl:param name="ee">&#8519;</xsl:param>   
+   <xsl:param name="empty">&#8709;</xsl:param>   
+   <xsl:param name="equiv">&#8801;</xsl:param>   
+   <xsl:param name="Exists">&#8707;</xsl:param>   
+   <xsl:param name="ExponentialE">&#8519;</xsl:param>   
+   <xsl:param name="ForAll">&#8704;</xsl:param>   
+   <xsl:param name="gamma">&#947;</xsl:param>   
+   <xsl:param name="GreaterEqual">&#8805;</xsl:param>   
+   <xsl:param name="gt">&gt;</xsl:param>   
+   <xsl:param name="ImaginaryI">&#8520;</xsl:param>   
+   <xsl:param name="infin">&#8734;</xsl:param>   
+   <xsl:param name="Integral">&#8747;</xsl:param>   
+   <xsl:param name="Intersection">&#8898;</xsl:param>   
+   <xsl:param name="InvisibleComma">&#8291;</xsl:param>   
+   <xsl:param name="InvisibleTimes">&#8290;</xsl:param>   
+   <xsl:param name="isin">&#8712;</xsl:param>   
+   <xsl:param name="lambda">&#955;</xsl:param>   
+   <xsl:param name="lang">&#9001;</xsl:param>   
+   <xsl:param name="LeftCeiling">&#8968;</xsl:param>   
+   <xsl:param name="LeftFloor">&#8970;</xsl:param>   
+   <xsl:param name="LessEqual">&#8806;</xsl:param>   
+   <xsl:param name="lt">&lt;</xsl:param>   
+   <xsl:param name="Not">&#172;</xsl:param>   
+   <xsl:param name="NotEqual">&#8800;</xsl:param>   
+   <xsl:param name="notin">&#8713;</xsl:param>   
+   <xsl:param name="NotSubset">&#8834;&#8402;</xsl:param>   
+   <xsl:param name="NotSubsetEqual">&#8840;</xsl:param>   
+   <xsl:param name="Or">&#8744;</xsl:param>   
+   <xsl:param name="ovbar">&#9021;</xsl:param>   
+   <xsl:param name="PartialD">&#8706;</xsl:param>   
+   <xsl:param name="pi">&#960;</xsl:param>   
+   <xsl:param name="Product">&#8719;</xsl:param>   
+   <xsl:param name="rang">&#9002;</xsl:param>   
+   <xsl:param name="RightArrow">&#8594;</xsl:param>   
+   <xsl:param name="RightFloor">&#8971;</xsl:param>   
+   <xsl:param name="RightCeiling">&#8969;</xsl:param>   
+   <xsl:param name="sigma">&#963;</xsl:param>   
+   <xsl:param name="SmallCircle">&#8728;</xsl:param>   
+   <xsl:param name="Subset">&#8912;</xsl:param>   
+   <xsl:param name="SubsetEqual">&#8838;</xsl:param>   
+   <xsl:param name="Sum">&#8721;</xsl:param>   
+   <xsl:param name="times">&#215;</xsl:param>   
+   <xsl:param name="Union">&#8899;</xsl:param>   
+   <xsl:param name="UpArrow">&#8593;</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(*)&gt;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(*)&gt;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(*)&gt;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(*)&gt;=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(*)&gt;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(*)&gt;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(*)&gt;=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(*)&gt;=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(*)&gt;=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(*)&gt;=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(*)&gt;=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(*)&gt;=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(*)&gt;=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(*)&gt;=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(*)&gt;=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">&#8476;</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">&#8465;</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(*)&gt;=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(*)&gt;=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(*)&gt;=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(*)&gt;=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(*)&gt;=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(*)&gt;=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">&#8776;</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">&#8776;</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(*)&lt;=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">&#8711;</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(*)&gt;=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(*)&gt;=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(*)&gt;=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(*)&gt;=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(*)&gt;=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(*)&gt;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">&#8484;</xsl:text>     
+      </mi>     
+   </xsl:template>    
+   <xsl:template match="m:reals">     
+      <mi>       
+         <xsl:text disable-output-escaping="yes">&#8477;</xsl:text>     
+      </mi>     
+   </xsl:template>    
+   <xsl:template match="m:rationals">     
+      <mi>       
+         <xsl:text disable-output-escaping="yes">&#8474;</xsl:text>     
+      </mi>     
+   </xsl:template>    
+   <xsl:template match="m:naturalnumbers">     
+      <mi>       
+         <xsl:text disable-output-escaping="yes">&#8469;</xsl:text>     
+      </mi>     
+   </xsl:template>    
+   <xsl:template match="m:complexes">     
+      <mi>       
+         <xsl:text disable-output-escaping="yes">&#8450;</xsl:text>     
+      </mi>     
+   </xsl:template>    
+   <xsl:template match="m:primes">     
+      <mi>       
+         <xsl:text disable-output-escaping="yes">&#8473;</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">
+          &lt;printedVariable&gt; 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>&#x0a;</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>&#x0a;</xsl:text>
+            <script src="{$mathJaxUrl}">
+              <xsl:if test="not($serializationMethod='XHTML5_MATHJAX')">
+                <xsl:attribute name="type" select="'text/javascript'"/>
+              </xsl:if>
+            </script>
+            <xsl:text>&#x0a;</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('&#xa0;(Instance&#xa0;', $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 &lt;assessmentResult&gt; 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">&#x21b3;</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) &gt; 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, '[&#x0d;&#x0a;]', ''), '('')', '\\$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; }