在Javascript中查找DOM元素所在行号的方法是什么?

18

虽然我没听说过这个问题,但是使用JS从DOM中检索节点并找出该节点在文件的哪一行出现是可能的吗?

我可以尝试任何方法,包括替代浏览器插件/附加组件等...它不需要跨浏览器。

我认为这应该是可能的,因为一些JS调试器可以找到脚本标签内的行号,但我不完全确定。


我在我的答案中添加了链接,指向一个插件版本的代码,你可能会感兴趣。 - Rob Van Dam
4个回答

8

好的,抱歉内容有点长。我认为这是一个非常有趣的问题,但是在尝试过程中,我很快意识到innerHTML等方法在维护空格、注释等方面相当不可靠。因此,我回到了拉取完整源代码的方式,以确保我获取了完整的源代码。然后,我使用了jQuery和一些(相对较小的)正则表达式来查找每个节点的位置。它似乎运行良好,尽管我肯定会错过一些边缘情况。是的,是的,正则表达式又出现了两个问题,什么什么的。

编辑:作为构建jQuery插件的练习,我修改了我的代码,使其可以作为独立的插件运行,并提供了一个类似下面HTML的示例。我试图使代码稍微更加健壮(例如现在处理引号字符串内部的标签,例如onclick),但最大的剩余bug是它无法解决页面的任何修改,例如添加元素。我可能需要使用iframe而不是ajax调用来处理这种情况。

<html>
    <head id="node0">
    <!-- first comment -->
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
        <style id="node1">
/*          div { border: 1px solid black; } */
            pre { border: 1px solid black; }
        </style>
    <!-- second comment -->
        <script>
            $(function() {

                // fetch and display source
                var source;
                $.ajax({
                    url: location.href,
                    type: 'get',
                    dataType: 'text',
                    success: function(data) {
                        source = data;


                        var lines = data.split(/\r?\n/);
                        var html = $.map(lines, function(line, i) {
                            return ['<span id="line_number_', i, '"><strong>', i, ':</strong> ', line.replace(/</g, '&lt;').replace(/>/g, '&gt;'), '</span>'].join('');
                        }).join('\n');

                        // now sanitize the raw html so you don't get false hits in code or comments
                        var inside = false;
                        var tag = '';
                        var closing = {
                            xmp: '<\\/\\s*xmp\\s*>',
                            script: '<\\/\\s*script\\s*>',
                            '!--': '-->'
                        };
                        var clean_source = $.map(lines, function(line) {
                            if (inside && line.match(closing[tag])) {
                                var re = new RegExp('.*(' + closing[tag] + ')', 'i');
                                line = line.replace(re, "$1");
                                inside = false;
                            } else if (inside) {
                                line = '';
                            }

                            if (line.match(/<(script|!--)/)) {
                                tag = RegExp.$1;
                                line = line.replace(/<(script|xmp|!--)[^>]*.*(<(\/(script|xmp)|--)?>)/i, "<$1>$2");
                                var re = new RegExp(closing[tag], 'i');
                                inside = ! (re).test(line);
                            }
                            return line;
                        });

                        // nodes we're looking for
                        var nodes = $.map([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(num) { return $('#node' + num) });

                        // now find each desired node in both the DOM and the source
                        var line_numbers = $.map(nodes, function(node) {
                            var tag = node.attr('tagName');
                            var tags = $(tag);
                            var index = tags.index(node) + 1;

                            var count = 0;
                            for (var i = 0; i < clean_source.length; i++) {
                                var re = new RegExp('<' + tag, 'gi');
                                var matches = clean_source[i].match(re);
                                if (matches && matches.length) {
                                    count += matches.length;
                                    if (count >= index) {
                                        console.debug(node, tag, index, count, i);
                                        return i;
                                    }
                                }
                            }


                            return count;
                        });

                        // saved till end to avoid affecting source html
                        $('#source_pretty').html(html);
                        $('#source_raw').text(source);
                        $('#source_clean').text(clean_source.join('\n'));

                        $.each(line_numbers, function() { $('#line_number_' + this).css('background-color', 'orange'); });
                    },
                });

                var false_matches = [
                    "<div>",
                    "<div>",
                    "</div>",
                    "</div>"
                ].join('');

            });
        </script>
    </head>
    <!-- third comment -->
    <body id="node2">
        <div>
            <pre id="source_pretty">
            </pre>
            <pre id="source_raw">
            </pre>
            <pre id="source_clean">
            </pre>
        </div>

        <div id="node3">
            <xmp>
                <code>
                // <xmp> is deprecated, you should put it in <code> instead
                </code>
            </xmp>
        </div>

    <!-- fourth comment -->
<div><div><div><div><div><div><span><div id="node4"><span><span><b><em>
<i><strong><pre></pre></strong></i><div><div id="node5"><div></div></div></div></em>
</b></span><span><span id="node6"></span></span></span></div></span></div></div></div></div></div></div>


        <div>
            <div>
                <div id="node7">
                    <div>
                        <div>
                            <div id="node8">
                                <span>
    <!-- fifth comment -->
                                    <div>
                                        <span>
                                            <span>
                                                <b>
                                                    <em id="node9">
                                                        <i>
                                                            <strong>
                                                                <pre>
                                                                </pre>
                                                            </strong>
                                                        </i>
                                                        <div>
                                                            <div>
                                                                <div>
                                                                </div>
                                                            </div>
                                                        </div>
                                                    </em>
                                                </b>
                                            </span>
                                            <span>
                                                <span id="node10">
                                                </span>
                                            </span>
                                        </span>
                                    </div>
                                </span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

也许我们应该继续尝试这段代码并进行微调,直到我们做对为止。你用的是哪个浏览器? - leeand00
(因为我只是要一个能够工作的解决方案,而不是跨浏览器的解决方案。) - leeand00
嗯...等一下,我不能保证这些节点都有id。 我想要做这件事的原因是自动删除大量HTML页面中节点的内联样式属性(我已经继承了这些页面)。我还想用类替换这些内联属性,并将该类添加到CSS文件或样式属性中。 这只是整个过程的一部分。 - leeand00
我只在Linux上的Firefox中测试过这个,但考虑到它使用了jquery,如果它是相当跨浏览器的,我会感到惊讶。而且它不需要ids(实际上只需查找id="foo"会更快),我只是因为懒惰而在我的示例中使用了ids。 - Rob Van Dam
1
只需将节点替换为 var nodes = $('div, script, span');(然后通过 $(node).attr('tagName') 获取标签),就可以看到它适用于您可能在 dom 中拥有的任意节点,因此对于您的情况,var nodes = $('[style]'); - Rob Van Dam
@Rob 好的!太好了,Rob!你介意我把这个链接发给jQuery的人吗?谁知道...也许他们会感兴趣。另外,那是你发布代码的网站吗?我的网站也是这样...即将推出...是的。 - leeand00

1

这是可以完成的。首先获取文档中的最高节点,如下所示:

var htmlNode = document.getElementsByTagName('html')[0];
var node = htmlNode;
while (node.previousSibling !== null) {
    node = node.previousSibling;
}
var firstNode = node;

(此代码已经测试并检索到了doctype节点以及html节点上方的注释)

然后,您需要循环遍历所有节点(包括兄弟节点和子节点)。在IE中,您只能看到元素和注释(而不是文本节点),所以最好使用FF或Chrome或其他浏览器(您说它不必跨浏览器)。

当您到达每个文本节点时,请解析它以查找换行符。


4
如果元素标记中有换行符,例如在属性之间,你会不会错过这些换行符? - thejoshwolfe

1

像这样的东西?

var wholeDocument = document.getElementsByTagName('html')[0]
var findNode = document.getElementById('whatever')
var documentUpToFindNode = wholeDocument.substr(0, wholeDocument.indexOf(findNode.outerHTML))
var nlsUpToFindNode = documentUpToFindNode.match(/\n/g).length

值 + 1 ... 或者 + x,如果页面在标准模式下呈现,并且有DOCTYPE和/或在HTML开放标记之前的附加注释。 - scunliffe
1
outerHTML是一个非标准的、仅适用于IE浏览器的属性,因此在其他浏览器中无法使用,并且正如@J-P所指出的那样,不保证唯一性。 - Tim Down
2
你好,我来自未来的六年后。outerHTML现在已经成为标准,无处不在。祝愉快! - EricP

-2

你可以尝试以下方法:

 - start at the 'whatever' node, 
 - traverse to each previous node back to the doc begining while concatenating the html of each node, 
 - then count the new lines in your collected HTML.

当你理解了代码,就把它发布出来,因为那是一个好问题 :)


这假设每行恰好有一个HTML节点,我不确定这会起作用。 - FiniteLooper
不,它并不会。但是比我最初说的要容易:你只需要将body html作为字符串抓取,使用元素id在要获取行号的位置进行分割,然后计算拆分的第一个成员中换行符的数量即可 ;) - ekerner

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