diff --git a/src/qtism/data/storage/xml/Utils.php b/src/qtism/data/storage/xml/Utils.php index 8930aa811..87bbfca84 100644 --- a/src/qtism/data/storage/xml/Utils.php +++ b/src/qtism/data/storage/xml/Utils.php @@ -1,5 +1,7 @@ = 0x20 && $char <= 0xDF77 + || $char >= 0xE000 && $char <= 0xFFFD + || $char >= 0x10000 && $char <= 0x10FFFF; + } + + public static function xmlSpecialChars(string $value): string + { + $result = ''; + + $last = 0; + $length = strlen($value); + $i = 0; + + while ($i < $length) { + $r = mb_substr(substr($value, $i), 0, 1); + $width = strlen($r); + $i += $width; + switch ($r) { + case '"': + $esc = '"'; + break; + case "'": + $esc = '''; + break; + case '&': + $esc = '&'; + break; + case '<': + $esc = '<'; + break; + case '>': + $esc = '>'; + break; + case "\t": + $esc = ' '; + break; + case "\n": + $esc = ' '; + break; + case "\r": + $esc = ' '; + break; + default: + if (!self::isInCharacterRange(mb_ord($r)) || (mb_ord($r) === 0xFFFD && $width === 1)) { + $esc = "\u{FFFD}"; + break; + } + continue 2; + } + $result .= substr($value, $last, $i - $last - $width) . $esc; + $last = $i; + } + return $result . substr($value, $last); + } + /** * Get the child elements of a given element by tag name. This method does * not behave like DOMElement::getElementsByTagName. It only returns the direct diff --git a/test/qtismtest/data/storage/xml/XmlUtilsTest.php b/test/qtismtest/data/storage/xml/XmlUtilsTest.php index 3e81f84cf..e0d891698 100644 --- a/test/qtismtest/data/storage/xml/XmlUtilsTest.php +++ b/test/qtismtest/data/storage/xml/XmlUtilsTest.php @@ -1,5 +1,7 @@ 'http://www.imsglobal.org/xsd/imsqtiv2p2_html5_v1p0'], + ['qh5' => 'http://www.imsglobal.org/xsd/imsqtiv2p2_html5_v1p0'], Utils::findExternalNamespaces($xml) ); } + public function testValueAsStringReplaceSpecialSymbols(): void + { + $this->assertEquals("160\u{FFFD}", Utils::valueAsString("160\u{0008}")); + } + + public function testProcessSpecialCharsetWithoutError(): void + { + $xml = (' + + + + + + 1 + + + + + PT22S + + + + completed + + + 0 + + + 1 + + + + %s + + + + +'); + $this->assertNotNull(Utils::findExternalNamespaces(sprintf($xml, Utils::valueAsString("160\u{0008}")))); + } + public function testremoveAllButFirstOccurrence(): void { $subject = 'abc 12 abc 345abc678abc';