Как этот запрос xpath (PHP) может быть более гибким? - PullRequest
1 голос
/ 28 февраля 2010

Я анализирую документ XHTML, используя PHP SimpleXML. Мне нужно запросить серию ul в документе для узла, содержащего определенное значение, а затем найти прямой родительский узел этого узла ... код поможет объяснить!

Учитывая следующий фиктивный xhtml:

<html>
<head></head>
<body>
...

<ul class="attr-list"> 
    <li>Active Life (active)</li> 
    <ul> 
        <li>Amateur Sports Teams (amateursportsteams)</li> 
        <li>Amusement Parks (amusementparks)</li> 
        <li>Fitness & Instruction (fitness)</li> 
        <ul> 
            <li>Dance Studios (dancestudio)</li> 
            <li>Gyms (gyms)</li> 
            <li>Martial Arts (martialarts)</li> 
            <li>Pilates (pilates)</li> 
            <li>Swimming Lessons/Schools (swimminglessons)</li>  
        </ul> 
        <li>Go Karts (gokarts)</li> 
        <li>Mini Golf (mini_golf)</li> 
        <li>Parks (parks)</li> 
        <ul> 
            <li>Dog Parks (dog_parks)</li> 
            <li>Skate Parks (skate_parks)</li> 
        </ul> 
        <li>Playgrounds (playgrounds)</li> 
        <li>Rafting/Kayaking (rafting)</li> 
        <li>Tennis (tennis)</li> 
        <li>Zoos (zoos)</li> 
    </ul> 
    <li>Arts & Entertainment (arts)</li> 
    <ul> 
        <li>Arcades (arcades)</li> 
        <li>Art Galleries (galleries)</li> 
        <li>Wineries (wineries)</li> 
    </ul> 
    <li>Automotive (auto)</li> 
    <ul> 
        <li>Auto Detailing (auto_detailing)</li> 
        <li>Auto Glass Services (autoglass)</li> 
        <li>Auto Parts & Supplies (autopartssupplies)</li> 
    </ul>
    <li>Nightlife (nightlife)</li>
    <ul>
        <li>Bars (bars)</li>
        <ul>
            <li>Dive Bars (divebars)</li>
        </ul>
    </ul>
</ul>

...
</body>
</html>

Мне нужно иметь возможность запросить список ul.attr для дочернего элемента и обнаружить его «корневую» категорию. Я не могу изменить xhtml, чтобы он формировался по-другому.

Итак, если у меня есть «галереи» в качестве категории, мне нужно знать, что она относится к категории «искусство» «корневая». Или, если у меня есть «dog_parks», мне нужно знать, что он находится в категории «активные». Следующий код выполняет работу, но только при условии, что в максимуме есть два вложенных уровня:

function get_root_category($shortCategoryName){

    $url = "http://www.yelp.com/developers/documentation/category_list";
    $result = file_get_contents($url);

    $dom = new domDocument();
    @$dom->loadHTML($result);
    $dom->preserveWhiteSpace = false;

    $sxml = simplexml_import_dom($dom);

    $lvl1 = $sxml->xpath("//li[contains(., '".$shortCategoryName."')]/parent::ul/preceding-sibling::li");
    $lvl2 = $sxml->xpath("//li[contains(., '".$shortCategoryName."')]/parent::ul/preceding-sibling::li/parent::ul/preceding-sibling::li");

    if($lvl2){
        return array_pop($lvl2);
    } else {
        return array_pop($lvl1);
    }
}

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

EDIT :: Спасибо тем, кто указал, что этот HTML-код недействителен. Однако структура страницы задана, и я не могу ее редактировать; Я могу использовать его только как ресурс, и я должен сделать то, что есть.

Ответы [ 2 ]

1 голос
/ 01 марта 2010

Мне нужно запросить серию ul в документ для узла, содержащий конкретное значение, а затем найти этот узел прямой родительский родной брат ...

Это будет (здесь $v - это значение, которое вы ищете):

$p = "/html/body//ul[li[contains(text(), '$v')]]/preceding-sibling::li[1]";
  • Убедитесь, что вы проверили, что $v не содержит одинарных кавычек, поскольку это нарушит выражение XPath.
  • Если вы хотите искать только целые слова, используйте:
    [contains(concat(' ', text(), ' '), concat(' ', '$v', ' '))].
  • Если вы хотите выглядеть без учета регистра, используйте (я сократил полный алфавит с ):
    [contains(translate(text(), 'ABC…XYZ', 'abc…xyz'), '{strtolower($v)}')].
  • Обратите внимание, что предикаты могут быть вложенными.
  • Обратите внимание, что использование text() обеспечивает учет только прямых дочерних текстовых узлов. Когда вместо этого вы используете ., все «поддерево» <li> преобразуется в строку, и вы можете получить больше результатов, чем вы на самом деле хотите.
  • Обратите внимание, что я ограничил оператор // (ярлык для оси descendant) определенной частью дерева - если вы можете ограничить его дальше, во что бы то ни стало.
    Разрешение XPath начинаться с // делает его намного медленнее, чем нужно, поскольку проверяются все узлы всего документа, даже те, которые ни при каких обстоятельствах не дают совпадения.
  • Как уже отмечали другие, HTML-код недействителен.
1 голос
/ 01 марта 2010

Как насчет:

/html/body/ul/ul[count(descendant::li[contains(.,'dog_parks')]) > 0]/preceding-sibling::li

Это должно работать с глубоко вложенными списками. Он всегда получает самую верхнюю категорию.

Кстати: я не думаю, что вложение ul похоже на это.

...