I am using the regex [^\\p{L}] and java.util.regex.Matcher#replaceAll(String) to match and remove all non-letter characters from a string. I noticed that for characters containing UTF-16 surrogates, replaceAll() creates a structurally invalid string (OpenJDK Runtime Environment (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1).
First a working example:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class Main {
public static void main(String args[]) {
Pattern p = Pattern.compile("[^\\p{L}]");
System.out.println(p.matcher("abcဍ*").replaceAll(""));
}
}
The above program prints abcဍ as expected (ဍ is MYANMAR LETTER DDA).
Now let's test the character "" (\uD835\uDD0D, MATHEMATICAL FRAKTUR CAPITAL J, Category: Letter, Uppercase [Lu]), which contains high surrogates:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class Main {
public static void main(String args[]) {
String original = ""; // "\uD835\uDD0D"
Pattern p1 = Pattern.compile("[\\p{L}]"); // try regex without negation first
Matcher m1 = p1.matcher(original);
String r1 = m1.replaceAll("");
System.out.println("r1: " + r1);
Pattern p2 = Pattern.compile("[^\\p{L}]"); // now try regex with negation
Matcher m2 = p2.matcher(original);
String r2 = m2.replaceAll("");
System.out.println("r2: " + r2);
System.out.println("r2 length: " + r2.length());
System.out.println("r2 char(0): " + (int) r2.charAt(0));
System.out.println("original: " + original);
}
}
Output:
r1: // r1 = empty string as expected
r2: ? // r2 = broken string
r2 length: 1
r2 char(0): 55349 // 0xD835 (high surrogate)
original:
Other examples I tested that produce structurally invalid strings:
- (
\uD801\uDCD8), OSAGE SMALL LETTER A, Category: Letter, Lowercase [Ll] - (
\uD806\uDECA), PAU CIN HAU LETTER KHA, Category: Letter, Other [Lo]
Is my regex broken, or is this a bug in the Java Class Library? If this is a bug, is there a workaround?