karasuba 0.0.3

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