diff --git a/src/qtism/runtime/expressions/operators/Utils.php b/src/qtism/runtime/expressions/operators/Utils.php index ec67c665d..26c88f521 100644 --- a/src/qtism/runtime/expressions/operators/Utils.php +++ b/src/qtism/runtime/expressions/operators/Utils.php @@ -333,59 +333,26 @@ public static function lastPregErrorMessage() * @param string $pattern * @return string */ - public static function prepareXsdPatternForPcre($pattern): string - { - // XML schema always implicitly anchors the entire regular expression - // Neither caret (^) nor dollar ($) sign have special meaning so they are - // considered as normal characters. - // see http://www.regular-expressions.info/xml.html - $pattern = self::removeCaretFromBegging($pattern); - $pattern = self::removeDollarFromEnd($pattern); - $pattern = self::escapeSymbols($pattern, ['$', '^']); - $pattern = self::withStartOfStringToken($pattern); - $pattern = self::withEndOfStringToken($pattern); - $pattern = self::pregAddDelimiter($pattern); - - // XSD regexp always case-sensitive (nothing to do), dot matches white-spaces (use PCRE_DOTALL). - $pattern .= 's'; - - return $pattern; - } - - private static function removeCaretFromBegging(string $pattern): string - { - if ($pattern[0] === '^' && ($pattern[1] === '[' || $pattern[1] === '(' || $pattern[1] === '{')) { - $pattern = ltrim($pattern, '^'); - } - - return $pattern; - } - - private static function removeDollarFromEnd(string $pattern): string - { - $length = strlen($pattern) - 1; - if ($pattern[$length] === '$' && ($pattern[$length - 1] === ']' || $pattern[$length - 1] === ')' || $pattern[$length - 1] === '}' || $pattern[$length - 1] === '+')) { - $pattern = rtrim($pattern, '$'); - } - - return $pattern; - } - - private static function withStartOfStringToken(string $pattern): string - { - if (substr($pattern, 0, 1) !== '^') { - $pattern = '^'.$pattern; - } - - return $pattern; - } - - private static function withEndOfStringToken(string $pattern): string - { - if ($pattern[strlen($pattern) - 1] !== '$') { - $pattern = $pattern.'$'; - } - - return $pattern; - } + public static function prepareXsdPatternForPcre(string $pattern): string + { + // XML schema always implicitly anchors the entire regular expression + // Neither caret (^) nor dollar ($) sign have special meaning so they are + // considered as normal characters. + // see http://www.regular-expressions.info/xml.html + $pattern = self::withoutStringAnchors($pattern); + $pattern = self::escapeSymbols($pattern, ['$', '^']); + $pattern = self::pregAddDelimiter('^' . $pattern . '$'); + + // XSD regexp always case-sensitive (nothing to do), dot matches white-spaces (use PCRE_DOTALL). + $pattern .= 's'; + + return $pattern; + } + + private static function withoutStringAnchors(string $pattern): string + { + $pattern = ltrim($pattern, '^'); + + return rtrim($pattern, '$'); + } } diff --git a/test/qtismtest/runtime/expressions/operators/OperatorsUtilsTest.php b/test/qtismtest/runtime/expressions/operators/OperatorsUtilsTest.php index 5ff5a8a8f..ad4b6e546 100644 --- a/test/qtismtest/runtime/expressions/operators/OperatorsUtilsTest.php +++ b/test/qtismtest/runtime/expressions/operators/OperatorsUtilsTest.php @@ -353,15 +353,20 @@ public function lastPregErrorMessageProvider() public function patternForPcreProvider(): array { return [ - 'max 5 chars string pattern' => ['[\s\S]{0,5}', '/^[\s\S]{0,5}$/s'], - 'max 5 chars string pattern with caret and dollar' => ['^[\s\S]{0,5}$', '/^[\s\S]{0,5}$/s'], - 'only digits pattern' => ['[0,1]+', '/^[0,1]+$/s'], - 'only digits pattern v2' => ['^[0,1]+$', '/^[0,1]+$/s'], - 'max 5 words pattern' => [ + 'max chars string pattern without anchors' => ['[\s\S]{0,5}', '/^[\s\S]{0,5}$/s'], + 'max chars string pattern with anchors' => ['^[\s\S]{0,5}$', '/^[\s\S]{0,5}$/s'], + 'digits only pattern' => ['[0,1]+', '/^[0,1]+$/s'], + 'digits only pattern with anchors' => ['^[0,1]+$', '/^[0,1]+$/s'], + 'string prefix without anchors' => ['test(.*)', '/^test(.*)$/s'], + 'string prefix with anchors' => ['^test(.*)$', '/^test(.*)$/s'], + 'max words pattern without anchors' => [ '(?:(?:[^\s\:\!\?\;\…\€]+)[\s\:\!\?\;\…\€]*){0,5}', '/^(?:(?:[^\s\:\!\?\;\…\€]+)[\s\:\!\?\;\…\€]*){0,5}$/s', ], - ['10$ are 10$', '/^10\\$ are 10\\$/s'], + 'max words pattern with anchors' => [ + '^(?:(?:[^\s\:\!\?\;\…\€]+)[\s\:\!\?\;\…\€]*){0,5}$', + '/^(?:(?:[^\s\:\!\?\;\…\€]+)[\s\:\!\?\;\…\€]*){0,5}$/s', + ], ]; } }