gexf 0.0.2

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.
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