挑战 XSLT 最擅长的 XML 文档转换

  XML 是描述数据的最佳语言,XML 是一个庞大的家族,因此很多人都极力的发掘 XML 的潜力,欲将其用至极致。 

  XSLT 的出现无疑大大加重了这样的倾向,因为 XSLT 中支持了众多的编程元素,所以对于 XML 文档的转换无疑采用 XSLT 是极为方便的。甚至有人采用 XSLT 作为模板来编写代码生成器。 

  本文将对这样对于 XML 的极致使用目标提出质疑,认为过分极致的使用目标就是“误用”,会使我们减少对于更适宜方案的选择。本文认为“物尽其用”不如“物用其宜”。
  

 
本文从一个简单的 XML 文档转换问题出发,给出采用传统的 XSLT 转换代码,并且针对这样的转换方案做出改进,采用一种新的编程语言 Nuva 语言(也可以采用其他编程语言)编写转换程序。两厢对比,相信读者不难看出,采用编程语言比 XSLT 这样的转换语言具有入门门槛低、程序更短、更易读以及更容易维护等众多的优点。

 

  考虑如下的源 XML 文档,该文档是 Microsoft .NET DataSet 对于 XML 的存档文件:

 

==【DotNetDataSet-Sample.xml】==

<?xml version="1.0" encoding="UTF-8"?>
<Schema1>
  <Table1>
    <Field11>A1</Field11>
    <Field12>A2</Field12>
  </Table1>
  <Table1>
    <Field11>B1</Field11>
    <Field12>B2</Field12>
  </Table1>
  <Table1>
    <Field11>C1</Field11>
    <Field12>C2</Field12>
  </Table1>
  <Table2>
    <Field21>D1</Field21>
    <Field22>D2</Field22>
    <Field23>D3</Field23>
  </Table2>
  <Table2>
    <Field21>E1</Field21>
    <Field22>E2</Field22>
    <Field23>E3</Field23>
  </Table2>
</Schema1>


 
现在欲将其转换成如下的 XML 文档,该文档是 Macrobject XObject 标准 XML 存档文件

 

==【ObjectSpace-Sample.xml】==

<?xml version="1.0" encoding="UTF-8"?>
<ObjectSpace Name="Schema1">
  <ObjectSet Name="Table1">
    <Object Field11="A1" Field12="A2" />
    <Object Field11="B1" Field12="B2" />
    <Object Field11="C1" Field12="C2" />
  </ObjectSet>
  <ObjectSet Name="Table2">
    <Object Field21="D1" Field22="D2" Field23="D3" />
    <Object Field21="E1" Field22="E2" Field23="E3" />
  </ObjectSet>
</ObjectSpace>


 
XML 文档是一种“长”结构,所有的字段都用单独的一个节点来表示,表的每一行分别表示,无论表是否相同,都相互没有关系,看起来比较费劲。另外其缺点是上下可能比较“长”。

 

  目标 XML 文档采用一种“宽”结构,字段都放在所属行(Object)的属性中,每一个表(ObjectSet)的所有行放在同一个节点下,所以很容易分表来看。缺点是左右可能比较“宽”。

 

  下面采用传统的 XSLT 来编写转换程序,如下:

 

==【DotNetDataSet2ObjectSpace.xslt】==

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/02/xpath-functions" xmlns:xdt="http://www.w3.org/2005/02/xpath-datatypes
"> <xsl:template match="/"> <xsl:apply-templates select="child::node()"/> </xsl:template> <xsl:template match="child::node()"> <xsl:element name="ObjectSpace"> <xsl:attribute name="Name"> <xsl:value-of select="fn:node-name(.)"/> </xsl:attribute> <xsl:variable name="ChildNodes" select="./*" /> <xsl:variable name="NodeNames"> <xsl:element name="Node"> <xsl:for-each select="$ChildNodes"> <xsl:attribute name="{fn:node-name(.)}"> <xsl:value-of select="'true'" /> </xsl:attribute> </xsl:for-each> </xsl:element> </xsl:variable> <xsl:variable name="DistinctNodes"> <xsl:for-each select="$NodeNames/@*"> <xsl:element name="{fn:name(.)}"> </xsl:element> </xsl:for-each> </xsl:variable> <xsl:for-each select="$DistinctNodes"> <xsl:element name="ObjectSet"> <xsl:attribute name="Name"> <xsl:value-of select="fn:node-name(.)" /> </xsl:attribute> <xsl:variable name="Name" select="fn:node-name(.)" /> <xsl:for-each select="$ChildNodes [fn:node-name(.) = $Name]"> <xsl:element name="Object"> <xsl:for-each select="./*"> <xsl:attribute name="{fn:node-name(.)}"> <xsl:value-of select="." /> </xsl:attribute> </xsl:for-each> </xsl:element> </xsl:for-each> </xsl:element> </xsl:for-each> </xsl:element> </xsl:template> </xsl:stylesheet>

  简单看一下,用 XSLT 写的转换程序比较复杂,而且也用了一些技巧,学起来比较费劲,老实说,我现在已经无法维护它了,因为时间一长,我已经不怎么看得懂了。

 

  后来,我转换了一下思维,用 Nuva 语言重写了一次,结果连我自己也吃了一惊,非常的简洁,如下:

<..========================================================
==                                                       ==
==                Macrobject Nuva Samples                ==
==                                                       ==
==      Copyright (c) 2004-2006 Macrobject Software      ==
==                                                       ==
==                  ALL RIGHTS RESERVED                  ==
==                                                       ==
==               http://www.macrobject.com               ==
==                                                       ==
========================================================..>
<.
  var sourceFile = 'DotNetDataSet-Sample.xml'

  var source = System.Xml.Document(sourceFile)
  var target = System.Xml.Document('ObjectSpace')

  target.Root.Props.Name = source.Root.Name
  foreach(sTable = source.Root.Nodes)
    var tObjSet = FindOrCreateObjectSet(target.Root, sTable.Name)
    var tObj = tObjSet.Nodes.Add('Object')
    foreach(sField = sTable.Nodes)
      tObj.Props[sField.Name] = sField.Value
    end foreach

    function FindOrCreateObjectSet(theParent, theName)
      Result = theParent.FindByProp('Name', theName)
      if (Result = nil)
        Result = theParent.Nodes.Add('ObjectSet')
        Result.Props.Name = theName
      end if
    end function
  end foreach

  ? target.ToString
.>

<..
【简介】
    本例是一个 XML 文档转换程序,本例的源 XML 文档是 Microsoft .NET 的 DataSet 存档为 XML 的文件,本例的目标 XML 文档是 Macrobject XObject 的 XML 存档文件。

    Microsoft .NET DataSet 信息请参考微软网站的相关内容:www.microsoft.com

    Macrobject XObject XML 是 Macrobject 所有 O/R Mapping 的标准存档格式,这个 XML 文件可以模拟一个简单的关系数据库,具体信息请参考 www.macrobject.com 的有关内容。

    本例演示了 Nuva 语言操纵 XML 文档的便利性,甚至超过了 XML 标准转换语言 XSLT。

【看点】
    1、本例演示了 Nuva 语言在 XML 的应用
       Nuva 语言提供了一个 XML 库,其中包括 XmlDocument、XmlNode、XmlNodeList 类:

       XmlDocument 对象通过 System.XML.Document([名称]) 创建,其中可以包含一个名称参数,如果该名称是一个文件,则从文件读取,否则返回的 XmlDocument 的 Root 节点即可以此名称来命名。

       XmlDocument 有一些属性和方法,具体可以参见<<Nuva API>>,本例用到了一下的属性和方法:
         XmlDocument.Root:返回根节点(XmlNode 对象)
         XmlDocument.ToString:返回 Xml 的字符串

       XmlNode 有以下属性和方法在本例中使用:
         XmlNode.Props:返回一个属性集,后面可以直接加上 .<PropName> 或者 [<索引器>],可以为此属性取值赋值
         XmlNode.Nodes:返回一个节点集(XmlNodeList 对象)
         XmlNode.FindByProp(theName, theValue, [inSub]):返回一个子节点,最后一个可选参数规定是否深层次递归查找所有子节点

       本例用到了 XmlNodeList 的一个方法:
         XmNodeList.Add(theName, [theValue]):返回一个新子节点,最后一个可选参数指定其 Value

    2、本例中对于 ObjectSet 节点的创建调用了一个函数 FindOrCreateObjectSet,目的是对于相同的表名必须只能创建一次,从而将相同的表(TableRow,Object)放在同一个 ObjectSet 节点下。

【扩展】
    本例可以进一步扩展以增强其实用性,比如可以修改以用于更多类型的 XML 文档转换。
..>

本例运行结果如下:

<?xml version="1.0"?>
<ObjectSpace Name="Schema1">
  <ObjectSet Name="Table1">
    <Object Field11="A1" Field12="A2"/>
    <Object Field11="B1" Field12="B2"/>
    <Object Field11="C1" Field12="C2"/>
  </ObjectSet>
  <ObjectSet Name="Table2">
    <Object Field21="D1" Field22="D2" Field23="D3"/>
    <Object Field21="E1" Field22="E2" Field23="E3"/>
  </ObjectSet>
</ObjectSpace>

posted on 2006-09-08 09:22  Wisdom-zh  阅读(2625)  评论(19编辑  收藏  举报

导航