Какое регулярное выражение для извлечения всех смайликов из строки?
У меня есть строка, закодированная в UTF-8. Например:
That's a nice joke 😆😆😆 😛
Я должен извлечь все смайлики, присутствующие в предложении. И смайлик может быть любым.
Когда это предложение просматривается в терминале с помощью command less text.txt
оно рассматривается как:
That's a nice joke <U+1F606><U+1F606><U+1F606> <U+1F61B>
Это соответствующий UTF-код для смайликов. Все коды для смайликов можно найти на emojitracker.
С целью поиска всех вхождений я использовал шаблон регулярного выражения (<U\+\w+?>)
но это не сработало для строки в кодировке UTF-8.
Ниже приведен мой код:
String s = "That's a nice joke 😆😆😆 😛";
Pattern pattern = Pattern.compile("(<U\\+\\w+?>)");
Matcher matcher = pattern.matcher(s);
List<String> matchList = new ArrayList<>();
while (matcher.find()) {
matchList.add(matcher.group());
}
for (int i = 0; i < matchList.size(); i++) {
System.out.println(matchList.get(i));
}
В этом PDF-файле написано Range: 1F300–1F5FF for Miscellaneous Symbols and Pictographs
. Итак, я хочу записать любой символ, лежащий в пределах этого диапазона.
Переведено автоматически
Ответ 1
Используя emoji-java, я написал простой метод, который удаляет все смайлики, включая модификаторы фитцпатрика. Требуется внешняя библиотека, но ее проще поддерживать, чем эти чудовищные регулярные выражения.
Использование:
String input = "A string 😄with a \uD83D\uDC66\uD83C\uDFFFfew 😉emojis!";
String result = EmojiParser.removeAllEmojis(input);
emoji-установка java maven:
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>emoji-java</artifactId>
<version>3.1.3</version>
</dependency>
gradle:
implementation 'com.vdurmont:emoji-java:3.1.3'
РЕДАКТИРОВАТЬ: ранее отправленный ответ был перенесен в исходный код emoji-java.
Ответ 2
Here in 2024 with Java 21, it's much simpler than it used to be back in 2014 when the answer below was written. You can use the Unicode character category IsEmoji
, see Mahozad's answer for details!
the pdf that you just mentioned says Range: 1F300–1F5FF for Miscellaneous Symbols and Pictographs. So lets say I want to capture any character lying within this range. Now what to do?
Okay, but I will just note that the emoji in your question are outside that range! :-)
The fact that these are above 0xFFFF
complicates things, because Java strings store UTF-16. So we can't just use one simple character class for it. We're going to have surrogate pairs. (More: http://www.unicode.org/faq/utf_bom.html)
U+1F300 in UTF-16 ends up being the pair \uD83C\uDF00
; U+1F5FF ends up being \uD83D\uDDFF
. Note that the first character went up, we cross at least one boundary. So we have to know what ranges of surrogate pairs we're looking for.
Not being steeped in knowledge about the inner workings of UTF-16, I wrote a program to find out (source at the end — I'd double-check it if I were you, rather than trusting me). It tells me we're looking for \uD83C
followed by anything in the range \uDF00-\uDFFF
(inclusive), or \uD83D
followed by anything in the range \uDC00-\uDDFF
(inclusive).
So armed with that knowledge, in theory we could now write a pattern:
// This is wrong, keep reading
Pattern p = Pattern.compile("(?:\uD83C[\uDF00-\uDFFF])|(?:\uD83D[\uDC00-\uDDFF])");
That's an alternation of two non-capturing groups, the first group for the pairs starting with \uD83C
, and the second group for the pairs starting with \uD83D
.
Но это не удается (ничего не находит). Я почти уверен, что это потому, что мы пытаемся указать половину суррогатной пары в разных местах:
Pattern p = Pattern.compile("(?:\uD83C[\uDF00-\uDFFF])|(?:\uD83D[\uDC00-\uDDFF])");
// Half of a pair --------------^------^------^-----------^------^------^
Мы не можем просто так разделить суррогатные пары, они не просто так называются суррогатными парами. :-)
Следовательно, я не думаю, что мы вообще можем использовать регулярные выражения (или какой-либо строковый подход) для этого. Я думаю, нам нужно выполнять поиск по char
массивам.
char
массивы содержат значения UTF-16, поэтому мы можем найти эти полупары в данных, если будем искать их сложным способом:
String s = new StringBuilder()
.append("Thats a nice joke ")
.appendCodePoint(0x1F606)
.appendCodePoint(0x1F606)
.appendCodePoint(0x1F606)
.append(" ")
.appendCodePoint(0x1F61B)
.toString();
char[] chars = s.toCharArray();
int index;
char ch3;
char ch2;
index = 0;
while (index < chars.length - 1) { // -1 because we're looking for two-char-long things
ch3 = chars[index];
if ((int)ch3 == 0xD83C) {
ch2 = chars[index+1];
if ((int)ch2 >= 0xDF00 && (int)ch2 <= 0xDFFF) {
System.out.println("Found emoji at index " + index);
index += 2;
continue;
}
}
else if ((int)ch3 == 0xD83D) {
ch2 = chars[index+1];
if ((int)ch2 >= 0xDC00 && (int)ch2 <= 0xDDFF) {
System.out.println("Found emoji at index " + index);
index += 2;
continue;
}
}
++index;
}
Очевидно, что это всего лишь код уровня отладки, но он выполняет свою работу. (В вашей заданной строке с ее смайликами, конечно, он ничего не найдет, поскольку они находятся за пределами диапазона. Но если вы измените верхнюю границу второй пары на 0xDEFF
вместо 0xDDFF
, так и будет. Хотя понятия не имею, будет ли это включать и не-смайлики.)
Исходный код моей программы, чтобы выяснить, каковы были суррогатные диапазоны:
public class FindRanges {
public static void main(String[] args) {
char last0 = '\0';
char last1 = '\0';
for (int x = 0x1F300; x <= 0x1F5FF; ++x) {
char[] chars = new StringBuilder().appendCodePoint(x).toString().toCharArray();
if (chars[0] != last0) {
if (last0 != '\0') {
System.out.println("-\\u" + Integer.toHexString((int)last1).toUpperCase());
}
System.out.print("\\u" + Integer.toHexString((int)chars[0]).toUpperCase() + " \\u" + Integer.toHexString((int)chars[1]).toUpperCase());
last0 = chars[0];
}
last1 = chars[1];
}
if (last0 != '\0') {
System.out.println("-\\u" + Integer.toHexString((int)last1).toUpperCase());
}
}
}
Вывод:
\uD83C \uDF00-\uDFFF
\uD83D \uDC00-\uDDFF
Ответ 3
Просто использовать регулярное выражение для решения этой проблемы:
s = s.replaceAll("\\p{So}+", "");
Вы можете найти его в
http://www.regular-expressions.info/unicode.html
https://docs.oracle.com/javase/7/docs/api/java/lang/Character.html#OTHER_SYMBOL
Ответ 4
У меня была похожая проблема. Следующее хорошо мне помогло и соответствует суррогатным парам
public class SplitByUnicode {
public static void main(String[] argv) throws Exception {
String string = "Thats a nice joke 😆😆😆 😛";
System.out.println("Original String:"+string);
String regexPattern = "[\uD83C-\uDBFF\uDC00-\uDFFF]+";
byte[] utf8 = string.getBytes("UTF-8");
String string1 = new String(utf8, "UTF-8");
Pattern pattern = Pattern.compile(regexPattern);
Matcher matcher = pattern.matcher(string1);
List<String> matchList = new ArrayList<String>();
while (matcher.find()) {
matchList.add(matcher.group());
}
for(int i=0;i<matchList.size();i++){
System.out.println(i+":"+matchList.get(i));
}
}
}
Вывод:
Original String:Thats a nice joke 😆😆😆 😛
0:😆😆😆
1:😛
Найдено регулярное выражение из https://javalang.ru/a/24071599/915972