Комплексное редактирование XML-файла - PullRequest
0 голосов
/ 19 июня 2010

Например, у нас есть этот xml:

<x>
    <y>some text</y>
    <y>[ID] hello</y>
    <y>world [/ID]</y>
    <y>some text</y>
    <y>some text</y>
</x>

и нам нужно удалить слова «[ID]», «[/ ID]» и текст между ними (чего мы не знаем при разборе), конечно, без ущерба для форматирования xml.

Единственное решение, о котором я могу подумать, это:

  1. Найти в xml текст с помощью регулярных выражений, например: "/\[ID\].*?\[\/ID\]/". В нашем случае результат будет "[ID]hello</y><y>world[/ID]"

  2. В результате предыдущего шага нам нужно найти текст без тегов xml, используя это регулярное выражение: "/(?<=^|>)[^><]+?(?=<|$)/" и удалите этот текст. Результат будет "</y><y>"

  3. Внесены изменения в исходный XML, выполнив что-то вроде этого:

    str_replace($step1string,$step2string,$xml);

это правильный способ сделать это? Я просто думаю, что это "str_replace" это не лучший способ редактировать xml, так что, может быть, вы знаете лучшее решение?

Ответы [ 3 ]

1 голос
/ 19 июня 2010

Удалить определенную строку очень просто:

<?php
$xml = '<x>
    <y>some text</y>
    <y>[ID] hello</y>
    <y>world [/ID]</y>
    <y>some text</y>
    <y>some text</y>
</x>';

$d = new DOMDocument();
$d->loadXML($xml);
$x = new DOMXPath($d);
foreach($x->query('//text()[(contains(.,\'[ID]\') or contains(.,\'[/ID]\'))]') as $elm){
    $elm->nodeValue = preg_replace('/\[\/?ID\]/','',$elm->nodeValue);
}
var_dump($d->saveXML());
?>

При удалении текстовых узлов в определенном теге можно изменить эти preg_replace на эти 2:

 $elm->nodeValue = preg_replace('/\[ID\].*$/','',$elm->nodeValue);
 $elm->nodeValue = preg_replace('/^.*\[/ID\]/','',$elm->nodeValue);

В результате для вашегопример:

<x>
<y>some text</y>
<y></y>
<y></y>
<y>some text</y>
<y>some text</y>
</x>

Однако удаление тегов между ними без повреждения правильно сформированного XML довольно сложно.Прежде чем углубляться в множество действий DOM, как бы вы хотели обработать:

An [/ ID] выше в дереве DOM:

<foo>[ID] foo
    <bar> lorem [/ID] ipsum </bar>
</foo>

An [/ ID] ниже в DOM-дереве

<foo> foo
    <bar> lorem [ID] ipsum </bar>
    [/ID]
</foo>

И открывать / закрывать охватывающие братья и сестры, как в вашем примере:

<foo> foo
    <bar> lorem [ID] ipsum </bar>
    <bar> lorem [/ID] ipsum </bar>
</foo>

И настоящий нарушитель порядкавопрос: возможно ли вложение, правильное ли это вложение и что оно должно делать?

<foo> foo
    <bar> lo  [ID] rem [ID] ipsum </bar>
    <bar> lorem [/ID] ipsum </bar>
    [/ID]
</foo>

Без дальнейшего знания того, как следует обрабатывать эти случаи, реального ответа нет.


Правка, более подробная информация была предоставлена, фактическое, отказоустойчивое решение (т. Е. Анализ XML, не использовать регулярные выражения) кажется довольно длинным, но будет работать в 99,99% случаев (личные опечатки и мозговые ошибки, конечно, исключаются:)):

<?php
$xml = '<x>
    <y>some text</y>
    <y>
      <a> something </a>
      well [ID] hello
      <a> and then some</a>
    </y>
    <y>some text</y>
    <x>
      world
      <a> also </a>
        foobar [/ID] something
      <a> these nodes </a>
    </x>
    <y>some text</y>
    <y>some text</y>
</x>';
echo $xml;
$d = new DOMDocument();
$d->loadXML($xml);
$x = new DOMXPath($d);
foreach($x->query('//text()[contains(.,\'[ID]\')]') as $elm){
        //if this node also contains [/ID], replace and be done:
        if(($startpos = strpos($elm->nodeValue,'[ID]'))!==false && $endpos = strpos($elm->nodeValue,'[/ID]',$startpos)){
                $elm->replaceData($startpos, $endpos-$startpos + 5,'');
                var_dump($d->saveXML($elm));
                continue;
        }
        //delete all siblings of this textnode not being text and having [/ID]
        while($elm->nextSibling){
                if(!($elm->nextSibling instanceof DOMTEXT) || ($pos =strpos($elm->nodeValue,'[/ID]'))===false){
                        $elm->parentNode->removeChild($elm->nextSibling);
                } else {
                        //id found in same element, replace and go to next [ID]
                        $elm->parentNode->appendChild(new DOMTExt(substr($elm->nextSibling->nodeValue,$pos+5)));
                        $elm->parentNode->removeChild($elm->nextSibling);
                        continue 2;
                }
        }
        //siblings of textnode deleted, string truncated to before [ID], now let's delete intermediate nodes
        while($sibling = $elm->parentNode->nextSibling){ // in case of example: other <y> elements:
                //loop though childnodes and search a textnode with [/ID]
                while($child = $sibling->firstChild){
                        //delete if not a textnode
                        if(!($child instanceof DOMText)){
                                $sibling->removeChild($child);
                                continue;
                        }
                        //we have text, check for [/ID]
                        if(($pos = strpos($child->nodeValue,'[/ID]'))!==false){
                                //add remaining text in textnode:
                                $elm->appendData(substr($child->nodeValue,$pos+5));
                                //remove current textnode with match:
                                $sibling->removeChild($child);
                                //sanity check: [ID] was in <y>, is [/ID]?
                                if($sibling->tagName!= $elm->parentNode->tagname){
                                        trigger_error('[/ID] found in other tag then [/ID]: '.$sibling->tagName.'<>'.$elm->parentNode->tagName, E_USER_NOTICE);
                                }
                                //add remaining childs of sibling to parent of [ID]:
                                while($sibling->firstChild){
                                        $elm->parentNode->appendChild($sibling->firstChild);
                                }
                                //delete the sibling that was found to hold [/ID]
                                $sibling->parentNode->removeChild($sibling);
                                //done: end both whiles
                                break 2;
                        }
                        //textnode, but no [/ID], so remove:
                        $sibling->removeChild($child);
                }
                //no child, no text, so no [/ID], remove:
                $elm->parentNode->parentNode->removeChild($sibling);
        }
}
var_dump($d->saveXML());
?>
1 голос
/ 19 июня 2010

Для вашего развлечения и назидания вы можете прочитать это: RegEx соответствует открытым тегам, за исключением автономных тегов XHTML

"Правильным" решением является использование библиотеки XML ипоиск по узлам для выполнения операции.Однако, вероятно, было бы намного проще просто использовать str_replace, даже если есть вероятность повредить форматирование XML.Вы должны оценить вероятность получения чего-то вроде <a href="[ID]"> и важность защиты от таких случаев и взвесить эти факторы против времени разработки.

0 голосов
/ 19 июня 2010

Единственный другой вариант, о котором я могу подумать, это если бы вы могли по-разному форматировать xml.

<x>
  <y>
    <z>[ID]</z>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...