سؤال

لدي العديد من العناصر المتطابقة ذات السمات المختلفة التي يمكنني الوصول إليها باستخدام SimpleXML:

<data>
    <seg id="A1"/>
    <seg id="A5"/>
    <seg id="A12"/>
    <seg id="A29"/>
    <seg id="A30"/>
</data>

أحتاج إلى إزالة معينة سيج عنصر بمعرف "A12"، كيف يمكنني القيام بذلك؟لقد حاولت التكرار من خلال سيج العناصر و غير محددعند تحديد العنصر المحدد، لكن هذا لا ينجح، تبقى العناصر.

foreach($doc->seg as $seg)
{
    if($seg['id'] == 'A12')
    {
        unset($seg);
    }
}
هل كانت مفيدة؟

المحلول

بينما SimpleXML يوفر طريقة لإزالة عقد XML، قدرات التعديل الخاصة بها محدودة إلى حد ما.أحد الحلول الأخرى هو اللجوء إلى استخدام DOM امتداد. dom_import_simplexml() سوف تساعدك في تحويل الخاص بك SimpleXMLElement الى DOMElement.

فقط بعض الأمثلة على التعليمات البرمجية (تم اختبارها باستخدام PHP 5.2.5):

$data='<data>
    <seg id="A1"/>
    <seg id="A5"/>
    <seg id="A12"/>
    <seg id="A29"/>
    <seg id="A30"/>
</data>';
$doc=new SimpleXMLElement($data);
foreach($doc->seg as $seg)
{
    if($seg['id'] == 'A12') {
        $dom=dom_import_simplexml($seg);
        $dom->parentNode->removeChild($dom);
    }
}
echo $doc->asXml();

النواتج

<?xml version="1.0"?>
<data><seg id="A1"/><seg id="A5"/><seg id="A29"/><seg id="A30"/></data>

بالمناسبة:يعد تحديد عقد معينة أكثر بساطة عند استخدام XPath (SimpleXMLElement->xpath):

$segs=$doc->xpath('//seq[@id="A12"]');
if (count($segs)>=1) {
    $seg=$segs[0];
}
// same deletion procedure as above

نصائح أخرى

خلافًا للاعتقاد الشائع في الإجابات الموجودة، يمكن إزالة كل عقدة عنصر Simplexml من المستند بمفردها فقط unset().النقطة المهمة هي أنك تحتاج إلى فهم كيفية عمل SimpleXML فعليًا.

حدد أولاً العنصر الذي تريد إزالته:

list($element) = $doc->xpath('/*/seg[@id="A12"]');

ثم قم بإزالة العنصر الممثل في $element قمت بإلغاء تعيينها المرجع الذاتي:

unset($element[0]);

يعمل هذا لأن العنصر الأول في أي عنصر هو العنصر نفسه في Simplexml (مرجع ذاتي).وهذا له علاقة بطبيعته السحرية، فالمؤشرات الرقمية تمثل العناصر الموجودة في أي قائمة (على سبيل المثال:الوالدين->الأطفال)، وحتى الطفل الوحيد هو مثل هذه القائمة.

تمثل مؤشرات السلسلة غير الرقمية السمات (في الوصول إلى المصفوفة) أو العناصر الفرعية (في الوصول إلى الخاصية).

لذلك فإن الفهارس الرقمية في الوصول إلى الخاصية مثل:

unset($element->{0});

العمل كذلك.

بطبيعة الحال، مع مثال xpath هذا، يكون الأمر واضحًا إلى حدٍ ما (في PHP 5.4):

unset($doc->xpath('/*/seg[@id="A12"]')[0][0]);

رمز المثال الكامل (تجريبي):

<?php
/**
 * Remove a child with a specific attribute, in SimpleXML for PHP
 * @link http://stackoverflow.com/a/16062633/367456
 */

$data=<<<DATA
<data>
    <seg id="A1"/>
    <seg id="A5"/>
    <seg id="A12"/>
    <seg id="A29"/>
    <seg id="A30"/>
</data>
DATA;


$doc = new SimpleXMLElement($data);

unset($doc->xpath('seg[@id="A12"]')[0]->{0});

$doc->asXml('php://output');

انتاج:

<?xml version="1.0"?>
<data>
    <seg id="A1"/>
    <seg id="A5"/>

    <seg id="A29"/>
    <seg id="A30"/>
</data>

فقط قم بإلغاء تعيين العقدة:

$str = <<<STR
<a>
  <b>
    <c>
    </c>
  </b>
</a>
STR;

$xml = simplexml_load_string($str);
unset($xml –> a –> b –> c); // this would remove node c
echo $xml –> asXML(); // xml document string without node c

هذا الكود مأخوذ من كيفية حذف / إزالة العقد في SimpleXML.

أعتقد أن إجابة ستيفان صحيحة.إذا كنت تريد إزالة عقدة واحدة فقط (بدلاً من جميع العقد المطابقة)، فإليك مثال آخر:

//Load XML from file (or it could come from a POST, etc.)
$xml = simplexml_load_file('fileName.xml');

//Use XPath to find target node for removal
$target = $xml->xpath("//seg[@id=$uniqueIdToDelete]");

//If target does not exist (already deleted by someone/thing else), halt
if(!$target)
return; //Returns null

//Import simpleXml reference into Dom & do removal (removal occurs in simpleXML object)
$domRef = dom_import_simplexml($target[0]); //Select position 0 in XPath array
$domRef->parentNode->removeChild($domRef);

//Format XML to save indented tree rather than one line and save
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml->asXML());
$dom->save('fileName.xml');

لاحظ أن الأقسام تقوم بتحميل XML...(أولاً) وتنسيق XML...(أخيرًا) يمكن استبداله برمز مختلف اعتمادًا على مصدر بيانات XML الخاصة بك وما تريد فعله بالمخرجات؛إن الأقسام الموجودة بينهما هي التي تجد العقدة وتزيلها.

بالإضافة إلى ذلك، فإن عبارة if موجودة فقط للتأكد من وجود العقدة المستهدفة قبل محاولة نقلها.يمكنك اختيار طرق مختلفة للتعامل مع هذه الحالة أو تجاهلها.

هذا العمل لي:

$data = '<data>
<seg id="A1"/>
<seg id="A5"/>
<seg id="A12"/>
<seg id="A29"/>
<seg id="A30"/></data>';

$doc = new SimpleXMLElement($data);

$segarr = $doc->seg;

$count = count($segarr);

$j = 0;

for ($i = 0; $i < $count; $i++) {

    if ($segarr[$j]['id'] == 'A12') {
        unset($segarr[$j]);
        $j = $j - 1;
    }
    $j = $j + 1;
}

echo $doc->asXml();

إذا قمت بتوسيع فئة SimpleXMLElement الأساسية، فيمكنك استخدام هذه الطريقة:

class MyXML extends SimpleXMLElement {

    public function find($xpath) {
        $tmp = $this->xpath($xpath);
        return isset($tmp[0])? $tmp[0]: null;
    }

    public function remove() {
        $dom = dom_import_simplexml($this);
        return $dom->parentNode->removeChild($dom);
    }

}

// Example: removing the <bar> element with id = 1
$foo = new MyXML('<foo><bar id="1"/><bar id="2"/></foo>');
$foo->find('//bar[@id="1"]')->remove();
print $foo->asXML(); // <foo><bar id="2"/></foo>

كمرجع مستقبلي، قد يكون حذف العقد باستخدام SimpleXML أمرًا مؤلمًا في بعض الأحيان، خاصة إذا كنت لا تعرف البنية الدقيقة للمستند.لهذا السبب كتبت SimpleDOM, ، وهي فئة تعمل على توسيع SimpleXMLElement لإضافة بعض الطرق الملائمة.

على سبيل المثال، سيحذف التابعdeletNodes()‎ كافة العقد المطابقة لتعبير XPath.وإذا كنت تريد حذف جميع العقد ذات السمة "id" التي تساوي "A5"، فكل ما عليك فعله هو:

// don't forget to include SimpleDOM.php
include 'SimpleDOM.php';

// use simpledom_load_string() instead of simplexml_load_string()
$data = simpledom_load_string(
    '<data>
        <seg id="A1"/>
        <seg id="A5"/>
        <seg id="A12"/>
        <seg id="A29"/>
        <seg id="A30"/>
    </data>'
);

// and there the magic happens
$data->deleteNodes('//seg[@id="A5"]');

لإزالة/الاحتفاظ بالعقد ذات قيمة سمة معينة أو الوقوع في مجموعة من قيم السمات التي يمكنك توسيعها SimpleXMLElement فئة مثل هذا (أحدث إصدار في ملفي جيثب جوهر):

class SimpleXMLElementExtended extends SimpleXMLElement
{    
    /**
    * Removes or keeps nodes with given attributes
    *
    * @param string $attributeName
    * @param array $attributeValues
    * @param bool $keep TRUE keeps nodes and removes the rest, FALSE removes nodes and keeps the rest 
    * @return integer Number o affected nodes
    *
    * @example: $xml->o->filterAttribute('id', $products_ids); // Keeps only nodes with id attr in $products_ids
    * @see: http://stackoverflow.com/questions/17185959/simplexml-remove-nodes
    */
    public function filterAttribute($attributeName = '', $attributeValues = array(), $keepNodes = TRUE)
    {       
        $nodesToRemove = array();

        foreach($this as $node)
        {
            $attributeValue = (string)$node[$attributeName];

            if ($keepNodes)
            {
                if (!in_array($attributeValue, $attributeValues)) $nodesToRemove[] = $node;
            }
            else
            { 
                if (in_array($attributeValue, $attributeValues)) $nodesToRemove[] = $node;
            }
        }

        $result = count($nodesToRemove);

        foreach ($nodesToRemove as $node) {
            unset($node[0]);
        }

        return $result;
    }
}

ثم وجود الخاص بك $doc XML يمكنك إزالة ملف <seg id="A12"/> استدعاء العقدة:

$data='<data>
    <seg id="A1"/>
    <seg id="A5"/>
    <seg id="A12"/>
    <seg id="A29"/>
    <seg id="A30"/>
</data>';

$doc=new SimpleXMLElementExtended($data);
$doc->seg->filterAttribute('id', ['A12'], FALSE);

أو إزالة متعددة <seg /> العقد:

$doc->seg->filterAttribute('id', ['A1', 'A12', 'A29'], FALSE);

للحفظ فقط <seg id="A5"/> و <seg id="A30"/> العقد وإزالة الباقي:

$doc->seg->filterAttribute('id', ['A5', 'A30'], TRUE);

هناك طريقة لإزالة عنصر فرعي عبر SimpleXml.يبحث الرمز عن عنصر ولا يفعل شيئًا.وإلا فإنه يضيف العنصر إلى سلسلة.ثم يكتب السلسلة إلى ملف.لاحظ أيضًا أن الكود يحفظ نسخة احتياطية قبل الكتابة فوق الملف الأصلي.

$username = $_GET['delete_account'];
echo "DELETING: ".$username;
$xml = simplexml_load_file("users.xml");

$str = "<?xml version=\"1.0\"?>
<users>";
foreach($xml->children() as $child){
  if($child->getName() == "user") {
      if($username == $child['name']) {
        continue;
    } else {
        $str = $str.$child->asXML();
    }
  }
}
$str = $str."
</users>";
echo $str;

$xml->asXML("users_backup.xml");
$myFile = "users.xml";
$fh = fopen($myFile, 'w') or die("can't open file");
fwrite($fh, $str);
fclose($fh);

فكرة جديدة: simple_xml يعمل كمصفوفة.

يمكننا البحث عن فهارس "المصفوفة" التي نريد حذفها، ومن ثم استخدام unset() وظيفة لحذف فهارس المصفوفة هذه.المثال الخاص بي:

$pos=$this->xml->getXMLUser();
$i=0; $array_pos=array();
foreach($this->xml->doc->users->usr[$pos]->u_cfg_root->profiles->profile as $profile) {
    if($profile->p_timestamp=='0') { $array_pos[]=$i; }
    $i++;
}
//print_r($array_pos);
for($i=0;$i<count($array_pos);$i++) {
    unset($this->xml->doc->users->usr[$pos]->u_cfg_root->profiles->profile[$array_pos[$i]]);
}

على الرغم من أن SimpleXML لا يحتوي على طريقة مفصلة لإزالة العناصر، إلا أنه يمكنك يستطيع إزالة العناصر من SimpleXML باستخدام PHP unset().المفتاح للقيام بذلك هو إدارة استهداف العنصر المطلوب.هناك طريقة واحدة على الأقل للقيام بالاستهداف وهي استخدام ترتيب العناصر.اكتشف أولاً رقم ترتيب العنصر الذي تريد إزالته (على سبيل المثال باستخدام حلقة)، ثم قم بإزالة العنصر:

$target = false;
$i = 0;
foreach ($xml->seg as $s) {
  if ($s['id']=='A12') { $target = $i; break; }
  $i++;
}
if ($target !== false) {
  unset($xml->seg[$target]);
}

يمكنك أيضًا إزالة عناصر متعددة باستخدام هذا، عن طريق تخزين عدد ترتيب العناصر المستهدفة في مصفوفة.فقط تذكر أن تقوم بالإزالة بترتيب عكسي (array_reverse($targets))، لأن إزالة عنصر ما يؤدي بطبيعة الحال إلى تقليل عدد طلبات العناصر التي تأتي بعده.

من المسلم به أن هذا الأمر يمثل نوعًا من الاختراق، ولكن يبدو أنه يعمل بشكل جيد.

كنت أعاني أيضًا من هذه المشكلة وكانت الإجابة أسهل بكثير من تلك المقدمة هنا.يمكنك فقط البحث عنه باستخدام xpath وإلغاء ضبطه بالطريقة التالية:

unset($XML->xpath("NODESNAME[@id='test']")[0]->{0});

سيبحث هذا الرمز عن عقدة تسمى "NODESNAME" بسمة المعرف "اختبار" ويزيل التكرار الأول.

تذكر حفظ ملف XML باستخدام $XML->saveXML(...);

نظرًا لأنني واجهت نفس الخطأ الفادح الذي واجهه جيري ولم أكن على دراية بـ DOM، فقد قررت أن أفعل ذلك على النحو التالي:

$item = $xml->xpath("//seg[@id='A12']");
$page = $xml->xpath("/data");
$id = "A12";

if (  count($item)  &&  count($page) ) {
    $item = $item[0];
    $page = $page[0];

     // find the numerical index within ->children().
    $ch = $page->children();
    $ch_as_array = (array) $ch;

    if (  count($ch_as_array)  &&  isset($ch_as_array['seg'])  ) {
        $ch_as_array = $ch_as_array['seg'];
        $index_in_array = array_search($item, $ch_as_array);
        if (  ($index_in_array !== false)
          &&  ($index_in_array !== null)
          &&  isset($ch[$index_in_array])
          &&  ($ch[$index_in_array]['id'] == $id)  ) {

             // delete it!
            unset($ch[$index_in_array]);

            echo "<pre>"; var_dump($xml); echo "</pre>";
        }
    }  // end of ( if xml object successfully converted to array )
}  // end of ( valid item  AND  section )

فكرة الوظائف المساعدة موجودة في أحد التعليقات الخاصة بـ DOM php.net وفكرة استخدام unset هي من kavoir.com.بالنسبة لي نجح هذا الحل أخيرًا:

function Myunset($node)
{
 unsetChildren($node);
 $parent = $node->parentNode;
 unset($node);
}

function unsetChildren($node)
{
 while (isset($node->firstChild))
 {
 unsetChildren($node->firstChild);
 unset($node->firstChild);
 }
}

استخدامه:$xml هو SimpleXmlElement

Myunset($xml->channel->item[$i]);

يتم تخزين النتيجة في $xml، لذلك لا تقلق بشأن تخصيصها لأي متغير.

مع FluidXML يمكنك استخدام XPath لتحديد العناصر المراد إزالتها.

$doc = fluidify($doc);

$doc->remove('//*[@id="A12"]');

https://github.com/servo-php/fluidxml


XPath //*[@id="A12"] وسائل:

  • في أي نقطة من الوثيقة (//)
  • كل عقدة (*)
  • مع السمة id يساوي A12 ([@id="A12"]).

إذا كنت تريد قص قائمة العناصر الفرعية المشابهة (غير الفريدة)، على سبيل المثال عناصر موجز RSS، فيمكنك استخدام هذا الرمز:

for ( $i = 9999; $i > 10; $i--) {
    unset($xml->xpath('/rss/channel/item['. $i .']')[0]->{0});
}

سيتم قطع ذيل RSS إلى 10 عناصر.حاولت إزالة مع

for ( $i = 10; $i < 9999; $i ++ ) {
    unset($xml->xpath('/rss/channel/item[' . $i . ']')[0]->{0});
}

لكنه يعمل بطريقة عشوائية إلى حد ما ولا يقطع سوى بعض العناصر.

كان نهجك الأولي صحيحًا، لكنك نسيت شيئًا صغيرًا يتعلق بالتعلم.إنه لا يعمل على المصفوفة/الكائن الأصلي، ولكنه ينشئ نسخة من كل عنصر أثناء تكراره، لذلك قمت بإلغاء تعيين النسخة.استخدم المرجع مثل هذا:

foreach($doc->seg as &$seg) 
{
    if($seg['id'] == 'A12')
    {
        unset($seg);
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top