通过JavaScript检测文档中的希伯来词 [英] Detecting Hebrew words in document via JavaScript

查看:91
本文介绍了通过JavaScript检测文档中的希伯来词的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是一个大多数新手,当涉及到Web开发(虽然不是一般编程),所以赦免任何不正确的术语。

I'm a mostly-newbie when it comes to web development (though not to programming in general) so pardon any incorrect terminology.

我想建立一个脚本,当添加到HTML页面时,检测页面中的每个希伯来语单词并将该单词转换为HTML元素,例如成为超级链接的标题。

I want to build a script that, when added to an HTML page, detects each Hebrew word in the page and transforms that word into an HTML element, e.g. into a hyperlink with title.

所以,以下:

<p>ראש הלשכה</p>

转换为:

<p><a title="word 1" href="#">הלשכה</a> <a title="word 2" href="#">ראש</a></p>

有意义吗?

所以,我假设第一个业务顺序是在页面中检测希伯来词。我该怎么做??我不知道从哪里开始,除了在jQuery文档附近戳。

So, I suppose the first order of business is detecting Hebrew words in a page. How would I go about doing this? I don't know where to start, outside of poking around jQuery documentation.

推荐答案

在字符串中搜索希伯来文字是相当简单使用符合希伯来语代码点的连续序列的正则表达式:

Searching for a Hebrew word in a string is fairly simple. Use a regexp that matches a contiguous sequence of Hebrew code points:

/[\u05D0-\u05FF]+/

由于JS支持功能编程,我们可以轻松地编写我们自己的函数来走文档树,调用函数在每个文本节点上。首先,一些脚手架。

Since JS supports functional programming, we can easily write our own functions to walk the document tree, calling a function on each text node. First, a bit of scaffolding.

if (! window.assert) {
    window.dbgLvl = 1; // change this to 0 for production release
    window.assert=function(succeeded, msg) {
        if (dbgLvl && !succeeded) {
            if (!msg) msg = 'assertion failed';
            throw msg;
        }
    }
}

接下来,我们定义一个方法将字符串拆分成数组,包括输出中的分隔符。

Next, we define a method to split strings into an array, including separators in the output.

/* String.separate is like String.split, but the result includes the 
   separators.

   These implementations of 'String.separate' will work for our purposes,
   but are buggy in general, due to differences in the implementation of
   String.split.

   The two misbehaviors we correct are including neither grouped patterns 
   nor empty strings in the result, though the latter is only corrected
   when the missing empty string is at the start or the end.
*/
if ('-'.split(/(-)/).length & 1) {
    assert('a'.split(/a/).length, 'split includes grouping but not empty strings');
    // split includes groups in result
    String.prototype.separate = function (separator) {
        if (typeof separator == 'string') {
            if (separator.charAt(0) != '(' 
                || separator.charAt(separator.length-1) != ')')
            {
                separator = new RegExp('(' + separator + ')', 'g');
            } else {
                separator = new RegExp(separator, 'g');
            }
        }
        return this.split(separator);
    }
} else {
    if ('a'.split(/a/).length) {
        // empty strings included, grouped aren't 
        String.prototype.separate = function (separator) {
            if (typeof separator == 'string') {
                separator = new RegExp(separator, 'g');
            }
            var fence = this.match(separator);
            if (!fence) {
                return [this];
            }
            var posts = this.split(separator);
            assert(posts.length = fence.length+1);
            var result = [], i;
            for (i=0; i<fence.length; ++i) {
                result.push(posts[i]);
                result.push(fence[i]);
            }
            result.push(posts[i]);
            return result;
        }
    } else {
        // neither empty strings nor groups are included. IE, you suck.
        String.prototype.separate = function (separator) {
            if (typeof separator == 'string') {
                separator = new RegExp(separator, 'g');
            }
            var fence = this.match(separator);
            if (!fence) {
                return [this];
            }
            var posts = this.split(separator);
            if (posts.length <= fence.length) {
                /* missing some posts. Assume that they are the first or 
                   last, though this won't be true in general.
                */
                if (posts.length < fence.length) {
                    posts.unshift('');
                    posts.push('');
                } else {
                    if (this.substring(0, fence[0].length) == fence[0]) {
                        posts.unshift('');
                    } else {
                        posts.push('');
                    }
                }
            }
            var result = [], i;
            for (i=0; i<fence.length; ++i) {
                result.push(posts[i]);
                result.push(fence[i]);
            }
            result.push(posts[i]);
            return result;
        }
    }
}

接下来,一些节点谓词。

Next, some node predicates.

if (! window.Node) {
    window.Node={TEXT_NODE: 3};
} else if (typeof Node.TEXT_NODE == 'undefined') {
    Node.TEXT_NODE = 3;
}

function isTextNode(node) {return node.nodeType == Node.TEXT_NODE;}
function hasKids(node) {return node.childNodes && node.childNodes.length;}
function allNodes(node) {return true;}

现在有DOM的功能。

/*
  forEachChild: pre-order traversal of document tree. Applies a function to some nodes, determined by the 'which' and 'descendInto' arguments.

Arguments:
  which  (function): Returns true if 'action' should be applied to a node.
  action (function): Takes a node and does something to it.
  parent (Node): The node to start from.
  descendInto (function, optional): By default, forEachChild will descend into every child that itself has children. Place additional restrictions by passing this argument.
*/
var forEachChild = (function() {
        /* the actual implementation is made a local function so that the
           optional parameter can be handled efficiently.
         */
        function _forEachChild(which, action, node, descendInto) {
            for (var child=node.firstChild; child; child=child.nextSibling) {
                if (which(child)) {
                    action(child);
                }
                if (hasKids(child) && descendInto(child)) {
                    _forEachChild(which, action, child, descendInto);
                }
            }
        }
        return function (which, action, node, descendInto) {
            if (!descendInto) {descendInto=allNodes}
            _forEachChild(which, action, node, descendInto);
        }
    })();

function forEachNode(which, action, descendInto) {
    return forEachChild(which, action, document, descendInto);
}

function forEachTextNode(action, descendInto) {
    return forEachNode(isTextNode, action, descendInto);
}

function forEachTextNodeInBody(action, descendInto) {
    return forEachChild(isTextNode, action, document.body, descendInto);
}

最后一组函数将文本节点中的文本替换为与模式匹配的文本您选择的新节点。该组(以及由 wrapText 返回的函数)尚未完全测试跨浏览器兼容性,包括是否正确处理文本方向。

The last group of functions replace text in a text node that matches a pattern with a new node of your choosing. This group (well, the function returned by wrapText) hasn't been completely tested for cross-browser compatibility, including whether it handles text direction properly.

/* 
   wrapText replaces substrings in a text node with new nodes.

 Arguments:
   pattern (RegExp || string): If a RegExp, must be of the form: '/(...)/g'.
   replace (function): Takes a string and returns a Node to replace the string.

Returns a function that takes a text node.
*/
function wrapText(pattern, replace) {
    return function (node) {
        var chunks = node.nodeValue.separate(pattern);
        if (chunks.length < 2)
            return;
        var wordCount=0;
        var fragment = document.createDocumentFragment();
        var i;
        // don't bother adding first chunk if it's empty.
        if (chunks[0].length) {
            fragment.appendChild(document.createTextNode(chunks[0]));
        }
        for (i=1; i < chunks.length; i+=2) {
            fragment.appendChild(replace(chunks[i])); // †
            fragment.appendChild(document.createTextNode(chunks[i+1])); // ‡
        }
        // clean-up
        assert(i == chunks.length, 'even number of chunks in ['+chunks+'] when it should be odd.');
        /* chunks.length and i will always be odd, thus i == chunks.length
         * when the loop finishes. This means the last element is never
         * missed. 
         * Here's another way of thinking about this. Since the last 
         * (and first) chunk won't match the pattern, it won't be 
         * processed by the line †. The penultimate chunk, however, does
         * match. Assuming the loop condition is correct,the penultimate 
         * chunk must be processed by †, hence the last chunk is 
         * processed by ‡.
         */
        if (! chunks[i-1].length) {
            // last chunk is empty; remove it.
            fragment.removeChild(fragment.lastChild);
        }
        node.parentNode.replaceChild(fragment, node);
    }
}

/*
  createAnchorWrap wraps a string in an anchor node. createAnchorWrap also
  sets the title of the anchor.

Arguments:
  title (string || function, optional): The title for the anchor element. 
      If title is a function, it's called with the string to wrap. If 
      title is a string, wrapper will use a word counter for the title 
      function.

Returns a function that takes a string and returns an anchor element.
 */
function createAnchorWrap(title) {
    if (typeof title == 'string') {
        title=createWordCounter(title);
    } else if (!title) {
        title=createWordCounter();
    }
    return function(word) {
        var a = document.createElement('a');
        a.title=title(word);
        a.appendChild(document.createTextNode(word));
        return a;
    }
}

/*
  createWordCounter creates a word counter, which returns the number of 
  times it's been called (including the current call), prefixed by a string.

Arguments:
  pre (string, optional): prefix for return value.

Returns a function that takes a string (ignored) and returns a string.

 */
function createWordCounter(pre) {
    var wordCount=0;
    if (pre) {
        pre = pre.replace(/ *$/, ' ');
    } else {
        pre = 'word ';
    }
    return function(text) {
        return pre + wordCount;
    }
}

最后一件事是开始进程在(例如)加载处理程序或页面底部的脚本。

The last thing you have to do is start the process in (e.g.) a load handler or a script at the bottom of the page.

forEachTextNodeInBody(wrapText(/([\u05D0-\u05FF]+)/g,
                               createAnchorWrap()));

如果要更改标题的前缀,请传递 createWordCounter(...) createAnchorWrap

If you want to change the prefix for the title, pass the result of createWordCounter(...) to the createAnchorWrap.

这篇关于通过JavaScript检测文档中的希伯来词的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆