jm81-dm-filters 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.md +42 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/dm-filters.rb +141 -0
- data/lib/filters/bible_ml.rb +244 -0
- data/lib/filters/linebreaker.rb +14 -0
- data/spec/dm-filters_spec.rb +43 -0
- data/spec/filters/bible_ml_spec.rb +106 -0
- data/spec/filters/fixtures/gen11/input.xml +16 -0
- data/spec/filters/fixtures/gen11/output.xml +27 -0
- data/spec/filters/linebreaker_spec.rb +11 -0
- data/spec/filters_resource_spec.rb +29 -0
- data/spec/spec_helper.rb +9 -0
- metadata +82 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Jared Morgan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
dm-filters
|
2
|
+
==========
|
3
|
+
|
4
|
+
This module enables a property in a DataMapper::Resource class to be filtered on
|
5
|
+
save into another property, using per-row and/or per-property filters.
|
6
|
+
|
7
|
+
To use in a model:
|
8
|
+
|
9
|
+
include Filters::Resource
|
10
|
+
|
11
|
+
The syntax when defining a property is:
|
12
|
+
|
13
|
+
property :prop_name, :filter => {
|
14
|
+
:to => :filtered_prop,
|
15
|
+
:with => :filter_column,
|
16
|
+
:default => "DefaultFilter"
|
17
|
+
}
|
18
|
+
|
19
|
+
(:with and :default are optional, though at least one should be specified.)
|
20
|
+
|
21
|
+
See Filters::AVAILABLE_FILTERS for some filter options. Additional filters
|
22
|
+
may be defined in this constant Hash.
|
23
|
+
|
24
|
+
If the properties in :to and :with have not yet been defined, they will be
|
25
|
+
defined automatically. Hence, if want to specify any options with this, they
|
26
|
+
should be defined before to filtered property.
|
27
|
+
|
28
|
+
##Installation
|
29
|
+
|
30
|
+
To install the gem:
|
31
|
+
|
32
|
+
gem sources -a http://gems.github.com
|
33
|
+
sudo gem install jm81-dm-filters
|
34
|
+
|
35
|
+
To require:
|
36
|
+
|
37
|
+
gem 'jm81-dm-filters'
|
38
|
+
require 'dm-filters'
|
39
|
+
|
40
|
+
##Copyright
|
41
|
+
|
42
|
+
Copyright (c) 2009 Jared Morgan. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "dm-filters"
|
8
|
+
gem.summary = %Q{TODO}
|
9
|
+
gem.email = "jmorgan@morgancreative.net"
|
10
|
+
gem.homepage = "http://github.com/jm81/dm-filters"
|
11
|
+
gem.authors = ["Jared Morgan"]
|
12
|
+
gem.add_dependency('dm-core')
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'spec/rake/spectask'
|
21
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
22
|
+
spec.libs << 'lib' << 'spec'
|
23
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
24
|
+
end
|
25
|
+
|
26
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
27
|
+
spec.libs << 'lib' << 'spec'
|
28
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
29
|
+
spec.rcov = true
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
task :default => :spec
|
34
|
+
|
35
|
+
require 'rake/rdoctask'
|
36
|
+
Rake::RDocTask.new do |rdoc|
|
37
|
+
if File.exist?('VERSION.yml')
|
38
|
+
config = YAML.load(File.read('VERSION.yml'))
|
39
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
40
|
+
else
|
41
|
+
version = ""
|
42
|
+
end
|
43
|
+
|
44
|
+
rdoc.rdoc_dir = 'rdoc'
|
45
|
+
rdoc.title = "dm-filters #{version}"
|
46
|
+
rdoc.rdoc_files.include('README*')
|
47
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
48
|
+
end
|
49
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/dm-filters.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
# This module enables a property to be filtered on save into another property,
|
4
|
+
# using per-row and/or per-property filters. The syntax when defining a property
|
5
|
+
# is:
|
6
|
+
# property :prop_name, :filter => {:to => :filtered_prop, :with => :filter_column, :default => "DefaultFilter"}
|
7
|
+
# (:with and :default are optional, though at least one should be specified.)
|
8
|
+
#
|
9
|
+
# If the properties in :to and :with have not yet been defined, they will be
|
10
|
+
# defined automatically. Hence, if want to specify any options with this, they
|
11
|
+
# should be defined before to filtered property.
|
12
|
+
|
13
|
+
module Filters
|
14
|
+
|
15
|
+
# A hash, with each entry of the form:
|
16
|
+
# Filter name (used in +filters+ property) =>
|
17
|
+
# Array of two elements Arrays representing classes that can process this
|
18
|
+
# filter. Elements are: [ argument_for_require, class_name ].
|
19
|
+
# Classes are assumed to respond to +to_html+.
|
20
|
+
AVAILABLE_FILTERS = {
|
21
|
+
'Smartypants' => [['rubypants', 'RubyPants']],
|
22
|
+
'Markdown' => [['rdiscount', 'RDiscount'], ['bluecloth', 'BlueCloth']],
|
23
|
+
'Textile' => [['redcloth', 'RedCloth']],
|
24
|
+
'BibleML' => [[File.dirname(__FILE__) + '/filters/bible_ml', 'BibleML']],
|
25
|
+
'Linebreaker' => [[File.dirname(__FILE__) + '/filters/linebreaker', 'Linebreaker']],
|
26
|
+
}
|
27
|
+
|
28
|
+
def self.process(filters, content)
|
29
|
+
return content if filters.nil?
|
30
|
+
|
31
|
+
filters = filters.split(';') if filters.kind_of?(String)
|
32
|
+
filters.each do |name|
|
33
|
+
filter = get_filter(name)
|
34
|
+
next unless filter
|
35
|
+
content = filter.new(content).to_html
|
36
|
+
end
|
37
|
+
content
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.get_filter(name)
|
41
|
+
name = name.strip.camel_case
|
42
|
+
info = AVAILABLE_FILTERS[name]
|
43
|
+
return(nil) unless info
|
44
|
+
|
45
|
+
# Try to find loaded class
|
46
|
+
info.each do |c|
|
47
|
+
return Object.const_get(c[1]) if Object.const_defined?(c[1])
|
48
|
+
end
|
49
|
+
|
50
|
+
# Try to require a class
|
51
|
+
info.each do |c|
|
52
|
+
begin
|
53
|
+
require(c[0])
|
54
|
+
return Object.const_get(c[1])
|
55
|
+
rescue LoadError
|
56
|
+
# Try next
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# Filters::Resource can be included in a model to enable the
|
64
|
+
# :filtered_to => (name of other property) option for +properties+.
|
65
|
+
# This adds a property name "filters", which is a semi-colon delimited list
|
66
|
+
# of filters through which to process the original property.
|
67
|
+
module Resource
|
68
|
+
class << self
|
69
|
+
def included(klass) # Set a few 'magic' properties
|
70
|
+
klass.extend(ClassMethods)
|
71
|
+
klass.before :save, :process_filters
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Process and filters for @filtered_properties.
|
76
|
+
def process_filters
|
77
|
+
return if self.class.filtered_properties.nil?
|
78
|
+
self.class.filtered_properties.each do |f|
|
79
|
+
if attribute_dirty?(f[:name])
|
80
|
+
filters = nil
|
81
|
+
if !f[:with].blank?
|
82
|
+
if f[:with].kind_of?(Symbol)
|
83
|
+
filters = self.__send__(f[:with])
|
84
|
+
else
|
85
|
+
filters = f[:with]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
if filters.blank?
|
89
|
+
filters = f[:default]
|
90
|
+
end
|
91
|
+
if filters == :site
|
92
|
+
filters = self.site.__send__("#{self.class.name.snake_case}_filter")
|
93
|
+
end
|
94
|
+
attribute_set(f[:to], Filters.process(filters, self.__send__(f[:name])))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
module ClassMethods
|
100
|
+
# Override DataMapper's +property+ class method to accept as an option
|
101
|
+
# +filter+. +filter+ Hash with the following pairs:
|
102
|
+
# - +:to+ - Name of property to filter to; this should not be explicitly
|
103
|
+
# declared as a property.
|
104
|
+
# - +:with+ - Either 1) Name of the property (as a symbol) that designates
|
105
|
+
# filter (does not need to be explicitly declared as a property) or
|
106
|
+
# 2) A semi-colon delimited String represented filters to use (or an
|
107
|
+
# Array of strings).
|
108
|
+
# - +:default+ - A semi-colon delimited String represented filters to use
|
109
|
+
# (or an Array of strings) if the filter column is blank.
|
110
|
+
def property(name, type, options = {})
|
111
|
+
if filter = options.delete(:filter)
|
112
|
+
@filtered_properties ||= []
|
113
|
+
@filtered_properties << filter.merge({:name => name})
|
114
|
+
begin
|
115
|
+
self.properties[filter[:to].to_s]
|
116
|
+
rescue
|
117
|
+
self.property(filter[:to], type)
|
118
|
+
end
|
119
|
+
if filter[:with].kind_of?(Symbol)
|
120
|
+
begin
|
121
|
+
self.properties[filter[:with].to_s]
|
122
|
+
rescue
|
123
|
+
self.property(filter[:with], String)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
super(name, type, options)
|
129
|
+
end
|
130
|
+
|
131
|
+
def filtered_properties
|
132
|
+
begin
|
133
|
+
# This is to work with STI models. It's not a very good solution.
|
134
|
+
@filtered_properties || self.superclass.filtered_properties
|
135
|
+
rescue
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'rubypants'
|
4
|
+
require 'rdiscount'
|
5
|
+
|
6
|
+
class BibleML
|
7
|
+
|
8
|
+
VERSION_IDS = {
|
9
|
+
'cev' => 46,
|
10
|
+
'niv' => 31,
|
11
|
+
'nasb' => 49,
|
12
|
+
'message' => 65,
|
13
|
+
'amp' => 45,
|
14
|
+
'nlt' => 51,
|
15
|
+
'21st century kjv' => 48,
|
16
|
+
'asv' => 8,
|
17
|
+
'darby' => 16,
|
18
|
+
'douay-rheims' => 63,
|
19
|
+
'esv' => 47,
|
20
|
+
'holman' => 77,
|
21
|
+
'kjv' => 9,
|
22
|
+
'ncv' => 78,
|
23
|
+
'nkjv' => 50,
|
24
|
+
'new life' => 74,
|
25
|
+
'wycliffe' => 53,
|
26
|
+
"young" => 15
|
27
|
+
}
|
28
|
+
|
29
|
+
def initialize(input)
|
30
|
+
@xml = REXML::Document.new("<fg:body xmlns:fg='http://fromgenesis.org'>#{input}</fg:body>")
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_html
|
34
|
+
txt = self.
|
35
|
+
primary_passage.
|
36
|
+
strip_comments.
|
37
|
+
block_quotes.
|
38
|
+
inline_quotes.
|
39
|
+
wikipedia_links.
|
40
|
+
to_s
|
41
|
+
txt = RubyPants.new(txt).to_html
|
42
|
+
txt = RDiscount.new(txt).to_html
|
43
|
+
end
|
44
|
+
|
45
|
+
# Convert fg:pp tags to Primary Passage links
|
46
|
+
# and set primary passage book and chapter from
|
47
|
+
# last fg:pp tag.
|
48
|
+
def primary_passage
|
49
|
+
@xml.elements.each("//fg:pp") do | pp_el |
|
50
|
+
ref = pp_el.attributes['p']
|
51
|
+
ref_link = REXML::Element.new("a")
|
52
|
+
ref_link.add_attribute('href', bg_link(ref))
|
53
|
+
ref_link.text = "Read #{ref}"
|
54
|
+
pp_el.parent.insert_after(pp_el, ref_link)
|
55
|
+
|
56
|
+
marker = REXML::Text.new(" |\n")
|
57
|
+
pp_el.parent.insert_after(ref_link, marker)
|
58
|
+
|
59
|
+
chapter = pp_el.attributes['p'].split(":")[0]
|
60
|
+
chapter_link = REXML::Element.new("a")
|
61
|
+
chapter_link.add_attribute('href', bg_link(chapter))
|
62
|
+
chapter_link.text = "Full Chapter"
|
63
|
+
pp_el.parent.insert_after(marker, chapter_link)
|
64
|
+
|
65
|
+
pp_el.parent.delete_element(pp_el)
|
66
|
+
|
67
|
+
m = chapter.match(/(.*)\s(\d+)\Z/)
|
68
|
+
@passage_book = m[1]
|
69
|
+
@passage_chapter = m[2]
|
70
|
+
end
|
71
|
+
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
# Strip fg:cm tags
|
76
|
+
def strip_comments
|
77
|
+
@xml = REXML::Document.new(@xml.to_s.gsub(/\<\/?fg\:cm([^>]*)>/, ''))
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
# Convent fg:bq tags to block quotes with reference
|
82
|
+
def block_quotes
|
83
|
+
@xml.elements.each("//fg:bq") do | bq_el |
|
84
|
+
blockquote = REXML::Element.new("blockquote")
|
85
|
+
|
86
|
+
if bq_el.has_elements? || bq_el.has_text?
|
87
|
+
bq_el.children.each do | child |
|
88
|
+
blockquote << child
|
89
|
+
end
|
90
|
+
else
|
91
|
+
blockquote << REXML::Text.new(get_passage(bq_el.attributes['p'], bq_el.attributes['v']))
|
92
|
+
end
|
93
|
+
|
94
|
+
blockquote << REXML::Text.new("\n")
|
95
|
+
blockquote << REXML::Element.new("br")
|
96
|
+
blockquote << REXML::Text.new("\n(")
|
97
|
+
blockquote << reference_a(bq_el.attributes['p'], bq_el.attributes['v'])
|
98
|
+
blockquote << REXML::Text.new(")\n")
|
99
|
+
|
100
|
+
bq_el.parent.replace_child(bq_el, blockquote)
|
101
|
+
end
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
# Convent fg:iq tags to inline quotes with reference
|
106
|
+
def inline_quotes
|
107
|
+
@xml.elements.each("//fg:iq") do | iq_el |
|
108
|
+
quote = REXML::Element.new("span")
|
109
|
+
|
110
|
+
quote << REXML::Text.new("\"")
|
111
|
+
|
112
|
+
if iq_el.has_elements? || iq_el.has_text?
|
113
|
+
iq_el.children.each do | child |
|
114
|
+
quote << child
|
115
|
+
end
|
116
|
+
else
|
117
|
+
quote << REXML::Text.new(get_passage(iq_el.attributes['p'], iq_el.attributes['v']))
|
118
|
+
end
|
119
|
+
|
120
|
+
quote << REXML::Text.new("\" (")
|
121
|
+
quote << reference_a(iq_el.attributes['p'], iq_el.attributes['v'])
|
122
|
+
quote << REXML::Text.new(")\n")
|
123
|
+
|
124
|
+
iq_el.parent.replace_child(iq_el, quote)
|
125
|
+
end
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
def wikipedia_links
|
130
|
+
@xml.elements.each("//fg:wp") do | wp_el |
|
131
|
+
a = REXML::Element.new("a")
|
132
|
+
|
133
|
+
a.add_attribute('href', "http://en.wikipedia.org/wiki/#{wp_el.attributes['a']}")
|
134
|
+
if wp_el.has_elements? || wp_el.has_text?
|
135
|
+
wp_el.children.each do | child |
|
136
|
+
a << child
|
137
|
+
end
|
138
|
+
else
|
139
|
+
a << REXML::Text.new(wp_el.attributes['a'])
|
140
|
+
end
|
141
|
+
|
142
|
+
wp_el.parent.replace_child(wp_el, a)
|
143
|
+
end
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
def fill_missing_text
|
148
|
+
@xml.elements.each("//fg:pp") do | pp_el |
|
149
|
+
chapter = pp_el.attributes['p'].split(":")[0]
|
150
|
+
m = chapter.match(/(.*)\s(\d+)\Z/)
|
151
|
+
@passage_book = m[1]
|
152
|
+
@passage_chapter = m[2]
|
153
|
+
end
|
154
|
+
|
155
|
+
@xml.elements.each("//fg:iq") do | q_el |
|
156
|
+
unless q_el.has_elements? || q_el.has_text?
|
157
|
+
q_el << REXML::Text.new(get_passage(q_el.attributes['p'], q_el.attributes['v']))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
@xml.elements.each("//fg:bq") do | q_el |
|
162
|
+
unless q_el.has_elements? || q_el.has_text?
|
163
|
+
psg = get_passage(q_el.attributes['p'], q_el.attributes['v'])
|
164
|
+
psg = wordwrap(" " + psg) + "\n"
|
165
|
+
q_el << REXML::Text.new(psg)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
self
|
169
|
+
end
|
170
|
+
|
171
|
+
# Based on http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/10655
|
172
|
+
def wordwrap(txt)
|
173
|
+
txt.gsub(/\t/," ").
|
174
|
+
gsub(/\"/,""").
|
175
|
+
gsub(/\'/,"'").
|
176
|
+
gsub(/.{1,78}(?:\s|\Z)/){($& + 5.chr).
|
177
|
+
gsub(/\n\005/,"\n ").
|
178
|
+
gsub(/\005/,"\n ")}.
|
179
|
+
gsub(/"/, "\"").
|
180
|
+
gsub(/'/, "\'")
|
181
|
+
end
|
182
|
+
|
183
|
+
class << self
|
184
|
+
def fill_missing_text(input)
|
185
|
+
new(input).fill_missing_text.to_s
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def to_s
|
190
|
+
@xml.to_s.gsub(/<\/?fg:body[^>]*>/, '')
|
191
|
+
end
|
192
|
+
|
193
|
+
# Add book and chapter information to shorthand references
|
194
|
+
def expand_reference(ref)
|
195
|
+
ref.strip!
|
196
|
+
if ref.match(/\A(\d+-?\d*)\Z/) # Just verse(s) given
|
197
|
+
"#{@passage_book} #{@passage_chapter}:#{ref}"
|
198
|
+
elsif ref.match(/\A(\d+):(\d+-?\d*)\Z/) # Just chapter and verse(s) given
|
199
|
+
"#{@passage_book} #{ref}"
|
200
|
+
else
|
201
|
+
ref
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Generate 'a' element for reference link
|
206
|
+
def reference_a(ref, version = nil, label = nil)
|
207
|
+
ref = expand_reference(ref)
|
208
|
+
|
209
|
+
unless label
|
210
|
+
label = ref
|
211
|
+
label += ", #{version}" if version
|
212
|
+
end
|
213
|
+
|
214
|
+
ref_link = REXML::Element.new("a")
|
215
|
+
ref_link.add_attribute('href', bg_link(ref, version))
|
216
|
+
ref_link.text = label
|
217
|
+
|
218
|
+
ref_link
|
219
|
+
end
|
220
|
+
|
221
|
+
def bg_link(ref, version = nil)
|
222
|
+
ref = expand_reference(ref)
|
223
|
+
|
224
|
+
ln = "http://www.biblegateway.com/passage/?search=" + ref.downcase.gsub(/\s/, "%20")
|
225
|
+
ln += ";&version=#{VERSION_IDS[version.downcase]};" if version
|
226
|
+
ln
|
227
|
+
end
|
228
|
+
|
229
|
+
def get_passage(ref, version = "NASB")
|
230
|
+
xhtml = open(bg_link(ref, version)) do |f|
|
231
|
+
html = f.read.split('<div class="result-text-style-normal">')[1]
|
232
|
+
html = html.split(/<\/?div/)[0]
|
233
|
+
REXML::Document.new("<body>#{html}</body>")
|
234
|
+
end
|
235
|
+
|
236
|
+
xhtml.root.elements.delete_all("//h4")
|
237
|
+
xhtml.root.elements.delete_all("//h5")
|
238
|
+
xhtml.root.elements.delete_all("//span")
|
239
|
+
xhtml.root.elements.delete_all("//sup")
|
240
|
+
xhtml.root.elements.delete_all("//br")
|
241
|
+
|
242
|
+
xhtml.to_s.gsub(/<\/?body>/, '').gsub(/<\/?p\/?>/, '').gsub(' ', ' ').squeeze(" ").gsub(/ \" /, ' "')
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# This filter just replaces single newlines with "<br />" tags.
|
2
|
+
class Linebreaker
|
3
|
+
|
4
|
+
def initialize(input)
|
5
|
+
@input = input
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_html
|
9
|
+
@input.
|
10
|
+
gsub(/(\S)\n(\S)/, "\\1<br />\n\\2").
|
11
|
+
gsub(/(\S)\r\n(\S)/, "\\1<br />\n\\2")
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Filters do
|
4
|
+
describe "#process" do
|
5
|
+
before(:each) do
|
6
|
+
@filter_one = mock("Filter 1")
|
7
|
+
@filter_two = mock("Filter 2")
|
8
|
+
@processor_one, @processor_two = mock("Processor 1"), mock("Processor 2")
|
9
|
+
@processed_one, @processed_two = mock("Processed 1"), mock("Processed 2")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should process through each filter" do
|
13
|
+
Filters.should_receive(:get_filter).with("FilterOne").and_return(@filter_one)
|
14
|
+
@filter_one.should_receive(:new).with("content").and_return(@processor_one)
|
15
|
+
@processor_one.should_receive(:to_html).and_return(@processed_one)
|
16
|
+
Filters.should_receive(:get_filter).with(" FilterTwo").and_return(@filter_two)
|
17
|
+
@filter_two.should_receive(:new).with(@processed_one).and_return(@processor_two)
|
18
|
+
@processor_two.should_receive(:to_html).and_return(@processed_two)
|
19
|
+
Filters.process("FilterOne; FilterTwo", "content").should == @processed_two
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#get_filter" do
|
24
|
+
it "should return nil if name not found in AVAILABLE_FILTERS" do
|
25
|
+
Filters.get_filter("neverafilter").should be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return a filter if it's constant is defined" do
|
29
|
+
red_cloth = mock('RedCloth')
|
30
|
+
Object.should_receive(:const_defined?).with('RedCloth').and_return(true)
|
31
|
+
Object.should_receive(:const_get).with('RedCloth').and_return(red_cloth)
|
32
|
+
Filters.get_filter("Textile").should be(red_cloth)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should attempt to require an undefined filter" do
|
36
|
+
red_cloth = mock('RedCloth')
|
37
|
+
Object.should_receive(:const_defined?).with('RedCloth').and_return(false)
|
38
|
+
Filters.should_receive(:require).with('redcloth').and_return(true)
|
39
|
+
Object.should_receive(:const_get).with('RedCloth').and_return(red_cloth)
|
40
|
+
Filters.get_filter("Textile").should be(red_cloth)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../lib/filters/bible_ml')
|
3
|
+
|
4
|
+
describe BibleML do
|
5
|
+
|
6
|
+
it "should convert a fg:bq element to a block quote" do
|
7
|
+
BibleML.new('<p>1</p><fg:bq p="Exodus 5:1-5" v="ESV">quote <span>hi</span></fg:bq><p>2</p>').
|
8
|
+
block_quotes.
|
9
|
+
to_s.should ==
|
10
|
+
"<p>1</p><blockquote>quote <span>hi</span>\n<br/>\n" +
|
11
|
+
"(<a href='http://www.biblegateway.com/passage/?search=exodus%205:1-5;&version=47;'>Exodus 5:1-5, ESV</a>)" +
|
12
|
+
"\n</blockquote><p>2</p>"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should fill in text of an empty fg:bq element" do
|
16
|
+
BibleML.new('<fg:bq p="Exodus 5:2-3" v="CEV" />').
|
17
|
+
block_quotes.
|
18
|
+
to_s.should ==
|
19
|
+
"<blockquote>\n "Who is this LORD and why should I obey him?" the king replied. "I refuse to let you and your people go!" " +
|
20
|
+
"They answered, "The LORD God of the Hebrews, has appeared to us. Please let us walk three days into the desert " +
|
21
|
+
"where we can offer sacrifices to him. If you don't, he may strike us down with terrible troubles or with war." \n" +
|
22
|
+
"<br/>\n" +
|
23
|
+
"(<a href='http://www.biblegateway.com/passage/?search=exodus%205:2-3;&version=46;'>Exodus 5:2-3, CEV</a>)" +
|
24
|
+
"\n</blockquote>"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should assume book and chapter based on bg:pp if ommitted" do
|
28
|
+
convertor = BibleML.new('<fg:pp p="Exodus 5:1-5" />').primary_passage
|
29
|
+
convertor.expand_reference("7").should == "Exodus 5:7"
|
30
|
+
convertor.expand_reference("7-10").should == "Exodus 5:7-10"
|
31
|
+
convertor.expand_reference("6:7").should == "Exodus 6:7"
|
32
|
+
convertor.expand_reference("6:7-10").should == "Exodus 6:7-10"
|
33
|
+
convertor.expand_reference("John 6:7").should == "John 6:7"
|
34
|
+
convertor.expand_reference("John 6:7-10").should == "John 6:7-10"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should convert a fg:iq element to an inline quote" do
|
38
|
+
BibleML.new('<p>this is a <fg:iq p="1 Peter 1:2" v="Young">inline quote</fg:iq></p>').
|
39
|
+
inline_quotes.
|
40
|
+
to_s.should ==
|
41
|
+
"<p>this is a <span>"inline quote" " +
|
42
|
+
"(<a href='http://www.biblegateway.com/passage/?search=1%20peter%201:2;&version=15;'>1 Peter 1:2, Young</a>)" +
|
43
|
+
"\n</span></p>"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should fill in text of an empty fg:iq element" do
|
47
|
+
BibleML.new('<p><fg:iq p="1 Peter 1:2" v="Young" /></p>').
|
48
|
+
inline_quotes.
|
49
|
+
to_s.should ==
|
50
|
+
"<p><span>"\n according to a foreknowledge of God the Father, in sanctification of the Spirit, " +
|
51
|
+
"to obedience and sprinkling of the blood of Jesus Christ: Grace to you and peace be multiplied! " " +
|
52
|
+
"(<a href='http://www.biblegateway.com/passage/?search=1%20peter%201:2;&version=15;'>1 Peter 1:2, Young</a>)" +
|
53
|
+
"\n</span></p>"
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should strip fg:cm tags" do
|
57
|
+
BibleML.new('test <fg:cm p="Genesis 11:10-11">comment</fg:cm> test2').
|
58
|
+
strip_comments.
|
59
|
+
to_s.should ==
|
60
|
+
"test comment test2"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should convert fg:pp tag to Primary Passage links" do
|
64
|
+
BibleML.new('<fg:pp p="Exodus 5:1-5" />').
|
65
|
+
primary_passage.
|
66
|
+
to_s.should ==
|
67
|
+
"<a href='http://www.biblegateway.com/passage/?search=exodus%205:1-5'>Read Exodus 5:1-5</a> |\n" +
|
68
|
+
"<a href='http://www.biblegateway.com/passage/?search=exodus%205'>Full Chapter</a>"
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should create link from reference with verses" do
|
72
|
+
BibleML.new('').bg_link("Genesis 1:10").should ==
|
73
|
+
"http://www.biblegateway.com/passage/?search=genesis%201:10"
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should create link from reference without verses" do
|
77
|
+
BibleML.new('').bg_link("Genesis 1").should ==
|
78
|
+
"http://www.biblegateway.com/passage/?search=genesis%201"
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should convert version names to biblegateway.com ids" do
|
82
|
+
BibleML::VERSION_IDS['niv'].should == 31
|
83
|
+
BibleML::VERSION_IDS['cev'].should == 46
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should convert an fg:wp element to a wikipedia link" do
|
87
|
+
BibleML.new('<fg:wp a="Bible">the Bible</fg:wp>').
|
88
|
+
wikipedia_links.
|
89
|
+
to_s.should ==
|
90
|
+
"<a href='http://en.wikipedia.org/wiki/Bible'>the Bible</a>"
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should convert fixture (full document)" do
|
94
|
+
input = File.read(File.dirname(__FILE__) + "/fixtures/gen11/input.xml")
|
95
|
+
output = File.read(File.dirname(__FILE__) + "/fixtures/gen11/output.xml")
|
96
|
+
output.gsub!("\r\n", "\n") # Just in case I run specs in git repo in Windows
|
97
|
+
BibleML.new(input).to_html.should == output
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should convert only missing text" do
|
101
|
+
BibleML::fill_missing_text('<p><fg:iq p="1 Peter 1:2" v="Young" /></p>').
|
102
|
+
should ==
|
103
|
+
"<p><fg:iq v='Young' p='1 Peter 1:2'>\n according to a foreknowledge of God the Father, in sanctification of the Spirit, " +
|
104
|
+
"to obedience and sprinkling of the blood of Jesus Christ: Grace to you and peace be multiplied! </fg:iq></p>"
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<fg:pp p="Genesis 11:10-20" />
|
2
|
+
|
3
|
+
<fg:bq p="Genesis 11:10-14" v="NASB" />
|
4
|
+
|
5
|
+
<fg:cm p="Genesis 11:10-11">This passage tells us something.</fg:cm>
|
6
|
+
<fg:cm p="Genesis 11:12-14">Now these verses elaborate</fg:cm>
|
7
|
+
|
8
|
+
<fg:bq p="Genesis 11:15-20" v="CEV">
|
9
|
+
Already quoted.
|
10
|
+
</fg:bq>
|
11
|
+
|
12
|
+
<fg:cm p="Genesis 11:15-18">
|
13
|
+
This comment <fg:iq p="Genesis 11:20" v="NIV">quotes a verse</fg:iq>
|
14
|
+
This comment quotes a verse to fill in
|
15
|
+
<fg:iq p="Genesis 11:21" v="NIV" />
|
16
|
+
</fg:cm>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<p><a href='http://www.biblegateway.com/passage/?search=genesis%2011:10-20'>Read Genesis 11:10-20</a> |
|
2
|
+
<a href='http://www.biblegateway.com/passage/?search=genesis%2011'>Full Chapter</a></p>
|
3
|
+
|
4
|
+
<blockquote>
|
5
|
+
These are the records of the generations of Shem. Shem was one hundred years old, and became the father of Arpachshad two years after the flood; and Shem lived five hundred years after he became the father of Arpachshad, and he had other sons and daughters. Arpachshad lived thirty-five years, and became the father of Shelah; and Arpachshad lived four hundred and three years after he became the father of Shelah, and he had other sons and daughters. Shelah lived thirty years, and became the father of Eber;
|
6
|
+
<br/>
|
7
|
+
(<a href='http://www.biblegateway.com/passage/?search=genesis%2011:10-14;&version=49;'>Genesis 11:10-14, NASB</a>)
|
8
|
+
</blockquote>
|
9
|
+
|
10
|
+
|
11
|
+
<p>This passage tells us something.
|
12
|
+
Now these verses elaborate</p>
|
13
|
+
|
14
|
+
<blockquote>
|
15
|
+
Already quoted.
|
16
|
+
|
17
|
+
<br/>
|
18
|
+
(<a href='http://www.biblegateway.com/passage/?search=genesis%2011:15-20;&version=46;'>Genesis 11:15-20, CEV</a>)
|
19
|
+
</blockquote>
|
20
|
+
|
21
|
+
|
22
|
+
<p> This comment <span>"quotes a verse" (<a href='http://www.biblegateway.com/passage/?search=genesis%2011:20;&version=31;'>Genesis 11:20, NIV</a>)
|
23
|
+
</span>
|
24
|
+
This comment quotes a verse to fill in
|
25
|
+
<span>"
|
26
|
+
And after he became the father of Serug, Reu lived 207 years and had other sons and daughters." (<a href='http://www.biblegateway.com/passage/?search=genesis%2011:21;&version=31;'>Genesis 11:21, NIV</a>)
|
27
|
+
</span></p>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../lib/filters/linebreaker')
|
3
|
+
|
4
|
+
describe Linebreaker do
|
5
|
+
|
6
|
+
it "should convert single line breaks to <br> tags" do
|
7
|
+
Linebreaker.new("abc\n\ndef\nghi").to_html.should ==
|
8
|
+
"abc\n\ndef<br />\nghi"
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'dm-core'
|
3
|
+
|
4
|
+
class MockContent
|
5
|
+
include DataMapper::Resource
|
6
|
+
include Filters::Resource
|
7
|
+
|
8
|
+
property :html, Text
|
9
|
+
property :filters, String
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Filters::Resource do
|
13
|
+
describe ".property" do
|
14
|
+
before(:each) do
|
15
|
+
@model = MockContent
|
16
|
+
@props = @model.properties
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should add :to property if not defined" do
|
20
|
+
@model.property(:body, String, :filter => {:to => :html2, :with => "Markdown"})
|
21
|
+
MockContent.properties['html2'].should be_kind_of(DataMapper::Property)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should not add :with property if not defined" do
|
25
|
+
@model.property(:body, String, :filter => {:to => :html, :with => :filters2})
|
26
|
+
MockContent.properties['filters2'].should be_kind_of(DataMapper::Property)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jm81-dm-filters
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jared Morgan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-07-09 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: dm-core
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: jmorgan@morgancreative.net
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.md
|
34
|
+
files:
|
35
|
+
- .document
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- lib/dm-filters.rb
|
42
|
+
- lib/filters/bible_ml.rb
|
43
|
+
- lib/filters/linebreaker.rb
|
44
|
+
- spec/dm-filters_spec.rb
|
45
|
+
- spec/filters/bible_ml_spec.rb
|
46
|
+
- spec/filters/fixtures/gen11/input.xml
|
47
|
+
- spec/filters/fixtures/gen11/output.xml
|
48
|
+
- spec/filters/linebreaker_spec.rb
|
49
|
+
- spec/filters_resource_spec.rb
|
50
|
+
- spec/spec_helper.rb
|
51
|
+
has_rdoc: false
|
52
|
+
homepage: http://github.com/jm81/dm-filters
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options:
|
55
|
+
- --charset=UTF-8
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.2.0
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: TODO
|
77
|
+
test_files:
|
78
|
+
- spec/dm-filters_spec.rb
|
79
|
+
- spec/filters/bible_ml_spec.rb
|
80
|
+
- spec/filters/linebreaker_spec.rb
|
81
|
+
- spec/filters_resource_spec.rb
|
82
|
+
- spec/spec_helper.rb
|