7

问题

我的问题如下所述:

如何使用 R 来读取包含 HTML 表情符号代码的字符串��

我想: (1)在解析的字符串中
表示表情符号(例如,作为unicode符号:),或者(2)将其转换为等效的文本(“ ”)
:hugging face:

背景

我有一个文本消息的 XML 数据集(来自 Android/iOS 应用Signal),我正在将其读入 R 以进行文本挖掘项目。数据如下所示,每个文本消息都表示在一个sms节点中:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!-- File Created By Signal -->
    <smses count="1">
        <sms protocol="0" address="+15555555555" contact_name="Jane Doe" date="1483256850399" readable_date="Sat, 31 Dec 2016 23:47:30 PST" type="1" subject="null" body="Hug emoji: &#55358;&#56599;" toa="null" sc_toa="null" service_center="null" read="1" status="-1" locked="0" />
</smses>

问题

我目前正在使用xml2R 的包读取数据。但是,当我使用该xml2::read_xml函数时,我收到以下错误消息:

Error in doc_parse_raw(x, encoding = encoding, base_url = base_url, as_html = as_html,  : 
  xmlParseCharRef: invalid xmlChar value 55358

据我了解,这表明表情符号字符未被识别为有效的 XML。

使用该xml2::read_html功能确实有效,但会删除表情符号字符。这里有一个小例子:

example_text <- "Hugging emoji: &#55358;&#56599;"
xml2::xml_text(xml2::read_html(paste0("<x>", example_text, "</x>")))

(输出[1] "Hugging emoji: ":)

这个字符有效的 HTML——谷歌搜索&#55358;&#56599;实际上将它在搜索栏中转换为“拥抱脸”表情符号,并显示与该表情符号相关的结果。

我发现的其他信息似乎与这个问题有关

我一直在搜索 Stack Overflow,但没有找到与此特定问题相关的任何问题。我也无法找到一个表格,在它们所代表的表情符号旁边直接给出 HTML 代码,因此无法在解析之前在一个大循环中将这些 HTML 代码(尽管效率低下)转换为它们的文本等价物数据集;例如,此列表及其基础数据集似乎都不包含字符串55358

4

4 回答 4

8

tl;博士:表情符号不是有效的 HTML 实体;UTF-16 数字已用于构建它们,而不是 Unicode 代码点。我在答案的底部描述了一种算法来将它们转换为有效的 XML。


识别问题

R 绝对可以处理表情符号:

在此处输入图像描述

事实上,R 中存在一些用于处理表情符号的包。例如,emojifontemo包都允许您根据 Slack 风格的关键字检索表情符号。这只是从 HTML 转义格式中获取源字符的问题,以便您可以转换它们。

xml2::read_xml似乎可以很好地处理其他 HTML 实体,例如 & 符号或双引号。我查看了这个 SO 答案,以查看 HTML 实体上是否存在任何特定于 XML 的约束,并且它们似乎可以很好地存储表情符号。因此,我尝试将您的代表中的表情符号代码更改为该答案中的表情符号代码:

body="Hug emoji: &#128512;&#128515;"

而且,果然,它们被保留了下来(尽管它们显然不再是拥抱表情符号了):

> test8 = read_html('Desktop/test.xml')
> test8 %>% xml_child() %>% xml_child() %>% xml_child() %>% xml_attr('body')
[1] "Hug emoji: \U0001f600\U0001f603"

我在此页面上查找了拥抱表情符号,但没有 给出十进制 HTML 实体&#55358;&#56599;。看起来表情符号的 UTF-16 十进制代码已包含在&#;.

总之,我认为答案是您的表情符号实际上不是有效的 HTML 实体。如果您无法控制来源,则可能需要进行一些预处理以解决这些错误。

那么,为什么浏览器会正确转换它们呢?我想知道浏览器是否对这些东西更灵活一些,并且正在猜测这些代码可能是什么。不过,我只是在猜测。


将 UTF-16 转换为 Unicode 代码点

经过进一步调查,看起来有效的表情符号 HTML 实体使用 Unicode 代码点(如果是 ,则为十进制,如果是&#...;,则为十六进制&#x...;)。Unicode 代码点不同于 UTF-8 或 UTF-16 代码。(那个链接解释了很多关于表情符号和其他字符是如何被不同编码的,顺便说一句!读得好。)

因此,我们需要将源数据中使用的 UTF-16 代码转换为 Unicode 代码点。参考这篇关于 UTF-16 的维基百科文章,我已经验证了它是如何完成的。每个 Unicode 代码点(我们的目标)是一个 20 位数字,或五个十六进制数字。当从 Unicode 到 UTF-16 时,你把它分成两个 10 位数字(中间的十六进制数字被切成两半,每个块有两个位),对它们做一些数学运算并得到你的结果) .

倒退,如你所愿,它是这样完成的:

  • 您的十进制 UTF-16 数字(现在位于两个单独的块中)是55358 56599
  • 将这些块转换为十六进制(单独)给出0x0d83e 0x0dd17
  • 0xd800您从第一个块中减去0xdc00第二个块以给出0x3e 0x117
  • 将它们转换为二进制,将它们填充为 10 位并将它们连接起来,它是0b0000 1111 1001 0001 0111
  • 然后我们将其转换回十六进制,即0x0f917
  • 最后,我们添加0x10000,给0x1f917
  • 因此,我们的(十六进制)HTML 实体是&#x1f917;. 或者,十进制,&#129303

所以,要预处理这个数据集,你需要提取现有的数字,使用上面的算法,然后把结果放回去(用一个&#...;,而不是两个)。


在 R 中显示表情符号

据我所知,在 R 控制台中打印表情符号没有解决方案:它们总是以"U0001f600"(或者你有什么)出现。但是,我上面描述的软件包可以帮助您在某些情况下绘制表情符号(我希望扩展ggflags以在某些时候显示任意全彩表情符号)。他们还可以帮助您搜索表情符号以获取他们的代码,但由于代码 AFAIK,他们无法获取名称。但也许您可以尝试将表情符号列表从emojilibR 导入并与您的数据框进行连接,如果您已将表情符号代码提取到一列中,以获取英文名称。

于 2018-01-08T00:59:17.910 回答
2

JavaScript 解决方案

我有这个完全相同的问题,但需要 JavaScript 中的解决方案,而不是 R。使用上面rensa评论 (非常有帮助!),我创建了以下代码来解决这个问题,我只是想分享它以防其他人像我一样在这个线程中发生,但在 JavaScript 中需要它。

str.replace(/(&#\d+;){2}/g, function(match) {
    match = match.replace(/&#/g,'').split(';');
    var binFirst = (parseInt('0x' + parseInt(match[0]).toString(16)) - 0xd800).toString(2);
    var binSecond = (parseInt('0x' + parseInt(match[1]).toString(16)) - 0xdc00).toString(2);
    binFirst = '0000000000'.substr(binFirst.length) + binFirst;
    binSecond = '0000000000'.substr(binSecond.length) + binSecond;
    return '&#x' + (('0x' + (parseInt(binFirst + binSecond, 2).toString(16))) - (-0x10000)).toString(16) + ';';
});

而且,如果您想运行它,这里有一个完整的片段:

var str = '&#55357;&#56842;&#55357;&#56856;&#55357;&#56832;&#55357;&#56838;&#55357;&#56834;&#55357;&#56833;'

str = str.replace(/(&#\d+;){2}/g, function(match) {
	match = match.replace(/&#/g,'').split(';');
	var binFirst = (parseInt('0x' + parseInt(match[0]).toString(16)) - 0xd800).toString(2);
	var binSecond = (parseInt('0x' + parseInt(match[1]).toString(16)) - 0xdc00).toString(2);
	binFirst = '0000000000'.substr(binFirst.length) + binFirst;
	binSecond = '0000000000'.substr(binSecond.length) + binSecond;
	return '&#x' + (('0x' + (parseInt(binFirst + binSecond, 2).toString(16))) - (-0x10000)).toString(16) + ';';
});

document.getElementById('result').innerHTML = str;

//  &#55357;&#56842;&#55357;&#56856;&#55357;&#56832;&#55357;&#56838;&#55357;&#56834;&#55357;&#56833;
//  is turned into
//  &#x1f60a;&#x1f618;&#x1f600;&#x1f606;&#x1f602;&#x1f601;
//  which is rendered by the browser as the emojis
Original:<br>&#55357;&#56842;&#55357;&#56856;&#55357;&#56832;&#55357;&#56838;&#55357;&#56834;&#55357;&#56833;<br><br>
Result:<br>
<div id='result'></div>

我的SMS XML Parser应用程序现在运行良好,但它在大型 XML 文件上停滞不前,所以我正在考虑用 PHP 重写它。如果/当我这样做时,我也会发布该代码。

于 2018-12-10T18:02:49.573 回答
1

我已经在 R 中实现了上面 rensa 描述的算法,并在这里分享。我很高兴在CC0 奉献下发布下面的代码片段(即,将此实现放入公共领域以供免费重用)。

这是 rensa 算法的快速且未完善的实现,但它确实有效!

utf16_double_dec_code_to_utf8 <- function(utf16_decimal_code){
  string_elements <- str_match_all(utf16_decimal_code, "&#(.*?);")[[1]][,2]

  string3a <- string_elements[1]
  string3b <- string_elements[2]

  string4a <- sprintf("0x0%x", as.numeric(string3a))
  string4b <- sprintf("0x0%x", as.numeric(string3b))

  string5a <- paste0(
    # "0x", 
    as.hexmode(string4a) - 0xd800
  )
  string5b <- paste0(
    # "0x",
    as.hexmode(string4b) - 0xdc00
  )

  string6 <- paste0(
    stringi::stri_pad(
      paste0(BMS::hex2bin(string5a), collapse = ""),
      10,
      pad = "0"
    ) %>%
      stringr::str_trunc(10, side = "left", ellipsis = ""),
    stringi::stri_pad(
      paste0(BMS::hex2bin(string5b), collapse = ""),
      10,
      pad = "0"
    ) %>%
      stringr::str_trunc(10, side = "left", ellipsis = "")
  )

  string7 <- BMS::bin2hex(as.numeric(strsplit(string6, split = "")[[1]]))

  string8 <- as.hexmode(string7) + 0x10000

  unicode_pattern <- string8
  unicode_pattern
}

make_unicode_entity <- function(x) {
  paste0("\\U000", utf16_double_dec_code_to_utf8(x))
}
make_html_entity <- function(x) {
  paste0("&#x", utf16_double_dec_code_to_utf8(x), ";")
}

# An example string, using the "hug" emoji:
example_string <- "test &#55358;&#56599; test"

output_string <- stringr::str_replace_all(
  example_string,
  "(&#[0-9]*?;){2}",  # Find all two-character "&#...;&#...;" codes.
  make_unicode_entity
  # make_html_entity
)

cat(output_string)

# To print Unicode string (doesn't display in R console, but can be copied and
# pasted elsewhere:
# (This assumes you've used 'make_unicode_entity' above in the str_replace_all
# call):
stringi::stri_unescape_unicode(output_string)
于 2018-01-08T16:45:37.647 回答
1

翻译 Chad 对 Go 的 JavaScript 答案,因为我也有同样的问题,但需要 Go 中的解决方案。

https://play.golang.org/p/h9JBFzqcd90

package main

import (
    "fmt"
    "html"
    "regexp"
    "strconv"
    "strings"
)

func main() {
    emoji := "&#55357;&#56842;&#55357;&#56856;&#55357;&#56832;&#55357;&#56838;&#55357;&#56834;&#55357;&#56833;"

    regexp := regexp.MustCompile(`(&#\d+;){2}`)
    matches := regexp.FindAllString(emoji, -1)

    var builder strings.Builder

    for _, match := range matches {
        s := strings.Replace(match, "&#", "", -1)

        parts := strings.Split(s, ";")
        a := parts[0]
        b := parts[1]

        c, err := strconv.Atoi(a)
        if err != nil {
            panic(err)
        }

        d, err := strconv.Atoi(b)
        if err != nil {
            panic(err)
        }

        c = c - 0xd800
        d = d - 0xdc00

        e := strconv.FormatInt(int64(c), 2)
        f := strconv.FormatInt(int64(d), 2)

        g := "0000000000"[2:len(e)] + e
        h := "0000000000"[10:len(f)] + f

        j, err := strconv.ParseInt(g + h, 2, 64)
        if err != nil {
            panic(err)
        }

        k := j + 0x10000

        _, err = builder.WriteString("&#x" + strconv.FormatInt(k, 16) + ";")
        if err != nil {
            panic(err)
        }
    }

    fmt.Println(html.UnescapeString(emoji))
    emoji = html.UnescapeString(builder.String())
    fmt.Println(emoji)
}
于 2019-10-06T23:52:16.220 回答