notroff 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ require 'rexml/document'
2
+ require 'pp'
3
+
4
+ class HtmlRenderer < Processor
5
+ include Tokenize
6
+ include REXML
7
+
8
+ def process( paragraphs )
9
+ body = Element.new('body')
10
+ paragraphs.each do |paragraph|
11
+ new_element = format( paragraph )
12
+ body.add new_element if new_element
13
+ end
14
+ body
15
+ end
16
+
17
+ def format( p )
18
+ type = p[:type]
19
+ text = p.string
20
+
21
+ return nil if text.empty? and :type != :code
22
+
23
+ if type == :code
24
+ code_element(type, text)
25
+ else
26
+ text_element(type, text)
27
+ end
28
+ end
29
+
30
+ def text_element(type, text)
31
+ element = Element.new(tag_for(type))
32
+ add_body_text(text, element)
33
+ element
34
+ end
35
+
36
+ def tag_for(type)
37
+ case type
38
+ when :body
39
+ 'p'
40
+ when :text
41
+ 'p'
42
+ when :author
43
+ 'h3'
44
+ when :section
45
+ 'h3'
46
+ when :sec
47
+ 'h3'
48
+ when :chapter
49
+ 'h2'
50
+ when :title
51
+ 'h1'
52
+ else
53
+ raise "Dont know what to do with #{type}"
54
+ end
55
+ end
56
+
57
+ def add_body_text( text, element )
58
+ tokens = tokenize_body_text( text )
59
+ tokens.each {|token| add_span( token, element ) }
60
+ end
61
+
62
+ def add_span( token, element )
63
+ case token[:type]
64
+ when :italic
65
+ element.add(span_for(token.string, "em"))
66
+ when :code
67
+ element.add(span_for(token.string, "code"))
68
+ when :bold
69
+ element.add(span_for(token.string, "b"))
70
+ when :normal
71
+ element.add_text(token.string)
72
+ when :footnote
73
+ add_body_text(" [#{token.string}] ", element)
74
+ else
75
+ raise "Dont know what to do with type #{token[:type]} - #{token}"
76
+ end
77
+ end
78
+
79
+ def code_element(type, text)
80
+ element = Element.new('code')
81
+ pre_element = Element.new('pre')
82
+ element.add pre_element
83
+ pre_element.add_text text
84
+ element
85
+ end
86
+
87
+ def span_for( text, style )
88
+ span = Element.new( style )
89
+ span.text = remove_escapes(text)
90
+ span
91
+ end
92
+ end
data/lib/notroff/io.rb ADDED
@@ -0,0 +1,18 @@
1
+ class FileProcessor
2
+ def initialize(path)
3
+ @path = path
4
+ end
5
+ end
6
+
7
+ class FileReader < FileProcessor
8
+ def process(ignored)
9
+ paras = File.readlines(@path)
10
+ paras.map! {|p| p.rstrip}
11
+ end
12
+ end
13
+
14
+ class FileWriter < FileProcessor
15
+ def process(output)
16
+ File.open(@path, 'w') {|f| f.write(output)}
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+
2
+ module Logger
3
+ @verbose = false
4
+
5
+ def self.verbose=(value)
6
+ @verbose = value
7
+ end
8
+
9
+ def self.log(*args)
10
+ return unless @verbose
11
+ puts args.join(' ')
12
+ end
13
+ end
@@ -0,0 +1,154 @@
1
+ require 'rexml/document'
2
+ require 'pp'
3
+
4
+
5
+ class OdtRenderer < Processor
6
+ include Tokenize
7
+ include REXML
8
+
9
+ LONG_DASH_CODE = 0xe1.chr + 0x80.chr + 0x93.chr
10
+
11
+ PARAGRAPH_STYLES = {
12
+ :body => 'BodyNoIndent',
13
+ :title => 'HB',
14
+ :section => 'HC',
15
+ :sec => 'HC',
16
+ :first_code => 'CDT1',
17
+ :middle_code => 'CDT',
18
+ :end_code => 'CDTX',
19
+ :author => 'AU',
20
+ :quote => 'Quotation',
21
+ :single_code => 'C1',
22
+ :pn => 'PN',
23
+ :pt => 'PT',
24
+ :cn => 'HA',
25
+ :chapter => 'HA',
26
+ :ct => 'HB' }
27
+
28
+ @@footnote_number = 1
29
+
30
+ def process( paragraphs )
31
+ elements = []
32
+ paragraphs.each do |paragraph|
33
+ new_element = format( paragraph )
34
+ elements << new_element if new_element
35
+ end
36
+ elements
37
+ end
38
+
39
+ def format( para )
40
+ Logger.log "Format: #{para.inspect}"
41
+ type = para[:type]
42
+ text = para
43
+
44
+ return nil if text.empty? and ! code_type?( type )
45
+
46
+ result = new_text_element( type )
47
+
48
+ if [ :author, :section, :sec, :title, :pn, :pt, :chapter ].include?( type )
49
+ result.add_text( text.string )
50
+ elsif [:body, :quote].include?(type)
51
+ add_body_text( text, result )
52
+ elsif code_type?(type)
53
+ add_code_text( text, result )
54
+ else
55
+ raise "Dont know what to do with type [#{type}]"
56
+ end
57
+ result
58
+ end
59
+
60
+ def code_type?( type )
61
+ [ :first_code, :middle_code, :end_code, :single_code ].include?(type)
62
+ end
63
+
64
+ def new_text_element( type )
65
+ result = Element.new( "text:p" )
66
+ result.attributes["text:style-name"] = PARAGRAPH_STYLES[type]
67
+ result
68
+ end
69
+
70
+ def add_body_text( text, element )
71
+ tokens = tokenize_body_text( text )
72
+ tokens.each {|token| add_span( token, element ) }
73
+ end
74
+
75
+ def add_code_text( text, element )
76
+ text = text.dup
77
+ re = /\S+|\s+/
78
+ until text.empty?
79
+ chunk = text.slice!( re )
80
+ if chunk !~ /^ /
81
+ element.add_text( chunk.string )
82
+ else
83
+ space_element = Element.new( 'text:s' )
84
+ space_element.attributes['text:c'] = chunk.size.to_s
85
+ element.add( space_element )
86
+ end
87
+ end
88
+ end
89
+
90
+ def add_span( token, element )
91
+ case token[:type]
92
+ when :italic
93
+ element.add( span_for( token.string, "T1" ))
94
+ when :code
95
+ element.add( span_for( token.string, "CD1" ))
96
+ when :bold
97
+ element.add( span_for( token.string, "T2" ))
98
+ when :normal
99
+ element.add_text( token.string )
100
+ when :footnote
101
+ element.add( footnote_for( token.string ) )
102
+ else
103
+ raise "Dont know what to do with #{token}"
104
+ end
105
+ end
106
+
107
+ def span_for( text, style )
108
+ span = Element.new( "text:span" )
109
+ span.attributes['text:style-name'] = style
110
+ span.text = remove_escapes(text)
111
+ span
112
+ end
113
+
114
+ def remove_escapes( text )
115
+ text = text.clone
116
+
117
+ results = ''
118
+
119
+ until text.empty?
120
+ match = /\\(.)/.match( text )
121
+ if match.nil?
122
+ results << text
123
+ text = ''
124
+ else
125
+ unless match.pre_match.empty?
126
+ results << match.pre_match
127
+ end
128
+ results << match[1]
129
+ text = match.post_match
130
+ end
131
+ end
132
+ results
133
+ end
134
+
135
+ def footnote_for(text )
136
+ note_element = Element.new( "text:note" )
137
+ note_element.attributes["text:id"] ="ftn#{@@footnote_number}"
138
+ note_element.attributes["text:note-class"] ="footnote"
139
+
140
+ cit = Element.new( "text:note-citation" )
141
+ cit.add_text( "#{@@footnote_number}" )
142
+ note_element.add( cit )
143
+
144
+ note_body = Element.new( "text:note-body" )
145
+ note_paragraph = Element.new( "text:p" )
146
+ note_paragraph.attributes['text:style-name'] = 'FTN'
147
+ add_body_text(text, note_paragraph)
148
+
149
+ note_body.add( note_paragraph )
150
+ note_element.add( note_body )
151
+ @@footnote_number += 1
152
+ note_element
153
+ end
154
+ end
@@ -0,0 +1,22 @@
1
+ require 'pp'
2
+ require 'zip/zipfilesystem'
3
+ require 'fileutils'
4
+
5
+ SKEL = File.expand_path('../skel.odt', __FILE__)
6
+
7
+ class OdtReplacer < Processor
8
+
9
+ def initialize( output_path )
10
+ @output_path = output_path
11
+ end
12
+
13
+ def process( new_content )
14
+ FileUtils.cp( SKEL, @output_path )
15
+
16
+ Zip::ZipFile.open( @output_path ) do |zipfile|
17
+ zipfile.file.open("content.xml", "w") do |content|
18
+ content.print( new_content )
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,53 @@
1
+ class ParagraphJoiner
2
+ def process( paragraphs )
3
+ processed_paragraphs = []
4
+ new_p = nil
5
+ paragraphs.each do |paragraph|
6
+ do_join = join?(paragraph)
7
+
8
+ if join?(paragraph)
9
+ if new_p
10
+ new_p.string += " "
11
+ new_p.string += paragraph
12
+ else
13
+ new_p = paragraph
14
+ end
15
+ else
16
+ if new_p
17
+ processed_paragraphs << new_p
18
+ new_p = nil
19
+ end
20
+ processed_paragraphs << paragraph unless skip?(paragraph)
21
+ end
22
+ end
23
+ processed_paragraphs << new_p if new_p
24
+ processed_paragraphs
25
+ end
26
+
27
+ def join?(paragraph)
28
+ false
29
+ end
30
+
31
+ def skip?(paragraph)
32
+ false
33
+ end
34
+ end
35
+
36
+ class BodyParagraphJoiner < ParagraphJoiner
37
+ def join?(paragraph)
38
+ return false unless paragraph[:type] == :body
39
+ return false if paragraph.empty?
40
+ true
41
+ end
42
+
43
+ def skip?(paragraph)
44
+ paragraph[:type] == :body && paragraph.empty?
45
+ end
46
+ end
47
+
48
+ class CodeParagraphJoiner < ParagraphJoiner
49
+ def join?(paragraph)
50
+ return false unless paragraph[:type] == :code
51
+ true
52
+ end
53
+ end
@@ -0,0 +1,192 @@
1
+ class Processor
2
+ end
3
+
4
+ class TextPrinter
5
+ def process( text )
6
+ puts text
7
+ end
8
+ end
9
+
10
+ class FileWriter
11
+ def initialize(output_file)
12
+ @output_file = output_file
13
+ end
14
+
15
+ def process(content)
16
+ File.open(@output_file, 'w') do |f|
17
+ f.print(content.to_s)
18
+ end
19
+ end
20
+ end
21
+
22
+ class Printer
23
+ def process( paragraphs )
24
+ paragraphs.each {|p| puts "#{p.type}: #{p.text}"}
25
+ end
26
+ end
27
+
28
+ class TextReader < Processor
29
+ def initialize( path )
30
+ @path = path
31
+ end
32
+
33
+ def process( ignored )
34
+ lines = File.open( @path ).readlines
35
+ lines.map! { |line| line.rstrip }
36
+ lines
37
+ end
38
+ end
39
+
40
+ #class CommandProcessor
41
+ #
42
+ # def process( lines )
43
+ # paragraphs = []
44
+ # lines.each do |line|
45
+ # cmd, text = parse_line( line )
46
+ # if cmd.empty?
47
+ # cmd = :text
48
+ # else
49
+ # cmd = cmd.sub(/^./, '').to_sym
50
+ # end
51
+ #
52
+ # text.attr(:type, cmd)
53
+ # text.attr(:original_text, line)
54
+ # paragraphs << text
55
+ # end
56
+ # paragraphs
57
+ # end
58
+ #
59
+ # def parse_line( line )
60
+ # match_data = /^(\.\w+ ?)(.*)/.match(line)
61
+ # if match_data
62
+ # cmd = match_data[1].strip
63
+ # text = match_data[2]
64
+ # else
65
+ # cmd = ''
66
+ # text = line
67
+ # end
68
+ # [ cmd.strip, text ]
69
+ # end
70
+ #end
71
+ #
72
+ #class ParagraphTypeAssigner
73
+ # def process( paragraphs )
74
+ # processed_paragraphs = []
75
+ #
76
+ # current_type = :body
77
+ #
78
+ # paragraphs.each do |paragraph|
79
+ # type = paragraph | :type
80
+ # if (type == :body) or (type == :code) or (type == :quote)
81
+ # current_type = type
82
+ #
83
+ # elsif type == :text
84
+ # new_p = paragraph.clone
85
+ # new_p.attr(:type, current_type)
86
+ # processed_paragraphs << new_p
87
+ #
88
+ # else
89
+ # processed_paragraphs << paragraph
90
+ # end
91
+ #
92
+ # current_type = :body if [ :section, :title, :code1 ].include?(type)
93
+ # end
94
+ # processed_paragraphs
95
+ # end
96
+ #end
97
+ #
98
+ #class CodeTypeRefiner
99
+ # def process( paragraphs )
100
+ # processed_paragraphs = []
101
+ #
102
+ # previous_type = nil
103
+ #
104
+ # paragraphs.each_with_index do |paragraph, i|
105
+ # type = paragraph | :type
106
+ # previous_type = ( paragraphs.first == paragraph) ? nil : paragraphs[i-1] | :type
107
+ # next_type = ( paragraphs.last == paragraph) ? nil : paragraphs[i+1] | :type
108
+ # new_type = code_type_for( previous_type, type, next_type )
109
+ # new_p = paragraph.clone
110
+ # new_p.attr(:type, new_type)
111
+ # processed_paragraphs << new_p
112
+ # end
113
+ # processed_paragraphs
114
+ # end
115
+ #
116
+ # def code_type_for( previous_type, type, next_type )
117
+ # if type != :code
118
+ # new_type = type
119
+ #
120
+ # elsif previous_type == :code and next_type == :code
121
+ # new_type = :middle_code
122
+ #
123
+ # elsif previous_type == :code
124
+ # new_type = :end_code
125
+ #
126
+ # elsif next_type == :code
127
+ # new_type = :first_code
128
+ #
129
+ # else
130
+ # new_type = :single_code
131
+ # end
132
+ #
133
+ # new_type
134
+ # end
135
+ #end
136
+ #
137
+ #class SimilarParagraphJoiner
138
+ # def initialize(target_type)
139
+ # @target_type = target_type
140
+ # end
141
+ #
142
+ # def process( paragraphs )
143
+ # processed_paragraphs = []
144
+ # new_p = nil
145
+ # paragraphs.each do |paragraph|
146
+ # if (paragraph | :type) != @target_type
147
+ # processed_paragraphs << new_p if new_p
148
+ # new_p = nil
149
+ # processed_paragraphs << paragraph
150
+ #
151
+ # elsif new_p
152
+ # new_p += "\n"
153
+ # new_p += paragraph
154
+ #
155
+ # else
156
+ # new_p = paragraph
157
+ # end
158
+ # end
159
+ # processed_paragraphs << new_p if new_p
160
+ # processed_paragraphs
161
+ # end
162
+ #end
163
+ #
164
+ #class TextParagraphJoiner
165
+ # def process( paragraphs )
166
+ # processed_paragraphs = []
167
+ #
168
+ # new_p = nil
169
+ #
170
+ # paragraphs.each do |paragraph|
171
+ # if (paragraph | type != :body) and (paragraph | type != :quote)
172
+ # processed_paragraphs << new_p if new_p
173
+ # new_p = nil
174
+ # processed_paragraphs << paragraph
175
+ #
176
+ # elsif paragraph.blank?
177
+ # processed_paragraphs << new_p if new_p
178
+ # new_p = nil
179
+ #
180
+ # elsif new_p
181
+ # new_p += paragraph.text
182
+ #
183
+ # else
184
+ # new_p = paragraph
185
+ # end
186
+ # end
187
+ # processed_paragraphs << new_p if new_p
188
+ # processed_paragraphs
189
+ # end
190
+ #end
191
+ #
192
+ #
Binary file
@@ -0,0 +1,6 @@
1
+ class String
2
+ # Return the number of leading blanks
3
+ def indent_depth
4
+ /^ */.match(self).to_s.size
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ require 'erb'
2
+
3
+ TEMPLATE = File.expand_path('../content.xml.erb', __FILE__)
4
+
5
+ class TemplateExpander < Processor
6
+
7
+ def initialize
8
+ @template = ERB.new(File.read(TEMPLATE))
9
+ end
10
+
11
+ def process( elements )
12
+ content = elements.join( "\n" )
13
+ @template.result(binding)
14
+ end
15
+ end
@@ -0,0 +1,67 @@
1
+ require 'delegate'
2
+
3
+ class Text
4
+ attr_accessor :string, :attrs
5
+
6
+ def initialize(initial_string, initial_attrs={})
7
+ @string = initial_string.to_str.clone
8
+ @attrs = initial_attrs.clone
9
+ end
10
+
11
+ def self.wrap_method(method_name)
12
+ define_method method_name do |*args|
13
+ result = @string.send(method_name, *args)
14
+ return Text.new(result, attrs) if result.kind_of? String
15
+ result
16
+ end
17
+ end
18
+
19
+ def self.wrap_methods(method_names)
20
+ method_names.each {|name| wrap_method(name)}
21
+ end
22
+
23
+ wrap_methods( String.instance_methods(false) )
24
+
25
+ def [](name)
26
+ @attrs[name]
27
+ end
28
+
29
+ def []=(name, value)
30
+ @attrs[name] = value
31
+ end
32
+
33
+ def ==(other)
34
+ return false unless other.kind_of? Text
35
+ @string == other.string && @attrs == other.attrs
36
+ end
37
+
38
+ def clone
39
+ Text.new(string, attrs)
40
+ end
41
+
42
+ def to_str
43
+ #puts "To string: #{@string.class} #{@string}"
44
+ @string
45
+ end
46
+
47
+ def to_s
48
+ @string
49
+ end
50
+
51
+ def inspect
52
+ "#{@string.inspect} :: #{@attrs.inspect}"
53
+ end
54
+ end
55
+
56
+ class String
57
+ def to_text
58
+ Text.new(self)
59
+ end
60
+
61
+ alias_method :old_double_equals, :'=='
62
+
63
+ def ==(other)
64
+ return self == other.string if other.kind_of?(Text)
65
+ old_double_equals(other)
66
+ end
67
+ end
@@ -0,0 +1,65 @@
1
+ #require 'rexml/document'
2
+ require 'pp'
3
+ #require 'zip/zipfilesystem'
4
+ #require 'fileutils'
5
+ #require 'erb'
6
+
7
+ FormatOdtDir = File.dirname(__FILE__)
8
+
9
+ require "#{FormatOdtDir}/utils"
10
+ require "#{FormatOdtDir}/processor"
11
+ require "#{FormatOdtDir}/code_processors"
12
+ require "#{FormatOdtDir}/odt_renderer"
13
+ require "#{FormatOdtDir}/odt_replacer"
14
+ require "#{FormatOdtDir}/template_expander"
15
+
16
+
17
+ class Editor
18
+ def initialize( input, output )
19
+ @input = input
20
+ @output = output
21
+ @processors = []
22
+ @processors << TextReader.new( @input )
23
+ @processors << CommandProcessor.new
24
+ @processors << ParagraphTypeAssigner.new
25
+ @processors << ProgramOutputInserter.new
26
+ @processors << C1Inserter.new
27
+ @processors << IncInserter.new
28
+ @processors << CodeInserter.new
29
+ @processors << LastOutputInserter.new
30
+ @processors << CodeTagFilter.new
31
+ @processors << CodeTypeRefiner.new
32
+ @processors << OdtRenderer.new
33
+ @processors << TemplateExpander.new( File.read( ContentTemplate ) )
34
+ @processors << OdtReplacer.new( OdtSkeleton, @output )
35
+ end
36
+
37
+ def process
38
+ result = nil
39
+ @processors.each do |processor|
40
+ result = processor.process( result )
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+ f = Editor.new( ARGV[0], ARGV[1] )
47
+ f.process
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+