给定字符串中的混合重音字符和普通字符在搜索时在 java 中不起作用

Posted

技术标签:

【中文标题】给定字符串中的混合重音字符和普通字符在搜索时在 java 中不起作用【英文标题】:Given mixed accented and normal characters in string not working in java when searching 【发布时间】:2019-03-21 00:07:40 【问题描述】:
String text = "Cámélan discovered ônte red aleŕt \n Como se extingue la deuda";

如果我给出输入 Ca,它应该从给定的字符串 Cá 中突出显示,但它没有突出显示。

以下是我尝试过的。

 Pattern mPattern; 
  String filterTerm; //this is the input which I give from input filter. Say for eg: Ca
   String regex = createFilterRegex(filterTerm);
        mPattern = Pattern.compile(regex);

 private String createFilterRegex(String filterTerm) 
        filterTerm = Normalizer.normalize(filterTerm, Normalizer.Form.NFD);
       filterTerm = filterTerm.replaceAll("[\\pInCombiningDiacriticalMarks]", "");
        return filterTerm;
    

public Pattern getPattern() 
        return mPattern;
    

在另一个班级,

private SpannableStringBuilder createHighlightedString(String nodeText, int highlightColor)  //nodeText is the entire list displaying. 
        SpannableStringBuilder returnValue = new SpannableStringBuilder(nodeText);
        String lowercaseNodeText = nodeText;
        Matcher matcher = mFilter.getPattern().matcher((createFilterRegex(lowercaseNodeText)));
        while (matcher.find()) 
            returnValue.setSpan(new ForegroundColorSpan(highlightColor), matcher.start(0),
                    matcher.end(0), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
        

        return returnValue;
    

viewHolder.mTextView.setText(createHighlightedString((node.getText()), mHighlightColor));

但是我得到的输出是,

如果我单独输入单个字母 o,它会突出显示,但如果我传递两个以上的字母,例如:Ca,它不会突出显示和显示。我无法弄清楚我在做什么错误。

但是,如果您查看 WhatsApp。它已经实现了。

我输入了 Co,它可以识别和突出句子中的重音字符。

【问题讨论】:

你尝试过我的解决方案@Star 吗? @PradyumanDixit 是的。但还是不行。 @Star 有什么问题? @PradyumanDixit 问题是当我在内容中搜索为 Ca 时,它不接受这个 Cá。只有当我搜索 Cá 时它才会接受。 您是否使用了我在答案中给出的两个代码来使字符串忽略重音字符? 【参考方案1】:

如你所说,

String text = "Cámélan 发现 ônte red aleŕt \n Como se extingue la deuda";

因此,每当您给出第一个输入时,都会接收第一个字符并进行比较。

例如:如果你给Ca,那么

if (StringUtils.isNotEmpty(substring))  //this is the search text
substring=substring.substring(0,1); //now you get C alone.

因此,无论您键入什么,它都会通过过滤第一个字符来显示。现在

 SpannableString builder = higlightString((yourContent.getText()), mHighlightColor);
    viewHolder.mTextView.setText(builder);




private SpannableString higlightString(String entireContent, int highlightColor) 
            SpannableString returnValue = new SpannableString(entireContent);

            String lowercaseNodeText = entireContent;
        try 
            Matcher matcher = mFilter.getPattern().matcher(((diacritical(lowercaseNodeText.toLowerCase()))));
            while (matcher.find()) 
                returnValue.setSpan(new ForegroundColorSpan(highlightColor), matcher.start(0),
                        matcher.end(0), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
            
        
        catch (Exception e)
            e.printStackTrace();
        

            return returnValue;

    



 private String diacritical(String original) 
       String removed=null;
           String decomposed = Normalizer.normalize(original, Normalizer.Form.NFD);
           removed = decomposed.replaceAll("\\pInCombiningDiacriticalMarks+", "");
       return removed;
   

测试用例:

当您输入 Ca 时,它通过显示所有 C 内容进入整个文本获取所有数据并通过对内容进行规范化过滤掉,它也与重音字符匹配并通过高亮显示。

【讨论】:

【参考方案2】:

我不是 Java 程序员,所以这里只是一些基本的原始正则表达式解决方案。

如果你能规范化字符串的分解形式 假设是这个

String sSourceTargetDecom = Normalizer.normalize(sourcetarget, Normalizer.Form.NFD);,

这应该变成0000C1 Á LATIN CAPITAL LETTER A WITH ACUTE 分成两个字符A000301 ́ COMBINING ACUTE ACCENT

您可以使用

从块中获取最多的组合字符
[\pBlock=Combining_Diacritical_Marks\pBlock=Combining_Diacritical_Marks_Extended\pBlock=Combining_Diacritical_Marks_For_Symbols\pBlock=Combining_Diacritical_Marks_Supplement\pBlock=Combining_Half_Marks]  

十六进制范围为

[\x300-\x36f\x1ab0-\x1aff\x1dc0-\x1dff\x20d0-\x20ff\xfe20-\xfe2f]  

事实证明,大多数与基本拉丁语相关的组合标记可以是 分解的在[\x300-\x36f] 范围内。

您可以分解两者源目标和输入搜索字符串。


然后从输入的搜索字符串创建一个正则表达式。 注入 [\x300-\x36f]? 在每个基本拉丁字母之后。

String regex = sSearch.replaceAll("([a-zA-Z])[\\x300-\\x36f]?", "\\1[\\x300-\\x36f]?");

(不确定 Java 在其正则表达式中使用什么代码点字符表示法,可能需要为 \uDD

然后在 sSourceTargetDecom 字符串上使用正则表达式,它将单独匹配基本拉丁语,和/或与可选的组合代码匹配。

【讨论】:

【参考方案3】:

你已经得到了:

private String convertToBasicLatin(String text) 
    return Normalizer.normalize(text, Normalizer.Form.NFD)
        .replaceAll("\\pM", "").replaceAll("\\R", "\n");

为了使 one 无重音的基本拉丁字符匹配 one 重音字母的 Unicode 代码点, 应该将 规范化为 composed 形式:

private String convertToComposedCodePoints(String text) 
    return Normalizer.normalize(text, Normalizer.Form.NFC).replaceAll("\\R", "\n");

一般来说,人们可能会假设 Unicode 代码点也是 1 个字符长。

搜索键使用 convertToBasicLatin(sought) 文本视图的内容使用 convertToComposedCodePoints(content) 匹配的文本内容使用convertToBasicLatin(content)

现在startend匹配器的索引位置是正确的。 我将\r\n\u0085 等明确的行尾(正则表达式\R)标准化为单个\n。 不能标准化为小写/大写,因为字符的数量可能会有所不同: 德语小写ß对应大写SS

String sought = ...;
String content = ...;

sought = convertToBasicLatin(sought);
String latinContent = convertToBasicLatin(content);
String composedContent = convertToComposedUnicode(content);

Matcher m = Pattern.compile(sought, Pattern.CASE_INSENSITIVE
        | Pattern.UNICODE_CASE | Pattern.UNICODE_CHARACTER_CLASS
        | Pattern.UNIX_LINES)
    .matcher(latinContent);
while (m.find()) 
    ... // One can apply `m.start()` and `m.end()` to composedContent of the view too.

【讨论】:

感谢@Joop Eggen。我可以知道为什么我不能使用 Pattern.UNICODE_CHARACTER_CLASS。如果我使用它,我会崩溃 [android] Unsupported Flag 256 for Pattern.java 放弃那个标志,不同的java实现。有利于其他人注意。

以上是关于给定字符串中的混合重音字符和普通字符在搜索时在 java 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

用普通的ascii字符替换重音字符[重复]

使用 JavaScript 执行带/不带重音字符的文本匹配

c ++:搜索忽略重音字符

如何搜索忽略重音字符的字符串(例如ã = a)[重复]

使用Javascript过滤搜索,重音等于common的字符

如何在 Erlang 中将重音字符串转换为常规字符串?