Libreoffice stores Writer document content in an XML formatted file. In PHP I would like to insert text with a different formatting into a text paragraph. Unfortunately, Libreoffice does that with a nested element inside the text of another element. Here's a simplified example:
<text:p text:style-name="P1">
the quick brown
<text:span text:style-name="T1"> fox jumps over</text:span>
the lazy dog
</text:p>
I have found no SimpleXML or XML DOM function in PHP that lets me insert a new element inside the text of another element as is required here. Am I overlooking something here?
SimpleXML does not do well with mixed child nodes but in DOM it is not difficult, just a little verbose. Keep in mind that in DOM anything is a node, not just the elements. So what you're trying to do is to replace a single text node with three new nodes - A text node, the new element node and another text node.
$xmlns = [
'text' => 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'
];
$xml = <<<'XML'
<text:p
text:style-name="P1"
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0">
the quick brown fox jumps over the lazy dog
</text:p>
XML;
$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);
$searchFor = 'fox jumps over';
// iterate over text nodes containing the search string
$expression = '//text:p//text()[contains(., "'.$searchFor.'")]';
foreach ($xpath->evaluate($expression) as $textNode) {
// split the text content at the search string and capture any part
$parts = preg_split(
'(('.preg_quote($searchFor).'))',
$textNode->textContent,
-1,
PREG_SPLIT_DELIM_CAPTURE
);
// here should be at least two parts
if (count($parts) < 2) {
continue;
}
// fragments allow to treat several nodes like one
$fragment = $document->createDocumentFragment();
foreach ($parts as $part) {
// matched the text
if ($part === $searchFor) {
// create the new span
$fragment->appendChild(
$span = $document->createElementNS($xmlns['text'], 'text:span')
);
$span->setAttributeNS($xmlns['text'], 'text:style-name', 'T1');
$span->appendChild($document->createTextNode($part));
} else {
// add the part as a new text node
$fragment->appendChild($document->createTextNode($part));
}
}
// replace the text node with the fragment
$textNode->parentNode->replaceChild($fragment, $textNode);
}
echo $document->saveXML();