Rectangle 27 1

This answer might be useful for getting your output truncated to the nearest word, and then simply append a () HTML entity onto the end of the output to get your final output.

As you've noticed there's not sufficiently wide browser support yet the CSS solution yet, and you've still got to worry about old browsers too.

html - Truncating long strings with ellipsis via PHP or CSS in Firefox...

php html css firefox
Rectangle 27 7

Gordon's recommendation to use a PHP function from within XPath will prove more flexible should you choose to use that. However, contrary to his answer, the translate string function is available in XPath 1.0 so that means you can use it; your problem is how.

First, there is the obvious typo that Charles pointed out in his comment to the question. Then there is the logic of how you're trying to match the text values.

//line[contains(translate(text(),'ABC...Z','abc...z'),'chicago')]

The above lowercases the text contained within the line node then checks that it (the lowercased text) contains the keyword chicago.

And now for the obligatory code snippet (but really, the above idea is what you really need to take home):

$xml    = simplexml_load_file($data);
$search = strtolower($keyword);
$nodes  = $xml->xpath("//line[contains(translate(text(), 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz'), '$search')]");

echo 'Got ' . count($nodes) . ' matches!' . PHP_EOL;
foreach ($nodes as $node){
   echo $node . PHP_EOL;
}

Inside the foreach, you could access the line number, chapter number and book name like below.

Line number -- this is just an attribute on the <line> element which makes accessing it super-easy. There are two ways, with SimpleXML, of accessing it: $node['number'] or $node->attributes()->number (I prefer the former).

Chapter number -- to get at this, as you rightly said, we need to traverse up the tree. If we were using the DOM classes, we would have a handy $node->parentNode property leading us directly to the <chapter> (since it is the immediate ancestor to our <line>). SimpleXML does not have such a handy property, but we can use a relative XPath query to get it. The parent axis allows us to traverse up the tree.

Since xpath() returns an array we can cheat and use current() to access the first (and only) item in the array returned from it. Then it is just a matter of accessing the number attribute as above.

// In the near future we can use: current(...)['number'] but not yet
$chapter = current($node->xpath('./parent::chapter'))->attributes()->number;

Book name -- the process for this is the same as that of accessing the chapter number. A relative XPath query from the <line> could make use of the ancestor axis like ./ancestor::book (or ./parent:chapter/parent::book). Hopefully you can figure out how to access its name attribute.

Thanks for the detailed explanation of how it works, in addition to the code snippet. Exactly what I was looking for! I have been using mainly simpleXML for this project but it's nice to have Gordon's answer below to compare.

One thing I would LOVE to know :) is within that foreach clause, How would I also go about listing the line-number, chapter-number and book name? I believe this is also xpath based on current node and navigating up the tree? for instance, (from first XML example) I'd like to search for 'atlanta' and receive: School Years, Chapter 1: Here's a line that talks about Atlanta. once again, trial and error has been tying me in knots!

thanks again! Since I'm working with simpleXML I never would have stumbled upon the 'current()' "cheat" as you call it. I knew about the axes but could never figure out how to describe the starting point. I always appreciate getting an explanation along with code. That way I learn something!

case insensitive xpath searching in php - Stack Overflow

php xpath