xpath match first and last child

Clash Royale CLAN TAG#URR8PPPxpath match first and last child
Trying to determine if any P tag text's is entirely within strong/B tags
// Match (unacceptable, flag to user):
<p><strong>Any text and <span>maybe</span> other <em>tags</em></strong></p>
// Don't match (acceptable):
<p>Any text and <strong>maybe</strong> other <em>tags</em></p>
string(//p)
string(//p/strong)
4 Answers
4
p
//p
strong
//p[.//strong]
//p[.//strong[normalize-space(.) != ""]]
and no text node descendant with content that has no strong ancestor node:
strong
//p[
.//strong[normalize-space(.) != ""] and
not(.//text()[normalize-space(.) != "" and not(ancestor::strong)])
]
This checks for two conditions. First that the paragraph has some actual content that is inside strong and that is has no actual content that has not inside a strong - in other word content that is formatted differently.
strong
strong
Example:
$html = <<<'HTML'
<p><strong>Any text and <span>maybe</span> other <em>tags</em></strong></p>
<p>Any text and <strong>maybe</strong> other <em>tags</em></p>
<p><strong>Builder's</strong> <strong>tea</strong></p>
<p><em><strong>Builder's</strong> <strong> tea</strong></em></p>
HTML;
$document = new DOMDocument();
$document->loadHTML($html);
$xpath = new DOMXpath($document);
$expression =
'//p[
.//strong[normalize-space(.) != ""] and
not(.//text()[normalize-space(.) != "" and not(ancestor::strong)])
]';
foreach ($xpath->evaluate($expression) as $p) {
var_dump(
$document->saveXml($p)
);
}
Output:
string(75) "<p><strong>Any text and <span>maybe</span> other <em>tags</em></strong></p>"
string(54) "<p><strong>Builder's</strong> <strong>tea</strong></p>"
string(64) "<p><em><strong>Builder's</strong> <strong> tea</strong></em></p>"
The expression can be extended to cover b as well:
b
//p[
(
.//strong[normalize-space(.) != ""] or
.//b[normalize-space(.) != ""]
) and
not(
.//text()[
normalize-space(.) != "" and
not(ancestor::*[self::strong or self::b])
]
)
]
Here's one way, partly based on @gangabass' suggestion. It counts the <p> elements that only contain a single <strong> element that are optionally only surrounded by whitespace text.
<p>
<strong>
$unacceptableNodesCount = $xpath->evaluate( 'count(//p[count(*) = 1 and name(*) = "strong" and normalize-space() = string(strong)])' );
var_dump( $unacceptableNodesCount );
To be honest though, if the goal is to prevent your users from using merely bold text and your users are determined, they'll probably find a way. For instance by surrounding the <strong> element with Unicode whitespace chars, or something similar.
<strong>
It's not that the users are determined. Instead usually, lazy (or unaware). Web CMS uses TinyMCE text editor. User pastes their content from Word document, not actually using the correct heading tags, using bolded text for headings. I'm writing a SEO report for the given text. Flagging that the user should use the correct heading styles given by the TinyMCE editor rather than bolded paragraphs. A small section of bolded text within a paragraph is of course acceptable.
– Christopher Aitken
Jul 24 at 4:20
Your problem description suggests you also want to catch
<p><strong>Builder's</strong><strong> tea</strong></p>
and perhaps also
<p><strong>Builder's</strong> <strong>tea</strong></p>
which aren't caught by some of the suggested solutions.
But it's not clear whether you also want to catch
<p><emph><strong>Builder's</strong> <strong> tea</strong></emph></p>
I think the closest to "any P tag text's is entirely within strong/B tags" in XPath 2.0 is
//p[empty(.//text()[normalize-space()] except .//strong//text()])]
which selects all p elements having no non-white descendant text node that is not a descendant of a strong element within the p.
I can't immediately see a way of doing this in XPath 1.0, but my XPath 1.0 is very rusty.
The following code checks that the P tag contains no text or other HTML tags before and after any Strong tags, determining that the P tag is entirely bold (strong).
$false_headings = $xpath->query("//p/strong");
foreach ($false_headings as $heading) {
if ($heading->previousSibling === null and $heading->nextSibling === null) {
// Report to user
break;
}
}
While this code snippet may solve the question, including an explanation really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.
– 31piy
Jul 23 at 5:45
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
You can compare
string(//p)andstring(//p/strong)– gangabass
Jul 23 at 2:36