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.
- checksums.yaml +15 -0
- data/lib/karasuba.rb +35 -0
- data/lib/karasuba/footer_appender.rb +62 -0
- data/lib/karasuba/link.rb +11 -0
- data/lib/karasuba/link_appender.rb +22 -0
- data/lib/karasuba/note.rb +40 -0
- data/lib/karasuba/parser.rb +89 -0
- data/lib/karasuba/todo.rb +119 -0
- data/lib/karasuba/version.rb +3 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -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=
|
data/lib/karasuba.rb
ADDED
@@ -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,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
|
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:
|