treecard 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -0
- data/lib/treecard/attribute.rb +20 -0
- data/lib/treecard/nodes/content_line_node.rb +16 -0
- data/lib/treecard/nodes/param_node.rb +11 -0
- data/lib/treecard/param.rb +9 -0
- data/lib/treecard.rb +109 -0
- data/test/test_helper.rb +12 -0
- data/test/unit/treecard_test.rb +40 -0
- metadata +105 -0
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
At some point this will be documented. But not today.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# A line in the VCard file.
|
2
|
+
#
|
3
|
+
# * +name+ is the name of the attribute, like 'fn', 'adr', etc.
|
4
|
+
# * +param+ is a list of +TreeCard::Param+ objects, representing each
|
5
|
+
# parameter associated with this attribute
|
6
|
+
# * +value+ is the raw text of the attribute's value. It is up to
|
7
|
+
# the caller to parse, if necessary.
|
8
|
+
class TreeCard::Attribute
|
9
|
+
|
10
|
+
attr_reader :name, :params, :value
|
11
|
+
|
12
|
+
def initialize(name, params, value)
|
13
|
+
@name = name
|
14
|
+
@params = {}
|
15
|
+
params.each do |param|
|
16
|
+
@params[param.name.downcase] = param
|
17
|
+
end
|
18
|
+
@value = value
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class TreeCard::ContentLineNode < Treetop::Runtime::SyntaxNode
|
2
|
+
|
3
|
+
# Returns the TreeCard::Attribute representing this node.
|
4
|
+
def attribute
|
5
|
+
TreeCard::Attribute.new(name.text_value, param_objects, value.text_value)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def param_objects
|
11
|
+
param_list.elements.map do |semicolon_and_param|
|
12
|
+
semicolon_and_param.param.param_object
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class TreeCard::ParamNode < Treetop::Runtime::SyntaxNode
|
2
|
+
|
3
|
+
# Returns the TreeCard::Param object representing this node.
|
4
|
+
def param_object
|
5
|
+
param_values = []
|
6
|
+
if self.respond_to?(:param_value)
|
7
|
+
param_values = [param_value.text_value] + extra_params.elements.map { |extra_param| extra_param.param_value.text_value }
|
8
|
+
end
|
9
|
+
TreeCard::Param.new(param_name.text_value, param_values)
|
10
|
+
end
|
11
|
+
end
|
data/lib/treecard.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'treetop'
|
2
|
+
require 'grammar/vcard' # implements the vcard spec, found at http://tools.ietf.org/html/rfc2425
|
3
|
+
|
4
|
+
class TreeCard; end
|
5
|
+
|
6
|
+
require 'treecard/nodes/content_line_node'
|
7
|
+
require 'treecard/nodes/param_node'
|
8
|
+
require 'treecard/attribute'
|
9
|
+
require 'treecard/param'
|
10
|
+
|
11
|
+
class TreeCard
|
12
|
+
|
13
|
+
attr_reader :raw_data
|
14
|
+
attr_reader :attributes
|
15
|
+
|
16
|
+
def initialize(data)
|
17
|
+
@attributes = Hash.new {|h, k| h[k] = []}
|
18
|
+
@raw_data = self.class.unfold_lines(data)
|
19
|
+
@parser = VCardGrammarParser.new
|
20
|
+
ast = @parser.parse(@raw_data)
|
21
|
+
if ast
|
22
|
+
parse(ast)
|
23
|
+
else
|
24
|
+
raise TreeCard::ParseError, @parser.failure_reason
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def name
|
29
|
+
attr = attributes['fn']
|
30
|
+
attr &&= attr.first
|
31
|
+
attr.value if attr
|
32
|
+
end
|
33
|
+
|
34
|
+
def emails
|
35
|
+
attr = attributes['email']
|
36
|
+
attr.map {|email| email.value} if attr
|
37
|
+
end
|
38
|
+
|
39
|
+
def phone
|
40
|
+
attr = attribute_with_param('tel', 'voice')
|
41
|
+
attr.value if attr
|
42
|
+
end
|
43
|
+
|
44
|
+
def fax
|
45
|
+
attr = attribute_with_param('tel', 'fax')
|
46
|
+
attr.value if attr
|
47
|
+
end
|
48
|
+
|
49
|
+
def photo
|
50
|
+
attr = attributes['photo']
|
51
|
+
attr &&= attr.first
|
52
|
+
attr.value if attr
|
53
|
+
end
|
54
|
+
|
55
|
+
def address
|
56
|
+
attr = attributes['adr']
|
57
|
+
attr &&= attr.first
|
58
|
+
attr.value.split(';').join("\n").strip if attr
|
59
|
+
end
|
60
|
+
|
61
|
+
def company
|
62
|
+
attr = attributes['org']
|
63
|
+
attr &&= attr.first
|
64
|
+
attr.value if attr
|
65
|
+
end
|
66
|
+
|
67
|
+
# VCards automatically wrap lines longer than 75 characters. Wrapped
|
68
|
+
# lines are signified by leading whitespace.
|
69
|
+
def self.unfold_lines(data)
|
70
|
+
unfolded_data = ""
|
71
|
+
current_line = ""
|
72
|
+
data.lines.each do |line|
|
73
|
+
if line =~ /^\s\S+/
|
74
|
+
current_line << line[1..-1].chomp
|
75
|
+
else
|
76
|
+
if current_line != ""
|
77
|
+
if current_line =~ /=0D=0A=$/ #weird quoted printable thing
|
78
|
+
unfolded_data << current_line.chomp
|
79
|
+
else
|
80
|
+
unfolded_data << current_line + "\n"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
current_line = line.chomp
|
84
|
+
end
|
85
|
+
end
|
86
|
+
unfolded_data << current_line + "\n"
|
87
|
+
unfolded_data
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns the first attribute with +attr_name+ where +param_name+ is
|
91
|
+
# set.
|
92
|
+
def attribute_with_param(attr_name, param_name)
|
93
|
+
attributes = self.attributes[attr_name.downcase]
|
94
|
+
found_attributes = attributes.select { |attribute| attribute.params[param_name.downcase] }
|
95
|
+
found_attributes.first
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def parse(root)
|
101
|
+
root.elements.each do |contentline|
|
102
|
+
parse_contentline(contentline)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def parse_contentline(node)
|
107
|
+
attributes[node.name.text_value.downcase] << node.attribute
|
108
|
+
end
|
109
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'shoulda'
|
2
|
+
require 'treecard'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
class Test::Unit::TestCase
|
6
|
+
|
7
|
+
@@my_path = Pathname.new(File.expand_path(File.dirname(__FILE__)))
|
8
|
+
|
9
|
+
def vcard_data(vcard_file_name)
|
10
|
+
File.read(@@my_path + 'data' + vcard_file_name)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TreeCardTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "A new TreeCard populated with data" do
|
6
|
+
setup do
|
7
|
+
@vcard = TreeCard.new(vcard_data('Mark_Ericsson.vcf'))
|
8
|
+
end
|
9
|
+
|
10
|
+
should "unfold its lines" do
|
11
|
+
assert_equal "REV:20090729T163338Z", @vcard.raw_data.split("\n")[15]
|
12
|
+
end
|
13
|
+
|
14
|
+
should "parse its data" do
|
15
|
+
assert_equal "Mark S. Ericsson", @vcard.name
|
16
|
+
assert_equal ["mericsson@example.com"], @vcard.emails
|
17
|
+
assert_equal "(925) 930-6000", @vcard.phone
|
18
|
+
assert_equal "(925) 934-5377", @vcard.fax
|
19
|
+
assert_equal "Youngman, Ericsson & Low, LLP", @vcard.company
|
20
|
+
assert_equal "1981 N. Broadway, Suite 300\nWalnut Creek\nCA\n94596-3841\nUnited States of America", @vcard.address
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "TreeCard#unfold_lines" do
|
25
|
+
should "unfold lines that begin with a single whitespace character" do
|
26
|
+
data = "This is the first line\nThis is a long descrip\n tion that exists o\n\tn a long line"
|
27
|
+
assert_equal "This is the first line\nThis is a long description that exists on a long line\n", TreeCard.unfold_lines(data)
|
28
|
+
end
|
29
|
+
|
30
|
+
should "not unfold if there is no real data on a line" do
|
31
|
+
data = "This is another line\n \n"
|
32
|
+
assert_equal "This is another line\n \n", TreeCard.unfold_lines(data)
|
33
|
+
end
|
34
|
+
|
35
|
+
should "not unfold more than one whitespace character" do
|
36
|
+
data = "This is a line\n test text"
|
37
|
+
assert_equal "This is a line\n test text\n", TreeCard.unfold_lines(data)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: treecard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Justin Weiss
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-21 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: treetop
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 4
|
33
|
+
- 8
|
34
|
+
version: 1.4.8
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: shoulda
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 33
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 10
|
49
|
+
- 3
|
50
|
+
version: 2.10.3
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
description:
|
54
|
+
email: jweiss@avvo.com
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files:
|
60
|
+
- README.md
|
61
|
+
files:
|
62
|
+
- lib/treecard/attribute.rb
|
63
|
+
- lib/treecard/nodes/content_line_node.rb
|
64
|
+
- lib/treecard/nodes/param_node.rb
|
65
|
+
- lib/treecard/param.rb
|
66
|
+
- lib/treecard.rb
|
67
|
+
- test/test_helper.rb
|
68
|
+
- test/unit/treecard_test.rb
|
69
|
+
- README.md
|
70
|
+
has_rdoc: true
|
71
|
+
homepage:
|
72
|
+
licenses: []
|
73
|
+
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 3
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.3.7
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: A simple vcard parser using Treetop
|
104
|
+
test_files: []
|
105
|
+
|