How to replace div with one of its child p nodes - php

This html I get from the Response.
And I need to remove the extra text.
There is a line of the following content
<?php
$str = <<<HTML
AAAA <span>span txt</span>
<div class='unique_div' id='xrz' data-id='1'>
div text
<span>span text</span>
<p class='unique_p'>
<span>p span text</span>
<p>p p text</p>
</p>
div text
</div>
BBBB <span>span txt</span>
HTML;
How to replace div on p which is inside it?
I need to write a regular expression to get the following result
<?php
$str = <<<HTML
AAAA <span>span txt</span>
<p class='unique_p'>
<span>p span text</span>
<p>p p text</p>
</p>
BBBB <span>span txt</span>
HTML;
There is only one div and p with such attributes.

Since you're looking at what appears to be HTML and given that your requirements entail some form of modification to the Document Object Model (DOM) I would suggest using a DOM parser like DOMDocument.
If I understood your question correctly, you're looking to replace the <div> node which appears to have an id attribute of xrz with the p node that has a class attribute of unique_p and is a child of the div.
Getting the div is easy, because it has an id and they are unique. So we can use a method like DOMDocument::getElementById to get that div.
Getting its child p gets a little trickier since we want to make sure it's both a child of div and has the specified class. So we'll rely on an XPath query for that using DOMXPath.
Finally, we'll replace the div with its captured child p by using DOMNode::replaceChild from there.
Here's a simple example.
$str = <<<HTML
AAAA <span>span txt</span>
<div class='unique_div' id='xrz' data-id='1'>
div text
<span>span text</span>
<p class='unique_p'>
<span>p span text</span>
<p>p p text</p>
</p>
div text
</div>
BBBB <span>span txt</span>
HTML;
libxml_use_internal_errors(true);
$dom = new DOMDocument;
$dom->loadHTML($str, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$xpath = new DOMXPath($dom);
$children = $xpath->query('//div/p[#class="unique_p"]');
$p = $children->item(0);
$div = $dom->getElementById('xrz');
$div->parentNode->replaceChild($p, $div);
echo $dom->saveHTML();
The output should look something like this.
<p>AAAA <span>span txt</span>
<p class="unique_p">
<span>p span text</span>
</p><p>
BBBB <span>span txt</span></p></p>
In case you're wondering why the output may appear slightly different than what you might expect, it's important to note that your initial HTML, provided in your question, is actually malformed.
See section 9.3.1 of the HTML 4.01 specification
The P element represents a paragraph. It cannot contain block-level elements (including P itself).
So each time a DOM parser finds an opening p tag inside of another p tag it will just implicitly close the previous one first.

Related

How exclude html comments from text node xpath?

I have the follow html structure:
<a>
<div>
<div>
<span>
text node 1<br>
text node 2 <!--//comments-->
</span>
</div>
</div>
</a>
With the follow query, i get second node, but how get that node excluding comments?
$spanx = $xpath->query('//a/div/div/span/text()[2]');
$span = $spanx->item($l)->nodeValue;
echo "<td>".$span."</td></tr>";
I have that result:
text node 2 //comments
I search for:
text node 2
I've tested the following on my localhost. I've created the file named DOM_with_comment.html containing:
<a>
<div>
<div>
<span>
text node 1<br>
text node 2 <!--//comments-->
</span>
</div>
</div>
</a>
When I run:
<?php
$doc = new DOMDocument;
libxml_use_internal_errors(true);
$doc->preserveWhiteSpace = false;
$doc->loadHTMLFile('DOM_with_comment.html');
$xpath = new DOMXPath($doc);
echo "<pre>";
foreach ($xpath->query('//a/div/div/span/text()') as $item) {
var_dump($item->nodeValue);
}
The output is:
string(29) "
text node 1"
string(31) "
text node 2 "
string(14) "
"
So, by accessing the first qualifying result [0] from your xpath query then displaying the trim()ed ->nodeValue() with var_export() it is revealed that there are no comments or whitespaces on either side of the targeted substring.
var_export(trim($xpath->query('//a/div/div/span/text()[2]')[0]->nodeValue));
// outputs: 'text node 2'
p.s. If your input is not coming from a file, but a variable, this works the same way:
$html = <<<HTML
<a>
<div>
<div>
<span>
text node 1<br>
text node 2 <!--//comments-->
</span>
</div>
</div>
</a>
HTML;
$doc->loadHTML($html);

Select p tag after h2 that has a child with id

How can I select a p-tag that is after a tag that has a specific child? Using a web crawler.
http://symfony.com/doc/current/components/css_selector.html
$crawler->filter('h2 span#hello + p')->each(function ($node) {
var_dump($node->html());
});
Example:
<h2><span id="hello">Hi</span></h2>
<p>I want this p-tag, that is after the h2 above</p>
<p>me too!</p>
<a>Not me!</a>
<h2>lol</h2>
<p>yo, not me</p>
does not work.
It is usually best to traverse HTML using the DOMDocument class (http://php.net/manual/en/class.domdocument.php) but you could do it with a regular expression thus:
// put the example HTML code into a string
$html = <<< EOF
<h2><span id="hello">Hi</span></h2>
<p>I want this p-tag, that is after the h2 above</p>
<p>me too!</p>
<a>Not me!</a>
<h2>lol</h2>
<p>yo, not me</p>
EOF;
// set up a regular expression
$re = "/<h2[^>]*>.*?<span[^>]*id=\"hello\"[^>]*>.*?<\\/h2[^>]*>.*?(<p.*?)<[^\\/p]/sim";
// get the match ... the (.*?) in the above regex
preg_match($re,$html,$matches);
print $matches[1];
Would output:
<p>I want this p-tag, that is after the h2 above<p>
<p>me too!</p>

php - Simple HTML dom - elements between other elements

I'm trying to write a php script to crawl a website and keep some elements in data base.
Here is my problem : A web page is written like this :
<h2>The title 1</h2>
<p class="one_class"> Some text </p>
<p> Some interesting text </p>
<h2>The title 2</h2>
<p class="one_class"> Some text </p>
<p> Some interesting text </p>
<p class="one_class"> Some different text </p>
<p> Some other interesting text </p>
<h2>The title 3</h2>
<p class="one_class"> Some text </p>
<p> Some interesting text </p>
I want to get only the h2 and p with interesting text, not the p class="one_class".
I tried this php code :
<?php
$numberP = 0;
foreach($html->find('p') as $p)
{
$pIsOneClass = PIsOneClass($html, $p);
if($pIsOneClass == false)
{
echo $p->outertext;
$h2 = $html->find("h2", $numberP);
echo $h2->outertext;
$numberP++;
}
}
?>
the function PIsOneClass($html, $p) is :
<?php
function PIsOneClass($html, $p)
{
foreach($html->find("p.one_class") as $p_one_class)
{
if($p == $p_one_class)
{
return true;
}
}
return false;
}
?>
It doesn't work, i understand why but i don't know how to resolve it.
How can we say "I want every p without class who are between two h2 ?"
Thx a lot !
This task is easier with XPath, since you're scraping more than one element and you want to keep the source in order. You can use PHP's DOM library, which includes DOMXPath, to find and filter the elements you want:
$html = '<h2>The title 1</h2>
<p class="one_class"> Some text </p>
<p> Some interesting text </p>
<h2>The title 2</h2>
<p class="one_class"> Some text </p>
<p> Some interesting text </p>
<p class="one_class"> Some different text </p>
<p> Some other interesting text </p>
<h2>The title 3</h2>
<p class="one_class"> Some text </p>
<p> Some interesting text </p>';
# create a new DOM document and load the html
$dom = new DOMDocument;
$dom->loadHTML($html);
# create a new DOMXPath object
$xp = new DOMXPath($dom);
# search for all h2 elements and all p elements that do not have the class 'one_class'
$interest = $xp->query('//h2 | //p[not(#class="one_class")]');
# iterate through the array of search results (h2 and p elements), printing out node
# names and values
foreach ($interest as $i) {
echo "node " . $i->nodeName . ", value: " . $i->nodeValue . PHP_EOL;
}
Output:
node h2, value: The title 1
node p, value: Some interesting text
node h2, value: The title 2
node p, value: Some interesting text
node p, value: Some other interesting text
node h2, value: The title 3
node p, value: Some interesting text
As you can see, the source text stays in order, and it's easy to eliminate the nodes you don't want.
From the simpleHTML dom manual
[attribute=value]
Matches elements that have the specified attribute with a certain value.
or
[!attribute]
Matches elements that don't have the specified attribute.

Retrieve a text node with Simple HTML DOM Parser

I'm quite new to Simple HTML DOM Parser. I want to get a child element from the following HTML:
<div class="article">
<div style="text-align:justify">
<img src="image.jpg" title="image">
<br>
<br>
"Text to grab"
<div>......</div>
<br></br>
................
................
</div>
</div>
I'm trying to get the text "Text to grab"
So far I've tried the following query:
$html->find('div[class=article] div')->children(3);
But it's not working. Any idea how to solve this ?
You don't need simple_html_dom here. It can be done with DOMDocument and DOMXPath. Both are part of the PHP core.
Example:
// your sample data
$html = <<<EOF
<div class="article">
<div style="text-align:justify">
<img src="image.jpg" title="image">
<br>
<br>
"Text to grab"
<div>......</div>
<br></br>
................
................
</div>
</div>
EOF;
// create a document from the above snippet
// if you are loading from a remote url use:
// $doc->load($url);
$doc = new DOMDocument();
$doc->loadHTML($html);
// initialize a XPath selector
$selector = new DOMXPath($doc);
// get the text node (also text elements in xml/html are nodes
$query = '//div[#class="article"]/div/br[2]/following-sibling::text()[1]';
$textToGrab = $selector->query($query)->item(0);
// remove newlines on start and end using trim() and output the text
echo trim($textToGrab->nodeValue);
Output:
"Text to grab"
If it's always in the same place you can do:
$html->find('.article text', 4);

How to keep <p><img ... /></p> with XPATH?

I use XPATH to remove untidy HTML tags,
$nodeList = $xpath->query("//*[normalize-space(.)='' and not(self::br)]");
foreach($nodeList as $node)
{
$node->parentNode->removeChild($node);
}
will remove the horrible input like these,
<p><em><br /></em></p>
<p><span style="text-decoration: underline;"><em><br /></em></span></p>
but it also removes the img tag like blow that I want to keep,
<p><img title="picture summit" src="images/32913430_127001_e.jpg" alt="picture summit" width="590" height="366" /></p>
How can I keep the img tag input with XPATH?
Use:
//p[not(descendant::*[self::img or self::br]) and normalize-space()='']
Maybe you could use an XPath 1.0 expression like the one below to remove unwanted paragraphs:
//p[count(text())=0 and count(img)=0]

Categories