背景
前段時間在寫一個功能:用原生php將獲得word中的內容並導入到網站系統中。因為文檔中存在公式,圖片,表格等,因此寫的比較麻煩。
思路
大體思路是先將word中格式為doc的文檔轉化為docx,用預處理程序將文檔中的公式轉化為swf圖片格式,將word轉化為xml格式,在獲得xml中的內容轉化為json格式。
預備知識
1. 理解xml基礎
xml是一種可擴展標記語言,是互聯網數據傳輸的重要工具,xml可以實現跨互聯網平台而不受編程語言和操作系統的限制,可以說是一個擁有互聯網最高級別通行證的數據攜帶者。
xml是當前處理結構化文檔信息中的技術,有助於在服務器之間穿梭結構化出具,使得開發工作者可以更加方便的控制數據的存儲和傳輸
xml用於標記電子文件使其具有結構性的標記語言,可用來標記數據,定義數據類型,是一種允許用戶對自己的標記語言進行定義的源語言。它是標准通用語言的子集,非常適合web傳輸。
具體的詳解可以看這里:https://blog.csdn.net/com_ma/article/details/73277535
2. word的兩種不同的存儲方式
word文檔的兩種存儲格式:doc和docx
doc:習慣上被稱為word,采用二進制存儲數據
docx:也就是word2007,采用xml存儲數據
那么后綴明明是docx格式的,為什么成xml格式了?
選擇一個test.docx,將后綴名改為.zip,然后進行解壓,得到下面的目錄結構:

所以你認為的docx文檔,其實是一個壓縮文件~
3. 了解DOM和PHP DOM XML解析
DOM提供了針對html和xml文檔的標准對象集,以及用於訪問和操作這些文檔的標准接口。XML DOM是為文檔定義標准的對象集。使用PHP DOM擴展可以實現PHP對DOM樹的一系列操作。
使用PHP DOM讀取一個XML文檔:
test.xml:
<?xml version="1.0" encoding="utf-8"?>
<teststore>
<test>
<name>php dom test</name>
<author>test-one</author>
</test>
<test>
<title>php dom test 2</title>
<author>test-two</author>
</test>
</teststore>
test.php:
<?php
$doc = new DOMDocument();
$doc->load("test.xml");
//獲取標簽對象
$book=$doc->getElementsByTagName("test");
//輸出第一個中的值
echo $book->item(0)->nodeValue;
echo "<br>----------------<br>";
$title=$doc->getElementsByTagName("name");
echo $title->item(0)->nodeValue;
echo "<br>----------------<br>";
//遍歷所有book標簽中的內容
foreach ($book as $note)
{
echo $note->nodeValue;
echo "<br>";
}
結果:

4. word中xml的定義格式
word中的數據是怎么定義的呢??
我們只會介紹連個l兩個文件/文件夾:
一個文件是word/document.xml,這個文件定義了word整個文檔的內容。
另一個文件夾是word/media,這個文件夾存放着文檔的多媒體內容,換句話說文檔中所有的圖片,音頻視頻都是在這個文件夾下存放。

document.ml中的整體結構定義:
<w:document mc:ignorable="w14 w15 wp14" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpscustomdata="http://www.wps.cn/officeDocument/2013/wpsCustomData">
<w:body>
<w:p>
<w:ppr>
<w:pstyle w:val="2">
</w:pstyle>
<w:keepnext w:val="0">
</w:keepnext>
<w:keeplines w:val="0">
</w:keeplines>
<w:widowcontrol>
</w:widowcontrol>
<w:suppresslinenumbers w:val="0">
</w:suppresslinenumbers>
<w:pbdr>
<w:top w:color="auto" w:space="0" w:sz="0" w:val="none">
</w:top>
<w:left w:color="auto" w:space="0" w:sz="0" w:val="none">
</w:left>
<w:bottom w:color="auto" w:space="0" w:sz="0" w:val="none">
</w:bottom>
<w:right w:color="auto" w:space="0" w:sz="0" w:val="none">
</w:right>
</w:pbdr>
文檔段落內容:
<w:p>
<w:ppr>
<w:pstyle w:val="2">
</w:pstyle>
<w:keepnext w:val="0">
</w:keepnext>
<w:keeplines w:val="0">
</w:keeplines>
<w:widowcontrol>
</w:widowcontrol>
<w:suppresslinenumbers w:val="0">
</w:suppresslinenumbers>
<w:pbdr>
<w:top w:color="auto" w:space="0" w:sz="0" w:val="none">
</w:top>
<w:left w:color="auto" w:space="0" w:sz="0" w:val="none">
</w:left>
<w:bottom w:color="auto" w:space="0" w:sz="0" w:val="none">
</w:bottom>
<w:right w:color="auto" w:space="0" w:sz="0" w:val="none">
</w:right>
</w:pbdr>
<w:shd w:fill="FAFAFA" w:val="clear">
</w:shd>
<w:spacing w:after="150" w:afterautospacing="0" w:before="150" w:beforeautospacing="0" w:line="378" w:linerule="atLeast">
</w:spacing>
<w:ind w:firstline="0" w:left="0" w:right="0">
</w:ind>
<w:rpr>
<w:rfonts w:ascii="Verdana" w:cs="Verdana" w:hansi="Verdana" w:hint="default">
</w:rfonts>
<w:i w:val="0">
</w:i>
<w:caps w:val="0">
</w:caps>
<w:color w:val="404040">
</w:color>
<w:spacing w:val="0">
</w:spacing>
<w:sz w:val="21">
</w:sz>
<w:szcs w:val="21">
</w:szcs>
</w:rpr>
</w:ppr>
<w:r>
<w:rpr>
<w:rfonts w:ascii="Verdana" w:cs="Verdana" w:hansi="Verdana" w:hint="default">
</w:rfonts>
<w:i w:val="0">
</w:i>
<w:caps w:val="0">
</w:caps>
<w:color w:val="404040">
</w:color>
<w:spacing w:val="0">
</w:spacing>
<w:sz w:val="21">
</w:sz>
<w:szcs w:val="21">
</w:szcs>
<w:bdr w:color="auto" w:space="0" w:sz="0" w:val="none">
</w:bdr>
<w:shd w:fill="FAFAFA" w:val="clear">
</w:shd>
</w:rpr>
<w:t>
作者: Test
</w:t>
</w:r>
</w:p>
圖片內容定義:
<w:r>
<w:rpr>
<w:rfonts w:ascii="Verdana" w:cs="Verdana" w:hansi="Verdana" w:hint="default">
</w:rfonts>
<w:i w:val="0">
</w:i>
<w:caps w:val="0">
</w:caps>
<w:color w:val="404040">
</w:color>
<w:spacing w:val="0">
</w:spacing>
<w:sz w:val="21">
</w:sz>
<w:szcs w:val="21">
</w:szcs>
<w:bdr w:color="auto" w:space="0" w:sz="0" w:val="none">
</w:bdr>
<w:shd w:fill="FAFAFA" w:val="clear">
</w:shd>
</w:rpr>
<w:drawing>
<wp:inline distb="0" distl="114300" distr="114300" distt="0">
<wp:extent cx="5543550" cy="5543550">
</wp:extent>
<wp:effectextent b="0" l="0" r="0" t="0">
</wp:effectextent>
<wp:docpr descr="IMG_256" id="1" name="Picture 1">
</wp:docpr>
<wp:cnvgraphicframepr>
<a:graphicframelocks nochangeaspect="1" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
</a:graphicframelocks>
</wp:cnvgraphicframepr>
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicdata uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:nvpicpr>
<pic:cnvpr descr="IMG_256" id="1" name="Picture 1">
</pic:cnvpr>
<pic:cnvpicpr>
<a:piclocks nochangeaspect="1">
</a:piclocks>
</pic:cnvpicpr>
</pic:nvpicpr>
<pic:blipfill>
<a:blip r:embed="rId4">
</a:blip>
<a:stretch>
<a:fillrect>
</a:fillrect>
</a:stretch>
</pic:blipfill>
<pic:sppr>
<a:xfrm>
<a:off x="0" y="0">
</a:off>
<a:ext cx="5543550" cy="5543550">
</a:ext>
</a:xfrm>
<a:prstgeom prst="rect">
<a:avlst>
</a:avlst>
</a:prstgeom>
<a:nofill>
</a:nofill>
<a:ln w="9525">
<a:nofill>
</a:nofill>
</a:ln>
</pic:sppr>
</pic:pic>
</a:graphicdata>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
結論:
<w:document> 定義整個文檔的開始
<w:body> document的子節點,文檔的主體內容
<w:p> body的子節點,一個段落,就是word文檔中的段落
<w:r> p元素的子節點,一個Run定義了段落中具有相同格式的一段內容
<w:t> Run元素節點的子節點,就是文檔的內容
<w:drawing> run元素的子節點,定義了一張圖片
<w:inline> drawing子節點,具體應用沒有研究
<a:graphic> 定義了圖片內容
<pic:blipfill> graphic文檔的子節點,定義了圖片內容的索引.
具體的說,如果用java,那么XWPF解析docx文檔就是做xml文檔解析,獲得所有的節點並轉換成更好用的屬性提供API進行使用,在java中poi能根據這個名稱拿到圖片相對應的資源,而獲取圖片位置的關鍵也就是這里。

但是很不幸,我用的是php~~~所以我們需要通過php的相關接口手動實現獲得圖片.
下面說一下我的具體思路:通過PHP的內置DOMDocument接口獲得docx文檔的xml節點,遍歷xml節點找到保存圖片的節點元素,向下遍歷圖片節點扎到r:embed索引的值。因為docx文檔是一個壓縮包格式,所以通過PHP內置接口ZipArchive接口遍歷該docx文檔(實質就是遍歷.zip壓縮包),通過索引找到對應的圖片,轉換成二進制數據,在拼接img標簽顯示格式為base64的圖片數據。
轉換成xml:
private $rels_xml;
private $doc_xml;
private function readZipPart($filename) {
$zip = new ZipArchive();
$_xml = 'word/document.xml';
$_xml_rels = 'word/_rels/document.xml.rels';
if (true === $zip->open($filename)) {
if (($index = $zip->locateName($_xml)) !== false) {
$xml = $zip->getFromIndex($index);
}
$zip->close();
} else die('non zip file');
if (true === $zip->open($filename)) {
if (($index = $zip->locateName($_xml_rels)) !== false) {
$xml_rels = $zip->getFromIndex($index);
}
$zip->close();
} else die('non zip file');
$this->doc_xml = new DOMDocument();
$this->doc_xml->encoding = mb_detect_encoding($xml);
$this->doc_xml->preserveWhiteSpace = false;
$this->doc_xml->formatOutput = true;
$this->doc_xml->loadXML($xml);
$this->doc_xml->saveXML();
$this->rels_xml = new DOMDocument();
$this->rels_xml->encoding = mb_detect_encoding($xml);
$this->rels_xml->preserveWhiteSpace = false;
$this->rels_xml->formatOutput = true;
$this->rels_xml->loadXML($xml_rels);
$this->rels_xml->saveXML();
}
判斷是否為圖片節點:
if($paragraph->name === 'w:drawing') {
(strstr($ts,'…封…') != false || strstr($ts,'…線…') != false) ? $t .= '' : $t .= $this->analysisDrawing($paragraph);
}
獲得圖片索引:
private function analysisDrawing(&$drawingXml) {
while($drawingXml->read()) {
if ($drawingXml->nodeType == XMLREADER::ELEMENT && $drawingXml->name === 'a:blip') {
$rId = $drawingXml->getAttribute('r:embed');
$rIdIndex = substr($rId,3);
return $this->checkImageFormating($rIdIndex);
}
}
}
顯示壓縮包中圖片文件:
private function checkImageFormating($rIdIndex) {
$imgname = 'word/media/image'.($rIdIndex-8);
$zipfileName = __DIR__.DIRECTORY_SEPARATOR.'b'.DIRECTORY_SEPARATOR.'test.docx';
$zip=zip_open($zipfileName);
while($zip_entry = zip_read($zip)) {//讀依次讀取包中的文件
$file_name=zip_entry_name($zip_entry);//獲取zip中的文件名
if(strstr($file_name,$imgname) != '' ) {
$a = ($rIdIndex-8 < 10) ? mb_substr($file_name,mb_strlen($imgname,"utf-8"),1, 'utf-8') : '';
if($rIdIndex-8 < 10 && $a != '.') continue;
if ($enter_zp = zip_entry_open($zip, $zip_entry, "r")) { //讀取包中文件
$ext = pathinfo(zip_entry_name ($zip_entry),PATHINFO_EXTENSION);//獲取圖片文件擴展名
$content = zip_entry_read($zip_entry,zip_entry_filesize($zip_entry));//讀取文件二進制數據
return sprintf('<img src="data:image/%s;base64,%s">', $ext, base64_encode($content));//利用base64_encode函數轉換讀取到的二進制數據並輸入輸出到頁面中
}
zip_entry_close($zip_entry); //關閉zip中打開的項目
}
}
zip_close($zip);//關閉zip文件
}
轉載地址:https://www.cnblogs.com/lishanlei/p/9283974.html

