From 1cd8c3bb4fc484f4d7c0d28e574d5d694dfcae74 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Fri, 26 Feb 2016 11:02:40 +0100
Subject: [PATCH] OO-1593: fix annoying encoding and recurrent namespaces
 issues

---
 .../richText/RichTextConfiguration.java       |  5 +-
 .../model/xml/AssessmentHtmlBuilder.java      | 92 +++++++++++++++----
 .../model/xml/AssessmentItemFactory.java      | 17 ++--
 .../FIBAssessmentItemBuilder.java             |  2 +
 4 files changed, 89 insertions(+), 27 deletions(-)

diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextConfiguration.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextConfiguration.java
index 7e8c299e2aa..7db5be2da20 100644
--- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextConfiguration.java
+++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextConfiguration.java
@@ -98,8 +98,7 @@ public class RichTextConfiguration implements Disposable {
 	private static final String TABFOCUS_SETTINGS_PREV_NEXT = ":prev,:next";
 	// Valid elements
 	private static final String EXTENDED_VALID_ELEMENTS = "extended_valid_elements";
-	private static final String EXTENDED_VALID_ELEMENTS_VALUE_FULL = "script[src|type|defer],form[*],input[*],a[*],p[*],#comment[*],img[*],iframe[*],map[*],area[*],textEntryInteraction[*]";
-	private static final String CUSTOM_ELEMENTS = "textEntryInteraction";
+	private static final String EXTENDED_VALID_ELEMENTS_VALUE_FULL = "script[src|type|defer],form[*],input[*],a[*],p[*],#comment[*],img[*],iframe[*],map[*],area[*],textentryinteraction[*]";
 	private static final String INVALID_ELEMENTS = "invalid_elements";
 	private static final String INVALID_ELEMENTS_FORM_MINIMALISTIC_VALUE_UNSAVE = "iframe,script,@[on*],object,embed";
 	private static final String INVALID_ELEMENTS_FORM_SIMPLE_VALUE_UNSAVE = "iframe,script,@[on*],object,embed";
@@ -654,7 +653,7 @@ public class RichTextConfiguration implements Disposable {
 	
 	public void enableQTITools() {
 		tinyConfig = tinyConfig.enableQTITools();
-		setQuotedConfigValue("custom_elements", "~textentryinteraction,~textEntryInteraction");
+		setQuotedConfigValue("custom_elements", "~textentryinteraction");
 		setQuotedConfigValue("extended_valid_elements", "textentryinteraction[*]");
 	}
 
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentHtmlBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentHtmlBuilder.java
index 5c12f1f1425..2687c2e9aa0 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentHtmlBuilder.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentHtmlBuilder.java
@@ -33,9 +33,9 @@ import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.filter.FilterFactory;
+import org.w3c.dom.Attr;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.xml.sax.Attributes;
 import org.xml.sax.InputSource;
@@ -117,25 +117,37 @@ public class AssessmentHtmlBuilder {
 			} else {
 				htmlFragment = "<p>" + htmlFragment + "</p>";
 			}
-			//wrap around <html> to have a root element
+			//wrap around <html> to have a root element for neko
 			Document document = filter("<html>" + htmlFragment + "</html>");
 			Element docElement = document.getDocumentElement();
-			Node pEl = docElement.getFirstChild();
-			NamedNodeMap attrs = pEl.getAttributes();
-			for(int i=0; i<attrs.getLength(); i++) {
-				Node attr = attrs.item(i);
-				System.out.println(attr);
-			}
-			
+			cleanUpNamespaces(docElement);
 			parent.getNodeGroups().load(docElement, new HTMLLoadingContext());
 		}
 	}
+	
+	private void cleanUpNamespaces(Element element) {
+		Attr xsiattr = element.getAttributeNode("xmlns:xsi");
+		if(xsiattr != null && "http://www.w3.org/2001/XMLSchema-instance".equals(xsiattr.getValue())) {
+			element.removeAttribute("xmlns:xsi");
+		}
+		Attr attr = element.getAttributeNode("xmlns");
+		if(attr != null && "http://www.imsglobal.org/xsd/imsqti_v2p1".equals(attr.getValue())) {
+			element.removeAttribute("xmlns");
+		}
+		
+		for(Node child=element.getFirstChild(); child != null; child = child.getNextSibling()) {
+			if(child instanceof Element) {
+				cleanUpNamespaces((Element)child);
+			}
+		}
+	}
 
 	private Document filter(String content) {
 		try {
 			SAXParser parser = new SAXParser();
 			parser.setProperty("http://cyberneko.org/html/properties/names/elems", "lower");
 			parser.setFeature("http://cyberneko.org/html/features/balance-tags/document-fragment", true);
+			parser.setProperty("http://cyberneko.org/html/properties/default-encoding", "UTF-8");
 			
 			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 			DocumentBuilder builder = factory.newDocumentBuilder();
@@ -156,6 +168,16 @@ public class AssessmentHtmlBuilder {
 		}
 	}
 	
+	/**
+	 * Convert:<br>
+	 * <ul>
+	 * 		<li>textentryinteraction -> camel cased textEntryInteraction</li>
+	 * </ul>
+	 * 
+	 * Initial date: 26.02.2016<br>
+	 * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+	 *
+	 */
 	private static class HtmlToDomBuilderHandler extends SimpleDomBuilderHandler {
 		
 		public HtmlToDomBuilderHandler(Document document) {
@@ -170,9 +192,27 @@ public class AssessmentHtmlBuilder {
 			}
 			super.startElement(uri, localName, qName, attributes);
 		}
+
+		@Override
+		public void endElement(String uri, String localName, String qName) {
+			if("textentryinteraction".equals(localName)) {
+				localName = qName = "textEntryInteraction";
+			}
+			super.endElement(uri, localName, qName);
+		}
 	}
 	
-
+	/**
+	 * Convert:<br>
+	 * <ul>
+	 * 		<li>responseidentifier -> camel cased responseIdentifier</li>
+	 * 		<li>and other attributes of textEntryInteraction</li>
+	 * </ul>
+	 * 
+	 * Initial date: 26.02.2016<br>
+	 * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+	 *
+	 */
 	private static class AttributesDelegate implements Attributes {
 		
 		private final Attributes attributes;
@@ -181,10 +221,12 @@ public class AssessmentHtmlBuilder {
 			this.attributes = attributes;
 		}
 
+		@Override
 		public int getLength() {
 			return attributes.getLength();
 		}
 
+		@Override
 		public String getURI(int index) {
 			return attributes.getURI(index);
 		}
@@ -192,48 +234,64 @@ public class AssessmentHtmlBuilder {
 		@Override
 		public String getLocalName(int index) {
 			String localName = attributes.getLocalName(index);
-			if("responseidentifier".equals(localName)) {
-				localName = "responseIdentifier";
-			}
-			return localName;
+			return translateAttributeName(localName);
 		}
 
+		@Override
 		public String getQName(int index) {
 			String qName = attributes.getQName(index);
-			if("responseidentifier".equals(qName)) {
-				qName = "responseIdentifier";
+			return translateAttributeName(qName);
+		}
+		
+		private final String translateAttributeName(String attrName) {
+			if(attrName != null) {
+				switch(attrName) {
+					case "responseidentifier": return "responseIdentifier";
+					case "placeholdertext": return "placeholderText";
+					case "expectedlength": return "expectedLength";
+					case "patternmask": return "patternMask";
+					default: return attrName;
+				}
 			}
-			return qName;
+			return attrName;
 		}
 
+		@Override
 		public String getType(int index) {
 			return attributes.getType(index);
 		}
 
+		@Override
 		public String getValue(int index) {
 			return attributes.getValue(index);
 		}
 
+		@Override
 		public int getIndex(String uri, String localName) {
 			return attributes.getIndex(uri, localName);
 		}
 
+		@Override
 		public int getIndex(String qName) {
 			return attributes.getIndex(qName.toLowerCase());
 		}
 
+		@Override
 		public String getType(String uri, String localName) {
 			return attributes.getType(uri, localName);
 		}
 
+		@Override
 		public String getType(String qName) {
 			return attributes.getType(qName);
 		}
 
+		@Override
 		public String getValue(String uri, String localName) {
 			return attributes.getValue(uri, localName);
 		}
 
+		@Override
 		public String getValue(String qName) {
 			return attributes.getValue(qName);
 		}
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemFactory.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemFactory.java
index a94d869f4c2..55bf0ceb1f2 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemFactory.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemFactory.java
@@ -29,6 +29,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.olat.core.helpers.Settings;
+import org.olat.core.util.StringHelper;
 import org.olat.ims.qti21.QTI21Constants;
 import org.olat.ims.qti21.model.IdentifierGenerator;
 import org.olat.ims.qti21.model.QTI21QuestionType;
@@ -180,7 +181,7 @@ public class AssessmentItemFactory {
 	}
 	
 	public static ResponseDeclaration createTextEntryResponseDeclaration(AssessmentItem assessmentItem,
-			Identifier declarationId, String response, Double maxScore, boolean caseSensitive,
+			Identifier declarationId, String response, Double score, boolean caseSensitive,
 			List<TextEntryAlternative> alternatives) {
 		ResponseDeclaration responseDeclaration = new ResponseDeclaration(assessmentItem);
 		responseDeclaration.setIdentifier(declarationId);
@@ -200,7 +201,7 @@ public class AssessmentItemFactory {
 		{//map correct response
 			MapEntry mapEntry = new MapEntry(mapping);
 			mapEntry.setMapKey(new StringValue(response));
-			mapEntry.setMappedValue(maxScore);
+			mapEntry.setMappedValue(score);
 			mapEntry.setCaseSensitive(new Boolean(caseSensitive));
 			mapping.getMapEntries().add(mapEntry);
 		}
@@ -208,11 +209,13 @@ public class AssessmentItemFactory {
 		//map alternatives
 		if(alternatives != null && alternatives.size() > 0) {
 			for(TextEntryAlternative alternative:alternatives) {
-				MapEntry mapEntry = new MapEntry(mapping);
-				mapEntry.setMapKey(new StringValue(alternative.getAlternative()));
-				mapEntry.setMappedValue(maxScore);
-				mapEntry.setCaseSensitive(new Boolean(caseSensitive));
-				mapping.getMapEntries().add(mapEntry);
+				if(StringHelper.containsNonWhitespace(alternative.getAlternative())) {
+					MapEntry mapEntry = new MapEntry(mapping);
+					mapEntry.setMapKey(new StringValue(alternative.getAlternative()));
+					mapEntry.setMappedValue(score);
+					mapEntry.setCaseSensitive(new Boolean(caseSensitive));
+					mapping.getMapEntries().add(mapEntry);
+				}
 			}
 		}
 		
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java
index 42ea069a993..43f9a47cd59 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java
@@ -194,6 +194,8 @@ public class FIBAssessmentItemBuilder extends AssessmentItemBuilder {
 									alternative.setScore(mapEntry.getMappedValue());
 									alternatives.add(alternative);
 									mappedScore += mapEntry.getMappedValue();
+								} else if(alt.equals(solution)) {
+									textEntry.setScore(mapEntry.getMappedValue());
 								}
 							}
 							
-- 
GitLab