Combining two Xpaths into one loop?

2020-04-16 18:16发布

问题:

I'm using xpath to grab information from a document, the only issue is I havn't been able to combine them into 1 for loop so the information displays correctly on the page. My code is:

<?php

$doc = new DOMDocument;
$doc->preserveWhiteSpace = FALSE;
$doc->load('http://mdoerrdev.com/xml/updates-mits.xml');

$xpath = new DOMXPath($doc);
$xpath->registerNamespace('MITS', "http://www.mitsproject.org/namespace");

$unitName  = $xpath->evaluate("//ILS_Unit[@FloorplanID='550584']/Unit/MITS:MarketingName");
$unitPrice = $xpath->evaluate("//ILS_Unit[@FloorplanID='550584']/Unit/MITS:Information/MITS:MarketRent");

?>

    <div class="unit-name">
        <?php
        foreach ($unitName as $un) {
            echo $un->nodeValue . "\n";
        }
        ?>
    </div>

    <div class="floor-plan-box fpb-one-bedroom cleafix"> <?php
        foreach ($unitPrice as $up) {
            echo $up->nodeValue . "\n";

            ?><img src='<?php bloginfo('stylesheet_directory'); ?>/img/floorplans/<?php echo "550584" ?>.jpg' /> <?php
        };
        ?>
    </div>

(from: http://pastie.org/8360106)

I need to combine the MarketRent and MarketingName information together as opposed to having them display separately as they do in the current code.

回答1:

Use the context node argument of $xpath->evaluate()...

Your code with these adjustments:

<?php

$doc = new DOMDocument;
$doc->preserveWhiteSpace = false;
$doc->load('http://mdoerrdev.com/xml/updates-mits.xml');

$xpath = new DOMXPath($doc);
$xpath->registerNamespace('MITS', "http://www.mitsproject.org/namespace");

$units = $xpath->evaluate("//ILS_Unit[@FloorplanID='550584']/Unit");
echo "<table>";
echo "<tr><th>Marketing Name</th><th>Market Rent</th></tr>";
foreach($units as $unit)  {
  $marketingName = $xpath->evaluate("string(MITS:MarketingName)", $unit);
  $marketRent = $xpath->evaluate("string(MITS:Information/MITS:MarketRent)", $unit);
  echo "<tr>";
  echo "  <td>" . $marketingName . "</td>";
  echo "  <td>" . $marketRent . "</td>";
  echo "</tr>";
}
echo "</table>";
?>

Yields this output with MarketingName and MarketRent displayed together rather than separately per your request:



回答2:

This depends a bit on what you're trying to achieve. You can combine two xpath expressions by using the Union operator (pipe |):

$unitName = $xpath->evaluate("
(
    //ILS_Unit[@FloorplanID='550584']/Unit/MITS:MarketingName
    |//ILS_Unit[@FloorplanID='550584']/Unit/MITS:Information/MITS:MarketRent
)
");

Which then would return all evaluated nodes in document order:

<div class="unit-name">
        1265.0000
C-304
1265.0000
C-308
1255.0000
C-204
1255.0000
C-208
1230.0000
C-102
1230.0000
C-103
    </div>

Which is probably not what you're looking for. Instead you want to either evaluate over both results the same time, which can be achieved with a MultipleIterator:

$unitName  = $xpath->evaluate("//ILS_Unit[@FloorplanID='550584']/Unit/MITS:MarketingName");
$unitPrice = $xpath->evaluate("//ILS_Unit[@FloorplanID='550584']/Unit/MITS:Information/MITS:MarketRent");

$it = new MultipleIterator();
$it->attachIterator(new IteratorIterator($unitName));
$it->attachIterator(new IteratorIterator($unitPrice));

foreach($it as $value) {
    list($name, $price) = $value;
    echo " * ", $name->nodeValue, " ", $price->nodeValue, "\n";
}

Output:

 * C-304 1265.0000
 * C-308 1265.0000
 * C-204 1255.0000
 * C-208 1255.0000
 * C-102 1230.0000
 * C-103 1230.0000

This is probably more what you're looking for. Also for what you do, SimpleXMLElement might be more easy to use because it already allows to output nodes in string context.

Additionally there is another concept that directly maps shallow objects to an underlying XML document and it has been outlined here:

  • Extracting data from HTML using PHP and xPath

It is quite an interesting concept. IIRC I wrote something similar like applying one xpath query onto all nodes of another one, but I don't find it right now.



回答3:

I couldn't resist to give this another try to apply a model on some XML source. I opted for SimpleXMLElement for this answer. It is merely transparent and has mostly only implications on the xpath expressions. It should be possible to translate the same interface to DOMDocument.

$unitPrototype = new XPrototype(
    "//ILS_Unit[@FloorplanID='550584']/Unit",
    [
        'name'  => 'MITS:MarketingName',
        'price' => 'MITS:Information/MITS:MarketRent'
    ]
);

$units = new XList('http://mdoerrdev.com/xml/updates-mits.xml', $unitPrototype);

foreach ($units as $unit) {
    echo
    $unit->name, " ",
    $unit->price, "\n";
}

The source of the used types like XPrototype and XList is on Github: XObjects.php.