Search code examples
phpxmlxpathxml-parsingsimplexml

SimpleXML xpath to element with certain attribute value?


I have some XML like this:

<item>
    <custom-attributes>
      <custom-attribute attribute-id="taco">false</custom-attribute>
      <custom-attribute attribute-id="htmlContent" xml:lang="en-US">testValue</custom-attribute>
      <custom-attribute attribute-id="htmlContent" xml:lang="default">testing123</custom-attribute>
  </custom-attribute>
</item>

How do I use xpath to find the custom-attribute element than has an attribute-id of taco?

How do I use xpath to find the custom-attribute element(s) than have attribute-ids of htmlContent? There are 2 in this case. Also these have namespaces on them. I'm not sure if that matters or not.

I have tried something like this:

var_dump($xml->xpath("custom-attribute[@attribute-id='taco']"));

But this doesn't work. I can iterate thru the custom-attribute elements and look for what I need, but it would be a lot easier to simply select it via xpath.

What am I missing here?


Solution

  • Add double slashes to the beginning of your xpath:

    $source = <<<X
    <item>
        <custom-attributes>
          <custom-attribute attribute-id="taco">false</custom-attribute>
          <custom-attribute attribute-id="htmlContent" xml:lang="en-US">testValue</custom-attribute>
          <custom-attribute attribute-id="htmlContent" xml:lang="default">testing123</custom-attribute>
      </custom-attributes>
    </item>
    X;
    
    $xml = new SimpleXMLElement($source);
    
    $taco             = $xml->xpath("//custom-attribute[@attribute-id='taco']")[0];
    $htmlContentArray = $xml->xpath("//custom-attribute[@attribute-id='htmlContent']");
    
    echo "taco       : ", $taco, PHP_EOL;
    echo "htmlContent: ", implode(', ', $htmlContentArray), PHP_EOL;
    

    Output:

    taco       : false
    htmlContent: testValue, testing123
    

    Update

    For your question in the comments, regarding searching within an item node; you can use .// to start the search from the current node.

    // Find an item, then do an xpath on the result of that
    // to find the custom attribute element.
    // <items> tag added as <item> would otherwise be the root element,
    // which would make the example quite pointless.
    $source = <<<X
    <items>
        <item>
            <custom-attributes>
                <custom-attribute attribute-id="taco">false</custom-attribute>
                <custom-attribute attribute-id="htmlContent" xml:lang="en-US">testValue</custom-attribute>
                <custom-attribute attribute-id="htmlContent" xml:lang="default">testing123</custom-attribute>
            </custom-attributes>
        </item>
        <item>
            <custom-attributes>
                <custom-attribute attribute-id="taco">true</custom-attribute>
                <custom-attribute attribute-id="htmlContent" xml:lang="en-US">item 2 testValue</custom-attribute>
                <custom-attribute attribute-id="htmlContent" xml:lang="default">item 2 testing456</custom-attribute>
              </custom-attributes>
        </item>
    </items>
    X;
    
    $xml = new SimpleXMLElement($source);
    
    $item             = $xml->item[1]; // Get second item
    $taco             = $item->xpath(".//custom-attribute[@attribute-id='taco']");
    $htmlContentArray = $item->xpath(".//custom-attribute[@attribute-id='htmlContent']");
    
    echo "taco from second item: ", $taco[0], PHP_EOL;
    echo "html from second item: ", implode(', ', $htmlContentArray), PHP_EOL;
    

    Output:

    taco from second item: true
    html from second item: item 2 testValue, item 2 testing456