xbd 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|