随着@jlarson 更新有关 Mac 是最大罪魁祸首的信息,我们可能会进一步了解。 Office for Mac 至少在 2011 年及以后版本对导入文件时读取 Unicode 格式的支持相当差。
对 UTF-8 的支持似乎几乎不存在,读过一些关于它的工作的 cmets,而大多数人说它没有。不幸的是,我没有任何 Mac 可以测试。再说一遍:文件本身应该是 UTF-8,但导入会停止进程。
用 Javascript 编写了一个快速测试,用于导出百分比转义的 UTF-16 小端和大端、带/不带 BOM 等。
代码可能应该被重构,但应该可以用于测试。它可能比 UTF-8 更好。当然,这通常也意味着更大的数据传输,因为任何字形都是两个或四个字节。
你可以在这里找到一个小提琴:
Unicode export sample Fiddle
请注意,它不以任何特定方式处理 CSV。它主要用于纯转换为具有 UTF-8、UTF-16 大/小端和 +/- BOM 的 数据 URL。 fiddle 中有一个选项可以用制表符替换逗号,但相信如果可行的话,这将是一个相当老套和脆弱的解决方案。
通常使用如下:
// Initiate
encoder = new DataEnc({
mime : 'text/csv',
charset: 'UTF-16BE',
bom : true
});
// Convert data to percent escaped text
encoder.enc(data);
// Get result
var result = encoder.pay();
对象有两个结果属性:
1.) encoder.lead
这是数据 URL 的 mime 类型、字符集等。从传递给初始化程序的选项构建,或者也可以说.config({ ... new conf ...}).intro() 重新构建。
data:[<MIME-type>][;charset=<encoding>][;base64]
你可以指定base64,但是没有base64转换(至少目前还没有)。
2.) encoder.buf
这是一个带有百分比转义数据的字符串。
.pay() 函数只是将 1.) 和 2.) 作为一个返回。
主要代码:
function DataEnc(a) {
this.config(a);
this.intro();
}
/*
* http://www.iana.org/assignments/character-sets/character-sets.xhtml
* */
DataEnc._enctype = {
u8 : ['u8', 'utf8'],
// RFC-2781, Big endian should be presumed if none given
u16be : ['u16', 'u16be', 'utf16', 'utf16be', 'ucs2', 'ucs2be'],
u16le : ['u16le', 'utf16le', 'ucs2le']
};
DataEnc._BOM = {
'none' : '',
'UTF-8' : '%ef%bb%bf', // Discouraged
'UTF-16BE' : '%fe%ff',
'UTF-16LE' : '%ff%fe'
};
DataEnc.prototype = {
// Basic setup
config : function(a) {
var opt = {
charset: 'u8',
mime : 'text/csv',
base64 : 0,
bom : 0
};
a = a || {};
this.charset = typeof a.charset !== 'undefined' ?
a.charset : opt.charset;
this.base64 = typeof a.base64 !== 'undefined' ? a.base64 : opt.base64;
this.mime = typeof a.mime !== 'undefined' ? a.mime : opt.mime;
this.bom = typeof a.bom !== 'undefined' ? a.bom : opt.bom;
this.enc = this.utf8;
this.buf = '';
this.lead = '';
return this;
},
// Create lead based on config
// data:[<MIME-type>][;charset=<encoding>][;base64],<data>
intro : function() {
var
g = [],
c = this.charset || '',
b = 'none'
;
if (this.mime && this.mime !== '')
g.push(this.mime);
if (c !== '') {
c = c.replace(/[-\s]/g, '').toLowerCase();
if (DataEnc._enctype.u8.indexOf(c) > -1) {
c = 'UTF-8';
if (this.bom)
b = c;
this.enc = this.utf8;
} else if (DataEnc._enctype.u16be.indexOf(c) > -1) {
c = 'UTF-16BE';
if (this.bom)
b = c;
this.enc = this.utf16be;
} else if (DataEnc._enctype.u16le.indexOf(c) > -1) {
c = 'UTF-16LE';
if (this.bom)
b = c;
this.enc = this.utf16le;
} else {
if (c === 'copy')
c = '';
this.enc = this.copy;
}
}
if (c !== '')
g.push('charset=' + c);
if (this.base64)
g.push('base64');
this.lead = 'data:' + g.join(';') + ',' + DataEnc._BOM[b];
return this;
},
// Deliver
pay : function() {
return this.lead + this.buf;
},
// UTF-16BE
utf16be : function(t) { // U+0500 => %05%00
var i, c, buf = [];
for (i = 0; i < t.length; ++i) {
if ((c = t.charCodeAt(i)) > 0xff) {
buf.push(('00' + (c >> 0x08).toString(16)).substr(-2));
buf.push(('00' + (c & 0xff).toString(16)).substr(-2));
} else {
buf.push('00');
buf.push(('00' + (c & 0xff).toString(16)).substr(-2));
}
}
this.buf += '%' + buf.join('%');
// Note the hex array is returned, not string with '%'
// Might be useful if one want to loop over the data.
return buf;
},
// UTF-16LE
utf16le : function(t) { // U+0500 => %00%05
var i, c, buf = [];
for (i = 0; i < t.length; ++i) {
if ((c = t.charCodeAt(i)) > 0xff) {
buf.push(('00' + (c & 0xff).toString(16)).substr(-2));
buf.push(('00' + (c >> 0x08).toString(16)).substr(-2));
} else {
buf.push(('00' + (c & 0xff).toString(16)).substr(-2));
buf.push('00');
}
}
this.buf += '%' + buf.join('%');
// Note the hex array is returned, not string with '%'
// Might be useful if one want to loop over the data.
return buf;
},
// UTF-8
utf8 : function(t) {
this.buf += encodeURIComponent(t);
return this;
},
// Direct copy
copy : function(t) {
this.buf += t;
return this;
}
};
上一个答案:
我没有任何设置可以复制您的设置,但如果您的情况与@jlarson 相同,那么生成的文件应该是正确的。
这个答案有点长,(你说的有趣的话题?),但是围绕问题讨论各个方面,(可能)发生什么,以及如何实际检查各种情况下发生了什么方式。
TL;DR:
文本很可能以 ISO-8859-1、Windows-1252 等格式导入,而不是 UTF-8。通过导入或其他方式强制应用程序以 UTF-8 格式读取文件。
PS:The UniSearcher 是一个很好的工具,可以在这个旅程中使用。
漫长的路
“最简单” 100% 确定我们正在查看的方法是在结果上使用十六进制编辑器。或者从命令行使用hexdump、xxd 等来查看文件。在这种情况下,字节序列应该是脚本传递的 UTF-8 的字节序列。
以 jlarson 的脚本为例,它采用 data Array:
data = ['name', 'city', 'state'],
['\u0500\u05E1\u0E01\u1054', 'seattle', 'washington']
这个被合并到字符串中:
name,city,state<newline>
\u0500\u05E1\u0E01\u1054,seattle,washington<newline>
通过 Unicode 转换为:
name,city,state<newline>
Ԁסกၔ,seattle,washington<newline>
由于 UTF-8 使用 ASCII 作为基础(最高位 not 设置的字节与 ASCII 中的相同)测试数据中唯一的特殊序列是“Ԁסกၔ”,这反过来,是:
Code-point Glyph UTF-8
----------------------------
U+0500 Ԁ d4 80
U+05E1 ס d7 a1
U+0E01 ก e0 b8 81
U+1054 ၔ e1 81 94
查看下载文件的十六进制转储:
0000000: 6e61 6d65 2c63 6974 792c 7374 6174 650a name,city,state.
0000010: d480 d7a1 e0b8 81e1 8194 2c73 6561 7474 ..........,seatt
0000020: 6c65 2c77 6173 6869 6e67 746f 6e0a le,washington.
在第二行我们找到d480 d7a1 e0b8 81e1 8194 与上面的匹配:
0000010: d480 d7a1 e0b8 81 e1 8194 2c73 6561 7474 ..........,seatt
| | | | | | | | | | | | | |
+-+-+ +-+-+ +--+--+ +--+--+ | | | | | |
| | | | | | | | | |
Ԁ ס ก ၔ , s e a t t
其他字符也没有损坏。
如果你愿意,可以做类似的测试。结果应该是相似的。
提供的样品—, â€, “
我们还可以查看问题中提供的示例。很可能假设文本在 Excel/TextEdit 中由代码页 1252 表示。
在 Windows-1252 上引用维基百科:
Windows-1252 或 CP-1252 是拉丁字母的字符编码,由
在 Microsoft Windows 的旧版组件中默认使用英语和其他一些
西方语言。它是 Windows 代码页组中的一个版本。
在 LaTeX 包中,它被称为“ansinew”。
检索原始字节
要将其翻译回原来的形式,我们可以查看code page layout,从中我们可以得到:
Character: <â> <€> <”> <,> < > <â> <€> < > <,> < > <â> <€> <œ>
U.Hex : e2 20ac 201d 2c 20 e2 20ac 9d 2c 20 e2 20ac 153
T.Hex : e2 80 94 2c 20 e2 80 9d* 2c 20 e2 80 9c
例如:
â => Unicode 0xe2 => CP-1252 0xe2
” => Unicode 0x201d => CP-1252 0x94
€ => Unicode 0x20ac => CP-1252 0x80
像9d这样的特殊情况在CP-1252中没有对应的code-point,我们直接复制就好了。
注意:如果通过将文本复制到文件并执行十六进制转储来查看损坏的字符串,请使用例如 UTF-16 编码保存文件以获得表中所示的 Unicode 值。例如。在 Vim 中:
set fenc=utf-16
# Or
set fenc=ucs-2
字节到 UTF-8
然后我们将结果(T.Hex 行)组合成 UTF-8。在 UTF-8 序列中,字节由 leading byte telling us how many subsequent bytes make the glyph 表示。例如,如果一个字节具有二进制值110x xxxx,我们知道这个字节和下一个字节代表一个代码点。一共两个。 1110 xxxx 告诉我们它是三个,依此类推。 ASCII 值没有设置高位,因此任何匹配 0xxx xxxx 的字节都是独立的。一共1个字节。
0xe2 = 1110 0010bin => 3 个字节 => 0xe28094 (em-dash) —
0x2c = 0010 1100bin => 1 字节 => 0x2c(逗号),
0x2c = 0010 0000bin => 1 字节 => 0x20(空格)
0xe2 = 1110 0010bin => 3 字节 => 0xe2809d (right-dq)”
0x2c = 0010 1100bin => 1 字节 => 0x2c(逗号),
0x2c = 0010 0000bin => 1 字节 => 0x20(空格)
0xe2 = 1110 0010bin => 3 字节 => 0xe2809c (left-dq) “
结论; 原来的 UTF-8 字符串是:
—, ”, “
把它弄回来
我们也可以反过来。原始字符串为字节:
UTF-8: e2 80 94 2c 20 e2 80 9d 2c 20 e2 80 9c
cp-1252中的对应值:
e2 => â
80 => €
94 => ”
2c => ,
20 => <space>
...
以此类推,结果:
—, â€, “
导入 MS Excel
换句话说:手头的问题可能是如何将 UTF-8 文本文件导入 MS Excel 和其他一些应用程序。在 Excel 中,这可以通过多种方式完成。
不要使用应用程序识别的扩展名保存文件,例如 .csv 或 .txt,而应完全省略或编造一些内容。
例如,将文件保存为"testfile",不带扩展名。然后在 Excel 中打开文件,确认我们确实要打开这个文件,然后 voilà 我们会得到编码选项。选择UTF-8,应该可以正确读取文件。
使用导入数据而不是打开文件。比如:
Data -> Import External Data -> Import Data
选择编码并继续。
检查 Excel 和所选字体是否确实支持字形
我们还可以使用有时更友好的剪贴板来测试对 Unicode 字符的字体支持。例如,将此页面中的文本复制到 Excel 中:
如果存在对代码点的支持,则文本应该可以正常呈现。
Linux
在 Linux 上,用户态主要是 UTF-8,这应该不是问题。使用 Libre Office Calc、Vim 等显示正确呈现的文件。
为什么有效(或应该)
来自规范状态的encodeURI,(另请阅读sec-15.1.3):
encodeURI 函数计算一个新版本的 URI,其中某些字符的每个实例都被一个、两个、三个或四个表示字符的 UTF-8 编码的转义序列替换。
我们可以在控制台中简单地测试一下,例如:
>> encodeURI('Ԁסกၔ,seattle,washington')
<< "%D4%80%D7%A1%E0%B8%81%E1%81%94,seattle,washington"
当我们注册时,转义序列等于上面十六进制转储中的转义序列:
%D4%80%D7%A1%E0%B8%81%E1%81%94 (encodeURI in log)
d4 80 d7 a1 e0 b8 81 e1 81 94 (hex-dump of file)
或者,测试一个 4 字节的代码:
>> encodeURI('?')
<< "%F3%B1%80%81"
如果不符合
如果这不适用,如果你添加它会有所帮助
- 预期输入与错位输出的示例(复制粘贴)。
- 原始数据与结果文件的十六进制转储示例。