0
votes

I'm trying to select nodes which don't contain any text. This bit of php code skips the empty node in the sample xml. However, when I try an online tester (like http://freeformatter.com/xpath-tester.html) it doesn't have any problem.

Is this a PHP thing?

My php code:

    $path = "//RecipeSteps/RecipeStep[not(text())]";
    $stepsQuery = $this->xpath->query($path);
    $numResults = $stepsQuery->length;

My sample xml:

<?xml version="1.0" encoding="utf-8"?>
<Recipes>
    <RecipeSteps>
      <RecipeStep number="1">Dummy content</RecipeStep>
      <RecipeStep number="2">Dummy content</RecipeStep>
      <RecipeStep number="3">Dummy content</RecipeStep>
      <RecipeStep number="4">Dummy content</RecipeStep>
      <RecipeStep number="5">Dummy content</RecipeStep>
      <RecipeStep number="6"></RecipeStep>
      <RecipeStep number="7">Variations</RecipeStep>
      <RecipeStep number="8">Some variation content..</RecipeStep>
    </RecipeSteps>
</Recipes>
3
i do not know why that is not working, but this should work: $emptyRecipeSteps=call_user_func(function() use (&$DOMDocument){$ret=array();foreach($DOMDocument->getElementsByTagName("RecipeStep") as &$recipeStep){if(empty($recipeStep->textContent){$ret[]=$recipeStep;}}return $ret;}); - hanshenrik
PHP and the online example you cite give exactly the same result: 3v4l.org/bKuOd - So what did you bother about? - hakre

3 Answers

1
votes

If you are looking for a XPATH solution, use //RecipeSteps/(RecipeStep[string-length() = 0]). e.g

$path = "//RecipeSteps/(RecipeStep[string-length() = 0])";
$stepsQuery = $this->xpath->query($path);
$numResults = $stepsQuery->length;
0
votes

When selecting full path it work:

$xmlString = '<?xml version="1.0" encoding="utf-8"?>
<Recipes>
    <RecipeSteps>
      <RecipeStep number="1">Dummy content</RecipeStep>
      <RecipeStep number="2">Dummy content</RecipeStep>
      <RecipeStep number="3">Dummy content</RecipeStep>
      <RecipeStep number="4">Dummy content</RecipeStep>
      <RecipeStep number="5">Dummy content</RecipeStep>
      <RecipeStep number="6"></RecipeStep>
      <RecipeStep number="7">Variations</RecipeStep>
      <RecipeStep number="8">Some variation content..</RecipeStep>
    </RecipeSteps>
</Recipes>';

$dom = new DOMDocument();
$dom->loadXML($xmlString);
$xpath = new DOMXpath($dom);
# it works also well: //RecipeSteps/RecipeStep[not(text())]
$query = $xpath->query('//Recipes/RecipeSteps/RecipeStep[not(text())]');
//returns "6"
print 'RecipeStep number: ' . $query->item(0)->getAttribute('number');

Also, selecting "//RecipeSteps/RecipeStep[not(text())]" works like a charm also well. So most likely you're doing something wrong.

0
votes

The path expressions //RecipeStep[not(text())] and //RecipeStep[string-length() = 0] do not mean the same, but taking as input the document you have shown, they return exactly the same. In both cases, one RecipeStep node is selected as the result:

<RecipeStep number="6"/>

//RecipeStep[not(text())] means, in plain English:

Select element nodes called RecipeStep anywhere in the document, but only if they do not have any immediate child text nodes.

On the other hand, //RecipeStep[string-length() = 0] means

Select element nodes called RecipeStep anywhere in the document, but only if the length of their string value (the concatenation of all descendant text nodes) is equal to 0.

The difference would only be apparent if recipe step number 6 actually looked like

<RecipeStep number="6"><child>text</child></RecipeStep>

Then, //RecipeStep[not(text())] would still select this node, whereas //RecipeStep[string-length() = 0] would not return anything.

(And just to make it clear: the leading //RecipeSteps that I have omitted does not change anything.)

So, your original XPath expression is correct - and the accepted answer does exactly the same as your original one. XPath ist not at fault here.