65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 HTA/Scripting Runtime 浏览 XML/XSL/XSD

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.61/5 (14投票s)

2005 年 11 月 14 日

12分钟阅读

viewsIcon

111189

downloadIcon

1390

本文介绍了一个 XML/XSL/XSD 浏览和验证工具,它生动地展示了如何将 Shell 脚本、Scripting Runtime 或 HTA 等各种技术结合起来,帮助程序员快速开发强大的脚本应用程序。

内容概要

本文的初衷是给您提供一个使用 Scripting Runtime 库的简单示例,“点击此处和此处,等等,谢谢”。我开始写作的原因是需要让我的同事和我自己的脚本能够感知文件系统。事实证明,这种能力对于软件原型设计或构建一些小型实用工具来说非常有用;当然,由于我们稍后将讨论的安全/隐私原因,它不应该用于 Web。

回到工具本身。在编写此工具之前,我花了几个月时间深入研究 XML,因此您在此看到的是一个 XML/XSLT 查看器/浏览器,它以HTML 应用程序的形式呈现。这在我学习 XML/XSL 时给了我很大帮助,现在它帮助<其他人>快速检查和跟踪大量的 XSL 模板;希望它也能帮助您。

当然,这个小浏览器(我以后称之为“Xbrowser”)绝不是任何企业级开发工具的替代品。它只是

  • 一个交互式学习工具,为 XML 开发的初学者展示了使用 JScript/MSXML 处理 XML 的基础知识;也许还能为
  • Office 工具和解决方案的开发者提供一个使用 Microsoft Scripting Runtime Object Library 的示例;当然,它也是
  • 一个用于验证 XML 文档(是否格式良好/是否符合模式)和查看 XSLT 输出的简单实用工具。

本文的最后几项添加内容是XML/XSL 转换XML/XSD 验证工具,它们使用了(本文描述的)大部分技术。

要求

为了使这段代码正常工作,您需要以下代码包

  • 通用对话框 ActiveX 控件 - 提供了一套标准的对话框,用于打开和保存文件、设置打印选项以及选择颜色和字体。它随 MS Visual Basic 和 MS Office 2000/XP 产品一起提供,或者可以从 Microsoft 网站下载
  • Scripting Runtime 对象库 是 Microsoft 的文件系统管理解决方案,设计用于脚本语言;它是 Microsoft Office 2000/XP 的组成部分。此库也可在Microsoft 网站上下载
  • Microsoft XML Core Services 和/或 SDK(3.0 版或最好是 4.0 版)。可以从 Microsoft 网站下载
  • 安装上述部分程序包可能需要一个CAB 提取实用程序。您可以从 Microsoft 网站下载

工作原理

如果您查看附带的存档,您会发现“Xbrowser”不过是一个 HTML 表单。让我们一步一步地看看如何使用它,以及后台代码是如何工作的。

  • 文件夹浏览

    第一步:选择 XML 文件所在的文件夹。

    这部分使用了 Shell 对象,特别是它的 BrowseForFolder 方法。

    function BrowseFolder()
    {
     // Accessing the Shell:
     var objShell = new ActiveXObject("Shell.Application");
    
     // Calling the browser dialog:
     var objFolder = objShell.BrowseForFolder(0, "Select a folder:", 0);
    
     if (objFolder == null) return "";
    
     // Accessing the folder through FolderItem object:
     var objFolderItem = objFolder.Items().Item();
     var objPath = objFolderItem.Path;
    
     var foldername = objPath;
     if (foldername.substr(foldername.length-1, 1) != "\\")
         foldername = foldername + "\\";
    
     // foldername is the actual folder name.
    
     ...
    }
  • 文件浏览和枚举。

    第二步:选择一个文件。

    这里有两个有趣的地方

    • Scripting.FileSystemObject 是访问文件系统的主要入口。简而言之,
      FileSystemObject 包含
      • Drives 集合
      • Folders 集合
      • Files 集合

      • GetDrive 方法(访问特定驱动器)。
      • GetFolder 方法(访问特定文件夹)。
      • GetFile 方法(访问特定文件)。
      Drives 集合包含
      • Item 属性(用于访问驱动器)。
      • Count 属性(系统中驱动器的数量)。
      Folders 集合包含
      • Item 属性(用于访问文件夹)。
      • Count 属性(集合中文件夹的数量)。

      • Add 方法(创建新文件夹)。
      Folder 对象包含
      • SubFolders 集合(文件夹的子文件夹,包括那些设置了隐藏和系统文件属性的)。
      • Files 集合(访问文件夹中的所有文件)。
      Files 集合包含
      • Item 属性(用于访问文件)。
      • Count 属性(文件夹中文件的数量)。
      File 对象包含
      • Name 属性(文件名)。
      • Size 属性(文件大小)。
      • DateCreated 属性(文件创建日期和时间)。

      FSO 拥有大量的集合、方法和属性;我只列出了最常用的。

    • Enumerator 对象是一个简单的迭代器,用于遍历对象集合
      Enumerator 对象包含
      • item 方法(返回集合中当前对象的引用)。
      • atEnd 方法(当迭代器到达集合末尾时返回 true)。
      • moveFirst 方法(迭代到集合中的第一个对象)。
      • moveNext 方法(迭代到集合中的下一个对象)。
      var fc = new Enumerator(colFiles);
      for (; !fc.atEnd(); fc.moveNext())
      {
          var objFile = fc.item();
          ...
      }

    实际代码

    // Here goes the Scripting Runtime, FileSystemObject object:
    var objFSO = new ActiveXObject("Scripting.FileSystemObject");
    // Accessing the folder:
    var objFolder = objFSO.GetFolder(curXMLfolder);
    // Accessing the files:
    var colFiles = objFolder.Files;
    
    var xmlcount = 0, xslcount = 0;
    
    // Cycling through the files, one by one:
    var fc = new Enumerator(colFiles);
    if (fc.atEnd() != true)
    // If collection of files is not empty...
    {
     for (; !fc.atEnd(); fc.moveNext())
     // Iterating through the files
     {
       var objFile = fc.item();
       var ftext = objFile.Name.toLowerCase();
    
       // Checking the extension:
       if ((ftext.substr(ftext.length-3, 3)=="xml") || 
           (ftext.substr(ftext.length-3, 3)=="rdf"))
       {
         xmlcount = xmlcount + 1;
         // Opening the <SELECT> tag is any XML files exist:
         if (xmlcount == 1) 
           xmlsel="<SELECT id='xmlselection' onchange='refresh()'>";
    
         // Adding an option:
         xmlsel=xmlsel+"<OPTION value="+ftext+">"+
                ftext+"</OPTION>";
    
         // Closing the tag:
         if (fc.atEnd()) xmlsel=xmlsel+"</SELECT>";
       }
     }
    }
  • 从文件加载 XML。

    这是 MSXML 的部分

    // Creating the new empty DOM tree:
    var xml = new ActiveXObject("MSXML2.DOMDOCUMENT");
    // No asynchronous load:
    xml.async = false;
    // Loading the file from disk:
    xml.load(curXMLfolder + xmlselection.value);

    对样式表执行相同操作。

    // Creating the new empty DOM tree:
    var xsl = new ActiveXObject("MSXML2.DOMDOCUMENT");
    // No asynchronous load:
    xsl.async = false;
    // Loading the file from disk:
    xsl.load(curXSLfolder + xslselection.value);
  • 从字符串加载 XML。

    从字符串加载 XML 数据与从文件加载略有不同。没有文件,没有选项;您需要做的就是编写一个包含 XML 代码的字符串。然后,您只需一次调用 LoadXML 方法即可解析该字符串。

    // Defining a string - default stylesheet:
    var defsheet="<?xml version=\"1.0\"?>";
    ...
    defsheet += "</xsl:stylesheet>";
    
    // String -> DOM:
    if(!defSheetCache)
    {
     defSheetCache = new ActiveXObject("MSXML2.DOMDocument");
     defSheetCache.async = false;
     defSheetCache.resolveExternals = false;
     defSheetCache.loadXML(defsheet);
    }

    此处,LoadXML 用于加载默认样式表(硬编码在字符串中),当在相应文件夹中找不到 XSL 文件时使用。

  • 文档验证。

    第三步:查看验证结果。

    实际验证在 XML 文档加载完成后立即进行。

       ...
       xml.load(curXMLfolder + xmlselection.value);
       // Document is already validated;
       // 'xml.parseError.errorCode' contains error code, if any.
       ...

    所以,您只需要检查

       if (xml.parseError.errorCode != 0)
       {
          // Handle error
       }
       else
       {
          // Proceed - XML is ok.
       }
  • 使用任意 XSD 模式验证 XML 文档。

    要使用模式验证 XML 文档,脚本会执行以下操作:

    ...
    
    if(xslFile.substr(xslFile.length - 3, 3) == "xsd")
    {
     // 1. Loading XSD schema into the DOMDocument:
     var schemaSource = new ActiveXObject("MSXML2.DOMDocument.4.0");
     if(!schemaSource.load(curXSLfolder + xslFile))
     {
       xslErrorCache = schemaSource.parseError.errorCode + 
                       ": " + schemaSource.parseError.reason;
       passedXSL.innerHTML = "... Schema is corrupt ...";
       result.innerHTML = "";
       return;
     }
    
     // 2. Extracting the targetNamespace
     // attribute from the schema:
     schemaSource.setProperty("SelectionLanguage", "XPath");
     schemaSource.setProperty("SelectionNamespaces", 
       "xmlns:xs='http://www.w3.org/2001/XMLSchema'");
    
     var tnsattr = schemaSource.selectSingleNode("/*[local-name()" + 
                   "='schema']/@targetNamespace");
     var nsuri = tnsattr ? tnsattr.nodeValue : "";
    
     // 3. Creating/purifying the schema cache:
     if(!schemaCache)
       schemaCache = new ActiveXObject("Msxml2.XMLSchemaCache.4.0");
     else
     {
       for(var i = 0; i < schemaCache.length; i++)
       {
         schemaCache.remove(schemaCache.namespaceURI(i));
       }
     }
    
     // 4. Adding schema to the schema cache:
     schemaCache.add(nsuri, schemaSource);
    
     ...
    
     // 5. Binding schema cache to an empty DOMDocument:
     var xmlSource = new ActiveXObject("MSXML2.DOMDocument.4.0");
     xmlSource.schemas = schemaCache;
     xmlSource.async = false;
    
     // 6. Loading the document:
     if(!xmlSource.load(curXMLfolder + xmlFile))
     {
       xslErrorCache = xmlSource.parseError.reason;
       passedXSL.innerHTML = 
         "... XML document doesn't conform to schema ...";
       result.innerHTML = "";
       return;
     }
     else
     {
       result.innerHTML = xml.transformNode(xsl.documentElement);
       passedXSL.innerHTML = 
         "... XML document conforms to schema ...";
     }
    }

    请注意:此验证过程要求您安装 MSXML 4.0。

  • 使用 XSL 样式表转换 XML。

    一旦 XML 和 XSL 文件被加载到 DOM 树中,使用样式表转换 XML 数据就非常简单了。

       resultCache = xml.transformNode(xsl.documentElement);
  • 重新连接 CSS。

    resultCache 会出现一个问题:如果输入的 XSLT 文档生成了嵌入的样式表(<STYLE>)块,这些块在我们通过 result.innerHTML 显示它们时会被剥离。通过将样式定义从结果中提取并合并到浏览器的文档中,可以解决此问题。

       var elem = document.createStyleSheet();
    
       elem.cssText = 
         trim(resultCache.substring(resultCache.indexOf("<style>") + 7, 
              resultCache.indexOf("</style>")));
       elem.title = "user_styles";

    为避免样式冲突,我们必须在每次转换之前立即“垃圾回收”。

       // ...somewhere at the beginning of 'refresh()':
    
       // Clear styles generated by previous XSLT stylesheet, if any:
       var stls = document.getElementsByTagName("style");
       for(var k = 0; k < stls.length; k++)
       {
         var stl = document.styleSheets[k];
         if (stl.title == "user_styles")
         {
           var r = stl.rules.length;
           for(var j = 0; j < r; j++)
             stl.removeRule[0];
    
           break;
         }
       }
  • 将转换结果保存到文件。

    第四步:将 XSLT 输出保存到文件。

    这里有两个值得关注的点:“保存”对话框和文件创建过程本身。为了使“保存”对话框正常工作,您必须注册并获取以下 ActiveX 组件的设计时许可证

       <object id="cmdlg"
               classid="clsid:F9043C85-F6F2-101A-A3C9-08002B2F49FB"
               codebase="http://activex.microsoft.com/controls/vb6/comdlg32.cab">
       </object>

    然后,您就可以使用它了。

       function fileSave()
       {
         cmdlg.CancelError = false;
         cmdlg.FilterIndex = 1;
         cmdlg.DialogTitle = "Save file as";
         cmdlg.Filter = "HTML file (*.html)|*.html|XML file (*.xml)|*.xml";
    
         // Calling the dialog:
         cmdlg.ShowSave();
    
         return cmdlg.FileName;
       }

    如果希望保存 XSLT 输出,我们只需获取 resultCache 并将其流式传输到文件。这里无需检查任何错误,因为如果两个文档(XML 或 XSL)中的任何一个未通过验证,我们就不会显示“保存...”按钮。

       function Save()
       {
         // Asking for file name:
         var filename = fileSave();
    
         if (filename != "")
         {
           // Creating the file:
           var objFSO = new ActiveXObject("Scripting.FileSystemObject");
           var objFile = objFSO.CreateTextFile(filename);
    
           // Writing the XSLT output:
           objFile.Write(resultCache);
           objFile.Close();
         }
       }

全部包含

作为对所介绍工具的测试平台,随附的 Zip 文件包含 Sun Microsystems Inc. 的NASDAQ 历史股价数据,以及我编写的三种 XSLT 样式表。

  • 纯文本。这个文件显示原始 XML 数据,并带有 IE 的配色方案。

  • 表格。这是一个简单的 XSL 转换示例。绿色/红色行显示股票价格的上涨/下跌,是<xsl:choose> 规则的说明。

  • 条形图。一个更复杂的样式表。这个文件包含:“for-next”风格的循环(通过命名模板的递归调用实现);搜索一行值中的最大值(使用<xsl:sort> 规则);当然,还有一个用于构建时尚条形图的算法。

快速完成

最后包含的一些小东西是:

  • transfrm 脚本 - 一个简单的 WSH 工具,用于将 XSLT 样式表应用于 XML 文档。该脚本有两种模式。
    • 批量模式非常适合一次性执行大量 XSL 转换。该脚本将单个文件名作为参数。
      transfrm.js batch.list

      作为参数传递的文件可以包含任意数量的行(每行一个转换),格式如下:

      <xml_file_name>,<xsl_file_name>,<result_file_name>

      示例批处理列表

      stock.xml,plain.xsl,result1.html
      stock.xml,table.xsl,result2.html
      stock.xml,bargraph.xsl,result3.html

      除了结果文件外,该脚本还会创建或追加“transfrm.log”文件,其中包含转换日志。

    • 单次转换模式下,该脚本接受三个参数。
      transfrm.js input.xml input.xsl output_file.html

      transfrm.log”文件将填充有关最后一次转换的信息。

  • validate.js 脚本 - 一个 WSH 工具,用于将单个 XML 文档针对任意 XSD 模式进行验证。
    • 单次验证模式
      validate.js input.xml input.xsd

      结果是,您会看到一个消息框,说明 input.xml 是否符合 input.xsd

    • 批量模式
      validate.js batch.file

      批处理文件包含输入 XML 文件和 XSD 模式的列表。

      <xml_file_1_name>,<xsd_file_1_name>
      <xml_file_2_name>,<xsd_file_2_name>

      该脚本会创建或追加“validate.log”文件,其中包含最后一次验证的详细信息。

安全

从前面的部分可以看出:一个可以将任意数据写入任意文件的脚本可能会带来很多麻烦和安全问题。此外,HTML 应用程序不受 IE 的安全限制(请参阅介绍),因此使用 FileSystemObject 的第三方(或有错误)脚本可能构成重大的安全威胁。

这决定了 Scripting Runtime 的两个主要用途:“本地”(非 Web)实用程序和服务器端脚本。正如MSDN 所说,“由于在客户端使用 FSO 会引发关于提供对客户端本地文件系统的潜在不受欢迎访问的严重安全问题,因此本文档假定使用 FSO 对象模型来创建由服务器端的 Internet Web 页面执行的脚本。由于使用的是服务器端,因此 Internet Explorer 的默认安全设置不允许在客户端使用 FileSystemObject 对象。覆盖这些默认设置可能会使本地计算机面临对文件系统的不受欢迎访问,从而导致文件系统的完整性被完全破坏,造成数据丢失,甚至更糟。”

有时,您唯一能考虑的选项是将独立的 HTA 转换为企业网页(只需将 .hta 文件重命名为 .html 并删除 HTA:APPLICATION 标签),从而在客户端使用 FSO。这会带来组件许可和执行权限的问题;此外,您必须确保您的内网是极其安全的。在这种情况下(即,如果您返回“普通 Web”),为了保护自己免受任何意外行为的影响,同时利用高级脚本对象(如 Scripting.FileSystemObjectMSXML.DOMDocument)的强大功能,请考虑以下事项:

  • 切勿允许未经您明确批准的非安全和未签名 ActiveX 组件运行。在 IE 的安全选项卡中,将“初始化和脚本化未标记为安全的 ActiveX 控件”选项设置为“提示”
  • 切勿允许未经您明确批准下载和运行 Java 组件。在 IE 的安全选项卡中,将 Java 虚拟机安全级别设置为“中”“高”
  • 通常,从家庭或企业内网下载的脚本是可信的;因此,您可能希望将“本地 Intranet”的安全级别设置为“中”甚至“低”,同时将“Internet”安全级别设置为“高”
  • 将您的脚本所在的服务器添加到“受信任的站点”区域。

请注意,这些只是基本规则;您可能需要咨询 IT 专业人员,以将您的内网安全措施构建到适当的级别。

替代方案

“Xbrowser”的其他缺点是:它严格绑定于 IE,并且过于依赖外部代码库。这是 IE 引擎提供的强大功能的另一面;然而,可以通过多种方式减少依赖性。

  • XML/XSL/XPath 处理

    XML for <SCRIPT> - 跨平台符合标准的 JavaScript XML 解析器。优点:包含 W3C DOM(级别 2)/SAX 解析器以及 XPath 处理器。缺点:如果您需要支持模式/DTD 的解析器,这不是您的选择。(几乎)非常适合跨浏览器工作。

    Sarissa - 不是解析器,而是原生 XML API 的 JavaScript 包装器。可以执行 DOM 操作、XSL 转换和 XPath 查询;支持所有流行浏览器。这(也)可以帮助您构建跨浏览器的 XML 解决方案。

  • I/O

    不幸(或幸运?)的是,对于文件 I/O,没有 Scripting Runtime Object Library 的标准替代方案。其他浏览器(Mozilla FirefoxOpera 等)不容忍任何偏离 ECMAScript 标准(Microsoft 的 JScript 是其实现)的代码,因此您可以确信没有任何代码能够摧毁您的文件系统。

链接

通用对话框控件

脚本

脚本安全

XML

工具

为了开发一些严肃的 XML 应用程序,您需要比记事本更强大的工具。

官方声明

正式的技术规范,一如既往地采用 W3C 的晦涩语言编写。

  • XML - XML 和基于 XML 的技术的首页。
  • XML Schema - 关于 XML 模式的一般信息;指向 XSD/DTD 辅助工具的链接。
  • XSL - 可扩展样式表语言的首页。规范、软件新闻和链接、教程。

文章与代码示例

网上关于 XML/XSLT 的文章比任何普通人一生都能读完的还要多。这里只是一些链接。

历史

  • 2005 年 11 月 10 日 - 初始发布。
  • 2006 年 3 月 15 日 - 修复了严重的 CSS问题,添加了关于通用控件许可的说明。
  • 2006 年 4 月 25 日 - 现在可以根据 XSD 模式验证 XML 文档;进行了小的改进和 bug 修复。
  • 2006 年 5 月 15 日 - 进行了一些小的优化和 bug 修复。
  • 2006 年 5 月 25 日 - 重写了转换验证工具。
© . All rights reserved.