RFC 5:GDAL中的Unicode支持

作者:安德烈·基塞列夫

联系方式:dron@ak4719.spb.edu

现状:发展

总结

本文档包含关于如何使GDAL核心区域设置独立于本地字符集保留支持的建议。

主要概念

GDAL的修改应支持以下三个主要思想:

  1. 用户使用本地语言在本地化环境中工作。这意味着,在处理传递给GDAL的字符串数据时,我们不能假定使用ASCII字符集。

  2. GDAL在处理字符串时在内部使用UTF-8编码。

  3. GDAL尽可能使用第三方api的Unicode版本。

因此,GDAL中使用的所有字符串都是UTF-8格式的,而不是纯ASCII格式的。这意味着我们应该在交互会话期间将用户的输入从本地编码转换为UTF-8。对于GDAL输出,应该做相反的操作。例如,当用户将文件名作为命令行参数传递给GDAL实用程序时,该文件名应立即转换为UTF-8,并且只向GDALOpen()或OGROpen()等函数传递一个参数。所有以字符串为参数的函数都采用UTF-8(除了几个将在不同编码之间进行转换的函数,请参见实现)。对于输出函数也是如此。嵌入在GDAL中的输出函数(cpleror/CPLDebug)应该在打印之前将所有字符串从UTF-8转换为本地编码。自定义错误处理程序应注意UTF-8问题,并对传递给它们的字符串进行适当的转换。

当GDAL需要调用第三方API时,字符串编码再次弹出。UTF-8应该转换为适合该API的编码。特别是,这意味着我们应该在VSIFOpenL()的Windows实现中调用CreateFile()函数之前将UTF-8转换为UTF-16。另一个例子是PostgreSQL API。PostgreSQL在内部以UTF-8编码存储字符串,因此我们应该通知服务器传递的字符串已经是UTF-8格式的,并且它将按原样存储,不会发生任何转换和丢失。

对于文件格式驱动程序,应根据每个驱动程序计算字符串表示。并非所有文件格式都支持非ASCII字符。例如,各种.HDR标记的raster只是7位ASCII文本文件,在这样的文件中写入8位字符串不是一个好主意。当我们需要传递从驱动程序外部的此类文件中提取的字符串时(例如,在SetMetadata()调用中),我们应该将它们转换为UTF-8。如果只想在驱动程序内部使用提取的字符串,则不需要进行任何转换。

在某些情况下,文件编码可能与本地系统编码不同,除了询问用户之外,我们无法知道文件编码(例如,假设有人在上述纯文本.HDR文件中添加了8位非ASCII字符串字段)。这意味着我们不能使用从本地编码到UTF-8的转换,而是从文件编码到UTF-8的转换。因此,我们需要一种方法以某种方式获得每个数据源的文件编码。问题的自然解决方案是在GDALOpen/OGROpen函数中引入可选的开放参数“ENCODING”。不幸的是,这些函数不接受选项。这应该在另一个RFC中介绍。幸运的是,tehre不需要立即添加编码参数,因为它独立于一般的i18n进程。我们可以添加此RFC中定义的UTF-8支持,并在稍后引入open选项时添加对强制每数据源编码的支持。

实施

  • 在CPLString类中将引入新的字符转换函数。该类的对象在内部始终包含UTF-8字符串。

// Get string in local encoding from the internal UTF-8 encoded string.
// Out-of-range characters replaced with '?' in output string.
// nEncoding A codename of encoding. If 0 the local system
// encoding will be used.
char* CPLString::recode( int nEncoding = 0 );

// Construct UTF-8 string object from string in other encoding
// nEncoding A codename of encoding. If 0 the local system
// encoding will be used.
CPLString::CPLString( const char*, int nEncoding );

// Construct UTF-8 string object from array of wchar_t elements.
// Source encoding is system specific.
CPLString::CPLString( wchar_t* );

// Get string from UTF-8 encoding into array of wchar_t elements.
// Destination encoding is system specific.
operator wchar_t* (void) const;
  • 为了在用户输入中使用非ASCII字符,每个应用程序都应该在入口点之后调用setlocale(LC_ALL,“”)函数。

  • 代码示例。让我们看看gdal实用程序和核心代码应该如何相对于Unicode进行更改。

输入而不是

pszFilename = argv[i];
if( pszFilename )
    hDataset = GDALOpen( pszFilename, GA_ReadOnly );

我们应该这样做

CPLString oFilename(argv[i], 0); // <-- Conversion from local encoding to UTF-8
hDataset = GDALOpen( oFilename.c_str(), GA_ReadOnly );

用于输出而不是

printf( "Description = %s\n", GDALGetDescription(hBand) );

我们应该这样做

CPLString oDescription( GDALGetDescription(hBand) );
printf( "Description = %s\n", oDescription.recode( 0 ) ); // <-- Conversion
                            // from UTF-8 to local

在上面的代码片段中,以UTF-8编码传递给GDALOpen()的文件名将在GDAL核心中进一步处理。在Windows上而不是

hFile = CreateFile( pszFilename, dwDesiredAccess,
    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, dwCreationDisposition,
    dwFlagsAndAttributes, NULL );

我们知道

CPLString oFilename( pszFilename );
// I am prefer call the wide character version explicitly
// rather than specify _UNICODE switch.
hFile = CreateFileW( (wchar_t *)oFilename, dwDesiredAccess,
        FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
        dwCreationDisposition,  dwFlagsAndAttributes, NULL );
  • 字符转换函数的实际实现尚未在本文档中指定。这需要进一步讨论。主要的问题是我们不仅需要本地的<->UTF-8编码转换,而且 任意的 <->UTF-8型。这就需要在软件部分提供大量支持。

向后兼容性

The GDAL/OGR backward compatibility will be broken by this new functionality in the way how 8-bit characters handled. Before users may rely on that all 8-bit character strings will be passed through the GDAL/OGR without change and will contain exact the same data all the way. Now it is only true for 7-bit ASCII and 8-bit UTF-8 encoded strings. Note, that if you used only ASCII subset with GDAL, you are not affected by these changes.

根据Unicode标准,第5章:

The width of wchar_t is compiler-specific and can be as small as 8 bits. Consequently, programs that need to be portable across any C or C++ compiler should not use wchar_t for storing Unicode text.

工具书类