Search code examples
phpsimplexml

Adding child newly created xml element by attribute using SimpleXML in PHP gives error "Attribute already exists" error


I'd like to create two 'Style' children to this xml document, then add children to them. Example:

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
    <Style id="red">
        <LineStyle>
            <color>ff0000ff</color>
            <width>1</width>
        </LineStyle>
        <PolyStyle>
            <color>110000ff</color>
        </PolyStyle>
    </Style>
    <Style id="blue">
        <LineStyle>
            <color>ffff0000</color>
            <width>1</width>
        </LineStyle>
        <PolyStyle>
            <color>11ff0000</color>
        </PolyStyle>
    </Style>
</Document>
</kml>

However, I get the error

SimpleXMLElement::addAttribute(): Attribute already exists in...

$xmlstr = <<<EOL
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2"/>
EOL;

$xml = new SimpleXMLElement($xmlstr);
$xml->addChild("Document");

$xml->Document->addChild("Style");
$xml->Document->Style->addAttribute("id", "red");
$xml->Document->Style->addChild("PolyStyle");
$xml->Document->Style->PolyStyle->AddChild("color", "110000ff"); # opacity,bbggrr
$xml->Document->Style->addChild("LineStyle");
$xml->Document->Style->LineStyle->AddChild("color", "ff0000ff"); # opacity,bbggrr
$xml->Document->Style->LineStyle->AddChild("width", "1");

$xml->Document->addChild("Style");
$xml->Document->Style->addAttribute("id", "blue");
$xml->Document->Style->addChild("PolyStyle");
$xml->Document->Style->PolyStyle->AddChild("color", "11ff0000"); # opacity,bbggrr
$xml->Document->Style->addChild("LineStyle");
$xml->Document->Style->LineStyle->AddChild("color", "ffff0000"); # opacity,bbggrr
$xml->Document->Style->LineStyle->AddChild("width", "1");

I thought the solution is using xpath to find the element I just created:

$style = $xml->xpath('Style[@id="red"]');
$style[0]->addChild("LineStyle");

But $style is empty and returns

Call to a member function addChild() on null


Solution

  • The problem is that although you call

    $xml->Document->addChild("Style");
    

    twice to add a new style element, you always set the details with

    $xml->Document->Style->addAttribute("id", "red");
    

    Doing it this method will always equate to setting the id attribute on the first Style element (it will always assume the first element when multiple elements as in Style are found). So the second time you do this (with "id", "blue"), it will already exist from the first call (set to "red").

    A better way to do this is to store the return value from addChild() and then set the details in this element...

    $style2 = $xml->Document->addChild("Style");
    $style2->addAttribute("id", "blue");
    $style2->addChild("PolyStyle");
    

    etc.

    The problem with your XPath expression is that you try and identify it by an attribute which can't be added until you can find the node - so a bit awkward. You could do...

    $xml->Document->Style[1]->addAttribute("id", "red");
    

    But this means keeping track of the number of Style elements you have created and updating the [1] - which has it's risks.