HtmlElement.Parent возвращает неверного родителя - PullRequest
12 голосов
/ 26 июля 2011

Я пытаюсь сгенерировать CSS-селекторы для случайных элементов на веб-странице с помощью C #.Немного предыстории:

Я использую форму с элементом управления WebBrowser.Во время навигации можно запросить CSS-селектор элемента под курсором.Получение html-элемента тривиально, конечно, с помощью:

WebBrowser.Document.GetElementFromPoint(<Point>);

Задача состоит в том, чтобы создать «строгий» селектор CSS, ведущий к элементу под курсором, а-ля:

html > body > span:eq(2) > li:eq(5) > div > div:eq(3) > span > a

Этот селектор основан на: операторах eq, так как он предназначен для обработки jQuery и / или SizzleJS (эти две поддержки: eq - оригинальные селекторы CSS не делают. Спасибо @BoltClock за помощь в разъяснении этого).Итак, вы получите картину.Чтобы достичь этой цели, мы поставляем полученный HtmlElement для метода ниже и начинаем подниматься вверх по дереву DOM, запрашивая Parent для каждого элемента, с которым мы сталкиваемся:

    private static List<String> GetStrictCssForHtmlElement(HtmlElement element)
    {
        List<String> familyTree;
        for (familyTree = new List<String>(); element != null; element = element.Parent)
        {
            string ordinalString = CalculateOrdinalPositionAmongSameTagSimblings(element);
            if (ordinalString == null) return null;

            familyTree.Add(element.TagName.ToLower() + ordinalString);
        }
        familyTree.Reverse();

        return familyTree;
    }

    private static string CalculateOrdinalPositionAmongSameTagSimblings(HtmlElement element, bool simplifyEq0 = true)
    {
        int count = 0;
        int positionAmongSameTagSimblings = -1;
        if (element.Parent != null)
        {
            foreach (HtmlElement child in element.Parent.Children)
            {
                if (element.TagName.ToLower() == child.TagName.ToLower())
                {
                    count++;
                    if (element == child)
                    {
                        positionAmongSameTagSimblings = count - 1;
                    }
                }
            }

            if (positionAmongSameTagSimblings == -1) return null; // Couldn't find child in parent's offsprings!?   
        }

        return ((count > 1) ? (":eq(" + positionAmongSameTagSimblings + ")") : ((simplifyEq0) ? ("") : (":eq(0)")));
    }

Этот метод надежно работал длямножество страниц.Однако есть одна конкретная страница, которая заставляет меня задуматься:

http://www.delicious.com/recent

Попытка получить селектор CSS любого элемента в списке (в центре страницы) не удалась для одногоочень простая причина:

После того, как вознесение достигает первого элемента SPAN на своем пути вверх (вы можете определить его, проверив страницу с помощью инструментов веб-разработчика IE9 для проверки), он пытается обработать его, вычислив его порядковое положениесреди его братьев и сестер.Для этого нам нужно попросить родительский узел для братьев и сестер.Здесь вещи становятся странными.Элемент SPAN сообщает, что его Parent является элементом DIV с id = "недавний индекс".Однако это не непосредственный родительский элемент SPAN (непосредственным родительским элементом является LI class = "wrap isAdv").Это приводит к сбою метода, потому что, что неудивительно, ему не удается обнаружить SPAN среди детей.

Но он становится еще более странным.Я извлек и изолировал HtmlElement самого SPAN.Затем я получил его Parent и использовал его, чтобы снова вернуться к элементу SPAN, используя:

HtmlElement regetSpanElement = spanElement.Parent.Children[0].Children[1].Children[1].Children[0].Children[2].Children[0];

Это вернуло нас к узлу SPAN, который мы начали ... однако с одним поворотом:

regetSpanElement.Parent.TagName;

Это теперь сообщает LI как родительский XX.Как это может быть?Есть идеи?

Еще раз спасибо заранее.

Примечания:

  1. Я сохранил HTML-код (как он представлен в WebBrowser.Document.Html) и проверил сам, чтобы быть на 100% уверенным в том, что ничего смешного не происходит (то есть, для управления WebBrowser используется другой код, чем тот, который я вижу в IE9 - но этого не происходит, структура соответствует 100% для рассматриваемого пути).

  2. Я управляю WebBrowser в IE9-режиме, используя инструкции, изложенные здесь:

    http://www.west -wind.com / weblog / posts / 2011 / May / 21/ Web-Browser-Control-Specification-the-IE-Version

    Попытка заставить элемент управления WebBrowser и IE9 работать так же, как это возможно.

  3. Iподозреваю, что наблюдаемые эффекты могут быть вызваны тем, что за моей спиной работает какой-то скрипт.Однако мои познания в области веб-программирования не так велики, чтобы их закрепить.

Редактировать: Опечатки

1 Ответ

2 голосов
/ 15 июня 2013

Полагаться на: eq () сложно! Трудно надежно повторно выбрать динамический DOM. Конечно, это может работать на очень статичных страницах, но с каждым днем ​​все становится все более динамичным. Вы можете немного изменить стратегию. Попробуйте использовать более умный и гибкий селектор. Возможно, вставьте какой-нибудь javascript примерно так:

predictCss = function(s, noid, noclass, noarrow) {
    var path, node = s;
    var psep = noarrow ? ' ' : ' > ';
    if (s.length != 1) return path; //throw 'Requires one element.';
    while (node.length) {
        var realNode = node[0];
        var name = (realNode.localName || realNode.tagName || realNode.nodeName);
        if (!name || name == '#document') break;
        name = name.toLowerCase();
        if(node.parent().children(name).length > 1){
            if (realNode.id && !noid) {
                try {
                    var idtest = $(name + '#' + realNode.id);
                    if (idtest.length == 1) return name + '#' + realNode.id + (path ? '>' + path : '');
                } catch (ex) {} // just ignore the exception, it was a bad ID
            } else if (realNode.className && !noclass) {
                name += '.' + realNode.className.split(/\s+/).join('.');
            }
        }
        var parent = node.parent();
        if (name[name.length - 1] == '.') { 
            name = name.substring(0, name.length - 1);
        }
        siblings = parent.children(name); 
        //// If you really want to use eq:
        //if (siblings.length > 1) name += ':eq(' + siblings.index(node) + ')';
        path = name + (path ? psep + path : '');
        node = parent;
    }
    return path
}

И используйте его для генерации множества селекторов:

var elem = $('#someelement');
var epath = self.model.util.predictCss(elem, true, true, false);
var epathclass = self.model.util.predictCss(elem, true, false, false);
var epathclassid = self.model.util.predictCss(elem, false, false, false);

Затем используйте каждое:

var relem= $(epathclassid);
if(relem.length === 0){
    relem = $(epathclass);
    if(relem.length === 0){
        relem = $(epath);
    }
}

И если ваш лучший селектор по-прежнему содержит более одного элемента, вам придется проявить изобретательность в том, как вы подходите элементу dom - возможно, levenshtein, или, возможно, есть какой-то конкретный текст, или вы можете вернуться к уравнению Надеюсь, это поможет!

Кстати, я предположил, что у вас есть jQuery - из-за ссылки на шипение. Вы можете добавить вышеприведенное в самозаполняющейся анонимной функции в теге скрипта, добавляемом, например, к последнему дочернему элементу тела.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...