推广函数以返回具有无效UTF-8字节/代码点的data.frame列的列表

时间:2016-07-25 22:33:33

标签: r encoding utf-8

我最近编写了一个函数,它使用grep和regex来查找无效的UTF-8代码点(因为我在mac上工作,我的语言环境也是UTF-8)。输入不必是UTF-8,因为它正在寻找无效的UTF-8字节。我写了这个函数用于工作,并想知道是否有人可以提供一些提示来概括/捕获我没有注意到的代码中的任何红色标记(例如使用基本代码而不是dplyr)。如果对您有用,请随意使用任何代码。

enc_check <- function(data) {

library(dplyr)

library(magrittr)

# Create vector of all possible 2-digit hexadecimal numbers (2 digits is the lenth of a byte)

allBytes <- list(x_esc = '\\x',
               hex1 = as.character(c(seq(0,9),
                                     c('a','b','c','d','e','f'))),
               hex2 = as.character(c(seq(0,9),
                                     c('a','b','c','d','e','f')))
               ) %$%
expand.grid(x_esc, hex1, hex2) %>%
apply(1, paste, collapse = '')

# Valid mixed alphanumeric bytes

validBytes1 <- list(x_esc = '\\x',
                 hexNum = as.character(c(seq(2,7))),     
                 hexAlpha = c('a','b','c','d','e','f')
                 ) %$%
expand.grid(x_esc, hexNum, hexAlpha) %>%
apply(1, paste, collapse = '') %>%
extract(. != '\\x7f')

# Valid purely numeric bytes

validBytes2 <- list(x_esc = '\\x',
                 hexNum2 = as.character(seq(20,79))
                 ) %$%
expand.grid(x_esc, hexNum2) %>%
apply(1, paste, collapse = '')

# New-line byte
validBytes3 <- '\\x0a'
# charToRaw('\n')
# [1] 0a

# Filter all possible combinations down to only invalid bytes
validBytes <- c(validBytes1, validBytes2, validBytes3)
invalidBytes <- allBytes %>%
  extract(not(is_in(., validBytes)))

# Create list of data.frame columns with invalid bytes
a_vector <- vector()
matches <- list()
for (i in 1:dim(data)[2]) {
  a_vector <- data[,i]
  matches[[i]] <- unlist(sapply(invalidBytes, grep, a_vector, useBytes =   TRUE))
} 

# Get rid of empty list elements
matches %<>%
  lapply(length) %$%
  extract(matches, . > 0)
# matches <- matches[lapply(matches,length) > 0]

return(matches)
}

编辑:这是已实施建议的更新代码。

enc_check <- function(dataset) {
library(dplyr)
library(magrittr)

rASCII <- c( '\n', '\r', '\t','\b',
           '\a', '\f', '\v', '\\', '\'', '\"', '\`')

validBytes <- paste0("\\x",
                     c(as.character(as.hexmode(32:126)),
                       sapply(rASCII, charToRaw))) %>%
  extract(not(duplicated(.)))

invalidBytes <- allBytes %>%
  extract(not(is_in(., validBytes)))

a_vector <- vector()
matches <- list()
  for (i in 1:dim(dataset)[2]) {
    a_vector <- dataset[,i]
    matches[[i]] <- unlist(sapply(invalidBytes, grep, a_vector, useBytes = TRUE))
} # sapply() is preferable to lapply due to USE.NAMES = TRUE
names(matches) <- names(dataset)

matches %<>%
  lapply(length) %$%
  extract(matches, . > 0)

return(matches)
}

第二编辑:更好的策略是使用iconv。假设你有一个带有一些无效字节的文件或对象,但通常是UTF-8。 Mac计算机通常就是这种情况,其默认语言环境设置似乎是UTF-8。此外,基于Mac的RStudio似乎在内部使用UTF-8,即使您将计算机的语言环境设置为不同的编码,也无法更改。无论如何,您可以使用iconv将所有无效字节(通常显示为十六进制字节)(例如“\ x8f”)替换为Unicode替换符号。然后,您可以搜索该符号,并返回带有该符号的data.frame列中的唯一观察列表。基于此,您可以使用“sub()”将这些字符替换为所需的字符。需要注意的一点是,如果存在无效字节,将文件转换为另一种编码(例如latin-1)可能会产生意外结果。当我这样做时,我注意到一些无效字节被转换为Unicode控制字符,而其他无效字节显然与有效的latin-1字节匹配并显示为无意义的字符。在任何一种情况下,我都写了一个包来搜索这些字符的data.frames并返回一个列表,然后做一些替换。这个软件包并不像CRAN那样正式,但如果有人感兴趣,那么这里有一个指向存储库的链接:https://github.com/jkroes/FixEncoding。重要的是要注意包的“稳定”版本不在“主”分支上;它实际上在分支“iconv”上。在安装正确的分支后,可以通过“?FixEncoding”在R中搜索文档,然后查找其中列出的函数并搜索这些函数。

1 个答案:

答案 0 :(得分:0)

这会将十六进制数的所有alpha版本构造为“ff”:

allBytes <-  as.character( as.hexmode(0:255) )    

或者像你似乎更喜欢的模式:

allBytes <- paste0("\\x", as.character( as.hexmode(0:255) ) )

R识别出的“特殊”字符包括您已放弃的“\ n”,但还有一些列在?Quotes帮助页面上的字符:

rASCII <- c( '\n', '\r', '\t','\b',
             '\a', '\f', '\v', '\\', '\'', '\"', '\`')

您可以使用以下内容为“characters”“space to tilde(”〜“)创建一个有效grep模式的向量:

validBytes1 <- c(rASCII, paste( "\\x", as.hexmode( c(20:126)) )

我担心使用此策略,因为我的R在尝试与它认为无效的输入字符串进行greppish匹配时会抛出错误。

> txt <- "ttt\nuuu\tiii\xff"
> dfrm <- data.frame(a = txt)
> lapply(dfrm, grep, patt = "\\xff")
$a
integer(0)

Warning message:
In FUN(X[[i]], ...) : input string 1 is invalid in this locale
> lapply(dfrm, grep, patt = "\\\xff")
Error in FUN(X[[i]], ...) : regular expression is invalid in this locale
> lapply(dfrm, grep, patt = "\xff")
Error in FUN(X[[i]], ...) : regular expression is invalid in this locale

您可能希望切换到grepRaw,因为它不会引发相同的错误:

> grepRaw("\xff", txt)
[1] 12

或者可以按照Duncan Murdoch的建议使用?tools::showNonASCII 4年前在Rhelp出现时:

 ?tools::showNonASCII
 # and the help page has a reproducible example of its use:

out <- c(
"fa\xE7ile test of showNonASCII():",
"\\details{",
"   This is a good line",
"   This has an \xfcmlaut in it.",
"   OK again.",
"}")
f <- tempfile()
cat(out, file = f, sep = "\n")

tools::showNonASCIIfile(f)
#-------output appears in red----
1: fa<e7>ile test of showNonASCII():
4:    This has an <fc>mlaut in it.