需求
将富文本编辑后的HTML代码片段转换为Word文件,替换Word文件模板变量,加盖章,生成并打包下载
摘要
在尝试使用PHPWord进行转换后,生成的Word文件没有样式,使用原生生成方式PHPWord则无法打开文件,因此转换思路如下
- 先将HTML代码块格式化为可导出的Word格式(视图、保留样式)
- 替换HTML内模板参数为变量
- 添加印章变量
- 打包zip下载(此处生成了多个文件所以多了一步打包zip)
工具类代码
<?php
namespace app\common\utils;
/**
* 自定义 HTML与Word操作类处理
* @author Teanxo
*/
class Html2Word
{
private $html;
private $htmlTagTemp = <<<HTML
<html xmlns:v='urn:schemas-microsoft-com:vml'xmlns:o='urn:schemas-microsoft-com:office:office'xmlns:w='urn:schemas-microsoft-com:office:word'xmlns:m='http://schemas.microsoft.com/office/2004/12/omml'xmlns='http://www.w3.org/TR/REC-html40'>
HTML;
/**
* html字符内 挂载到header标签内的模板
* @var string
*/
private $headerTmp = <<<HTML
<!--[if gte mso 9]><xml><w:WordDocument><w:View>Print</w:View><w:TrackMoves>false</w:TrackMoves><w:TrackFormatting/><w:ValidateAgainstSchemas/><w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid><w:IgnoreMixedContent>false</w:IgnoreMixedContent><w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText><w:DoNotPromoteQF/><w:LidThemeOther>EN-US</w:LidThemeOther><w:LidThemeAsian>ZH-CN</w:LidThemeAsian><w:LidThemeComplexScript>X-NONE</w:LidThemeComplexScript><w:Compatibility><w:BreakWrappedTables/><w:SnapToGridInCell/><w:WrapTextWithPunct/><w:UseAsianBreakRules/><w:DontGrowAutofit/><w:SplitPgBreakAndParaMark/><w:DontVertAlignCellWithSp/><w:DontBreakConstrainedForcedTables/><w:DontVertAlignInTxbx/><w:Word11KerningPairs/><w:CachedColBalance/><w:UseFELayout/></w:Compatibility><w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel><m:mathPr><m:mathFont m:val='Cambria Math'/><m:brkBin m:val='before'/><m:brkBinSub m:val='--'/><m:smallFrac m:val='off'/><m:dispDef/><m:lMargin m:val='0'/> <m:rMargin m:val='0'/><m:defJc m:val='centerGroup'/><m:wrapIndent m:val='1440'/><m:intLim m:val='subSup'/><m:naryLim m:val='undOvr'/></m:mathPr></w:WordDocument></xml><![endif]-->
HTML;
/**
* 当有一些不是成员变量的时候
* 需要指定一个对象 才可对特殊变量进行高级赋值操作
* 代替适配器,使用动态调用方式
* $operationInstance->$func($sourceData)
* @var null
*/
private $operationInstance = null;
/**
* 将一个HTML字符串格式为可导出Word的数据
*/
public function replaceHTML()
{
// 判断字符串中是否存在HTML标签
$htmlIndex = strpos($this->html, "<html");
if ($htmlIndex !== false) {
$htmlEndIndex = stripos($this->html, ">");
// 删除HTML开始头部标签
$endHtml = substr($this->html, $htmlEndIndex + 1);
$this->html = $this->htmlTagTemp . $endHtml;
} else {
$this->html = $this->htmlTagTemp . $this->html . "</html>";
}
// 判断字符串中是否存在header标签
$headerIndex = stripos($this->html, "<head>");
$hRepIndex = stripos($this->html, "<!--[if gte mso 9]>");
// header标签 且定义插入标签不存在时候执行
if ($headerIndex !== false && $hRepIndex === false) {
// 拿到meta标签开始位置
$startIndex = stripos($this->html, "<meta");
// 必须匹配到meta结束标签才能执行
preg_match_all('/<meta(.*)$/m', $this->html, $matches);
if (count($matches) > 0) {
// 拿到head结束标签位置
$lastMetaStr = end($matches[0]);
$lastIndex = strpos($this->html, $lastMetaStr);
$endIndex = $lastIndex + strlen($lastMetaStr);
// 为header插入word导出所需标签
$startHtmlStr = substr($this->html, 0, $lastIndex);
$endHhtmlStr = substr($this->html, $endIndex);
// 为head拼接指定标签
$this->html = $startHtmlStr . $this->headerTmp . $endHhtmlStr;
}
} else if ($headerIndex === false) {
// header标签不存在 那么创建
// 拿到meta标签开始位置
$startIndex = stripos($this->html, "<meta");
// 拿到meta标签结束位置
preg_match_all('/<meta(.*)$/m', $this->html, $matches);
if (count($matches) > 0) {
// 拿到head结束标签位置
$lastMetaStr = end($matches[0]);
$lastIndex = strpos($this->html, $lastMetaStr);
$endIndex = $lastIndex + strlen($lastMetaStr);
$startHtmlStr = substr($this->html, 0, $lastIndex);
$endHhtmlStr = substr($this->html, $endIndex);
// 为head拼接指定标签
$this->html = $startHtmlStr . "<head>" . $this->headerTmp . "</head>" . $endHhtmlStr;
}
}
return $this;
}
/**
* 将模板化的HTML变量 替换
* @param array $tmpVar 示例 ['模板名称 例如:项目名称'=>'字段名称']
* @param array $sourceData 示例 ['字段名称'=>'字段值']
* @return $this
*/
public function variables(array $tmpVar, array $sourceData)
{
foreach ($tmpVar as $key => $value) {
if (is_string($value) && isset($sourceData[$value])) {
$this->variable('$(' . $key . ')', $sourceData[$value]);
} else if (is_array($value) && key_exists("func", $value)) {
$func = $value['func'];
$this->html = str_replace('$(' . $key . ')', $this->operationInstance->$func($sourceData), $this->html);
}
}
return $this;
}
/**
* 单个模板变量赋值
* @param string $tmpName
* @param $val
*/
public function variable(string $tmpName, $val)
{
$this->html = str_replace($tmpName, $val, $this->html);
return $this;
}
/**
* 将变量以正则表达式形式进行赋值
* @param $pattern
* @param $val
*/
public function pregMatchVariable($pattern, $val)
{
$this->html = preg_replace($pattern, $val, $this->html);
return $this;
}
/**
* 写入Word文件
* @param string $fileName 文件路径
* @return void
*/
public function writeWord(string $fileName)
{
$writefile = fopen($fileName, 'wb') or die("创建文件失败"); //wb以二进制写入
fwrite($writefile, $this->html);
fclose($writefile);
file_put_contents($fileName, $this->html);
}
/**
* 挂载HTML数据
* @param mixed $html
*/
public function setHtml(string $html)
{
$this->html = $html;
return $this;
}
/**
* 获取HTML数据
* @return string
*/
public function getHtml()
{
return $this->html;
}
/**
* @param $operationInstance
*/
public function setOperationInstance($operationInstance)
{
$this->operationInstance = new $operationInstance();
return $this;
}
}
使用方法
$html2Word = new Html2Word();
// 一些示例数据
$configs = array(
'项目名称' => 'product_name',
'项目别名' => 'product_as',
// 当此处设置为 array时 且存在func属性,那么会执行 Html2WordOperation类中的seal方法,也就是setOperationInstance时传入的类
'盖章' => [
'func' => 'seal'
]
);
$variableData = array(
'product_name' => '小米手机',
'product_as' => '小米'
);
$html2Word->setOperationInstance(Html2WordOperation::class) // 传入自定义操作类
->setHtml($productTemplateinfo['content']) // 挂载HTML代码片段
->replaceHTML() // 整理为可导出Word格式
->variables($configs, $variableData) // 将HTML代码模板的变量转为实际参数
->pregMatchVariable('/人民币【.*万元整+/', $amountText) // 正则表达式变量赋值
->writeWord($filePath) // 导出文件;
Html2WordOperation类示例
/**
* 对转换中变量进行特殊方法处理
*/
class Html2WordOperation
{
/**
* 生成盖章
* @param $sourceData 同等variableData
* @return string
*/
public function seal($sourceData)
{
$seal = $sourceData['seal'];
// 读取盖章数据
return <<<HTML
<img src="$seal" style="width: 100px;height: 100px;margin-top: 15px;" />
HTML;
}
}