3
votes

I’m writing a custom parser/data extractor for some pretty shitty HTML.

Changing the HTML is out of the question.

I will spare you the details of the hoops I’ve had to jump through but I’ve now come pretty close to my original goal. I’m using a combination of DOMDocument getElementByName, regular expression replace (I know, I know...), and XPath queries.

I need to get all the text out of the body of the document. I would like for the navigation to remain a separate entity, at least in the abstract. Here’s what I’m doing now:

$contentnodes = $xpath->query("//body//*[not(self::a)]/text()|//body//ul/li/a");

foreach ($contentnodes as $contentnode) {    
    $type      = $contentnode->nodeName;
    $content   = $contentnode->nodeValue;

    $output[] = array( $type, $content);
}

This works, except that of course it treats all of the links on the page differently, and I only want it to do that to the navigation.

What XPath syntax can I use so that, in the first part of that query, before the |, I tell it to get all the text nodes of body’s children except ul > li > a.

Please note that I cannot rely on the presence of p tags or h1 tags or anything sensible like that to make educated guesses about content.

Thanks

Update: @hr_117’s answer below works. I’ve also found that you can use multiple not statements like so:

//body//text()[not(parent::a/parent::li/parent::ul)][not(parent::h1)]

2

2 Answers

2
votes

You may try something like this:

//body//text()[not(parent::a/parent::li/parent::ul)]|//body//ul/li/a
1
votes
//body//*[not(self::a/parent::li/parent::ul)]/text()[normalize-space()]|//body//ul/li/a

(test)