首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

用面向对象编程创建有效的 XML

用面向对象编程创建有效的 XML

XML 具有某种双重标识。一方面,在其 SGML 根源方面,XML 是一种表达规范和严格的结构化数据的方法;DTD 和 XML Schema 描述这些结构。另一方面,就象人们在一些流行的 API(DOM、SAX 和 XSLT,以及通过其它 XML 库)中看到的那样,XML 只是一种表示一般分层的数据的方法。遗憾的是,这两方面交流得很不好。                创建有效文档超出了这些 API 的范畴(有效性通常移交给解析阶段)。然而,仅仅通过使用几个特殊的策略,您就至少可以使 OOP 对象看起来十分类似于有效的 XML。            
什么构成有效性?可以用 DTD 表达的有效性约束种类与可以用 W3C XML 模式表达的有效性约束种类有些区别。在本技巧文章中,我将集中讨论不同有效性约束规范风格所共有的问题。
XML 有效性的基本思想是指定在元素内部可以出现                什么,它每隔                多久可以出现一次以及可以出现的内容有哪些                替代选择。另外,当在一个元素中可以出现多个事物时,还可以指定它们出现的顺序(或按照需要不指定)。下面我们看一看一个高度简化的假想的 dissertation.dtd:            
具备所有基本约束的“dissertation”DTD
1
2
3
4
5
6
7
8
<!ELEMENT dissertation (dedication?, chapter+, appendix*)>
<!ELEMENT dedication (#PCDATA)>
<!ELEMENT chapter (title, paragraph+)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT paragraph (#PCDATA | figure | table)>
<!ELEMENT figure EMPTY>
<!ELEMENT table EMPTY>
<!ELEMENT appendix (#PCDATA)>




换句话说,dissertation                可以包含一个 dedication,                必须包含一个或多个 chapter 并且                可以包含零或多个 appendix。不同的子元素以列出的顺序出现(如果有的话)。一些元素仅包含字符数据。就                 <paragraph> 标记而言,它                可以包含字符数据,                可以包含一个                 <figure> 子元素                或一个<table> 子元素。结构可以嵌套,但是这一示例包含了每一个基本有效性概念。            
使对象看起来象文档尽管 DOM 不这样做,但 OOP 语言具有表示有效性的所有基本数据结构(一些语言比其它语言更完整)。基本说来,这里的窍门在于创建属性受约束的对象,以便仅允许某些事物进入它们。这一工作是在编译时完成还是在运行时完成取决于所使用的语言,但是这两种方式都可以引入必要的约束。在任何情况下,都必须对每种文档类型 ― 而不是笼统地对所有 XML ― 创建正确种类的对象。
XML 文档是分层的,类似的,可以包含其它对象作为属性的对象也是分层的。对于不同的属性,我们仅需选择几种对象类型。
在文档根处 ― 以及在其之下的其它级别上 ― 我们要指定可以出现的子元素及其出现的顺序。我想起了两种方法。一种方法是创建可以在该元素中出现的那些事物的不同种类的序列。Haskell 和 Python 元组具有保持不变的良好品质(例如,象 Fortran 记录那样),但是常规列表或数组(例如 void 指针)也可以这样做。例如,在 Ruby 中,我们可能会“声明”:
用成员“part”初始化 Ruby“Root”类
1
2
3
4
5
dissertation = Root.new [ Maybe_dedication.new, \
                          Some_chapter.new, \
                          Any_appendix.new ]
# Example of accessing part of root element sequence
dissertation.part[1].addchapter(some, arguments, here)




该示例完全没有说明类                 Maybe_dedication 、                 Some_chapter 和                 Any_appendix 做什么,但是包含序列思想。                 dissertation 是类                 Root 的实例,其工作是保存一个序列(可能还有其它几个方法,如写 XML 或验证当前实例)。            
第二种方法在很多语言中要更简单一些,它使事物直接以根元素属性出现,但是保留一个指示序列的特殊属性。例如,Java 可能做类似于下面的事:
将 dissertation 部分作为成员保留的 Java 类
1
2
3
4
5
6
7
8
public class dissertation {
    Maybe dedication = new Maybe(dedication_type);
    Some chapters = new Some(chapter_type);
    Any appendices = new Any(appendix_type);
    Seq _seq = new Seq[] {dedication, chapters, appendices};
}
/* Access is somewhat more natural now */
dissertation.chapter.add(some, arguments, here);




表达量化我们已经看到可以允许元素的各个“部分”出现的次数不同:一次或零次;一次或多次;零次或多次。我们需要对象来表示这些数量。                可能性情况(零次或多次)很简单,可以由标准(同类)序列表示:列表、数组、向量或编程语言所称呼和提供的任何东西。为了提供其它行为,将一个基本序列封装在一个定制类中可能是值得的。            
限制情况(一次或零次,也就是说不多于一次)的结构不太明显。最可能的情况是,应该在基本序列对象的基础之上构建                 Maybe 类(以及象                 Maybe_dedication 这样的子类),并且在提供的方法中有防止添加一个以上事物的保护手段。这类似于我们                存在情况(一次或多次)的示例。那么,让我们看一看可能(不完整)的 Python 实现:            
拥有“一次或多次”事物的 Python 类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class
                 
         ExistentialList(UserList.UserList):
     
         
        def
                 
         __init__(self, tag=None, initlist=[]):
         
         
        if
                 
         tag
         
        is
                 
         None:
            
         
        raise
                 
         ValueError,
         
        "Must specify tag name"
                 
         
        self.tag = tag
        UserList.UserList.__init__(self, initlist)
     
         
        def
                 
         __delitem__(self, i):
         
         
        del
                 
         self.data
        self.validate()
     
         
        def
                 
         __repr__(self):
        self.validate()
         
         
        return'\n'
                 
        .join([
         
        '<%s>%s</%s>'
                 
         % (self.tag, item, self.tag)
                           
         
        for
                 
         item
         
        in
                 
         self.data])
     
         
        def
                 
         validate(self):
         
         
        ifnot
                 
         len(initlist):
            
         
        raise"ExistentialError"
                 
        ,
         
        "List must have length >= 1"




更健壮的示例可能会在                 .append() 、                 .extend() 、                 .__setitem__() 和其它可以添加到列表的方法中添加一些特性,例如检查项目类型是否正确。具有静态类型定义的语言可能仅仅会在列表中声明事物类型,但可能使用类似的大小验证。在 C++ 中,您可能使用模板和通用编程方法来实现                 ExistentialList 的一般特性,但是要特别指明子元素类型。            
每一个表示子元素的对象的重要特性是将其本身序列化成 XML 的能力。Python 保留一个名为                 __repr__() 的“神奇”方法;其它语言可能使用一个名为                 .write() 或                 .toXML() 的方法。序列化还需要向下递归遍历子元素,这由 Python 使用神奇方法自动处理。在其它情况下,需要对属性对象进行一些人工调用。            
表达分离出现模式之间的交替在大多数 OOP 语言中更加难以表达。有几种语言(例如 Pascal、Fortran 和最棒的 Haskell)允许枚举类型(有区别的联合),但是这些是例外,而不是规则。在大多数语言中,您必须添加定制检查以确保可允许项之一由一个实例约束。
您可能通过为构造器提供多个类型说明来编写多态构造器函数。交替模式中的每一个允许的事物都将有一个构造器(可能包括在与 DTD 中的分离一起使用的量词处的序列类型)。
然而,更灵活的方法是使用一个通用框架,该通用框架来通过初始化变量或通过                 Or 超类的继承来接收其交替列表。这样的子类看起来可能类似于:            
Or 抽象类的 Python 专门化
1
2
class paragraph(Or):
    disjoins = (PCDATA, figure, table)




假设抽象类                 Or 的基本功能知道查找                 disjoins 类成员,那么这可能是足够的专门化。当然,在父类中需要定义受保护的写方法和读方法(在 Python 案例中,可能用神奇方法来实现)。            
结束语通过在数据类所包含的值上使用一些约束来使用数据类允许我们反映出 XML 文档的有效性要求。可以象构造子元素容器那样构造类容器。有了一个优秀的基类框架,基本上,您就可以将 DTD 直接复制到一组类定义或初始化中了。实例成为其它实例的成员,最终您得到一个根对象实例,该实例与其属性一起,具有以不能破坏有效性要求的方式操纵数据的多种方法(与之相反,使用象字符串和数组那样的基本类型很容易破坏有效性)。在操纵结束时,根对象应该具有某种方法写出 XML ― 向下递归遍历其属性实例 ― 这将产生一个完全成熟而有效的 XML 文档。例如,一个                 ExistentialList 可以包含其它                 ExistentialList 对象 ― 嵌套的序列化(                 print 语句)与给定的样本合作得非常好
返回列表