karasuba 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZGM4NWZiMzg5MzVmYzRhN2M2NTNmNzc3ZDVlNjk4Njg1NzU5ZDNhZA==
5
+ data.tar.gz: !binary |-
6
+ ZDc4OTlmYWEwNjExNjFmMTljNjk1ZGIzOGViNzUyZjcwZjNkZTU0ZQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MzJhZmFiMzI4MjQ2NjQ0NTQ1ZjdhYWYyNGI3ZmE1ZTI5NjkyOGI5ODAzYTkx
10
+ MDJhYWIyZWUwZTRjNDA1MWRhMDRiYTZjNmEwYThjYjJlY2QxMjQxNGIzNmRk
11
+ NGE0OGEwMzIzODFiNmNlYmM2M2UyNTgxNjk4NDk0MzYyNWZmZTU=
12
+ data.tar.gz: !binary |-
13
+ NDU5Mzg1Yjg0YjEzNzg5NzNkNzkyYTc0YjVjZTczMGZkNDU3MDI3NTM0MGM5
14
+ NGE0ZmFlZmM1YWE2MTJmZTZjNjBjOGJjZjNiZTlmMTYwMWE0ZjA1NmM1ZGNh
15
+ MGU0NGM0ZDFkMDlhMmMzZjE2MTA1YmE2N2MyMmJjZjI5ZmFmMjE=
@@ -0,0 +1,35 @@
1
+ require 'nokogiri'
2
+ require 'equivalent-xml'
3
+
4
+ require 'karasuba/version'
5
+ require 'karasuba/todo'
6
+ require 'karasuba/link'
7
+ require 'karasuba/link_appender'
8
+ require 'karasuba/footer_appender'
9
+ require 'karasuba/parser'
10
+ require 'karasuba/note'
11
+
12
+ class Karasuba
13
+ attr_reader :xml, :note
14
+
15
+ # @attr note_or_string [String/#content]
16
+ # @options { ignore_link: { href: [String/Regexp], content: [String/Regexp] },
17
+ # stop_link: { href: [String/Regexp], content: [String/Regexp] } }
18
+ def initialize(note_or_string, options = {})
19
+ @xml = if note_or_string.respond_to?(:content)
20
+ Nokogiri.parse(note_or_string.content)
21
+ else
22
+ Nokogiri.parse(note_or_string)
23
+ end
24
+ @note = Note.new(xml, options)
25
+ end
26
+
27
+ def equivalent?(doc_or_string)
28
+ note.equivalent?(doc_or_string)
29
+ end
30
+
31
+ def todos
32
+ note.todos
33
+ end
34
+ end
35
+
@@ -0,0 +1,62 @@
1
+ class Karasuba
2
+ # A footer should look like this :
3
+ # <div title="a-footer-id">
4
+ # </br>
5
+ # <hr style="margin: 15px 0px;"/>
6
+ # <p display="color:#878888;">
7
+ # Lorem ipsum<br/>
8
+ # Lauren hipsum
9
+ # </p>
10
+ # </div>
11
+ class FooterAppender
12
+ attr_reader :append_point
13
+
14
+ def initialize(append_point)
15
+ @append_point = append_point
16
+ end
17
+
18
+ def append_footer(xml_text = '', options = {})
19
+ hr = horizontal_rule(options)
20
+ p = paragraph(text_nodes(xml_text), options)
21
+ div = division(hr, p, options)
22
+ append_point.next = div
23
+ div
24
+ end
25
+
26
+ def document
27
+ append_point.document
28
+ end
29
+
30
+ private
31
+
32
+ def break_element(options = {})
33
+ Nokogiri::XML::Node.new('br', document)
34
+ end
35
+
36
+ def text_nodes(xml_text)
37
+ Nokogiri::XML::DocumentFragment.parse(xml_text).children
38
+ end
39
+
40
+ def paragraph(nodes, options)
41
+ p = Nokogiri::XML::Node.new('p', document)
42
+ p['style'] = options[:style][:paragraph_style] if options[:style]
43
+ p.children = Nokogiri::XML::NodeSet.new(document, nodes)
44
+ p
45
+ end
46
+
47
+ def horizontal_rule(options)
48
+ hr = Nokogiri::XML::Node.new('hr', document)
49
+ hr['style'] = options[:style][:horizontal_rule_style] if options[:style]
50
+ hr
51
+ end
52
+
53
+ def division(hr, p, options)
54
+ div = Nokogiri::XML::Node.new('div', document)
55
+ div['title'] = options[:title] if options[:title]
56
+ div['style'] = options[:style][:division_style] if options[:style]
57
+ br = break_element
58
+ div.children = Nokogiri::XML::NodeSet.new(document, [br, hr, p])
59
+ div
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,11 @@
1
+ class Karasuba
2
+ class Link
3
+ attr_accessor :element, :href, :text
4
+
5
+ def initialize(element, href = '', text = '')
6
+ @element = element
7
+ @href = href
8
+ @text = text
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ class Karasuba
2
+ class LinkAppender
3
+ attr_reader :append_point
4
+
5
+ def initialize(append_point)
6
+ @append_point = append_point
7
+ end
8
+
9
+ def append_link(href, text = '', options = {})
10
+ node = Nokogiri::XML::Node.new('a', document)
11
+ node['href'] = href
12
+ node['style'] = options[:style] if options[:style]
13
+ node['title'] = options[:title] if options[:title]
14
+ node.content = text
15
+ append_point.next = node
16
+ end
17
+
18
+ def document
19
+ append_point.document
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,40 @@
1
+ class Karasuba
2
+ class Note
3
+ attr_reader :xml, :todos, :options
4
+
5
+ def initialize(xml, options = {})
6
+ @xml = xml
7
+ @options = options
8
+ @todos = []
9
+ @todo_elements = xml.xpath('//en-todo')
10
+ instanciate_todos
11
+ end
12
+
13
+ def append_footer(text, options = {})
14
+ appender = FooterAppender.new(append_footer_point)
15
+ appender.append_footer(text, options)
16
+ end
17
+
18
+ def equivalent?(doc_or_string)
19
+ doc_or_string = Nokogiri.parse(doc_or_string) if doc_or_string.is_a?(String)
20
+ EquivalentXml.equivalent?(self.xml, doc_or_string, element_order: true, normalize_whitespace: true)
21
+ end
22
+
23
+ def footer?(title)
24
+ !!self.xml.xpath("//*[attribute::title=\"#{title}\"]").last
25
+ end
26
+
27
+ private
28
+
29
+ def instanciate_todos
30
+ @todos = @todo_elements.map { |todo| Parser.new(todo, options).parse }
31
+ end
32
+
33
+ def append_footer_point
34
+ # The second part is a hack, it adds a div to the en-note body
35
+ # in order to have an append point
36
+ self.xml.xpath('//en-note/child::*[position()=last()]').first ||
37
+ ( self.xml.xpath('//en-note').first.children = Nokogiri::XML::Node.new('div', xml))
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,89 @@
1
+ class Karasuba
2
+ class Parser
3
+ STOPPING_ELEMENTS = ['en-todo', 'br', 'en-note']
4
+ IGNORED_TEXT_ELEMENTS = ['img', 'map', 'table']
5
+
6
+ attr_reader :todo
7
+
8
+ def initialize(todo_or_element, options = {})
9
+ @todo = todo_or_element.is_a?(Todo) ? todo_or_element : Todo.new(todo_or_element, '', [], [], options)
10
+ @ignore_link = options[:ignore_link]
11
+ @stop_link = options[:stop_link]
12
+ end
13
+
14
+ def parse
15
+ cursor = todo.element
16
+ while(cursor = next_element(cursor))
17
+ stop_todo(cursor) && break if stopping_element?(cursor)
18
+ todo.following_siblings << cursor
19
+ if ignore_element?(cursor)
20
+ @ignore_children = true
21
+ else
22
+ if cursor.text?
23
+ todo.text_sibblings << cursor
24
+ todo.title << cursor.text
25
+ end
26
+ end
27
+ end
28
+ todo
29
+ end
30
+
31
+ def stop_todo(cursor)
32
+ todo.stopped_by_link = stop_link?(cursor)
33
+ todo.stopping_sibling = cursor
34
+ end
35
+
36
+ def stopping_element?(el)
37
+ STOPPING_ELEMENTS.include?(el.name) || stop_link?(el)
38
+ end
39
+
40
+ def ignore_element?(el)
41
+ IGNORED_TEXT_ELEMENTS.include?(el.name) || ignore_link?(el)
42
+ end
43
+
44
+ def ignore_link?(el)
45
+ return false unless @ignore_link
46
+ match_href(el, @ignore_link) && match_content(el, @ignore_link)
47
+ end
48
+
49
+ def stop_link?(el)
50
+ return false unless @stop_link
51
+ match_href(el, @stop_link) && match_content(el, @stop_link)
52
+ end
53
+
54
+ def match_href(el, link_options)
55
+ return true unless link_options[:href]
56
+ if link_options[:href].is_a?(Regexp)
57
+ link_options[:href].match(el['href'])
58
+ else
59
+ link_options[:href] == el['href']
60
+ end
61
+ end
62
+
63
+ def match_content(el, link_options)
64
+ return true unless link_options[:content]
65
+ if link_options[:content].is_a?(Regexp)
66
+ link_options[:content].match(el.content)
67
+ else
68
+ link_options[:content] == el.content
69
+ end
70
+ end
71
+
72
+ def next_element(cursor)
73
+ if @ignore_children
74
+ @ignore_children = false
75
+ following_element(cursor)
76
+ else
77
+ cursor.children.first || following_element(cursor)
78
+ end
79
+ end
80
+
81
+ def following_element(cursor)
82
+ cursor.next || (following_element(cursor.parent) unless is_root?(cursor.parent))
83
+ end
84
+
85
+ def is_root?(element)
86
+ element.name == 'en-note'
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,119 @@
1
+ class Karasuba
2
+ class Todo
3
+ attr_accessor :element, :title, :following_siblings,
4
+ :stopping_sibling, :text_sibblings, :stopped_by_link, :options
5
+
6
+ def initialize(element, title = '', following_siblings = [], text_sibblings = [], options = {})
7
+ @element = element
8
+ @title = title
9
+ @following_siblings = following_siblings
10
+ @stopping_sibling = nil
11
+ @stopping_link = nil
12
+ @stopped_by_link = false
13
+ @text_sibblings = text_sibblings
14
+ @options = options
15
+ end
16
+
17
+ def update_title(string)
18
+ @title = string
19
+ self.text_sibblings.each(&:remove)
20
+ clean_links
21
+ el = title_element(string)
22
+ self.element.next = el
23
+ reset
24
+ parse
25
+ title
26
+ end
27
+
28
+ def checked=(bool)
29
+ self.element["checked"] = (!!bool).to_s
30
+ end
31
+
32
+ def checked
33
+ self.element["checked"] == "true"
34
+ end
35
+ alias :checked? :checked
36
+
37
+ def links(href = nil)
38
+ regex = Regexp.new(href) if href
39
+ self.following_siblings.inject([]) do |ary, s|
40
+ match = if href
41
+ s.name == 'a' && regex.match(s['href'])
42
+ else
43
+ s.name == 'a'
44
+ end
45
+ next ary unless match
46
+ ary << Link.new(s, s['href'], s.text)
47
+ end
48
+ end
49
+
50
+ def remove_links(href = nil)
51
+ links(href).each { |link| link.element.remove }
52
+ reset
53
+ parse
54
+ links(href)
55
+ end
56
+
57
+ def clean_links(href = nil)
58
+ regex = Regexp.new(href) if href
59
+ links(href).map do |link|
60
+ match = if href
61
+ regex.match(link.href)
62
+ else
63
+ true
64
+ end
65
+ next unless match
66
+ if link.element.xpath('.//text()').empty?
67
+ link.element.remove
68
+ end
69
+ end.compact
70
+ reset
71
+ parse
72
+ links(href)
73
+ end
74
+
75
+ def linked?(href = nil)
76
+ !links(href).empty?
77
+ end
78
+
79
+ def stopped_by_link?
80
+ @stopped_by_link
81
+ end
82
+
83
+ def stopping_link
84
+ return nil unless stopped_by_link?
85
+ Link.new(stopping_sibling, stopping_sibling['href'], stopping_sibling.text)
86
+ end
87
+
88
+ def append_link(href, text = '', options = {})
89
+ appender = LinkAppender.new(append_link_point)
90
+ appender.append_link(href, text, options)
91
+ reset
92
+ parse
93
+ self
94
+ end
95
+
96
+ private
97
+
98
+ def reset
99
+ @title = ''
100
+ @following_siblings = []
101
+ @stopping_sibling = nil
102
+ @text_sibblings = []
103
+ @stopping_link = nil
104
+ @stopped_by_link = false
105
+ end
106
+
107
+ def parse
108
+ Parser.new(self, options).parse
109
+ end
110
+
111
+ def append_link_point
112
+ self.text_sibblings.reverse.find { |s| !s.blank? } || self.element
113
+ end
114
+
115
+ def title_element(string)
116
+ Nokogiri::XML::Text.new(string, self.element.document)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,3 @@
1
+ class Karasuba
2
+ VERSION = '0.0.3'
3
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: karasuba
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - antoinelyset
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>'
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>'
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: equivalent-xml
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '0.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '0.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>'
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>'
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: ENML Parser
70
+ email:
71
+ - antoinelyset@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - lib/karasuba.rb
77
+ - lib/karasuba/footer_appender.rb
78
+ - lib/karasuba/link.rb
79
+ - lib/karasuba/link_appender.rb
80
+ - lib/karasuba/note.rb
81
+ - lib/karasuba/parser.rb
82
+ - lib/karasuba/todo.rb
83
+ - lib/karasuba/version.rb
84
+ homepage: https://github.com/antoinelyset/karasuba
85
+ licenses: []
86
+ metadata: {}
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 2.2.2
104
+ signing_key:
105
+ specification_version: 4
106
+ summary: Evernote is using a subset of XML, called ENML. Karabusa is a parser, a friendly
107
+ reference to Nokogiri
108
+ test_files: []
109
+ has_rdoc: