PHP生成图文混排的Word XML试卷
作者:李永中 甘肃省成县城关中学 联系: [email protected]
摘要:本文介绍不借助任何插件及COM组件,通过借鉴Word XML基本代码,采用移花接木思想,直接由PHP生成图文及特殊符号混排的Word XML文档。
关键词:图文混排;试卷;Word XML;
随着网络技术的发展,无纸化的网络办公已逐渐成为一种时尚。但在服务端生成图文混排的Word文档一直困挠着网络技术工作者,一直制约着网络技术在各个领域的发展,在教育领域显得尤为突出。目前利用网络技术生成图文并茂的纸质卷,仍然是影响网络技术在教育领域发展的拦路虎。
但铁捧磨锈针,工夫不负有心人,今天这个问题可以迎刃而解。
一.实现思想——移花接木
目前,并不能直接生成图文混排的Word文档,但可直接生成Word XML文件, 这个文件就如同HTML页一样,由PHP生成不存在什么问题。问题关键在于生成的文件必须兼容Word及WPS?考虑到XML语言的特点,只认数据,不认格式。要生成兼容于Word的XML文件,就必须符合微软的XML规范,而这个规范由于技术的壁垒,不为外人所知,因此只有借助微软自身的XML代码来生成试卷,这样兼容性问题就解决了。
下面如何分离Word XML功能代码。
二.提取并分离Word XML功能代码
(一)制作标准试卷XML模板
在模板中,按标准试卷的格式,设计好这个信息:卷头部分,试卷标题样式,大题号样式,选择型试题表格定位样式,图片样式,页码样式,试卷版面样式等。设计好后,反复修改,直到形成一个固定的版面。
(二)提取并分离功能代码
1.试卷标题及卷头代码块
用记事本打开XML模板,按以下信息块进行分析,你可以看到,试卷标题及卷头部分可以划归到个代码块,这一块的代码存入first.txt。在存入前将试卷标题用自编的长字串替换,此字串切忌不要与模板中的某此关键字符相冲突,first.txt文件的编码方式为UTF-8 (以下的.txt文件均与此同)。
2.大题标题段落和一般的正文段落
大题标题段落存入 p1.txt,段落内容字串biaotimsg,整段代码为:
<w:p><w:pPr><w:pStyle w:val="a8"/><w:ind w:left="0" w:first-line-chars="200"/></w:pPr><w:r><w:rPr><w:rFonts w:hint="fareast"/><wx:font wx:val="宋体"/></w:rPr><w:t> biaotimsg</w:t></w:r></w:p>
一般的五号字正文段落存入p2.txt,段落内容字串xiaotihaomsg,整段代码为:
<w:p><w:pPr><w:pStyle w:val="a8"/><w:ind w:left="0" w:first-line-chars="0"/></w:pPr><w:r><w:rPr><w:rFonts w:hint="fareast"/></w:rPr><w:t>xiaotihaomsg
</w:t></w:r></w:p>
3.
( )1. 以下是FLASH源文件格式的是: |
||
(A) .fla |
|
(B) .swf |
(C) .doc |
|
(D) .wps |
表格设计成不打印,仅在编辑时可见,一个选择题就是一个表格,将题干和选项全部放进表格中。将制作好的word试卷转换为XML格式,然后用记事本打开,分析其代码大致组成及原理。经过研究发现,生成XML文件还是有规律可循的。
XML文件的规律:XML文件跟HTML语言一样,将代码按重复可编辑部分进行划分,可将一个完整XML试卷代码分成12个小段。将这12段代码分割成功后分别放到网站目录下的word-example文件夹中,用时程控读取并写入生成新的XML试卷文件,其XML文件生成的就跟生成HTML文件没有什么区别了。这12段代码将在程序中给出解释,这里暂不说明。
(二)生成纸质试卷
事先设计一个页面,用来输入试卷标题等卷头信息,并设计四个题型的复选框,让用户选择生成试卷的题型,当用户点击生成试卷时,会按选定的题型搜索AB组卷表的ID号,按ID号从原始试题库读出试题生成XML试卷。
其过程核心代码如下,下面代码中,除生成XML格式试卷外,同时生成试卷答案.txt文件。
<?php
session_start();
$_SESSION['duoxuanstart']=0; $_SESSION['duoxuanend']=-1;//多选题的开始点结束点
$_SESSION['fenshu']=0; $_SESSION['danxuantishu']=0;//总分和单选题数
$_SESSION['panduanstart']=0;$_SESSION['panduantishu']=0;//判断题的开始点和结束点
$_SESSION['itemnum']=0; $_SESSION['tiankongnum']=0;//大题数和填空数
$_SESSION['optnum']=0 ;//所有小题数:包括填空题、选择题、判断题小题数的总和
$answer=array();$tiankonganswer=array();$item=array();$_SESSION['answer']=$answer ;
$_SESSION['tiankonganswer']=$tiankonganswer;$item=@$_GET["item"];$kemu=$_SESSION['kemu'];
$shijuanid=$_GET["shijuanid"];//试卷类型:A或B
$id=$_GET["id"];//AB组卷表中组卷记录ID号
$_SESSION["path"]=$_SERVER['DOCUMENT_ROOT']."/test/";
$n=count($item);//$n为题型数,即实际大题数,并按次序赋值给tablename
$_session["datishu"]=$n;
if($n==0){echo "<script type='text/javascript'>alert('没有选择任何生成试卷的题型,页面将跳转到系统主页面!');self.location.href='/test/make_test.php';</script>";exit();
}
if($n !=0){
//以下十多行代码为XML试卷代码固定部分,$testname纸质试卷文件名,$testanswer为答案文件名
$testname=$_SESSION["path"]."wordtest/".session_id()."_".$_GET["shijuanid"].".xml";
$testanswer=$_SESSION["path"]."wordtest/".session_id()."_".$_GET["shijuanid"]."_answer.txt";
file_put_contents($testanswer,"---------------------".$_SESSION["testname"]."(答案)----------------------"."\r\n\r\n\r\n");
//分析word试卷的XML代码,将可编辑部分提出来,为程控写入代码作准备。
$xml1=file_get_contents($_SESSION["path"]."word-example/xml1.txt");
$xml2=file_get_contents($_SESSION["path"]."word-example/xml2.txt");
//$xml1和$xml2为试卷标题前后代码,下面是写入XML试卷试卷标题
file_put_contents($testname,$xml1.$_SESSION["testname"].$xml2);
//$xml3和$xml4为每道大题标题前后代码
$xml3="<w:p></w:p> <w:p><w:r><w:rPr><w:rFonts w:hint='fareast'/><wx:font wx:val='宋体'/><w:sz w:val='24'/><w:sz-cs w:val='24'/></w:rPr> <w:t>";
$xml4="</w:t></w:r></w:p>";
//xml5和XML6为一般的段落前和段落后代码,如填空题的每一小题
$xml5="<w:p><w:pPr><w:pStyle w:val='a8'/><w:ind w:left='420' w:hanging='420'/></w:pPr><w:r><w:rPr><w:rFonts w:hint='fareast'/><wx:font wx:val='宋体'/></w:rPr><w:t>";
$xml6="</w:t></w:r></w:p>";
//$xml7是选择题的表格控制代码,在每个选择题小题前必须写入此代码一次。
$xml7=file_get_contents($_SESSION["path"]."word-example/xml7.txt");
//$xml8和$xml9为表格第一行题干行前后控制代码。
$xml8="<w:tr><w:trPr><w:trHeight w:val='327'/></w:trPr>\<w:tc><w:tcPr><w:tcW w:w='8540' w:type='dxa'/><w:gridSpan w:val='3'/></w:tcPr><w:p><w:pPr><w:rPr><w:sz w:val='23'/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:hint='fareast'/><wx:font wx:val='宋体'/><w:sz w:val='23'/></w:rPr><w:t>";
$xml9="</w:t></w:r></w:p></w:tc></w:tr>";
//$xml10和$xml11为A选项和 C选项前后代码,$xml12为B选项和D选项后代码。
$xml10=file_get_contents($_SESSION["path"]."word-example/xml10.txt");;
$xml11=file_get_contents($_SESSION["path"]."word-example/xml11.txt");;
$xml12=file_get_contents($_SESSION["path"]."word-example/xml12.txt");
//表格与表格之间的分隔行,若不分隔,两小题的表格会粘在一些,加入这段代码便于后期编辑。
$p_height="<w:p><w:pPr><w:pStylew:val='a8'/><w:spacing w:line='100' w:line-rule='exact'/><w:ind w:left='420' w:hanging='420'/></w:pPr></w:p>";
}
//自定义打开数据库函数
function opendatabase($sql,$flag){
$con=mysql_connect("localhost","root",""); $f=mysql_select_db($_SESSION["kemu"],$con);
$res=mysql_query($sql,$con);if($flag==1){return($res);} mysql_close($con);
}
//针对abshijuanku数据库的自定义函数,从中快速组卷信息表搜索出试题信息
function openselitemid($sql,$flag){
$con=mysql_connect("localhost","root","");$f=mysql_select_db("abshijuanku",$con);
if(! $f){
echo "<script type='text/javascript'>alert('打开test数据库失败,请确定该数据库是否存在!');</script>";mysql_close($con);exit();}
$res=mysql_query($sql,$con);$res=mysql_query($sql,$con); if($flag==1){return($res);} mysql_close($con);
}
//若用户所选试题类型数不为0,从AB试卷信息表中得到试题的ID号,并在试题库读出试题
if($n !=0){$mainstr="<div id='main'>";
for($i=0;$i<$n;$i++){
$sql="select ".$item[$i]." from ".$kemu." where id='".$_GET["id"]."'";
$res=openselitemid($sql,1);$row=mysql_fetch_array($res);$idstr=$row[$item[$i]];
if(!$idstr){echo "手动抽题-快速组卷表中".$item[$i]."题型为空,请检查后再试!";break;}
switch($item[$i]){
case "tiankong":
$itemtitle[$i]="填空题";
funtiankong($testname,$testanswer,$xml3,$xml4,$xml5,$xml6,$itemtitle[$i],$idstr);break;
case "danxuan":
$itemtitle[$i]="单选题";
fundanxuan($testname,$testanswer,$xml3,$xml4,$xml7,$xml8,$xml9,$xml10,$xml11,$xml12,$p_height,$itemtitle[$i],$idstr);break;
case "duoxuan":
$itemtitle[$i]="多选题";
funduoxuan($testname,$testanswer,$xml3,$xml4,$xml7,$xml8,$xml9,$xml10,$xml11,$xml12,$p_height,$itemtitle[$i],$idstr); break;
case "panduan":
$itemtitle[$i]="判断题";
funpanduan($testname,$testanswer,$xml3,$xml4,$xml5,$xml6,$itemtitle[$i],$idstr);break;
}
}
$_SESSION["zongfen"]=$_SESSION["fenshu"];
//最后写入XML试卷结束代码:end.txt。
$filestr=file_get_contents($_SESSION["path"]."word-example/end.txt");
file_put_contents($testname,$filestr,FILE_APPEND);
$_SESSION["itemnum"]=$_SESSION["itemnum"]+$_SESSION["optnum"];
//试卷生成后的提示信息
$end3=" <div><h2>试卷编辑者须知</h2>
<li><b>试卷总分:</b>试题实际总分为<b>".$_SESSION['fenshu']."分</b>,请注意看与预设总分是否相符。不相符请返回继续添加试题或修改预设总分。</li><br />
<li><b>纸质试卷下载:</b></li><br />
<form id='downloadfrm' action='/test/download.php' method='get' >
<input type='hidden' name='filename' />
<table border='thin' width='760px' align='center' cellspacing='0' >
<tr><td colspan='2' align='center'><b>下载纸质试卷和参考答案(生成的试卷只能用word打开)</b></td></tr>
<tr align='center'><td><input type='submit' name='testbtn' value='下载纸质试卷' onclick='btnclick(this.name)' /></td><td><input type='submit' name='answerbtn' value='下载试卷答案' onclick='btnclick(this.name)' /></td></tr>
</table></form><br/>
<script type='text/javascript' >
function btnclick(name){
if(name=='answerbtn'){ obj=document.getElementById('downloadfrm'); obj.filename.value='".$testanswer."'; }
if(name=='testbtn'){ obj=document.getElementById('downloadfrm'); obj.filename.value='".$testname."'; }
}
</script></div>";
echo $end3; echo "<script type='text/javascript' >";
echo "alert('当前实际总分为".$_SESSION["fenshu"].",测试卷实际总分可能跟设计值不同,请继续添加试题或修改卷头总分!另外生成的纸质试卷在页后,请注意下载。');";
echo "</script>"; echo "</div>"; echo "</form>";
//输出填空题,不是重点,代码参照单选题,以下输出单选题
function fundanxuan($testname,$testanswer,$xml3,$xml4,$xml7,$xml8,$xml9,$xml10,$xml11,$xml12,$p_height,$title,$idstr){
$answerstr=""; $tiganstr="";$idarray=explode(",",$idstr); $rows=count($idarray); $i=0;$datizongfen=0;
if($rows<=0){echo "<p style='margin-left:50px;'>该题型试题库为空</p>"."</div>";}
if($rows>0){ $_SESSION['kong']=0; $n=0;
for($i=0;$i<$rows;$i++){
$res=opendatabase("select * from danxuan where id=".$idarray[$i],1);
$row=mysql_fetch_array($res);$id=$idarray[$i];$tigan=$row["tigan"];
$opta=$row["opta"];$optb=$row["optb"];$optc=$row["optc"];$optd=$row["optd"];
$fenzhi=$row["fenzhi"];$n++;
$_SESSION["answer"][$n]=$row["answer"];$datizongfen=$datizongfen+$fenzhi;
//记录纸质试卷的答案和题干
$answerstr= $answerstr."(".($i+1).")".$_SESSION["answer"][$n]." ";
//将所有选择题XML代码放于题干字符串
$tiganstr= $tiganstr.$xml7.$xml8."( )".($i+1).". ".$tigan.":".$xml9.$xml10."(A) ".$opta.$xml11."(B) ".$optb.$xml12.$xml10."(C) ".$optc.$xml11."(D) ".$optd.$xml12."</w:tbl>".$p_height;
}
$answerstr= $answerstr."\r\n";$_SESSION["fenshu"]=$_SESSION["fenshu"]+$datizongfen ;
$_SESSION["optnum"]=$n ;$_SESSION["danxuantishu"]=($i+1) ;$_SESSION['duoxuanstart']=$n+1 ;
$_SESSION["duoxuanend"]=$n+1;
//写入纸质试卷答案选择题标题
file_put_contents($testanswer,"二. 选择题(每小题:".$fenzhi."分,总小题数:".($i+1).",共:".$datizongfen."分)"."\r\n\r\n",FILE_APPEND);
//以下3行代码生成纸质试卷
file_put_contents($testanswer,$answerstr."\r\n",FILE_APPEND);
file_put_contents($testname,$xml3."二. 选择题(每小题:".$fenzhi."分,(总小题数:".($i+1).",共:".$datizongfen."分)".$xml4,FILE_APPEND);
file_put_contents($testname,$tiganstr,FILE_APPEND);
}}
//输出多选题,判断题跟单选题基本类似,略
?>
生成的试卷文件名分别用session_id()._A.XML和session_id()._B.XML来表示。
(三)Download.php 文件下载页
实现强制下载,消除答案文件在下载时,被浏览器打开而造成的乱码现象。此段代码可以从http://www.hackbase.com去下载。
三.参阅文献资料
1. 有关技术知识点参阅了 “W3school WEB技术教程网:http://www.w3school.com.cn/ ,在设计过程中给了很朋的帮助,在此表示真心的感谢!
2. 还有个别的技术知识点,来自于百度热心提示,在这里一并表示感谢!
四. 作者简介
作者:李永中,生于1973年,1999年7月毕业于甘肃省西北师范大学电化教育系,现工作于甘肃省成县城关中学,致力于网络教学研究。
联系方式:QQ :1184373398 [email protected] 。