使用 PHP 转换所有类型的智能引号

Posted

技术标签:

【中文标题】使用 PHP 转换所有类型的智能引号【英文标题】:Convert all types of smart quotes with PHP 【发布时间】:2013-11-30 06:14:21 【问题描述】:

在处理文本时,我正在尝试将所有类型的智能引号转换为常规引号。但是,我编译的以下函数似乎仍然缺乏支持和适当的设计。

有谁知道如何正确转换所有quote characters?

function convert_smart_quotes($string)

    $quotes = array(
        "\xC2\xAB"   => '"', // « (U+00AB) in UTF-8
        "\xC2\xBB"   => '"', // » (U+00BB) in UTF-8
        "\xE2\x80\x98" => "'", // ‘ (U+2018) in UTF-8
        "\xE2\x80\x99" => "'", // ’ (U+2019) in UTF-8
        "\xE2\x80\x9A" => "'", // ‚ (U+201A) in UTF-8
        "\xE2\x80\x9B" => "'", // ‛ (U+201B) in UTF-8
        "\xE2\x80\x9C" => '"', // “ (U+201C) in UTF-8
        "\xE2\x80\x9D" => '"', // ” (U+201D) in UTF-8
        "\xE2\x80\x9E" => '"', // „ (U+201E) in UTF-8
        "\xE2\x80\x9F" => '"', // ‟ (U+201F) in UTF-8
        "\xE2\x80\xB9" => "'", // ‹ (U+2039) in UTF-8
        "\xE2\x80\xBA" => "'", // › (U+203A) in UTF-8
    );
    $string = strtr($string, $quotes);

    // Version 2
    $search = array(
        chr(145),
        chr(146),
        chr(147),
        chr(148),
        chr(151)
    );
    $replace = array("'","'",'"','"',' - ');
    $string = str_replace($search, $replace, $string);

    // Version 3
    $string = str_replace(
        array('‘','’','“','”'),
        array("'", "'", '"', '"'),
        $string
    );

    // Version 4
    $search = array(
        '‘', 
        '’', 
        '“', 
        '”', 
        '—',
        '–',
    );
    $replace = array("'","'",'"','"',' - ', '-');
    $string = str_replace($search, $replace, $string);

    return $string;

注意:这个问题是关于包括"Microsoft" quotes asked here在内的所有引号的完整查询这是一个“重复”,就像询问所有轮胎尺寸是询问汽车轮胎的“重复”一样大小。

【问题讨论】:

替换智能引号的目的是什么?通常最好保存它们;如果您在处理字符时遇到问题,那么很可能您对所有其他非 ASCII 字符也有问题,这些问题不会通过隐藏智能引号而消失。这段代码尝试将文本同时处理为 UTF-8 和 ISO-8859-1,同时处理原始文本和 html,这是一项混乱的业务,通常会严重破坏许多其他 Unicode 字符,而不仅仅是引号。 @bobince,我正在解析字符串,引号字符对我来说很重要。我确实按原样处理其余的 unicode 字形。 @bobince 我很乐意提供一个也能处理其他字符的答案 - 但我担心的是识别所有引号字形,这样我就可以解析字符串而无需担心许多其他形式。 您要进行哪种解析,需要将不同类型的引号转换为一种?将例如 ‘don't’ 转换为使用所有撇号似乎会使解析变得更加困难。 是的,如果您的输入绝对是 HTML 格式的文本内容,那将可以正常工作。有一个细微的区别:在非基于 XML 的 HTML 中,€Ÿ€ÿ)范围内的字符引用被 Web 浏览器解码为具有相同编号的 Windows 代码的字符第 1252 页代码单元,而不是您期望的字符 U+0080 到 U+00FF。 php 不会重现这种历史怪癖,并且会在字符串中为这些格式错误的引用留下与号序列。 【参考方案1】:

你可以使用这个函数来转换所有字符:

$output = iconv('UTF-8', 'ASCII//TRANSLIT', $input);

确保将您的类型更改为您需要的类型。

(注意:这是来自另一个类似的问题here)。

【讨论】:

需要明确的是,这不仅仅是转换智能引号,因此可能会产生意想不到的后果。【参考方案2】:

你需要这样的东西(假设 UTF-8 输入,忽略 CJK(中文、日文、韩文)):

$chr_map = array(
   // Windows codepage 1252
   "\xC2\x82" => "'", // U+0082⇒U+201A single low-9 quotation mark
   "\xC2\x84" => '"', // U+0084⇒U+201E double low-9 quotation mark
   "\xC2\x8B" => "'", // U+008B⇒U+2039 single left-pointing angle quotation mark
   "\xC2\x91" => "'", // U+0091⇒U+2018 left single quotation mark
   "\xC2\x92" => "'", // U+0092⇒U+2019 right single quotation mark
   "\xC2\x93" => '"', // U+0093⇒U+201C left double quotation mark
   "\xC2\x94" => '"', // U+0094⇒U+201D right double quotation mark
   "\xC2\x9B" => "'", // U+009B⇒U+203A single right-pointing angle quotation mark

   // Regular Unicode     // U+0022 quotation mark (")
                          // U+0027 apostrophe     (')
   "\xC2\xAB"     => '"', // U+00AB left-pointing double angle quotation mark
   "\xC2\xBB"     => '"', // U+00BB right-pointing double angle quotation mark
   "\xE2\x80\x98" => "'", // U+2018 left single quotation mark
   "\xE2\x80\x99" => "'", // U+2019 right single quotation mark
   "\xE2\x80\x9A" => "'", // U+201A single low-9 quotation mark
   "\xE2\x80\x9B" => "'", // U+201B single high-reversed-9 quotation mark
   "\xE2\x80\x9C" => '"', // U+201C left double quotation mark
   "\xE2\x80\x9D" => '"', // U+201D right double quotation mark
   "\xE2\x80\x9E" => '"', // U+201E double low-9 quotation mark
   "\xE2\x80\x9F" => '"', // U+201F double high-reversed-9 quotation mark
   "\xE2\x80\xB9" => "'", // U+2039 single left-pointing angle quotation mark
   "\xE2\x80\xBA" => "'", // U+203A single right-pointing angle quotation mark
);
$chr = array_keys  ($chr_map); // but: for efficiency you should
$rpl = array_values($chr_map); // pre-calculate these two arrays
$str = str_replace($chr, $rpl, html_entity_decode($str, ENT_QUOTES, "UTF-8"));

背景来了:

每个 Unicode 字符只属于一个"General Category",其中可以包含引号字符的有:

Ps"Punctuation, Open" Pe"Punctuation, Close" Pi"Punctuation, Initial quote (may behave like Ps or Pe depending on usage)" Pf"Punctuation, Final quote (may behave like Ps or Pe depending on usage)" Po"Punctuation, Other"

(这些页面可以方便地检查您是否没有遗漏任何内容 - 还有一个 index of categories)

在启用 Unicode 的正则表达式中 match these categories 有时很有用。

此外,Unicode 字符有"properties",其中你感兴趣的是Quotation_Mark。不幸的是,这些不能在正则表达式中访问。

在 Wikipedia 中,您可以找到 group of characters with the Quotation_Mark property。最终参考是 unicode.org 上的 PropList.txt,但这是一个 ASCII 文本文件。

如果您还需要翻译 CJK 字符,您只需获取它们的代码点,决定它们的翻译,并找到它们的 UTF-8 编码,例如,通过在 fileformat.info 中查找(例如,对于 U+ 301E:http://www.fileformat.info/info/unicode/char/301e/index.htm)。

关于 Windows 代码页 1252:Unicode 定义了前 256 个代码点来表示与 ISO-8859-1 完全相同的字符,但 ISO-8859-1 经常与 Windows codepage 1252 混淆,因此所有浏览器都呈现范围 0x80 -0x9F,在 ISO-8859-1 中是“空的”(更准确地说:它包含控制字符),就好像它是 Windows 代码页 1252。The table in the Wikipedia page 列出了 Unicode 等效项。

注意:strtr() 通常比str_replace() 慢。用你的输入和你的 PHP 版本来计时。如果速度够快,可以直接用我的$chr_map之类的地图。


如果您不确定您的输入是 UTF-8 编码的,并且愿意假设如果不是,那么它是 ISO-8859-1 或 Windows 代码页 1252,那么您可以先执行此操作:

if ( !preg_match('/^\\X*$/u', $str)) 
   $str = utf8_encode($str);

警告:不过,此正则表达式在极少数情况下可能无法检测到非 UTF-8 编码。例如:"Gruß…"/*CP-1252*/=="Gru\xDF\x85" 在这个正则表达式中看起来像 UTF-8(U+07C5 是 N'ko 数字 5)。这个正则表达式可以稍微增强,但不幸的是它可以证明对于编码检测问题没有完全万无一失的解决方案。


如果您想将源自 Windows 代码页 1252 的 0x80-0x9F 范围标准化为常规 Unicode 代码点,您可以这样做(并删除上面 $chr_map 的第一部分):

$normalization_map = array(
   "\xC2\x80" => "\xE2\x82\xAC", // U+20AC Euro sign
   "\xC2\x82" => "\xE2\x80\x9A", // U+201A single low-9 quotation mark
   "\xC2\x83" => "\xC6\x92",     // U+0192 latin small letter f with hook
   "\xC2\x84" => "\xE2\x80\x9E", // U+201E double low-9 quotation mark
   "\xC2\x85" => "\xE2\x80\xA6", // U+2026 horizontal ellipsis
   "\xC2\x86" => "\xE2\x80\xA0", // U+2020 dagger
   "\xC2\x87" => "\xE2\x80\xA1", // U+2021 double dagger
   "\xC2\x88" => "\xCB\x86",     // U+02C6 modifier letter circumflex accent
   "\xC2\x89" => "\xE2\x80\xB0", // U+2030 per mille sign
   "\xC2\x8A" => "\xC5\xA0",     // U+0160 latin capital letter s with caron
   "\xC2\x8B" => "\xE2\x80\xB9", // U+2039 single left-pointing angle quotation mark
   "\xC2\x8C" => "\xC5\x92",     // U+0152 latin capital ligature oe
   "\xC2\x8E" => "\xC5\xBD",     // U+017D latin capital letter z with caron
   "\xC2\x91" => "\xE2\x80\x98", // U+2018 left single quotation mark
   "\xC2\x92" => "\xE2\x80\x99", // U+2019 right single quotation mark
   "\xC2\x93" => "\xE2\x80\x9C", // U+201C left double quotation mark
   "\xC2\x94" => "\xE2\x80\x9D", // U+201D right double quotation mark
   "\xC2\x95" => "\xE2\x80\xA2", // U+2022 bullet
   "\xC2\x96" => "\xE2\x80\x93", // U+2013 en dash
   "\xC2\x97" => "\xE2\x80\x94", // U+2014 em dash
   "\xC2\x98" => "\xCB\x9C",     // U+02DC small tilde
   "\xC2\x99" => "\xE2\x84\xA2", // U+2122 trade mark sign
   "\xC2\x9A" => "\xC5\xA1",     // U+0161 latin small letter s with caron
   "\xC2\x9B" => "\xE2\x80\xBA", // U+203A single right-pointing angle quotation mark
   "\xC2\x9C" => "\xC5\x93",     // U+0153 latin small ligature oe
   "\xC2\x9E" => "\xC5\xBE",     // U+017E latin small letter z with caron
   "\xC2\x9F" => "\xC5\xB8",     // U+0178 latin capital letter y with diaeresis
);
$chr = array_keys  ($normalization_map); // but: for efficiency you should
$rpl = array_values($normalization_map); // pre-calculate these two arrays
$str = str_replace($chr, $rpl, $str);

【讨论】:

@SebastiánGrignoli,您可以在此处阅读:regular-expressions.info/unicode.html#grapheme 正如它所说:“您可以考虑\X Unicode 版本的点”。更准确地说,它匹配 UTF-8 非修饰符字符(可选地后跟修饰符),从开始 (^) 到结束 ($)。我不知道它是否还检查修饰符对它们修改的字符的有效性,但可以肯定的是,它会检查整个字符串是否由有效的 UTF-8 字节序列(编码有效的 Unicode 代码点)组成,并且它没有以修饰符开头。 @SebastiánGrignoli,对不起,我应该说“组合标记”(\pM)而不是“修饰符” @WalterTross - 非常感谢 - 我正在寻找一些开箱即用的解决方案,但找不到。相反,我为此目的创建了一个包 - 使用上述部分内容 - 希望你不介意。 github.com/sebastiansulinski/smart-quotes 网络上对这个问题的唯一完整且正确的答案(可能不是真的,但你知道我的意思)。太糟糕了,它在相关搜索中的排名不高。 @FrankForte 总体上是正确的,但如果你仔细阅读,我已经写了“在其他任何事情之前

以上是关于使用 PHP 转换所有类型的智能引号的主要内容,如果未能解决你的问题,请参考以下文章

我可以使用 iconv 将多字节智能引号转换为扩展的 ASCII 智能引号吗?

将“直引号”转换为“弯引号”

仅针对文本而非 HTML 代码将哑引号转换为智能引号

PHP 和 FFMPEG - 执行智能视频转换

JSON 编码和大引号

js脚本语言基础和数组