Недавно мне пришлось провести массовое преобразование HTML- файлов в DITA XML — материал, который я написал для будущей справки по JavaScript Ultimate (третья и, пожалуй, самая сложная часть справочника SitePoint ).
Но проблема, с которой я сталкивался несколько раз, заключалась в явной сложности рекурсивного преобразования элементов — <code>
становится <jsvalue>
(или одним из дюжины похожих элементов), <a>
становится <xref>
… и все это достаточно просто; но каждый из этих элементов может содержать другие или дополнительные дочерние элементы, такие как <em>
, и по мере того, как мы проходим через DOM, вероятность потенциальной рекурсии увеличивается, пока не дойдет до точки, где мой мозг взрывается.
Есть предел тому, сколько рекурсии я могу обдумать — или, скорее, — предел того, насколько я подготовлен, чтобы обдумать, прежде чем я просто пошлю на это, почему я не могу исказить это как текст с регулярные выражения!?
К сожалению, в PHP DOM нет способа получить текстовый эквивалент любого произвольного узла, но мы можем сделать это на уровне Document
или DocumentFragment
; поэтому, немного поиграв, я нашел способ использовать эту возможность и заставить ее работать на уровне Node
.
Например, давайте начнем с этого XML :
<?xml version="1.0" encoding="utf-8"?> <root id="introduction"> <div class="section"> The fundamental data type is <code>Node</code> </div> </root>
У нас есть ссылка на его DOM, сохраненную в переменной PHP с именем $xmldom
. И мы хотим проанализировать его так, чтобы элемент <code>
стал <jstype>
, а <div class="section">
стал просто <section>
, и все это не повлияло на остальную часть документа.
Вот полный код для этого, о котором я расскажу поэтапно:
$node = $xmldom->documentElement->firstChild; $doc = new DOMDocument(); $doc->loadXML('<xmltext/>'); $node = $doc->importNode($node, true); $doc->documentElement->appendChild($node); $xmltext = ereg_replace('^.*<xmltext>(.*)</xmltext>.*$', '\1', $doc->saveXML()); $xmltext = ereg_replace('<([/]?)code>', '<\1jstype>', $xmltext); $xmltext = ereg_replace('<([/]?)div[^>]*>', '<\1section>', $xmltext); $node = $xmldom->createDocumentFragment(); $node->appendXML($xmltext); $xmldom->documentElement->replaceChild($node, $xmldom->documentElement->firstChild);
На первом шаге мы получаем ссылку на элемент, с которым мы хотим работать, и сохраняем его в $node
.
На втором шаге мы создаем новый документ и используем loadXML()
для создания корневого узла-заполнителя ( метод loadXML
преобразует ввод текста в XML и является одним из краеугольных камней нашего процесса). Затем мы импортируем исходный узел в этот документ, затем используем saveXML()
для преобразования всего документа в текст ( метод saveXML
преобразует документ XML в текст, и он так же важен, как loadXML()
для того, что мы здесь делаем). Текстовый вывод анализируется с использованием ereg_replace
для удаления внешнего содержимого документа (его пролога и корневого узла), так что у нас остается текстовый эквивалент исходного узла ввода.
На третьем этапе мы делаем все, что нужно для текстового искажения; в данном случае это простые преобразования имен элементов, но это может быть что угодно.
На четвертом шаге мы хотим преобразовать наш разобранный текст обратно в XML , и мы делаем это, создавая фрагмент документа, затем используя appendXML()
для загрузки текста и его преобразования в XML ( метод appendXML
делает то же самое, что и loadXML()
, но не требует создания всего документа).
Наконец, на пятом шаге мы объединяем обработанный XML обратно в наш оригинальный документ. Фрагмент документа имеет в качестве владельца исходный документ, поэтому мы можем просто использовать метод replaceChild
для замены исходного узла и его дочерних replaceChild
обработанной версией. (Всякий раз, когда фрагмент документа добавляется в документ, фактически добавляются только его дочерние элементы, сам фрагмент документа отбрасывается; DocumentFragment
является виртуальной конструкцией и фактически никогда не появляется в документе.)
И первый, и последний шаг являются произвольными — мы можем работать со всем документом или только с одним узлом и соответствующим образом редактировать наши ссылки и операторы слияния. Или мы могли бы создать метод из внутренних шагов, который принимает $node
в качестве аргумента (и, возможно, массив выражений замены) и возвращает обработанный узел в конце:
function mangleXML($node) { ... return $node; }