在PHP中读写MS Word文件

32

在不使用COM对象的情况下,能否在PHP中读写Word(2003和2007)文件? 我知道我可以:

$file = fopen('c:\file.doc', 'w+');
fwrite($file, $text);
fclose();

但是Word将把它视为HTML文件而不是本机的.doc文件。

我认为如果不使用COM,你很难实现这个。 - Peter Bailey
16个回答

29
阅读二进制Word文档需要根据DOC格式的公开文件格式规范创建解析器。我认为这不是一个真正可行的解决方案。
您可以使用Microsoft Office XML格式来读写Word文件-这与Word 2003和2007版本兼容。对于阅读,您必须确保Word文档以正确的格式保存(在Word 2007中称为Word 2003 XML文档)。对于写作,您只需遵循公开可用的XML模式。我从未使用过此格式从PHP写出Office文档,但我正在使用它来读取Excel工作表(自然保存为XML电子表格2003),并在网页上显示其数据。由于文件仅是XML数据,因此导航并找出所需数据的提取方法没有问题。
另一个选项——仅适用于Word 2007的选项(如果未在Word 2003中安装OpenXML文件格式)——是使用OpenXML。正如databysshere所指出的那样,DOCX文件格式只是包含XML文件的ZIP存档。关于OpenXML文件格式,MSDN上有很多资源,因此您应该能够找出如何读取所需数据。我认为编写将更加复杂,这只取决于您投入了多少时间。

也许您可以查看PHPExcel,它是一个库,能够使用OpenXML标准向Excel 2007文件写入和从Excel 2007文件读取。您可以通过尝试读取和编写OpenXML Word文档来了解涉及的工作量。


1
看起来 PHPExcel 的开发人员已经制作了 PHPWord,用于创建 Word 文档。 - Basic

17

这适用于vs < office 2007,它是纯PHP,没有COM垃圾,仍在尝试解决2007问题

<?php



/*****************************************************************
This approach uses detection of NUL (chr(00)) and end line (chr(13))
to decide where the text is:
- divide the file contents up by chr(13)
- reject any slices containing a NUL
- stitch the rest together again
- clean up with a regular expression
*****************************************************************/

function parseWord($userDoc) 
{
    $fileHandle = fopen($userDoc, "r");
    $line = @fread($fileHandle, filesize($userDoc));   
    $lines = explode(chr(0x0D),$line);
    $outtext = "";
    foreach($lines as $thisline)
      {
        $pos = strpos($thisline, chr(0x00));
        if (($pos !== FALSE)||(strlen($thisline)==0))
          {
          } else {
            $outtext .= $thisline." ";
          }
      }
     $outtext = preg_replace("/[^a-zA-Z0-9\s\,\.\-\n\r\t@\/\_\(\)]/","",$outtext);
    return $outtext;
} 

$userDoc = "cv.doc";

$text = parseWord($userDoc);
echo $text;


?>

3
如果您希望保留umlaut,则不要使用此选项。 - Jan Beck
我发现在这个函数中有一些特殊字符无法解析。 - Roger Ng

8
你可以使用Antiword,它是Linux和大多数流行操作系统的免费MS Word阅读器。
$document_file = 'c:\file.doc';
$text_from_doc = shell_exec('/usr/local/bin/antiword '.$document_file);

8
这种解决方案的问题在于它假设可以在服务器上安装软件。 - UnkwnTech
2
有点长时间了,但如果我错了,请纠正我。C:\file.doc是Windows目录,而/usr/local/bin是Linux/Unix目录? - Daryl Gill
1
只要程序不需要提升权限,大多数程序都可以安装在您有写入权限的任何目录中。然后,您可以使用完整路径来引用该程序,或将安装目录添加到您的PATH变量中。 - Lie Ryan
@LieRyan,你错过了重点。如果你在共享托管环境中运行此程序,无论目录如何,你通常都不能安装任何软件。 - UnkwnTech
@UnkwnTech:安装时,我指的是将其简单地复制到您具有写入权限的任何目录中,并设置执行位。这适用于任何提供ssh访问或至少能够执行脚本的共享托管环境(即唯一无法工作的环境是仅限静态文件的托管环境,但那时您也不会谈论PHP)。如果您只有ftp访问权限而没有ssh,则仍然可以实现,尽管您可能需要编写一些PHP脚本来设置执行位。 - Lie Ryan

6

只是更新代码

<?php

/*****************************************************************
This approach uses detection of NUL (chr(00)) and end line (chr(13))
to decide where the text is:
- divide the file contents up by chr(13)
- reject any slices containing a NUL
- stitch the rest together again
- clean up with a regular expression
*****************************************************************/

function parseWord($userDoc) 
{
    $fileHandle = fopen($userDoc, "r");
    $word_text = @fread($fileHandle, filesize($userDoc));
    $line = "";
    $tam = filesize($userDoc);
    $nulos = 0;
    $caracteres = 0;
    for($i=1536; $i<$tam; $i++)
    {
        $line .= $word_text[$i];

        if( $word_text[$i] == 0)
        {
            $nulos++;
        }
        else
        {
            $nulos=0;
            $caracteres++;
        }

        if( $nulos>1996)
        {   
            break;  
        }
    }

    //echo $caracteres;

    $lines = explode(chr(0x0D),$line);
    //$outtext = "<pre>";

    $outtext = "";
    foreach($lines as $thisline)
    {
        $tam = strlen($thisline);
        if( !$tam )
        {
            continue;
        }

        $new_line = ""; 
        for($i=0; $i<$tam; $i++)
        {
            $onechar = $thisline[$i];
            if( $onechar > chr(240) )
            {
                continue;
            }

            if( $onechar >= chr(0x20) )
            {
                $caracteres++;
                $new_line .= $onechar;
            }

            if( $onechar == chr(0x14) )
            {
                $new_line .= "</a>";
            }

            if( $onechar == chr(0x07) )
            {
                $new_line .= "\t";
                if( isset($thisline[$i+1]) )
                {
                    if( $thisline[$i+1] == chr(0x07) )
                    {
                        $new_line .= "\n";
                    }
                }
            }
        }
        //troca por hiperlink
        $new_line = str_replace("HYPERLINK" ,"<a href=",$new_line); 
        $new_line = str_replace("\o" ,">",$new_line); 
        $new_line .= "\n";

        //link de imagens
        $new_line = str_replace("INCLUDEPICTURE" ,"<br><img src=",$new_line); 
        $new_line = str_replace("\*" ,"><br>",$new_line); 
        $new_line = str_replace("MERGEFORMATINET" ,"",$new_line); 


        $outtext .= nl2br($new_line);
    }

 return $outtext;
} 

$userDoc = "custo.doc";
$userDoc = "Cultura.doc";
$text = parseWord($userDoc);

echo $text;


?>

虽然有趣,但它未能找到Word97文档的开头,并截断了文档。我发现它在1536和1996数字中,应该通过解析确定,而不是任意硬编码。此外,像智能引号、省略号、破折号和特殊单引号等特殊字符都被剥离了,输出中还出现了很多&符号。因此,这是一个有趣的开始,但需要大量改进。 - Volomike
您可能还想参考一下有关如何转换特殊 MS Word 字符的教程:http://www.toao.net/48-replacing-smart-quotes-and-em-dashes-in-mysql - Volomike
1
该函数产生了一些奇怪的字符:"Œ’ÛJA†ïßaÈ}7Û"ÒÙÞH¡w"ë„™ìw̤ھ½..." - Yoong Kim
@Volomike将$nulus更改为更高的数字以避免中断。 - Peyman

6

我不确定如何在PHP中读取原生Word文档,但如果你想在PHP中编写Word文档,WordprocessingML(又称WordML)可能是一个好的解决方案。你只需要按正确格式创建一个XML文档即可。我相信Word 2003和2007都支持WordML。


5

很可能您无法在没有COM的情况下阅读Word文档。

关于写入Word文档的问题,可以参考这个主题


3

2007年可能也有点复杂。

.docx格式是一个zip文件,其中包含一些其他文件夹和文件,用于格式化和其他内容。

将.docx文件重命名为.zip,您就会明白我的意思。

因此,如果您可以在PHP中处理zip文件,您应该走在正确的道路上。


2
直接使用以下类来读取Word文档。最初的回答来源于:此处
class DocxConversion{
    private $filename;

    public function __construct($filePath) {
        $this->filename = $filePath;
    }

    private function read_doc() {
        $fileHandle = fopen($this->filename, "r");
        $line = @fread($fileHandle, filesize($this->filename));   
        $lines = explode(chr(0x0D),$line);
        $outtext = "";
        foreach($lines as $thisline)
          {
            $pos = strpos($thisline, chr(0x00));
            if (($pos !== FALSE)||(strlen($thisline)==0))
              {
              } else {
                $outtext .= $thisline." ";
              }
          }
         $outtext = preg_replace("/[^a-zA-Z0-9\s\,\.\-\n\r\t@\/\_\(\)]/","",$outtext);
        return $outtext;
    }

    private function read_docx(){

        $striped_content = '';
        $content = '';

        $zip = zip_open($this->filename);

        if (!$zip || is_numeric($zip)) return false;

        while ($zip_entry = zip_read($zip)) {

            if (zip_entry_open($zip, $zip_entry) == FALSE) continue;

            if (zip_entry_name($zip_entry) != "word/document.xml") continue;

            $content .= zip_entry_read($zip_entry, zip_entry_filesize($zip_entry));

            zip_entry_close($zip_entry);
        }// end while

        zip_close($zip);

        $content = str_replace('</w:r></w:p></w:tc><w:tc>', " ", $content);
        $content = str_replace('</w:r></w:p>', "\r\n", $content);
        $striped_content = strip_tags($content);

        return $striped_content;
    }

 /************************excel sheet************************************/

function xlsx_to_text($input_file){
    $xml_filename = "xl/sharedStrings.xml"; //content file name
    $zip_handle = new ZipArchive;
    $output_text = "";
    if(true === $zip_handle->open($input_file)){
        if(($xml_index = $zip_handle->locateName($xml_filename)) !== false){
            $xml_datas = $zip_handle->getFromIndex($xml_index);
            $xml_handle = DOMDocument::loadXML($xml_datas, LIBXML_NOENT | LIBXML_XINCLUDE | LIBXML_NOERROR | LIBXML_NOWARNING);
            $output_text = strip_tags($xml_handle->saveXML());
        }else{
            $output_text .="";
        }
        $zip_handle->close();
    }else{
    $output_text .="";
    }
    return $output_text;
}

/*************************power point files*****************************/
function pptx_to_text($input_file){
    $zip_handle = new ZipArchive;
    $output_text = "";
    if(true === $zip_handle->open($input_file)){
        $slide_number = 1; //loop through slide files
        while(($xml_index = $zip_handle->locateName("ppt/slides/slide".$slide_number.".xml")) !== false){
            $xml_datas = $zip_handle->getFromIndex($xml_index);
            $xml_handle = DOMDocument::loadXML($xml_datas, LIBXML_NOENT | LIBXML_XINCLUDE | LIBXML_NOERROR | LIBXML_NOWARNING);
            $output_text .= strip_tags($xml_handle->saveXML());
            $slide_number++;
        }
        if($slide_number == 1){
            $output_text .="";
        }
        $zip_handle->close();
    }else{
    $output_text .="";
    }
    return $output_text;
}


    public function convertToText() {

        if(isset($this->filename) && !file_exists($this->filename)) {
            return "File Not exists";
        }

        $fileArray = pathinfo($this->filename);
        $file_ext  = $fileArray['extension'];
        if($file_ext == "doc" || $file_ext == "docx" || $file_ext == "xlsx" || $file_ext == "pptx")
        {
            if($file_ext == "doc") {
                return $this->read_doc();
            } elseif($file_ext == "docx") {
                return $this->read_docx();
            } elseif($file_ext == "xlsx") {
                return $this->xlsx_to_text();
            }elseif($file_ext == "pptx") {
                return $this->pptx_to_text();
            }
        } else {
            return "Invalid File Type";
        }
    }

}

$docObj = new DocxConversion("test.docx"); //replace your document name with correct extension doc or docx 
echo $docText= $docObj->convertToText();

2

www.phplivedocx.org是基于SOAP的服务,这意味着您始终需要在线测试文件,而且该网站没有足够的使用示例。奇怪的是,我在下载了2天后才发现它是一个基于SOAP的程序(我被诅咒了!!!)...我认为在Linux服务器上没有COM就不可能实现,唯一的想法是将文档文件更改为另一个可用的文件,以便PHP可以解析...


1

Office 2007的.docx格式应该是可以的,因为它是一个XML标准。Word 2003可能需要使用COM才能读取,即使现在微软已经发布了这些标准,因为这些标准非常庞大。我还没有看到很多编写与它们匹配的库。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接