Java RegEx matcher разбивает символы вне BMP - PullRequest
0 голосов
/ 23 мая 2019

В настоящее время я пишу класс util для sanitize input, который сохраняется в xml-документе.Санитарная обработка для нас означает, что все недопустимые символы (https://en.wikipedia.org/wiki/Valid_characters_in_XML#XML_1.0) просто удалены из строки.

Я попытался сделать это, просто используя некоторое регулярное выражение, которое заменяет все недопустимые символы пустой строкой,но для символов юникода вне BMP это, кажется, как-то нарушает кодировку, оставляя меня с этими ? символами. Также не имеет значения, какой способ замены использовать регулярное выражение (String#replaceAll(String, String), Pattern#compile(String), org.apache.commons.lang3.RegExUtil#removeAll(String, String))

Вот пример реализации с тестом (в Spock), который показывает проблему: XmlStringUtil.java

package com.example.util;

import lombok.NonNull;

import java.util.regex.Pattern;

public class XmlStringUtil {

    private static final Pattern XML_10_PATTERN = Pattern.compile(
        "[^\\u0009\\u000A\\u000D\\u0020-\\uD7FF\\uE000-\\uFFFD\\x{10000}-\\x{10FFFF}]"
    );

    public static String sanitizeXml10(@NonNull String text) {
        return XML_10_PATTERN.matcher(text).replaceAll("");
    }

}

XmlStringUtilSpec.groovy

package com.example.util

import spock.lang.Specification

class XmlStringUtilSpec extends Specification {

    def 'sanitize string values for xml version 1.0'() {
        when: 'a string is sanitized'
            def sanitizedString = XmlStringUtil.sanitizeXml10 inputString

        then: 'the returned sanitized string matches the expected one'
            sanitizedString == expectedSanitizedString

        where:
            inputString                                | expectedSanitizedString
            ''                                         | ''
            '\b'                                       | ''
            '\u0001'                                   | ''
            'Hello World!\0'                           | 'Hello World!'
            'text with emoji \uD83E\uDDD1\uD83C\uDFFB' | 'text with emoji \uD83E\uDDD1\uD83C\uDFFB'
    }

}

Теперь у меня есть решение, в котором я перестраиваю всю строку из отдельных кодовых точек, но это не похоже на правильное решение.

Заранее спасибо!

Ответы [ 2 ]

1 голос
/ 24 мая 2019

После некоторого чтения и экспериментов, небольшое изменение в Regex (замена \x{..} на суррогаты \u...\u... работает:

private static final Pattern XML_10_PATTERN = Pattern.compile(
        "[^\\u0009\\u000A\\u000D\\u0020-\\uD7FF\\uE000-\\uFFFD\uD800\uDC00-\uDBFF\uDFFF]"
    );

Проверка:

sanitizeXml10("\uD83E\uDDD1\uD83C\uDFFB").codePoints().mapToObj(Integer::toHexString).forEach(System.out::println);

Результаты в

1f9d1
1f3fb
1 голос
/ 23 мая 2019

Решением без регулярного выражения может быть отфильтрованный поток кодовых точек:

public static String sanitize_xml_10(String input) {
    return input.codePoints()
            .filter(Test::allowedXml10)
            .collect(StringBuilder::new,StringBuilder::appendCodePoint, StringBuilder::append)
            .toString();
}

private static boolean allowedXml10(int codepoint) {
    if(0x0009==codepoint) return true;
    if(0x000A==codepoint) return true;
    if(0x000D==codepoint) return true;
    if(0x0020<=codepoint && codepoint<=0xD7FF) return true;
    if(0xE000<=codepoint && codepoint<=0xFFFD) return true;
    if(0x10000<=codepoint && codepoint<=0x10FFFF) return true;
    return false;
}

...