Скрытие HTML с помощью PhpWord: ошибка - DOMDocument :: loadXML (): префикс пространства имен o для p не определен в Entity - PullRequest
0 голосов
/ 24 сентября 2018

Я пытаюсь скрыть HTML, отформатированный с помощью Php word.

Я создал HTML-форму с summernote.Summernote позволяет пользователю форматировать текст.Этот текст сохраняется в базе данных с тегами HTML.

Далее, используя phpWord, я хотел бы вывести захваченную информацию в текстовый документ.Пожалуйста, посмотрите код ниже:

$rational = DB::table('rationals')->where('qualificationheader_id',$qualId)->value('rational');

 $wordTest = new \PhpOffice\PhpWord\PhpWord();
        $newSection = $wordTest->addSection();
        $newSection->getStyle()->setPageNumberingStart(1);


    \PhpOffice\PhpWord\Shared\Html::addHtml($newSection,$rational);
    $footer = $newSection->addFooter();
    $footer->addText($curriculum->curriculum_code.'-'.$curriculum->curriculum_title);



    $objectWriter = \PhpOffice\PhpWord\IOFactory::createWriter($wordTest,'Word2007');
    try {
        $objectWriter->save(storage_path($curriculum->curriculum_code.'-'.$curriculum->curriculum_title.'.docx'));
    } catch (Exception $e) {
    }

    return response()->download(storage_path($curriculum->curriculum_code.'-'.$curriculum->curriculum_title.'.docx'));

Текст, сохраненный в базе данных, выглядит следующим образом:

<p class="MsoNormal"><span lang="EN-GB" style="background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial;"><span style="font-family: Arial;">The want for this qualification originated from the energy crisis in
South Africa in 2008 together with the fact that no existing qualifications
currently focuses on energy efficiency as one of the primary solutions.  </span><span style="font-family: Arial;">The fact that energy supply remains under
severe pressure demands the development of skills sets that can deliver the
necessary solutions.</span><span style="font-family: Arial;">  </span><o:p></o:p></span></p><p class="MsoNormal"><span lang="EN-GB" style="background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; font-family: Arial;">This qualification addresses the need from Industry to acquire credible
and certified professionals with specialised skill sets in the energy
efficiency field. The need for this skill set has been confirmed as a global
requirement in few of the International commitment to the reduction of carbon

Я получаю ошибку ниже:

ErrorException (E_WARNING) DOMDocument :: loadXML (): Префикс пространства имен o для p не определен в Entity, строка: 1

1 Ответ

0 голосов
/ 01 октября 2018

Проблема

Анализатор жалуется, что ваш текст содержит пространства имен в тегах элементов, более конкретно префикс тега <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>'
...