gexf 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH << File.expand_path('../gexf', __FILE__)
2
+
3
+ require 'nokogiri'
4
+ require 'forwardable'
5
+ require 'set'
6
+
7
+ module GEXF;end
8
+
9
+ require 'version'
10
+ require 'attribute'
11
+ require 'attribute/definable'
12
+ require 'attribute/assignable'
13
+ require 'set_of_sets'
14
+ require 'node'
15
+ require 'nodeset'
16
+ require 'edge'
17
+ require 'edgeset'
18
+ require 'graph'
19
+ require 'xml_serializer'
20
+ require 'document'
21
+ require 'support'
@@ -0,0 +1,92 @@
1
+ class GEXF::Attribute
2
+
3
+ BOOLEAN = :boolean
4
+ STRING = :string
5
+ INTEGER = :integer
6
+ FLOAT = :float
7
+ ANY_URI = :anyURI
8
+ LIST_STRING = :liststring
9
+ TYPES = [ BOOLEAN, STRING, INTEGER, FLOAT, ANY_URI, LIST_STRING]
10
+
11
+ DYNAMIC = :dynamic
12
+ STATIC = :static
13
+ MODES = [DYNAMIC, STATIC]
14
+
15
+ NODE = :node
16
+ EDGE = :edge
17
+ CLASSES = [NODE, EDGE]
18
+
19
+ attr_reader :type, :id, :title, :options, :mode, :attr_class, :default
20
+
21
+ def initialize(id, title, opts={})
22
+
23
+ attr_class = opts[:class] || NODE
24
+ mode = opts[:mode] || STATIC
25
+ type = opts[:type] || STRING
26
+ default = opts[:default]
27
+ options = opts[:options]
28
+ id = id.to_s
29
+
30
+ @options = if type == LIST_STRING && options.respond_to?(:split)
31
+ options.split('|').uniq
32
+ else
33
+ Array(options).uniq
34
+ end
35
+
36
+
37
+ raise ArgumentError.new "Invalid or missing type: #{type}" if !TYPES.include?(type)
38
+ raise ArgumentError.new "invalid or missing class: #{attr_class}" if !CLASSES.include?(attr_class)
39
+ raise ArgumentError.new "Invalid mode: #{mode}" if !MODES.include?(mode)
40
+
41
+ @attr_class = attr_class
42
+ @type = type
43
+ @id = id
44
+ @type = type
45
+ @mode = mode
46
+ @title = title.to_s
47
+ self.default=default
48
+ end
49
+
50
+ def default=(value)
51
+ raise ArgumentError.new "value value '#{value}' is not included in 'options' list" if value && @options.any? && !@options.include?(value)
52
+ @default=value
53
+ end
54
+
55
+ #Note: is this a violation of the "Tell don't ask principle"?
56
+ def coherce(value)
57
+ case @type
58
+ when BOOLEAN
59
+ case value
60
+ when *['1', 'true', 1, true]
61
+ true
62
+ when *['0', 'false', 0, false]
63
+ false
64
+ end
65
+ when STRING, ANY_URI
66
+ value.to_s
67
+ when FLOAT
68
+ value.to_f if value.respond_to?(:to_f)
69
+ when INTEGER
70
+ value.to_i if value.respond_to?(:to_i)
71
+
72
+ when LIST_STRING
73
+ Array(value).flatten.map(&:to_s).uniq
74
+ end
75
+ end
76
+
77
+ def is_valid?(value)
78
+ if @options.empty?
79
+ true
80
+ else
81
+ value = value.first if value.respond_to?(:first)
82
+ @options.map(&:to_s).include?(value.to_s)
83
+ end
84
+ end
85
+
86
+ def to_hash
87
+ optional = {}
88
+ optional[:options] = options.join('|') if options && options.any?
89
+
90
+ {:id => id, :title => title, :type => type}.merge(optional)
91
+ end
92
+ end
@@ -0,0 +1,81 @@
1
+ module GEXF::Attribute::Assignable
2
+
3
+ # Delegates calls for the 'defined_attributes' method to the @collection instance variable
4
+ #
5
+ # Returns nothing.
6
+
7
+ def self.included(base)
8
+ base.extend(Forwardable) unless base.ancestors.include?(Forwardable)
9
+
10
+ base.def_delegator :collection, :attribute_definitions, :defined_attributes
11
+ base.def_delegator :attr_values, :[], :attr_value
12
+ end
13
+
14
+ # Reconstructs a hash of attiribute titles/values for the current node/edge
15
+ #
16
+ # Example:
17
+ #
18
+ # node.attributes
19
+ # => {:site => 'http://www.archive.org', :name => 'The internet archive'}
20
+ #
21
+ # Returns
22
+ #
23
+ # The attributes hash
24
+ #
25
+ def attributes()
26
+ Hash[*defined_attributes.map do |_, attr|
27
+ [attr.title, attr_value(attr.id)]
28
+ end.flatten]
29
+ end
30
+
31
+ #
32
+ # Fetches the attribute hash, and sets it to an empty array if this is not defined.
33
+ #
34
+ # Returns the 'attr_values' hash.
35
+ #
36
+
37
+ def attr_values
38
+ @attr_values ||= {}
39
+ end
40
+
41
+ #
42
+ # Fetches an attribute by title.
43
+ #
44
+ # Returns the attribute value.
45
+ # Raises a warning if the attribute has not been defined..
46
+ #
47
+
48
+ def [](key)
49
+ if attr = attribute_by_title(key)
50
+ attr_value(attr.id) || attr.default
51
+ else
52
+ Kernel.warn "undefined attribute '#{key}'"
53
+ end
54
+ end
55
+
56
+ # low level setter, suitable to be used when parsing (see GEXF::Document)
57
+ def set_attr_by_id(attr_id, value)
58
+ @attr_values[attr_id] = value
59
+ end
60
+
61
+ def []=(key, value)
62
+ attr = attribute_by_title(key)
63
+ value = attr && attr.coherce(value) || value
64
+
65
+ if attr && attr.is_valid?(value)
66
+ attr_values[attr.id] = value
67
+ else
68
+ Kernel.warn "undefined attribute '#{key}'"
69
+ end
70
+ end
71
+
72
+ private
73
+ def collection
74
+ @collection || []
75
+ end
76
+
77
+ def attribute_by_title(key)
78
+ _, attribute = *defined_attributes.find { |id, attr| attr.title == key.to_s }
79
+ attribute
80
+ end
81
+ end
@@ -0,0 +1,20 @@
1
+ module GEXF::Attribute::Definable
2
+
3
+ def define_attribute(id, title, opts={})
4
+ @attribute_definitions ||= {}
5
+ @attribute_definitions[id] = GEXF::Attribute.new(id, title, opts)
6
+ end
7
+
8
+ def attributes
9
+ Hash[*map do |item|
10
+ attributes = item.attributes
11
+ [item.id, attributes] if attributes && attributes.any?
12
+ end.compact.flatten]
13
+ end
14
+
15
+ def attribute_definitions
16
+ @attribute_definitions ||= {}
17
+ end
18
+
19
+ alias_method :defined_attributes, :attribute_definitions
20
+ end
@@ -0,0 +1,112 @@
1
+ class GEXF::Document < Nokogiri::XML::SAX::Document
2
+
3
+ attr_reader :graph, :meta
4
+
5
+ def start_document
6
+ @graph = nil
7
+ @node = nil
8
+ @edge = nil
9
+ @attr_class = nil
10
+ @attr = nil
11
+ @defined_attrs = {}
12
+ end
13
+
14
+ def start_element(tagname, attributes)
15
+ @current_tag = tagname
16
+ @current_tag_attrs = attributes
17
+
18
+ dispatch_event(tagname, sanitize_attrs(attributes))
19
+ end
20
+
21
+ def end_element(tagname)
22
+ case tagname
23
+ when 'attributes'
24
+ @attr_class = nil
25
+ when 'attribute'
26
+ @attr = nil
27
+ when 'node'
28
+ @node = nil
29
+ when 'edge'
30
+ @edge = nil
31
+ end
32
+
33
+ @current_tag_attrs = {}
34
+ @current_tag = nil
35
+ end
36
+
37
+ def characters(chars)
38
+ chars = chars.strip
39
+ case @current_tag
40
+ when 'default'
41
+ @attr.default = chars
42
+ end
43
+ end
44
+
45
+ private
46
+ def dispatch_event(tagname, attributes)
47
+ case tagname
48
+ when 'graph'
49
+ on_graph_start(attributes)
50
+ when 'attributes'
51
+ @attr_class = attributes[:class]
52
+ when 'attribute'
53
+ on_attribute_start(attributes)
54
+ when 'attvalue'
55
+ on_attrvalue_start(attributes)
56
+ when 'node'
57
+ on_node_start(attributes)
58
+ when 'edge'
59
+ on_edge_start(attributes)
60
+ else
61
+ end
62
+ end
63
+
64
+ def sanitize_attrs(attributes)
65
+ Hash[*attributes.flatten].
66
+ symbolize_keys.
67
+ symbolize_graph_types
68
+ end
69
+
70
+ def on_graph_start(attributes)
71
+ @graph = GEXF::Graph.new(attributes)
72
+ end
73
+
74
+ def on_attribute_start(definition)
75
+ title = definition.delete(:title)
76
+ type = definition[:type]
77
+ definition.delete(:class)
78
+
79
+ @attr = case @attr_class
80
+ when 'node'
81
+ graph.define_node_attribute(title, definition)
82
+ when 'edge'
83
+ graph.define_edge_attribute(title, definition)
84
+ end
85
+
86
+ @defined_attrs[@attr.id] = @attr.title
87
+ end
88
+
89
+ def on_node_start(attributes)
90
+ @node = graph.create_node(attributes)
91
+ end
92
+
93
+ def on_edge_start(attributes)
94
+ source_id = attributes.delete(:source)
95
+ target_id = attributes.delete(:target)
96
+
97
+ source = graph.nodes[source_id]
98
+ target = graph.nodes[target_id]
99
+
100
+ @edge = graph.create_edge(source, target, attributes)
101
+ end
102
+
103
+ def on_attrvalue_start(attrs)
104
+ attr_id = attrs[:for]
105
+
106
+ if @defined_attrs[attr_id]
107
+ @node.set_attr_by_id(attr_id, attrs[:value])
108
+ else
109
+ warn "Cannot find an attribute with id: #{id}"
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,73 @@
1
+ class GEXF::Edge
2
+ include GEXF::Attribute::Assignable
3
+
4
+ DIRECTED = :directed
5
+ UNDIRECTED = :undirected
6
+ MUTUAL = :mutual
7
+
8
+ TYPES = [ DIRECTED, UNDIRECTED, MUTUAL]
9
+
10
+ attr_reader :id, :source_id, :target_id, :weight, :type, :label
11
+
12
+ def initialize(id, source_id, target_id, opts={})
13
+
14
+ type = opts[:type] || UNDIRECTED
15
+ weight = opts[:weight] && opts[:weight].to_f
16
+ graph = opts[:graph]
17
+ label = opts[:label] && opts[:label].to_s || nil
18
+ id = id.to_s
19
+ source_id = source_id.to_s
20
+ target_id = target_id.to_s
21
+
22
+ raise ArgumentError.new("Invalid type: #{type}") if !TYPES.include?(type)
23
+ raise ArgumentError.new("'weight' should be a positive, numerical value") if weight && weight < 0.1
24
+ raise ArgumentError.new("Missing graph") if !graph
25
+ raise ArgumentError.new("Missing id") if !id || id.empty?
26
+ raise ArgumentError.new("Missing source_id") if !source_id || source_id.empty?
27
+ raise ArgumentError.new("Missing target_id") if !target_id || target_id.empty?
28
+
29
+ @id = id.to_s
30
+ @label = label
31
+ @type = type
32
+ @source_id = source_id.to_s
33
+ @target_id = target_id.to_s
34
+ @weight = weight || 1.0
35
+ @graph = graph
36
+ # see GEXF::Attribute::Assignable
37
+ @collection = @graph.edges
38
+ @attr_values = {}
39
+
40
+ set_attributes(opts.fetch(:attributes, {}))
41
+ end
42
+
43
+ [:directed, :undirected, :mutual].each do |type|
44
+ define_method("#{type}?") do
45
+ @type == self.class.const_get(type.to_s.upcase)
46
+ end
47
+ end
48
+
49
+ def target
50
+ @graph.nodes[target_id]
51
+ end
52
+
53
+ def source
54
+ @graph.nodes[source_id]
55
+ end
56
+
57
+ def to_hash
58
+ optional = {}
59
+ optional[:label] = label if label && !label.empty?
60
+
61
+ {:id => id,
62
+ :source => source_id,
63
+ :target => target_id,
64
+ :type => type
65
+
66
+ }.merge(optional)
67
+ end
68
+
69
+ private
70
+ def set_attributes(attributes={})
71
+ attributes.each { |attr,value| self[attr]=value }
72
+ end
73
+ end
@@ -0,0 +1,7 @@
1
+ class GEXF::EdgeSet < GEXF::SetOfSets
2
+ def <<(edge)
3
+ append_to_key(edge.source_id, edge)
4
+ append_to_key(edge.target_id, edge) unless edge.directed?
5
+ self
6
+ end
7
+ end
@@ -0,0 +1,82 @@
1
+ class GEXF::Graph
2
+
3
+ STRING = :string
4
+ INTEGER = :integer
5
+ IDTYPES = [STRING, INTEGER]
6
+
7
+ STATIC = :static
8
+ DYNAMIC = :dynamic
9
+ MODES = [STATIC, DYNAMIC]
10
+
11
+ EDGETYPES = GEXF::Edge::TYPES
12
+
13
+ attr_reader :defaultedgetype, :idtype, :mode, :nodes, :edges
14
+
15
+ def initialize(opts={})
16
+ edgetype = opts[:defaultedgetype] || GEXF::Edge::UNDIRECTED
17
+ idtype = opts[:idtype] || STRING
18
+ mode = opts[:mode] || STATIC
19
+ id_counter = Struct.new(:nodes, :edges, :attributes)
20
+
21
+ raise ArgumentError.new "Invalid defaultedgetype: '#{edgetype}'" unless EDGETYPES.include?(edgetype)
22
+ raise ArgumentError.new "Invalid idtype: '#{idtype}'" unless IDTYPES.include?(idtype)
23
+ raise ArgumentError.new "Invalid mode: '#{mode}'" unless MODES.include?(mode)
24
+
25
+ @defaultedgetype = edgetype
26
+ @idtype = idtype
27
+ @mode = mode
28
+ @id_counter = id_counter.new(0,0,0)
29
+ @attributes = {}
30
+
31
+ @nodes = GEXF::NodeSet.new
32
+ @edges = GEXF::EdgeSet.new
33
+ end
34
+
35
+ def defined_attributes
36
+ nodes.defined_attributes.merge(edges.defined_attributes)
37
+ end
38
+
39
+ def define_node_attribute(title, opts={})
40
+ id = assign_id(:attributes, opts.delete(:id)).to_s
41
+ @nodes.define_attribute(id, title, opts)
42
+ end
43
+
44
+ def define_edge_attribute(title, opts={})
45
+ id = assign_id(:attributes, opts.delete(:id)).to_s
46
+ @edges.define_attribute(id, title, opts)
47
+ end
48
+
49
+ def create_node(opts={})
50
+ id = assign_id(:nodes, opts.delete(:id))
51
+ @nodes << node = GEXF::Node.new(id, self, opts)
52
+ node
53
+ end
54
+
55
+ def create_edge(source, target, opts={})
56
+ opts[:type] ||= defaultedgetype
57
+ id = assign_id(:edges, opts.delete(:id))
58
+ @edges << edge = GEXF::Edge.new(id, source.id, target.id, opts.merge(:graph => self))
59
+ edge
60
+ end
61
+
62
+ def attribute_definitions
63
+ @attributes.dup
64
+ end
65
+
66
+ private
67
+ def assign_id(counter_name, id=nil)
68
+ auto_id = @id_counter.send(counter_name) + 1
69
+
70
+ if !id
71
+ id = auto_id
72
+ @id_counter.send("#{counter_name}=", id)
73
+ end
74
+
75
+ cast_id(id)
76
+ end
77
+
78
+ def cast_id(id)
79
+ @idtype == INTEGER ? id.to_i : id.to_s
80
+ end
81
+
82
+ end