【问题标题】:PHP read docx file content but keeping line breaks, italic, underline and bold?PHP 读取 docx 文件内容但保留换行符、斜体、下划线和粗体?
【发布时间】:2016-11-18 12:03:51
【问题描述】:

我如何阅读 docx 内容,剥离所有标签但保留在下方?

  1. 粗体
  2. 斜体
  3. 下划线
  4. 新线

以下是我从其他答案中获得的代码:

//FUNCTION :: read a docx file and return the string
// http://stackoverflow.com/questions/4587216/how-can-i-convert-a-docx-document-to-html-using-php
// https://www.jackreichert.com/2012/11/how-to-convert-docx-to-html/
function readDocx($filePath) {
    // Create new ZIP archive
    $zip = new ZipArchive;
    $dataFile = 'word/document.xml';
    // Open received archive file
    if (true === $zip->open($filePath)) {
        // If done, search for the data file in the archive
        if (($index = $zip->locateName($dataFile)) !== false) {
            // If found, read it to the string
            $data = $zip->getFromIndex($index);
            // Close archive file
            $zip->close();
            // Load XML from a string
            // Skip errors and warnings
            $xml = DOMDocument::loadXML($data, LIBXML_NOENT | LIBXML_XINCLUDE | LIBXML_NOERROR | LIBXML_NOWARNING);
            // Return data without XML formatting tags
            $xmldata = $xml->saveXML();
            // </w:p> is what word uses to mark the end of a paragraph. E.g.
            // <w:p>This is a paragraph.</w:p>
            // <w:p>And a second one.</w:p>
            // http://stackoverflow.com/questions/5607594/find-linebreaks-in-a-docx-file-using-php
            $xmldata = str_replace("</w:p>", "\r\n", $xmldata);
            $xmldata = str_replace("<w:i/>", "<i>", $xmldata);

            $contents = explode('\n',strip_tags($xmldata, "<i>"));
            $text = '';
            foreach($contents as $i=>$content) {
                $text .= $contents[$i];
            }
            return $text;
        }
        $zip->close();
    }
    // In case of failure return empty string
    return "";
}

$filePath = 'sample.docx';
$string = readDocx($filePath);
var_dump($string);

到目前为止,我只设法保留换行符,而不是其余的:

$xmldata = str_replace("</w:p>", "\r\n", $xmldata);
$xmldata = str_replace("<w:i/>", "<i>", $xmldata); // will get <i>Hello World <-- no closing i

有什么想法吗?

编辑:

$xmldata = preg_replace("/<w\:i\/>(.*?)<\/w\:r>/is", "<i>$1</i>", $xmldata);
$xmldata = preg_replace("/<w\:b\/>(.*?)<\/w\:r>/is", "<b>$1</b>", $xmldata);
$xmldata = preg_replace("/<w\:u (.*?)\/>(.*?)<\/w\:r>/is", "<u>$2</u>", $xmldata);

但上述解决方案存在缺陷,例如:

<w:r><w:t xml:space="preserve"><w:i/>Hello</w:t></w:r><w:r><w:t xml:space="preserve"> World</w:t></w:r>

你会注意到我正在替换 &lt;w:i/&gt;&lt;\/w\:r&gt;,因为 &lt;w:i/&gt; 没有配对。

有更好的解决方案吗?

【问题讨论】:

    标签: php domdocument docx strip-tags


    【解决方案1】:

    我认为不需要那些 str_repalce()explode() 函数,因此我只做一个 strip_tags()

    $contents = strip_tags($xmldata, '<w:p><w:u><w:i><w:b>');
    

    现在您确定所有需要的标签都已保留。再进一步,我们应该将&lt;w:*&gt;标签替换为对应的HTML标签:

    $contents = preg_replace("/(<(\/?)w:(.)[^>]*>)\1*/", "<$2$3>", $contents);
    

    我们只有名称中包含一个字符的 HTML 标记 &lt;p&gt;&lt;b&gt;&lt;i&gt;&lt;u&gt;,因此捕获它们的名称就像使用 点捕获组一样简单 em>

     (               # (1 start)
          <             # Match XML opening tag character           
          ( \/? )       # (2) Match if it is going to be an ending tag
          w:            # Literal `w:`
          ( . )         # (3) Match b,p,u,i
          [^>]* >       # Up to closing tag character
     )               # (1 end)
     \1*             # Match if latter group repeats 
    

    我必须检查相同的匹配标签\1*,因为我发现它很有可能发生。如果我们的 docx 文件包含如下三行:

    粗体

    斜体

    正常

    那么此时我们的输出类似这样:

    <p><b><b>Bold</p><p><i><i>Italic</p><p>Normal</p>
    

    但正如您所见,我们有未配对的重复标签,这根本不好。我们应该清理我们的文档。但是怎么做呢?

    1. 通过 PHP Tidy 扩展
    2. 将我们的 HTML 加载到 DOMDocument 对象中

    虽然 PHP Tidy 非常适合这种工作,但我发现 DOMDocument 更适合做我们的任务:

    $dom = new DOMDocument;
    @$dom->loadHTML($contents, LIBXML_HTML_NOIMPLIED  | LIBXML_HTML_NODEFDTD);
    $contents = $dom->saveHTML();
    

    我们设置了两个相关的标志,因为我们不需要 HTML DOCTYPE 以及 &lt;html&gt;/&lt;body&gt; 标记。

    此时我们的输出:

    <p><b><b>Bold</b></b><p><i><i>Italic</i></i></p><p>Normal</p></p>
    

    好消息是我们现在有了配对标签,但坏消息可能是我们有不必要的打开标签:

    <p><b><b>Bold</b></b><p><i><i>Italic</i></i></p><p>Normal</p></p>
       ^  ^                 ^  ^
    

    关于删除额外开始标签的有效解决方案,我写了另一个正则表达式:

    $contents = preg_replace('~<([ibu])>(?=(?:\s*<[ibu]>\s*)*?<\1>)|</([ibu])>(?=(?:\s*</?[ibu]>\s*)*?</?\2>)|<p></p>~s', "", $contents);
    

    可以在这里看到它要做什么:

     <                                  # Match an opening tag
     ( [ibu] )                          # (1) Any type except `p`
     >                                  # Up to closing character
     (?=                                # Which is immediately followed by
          (?: \s* < [ibu] > \s* )*?     # Another opening tag (or nothing)
          < \1 >                        # And then its own closing tag.
     )                                  # End of lookahead
     |                                  # Or match
     </                                 # A closing tag
     ( [ibu] )                          # (2) Any type except `p`
     >                                  # Up to closing character
     (?=                                # Which is immediately followed by
          (?: \s* </ [ibu] > \s* )*?    # Another closing tag (or nothing)
          </? \2 >                      # And then the same closing tag
     )                                  # End of lookahead
     |                                  # Or match
     <p></p>                            # Empty <p> tags
    

    现在我们有了正确的输出:

    <p><b>Bold</b><p><i>Italic</i></p><p>Normal</p></p>
    

    把所有东西放在一起:

    <?php
    
    function readDocx($filePath) {
        // Create new ZIP archive
        $zip = new ZipArchive;
        $dataFile = 'word/document.xml';
        // Open received archive file
        if (true === $zip->open($filePath)) {
            // If done, search for the data file in the archive
            if (($index = $zip->locateName($dataFile)) !== false) {
                $data = $zip->getFromIndex($index);
                $zip->close();
    
                $dom = new DOMDocument;
                $dom->loadXML($data, LIBXML_NOENT
                    | LIBXML_XINCLUDE
                    | LIBXML_NOERROR
                    | LIBXML_NOWARNING);
    
                $xmldata = $dom->saveXML();
    
                $contents = strip_tags($xmldata, '<w:p><w:u><w:i><w:b>');
                $contents = preg_replace("/(<(\/?)w:(.)[^>]*>)\1*/", "<$2$3>", $contents);
    
                $dom = new DOMDocument;
                @$dom->loadHTML($contents, LIBXML_HTML_NOIMPLIED  | LIBXML_HTML_NODEFDTD);
                $contents = $dom->saveHTML();
    
                $contents = preg_replace('~<([ibu])>(?=(?:\s*<[ibu]>\s*)*?<\1>)|</([ibu])>(?=(?:\s*</[ibu]>\s*)*?</?\2>)|<p></p>~s', "", $contents);
    
                return $contents;
            }
            $zip->close();
        }
        // In case of failure return empty string
        return "";
    }
    
    $filePath = 'sample.docx';
    $string = readDocx($filePath);
    echo $string;
    

    【讨论】:

    • 谢谢!但是粗体/斜体不是整个段落的内容?目前,该样式将应用于开始标记之后的整个段落。
    【解决方案2】:

    我有这些解决方案 - 它很丑但很有效:

            $xmldata =
                        '<w:r>
            <w:rPr>
            <w:u/>
            <w:b/>
            <w:i/>
            </w:rPr>
            <w:t>I feel that there is much to be said for the Celtic belief that the souls of those whom we have lost are held captive in some inferior being...</w:t>
            </w:r>';
            // </w:p> is what word uses to mark the end of a paragraph. E.g.
            // <w:p>This is a paragraph.</w:p>
            // <w:p>And a second one.</w:p>
            // http://stackoverflow.com/questions/5607594/find-linebreaks-in-a-docx-file-using-php
            // http://officeopenxml.com/WPtext.php
            $xmldata = str_replace("</w:p>", "\r\n", $xmldata);
            $xmldata = preg_replace("/<w\:i\/>(.*?)<w:t(.*?)>(.*?)<\/w\:t>/is", "<w:i/>$1<w:t$2><i>$3</i></w:t>", $xmldata);
            $xmldata = preg_replace("/<w\:b\/>(.*?)<w:t(.*?)>(.*?)<\/w\:t>/is", "<w:b/>$1<w:t$2><b>$3</b></w:t>", $xmldata);
            $xmldata = preg_replace("/<w\:u(.*?)\/>(.*?)<w:t(.*?)>(.*?)<\/w\:t>/is", "<w:u$1/>$2<w:t$3><u>$4</u></w:t>", $xmldata);
    

    输出:

    <u><b><i>I feel that there is much to be said for the Celtic belief that the souls of those whom we have lost are held captive in some inferior being...</i></b></u>
    

    【讨论】:

    • 酷!文本对齐怎么样?有什么方法可以保持居中对齐、右对齐等?谢谢!
    【解决方案3】:

    剥离标签不是好方法,因为使用当前的解决方案,您并没有结束格式化 - 您应该考虑改为解释 xml

    您搜索的其他标签是&lt;w:b/&gt;(粗体)和&lt;w:u ...&gt;(下划线)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-25
      • 1970-01-01
      • 1970-01-01
      • 2011-03-03
      相关资源
      最近更新 更多