【问题标题】:Can R read html-encoded emoji characters?R 可以读取 html 编码的表情符号字符吗?
【发布时间】:2018-01-07 23:47:34
【问题描述】:

问题

下面解释的我的问题是:

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

我想:
(1) 在解析的字符串中表示表情符号(例如,作为 unicode 符号:????),OR
(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>

问题

我目前正在使用 R 的 xml2 包读取数据。但是,当我使用 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 -- 谷歌搜索 &amp;#55358;&amp;#56599; 实际上会将其在搜索栏中转换为“拥抱脸”表情符号,并显示与该表情符号相关的结果。

我发现的与此问题似乎相关的其他信息

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

【问题讨论】:

    标签: r xml emoji html-encode xml2


    【解决方案1】:

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


    识别问题

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

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

    xml2::read_xml 似乎可以很好地处理其他 HTML 实体,例如与号或双引号。我查看了this SO answer 以查看是否对 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"
    

    我在 this page 上查找了拥抱表情符号,那里给出的十进制 HTML 实体 不是 &amp;#55358;&amp;#56599;。表情符号的 UTF-16 十进制代码似乎已包含在 &amp;#; 中。

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

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


    将 UTF-16 转换为 Unicode 码位

    经过进一步调查,看起来有效的表情符号 HTML 实体使用 Unicode 代码点(如果是 &amp;#...;,则为十进制,如果是 &amp;#x...;,则为十六进制)。 The Unicode code point is different from the UTF-8 or UTF-16 code.(该链接解释了很多关于表情符号和其他字符是如何进行各种编码的,顺便说一句!好读。)

    因此,我们需要将源数据中使用的 UTF-16 代码转换为 Unicode 代码点。参考this Wikipedia article on 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 实体是&amp;#x1f917;。或者,十进制,&amp;#129303

    因此,要预处理此数据集,您需要提取现有数字,使用上述算法,然后将结果放回原处(使用一个 &amp;#...;,而不是两个)。


    在 R 中显示表情符号

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

    【讨论】:

    • 哇,谢谢!您的回答让我更多了解正在发生的事情。感谢您的工作和帮助!正如您所建议的,我无法控制数据集(除非我可以让应用程序本身接受 PR)。我现在正试图了解如何让 R 读取字符的十进制代码——我对在解析文件之前在文件上运行正则表达式查找和替换的想法很好,但我不确定是什么格式将&amp;#...; 字符串更改为。你有什么建议吗?
    • 别担心!看起来使用&amp;#...; 实际上很好——只是您的源数据集使用了错误的...。我不确定 UTF-16 十进制代码(您正在使用)和十进制 HTML 实体(您应该使用)之间的关系是什么,但根据我之前与 ggflags 的合作,我' d 猜测表情符号在相同的位置,但块以不同的数字开始。我看看能不能找到:)
    • ~~另一种选择(现在我想起来了,更简单的解决方案)可能是尝试去掉&amp;#;,将UTF-16十进制代码转换为十六进制,并在前面加上\U000。看看我回答中的那个中间例子,read_html 似乎就是这样做的。~~实际上,忽略它!我认为您需要转换数字。
    • 呃,抱歉——忽略最后一条评论。我认为您需要翻译数字;这并不像将它们更改为十六进制并将&amp;#...; 更改为/U000... 那样简单。
    • 感谢您的想法;谢谢你!当您说“转换数字”时,您的意思是从 UTF-16 十进制代码以某种方式转换为 htmlentities,对吗?另外,正如您所指出的,"\U0001f600\U0001f603" 在 R 中运行时实际上不会显示为拥抱表情符号。您知道如何让拥抱表情符号显示吗? (如果我能找到一个可以到达的目标地点,也就是拥抱表情符号正确显示的地方,我可以尝试向后工作以找到到达那里的方法。
    【解决方案2】:

    JavaScript 解决方案

    我遇到了这个完全相同同样的问题,但需要 JavaScript 中的解决方案,而不是 R。使用 rensacomment above (非常有帮助! ),我创建了以下代码来解决这个问题,我只是想分享它,以防其他人像我一样在这个线程中发生,但在 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) + ';';
    });
    

    而且,如果你想运行它,这里有一个完整的 sn-p:

    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 重写它。如果/当我这样做时,我也会发布该代码。

    【讨论】:

      【解决方案3】:

      我已经在 R 中实现了算法described by rensa above,并在这里分享。 我很高兴在 CC0 dedication 下发布下面的代码 sn-p(即,将此实现放入公共领域以供免费重用)。

      这是 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)
      

      【讨论】:

      【解决方案4】:

      翻译了 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)
      }
      

      【讨论】:

        猜你喜欢
        • 2018-12-11
        • 1970-01-01
        • 2016-06-10
        • 1970-01-01
        • 2023-03-04
        • 2011-11-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多