在本文中,将探讨一个基于MVC(Model-View-Controller)架构的XML数据查看器的设计与实现。这个查看器使用JavaScript和DOM(Document Object Model)来解析和展示XML数据。为了更好地理解本文,建议先阅读有关使用Internet Explorer作为脚本GUI的文章,以及了解如何在JScript中读取二进制文件的背景知识。此外,本文也会涉及AJAX的相关内容。
Exsead的核心概念在这里被介绍。为了从本文中获得最大的收益,最好已经阅读了有关使用Internet Explorer作为脚本GUI的文章。本文的代码也利用了作者的JScript读取二进制文件的系统,该系统在这里有详细描述。本文使用的AJAX背景信息在这里。
代码示例中包含了XMLViewer.js脚本,该脚本使用MVC概念来构建一个简单但有效的XML数据查看器。还包括了一个更简单的MVC示例(来自之前关于使用Internet Explorer作为GUI的文章),它被称为MVCExample.js。任何像这样解析XML的脚本的目的是将存储在XML中的原始数据转换为可用的知识。XML本身是完全静态的数据,没有用处;它必须被解析到程序中才能“活起来”。这正是XMLViewer.js所做的。
XMLViewer.js是一个相当长的脚本,因为它执行了几个不同的功能来达到其目标。然而,整体结构非常简单;各部分如下:
如果向脚本传递参数,这正是当将XML文件拖放到脚本图标上时发生的情况,这个函数将作为二进制文件读取该文件,然后将内容传递给DisplayXML。如果没有参数,则ProcessArguments使用AJAX从Nerds-Central ATOM feed获取XML数据流,并将返回的XML传递给DisplayXML。
以下是处理参数和文件读取部分的JavaScript代码示例:
for (var i = 0; i < WScript.arguments.count(); ++i) {
var chunks;
var fn = WScript.arguments.item(i);
var bf1 = new BinaryFile(fn);
var xml = bf1.ReadAll();
DisplayXML(xml);
}
以下是AJAX部分的JavaScript代码示例:
var ajax = WScript.CreateObject('Microsoft.XMLHTTP');
for (var i = 0; i < 32; ++i) {
try {
ajax.open('GET', 'http://nerds-central.blogspot.com/feeds/posts/default', false);
ajax.setRequestHeader('Connection', 'close');
ajax.send();
if (!ajax.status == 200) throw ('Got Wrong Status:' + ajax.status);
break;
} catch (e) {
for (var a in e) {
WScript.Echo(a + ' = ' + e[a]);
}
WScript.echo('Failed to get atom feed from Nerds-Central: tries left=' + (32 - i));
}
}
if (i != 32) DisplayXML(ajax.responseText);
给定一个XML字符串,这个函数使用Microsoft DOM解析器来解析它。然后它定位DOM的文档部分(DOM - 文档对象模型 - 除了文档本身还有其他部分)。
var xmlDOM = new ActiveXObject("Microsoft.XMLDOM");
xmlDOM.loadXML(xml);
var doc = xmlDOM.documentElement;
一旦它有了文档元素,它就创建一个数组,该数组将用于存储所有的输出HTML。这种方法被采用是因为新的HTML片段可以非常高效地“推送”到数组的末尾,然后整个数组在最后一刻被转换为字符串。
要理解下一个谜题的片段,必须理解DOM概念是如何模拟XML的。像大多数现代软件概念一样,它实际上比许多人希望相信的要简单得多!在处理XML时,有两个核心概念需要理解:
节点实际上只是简单的容器。它们可以包含其他节点,或者可以包含文本。所以像这样的XML片段:
<myParent><myChild>Hello World</myChild></myParent>
将在DOM中存储为3个节点。第一个节点将有nodeName为myParent。它将有一个子节点,该子节点将有nodeName为myChild。myChild节点也将有一个子节点。但这个子节点没有名称。然而,它包含文本。它有一个nodeValue为Hello World。
ProcessNode函数接受一个节点,并生成该节点及其所有后代的HTML表示。所以DisplayXML将文档元素(为什么它不被称为文档节点 - 不知道)传递给ProcessNode。
ProcessNode是一个“递归下降处理器”。听起来非常复杂,令人困惑和可怕... 但再次,它实际上相当简单。推理过程是这样的:每个节点要么有值,要么有子节点。有一个函数并处理一个父节点。一个父节点就像一个子节点一样。所以,使用同一个函数来处理父节点及其子节点。这样做的最简单的方法是让处理父节点的函数调用自身来处理每个子节点。一个调用自身的函数称为递归函数。一个使用递归来“遍历父/子关系”的函数称为递归下降。最后,它在遍历过程中处理节点,所以它是一个递归下降处理器。
function ProcessNode(node, outArr) {
if (node.nodeType < 3 || node.nodeType > 4) {
var atts = node.attributes;
if (atts.length > 0) {
for (var i = 0; i < atts.length; ++i) {
var aNode = atts.item(i);
// Attributes are nodes as well! They have a name and value name=value
}
}
if (node.hasChildNodes()) {
var newNode = node.firstChild;
while (newNode != null) {
ProcessNode(newNode, outArr);
newNode = newNode.nextSibling;
}
}
} else {
// IS A TEXT NODE
}
}
这是使用以下代码片段创建的:
var out = ConcatStringArray(outArr);
var gui = new GUIWindow();
gui.SetHTML(out);
var doc = gui.GetDocument();
var styleSheet;
if (doc.styleSheets.length == 0) {
styleSheet = doc.createStyleSheet();
} else {
styleSheet = doc.styleSheets[0];
}
styleSheet.addRule('body', 'color: black;');
styleSheet.addRule('body', 'font-family: Arial, helvetic, sans-serif;');
styleSheet.addRule('span.nodeName', 'font-weight: 700;');
styleSheet.addRule('ul.attributeList', 'color: #008;');
styleSheet.addRule('li.nodeValue', 'color: #080;');
styleSheet.addRule('*.missing', 'color: #888;');
gui.SetVisible(true);