Проблема
Анализатор жалуется, что ваш текст содержит пространства имен в тегах элементов, более конкретно префикс тега <o:p>
(где o:
- префикс).Кажется, какое-то форматирование для Word .
Воспроизведение проблемы
Чтобы воспроизвести эту проблему, мне пришлось немного покопаться, потому что это был не PHPWordвыдает исключение, но DOMDocument
, который использует PHPWord.Приведенный ниже код использует тот же метод синтаксического анализа , который используется PHPWord и должен выводить все предупреждения и уведомления о коде.
# Make sure to display all errors
ini_set("display_errors", "1");
error_reporting(E_ALL);
$html = '<o:p>Foo <o:b>Bar</o:b></o:p>';
# Set up and parse the code
$doc = new DOMDocument();
$doc->loadXML($html); # This is the line that's causing the warning.
# Print it back
echo $doc->saveXML();
Анализ
Для хорошо отформатированногоВ структуре HTML можно включить пространства имен в объявление и, таким образом, сообщить парсеру, каковы эти префиксы на самом деле.Но так как он, кажется, является только частью HTML-кода, который будет проанализирован, это невозможно.
Можно было бы заполнить DOMXPath
пространством имен , так что PHPWord
может использовать это.К сожалению, DOMXPath
не является общедоступным в API и, следовательно, не представляется возможным.
Вместо этого, кажется, лучший способ состоит в том, чтобы убрать префиксы из тегов и сделать предупреждениеуйти.
Редактировать 2018-10-04 : с тех пор я нашел способ сохранить префикс в тегах и при этом устранить ошибку, однако выполнение неоптимальный.Если кто-то может найти лучшее решение, смело редактируйте мой пост или оставляйте комментарий.
Решение
Основываясь на анализе, решение состоит в том, чтобы удалить префиксы, и, в свою очередь, мы должныПредварительный анализ кода. Поскольку PHPWord использует DOMDocument
, мы также можем использовать его и быть уверенными, что нам не нужно устанавливать никаких (дополнительных) зависимостей.
PHPWord анализирует HTML с помощью loadXML
, которая является функцией, которая жалуется на форматирование.В этом методе возможно подавить сообщения об ошибках, которые мы должны будем сделать в обоих решениях.Это делается с помощью передачи дополнительного параметра в функцию loadXML
и loadHTML
.
Решение 1: предварительно проанализируйте как XML и удалите префиксы
Первый подход будет анализировать HTML-код как XML и рекурсивно проходить по дереву и удалять любые вхождения префикса в имени тега.
Я создал класс, который должен решить эту проблему.
class TagPrefixFixer {
/**
* @desc Removes all prefixes from tags
* @param string $xml The XML code to replace against.
* @return string The XML code with no prefixes in the tags.
*/
public static function Clean(string $xml) {
$doc = new DOMDocument();
/* Load the XML */
$doc->loadXML($xml,
LIBXML_HTML_NOIMPLIED | # Make sure no extra BODY
LIBXML_HTML_NODEFDTD | # or DOCTYPE is created
LIBXML_NOERROR | # Suppress any errors
LIBXML_NOWARNING # or warnings about prefixes.
);
/* Run the code */
self::removeTagPrefixes($doc);
/* Return only the XML */
return $doc->saveXML();
}
private static function removeTagPrefixes(DOMNode $domNode) {
/* Iterate over each child */
foreach ($domNode->childNodes as $node) {
/* Make sure the element is renameable and has children */
if ($node->nodeType === 1) {
/* Iterate recursively over the children.
* This is done before the renaming on purpose.
* If we rename this element, then the children, the element
* would need to be moved a lot more times due to how
* renameNode works. */
if($node->hasChildNodes()) {
self::removeTagPrefixes($node);
}
/* Check if the tag contains a ':' */
if (strpos($node->tagName, ':') !== false) {
print $node->tagName;
/* Get the last part of the tag name */
$parts = explode(':', $node->tagName);
$newTagName = end($parts);
/* Change the name of the tag */
self::renameNode($node, $newTagName);
}
}
}
}
private static function renameNode($node, $newName) {
/* Create a new node with the new name */
$newNode = $node->ownerDocument->createElement($newName);
/* Copy over every attribute from the old node to the new one */
foreach ($node->attributes as $attribute) {
$newNode->setAttribute($attribute->nodeName, $attribute->nodeValue);
}
/* Copy over every child node to the new node */
while ($node->firstChild) {
$newNode->appendChild($node->firstChild);
}
/* Replace the old node with the new one */
$node->parentNode->replaceChild($newNode, $node);
}
}
Чтобы использовать код, просто вызовите функцию TagPrefixFixer::Clean
.
$xml = '<o:p>Foo <o:b>Bar</o:b></o:p>';
print TagPrefixFixer::Clean($xml);
Вывод
<?xml version="1.0"?>
<p>Foo <b>Bar</b></p>
Решение 2: Предварительный анализ в HTML
Я заметил, что если вы используете loadHTML
вместо loadXML
, который PHPWord использует , он сам удалит префиксы при загрузке HTML в класс.
Этот код значительно короче.
function cleanHTML($html) {
$doc = new DOMDocument();
/* Load the HTML */
$doc->loadHTML($html,
LIBXML_HTML_NOIMPLIED | # Make sure no extra BODY
LIBXML_HTML_NODEFDTD | # or DOCTYPE is created
LIBXML_NOERROR | # Suppress any errors
LIBXML_NOWARNING # or warnings about prefixes.
);
/* Immediately save the HTML and return it. */
return $doc->saveHTML();
}
И чтобы использовать этот код, просто вызовите функцию cleanHTML
$html = '<o:p>Foo <o:b>Bar</o:b></o:p>';
print cleanHTML($html);
Выход
<p>Foo <b>Bar</b></p>
Решение 3. Сохраните префиксы и добавьте пространства имен
Я пытался обернуть код с помощью MicПространства имен rosoft Office перед подачей данных в анализатор, что также решит проблему.По иронии судьбы я не нашел способа добавить пространства имен с помощью парсера DOMDocument
без фактического создания исходного предупреждения.Итак, выполнение этого решения немного хакерское, и я бы не советовал использовать его, а вместо этого создать свой собственный.Но вы поняли:
function addNamespaces($xml) {
$root = '<w:wordDocument
xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml"
xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"
xmlns:o="urn:schemas-microsoft-com:office:office">';
$root .= $xml;
$root .= '</w:wordDocument>';
return $root;
}
И чтобы использовать этот код, просто вызовите функцию addNamespaces
$xml = '<o:p>Foo <o:b>Bar</o:b></o:p>';
print addNamespaces($xml);
Выход
<w:wordDocument
xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml"
xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"
xmlns:o="urn:schemas-microsoft-com:office:office">
<o:p>Foo <o:b>Bar</o:b></o:p>
</w:wordDocument>
Этот код затем может быть передан в функцию PHPWord addHtml
без каких-либо предупреждений.
Необязательные решения (не рекомендуется)
В предыдущем ответе они были представлены как (необязательные) решения,но ради решения проблем я позволю им быть здесь ниже.Помните, что ни один из них не рекомендуется и должен использоваться с осторожностью.
Выключите предупреждения
Поскольку это «всего лишь» предупреждение, а не исключение из-за фатальной остановки, вы можете отключить предупреждения.Вы можете сделать это, включив этот код в верхней части скрипта.Это, однако, все равно будет замедлять работу вашего приложения, и лучший подход - всегда следить за тем, чтобы не было никаких предупреждений или ошибок.
// Show the default reporting except from warnings
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED & ~E_WARNING);
Настройки получены из уровня отчетности по умолчанию .
Использование регулярных выражений
Можно (вероятно) избавиться от (большинства) пространств имен с помощью регулярного выражения в вашем тексте либо перед сохранениемв базе данных или после извлечения для использования в этой функции.Поскольку он уже хранится в базе данных, было бы лучше использовать приведенный ниже код после извлечения его из базы данных.Регулярное выражение может, однако, пропустить некоторые случаи или в худшем случае испортить HTML.
Регулярное выражение :
$text_after = preg_replace('/[a-zA-Z]+:([a-zA-Z]+[=>])/', '$1', $text_before);
Пример :
$text = '<o:p>Foo <o:b>Bar</o:b></o:p>';
$text = preg_replace('/[a-zA-Z]+:([a-zA-Z]+[=>])/', '$1', $text);
echo $text; // Outputs '<p>Foo <b>Bar</b></p>'