tags:

views:

47

answers:

2

I have the following XML structure:

<?xml version="1.0" encoding="ISO-8859-1"?>
<articles>
  <article id="1">
    <title>Article title 001</title>
    <short>Short text</short>
    <long>Long text</long>
  </article>
  <article id="2">
    <title>Article title 002</title>
    <short>Short text</short>
    <long>Long text</long>
  </article>
</articles>

I want to select only <title> and <short>.

Currently using this to display everything:

 $queryResult = $xpathvar->query('//articles/article'); // works fine grabs all articles
 foreach($queryResult as $result){
   echo $result->textContent;
 }

The expected output would be:

Article title 001

Short text

Any assistance would be greatly appreciated.

Working solution!

if ($artId == "") {
    $queryResult = $xpathvar->query('//articles/article/*'); // grab all children
    foreach($queryResult as $result){
      if($result->nodeName === 'title' || $result->nodeName === 'short') {
          echo $result->textContent;
      }
    }
}else{
    $queryResult = $xpathvar->query(sprintf('//articles/article[@id="%s"]/*', $artId)); // Show requested article
    foreach($queryResult as $result){
      if($result->nodeName === 'title' || $result->nodeName === 'long') {
          echo $result->textContent;
      }
    }
}
+3  A: 

You can use

/articles/article/*[name()="title" or name()="short"]

which would only return children of any "articles/article" with an element name of "title" or "short".


As an alternative, change the XPath to /articles/article/* to fetch all childNodes of article and when iterating $results check if DOMNode::nodeName is "title" or "short", e.g.

$queryResult = $xpathvar->query('/articles/article/*'); // grab all children
foreach($queryResult as $result){
  if($result->nodeName === 'title' || $result->nodeName === 'short') {
      echo $result->textContent;
  }
}

If you dont want to change the XPath, you have to iterate the childNodes of the article, e.g.

$queryResult = $xpathvar->query('/articles/article');
foreach($queryResult as $result) {
  foreach($result->childNodes as $child) {       
  if($child->nodeName === 'title' || $child->nodeName === 'short') {
      echo $child->textContent;
  }
}
Gordon
Thanks Gordon, Your first solution works, but... I also need to be able to select just one article based on ID and display Title and long text.
StefWill
@StefWill The XPath would be `//articles/article[@id=1]/*[name()="title" or name()="long"]`. Have a look at this [Xpath Tutorial](http://www.w3schools.com/XPath/default.asp)
Gordon
@Gordon: +1 Good answer. But, please, don't start an expression with `//` operator when schema is well known.
Alejandro
@Alejando thanks, also for the edits. Actually, I just copied over the XPath and didnt bother about the inital part of it. I know you keep telling me that :) But does it make a difference whether one uses the direct path or the double slashes when the document is just as shown above? Or put different: at what document size or structure does it really start to have an impact?
Gordon
+1  A: 

Use:

/*/*/*[self::title or self::short]

or if the title and short children of a specific article with known @id (say '2') should be displayed:

/*/article[@id='2']/*[self::title or self::short]

Always try to avoid using the // abbreviation when this is possible (when the structure of the XML document is known).

Using // very often results in grossly-inefficient evaluation, because // causes the whole (sub) tree rooted in the current node to be searched.

Dimitre Novatchev
+1 for `self::title` instead of `name()='title'`
LarsH