创建在线测试的思想来自于我寻找XML的辅导课程的一次偶然机会。我访问了W3School.com网站发现更令人感兴趣是这个网站里的一个在线测试而不仅仅是寻找XML的辅导课程。我一次做了20道测试题感觉相当好。我不知道为什么只有一些网页提供了这种在线测试的服务。
测试是一个很好的考察知识的方法。一个在线测试是对一个网页来说是一个很好的补充,他可以让访问者停留在网页上更长的时间。
下载了试用版本并用了一下,是一个加强你对澳大利亚地理知识的十道测验。他的答案是写在一个透明的普通人都可以看得懂的XML文档,我们可以很容易看到答案。
代码解释
我不会详细解释代码的每个细节,而重点讨论其中某些部分。一旦明白代码的整体意义,就可以修改或增加代码以满足个人需求。
递归代码
在这个在线测试的网页里只有一个活动的递归代码完成多项不同的任务,递归代码就是一种可以反复调用自己本身直到定义的条件达到为止。准确的说这种代码并没有调用自身,只是传递形成的数据给自身,这个过程被称为往回传递。
由于这种代码在测试期间不断的往回传递信息给自身,可以说这种代码有多种状态。第一个状态是初始化一些基本变量,计算总的问题数和记录测试的开始时间。然后,在第一个和以后的各个状态里该代码让页面上显示出一道多项选择题让用户回答(可以参看上面的图片)。回答问题后会触发一个onClick事件促使往回传递,并推动代码运行到下一个状态。在这个状态下代码会在事件发生的同时运行一个子程序来检查答案并显示下一道问题。递归不断重复直到最后一个问题回答完毕,页面显示出答题结果的时候。
下面的工作图表可以表示出这种在线测试的递归流的代码的运行过程:
状态维护
在线测试的代码需要维护它各个变量的状态。有许多可供选择的办法来解决,最先进的方法是使用session对象而最传统的方法是使用输入隐藏或者使用一种QueryString。ASP.NET引入了一种叫做‘state bag’的方法。与session对象不同的是‘state bag’不是一直检测整个用户会话过程而是象隐藏输入和QueryString一样提前到从一页到另一页转换的时候。然而它又比隐藏输入和QueryString高级,这是因为它可以接受更多的数据类型以及它的内容被编译成一个单独的字符串从而不容易被任意更改。
存储数值到‘state bag’
ViewState("TotalQuestion") = intTotalQuestion
从‘state bag’中取值
intTotalQuestion = ViewState("TotalQuestion")
下面的表格是变量存储在‘state bag’中的清单
变量名 |
State Bag 名 |
数据类型 |
说明 |
intTotalQuestion |
TotalQuestion |
int |
在测试中保存总问题数。它的值在初始状态的时候赋予并保存为恒量。 |
intScore |
Score |
int |
保存答对的问题数 |
intQuestionNo |
QuestionNo |
int |
保存目前用户回答的最后一个问题的题号 |
arrAnswerHistory |
AnswerHistory |
arraylist of int |
记录测试的答案。如果回答正确记0否则记录所选择的答案的索引号。 |
(none) |
CorrectAnswer |
int |
保存上一问题的正确答案。当答案被检查正确是下一状态可用。 |
(none) |
StartTime |
date |
保存测试的开始时间,可以用来计算测试所耗费的时间。 |
XML数据
在线测试的数据保存在叫做quiz.xml的可以使用XML表quiz.xsd的XML的文档里。一个有效的XML文档含有一个叫quiz的元素集合里,它至少有一个叫mchoice的元素(multiple-choice的简写)。每个mchoice元素有一个问题子元素和两个以上的答案子元素。答案元素可能有correct属性其值为yes或no。事实上,在一同个mchoice里要给每个答案一个correct属性的yes值,否则这个问题就不会有正确答案。
quiz.xml:
<?xml version="1.0" encoding="UTF-8"?>
<quiz xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="quiz.xsd">
<mchoice>
<question>What is the capital city of Australia ?</question>
<answer>Sydney</answer>
<answer correct="yes">Canberra</answer>
<answer>Melbourne</answer>
<answer>Gold Coast</answer>
</mchoice>
<mchoice>
<question>Which city has an extensive tram network?</question>
<answer>Sydney</answer>
<answer correct="yes">Melbourne</answer>
<answer>Adelaide</answer>
<answer>Ballarat</answer>
</mchoice>
</quiz>
为XML数据插入HTML标签是必要的,可以使页面含有修饰过的文档,图片,链接等而不会只有单调的文档。只要把CDATA块围绕在HTML标签周围就可以保证XML文档的可用性。看看下面的例子:
<mchoice>
<question><![CDATA[Which of the following is <u>NOT</u>
Australian native animals?]]></question>
<answer>Kangaroo</answer>
<answer correct="yes">Penguin</answer>
<answer>Koala</answer>
<answer>Wombat</answer>
</mchoice>
quiz.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="quiz">
<xs:complexType>
<xs:choice>
<xs:element name="mchoice" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="question" type="xs:string"/>
<xs:element name="answer" minOccurs="2" maxOccurs="unbounded">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="correct" use="optional">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="yes"/>
<xs:enumeration value="no"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
有以下几个原因可以说明在线测试的代码不会使XML文档违背XML表。
第一、 它是一种强制资源进程,迫使XMLTextReader浏览文档里的每个元素每个属性。
第二、 我们不需要每次载入文件时激活XML文档只要在更新以后激活一次就够了,可以使用一个单独的aspx代码或者使用第三方工具并在每次XML文档更新完后手动运行来激活XML文档
XML文档的对象模板
XML文档的对象模板(DOM)是一个基于与其父结点和子结点继承关系的表现每个XML文档结点的树状结构。XML文档的对象模板允许通过多种逻辑途径来操作XML文档
我们使用XMLDocument类来建立XML文档的对象模板。XMLDocument类是对XMLNode类的扩展,它可以从XMLNode类中继承许多性质和方法。当XMLNode类的性质与方法在XML文档的某个结点应用的时候,XMLDocument类的性质与方法就在整个XML文档中应用。
下面的代码是从quiz.xml建立XMLDocument类的实例和XML文档的对象模板
Dim xDoc as XMLDocument = New XMLDocument()
xDoc.Load(Server.MapPath("quiz.xml"))
在XML文档的对象模板中定位一个具体的结点有一点技巧。我们要让指针‘pointer’从根结点通过XML文档的对象模板的起点。下面的代码表明如何定位第一个问题和第一个多选项:
Dim xNode as XMLNode
'Goto the first/base element in the file, which is <?xml ?>
xNode = xDoc.DocumentElement
'Goto the next sibling of current node, which is <quiz>
xNode = xNode.NextSibling
'Goto the first child of current node, which is <mchoice>
xNode = xNode.FirstChild
'Goto the first child of current node, which is <quiz>
xNode = xNode.FirstChild
'Print the content of current node
Response.Write("The question is: " & xNode.InnerHtml)
很明显是一个单调乏味的工作,尤其是当你想定位到一个处于低层次的结点的时候。幸运的是我们可以利用XPath语言来更直接定位一个具体结点或者一组结点。如果你多XPath不熟悉,可以参看下一段对它的简单介绍。
XMLNode 和 XMLDocument类的 SelectNodes方法接受一个XPath字符串并返回一组叫做XMLNodeList的XMLNode的对象。另一种方法叫做SelectSingleNode作同样的事情但只返回一个XMLNode对象
'Using SelectNodes to address all answers of the first multiple choice
Dim xNodeList As XMLNodeList
Dim i as Integer
xNodeList = xDoc.SelectNodes("/quiz/mchoice[1]/answer")
For i = 0 to xNodeList.Count-1
Response.Write("<p>" & xNodeList.Item(i).InnerHtml)
Next
'Using SelectSingleNode to select the first question of the first multiple choice
Dim xNode as XMLNode
xNode = xDoc.SelectSingleNode("/quiz/mchoice[1]/question")
Response.Write("The question is: " & xNode.InnerHtml)
XPath
XPath是一种在XML文档中定位具体结点的语言。它通过一个描述继承关系的字符串来定位一个结点或一组结点,因此这种字符串通常被称为XPath字符串。如果你对文件路径或URL熟悉的话那么XPath的概念并不是新东西。
从XPath字符串的左边读到右边你就可以指出它所定位的结点。除了一些情况或用途,XPath都很好用。下面的表格是XPath依靠quiz.xml的一些使用:
XPath字符串 |
结果 |
/quiz |
选择包含所有元素的XML根结点 |
/quiz/mchoice |
选择quiz的所有mchoice子元素 |
/quiz/mchoice[1] |
选择quiz的第一个mchoice的子元素 |
/quiz/mchoice[1]/question |
选择quiz的第一个mchoice的所有问题 |
/quiz/mchoice[1]/answer[4] |
选择quiz 的第一个mchoice的第四个答案 |
/quiz/mchoice[1]/answer[4]/@correct |
选择quiz的第一个mchoice的第四个答案的‘correct’属性 |
XPath包含太多的惊奇。如果你有兴趣了解更多的话,可以在W3CSchools中找到XPath的辅导课程。
总结
这篇文章介绍了在线测试作为一种工具对你的网页来说是很好的补充。它也阐述了一些主题象递归代码,使用在XML文档的对象模板来操作和控制XML文档,稍微提到XPath语言,并简单讨论了状态维护。
评论:
# re: 使用XML的DOM和XPath来创建多项选择题的在线测试 2005-06-28 15:17 | .Net Swimmer
re: 使用XML的DOM和XPath来创建多项选择题的在线测试 2005-06-23 10:17 | YuL
文章没完全看懂,但在看的过程中想到一个小小的问题,就是这里是如何对付[后退]的嗫???我是一个很不小心的参与者,当我点了[NEXT]才发现好象自己应该选另外一个答案,那我只好点[后退]再填,那么这时VIEWSTATE里的东东是回到以前的状态嗫,还是已经到了新的状态?然后就是当我又一次点 [NEXT]后,突然又改变主意,想改前面某一题的选择,我又不断的点[后退],结果哦哦哦~~~~晕倒!HOW TO?
呵呵,这些好像不是本文考虑的问题。在具体实现过程中,这的确应该好好解决。感谢
文章没完全看懂,但在看的过程中想到一个小小的问题,就是这里是如何对付[后退]的嗫???我是一个很不小心的参与者,当我点了[NEXT]才发现好象自己应该选另外一个答案,那我只好点[后退]再填,那么这时VIEWSTATE里的东东是回到以前的状态嗫,还是已经到了新的状态?然后就是当我又一次点 [NEXT]后,突然又改变主意,想改前面某一题的选择,我又不断的点[后退],结果哦哦哦~~~~晕倒!HOW TO?
呵呵,这些好像不是本文考虑的问题。在具体实现过程中,这的确应该好好解决。感谢