xbd 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE.TXT +25 -0
- data/README.md +20 -0
- data/Rakefile +4 -0
- data/lib/xbd.rb +2 -0
- data/lib/xbd/asi.rb +180 -0
- data/lib/xbd/version.rb +3 -0
- data/lib/xbd/xbd.rb +122 -0
- data/lib/xbd/xbd_dictionary.rb +52 -0
- data/lib/xbd/xbd_tag.rb +187 -0
- data/spec/xbd_asi_spec.rb +70 -0
- data/spec/xbd_dictionary_spec.rb +51 -0
- data/spec/xbd_tag_spec.rb +132 -0
- data/spec/xbd_test_helper.rb +3 -0
- data/tasks/spec.rake +17 -0
- data/xbd.gemspec +24 -0
- metadata +130 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.TXT
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
XBD Ruby-Gem Copyright (c) 2012, Imikimi LLC
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
* Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
* Neither the name of the <organization> nor the
|
12
|
+
names of its contributors may be used to endorse or promote products
|
13
|
+
derived from this software without specific prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL IMIKIMI LLC BE LIABLE FOR ANY
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
25
|
+
|
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
## XBD
|
2
|
+
|
3
|
+
The XBD gem allows you to create, load and save XBD files. XBD files are arbitrary, self-describing, hierarchical,
|
4
|
+
binary data structures consisting of "tags", "attributes", and "sub-tags".
|
5
|
+
|
6
|
+
## XBD vs XML
|
7
|
+
|
8
|
+
Feature differences:
|
9
|
+
|
10
|
+
* Any XML file can be converted to XBD, but the referse is not true.
|
11
|
+
* XBD allows you to store aribrary binary strings in attributes.
|
12
|
+
* XBD does not allow any data between sub-tags
|
13
|
+
* XBD Tag and Attribute names can be arbitrary binary string.
|
14
|
+
|
15
|
+
Additional benefits of XBD:
|
16
|
+
|
17
|
+
* XBD uses dictionaries (hashes) to store all Tag-Name, Attribute-Name and Attribute-Values. Consequently: XBD files can be as small as 1/10th the size an equivelent XML file
|
18
|
+
* XBD files are simple and streamlined for encoding/decoding. Consequently they can be as much as 10x faster to read and write.
|
19
|
+
|
20
|
+
(NOTE: The 10x performance improvement was tested on the original pure-C++ implementation vs a fast, pure-C++ XML parser. Tests have not been made in this Ruby version.)
|
data/Rakefile
ADDED
data/lib/xbd.rb
ADDED
data/lib/xbd/asi.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
$ruby_inline=true
|
3
|
+
require "inline" if $ruby_inline
|
4
|
+
require "stringio"
|
5
|
+
|
6
|
+
module Xbd
|
7
|
+
#*********************************
|
8
|
+
# Xbd::Asi module
|
9
|
+
#*********************************
|
10
|
+
#
|
11
|
+
# Read and Generate ASI strings
|
12
|
+
class Asi
|
13
|
+
|
14
|
+
# read an ASI from a string, returning an integer
|
15
|
+
# optionally starts and the specified offset index.
|
16
|
+
#
|
17
|
+
# returns the number read and the first index after the ASI data in the string.
|
18
|
+
def Asi.read_asi(source,index=0)
|
19
|
+
ret=0
|
20
|
+
shift=0
|
21
|
+
val=0
|
22
|
+
while index<source.length
|
23
|
+
val=source.byte(index)
|
24
|
+
ret+= (val & 0x7F) << shift;
|
25
|
+
shift+=7
|
26
|
+
index+=1
|
27
|
+
break if val<128
|
28
|
+
end
|
29
|
+
return ret,index
|
30
|
+
end
|
31
|
+
|
32
|
+
def Asi.read_asi_string(source,index=0)
|
33
|
+
n,index=read_asi(source,index)
|
34
|
+
return source[index,n],index+n
|
35
|
+
end
|
36
|
+
|
37
|
+
def Asi.read_asi_from_file(file)
|
38
|
+
ret=0
|
39
|
+
shift=0
|
40
|
+
val=0
|
41
|
+
while val=file.readbyte
|
42
|
+
ret+= (val & 0x7F) << shift;
|
43
|
+
shift+=7
|
44
|
+
break if val<128
|
45
|
+
end
|
46
|
+
return ret
|
47
|
+
end
|
48
|
+
|
49
|
+
def Asi.asi_to_i(source)
|
50
|
+
Asi.read_asi(source,0)[0]
|
51
|
+
end
|
52
|
+
|
53
|
+
# this C function supports all values up to the maximum value of 2^64-1
|
54
|
+
# RubyInline autodetects if the number is too big and throws an
|
55
|
+
if $ruby_inline
|
56
|
+
inline do |compiler|
|
57
|
+
compiler.c <<-ENDC
|
58
|
+
VALUE i_to_asi_c(unsigned long num) {
|
59
|
+
char str[11]; // I think 10 is enough, but just to be safe
|
60
|
+
int p=0;
|
61
|
+
while(p==0 || num > 0) {
|
62
|
+
int byte = (int)(num & 0x7F);
|
63
|
+
num = num >> 7;
|
64
|
+
if (num > 0) byte = byte | 0x80;
|
65
|
+
str[p++]=byte;
|
66
|
+
}
|
67
|
+
return rb_str_new(str,p);
|
68
|
+
}
|
69
|
+
ENDC
|
70
|
+
end
|
71
|
+
else
|
72
|
+
def i_to_asi_c(num)
|
73
|
+
Asi.i_to_asi_ruby(num)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
ASI_INSTANCE=Asi.new
|
78
|
+
def Asi.i_to_asi2(num)
|
79
|
+
ASI_INSTANCE.i_to_asi_c(num)
|
80
|
+
end
|
81
|
+
|
82
|
+
def Asi.i_to_asi_ruby(num)
|
83
|
+
ret=""
|
84
|
+
while ret.length==0 || num>0
|
85
|
+
val=num & 0x7F;
|
86
|
+
num=num>>7
|
87
|
+
val|=0x80 if num>0
|
88
|
+
ret<<val
|
89
|
+
end
|
90
|
+
ret
|
91
|
+
end
|
92
|
+
class <<self
|
93
|
+
alias :i_to_asi :i_to_asi_ruby
|
94
|
+
end
|
95
|
+
|
96
|
+
def Asi.asi_length(num)
|
97
|
+
count=1
|
98
|
+
while num>=0x80
|
99
|
+
num>>=7
|
100
|
+
count+=1
|
101
|
+
end
|
102
|
+
count
|
103
|
+
end
|
104
|
+
|
105
|
+
#*********************************
|
106
|
+
# Enable ASI reading and writing
|
107
|
+
# in standard objects.
|
108
|
+
#*********************************
|
109
|
+
module IO
|
110
|
+
def read_asi(index=0)
|
111
|
+
Asi.read_asi_from_file(self)
|
112
|
+
end
|
113
|
+
|
114
|
+
# read an asi and then read the next N bytes, where N is the asi value
|
115
|
+
# index's value is ignored
|
116
|
+
def read_asi_string(index=0)
|
117
|
+
read(Asi.read_asi_from_file(self))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module Fixnum
|
122
|
+
ASI_INSTANCE = Asi.new
|
123
|
+
def to_asi
|
124
|
+
ASI_INSTANCE.i_to_asi_c(self)
|
125
|
+
end
|
126
|
+
def asi_length
|
127
|
+
Xbd::Asi.asi_length(self)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
module Bignum
|
132
|
+
def to_asi
|
133
|
+
Asi.i_to_asi(self)
|
134
|
+
end
|
135
|
+
def asi_length
|
136
|
+
Xbd::Asi.asi_length(self)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
module String
|
141
|
+
def from_asi
|
142
|
+
Asi.asi_to_i(self)
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_asi_string
|
146
|
+
self.length.to_asi+self
|
147
|
+
end
|
148
|
+
|
149
|
+
def read_asi(index=0)
|
150
|
+
Asi.read_asi(self,index)
|
151
|
+
end
|
152
|
+
|
153
|
+
def read_asi_string(index=0)
|
154
|
+
Asi.read_asi_string(self,index)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Ruby 1.8 patch to ignore force_encoding
|
158
|
+
if !"".respond_to?(:force_encoding)
|
159
|
+
def to_binary; self end
|
160
|
+
def force_encoding(a) self end
|
161
|
+
def byte(index)
|
162
|
+
self[index]
|
163
|
+
end
|
164
|
+
else
|
165
|
+
# Ruby 1.9
|
166
|
+
def to_binary; self.force_encoding("BINARY") end
|
167
|
+
def byte(index)
|
168
|
+
char=self[index]
|
169
|
+
char && char.bytes.next
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class Fixnum ; include Xbd::Asi::Fixnum ; end
|
177
|
+
class File ; include Xbd::Asi::IO ; end
|
178
|
+
class StringIO; include Xbd::Asi::IO ; end
|
179
|
+
class Bignum ; include Xbd::Asi::Bignum ; end
|
180
|
+
class String ; include Xbd::Asi::String ; end
|
data/lib/xbd/version.rb
ADDED
data/lib/xbd/xbd.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
#*****************************************************
|
3
|
+
# Ruby XBD Library
|
4
|
+
# (C) 2010-10-03 Shane Brinkman-Davis
|
5
|
+
#
|
6
|
+
# SRC home:
|
7
|
+
# URL: https://svn.imikimi.com/auto_branch/2008-10-30_15-54-25_iphone_edge/xbd/xbd.rb
|
8
|
+
# Repository Root: https://svn.imikimi.com
|
9
|
+
# Repository UUID: d1359c2d-ec2c-0410-b0bc-eca7d5e44040
|
10
|
+
# Revision: 11647
|
11
|
+
#*****************************************************
|
12
|
+
=begin
|
13
|
+
|
14
|
+
This code read and writes XBD files.
|
15
|
+
|
16
|
+
To get started:
|
17
|
+
|
18
|
+
Take any XBD file
|
19
|
+
example Kimi from Imikimi.com (all Kimis are XBD containers)
|
20
|
+
http://imikimi.com/plugin/get_kimi/o-10i
|
21
|
+
|
22
|
+
And run:
|
23
|
+
require "xbd.rb"
|
24
|
+
puts Xbd.load_from_file("your_xbd_filename.xbd")
|
25
|
+
|
26
|
+
Master verison of this file lives in: imikimi_plugin_source/xbd/xbd.rb
|
27
|
+
|
28
|
+
=end
|
29
|
+
require File.join(File.dirname(__FILE__),"asi")
|
30
|
+
require File.join(File.dirname(__FILE__),"xbd_dictionary")
|
31
|
+
require File.join(File.dirname(__FILE__),"xbd_tag")
|
32
|
+
|
33
|
+
#*********************************
|
34
|
+
# Xbd Module
|
35
|
+
#*********************************
|
36
|
+
module Xbd
|
37
|
+
|
38
|
+
SBDXML_HEADER="SBDXML\1\0"
|
39
|
+
|
40
|
+
#**********************************************************
|
41
|
+
# XML escaping
|
42
|
+
#**********************************************************
|
43
|
+
# Used when converting TO xml (Xbd::Tag.to_s)
|
44
|
+
# Note, some of these codes are actually invalid strict XML because XML has no escape codes for some values.
|
45
|
+
# SBD: WTF! (I'm still surprised by this, but it appears to be true.)
|
46
|
+
XML_ESCAPE_CODES={
|
47
|
+
"�"=>0,
|
48
|
+
""=>1,
|
49
|
+
""=>2,
|
50
|
+
""=>3,
|
51
|
+
""=>4,
|
52
|
+
""=>5,
|
53
|
+
""=>6,
|
54
|
+
""=>7,
|
55
|
+
""=>8,
|
56
|
+
"	"=>9,
|
57
|
+
" "=>10,
|
58
|
+
""=>11,
|
59
|
+
""=>12,
|
60
|
+
" "=>13,
|
61
|
+
""=>14,
|
62
|
+
""=>15,
|
63
|
+
""=>16,
|
64
|
+
""=>17,
|
65
|
+
""=>18,
|
66
|
+
""=>19,
|
67
|
+
""=>20,
|
68
|
+
""=>21,
|
69
|
+
""=>22,
|
70
|
+
""=>23,
|
71
|
+
""=>24,
|
72
|
+
""=>25,
|
73
|
+
""=>26,
|
74
|
+
""=>27,
|
75
|
+
""=>28,
|
76
|
+
""=>29,
|
77
|
+
""=>30,
|
78
|
+
""=>31,
|
79
|
+
"""=>34, #"
|
80
|
+
"&"=>38, #&
|
81
|
+
"'"=>39, #'
|
82
|
+
"<"=>60, #<
|
83
|
+
">"=>62 #>
|
84
|
+
}
|
85
|
+
|
86
|
+
@@escape_for_xml=[]
|
87
|
+
(0..255).each {|i| @@escape_for_xml<<i.chr}
|
88
|
+
XML_ESCAPE_CODES.each {|k,v| @@escape_for_xml[v]=k}
|
89
|
+
|
90
|
+
def self.xml_escape(s)
|
91
|
+
out=""
|
92
|
+
s.each_byte {|b| out<< @@escape_for_xml[b]}
|
93
|
+
out
|
94
|
+
end
|
95
|
+
|
96
|
+
# XBD.parse accepts:
|
97
|
+
# a string or
|
98
|
+
# any object that returns a string in response to the method "read"
|
99
|
+
# (for example, an open file handle)
|
100
|
+
def Xbd.parse(source)
|
101
|
+
#treat source as a stream(file) if it isn't a string
|
102
|
+
source=source.read.force_encoding("BINARY") if source.class!=String
|
103
|
+
|
104
|
+
# read the header
|
105
|
+
raise "Not a valid XBD file" unless source[0..SBDXML_HEADER.length-1]==SBDXML_HEADER
|
106
|
+
index=SBDXML_HEADER.length
|
107
|
+
|
108
|
+
# read each of the 3 dictionaries in order
|
109
|
+
tagsd,index=Dictionary.parse(source,index)
|
110
|
+
attrsd,index=Dictionary.parse(source,index)
|
111
|
+
valuesd,index=Dictionary.parse(source,index)
|
112
|
+
|
113
|
+
# read all tags, return the root-tag
|
114
|
+
Tag.parse(source,index,tagsd,attrsd,valuesd)[0]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Load XBD from filename
|
118
|
+
def Xbd.load_from_file(filename)
|
119
|
+
Xbd.parse(File.open(filename,"rb"))
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Xbd
|
2
|
+
#*********************************
|
3
|
+
# Xbd::Dictionary
|
4
|
+
#*********************************
|
5
|
+
# Consists of:
|
6
|
+
# @hash: a map from values to IDs and IDs to values
|
7
|
+
# @array: a list of values; their indexes == their IDs
|
8
|
+
#
|
9
|
+
class Dictionary
|
10
|
+
attr_reader :hash,:array
|
11
|
+
|
12
|
+
def initialize(initial_values=[])
|
13
|
+
@hash={}
|
14
|
+
@array=[]
|
15
|
+
initial_values.each {|v| self<<(v)}
|
16
|
+
end
|
17
|
+
|
18
|
+
# return String given an ID, or ID given a String
|
19
|
+
def [](i) @hash[i] end
|
20
|
+
|
21
|
+
def Dictionary.sanitize_string(str)
|
22
|
+
case str
|
23
|
+
when String then "#{str}".force_encoding("BINARY")
|
24
|
+
else str.to_s.force_encoding("BINARY")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# add a String to the dictionary
|
29
|
+
def <<(str)
|
30
|
+
str = Dictionary.sanitize_string str
|
31
|
+
@hash[str] ||= begin
|
32
|
+
new_id = @array.length
|
33
|
+
@array << @hash[new_id] = str
|
34
|
+
new_id
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# convert to binary string
|
39
|
+
def to_binary
|
40
|
+
[@array.length.to_asi, @array.collect{|v| v.length.to_asi}, @array].join.to_asi_string
|
41
|
+
end
|
42
|
+
|
43
|
+
def Dictionary.parse(source,index=0)
|
44
|
+
encoded_dictionary, index = source.read_asi_string index
|
45
|
+
encoded_dictionary = StringIO.new(encoded_dictionary)
|
46
|
+
num_entries = encoded_dictionary.read_asi
|
47
|
+
lengths = num_entries.times.collect {encoded_dictionary.read_asi}
|
48
|
+
strings = lengths.collect {|len| encoded_dictionary.read len}
|
49
|
+
[Dictionary.new(strings), index]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/xbd/xbd_tag.rb
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
module Xbd
|
2
|
+
#*********************************
|
3
|
+
# Xbd::Tag object
|
4
|
+
#*********************************
|
5
|
+
#
|
6
|
+
# Consists of:
|
7
|
+
# name: a string
|
8
|
+
# attrs: a Hash of Attributes, String -> String
|
9
|
+
# tags: an ordered Array of Tag objects
|
10
|
+
class Tag
|
11
|
+
|
12
|
+
def initialize(name,attrs=nil,tags=nil,&block)
|
13
|
+
@name=name.to_s
|
14
|
+
@attrs={}
|
15
|
+
attrs.each {|k,v|@attrs[k.to_s]=v.to_s} if attrs
|
16
|
+
@tags=[]
|
17
|
+
self<<tags if tags
|
18
|
+
yield self if block
|
19
|
+
end
|
20
|
+
#************************************************************
|
21
|
+
# Access Name
|
22
|
+
#************************************************************
|
23
|
+
def name() @name end
|
24
|
+
def name=(n) @name=n end
|
25
|
+
|
26
|
+
#************************************************************
|
27
|
+
# Access Attrs
|
28
|
+
#************************************************************
|
29
|
+
attr_reader :attrs
|
30
|
+
def [](attr) @attrs[attr.to_s] end
|
31
|
+
def []=(attr,val) val==nil ? @attrs.delete(attr.to_s) : @attrs[attr.to_s]=val.to_s end
|
32
|
+
|
33
|
+
#************************************************************
|
34
|
+
# Access Tags
|
35
|
+
#************************************************************
|
36
|
+
# return tags array
|
37
|
+
attr_reader :tags
|
38
|
+
def tagnames() @tags.collect {|t| t.name} end
|
39
|
+
|
40
|
+
# returns first tag that matches name
|
41
|
+
# names can be a "/" delimited path string
|
42
|
+
# OR an array of exact string values to match (in case you want to match "/" in a tag name)
|
43
|
+
def tag(names)
|
44
|
+
return self if !names || (names.kind_of?(Array) && names.length==0)
|
45
|
+
names=names.split("/") unless names.kind_of?(Array)
|
46
|
+
name=names[0]
|
47
|
+
tags.each do |tag|
|
48
|
+
return tag.tag(names[1..-1]) if tag.name==name
|
49
|
+
end
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def each_attribute
|
54
|
+
@attrs.each {|k,v| yield k,v}
|
55
|
+
end
|
56
|
+
|
57
|
+
# iterate over all tags or only with matching names
|
58
|
+
def each_tag(name=nil)
|
59
|
+
tags.each do |tag|
|
60
|
+
yield tag if !name || tag.name==name
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Add sub-tag or array of sub-tags
|
65
|
+
def <<(tag)
|
66
|
+
return unless tag # ignore nil
|
67
|
+
tags= tag.kind_of?(Array) ? tag : [tag]
|
68
|
+
tags.each {|t| raise "All sub-tags in must be #{self.class} objects. Attempted to add #{t.class} object." unless t.kind_of?(self.class)}
|
69
|
+
@tags+=tags
|
70
|
+
tag
|
71
|
+
end
|
72
|
+
|
73
|
+
def ==(tag)
|
74
|
+
name==tag.name &&
|
75
|
+
attrs==tag.attrs &&
|
76
|
+
tags==tag.tags
|
77
|
+
end
|
78
|
+
|
79
|
+
#************************************************************
|
80
|
+
# to XML (to_s)
|
81
|
+
#************************************************************
|
82
|
+
def to_s(indent="",max_attr_value_length=nil)
|
83
|
+
a=[name]
|
84
|
+
attrs.keys.sort.each do |k|
|
85
|
+
v=attrs[k]
|
86
|
+
v=v[0..max_attr_value_length-1] if max_attr_value_length
|
87
|
+
a<<"#{k}=\"#{Xbd.xml_escape(v)}\""
|
88
|
+
end
|
89
|
+
ret="#{indent}<#{a.join(' ')}"
|
90
|
+
if tags.length>0
|
91
|
+
ret+=">\n"
|
92
|
+
tags.each {|st| ret+=st.to_s(indent+" ",max_attr_value_length)}
|
93
|
+
ret+="#{indent}</#{name}>\n"
|
94
|
+
else
|
95
|
+
ret+="/>\n"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
alias :to_xml :to_s
|
99
|
+
|
100
|
+
def inspect
|
101
|
+
to_s("",32)
|
102
|
+
end
|
103
|
+
|
104
|
+
# convert to basic ruby data structure
|
105
|
+
def to_ruby
|
106
|
+
{:name=>name, :attrs=>@attrs.clone, :tags=>tags.collect {|tag|tag.to_ruby}}
|
107
|
+
end
|
108
|
+
|
109
|
+
#************************************************************
|
110
|
+
# to binary XBD support methods
|
111
|
+
#************************************************************
|
112
|
+
def populate_dictionaries(tagsd,attrsd,valuesd)
|
113
|
+
tagsd<<name # add this tag's name
|
114
|
+
attrs.each {|k,v| attrsd<<k; valuesd<<v} # add all attribute names and values
|
115
|
+
tags.each {|tag| tag.populate_dictionaries(tagsd,attrsd,valuesd)} # recurse on sub-tags
|
116
|
+
end
|
117
|
+
|
118
|
+
# encode just this tag in binary
|
119
|
+
# Note this returned value alone is not parsable
|
120
|
+
def to_binary_partial(tagsd,attrsd,valuesd)
|
121
|
+
# build attrs_data string: all attr name-value pairs as ASIs concatinated
|
122
|
+
attrs_data=attrs.keys.sort.collect {|key| attrsd[key].to_asi + valuesd[attrs[key]].to_asi}.join
|
123
|
+
|
124
|
+
data=tagsd[name].to_asi + # name asi
|
125
|
+
attrs_data.length.to_asi + attrs_data + # attrs length asi and attrs
|
126
|
+
tags.collect {|tag| tag.to_binary_partial(tagsd,attrsd,valuesd)}.join # sub-tags
|
127
|
+
data.to_asi_string # tag data pre-pended with tag-data length asi
|
128
|
+
end
|
129
|
+
|
130
|
+
#************************************************************
|
131
|
+
# to binary XBD (to_xbd)
|
132
|
+
#************************************************************
|
133
|
+
# use this to convert an xbd tag structure into a saveable xbd file-string
|
134
|
+
def to_binary
|
135
|
+
populate_dictionaries(tagsd=Dictionary.new, attrsd=Dictionary.new, valuesd=Dictionary.new)
|
136
|
+
Xbd::SBDXML_HEADER + tagsd.to_binary + attrsd.to_binary + valuesd.to_binary + to_binary_partial(tagsd,attrsd,valuesd)
|
137
|
+
end
|
138
|
+
|
139
|
+
#**********************************************************
|
140
|
+
# Load XBD Tag Data from String
|
141
|
+
#**********************************************************
|
142
|
+
# parse a Tag, all its Attributes and all its Sub-tags recursively.
|
143
|
+
#
|
144
|
+
# inputs:
|
145
|
+
# source - source binary string
|
146
|
+
# index - offset to start reading at
|
147
|
+
# tagsd - tag-names dictionary
|
148
|
+
# attrsd - attribute-names dictionary
|
149
|
+
# valuesd - attribute-values dictionary
|
150
|
+
# returns the Tag object generated AND the first "index" in the string after read tag-data
|
151
|
+
def Tag.parse(source,index,tagsd,attrsd,valuesd)
|
152
|
+
tag_length,index=Asi.read_asi(source,index)
|
153
|
+
tag_start_index=index
|
154
|
+
|
155
|
+
# read tag name
|
156
|
+
tag_name_id,index=Asi.read_asi(source,index)
|
157
|
+
tag_name=tagsd[tag_name_id]
|
158
|
+
raise "tag name id(#{tag_name_id}) not in tag-names dictionary" if !tag_name
|
159
|
+
|
160
|
+
# read attributes
|
161
|
+
attr_byte_size,index=Asi.read_asi(source,index)
|
162
|
+
attrs_hash={}
|
163
|
+
while attr_byte_size>0
|
164
|
+
i=index
|
165
|
+
name_id,index=Asi.read_asi(source,index)
|
166
|
+
value_id,index=Asi.read_asi(source,index)
|
167
|
+
attr_byte_size-=(index-i)
|
168
|
+
n=attrsd[name_id]
|
169
|
+
v=valuesd[value_id]
|
170
|
+
raise "attribute name id(#{name_id}) not in attribute-names dictionary" if !n
|
171
|
+
raise "attribute value id(#{value_id}) not in attribue-values dictionary" if !v
|
172
|
+
attrs_hash[n]=v
|
173
|
+
end
|
174
|
+
tag_length-=(index-tag_start_index)
|
175
|
+
|
176
|
+
# read sub-tags
|
177
|
+
tags=[]
|
178
|
+
while tag_length>0
|
179
|
+
i=index
|
180
|
+
node,index=Tag.parse(source,index,tagsd,attrsd,valuesd)
|
181
|
+
tags<<node
|
182
|
+
tag_length-=(index-i)
|
183
|
+
end
|
184
|
+
return Tag.new(tag_name,attrs_hash,tags),index
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__),"xbd_test_helper")
|
2
|
+
|
3
|
+
module Xbd
|
4
|
+
describe Asi do
|
5
|
+
|
6
|
+
def test_asi(n)
|
7
|
+
asi1=n.to_asi
|
8
|
+
asi2=Asi::ASI_INSTANCE.i_to_asi_c(n)
|
9
|
+
raise "(asi1=n.to_asi)!=asi2 (#{asi1.inspect}!=#{asi2.inspect}) n=#{n} asi1.encoding=#{asi1.encoding} asi2.encoding=#{asi2.encoding}" unless asi1==asi2
|
10
|
+
asi1.read_asi[0].should == n
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should be possible to convert all powers of two up to 2^64-1" do
|
14
|
+
v=0
|
15
|
+
65.times do
|
16
|
+
n=2**v-1
|
17
|
+
test_asi(n)
|
18
|
+
v+=1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
if $ruby_inline
|
23
|
+
it "should fail to convert 2^64 to an asi" do
|
24
|
+
lambda {test_asi(2**64)}.should raise_error(RangeError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "Fixnum > String > Fixnum" do
|
29
|
+
test_edges do |num|
|
30
|
+
num.to_asi.from_asi.should==num
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "to_asi_string and StringIO" do
|
35
|
+
test_string="foo"
|
36
|
+
asi_foo=test_string.to_asi_string
|
37
|
+
StringIO.new(asi_foo).read_asi_string.should==test_string
|
38
|
+
end
|
39
|
+
|
40
|
+
it "to_asi and StringIO" do
|
41
|
+
test_edges do |num|
|
42
|
+
asi=num.to_asi
|
43
|
+
StringIO.new(asi).read_asi.should==num
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "to_asi_string and String" do
|
48
|
+
test_string="foo"
|
49
|
+
asi_foo=test_string.to_asi_string
|
50
|
+
asi_foo.read_asi_string.should== [test_string,asi_foo.length]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "asi_length" do
|
54
|
+
test_edges do |n,c|
|
55
|
+
n.asi_length.should==c
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_edges
|
60
|
+
yield 0,1
|
61
|
+
|
62
|
+
(1..10).each do |c|
|
63
|
+
n=(1<<(7*c))
|
64
|
+
n_1=n-1
|
65
|
+
yield n_1,c
|
66
|
+
yield n,c+1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__),"xbd_test_helper")
|
2
|
+
|
3
|
+
module Xbd
|
4
|
+
describe Dictionary do
|
5
|
+
it "should be possible to create a dictionary" do
|
6
|
+
Dictionary.new.should_not == nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should return nils for non-existant entries" do
|
10
|
+
dict=Dictionary.new
|
11
|
+
dict["not_there"].should==nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be enumerate words added to it" do
|
15
|
+
dict=Dictionary.new
|
16
|
+
dict<<"word"
|
17
|
+
dict<<"with"
|
18
|
+
dict<<"you"
|
19
|
+
|
20
|
+
dict["word"].should==0
|
21
|
+
dict["with"].should==1
|
22
|
+
dict["you"].should==2
|
23
|
+
|
24
|
+
dict[0].should=="word"
|
25
|
+
dict[1].should=="with"
|
26
|
+
dict[2].should=="you"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should accept non-string values and convert them to strings" do
|
30
|
+
dict=Dictionary.new
|
31
|
+
dict<<:word
|
32
|
+
dict["word"].should==0
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should work to_bin and parse" do
|
36
|
+
dict=Dictionary.new
|
37
|
+
dict<<"foo"
|
38
|
+
dict<<"bar"
|
39
|
+
dict["foo"].should==0
|
40
|
+
dict["bar"].should==1
|
41
|
+
dict[0].should=="foo"
|
42
|
+
dict[1].should=="bar"
|
43
|
+
|
44
|
+
bin=dict.to_binary
|
45
|
+
dict2,next_index=Dictionary.parse(bin)
|
46
|
+
next_index.should==bin.length
|
47
|
+
dict2["foo"].should==0
|
48
|
+
dict2["bar"].should==1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__),"xbd_test_helper")
|
2
|
+
require "fileutils"
|
3
|
+
|
4
|
+
module Xbd
|
5
|
+
describe Tag do
|
6
|
+
it "test the various creation methods" do
|
7
|
+
Tag.new("my_tag").attrs.should=={}
|
8
|
+
Tag.new("my_tag",{:foo=>:bar}).attrs.should=={"foo"=>"bar"}
|
9
|
+
Tag.new("my_tag",{:foo=>:bar},[Tag.new("my_other_tag")]).attrs.should=={"foo"=>"bar"}
|
10
|
+
Tag.new("my_tag") do |tag|
|
11
|
+
tag[:foo]=:bar
|
12
|
+
end.attrs.should=={"foo"=>"bar"}
|
13
|
+
end
|
14
|
+
|
15
|
+
it "test attrs" do
|
16
|
+
tag=Tag.new("my_tag")
|
17
|
+
tag.attrs.should=={}
|
18
|
+
tag[:foo]=:bar
|
19
|
+
tag["foo"].should=="bar"
|
20
|
+
tag.attrs.should=={"foo"=>"bar"}
|
21
|
+
tag[:foo]=:bar2
|
22
|
+
tag["foo"].should=="bar2"
|
23
|
+
tag.attrs.should=={"foo"=>"bar2"}
|
24
|
+
tag[:foo2]=:bar3
|
25
|
+
tag["foo2"].should=="bar3"
|
26
|
+
tag.attrs.should=={"foo"=>"bar2","foo2"=>"bar3"}
|
27
|
+
end
|
28
|
+
|
29
|
+
it "test tags" do
|
30
|
+
tag=full_test_tag
|
31
|
+
tag.tagnames.should==["sub1","sub2"]
|
32
|
+
tag.tag("sub1")["sub1k"].should==nil
|
33
|
+
tag.tag("sub2")["sub2k"].should=="sub2v"
|
34
|
+
tag.each_tag("sub1") do |t|
|
35
|
+
t.name.should=="sub1"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should convert to XML" do
|
40
|
+
tag=full_test_tag
|
41
|
+
tag.tag("sub1").to_s.strip.should=='<sub1/>'
|
42
|
+
tag.tag("sub2").to_s.strip.should=='<sub2 sub2k="sub2v"/>'
|
43
|
+
tag.to_s.gsub(/\s+/," ").strip.should=='<my_tag a1="v1"> <sub1/> <sub2 sub2k="sub2v"/> </my_tag>'
|
44
|
+
tag.inspect
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should convert to Ruby Hashes" do
|
48
|
+
tag=full_test_tag
|
49
|
+
|
50
|
+
tag.to_ruby.should=={
|
51
|
+
:name=>"my_tag",
|
52
|
+
:attrs=>{"a1"=>"v1"},
|
53
|
+
:tags=>[
|
54
|
+
{:name=>"sub1", :attrs=>{}, :tags=>[]},
|
55
|
+
{:name=>"sub2", :attrs=>{"sub2k"=>"sub2v"}, :tags=>[]}
|
56
|
+
]
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should work to convert to xbd" do
|
61
|
+
tag=full_test_tag
|
62
|
+
xbd=tag.to_binary
|
63
|
+
tag2=Xbd.parse(xbd)
|
64
|
+
tag.to_s.should==tag2.to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should work to store binaries" do
|
68
|
+
tag1=full_test_tag
|
69
|
+
tag1["nested_xbd"]=full_test_tag.to_binary
|
70
|
+
tag2=Xbd.parse(tag1.to_binary)
|
71
|
+
tag2["nested_xbd"].should==tag1["nested_xbd"]
|
72
|
+
Xbd.parse(tag2["nested_xbd"]).should==full_test_tag
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should work to == and !=" do
|
76
|
+
tag1=full_test_tag
|
77
|
+
tag2=full_test_tag
|
78
|
+
(tag1!=tag2).should==false
|
79
|
+
(tag1==tag2).should==true
|
80
|
+
|
81
|
+
# test diff tags
|
82
|
+
tag2.tags.reverse!
|
83
|
+
(tag1!=tag2).should==true
|
84
|
+
(tag1==tag2).should==false
|
85
|
+
|
86
|
+
# test diff names
|
87
|
+
tag2=full_test_tag
|
88
|
+
tag2.name="nameeroo"
|
89
|
+
(tag1!=tag2).should==true
|
90
|
+
(tag1==tag2).should==false
|
91
|
+
|
92
|
+
# test diff attrs
|
93
|
+
tag2=full_test_tag
|
94
|
+
tag2["foomagoo"]="goofoo"
|
95
|
+
(tag1!=tag2).should==true
|
96
|
+
(tag1==tag2).should==false
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should work to load from file" do
|
100
|
+
begin
|
101
|
+
filename=File.join(File.dirname(__FILE__),"xbd_test_file.xbd")
|
102
|
+
tag=full_test_tag
|
103
|
+
File.open(filename,"wb") {|file| file.write(tag.to_binary)}
|
104
|
+
File.exists?(filename).should==true
|
105
|
+
tag2=Xbd.load_from_file(filename)
|
106
|
+
tag2.should==tag
|
107
|
+
ensure
|
108
|
+
FileUtils.rm filename
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should work to convert to/from binary" do
|
113
|
+
tagsd=Dictionary.new
|
114
|
+
attrsd=Dictionary.new
|
115
|
+
valuesd=Dictionary.new
|
116
|
+
tag=full_test_tag
|
117
|
+
tag.populate_dictionaries(tagsd,attrsd,valuesd)
|
118
|
+
bin=tag.to_binary_partial(tagsd,attrsd,valuesd)
|
119
|
+
tag2,index=Tag.parse(bin,0,tagsd,attrsd,valuesd)
|
120
|
+
index.should==bin.length
|
121
|
+
tag.to_s.should==tag2.to_s
|
122
|
+
end
|
123
|
+
|
124
|
+
def full_test_tag
|
125
|
+
Tag.new("my_tag") do |tag|
|
126
|
+
tag["a1"]="v1"
|
127
|
+
tag<<Tag.new("sub1")
|
128
|
+
tag<<Tag.new("sub2",{:sub2k=>:sub2v})
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/tasks/spec.rake
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
begin
|
2
|
+
require 'rspec'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
desc "Run all examples with RCov"
|
6
|
+
RSpec::Core::RakeTask.new('spec:rcov') do |t|
|
7
|
+
t.rcov = true
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
11
|
+
t.verbose = true
|
12
|
+
end
|
13
|
+
|
14
|
+
task :default => :spec
|
15
|
+
rescue LoadError
|
16
|
+
puts "rspec, or one of its dependencies, is not available. Install it with: sudo gem install rspec"
|
17
|
+
end
|
data/xbd.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "xbd/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "xbd"
|
7
|
+
s.version = Xbd::VERSION
|
8
|
+
s.authors = ["Shane Brinkman-Davis"]
|
9
|
+
s.email = ["shanebdavis@imikimi.com"]
|
10
|
+
s.homepage = "https://github.com/Imikimi-LLC/xbd"
|
11
|
+
s.summary = %q{A fast, simplified, XML-inspired binary file format.}
|
12
|
+
s.description = %q{The XBD gem allows you to create, load and save XBD files. XBD files are arbitrary, self-describing, hierarchical, binary data structures consisting of "tags", "attributes", and "sub-tags".}
|
13
|
+
|
14
|
+
s.rubyforge_project = "xbd"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency 'RubyInline', '~> 3.11.0'
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'rspec', '~> 2.6.0'
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xbd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Shane Brinkman-Davis
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-06-08 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: RubyInline
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 43
|
29
|
+
segments:
|
30
|
+
- 3
|
31
|
+
- 11
|
32
|
+
- 0
|
33
|
+
version: 3.11.0
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: rspec
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ~>
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 23
|
59
|
+
segments:
|
60
|
+
- 2
|
61
|
+
- 6
|
62
|
+
- 0
|
63
|
+
version: 2.6.0
|
64
|
+
type: :development
|
65
|
+
version_requirements: *id003
|
66
|
+
description: The XBD gem allows you to create, load and save XBD files. XBD files are arbitrary, self-describing, hierarchical, binary data structures consisting of "tags", "attributes", and "sub-tags".
|
67
|
+
email:
|
68
|
+
- shanebdavis@imikimi.com
|
69
|
+
executables: []
|
70
|
+
|
71
|
+
extensions: []
|
72
|
+
|
73
|
+
extra_rdoc_files: []
|
74
|
+
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE.TXT
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- lib/xbd.rb
|
82
|
+
- lib/xbd/asi.rb
|
83
|
+
- lib/xbd/version.rb
|
84
|
+
- lib/xbd/xbd.rb
|
85
|
+
- lib/xbd/xbd_dictionary.rb
|
86
|
+
- lib/xbd/xbd_tag.rb
|
87
|
+
- spec/xbd_asi_spec.rb
|
88
|
+
- spec/xbd_dictionary_spec.rb
|
89
|
+
- spec/xbd_tag_spec.rb
|
90
|
+
- spec/xbd_test_helper.rb
|
91
|
+
- tasks/spec.rake
|
92
|
+
- xbd.gemspec
|
93
|
+
homepage: https://github.com/Imikimi-LLC/xbd
|
94
|
+
licenses: []
|
95
|
+
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
hash: 3
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
version: "0"
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
hash: 3
|
116
|
+
segments:
|
117
|
+
- 0
|
118
|
+
version: "0"
|
119
|
+
requirements: []
|
120
|
+
|
121
|
+
rubyforge_project: xbd
|
122
|
+
rubygems_version: 1.8.10
|
123
|
+
signing_key:
|
124
|
+
specification_version: 3
|
125
|
+
summary: A fast, simplified, XML-inspired binary file format.
|
126
|
+
test_files:
|
127
|
+
- spec/xbd_asi_spec.rb
|
128
|
+
- spec/xbd_dictionary_spec.rb
|
129
|
+
- spec/xbd_tag_spec.rb
|
130
|
+
- spec/xbd_test_helper.rb
|