JavaScript:如何检查字符是否为RTL?

24
如何在JavaScript中以编程方式检查浏览器是否将某些字符视为RTL?
也许可以创建一个透明的DIV并查看文本放置的位置吗?
有一些背景信息。Unicode 5.2添加了对Avestan字母表的支持。因此,如果浏览器支持Unicode 5.2,它会将像U+10B00这样的字符视为RTL(目前只有Firefox支持)。否则,它将把这些字符视为LTR,因为这是默认设置。
如何以编程方式检查这一点?我正在编写一个Avestan输入脚本,并希望在浏览器太笨的情况下重写双向文本方向。但是,如果浏览器支持Unicode,则不应覆盖双向文本方向设置(因为这将允许混合使用Avestan和Cyrillic)。
我目前是这样做的:
var ua = navigator.userAgent.toLowerCase();

if (ua.match('webkit') || ua.match('presto') || ua.match('trident')) {
    var input = document.getElementById('orig');
    if (input) {
        input.style.direction = 'rtl';
        input.style.unicodeBidi = 'bidi-override';
    }
}

但很明显,在Chrome和Opera开始支持Unicode 5.2之后,这将使脚本的可用性降低。


1
你无法通过编程方式检查浏览器如何呈现某个字符。这可能取决于底层操作系统,或者浏览器可能具有自己的渲染代码(例如,我认为Windows上的Safari不使用Windows操作系统文本渲染器)。如果你很幸运,你可能会找到一个资源告诉你每个浏览器版本支持哪个Unicode版本。你可以检查给定字符是否是RTL,但你需要找到一个JavaScript Unicode库或从UnicodeData.txtbsearch()获取数据。 - hippietrail
好的,有17种是从右到左书写的语言,所以你可以检查keydown事件的keyCode并将其与这17种语言的键码范围进行匹配...http://en.wikipedia.org/wiki/Right-to-left - vsync
可能是更改文本框的文本方向自动化的重复问题。 - Iman Mahmoudinasab
6个回答

36
function isRTL(s){           
    var ltrChars    = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF'+'\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF',
        rtlChars    = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC',
        rtlDirCheck = new RegExp('^[^'+ltrChars+']*['+rtlChars+']');

    return rtlDirCheck.test(s);
};

游乐场页面


@Javid - 我不记得了。为什么? - vsync
@vsync 我非常好奇。你是查阅了Unicode文档还是从其他地方复制的呢? - Javid
1
@Javid - 我可能在其他地方找到了那些代码,并围绕它构建了代码。那是5年前的事情,所以我真的记不太清楚了..你问这个问题是因为你认为有一些代码可能丢失了吗? - vsync
1
我在这里找到了文档:http://www.unicode.org/Public/UNIDATA/extracted/DerivedBidiClass.txt - tanghao
@boazlevinson - 它所做的一切只是提供给您一个函数,该函数获取输入(一个字符),并且输出对于“rtl”为“true”。您对此测试的操作以及何时进行测试取决于您。您可以在整个字符串或按键入字符的基础上应用此函数。 - vsync
显示剩余6条评论

9

我知道这篇文章的提问和回答已经过去了一段时间,但我发现vsync的更新非常有用,想要补充一些观察结果。我本来想在他的回答中添加评论,但我的声望还不够高。

与其使用正则表达式从行首开始搜索零个或多个非LTR字符,然后再加上一个RTL字符,不如从行首开始搜索零个或多个弱/中性字符,然后再加上一个RTL字符,这样就可以避免匹配许多不必要的RTL字符。我欢迎对我的弱/中性字符组进行更彻底的检查,因为我仅仅使用了LTR和RTL字符组的否定。

此外,应该将LTR/RTL标记、嵌入和覆盖等字符包含在相应的字符组中吗?

我认为最终代码应该类似于:

function isRTL(s){           
    var weakChars       = '\u0000-\u0040\u005B-\u0060\u007B-\u00BF\u00D7\u00F7\u02B9-\u02FF\u2000-\u2BFF\u2010-\u2029\u202C\u202F-\u2BFF',
        rtlChars        = '\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC',
        rtlDirCheck     = new RegExp('^['+weakChars+']*['+rtlChars+']');

    return rtlDirCheck.test(s);
};

更新

有一些方法可以加快上述正则表达式的速度。使用带有惰性量词的否定字符类似乎有助于提高速度(在 http://regexhero.net/tester/?id=6dab761c-2517-4d20-9652-6d801623eeec 上测试,该网站需要 Silverlight 5)。

此外,如果字符串的方向性未知,则我猜对于大多数情况,字符串将是从左到右而不是从右到左,如果是这种情况,则创建一个 isLTR 函数将更快地返回结果,但由于 OP 要求 isRTL,因此提供 isRTL 函数:

function isRTL(s){           
    var rtlChars        = '\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC',
        rtlDirCheck     = new RegExp('^[^'+rtlChars+']*?['+rtlChars+']');

    return rtlDirCheck.test(s);
};

你可以在 jsPERF 上测试它。顺便说一句,我已经测试了你的函数,它们不起作用... 你可以在我的 playground 页面上测试它们,在我的回答中。 - vsync

3

测试希伯来语和阿拉伯语(我了解的唯一现代从右到左流动的语言/字符集,除了任何波斯语相关的,我没有研究过):

/[\u0590-\u06FF]/.test(textarea.value)

更多的研究表明以下内容:

/[\u0590-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]/.test(textarea.value)

2

首先回答标题中的问题:

JavaScript本身没有用于访问字符的Unicode属性的工具。您需要找到一个库或服务来实现此目的(如果您需要可靠性,则可能会很困难),或者从Unicode字符“数据库”(一组特定格式的文本文件)中提取相关信息并编写自己的代码来使用它。

然后是消息正文中的问题:

这似乎更加令人绝望。但由于这可能只针对少数精通阿维斯陀语的用户,因此显示一串阿维斯陀语字符及其正确方向的图像,并要求用户在顺序错误时单击按钮,也许不会太糟糕。您可以将此选择保存在cookie中,以便用户仅需执行一次(每个浏览器;尽管应该是相对较短寿命的cookie,因为浏览器可能会更新)。


我知道这不是一件容易的事情。但是,我希望它可以以某种方式完成。我目前正在检查是否可以创建一个包含两个span的隐藏div,获取它们的边界矩形并比较X坐标。如果这样可以实现,我会在这里写下来。 - Kryzhovnik

2

感谢您的评论,但我似乎已经自己完成了这个任务:

function is_script_rtl(t) {
    var d, s1, s2, bodies;

    //If the browser doesn’t support this, it probably doesn’t support Unicode 5.2
    if (!("getBoundingClientRect" in document.documentElement))
        return false;

    //Set up a testing DIV
    d = document.createElement('div');
    d.style.position = 'absolute';
    d.style.visibility = 'hidden';
    d.style.width = 'auto';
    d.style.height = 'auto';
    d.style.fontSize = '10px';
    d.style.fontFamily = "'Ahuramzda'";
    d.appendChild(document.createTextNode(t));

    s1 = document.createElement("span");
    s1.appendChild(document.createTextNode(t));
    d.appendChild(s1);

    s2 = document.createElement("span");
    s2.appendChild(document.createTextNode(t));
    d.appendChild(s2);

    d.appendChild(document.createTextNode(t));

    bodies = document.getElementsByTagName('body');
    if (bodies) {
        var body, r1, r2;

        body = bodies[0];
        body.appendChild(d);
        var r1 = s1.getBoundingClientRect();
        var r2 = s2.getBoundingClientRect();
        body.removeChild(d);

        return r1.left > r2.left;
    }

    return false;   
}

使用示例:

Avestan in <script>document.write(is_script_rtl('') ? "RTL" : "LTR")</script>,
Arabic is <script>document.write(is_script_rtl('العربية') ? "RTL" : "LTR")</script>,
English is <script>document.write(is_script_rtl('English') ? "RTL" : "LTR")</script>.

看起来它运作正常。:)


1
是的,测量页面元素布局是我能想到的检测支持的唯一方法。 我建议使用offsetLeft而不是getBoundingClientRect,因为浏览器支持更好。 - bobince
谢谢,我会用到它。但是我发现另一个问题:Opera在页面上将阿维斯陀语布局为RTL,但在文本区域中将其布局为LTR!:( - Kryzhovnik

0
这里有另一种解决方案,它可以有效地处理主要为LTR字符串的少量RTL文本或RTL字符串中的少量LTR文本。
它通过计算LTR或RTL字符的数量,然后根据LTR或RTL字符的数量多少对字符串进行分类来实现。
isRTL(text) {
  let rtl_count = (text.match(/[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/g) || []).length;
  let ltr_count = (text.match(/[A-Za-z\u00C0-\u00C0\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF]/g) || []).length;

  return (rtl_count > ltr_count);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接