php - 带有 PhpWord 的隐蔽 HTML:错误 - DOMDocument::loadXML():p 上的命名空间前缀 o 未在实体中定义

标签 php laravel summernote phpword

我正在尝试隐藏用 Php word 格式化的 HTML。

我用summernote创建了一个html表单。 Summernote 允许用户格式化文本。此文本使用 html 标签保存到数据库中。

接下来使用phpWord,我想将捕获的信息输出到word文档中。请看下面的代码:

$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(): Namespace prefix o on p is not defined in Entity, line: 1

最佳答案

问题

解析器提示您的文本在元素标签中包含命名空间,更具体地说是标签上的前缀 <o:p> (其中 o: 是前缀)。好像是some kind of formatting for Word .

重现问题

为了重现这个问题,我不得不挖一点,因为不是 PHPWord 抛出异常,而是 DOMDocument PHPWord 正在使用。下面的代码使用 same parsing method 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 with the namespace ,所以 PHPWord可以利用它。不幸的是,DOMXPath isn't public在 API 中,因此不可能。

相反,似乎最好的方法是从标签中去除前缀​​,并使警告消失。

编辑 2018-10-04 :从那以后,我发现了一种将前缀保留在标签中并仍然使错误消失的方法,但是执行并不是最佳的。如果有人可以提出更好的解决方案,请随时编辑我的帖子或发表评论。

解决方案

根据分析,解决方案是去掉前缀,反过来我们必须对代码进行预解析。 Since PHPWord is using DOMDocument ,我们也可以使用它,并确保我们不需要安装任何(额外的)依赖项。

PHPWord 正在使用 loadXML 解析 HTML ,这是提示格式的函数。在这种方法中可以抑制错误消息,我们在两种解决方案中都必须这样做。这是由 passing an additional parameter 完成的进loadXMLloadHTML功能。

解决方案 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 is using它将在将 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:保留前缀并添加命名空间

我试图用给定的 Microsoft Office namespaces 包装代码在将数据输入解析器之前,这也将解决问题。具有讽刺意味的是,我还没有找到使用 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);

这些设置源自 default reporting level .

使用正则表达式

使用 regex on your text 删除(大部分)命名空间是(可能)可能的。要么在将其保存到数据库中之前,要么在获取它以在此函数中使用之后。由于它已经存储在数据库中,因此最好在从数据库中获取它后使用下面的代码。尽管正则表达式可能会遗漏某些事件,或者在最坏的情况下会弄乱 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>'

关于php - 带有 PhpWord 的隐蔽 HTML:错误 - DOMDocument::loadXML():p 上的命名空间前缀 o 未在实体中定义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52483285/

相关文章:

php - 按分隔符拆分字符串,但如果转义则不拆分

php - php 错误报告(在代码中)是通过 php 文件还是通过网站?

php - laravel 通过 Controller 将错误传递给 View

php - 无法在 Lumen 中使用 Laravel/Socialite

javascript - 如何在airmode 和toolbar 模式之间切换summernote 编辑器?

PHP MySQL 执行多个相关查询的最佳方式

php - 2020年如何使用brew安装php7.1

jquery - Bootstrap 导航栏切换不适用于 Laravel

javascript - Summernote js 提示返回链接而不是文本

php - summernote 在从 Mysql DB 检索后显示 html 代码