如何解码表情符号字符的格式不正确的十六进制字符串,如“`1f1e81f1f3`”?

How to decode unwell-formed hex string of emoji character like "`1f1e81f1f3`"?

假设有一个表情符号字符的十六进制字符串,如“1f1e81f1f3”,它是一个表情符号字符代码点的格式不正确的十六进制字符串,它应该是两个字符串,如1f1e8 1f1f3

我正在使用 org.apache.commons.codec.binary.Hex 解码十六进制字符串,但显然十六进制需要输入字符串的长度是偶数,所以我需要将十六进制字符串设为零填充样式,如“ 01f1e801f1f3".

目前,我只是将“1f”替换为“01f”,到目前为止还不错,但是自从 an emoji glyph may contains a sequence of unicode characters,所以

背景

这个十六进制的表情符号字符串是从“<span class="emoji emojiXXXXXXXXXX"></span>”字符串中剥离出来的,它是通过非官方 HTTP API.

从流行的 IM 软件中检索到的一条文本消息

我最终写了一个小函数来恢复表情符号字符。

基本程序:

  1. 创建一个指向十六进制字符串开头的指针。
  2. 从十六进制字符串的指针位置开始查找,
    • 如果它以“1f”开头,则在“1f”之前填充三个零,将其存储到一个新的十六进制字符串中,然后指针步进到下一个第 5 个位置。否则,不进行零填充,将子字符串存储到新的十六进制字符串,并将指针步进到下一个第 4 个位置。
    • 将新的十六进制字符串解码为字节数组。
    • 使用字节数组中的 UTF_32BE 或 UTF_16BE 字符编码创建新字符串。
  3. 循环到第 2 步,直到十六进制字符串结束。

可以,但不完美,如果

可能会引入错误
  • emoji字符序列中的一个字符位于增补字符
  • 它的十六进制字符串不是以“1f”开头,或者它的十六进制字符串的长度不是5。

代码片段:

import java.util.*;
import java.util.regex.*;

import org.apache.commons.codec.*;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.*;

public static final Charset UTF_32BE = Charset.forName ("UTF-32BE");
public static final String REGEXP_FindTransformedEmojiHexString = "<span class=\"emoji emoji(\p{XDigit}+)\"></span>";
public static final Pattern PATTERN_FindTransformedEmojiHexString = Pattern.compile (REGEXP_FindTransformedEmojiHexString, Pattern.CASE_INSENSITIVE);
public static String RestoreEmojiCharacters (String sContent)
{
        bMatched = true;
        String sEmojiHexString = matcher.group(1);

        Hex hex = new Hex (StandardCharsets.ISO_8859_1);
        try
        {
            for (int i=0; i<sEmojiHexString.length ();)
            {
                String sEmoji = null;
                Charset charset = null;
                String sSingleEmojiGlyphHexString = null;
                String sStartString = StringUtils.substring (sEmojiHexString, i, i+2);
                if (StringUtils.startsWithIgnoreCase (sStartString, "1f"))
                {
                    sSingleEmojiGlyphHexString = "000" + StringUtils.substring (sEmojiHexString, i, i+5);
                    i += 5;
                    charset = UTF_32BE;
                }
                else
                {
                    sSingleEmojiGlyphHexString = StringUtils.substring (sEmojiHexString, i, i+4);
                    i += 4;
                    charset = StandardCharsets.UTF_16BE;
                }
                byte[] arrayEmoji = null;
                arrayEmoji = (byte[])hex.decode (sSingleEmojiGlyphHexString);
                sEmoji = new String (arrayEmoji, charset);
                matcher.appendReplacement (sbReplace, sEmoji);
            }
        }
        catch (DecoderException e)
        {
            e.printStackTrace();
        }
    }
    matcher.appendTail (sbReplace);

    if (bMatched)
        sContent = sbReplace.toString ();

    return sContent;
}