每次完成一个任务的时候,都要看看有没有什么潜在的可以把功能是现成库的地方。这十分有利于提高自己的水平。但至于你写出来的库会不会有人用,那是另一回事情了。
这次为了完成一个多编程语言+多自然语言的文档编写工具,不得不做一个可以一次生成一大批文本文件的模板结构出来。有了模板必然有元数据,元数据必然是类似字符串的东西,所以顺手就支持了xml和json。为了应付巨大的xml和json,必然要做出xml和json的流式读写。大家应该听说过SAX吧,大概就是那样的。
有了xml和json之后,就可以在上面实现query了。把xml和json统一起来是一个比较麻烦的问题,因为本身从结构上来看他们是不相容的。而且为了便于给vlscript.dll添加compiler service的支持,势必要在dll上面做出字符串到语法树或者语法树到字符串这样子的操作。本地dll接口显然不可能做出一个顺眼的异构树结构,因此势必要把语法树表示成xml或者json。因此就有了下面的结构:
1、一颗简洁但是功能强大的字符串,在上面可以存放xml、json和强类型数据。
2、可以把xml和树相互转换。
3、可以把json和树相互转换。
4、当树存放的是强类型数据(基本类型都会用字符串存放)的时候,树可以映射到一个带有约束的xml或者json结构上去。
在这里可以展示一下什么是强类型数据。我们知道json表达的结构是弱类型的。当你拿到一个object的时候你不知道它的类型,你只知道他有多少个成员变量。强类型结构要求object有一个类型标记,数组也有一个类型标记(因为我们不能从数组的元素类型推断出数组本身的类型——因为类型可以拥有继承关系),基本类型还要可扩展(json只支持数字、字符串、布尔值和null,显然是不够的)。下面贴一个强类型数据结构的xml和json的表现形式:
2 < Languages >
3 < array:String >
4 < primitive:String value = "C++" />
5 < primitive:String value = "C#" />
6 < primitive:String value = "F#" />
7 < primitive:String value = "Haskell" />
8 </ array:String >
9 </ Languages >
10 < Name >
11 < primitive:String value = "vczh" />
12 </ Name >
13 < Project >
14 < Project >
15 < Host >
16 < primitive:String value = "Codeplex" />
17 </ Host >
18 < Language >
19 < primitive:String value = "C++" />
20 </ Language >
21 </ Project >
22 </ Project >
23 </ Developer >
2 " $object " : " Developer " ,
3 " Languages " : {
4 " $array " : " String " ,
5 " value " : [{
6 " $primitive " : " String " ,
7 " value " : " C++ "
8 }, {
9 " $primitive " : " String " ,
10 " value " : " C# "
11 }, {
12 " $primitive " : " String " ,
13 " value " : " F# "
14 }, {
15 " $primitive " : " String " ,
16 " value " : " Haskell "
17 }]
18 },
19 " Name " : {
20 " $primitive " : " String " ,
21 " value " : " vczh "
22 },
23 " Project " : {
24 " $object " : " Project " ,
25 " Host " : {
26 " $primitive " : " String " ,
27 " value " : " Codeplex "
28 },
29 " Language " : {
30 " $primitive " : " String " ,
31 " value " : " C++ "
32 }
33 }
34 }
上面的xml和json都是对一个相同的强类型数据结构的表示。我们可以看到$array、$object和$primitive是用来区分他们的实际类型的。
下面是流式xml和json的读写的接口。可以很容易的看出用这种方法来读写xml和json必须将代码做成一个超级复杂的状态机才可以。这里太长贴不下,如果大家有兴趣的话可以去 Vczh Library++3.0下载最新代码并打开
Library\Entity\TreeXml.cpp
Library\Entity\TreeJson.cpp
Library\Entity\TreeQuery.cpp
2 {
3 public :
4 enum ComponentType
5 {
6 ElementHeadOpening, // name
7 ElementHeadClosing, //
8 ElementClosing, //
9 Attribute, // name, value
10 Text, // value
11 CData, // value
12 Comment, // value
13
14 BeginOfFile,
15 EndOfFile,
16 WrongFormat,
17 };
18 public :
19 XmlReader(stream::TextReader & _reader);
20 ~ XmlReader();
21
22 ComponentType CurrentComponentType() const { return componentType; }
23 const WString & CurrentName() const { return name; }
24 const WString & CurrentValue() const { return value; }
25 bool Next();
26 bool IsAvailable() const { return componentType != EndOfFile && componentType != WrongFormat; }
27 };
28
29 class XmlWriter
30 {
31 public :
32 XmlWriter(stream::TextWriter & _writer, bool _autoNewLine = true , const WString & _space = L " " );
33 ~ XmlWriter();
34
35 bool OpenElement( const WString & name);
36 bool CloseElement();
37 bool WriteElement( const WString & name, const WString & value);
38 bool WriteAttribute( const WString & name, const WString & value);
39 bool WriteText( const WString & value);
40 bool WriteCData( const WString & value);
41 bool WriteComment( const WString & value);
42 };
43
44 class JsonReader
45 {
46 public :
47 enum ComponentType
48 {
49 ObjectOpening,
50 ObjectClosing,
51 Field,
52 ArrayOpening,
53 ArrayClosing,
54 Bool,
55 Int,
56 Double,
57 String,
58 Null,
59
60 BeginOfFile,
61 EndOfFile,
62 WrongFormat,
63 };
64 public :
65 JsonReader(stream::TextReader & _reader);
66 ~ JsonReader();
67
68 ComponentType CurrentComponentType() const { return componentType; }
69 const WString & CurrentValue() const { return value; }
70 bool Next();
71 bool IsAvailable() const { return componentType != EndOfFile && componentType != WrongFormat; }
72 };
73
74 class JsonWriter
75 {
76 public :
77 JsonWriter(stream::TextWriter & _writer, bool _autoNewLine = true , const WString & _space = L " " );
78 ~ JsonWriter();
79
80 bool OpenObject();
81 bool CloseObject();
82 bool AddField( const WString & name);
83 bool OpenArray();
84 bool CloseArray();
85 bool WriteBool( bool value);
86 bool WriteInt(vint value);
87 bool WriteDouble( double value);
88 bool WriteString( const WString & value);
89 bool WriteNull();
90 };
有xml自然要有xpath,只不过xpath用来处理json和强类型数据结构都有点力不从心,所以我打算修改xpath,做成适合查询我这种跟它们稍微有点不同的树的查询语句,然后加入到Vczh Library++3.0里面去。有了这个之后就可以做很多事情了,譬如说在模板生成器里面使用query来查询复杂的配置,譬如说在脚本语言里面支持xml和json,还有很多其他的等等。query写完之后就可以开始写一个可以一次生成一大批文本文件的模板生成器了。我会将模板生成本身写成一个可以扩展功能的库,最后再写一个DocWrite.exe利用这个库实现一个文档生成器。