将Xml文件从文本格式转换为二进制格式可以划分为六个步骤

Step 1. 收集有资源ID的属性的名称字符串
  这一步除了收集那些具有资源ID的Xml元素属性的名称字符串之外,还会将对应的资源ID收集起来放在一个数组中。这里收集到的属性名称字符串保存在一个字符串资源池中,它们与收集到的资源ID数组是一一对应的。
  对于main.xml文件来说,具有资源ID的Xml元素属性的名称字符串有“orientation”、“layout_width”、“layout_height”、“gravity”、“id”和“text”,假设它们对应的资源ID分别为0x010100c4、0x010100f4、0x010100f5、0x010100af、0x010100d0和0x0101014f,那么最终得到的字符串资源池的前6个位置和资源ID数组的对应关系如图11所示:
  

  图11 属性名称字符串与属性资源ID的对应关系
  Step 2. 收集其它字符串
  这一步收集的是Xml文件中的其它所有字符串。由于在前面的Step 1中,那些具有资源ID的Xml元素属性的名称字符串已经被收集过了,因此,它们在一步中不会被重复收集。对于main.xml文件来说,这一步收集到的字符串如图12所示:
  

  图12 其它字符串
  其中,“android”是android命名空间前缀,“http://schemas.android.com/apk/res/android”是android命名空间uri,“LinearLayout”是LinearLayout元素的标签,“Button”是Button元素的标签。
  Step 3. 写入Xml文件头
  最终编译出来的Xml二进制文件是一系列的chunk组成的,每一个chunk都有一个头部,用来描述chunk的元信息。同时,整个Xml二进制文件又可以看成一块总的chunk,它有一个类型为ResXMLTree_header的头部。
  ResXMLTree_header定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
  1.   [cpp] view plaincopy/**
  2.   * Header that appears at the front of every data chunk in a resource.
  3.   */
  4.   struct ResChunk_header
  5.   {
  6.   // Type identifier for this chunk. The meaning of this value depends
  7.   // on the containing chunk.
  8.   uint16_t type;
  9.   // Size of the chunk header (in bytes). Adding this value to
  10.   // the address of the chunk allows you to find its associated data
  11.   // (if any).
  12.   uint16_t headerSize;
  13.   // Total size of this chunk (in bytes). This is the chunkSize plus
  14.   // the size of any data associated with the chunk. Adding this value
  15.   // to the chunk allows you to completely skip its contents (including
  16.   // any child chunks). If this value is the same as chunkSize, there is
  17.   // no data associated with the chunk.
  18.   uint32_t size;
  19.   };
  20.   /**
  21.   * XML tree header. This appears at the front of an XML tree,
  22.   * describing its content. It is followed by a flat array of
  23.   * ResXMLTree_node structures; the hierarchy of the XML document
  24.   * is described by the occurrance of RES_XML_START_ELEMENT_TYPE
  25.   * and corresponding RES_XML_END_ELEMENT_TYPE nodes in the array.
  26.   */
  27.   struct ResXMLTree_header
  28.   {
  29.   struct ResChunk_header header;
  30.   };
复制代码

  ResXMLTree_header内嵌有一个类型为ResChunk_header的头部。事实上,每一种头部类型都会内嵌有一个类型为ResChunk_header的基础头部,并且这个ResChunk_header都是作为第一个成员变量出现的。这样在解析二进制Xml文件的时候,只需要读出前面大小为sizeof(ResChunk_header)的数据块,并且通过识别其中的type值,就可以知道实际正在处理的chunk的具体类型。
  对于ResXMLTree_header头部来说,内嵌在它里面的ResChunk_header的成员变量的值如下所示:
  --type:等于RES_XML_TYPE,描述这是一个Xml文件头部。
  --headerSize:等于sizeof(ResXMLTree_header),表示头部的大小。
  --size:等于整个二进制Xml文件的大小,包括头部headerSize的大小。
  Step 4. 写入字符串资源池
  原来定义在Xml文件中的字符串已经在Step 1和Step 2中收集完毕,因此,这里就可以将它们写入到最终收集到二进制格式的Xml文件中去。注意,写入的字符串是严格按照它们在字符串资源池中的顺序写入的。例如,对于main.xml来说,依次写入的字符串为“orientation”、“layout_width”、“layout_height”、“gravity”、“id”、"text"、"android"、“http://schemas.android.com/apk/res/android”、“LinearLayout”和“Button”。之所以要严格按照这个顺序来写入,是因为接下来要将前面Step 1收集到的资源ID数组也写入到二进制格式的Xml文件中去,并且要保持这个资源ID数组与字符串资源池前六个字符串的对应关系。
  写入的字符串池chunk同样也是具有一个头部的,这个头部的类型为ResStringPool_header,它定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
  1.   [cpp] view plaincopy/**
  2.   * Definition for a pool of strings. The data of this chunk is an
  3.   * array of uint32_t providing indices into the pool, relative to
  4.   * stringsStart. At stringsStart are all of the UTF-16 strings
  5.   * concatenated together; each starts with a uint16_t of the string's
  6.   * length and each ends with a 0x0000 terminator. If a string is >
  7.   * 32767 characters, the high bit of the length is set meaning to take
  8.   * those 15 bits as a high word and it will be followed by another
  9.   * uint16_t containing the low word.
  10.   *
  11.   * If styleCount is not zero, then immediately following the array of
  12.   * uint32_t indices into the string table is another array of indices
  13.   * into a style table starting at stylesStart. Each entry in the
  14.   * style table is an array of ResStringPool_span structures.
  15.   */
  16.   struct ResStringPool_header
  17.   {
  18.   struct ResChunk_header header;
  19.   // Number of strings in this pool (number of uint32_t indices that 
  20. follow
  21.   // in the data).
  22.   uint32_t stringCount;
  23.   // Number of style span arrays in the pool (number of uint32_t indices
  24.   // follow the string indices).
  25.   uint32_t styleCount;
  26.   // Flags.
  27.   enum {
  28.   // If set, the string index is sorted by the string values (based
  29.   // on strcmp16()).
  30.   SORTED_FLAG = 1<<0,
  31.   // String pool is encoded in UTF-8
  32.   UTF8_FLAG = 1<<8
  33.   };
  34.   uint32_t flags;
  35.   // Index from header of the string data.
  36.   uint32_t stringsStart;
  37.   // Index from header of the style data.
  38.   uint32_t stylesStart;
  39.   };
复制代码

  内嵌在ResStringPool_header里面的ResChunk_header的成员变量的值如下所示:
  --type:等于RES_STRING_POOL_TYPE,描述这是一个字符串资源池。
  --headerSize:等于sizeof(ResStringPool_header),表示头部的大小。
  --size:整个字符串chunk的大小,包括头部headerSize的大小。
  ResStringPool_header的其余成员变量的值如下所示:
  --stringCount:等于字符串的数量。
  --styleCount:等于字符串的样式的数量。
  --flags:等于0、SORTED_FLAG、UTF8_FLAG或者它们的组合值,用来描述字符串资源串的属性,例如,SORTED_FLAG位等于1表示字符串是经过排序的,而UTF8_FLAG位等于1表示字符串是使用UTF8编码的,否则就是UTF16编码的。
  --stringsStart:等于字符串内容块相对于其头部的距离。
  --stylesStart:等于字符串样式块相对于其头部的距离。
  无论是UTF8,还是UTF16的字符串编码,每一个字符串的前面都有2个字节表示其长度,而且后面以一个NULL字符结束。对于UTF8编码的字符串来说,NULL字符使用一个字节的0x00来表示,而对于UTF16编码的字符串来说,NULL字符使用两个字节的0x0000来表示。
  如果一个字符串的长度超过32767,那么就会使用更多的字节来表示。假设字符串的长度超过32767,那么前两个字节的最高位就会等于0,表示接下来的两个字节仍然是用来表示字符串长度的,并且前两个字表示高16位,而后两个字节表示低16位。
  除了ResStringPool_header头部、字符串内容块和字符串样式内容块之外,还有两个偏移数组,分别是字符串偏移数组和字符串样式偏移数组,这两个偏移数组的大小就分别等于字符串的数量stringCount和styleCount的值,而每一个元素都是一个无符号整数。整个字符中资源池的组成就如图13所示:
  

  图13 字符串资源池结构
  注意,字符串偏移数组和字符串样式偏移数组的值分别是相对于stringStart和styleStart而言的。在解析二进制Xml文件的时候,通过这两个偏移数组以及stringsStart和stylesStart的值就可以迅速地定位到第i个字符串。
  接下来,我们就重点说说什么是字符串样式。假设有一个字符串资源池,它有五个字符串,分别是"apple"、“banana”、“orange”、“mango”和“pear”。注意到第四个字符串“mango”,它实际表示的是一个字符串“mango”,不过它的前三个字符“man”通过b标签来描述为粗体的,而后两个字符通过i标签来描述为斜体的。这样实际上在整个字符串资源池中,包含了七个字符串,分别是"apple"、“banana”、“orange”、“mango”、“pear”、“b”和“i”,其中,第四个字符串“mango”来有两个sytle,第一个style表示第1到第3个字符是粗体的,第二个style表示第4到第5个字符是斜体的。
  字符串与其样式描述是一一对应的,也变是说,如果第i个字符串是带有样式描述的,那么它的样式描述就位于样式内容块第i个位置上。以上面的字符串资源池为例,由于第4个字符中带有样式描述,为了保持字符串与样式描述的一一对应关系,那么也需要假设前面3个字符串也带有样式描述的,不过需要将这3个字符串的样式描述的个数设置为0。也就是说,在这种情况下,字符串的个数等于7,而样式描述的个数等于4,其中,第1到第3个字符串的样式描述的个数等于0,而第4个字符串的样式描述的个数等于2。
  假设一个字符串有N个样式描述,那么它在样式内容块中,就对应有N个ResStringPool_span,以及一个ResStringPool_ref,其中,N个ResStringPool_span位于前面,用来描述每一个样式,而ResStringPool_ref表示一个结束占位符。例如,对于上述的“mango”字符串来说,它就对应有2个ResStringPool_span,以及1个ResStringPool_ref,而对于"apple"、“banana”和“orange”这三个字符串来说,它们对应有0个ResStringPool_span,但是对应有1个ResStringPool_ref,最后三个字符串“pear”、“b”和"i"对应有0个ResStringPool_span和0个ResStringPool_ref。
  ResStringPool_span和ResStringPool_ref定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
  1.   [cpp] view plaincopy/**
  2.   * Reference to a string in a string pool.
  3.   */
  4.   struct ResStringPool_ref
  5.   {
  6.   // Index into the string pool table (uint32_t-offset from the indices
  7.   // immediately after ResStringPool_header) at which to find the 
  8. location
  9.   // of the string data in the pool.
  10.   uint32_t index;
  11.   };
  12.   /**
  13.   * This structure defines a span of style information associated with
  14.   * a string in the pool.
  15.   */
  16.   struct ResStringPool_span
  17.   {
  18.   enum {
  19.   END = 0xFFFFFFFF
  20.   };
  21.   // This is the name of the span -- that is, the name of the XML
  22.   // tag that defined it. The special value END (0xFFFFFFFF) indicates
  23.   // the end of an array of spans.
  24.   ResStringPool_ref name;
  25.   // The range of characters in the string that this span applies to.
  26.   uint32_t firstChar, lastChar;
  27.   };
复制代码

  由于ResStringPool_ref在这里出现的作用就是充当样式描述结束占位符,因此,它唯一的成员变量index的取值就固定为ResStringPool_span::END。
  再来看ResStringPool_span是如何表示一个样式描述的。以字符串“mango”的第一个样式描述为例,对应的ResStringPool_span的各个成员变量的取值为:
  --name:等于字符串“b”在字符串资源池中的位置。
  --firstChar:等于0,即指向字符“m”。
  --lastChar:等于2,即指向字符"n"。
  综合起来就是表示字符串“man”是粗体的。
  再以字符串“mango”的第二个样式描述为例,对应的ResStringPool_span的各个成员变量的取值为:
  --name:等于字符串“i”在字符串资源池中的位置。
  --firstChar:等于3,即指向字符“g”。
  --lastChar:等于4,即指向字符“o”。
  综合起来就是表示字符串“go”是斜体的。
  另外有一个地方需要注意的是,字符串样式内容的最后会有8个字节,每4个字节都被填充为ResStringPool_span::END,用来表达字符串样式内容结束符。这个结束符可以在解析过程中用作错误验证。
  Step 5. 写入资源ID
  在前面的Step 1中,我们把属性的资源ID都收集起来了。这些收集起来的资源ID会作为一个单独的chunk写入到最终的二进制Xml文件中去。这个chunk位于字符串资源池的后面,它的头部使用ResChunk_header来描述。这个ResChunk_header的各个成员变量的取值如下所示:
  --type:等于RES_XML_RESOURCE_MAP_TYPE,表示这是一个从字符串资源池到资源ID的映射头部。
  --headerSize:等于sizeof(ResChunk_header),表示头部大小。
  --size:等于headerSize的大小再加上sizeof(uint32_t) * count,其中,count为收集到的资源ID的个数。
  以main.xml为例,字符串资源池的第一个字符串为“orientation”,而在资源ID这个chunk中记录的第一个数据为0x010100c4,那么就表示属性名称字符串“orientation”对应的资源ID为0x010100c4。
  Step 6. 压平Xml文件
  压平Xml文件其实就是指将里面的各个Xml元素中的字符串都替换掉。这些字符串要么是被替换成到字符串资源池的一个索引,要么是替换成一个具有类型的其它值。我们以main.xml为例来说这个压平的过程。
二进制XML存储方案BinXML实现方法 BinXML是我个人杜撰出来的名字,不知道是否合适,也不知道是否已存在类似的解决方案。在vcer.net上我将前一段时间的这个BinXML方案贡献出来,希望能够与广大vcer分享。 当时问题的背景是这样的,项目需要确定存储方案,这种方案需要满足如下要求: · 生成的单机板exe尽可能的不依赖于其它软件,如:数据库管理系统; · 存储的数据最好能够方便的拷贝,以满足项目的上报、汇总的功能; · 存储的格式以后可能还会变,变化不频繁,但是最好能支持这种变化; · 只需要数据的持久化与反持久化的功能,不需要查询统计等复杂功能; · 存储的数据中可能包括一个或者多个的文件附件,如:word文档; · 存储的数据可能被不同的平台使用,如:windows或者linux; · 作为web项目的一部分,除了提供VC的接口之外,存储的数据格式需要提供java的接口; 我首先考虑到了MsAccess格式,文件型数据库,方便拷贝,而且表的设计很柔性化。在windows环境下,MsAccess似乎可以不需要再安装额外的驱动,但是在linux环境下,如何被java调用是个问题(当然,这个问题也是可以解决的,但是很别扭)。其次,使用RDB还有一个问题:数据的层次表达与多值问题,将树型数据扁平化存储的方案是有的,但是,将几层简单的节点拆分成N个表格,岂不是杀鸡用牛刀? 其次的考虑当然是XML,然而XML是基于可读文本的,如何解决二进制数据问题?当然可以通过编码的途径来解决,但是这样使用XML是不是太牵强了?而且,XML有个缺陷,数据都是文本型的。要使用数值型、布尔型、日期型的数据,需要做进一步的解析。 那么就使用自定义的数据文件格式?传统情况下,我们会用一个或者若干个struct将数据打包,一下子塞进文件。但是现在用户说了:我们现在定义的数据结构可能会变:) 看来,我们的存储方案还必须要足够的柔性化。 想到最后,我决定还是借鉴XML的树型标签形式,来实现一种二进制的存储结构,即BinXML:) 关于src包 对于BinXML-src里面的例子,数据的结构类似于: BinXML-src.zip包括两部分,一部分是vc的工程,一部分是java的工程,在Visual C++6.0和eclipse+JDK1.4中都已编译、运行通过。大家可以运行BinXML-bin.zip里面的exe程序,测试一下BinXML文件的加载和保存功能。 对象模型 BinXML的对象模型主要包括:文档(document)和节点(node)。每个文档包含一个根节点(root),根节点下面包含一个或者多个子节点,如此类推。 文档包含一个文件头,其中包含了一些标识串、操作系统版本、文件大小、文档创建时间等信息。 每个节点也包含一个节点头,表明该节点的名称以及大小。一般来说,用户不会直接接触到文件头和节点头这些信息。 如下为VC里的类声明: 在java里,定义了binxml.io包,其中的类、接口定义与以上相似。 你很容易想到,使用BinXML,可以很方便地完成CTreeCtrl的持久化。 如何使用? 如下是VC中加载BinXML的代码片断: 对应的,写入BinXML的代码片断: 是不是很简单?在java里面,BinXML的一个测试用例: TODO 希望BinXML能给你带来一定的帮助和启发,更多的是希望能带来启发:)希望有兴趣的哥们继续完善BinXML,别忘了在你的大名之前保留我的版权信息: BinXML需要完善的地方表现在: · 暂时只实现了string/long/byte[],还没有提供其它类型的解析与转换; · XML标签不支持属性,只支持子元素:) · 是不是可以提供一个oxm模型,完成BinXML与Object之间的直接映射,现在这个映射工作还是人工代码实现的,譬如:GetString/GetLong,等等。应该可以完善一个映射机制,自动根据定义好的类型进行转换;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值