mochigome 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/Gemfile +7 -0
  2. data/Gemfile.lock +15 -0
  3. data/TODO +3 -0
  4. data/lib/mochigome_ver.rb +1 -1
  5. data/lib/model_extensions.rb +1 -1
  6. data/lib/query.rb +6 -2
  7. data/test/app_root/app/controllers/application_controller.rb +15 -0
  8. data/test/app_root/app/controllers/report_controller.rb +388 -0
  9. data/test/app_root/app/helpers/application_helper.rb +8 -0
  10. data/test/app_root/app/models/product.rb +2 -2
  11. data/test/app_root/app/models/sale.rb +1 -1
  12. data/test/app_root/app/models/store.rb +4 -0
  13. data/test/app_root/app/transforms/report.fo.via-html.xslt.haml +173 -0
  14. data/test/app_root/app/transforms/report.html.xslt.haml +198 -0
  15. data/test/app_root/app/views/layouts/application.html.haml +15 -0
  16. data/test/app_root/app/views/report/edit.html.haml +56 -0
  17. data/test/app_root/app/views/report/show.html.haml +6 -0
  18. data/test/app_root/config/database.yml +7 -0
  19. data/test/app_root/config/environments/development.rb +17 -0
  20. data/test/app_root/config/initializers/mime_types.rb +8 -0
  21. data/test/app_root/config/routes.rb +3 -2
  22. data/test/app_root/db/development.sqlite3 +0 -0
  23. data/test/app_root/lib/apache_fop.rb +151 -0
  24. data/test/app_root/public/index.html +14 -0
  25. data/test/app_root/public/javascripts/prototype.js +6084 -0
  26. data/test/app_root/public/javascripts/report_edit.js +153 -0
  27. data/test/app_root/public/stylesheets/common.css +183 -0
  28. data/test/app_root/vendor/plugins/mochigome/init.rb +3 -1
  29. data/test/server.rb +3 -0
  30. data/test/test_helper.rb +1 -1
  31. data/test/unit/query_test.rb +12 -3
  32. metadata +20 -5
  33. data/test/app_root/app/controllers/owners_controller.rb +0 -2
@@ -0,0 +1,173 @@
1
+ -# FIXME Do this with xsl:attribute-set instead
2
+ - block_attrs = {"font-family" => "Helvetica", "font-size" => "10pt", "start-indent" => "0pt", "end-indent" => "0pt"}
3
+ - h_block_attrs = {"font-family" => "Helvetica", "space-before" => "6pt", "space-after" => "6pt"}
4
+ - footer_block_attrs = block_attrs.merge({"font-size" => "9pt", "text-align" => "center"})
5
+
6
+ !!! XML
7
+ %xsl:stylesheet(version="1.0"
8
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
9
+ xmlns:fo="http://www.w3.org/1999/XSL/Format")
10
+
11
+ %xsl:template(match="/env/context")
12
+ -# Nothing
13
+
14
+ %xsl:template{:match => "/env/div[#{help.xpath_class("report")}]"}
15
+ %fo:root
16
+ %fo:layout-master-set
17
+ %fo:simple-page-master(master-name="content-page"
18
+ page-width="8.5in"
19
+ page-height="11in"
20
+ margin-top="0.25in"
21
+ margin-bottom="0.25in"
22
+ margin-left="0.4in"
23
+ margin-right="0.4in")
24
+ %fo:region-body(margin-top="0.25in" margin-bottom="0.25in")
25
+ %fo:region-before(extent="0.1in")
26
+ %fo:region-after(extent="0.1in")
27
+
28
+ %fo:bookmark-tree
29
+ %xsl:apply-templates(select="div[@id='floating-sidebar']/span/ul/li")
30
+
31
+ %fo:page-sequence(master-reference="content-page")
32
+ %fo:static-content(flow-name="xsl-region-before")
33
+ %fo:block{footer_block_attrs}
34
+ %xsl:value-of(select="/env/context/report_name")
35
+ %fo:static-content(flow-name="xsl-region-after")
36
+ %fo:block{footer_block_attrs}
37
+ Page
38
+ %fo:page-number
39
+ %fo:flow(flow-name="xsl-region-body")
40
+ %xsl:apply-templates(select="div[#{help.xpath_class("report-header")}]")
41
+ %xsl:apply-templates(select="img[#{help.xpath_class("chart")}]")
42
+ %xsl:apply-templates(select="div[#{help.xpath_class("node")}]")
43
+
44
+ %xsl:template{:match => "img[#{help.xpath_class("chart")}]"}
45
+ %fo:block{block_attrs.merge({"margin-left" => "18pt", "padding" => "4px"})}
46
+ %fo:external-graphic(content-height="scale-to-fit" content-width="scale-to-fit" scaling="uniform")
47
+ %xsl:attribute(name="src")
48
+ %xsl:value-of(select="@src")
49
+ %xsl:attribute(name="width")
50
+ -# FIXME This scaling results in ugly images...
51
+ %xsl:value-of(select="concat((number(@width) div 100), 'in')")
52
+
53
+ %xsl:template{:match => "div[#{help.xpath_class("node")}]"}
54
+ %fo:block{block_attrs.merge({"margin-left" => "18pt", "padding" => "4px"})}
55
+ %xsl:attribute(name="id")
56
+ %xsl:value-of(select="div[#{help.xpath_class("internal-jump-handle")}]/@id")
57
+ %xsl:choose
58
+ %xsl:when{:test => help.xpath_class("subtable-node")}
59
+ %xsl:attribute(name="border") solid 1px black
60
+ %xsl:attribute(name="margin-bottom") 15px
61
+ %xsl:otherwise
62
+ %xsl:attribute(name="border-left") solid 1px gray
63
+ %xsl:attribute(name="margin-bottom") 8pt
64
+ %xsl:apply-templates
65
+
66
+ %xsl:template{:match => "div[#{help.xpath_class("bad-print-warning")}]"}
67
+ -# Nothing
68
+
69
+ %xsl:template{:match => "div[#{help.xpath_class("report-header")}]"}
70
+ %fo:block{block_attrs.merge({"margin-left" => "18pt", "padding" => "4px"})}
71
+ %xsl:apply-templates
72
+
73
+ %xsl:template(match="h1|h2|h3|h4|h5|h6")
74
+ %fo:block{h_block_attrs}
75
+ %xsl:choose
76
+ %xsl:when(test="name()='h1'")
77
+ %xsl:attribute(name="font-size") 240%
78
+ %xsl:when(test="name()='h2'")
79
+ %xsl:attribute(name="font-size") 200%
80
+ %xsl:when(test="name()='h3'")
81
+ %xsl:attribute(name="font-size") 170%
82
+ %xsl:when(test="name()='h4'")
83
+ %xsl:attribute(name="font-size") 150%
84
+ %xsl:when(test="name()='h5'")
85
+ %xsl:attribute(name="font-size") 135%
86
+ %xsl:when(test="name()='h6'")
87
+ %xsl:attribute(name="font-size") 120%
88
+ %xsl:apply-templates
89
+
90
+ %xsl:template(match="table")
91
+ %xsl:if(test="count(*/tr|*/td)>0")
92
+ %fo:table(table-layout="fixed")
93
+ %xsl:choose
94
+ %xsl:when(test="count(tbody/tr[last()]/td) = 1")
95
+ %fo:table-column(column-width="proportional-column-width(1)")
96
+ %xsl:otherwise
97
+ %fo:table-column(column-width="2in")
98
+ %xsl:for-each(select="tbody/tr[last()]/td[position() > 1]")
99
+ %fo:table-column(column-width="proportional-column-width(1)")
100
+ %xsl:apply-templates
101
+
102
+ %xsl:template(match="thead")
103
+ %fo:table-header{"start-indent" => "0pt", "end-indent" => "0pt"}
104
+ %xsl:attribute(name="keep-together.within-page") always
105
+ %xsl:apply-templates
106
+
107
+ %xsl:template(match="tbody")
108
+ %fo:table-body{"start-indent" => "0pt", "end-indent" => "0pt"}
109
+ %xsl:apply-templates
110
+
111
+ %xsl:template(match="tr[#{help.xpath_class("subtable-name-row")}]")
112
+ %fo:table-row
113
+ %xsl:apply-templates
114
+ %xsl:with-param(name="tr-cls" select="'subtable-name-row'")
115
+
116
+ %xsl:template(match="tr[#{help.xpath_class("header-row")}]")
117
+ %fo:table-row
118
+ %xsl:apply-templates
119
+ %xsl:with-param(name="tr-cls" select="'header-row'")
120
+
121
+ %xsl:template(match="tr[#{help.xpath_class("subtable-datum-row")}]")
122
+ %fo:table-row
123
+ %xsl:apply-templates
124
+ %xsl:with-param(name="tr-cls" select="'subtable-datum-row'")
125
+
126
+ %xsl:template(match="tr")
127
+ %xsl:variable(name="rownum")
128
+ %xsl:number
129
+ %fo:table-row
130
+ -# The code below doesn't do what I was hoping; it does prevent
131
+ -# widowing of the first ten rows, but then the un-widowed
132
+ -# section just runs right through the footer and off the
133
+ -# page. :-\
134
+ -#%xsl:if(test="$rownum < 10")
135
+ -#%xsl:attribute(name="keep-with-previous.within-page") always
136
+ %xsl:apply-templates
137
+ %xsl:with-param(name="tr-cls" select="'detail-row'")
138
+
139
+ %xsl:template(match="td|th")
140
+ %xsl:param(name="tr-cls")
141
+ %fo:table-cell(border="1px solid gray")
142
+ %xsl:if(test="@colspan")
143
+ %xsl:attribute(name="number-columns-spanned")
144
+ %xsl:value-of(select="@colspan")
145
+ %xsl:if(test="name()='th'")
146
+ %xsl:attribute(name="font-style") italic
147
+ %xsl:if(test="$tr-cls='subtable-name-row'")
148
+ %xsl:attribute(name="background-color") #eee
149
+ %xsl:if(test="$tr-cls='header-row'")
150
+ %xsl:attribute(name="background-color") #ddd
151
+ %xsl:if(test="$tr-cls='subtable-datum-row'")
152
+ %xsl:attribute(name="background-color") #ddd
153
+ %xsl:attribute(name="font-weight") bold
154
+ %fo:block{block_attrs.merge({"padding" => "2pt", "margin-left" => "2pt"})}
155
+ %xsl:apply-templates
156
+
157
+ %xsl:template(match="li[#{help.xpath_class("toc-item")}]")
158
+ %fo:bookmark
159
+ %xsl:attribute(name="internal-destination")
160
+ -# Remove the hash symbol from the HTML internal href
161
+ %xsl:value-of(select="substring(a/@href, 2)")
162
+ %fo:bookmark-title
163
+ %xsl:value-of(select="a")
164
+ %xsl:apply-templates(select="ul/li")
165
+
166
+ %xsl:template(match="li")
167
+ %fo:block{block_attrs.merge({"start-indent" => "25pt"})}
168
+ %xsl:apply-templates
169
+
170
+ %xsl:template(match="label")
171
+ %fo:inline()
172
+ %xsl:apply-templates
173
+ \:
@@ -0,0 +1,198 @@
1
+ !!! XML
2
+ %xsl:stylesheet(version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform")
3
+
4
+ %xsl:variable(name="detail_type" select="/env/context/layer_names/val[last()]")
5
+ %xsl:variable(name="subtable_type" select="/env/context/layer_names/val[last()-1]")
6
+
7
+ %xsl:template(match="/env/context")
8
+ -# Nothing
9
+
10
+ %xsl:template(match="/env/node")
11
+ %div.report
12
+ %xsl:choose
13
+ %xsl:when(test="string(/env/context/skip_root) = 'true'")
14
+ %xsl:apply-templates(select="node" mode="report-root")
15
+ %xsl:otherwise
16
+ %xsl:apply-templates(select="." mode="report-root")
17
+
18
+ -### REPORT CONTENT
19
+
20
+ %xsl:template(match="node" mode="report-root")
21
+ %xsl:call-template(name="print-warning")
22
+ %xsl:call-template(name="sidebar")
23
+ %xsl:call-template(name="report-header")
24
+ %xsl:call-template(name="charts")
25
+
26
+ %xsl:if(test="count(node)=0")
27
+ %tr.detail-row.no-data-row
28
+ %td
29
+ No data was found that matches this report's conditions.
30
+
31
+ %xsl:choose
32
+ %xsl:when(test="count(/env/context/layer_names/val) = 1")
33
+ %xsl:call-template(name="data-table")
34
+ %xsl:otherwise
35
+ %xsl:call-template(name="upper-node")
36
+
37
+ %xsl:template(name="report-header")
38
+ .report-header
39
+ %h1.node-name
40
+ %xsl:value-of(select="/env/context/report_name")
41
+ %xsl:if(test="count(/env/context/condition_descs/val) > 0")
42
+ %h3 Filtered by:
43
+ %ul.filter-list
44
+ %xsl:for-each(select="/env/context/condition_descs/val")
45
+ %li.filter
46
+ %xsl:value-of(select=".")
47
+ %xsl:if(test="count(datum) > 0")
48
+ %h3 Report totals:
49
+ %ul.data-list
50
+ %xsl:apply-templates(select="datum")
51
+
52
+ %xsl:template(name="charts")
53
+ %xsl:for-each(select="/env/context/charts/val")
54
+ %xsl:value-of(select="." disable-output-escaping="yes")
55
+
56
+ %xsl:template(match="node")
57
+ %xsl:choose
58
+ %xsl:when(test="@internal_type=$detail_type")
59
+ %tr.detail-row
60
+ %td.item-name
61
+ %xsl:value-of(select="@name")
62
+ %xsl:apply-templates
63
+ %xsl:when(test="@internal_type=$subtable_type")
64
+ %xsl:call-template(name="data-table")
65
+ %xsl:otherwise
66
+ %xsl:call-template(name="upper-node")
67
+
68
+ %xsl:template(name="upper-node")
69
+ %div.node
70
+ %div.internal-jump-handle
71
+ %xsl:attribute(name="id")
72
+ %xsl:value-of(select="generate-id(.)")
73
+ %xsl:variable(name="node_name" select="@name")
74
+ %xsl:if(test="@internal_type")
75
+ %xsl:variable(name="node_internal_type" select="@internal_type")
76
+ %xsl:for-each(select="/env/context/layer_names/val")
77
+ %xsl:if(test="$node_internal_type=string(.)")
78
+ %xsl:element(name="{concat('h',(position()+1))}")
79
+ %xsl:attribute(name="class") node-name
80
+ %xsl:value-of(select="$node_name")
81
+ %ul.data-list
82
+ %xsl:apply-templates(select="datum")
83
+ -# TODO: Do something to indicate that these datums are report
84
+ -# totals if this is the root node.
85
+ %xsl:apply-templates(select="node")
86
+
87
+ %xsl:template(match="datum")
88
+ %xsl:choose
89
+ %xsl:when(test="../@internal_type=$detail_type")
90
+ %td
91
+ %xsl:value-of(select=".")
92
+ %xsl:when(test="../@internal_type=$subtable_type")
93
+ -# Nothing, data was displayed in the subtable node template
94
+ %xsl:otherwise
95
+ %li.datum(class="datum-{@name}")
96
+ %label
97
+ %xsl:value-of(select="@name")
98
+ %span.value
99
+ %xsl:value-of(select=".")
100
+
101
+ %xsl:template(name="data-table")
102
+ %div.node.subtable-node
103
+ %div.internal-jump-handle
104
+ %xsl:attribute(name="id")
105
+ %xsl:value-of(select="generate-id(.)")
106
+ %table
107
+ %col.item-name
108
+ %thead
109
+ %tr.subtable-name-row
110
+ %th
111
+ %xsl:if(test="count(node)>0")
112
+ %xsl:attribute(name="colspan")
113
+ %xsl:value-of(select="count(node[1]/datum)+1")
114
+ %xsl:value-of(select="@name")
115
+ %tr.header-row
116
+ %th.item-name
117
+ -# Skip this column
118
+ %xsl:for-each(select="node[1]/datum")
119
+ %th
120
+ %xsl:value-of(select="@name")
121
+ %xsl:if(test="count(node)>0")
122
+ %tr.subtable-datum-row
123
+ %td.item-name
124
+ %xsl:value-of(select="@type")
125
+ Total:
126
+ %xsl:value-of(select="count(node)")
127
+ %xsl:variable(name="node" select=".")
128
+ %xsl:for-each(select="node[1]/datum")
129
+ %td
130
+ %xsl:variable(name="tgtname" select="@name")
131
+ %xsl:if(test="$node/datum[@name=$tgtname]")
132
+ %xsl:value-of(select="$node/datum[@name=$tgtname]")
133
+ %tbody
134
+ %xsl:if(test="count(node)=0")
135
+ %tr.detail-row.no-data-row
136
+ %td
137
+ No data
138
+ %xsl:apply-templates(select="node")
139
+
140
+ %xsl:template(name="print-warning")
141
+ %div.printonly.bad-print-warning
142
+ %p
143
+ %strong Warning:
144
+ This printout does not have correct formatting.
145
+ %p.tight
146
+ For better results, use the
147
+ %i Print
148
+ button in the report sidebar on the left,
149
+ %b not
150
+ your browser's print function.
151
+
152
+
153
+ -### REPORT SIDEBAR
154
+
155
+ %xsl:template(name="sidebar")
156
+ -# Hidden links to tell javascript about download targets
157
+ %xsl:for-each(select="/env/context/download_paths/val")
158
+ %a(style="display: none" class="report-download-link")
159
+ %xsl:attribute(name="id")
160
+ %xsl:value-of(select="concat('report-download-link-', ./ext)")
161
+ %xsl:attribute(name="href")
162
+ %xsl:value-of(select="./path")
163
+ %xsl:value-of(select="./name")
164
+
165
+ -# Sidebar with Print and download buttons, and a table of contents.
166
+ -# TODO Why is this even in XSLT? Should just put it in the view.
167
+ %div#sidebar
168
+ %span.sidebar-header Actions
169
+ %ul
170
+ %li
171
+ %a
172
+ %xsl:attribute(name="href")
173
+ %xsl:value-of(select="/env/context/print_path")
174
+ Print
175
+ %xsl:for-each(select="/env/context/download_paths/val")
176
+ %li
177
+ %a#sidebar-download-btn
178
+ %xsl:attribute(name="href")
179
+ %xsl:value-of(select="path")
180
+ Download
181
+ %xsl:value-of(select="name")
182
+ %span#sidebar-contents
183
+ %span.sidebar-header Report Contents
184
+ %ul#sidebar-node-list
185
+ %xsl:apply-templates(select="node" mode="toc")
186
+
187
+ %xsl:template(match="node" mode="toc")
188
+ %li.toc-item
189
+ %a
190
+ %xsl:attribute(name="href")
191
+ %xsl:value-of(select="concat('#',generate-id(.))")
192
+ %xsl:value-of(select="@name")
193
+ %xsl:if(test="count(node)>0 and node[1]/@internal_type!=$detail_type")
194
+ %ul
195
+ %xsl:apply-templates(mode="toc")
196
+
197
+ %xsl:template(match="datum" mode="toc")
198
+ -# Nothing
@@ -0,0 +1,15 @@
1
+ !!!
2
+ %html{ "xml:lang" => "en", :lang => "en", :xmlns => "http://www.w3.org/1999/xhtml"}
3
+ %head
4
+ %meta{ :content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }
5
+ %title
6
+ Mochigome Demo
7
+ = stylesheet_link_tag 'common.css'
8
+ = javascript_include_tag 'prototype.js', 'report_edit.js', :cache => true
9
+ %body
10
+ #flashbox
11
+ - [:alert, :warning, :notice].each do |key|
12
+ - next unless flash[key]
13
+ .flash{ :class => "flash_#{key}" }
14
+ = flash[key]
15
+ = yield
@@ -0,0 +1,56 @@
1
+ - @possible_layer_models.each do |label, clsname|
2
+ :javascript
3
+ add_possible_layer("#{label}", "#{clsname}");
4
+
5
+ - ReportController::AGGREGATE_SOURCES.each do |label, name|
6
+ :javascript
7
+ add_possible_aggregate_source("#{label}", "#{name}");
8
+
9
+ - form_tag report_path, :method => :get, :id => 'report-edit-form' do
10
+ %div
11
+ %h5 Grouping
12
+ %ul#layer-list
13
+ :javascript
14
+ var layer_list =
15
+ new ReportSettingList('layer-list', 'l', function(rsl) {
16
+ var chosen = rsl.values_to_here();
17
+ return report_settings_possible_layers.reject(function(e) {
18
+ return chosen.any(function(c) {
19
+ return c == e[1];
20
+ })
21
+ })
22
+ }, {"label_f" : function(rsl) {
23
+ if (rsl.num_prev() == 0) {
24
+ return "Group first by"
25
+ } else {
26
+ return "Then by"
27
+ }
28
+ }, "limit": 4});
29
+ - @layer_names.each do |clsname|
30
+ :javascript
31
+ layer_list.preload_value("#{clsname}");
32
+ %div
33
+ %h5 Data
34
+ %ul#agg-list
35
+ :javascript
36
+ var agg_list = new ReportSettingList('agg-list', 'a', function(rsl) {
37
+ var chosen = rsl.values_to_here();
38
+ return report_settings_possible_aggregate_sources.reject(function(e) {
39
+ return chosen.any(function(c) {
40
+ return c == e[1];
41
+ })
42
+ })
43
+ }, {"limit": 2});
44
+ - @aggregate_source_names.each do |srcname|
45
+ :javascript
46
+ agg_list.preload_value("#{srcname}")
47
+ - unless @condition_params.empty?
48
+ %div
49
+ %h5 Filtering By
50
+ %ul#cond-list
51
+ - @condition_params.each_with_index do |c, i|
52
+ %ul= condition_desc(c)
53
+ - c.each do |k,v|
54
+ = hidden_field_tag "c[f#{i}][#{k}]", v
55
+
56
+ = submit_tag("Run Report")
@@ -0,0 +1,6 @@
1
+ - edit_params = params.dup
2
+ - edit_params[:action] = "edit"
3
+
4
+ #report-body
5
+ = link_to "Re-design this report", edit_report_path(edit_params)
6
+ = @report_html
@@ -4,3 +4,10 @@ test:
4
4
  verbosity: quiet
5
5
  pool: 5
6
6
  timeout: 5000
7
+
8
+ development:
9
+ adapter: sqlite3
10
+ database: "db/development.sqlite3"
11
+ verbosity: quiet
12
+ pool: 5
13
+ timeout: 5000
@@ -0,0 +1,17 @@
1
+ # Settings specified here will take precedence over those in config/environment.rb
2
+
3
+ # In the development environment your application's code is reloaded on
4
+ # every request. This slows down response time but is perfect for development
5
+ # since you don't have to restart the webserver when you make code changes.
6
+ config.cache_classes = false
7
+
8
+ # Log error messages when you accidentally call methods on nil.
9
+ config.whiny_nils = true
10
+
11
+ # Show full error reports and disable caching
12
+ config.action_controller.consider_all_requests_local = true
13
+ config.action_view.debug_rjs = true
14
+ config.action_controller.perform_caching = false
15
+
16
+ # Don't actually send out emails in development mode
17
+ config.action_mailer.perform_deliveries = false
@@ -0,0 +1,8 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new mime types for use in respond_to blocks:
4
+ # Mime::Type.register "text/richtext", :rtf
5
+ # Mime::Type.register_alias "text/html", :iphone
6
+
7
+ Mime::Type.register "application/vnd.ms-excel", :xlsx
8
+ Mime::Type.register "application/pdf", :pdf
@@ -1,4 +1,5 @@
1
1
  ActionController::Routing::Routes.draw do |map|
2
- map.connect ':controller/:action/:id'
3
- map.connect ':controller/:action/:id.:format'
2
+ map.report '/report', :controller => 'report', :action => 'show'
3
+ map.report '/report.:format', :controller => 'report', :action => 'show'
4
+ map.edit_report '/report/edit', :controller => 'report', :action => 'edit'
4
5
  end
@@ -0,0 +1,151 @@
1
+ module ApacheFop
2
+ # Returns a closed Tempfile which contains the pdf
3
+ def self.generate_pdf(xsl_fo, options = {})
4
+ temp_files = Hash[[:fo, :pdf, :errout].map{|fn| [fn, Tempfile.new(fn.to_s)]}]
5
+ begin
6
+ temp_files[:fo].write(xsl_fo)
7
+ temp_files.each{|k,t| t.close}
8
+
9
+ # FIXME Should use FOP as an http service instead, it would be faster.
10
+ # Can I somehow forward its response directly to the client?
11
+ system(
12
+ "fop" +
13
+ " -fo \"#{temp_files[:fo].path}\" " +
14
+ " -pdf \"#{temp_files[:pdf].path}\" " +
15
+ " >\"#{temp_files[:errout].path}\" 2>&1"
16
+ )
17
+ fop_info = File.read(temp_files[:errout].path)
18
+ if fop_info =~ /SEVERE: Exception/
19
+ raise "Apache FOP raised an internal exception: #{fop_info}"
20
+ end
21
+
22
+ unless File.size?(temp_files[:pdf].path)
23
+ raise "Apache FOP failed to generate a PDF: #{fop_info}"
24
+ end
25
+
26
+ if options[:auto_print]
27
+ unless options[:from_url]
28
+ raise "The auto_print option requires the from_url option"
29
+ end
30
+ append_auto_print(temp_files[:pdf].path, options[:from_url])
31
+ end
32
+
33
+ return temp_files[:pdf]
34
+ ensure
35
+ [:fo, :errout].each{|sym| temp_files[sym].close!}
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def self.append_auto_print(path, from_url)
42
+ # FIXME: All the parsing code below assumes single-char line endings
43
+ # Also assuming that the PDF doesn't already have secondary sections
44
+ catalog_obj = nil
45
+ catalog_obj_num = nil
46
+ catalog_obj_gen = nil
47
+ file_trailer = nil
48
+ orig_xref_pos = nil
49
+ orig_size = nil
50
+
51
+ File.open(path) do |fh|
52
+ # Extract the file trailer
53
+ fh.seek(-1024, IO::SEEK_END)
54
+ file_trailer = fh.read()
55
+ file_trailer.sub!(/.+^trailer/m, "trailer")
56
+ raise "Could not find PDF trailer section" unless file_trailer =~ /trailer/
57
+
58
+ if file_trailer =~ /^startxref\s*^(\d+)/m
59
+ orig_xref_pos = $1.to_i
60
+ else
61
+ raise "Could not find PDF xref position"
62
+ end
63
+
64
+ if file_trailer =~ /Size (\d+)/
65
+ orig_size = $1.to_i
66
+ else
67
+ raise "Could not find PDF obj count"
68
+ end
69
+
70
+ if file_trailer =~ /\/Root (\d+) (\d+) R/
71
+ catalog_obj_num = $1.to_i
72
+ catalog_obj_gen = $2.to_i
73
+ else
74
+ raise "Could not find PDF catalog obj num"
75
+ end
76
+
77
+ fh.seek(orig_xref_pos)
78
+ fh.gets # Skip the "xref" line
79
+ cur_num = 0
80
+ while fh.gets
81
+ if $_ =~ /^(\d+) \d+\s*$/
82
+ # Start of an xref section at the given number
83
+ cur_num = $1.to_i
84
+ elsif $_ =~ /^(\d+) (\d+) ([nf])\s*$/
85
+ offset, gen, xtype = $1.to_i, $2.to_i, $3
86
+ if xtype == "n" && cur_num == catalog_obj_num && gen = catalog_obj_gen
87
+ fh.seek(offset)
88
+ catalog_obj = ""
89
+ while fh.gets
90
+ catalog_obj += $_
91
+ break if catalog_obj =~ /\bendobj/
92
+ end
93
+ if catalog_obj !~ /\bendobj/
94
+ raise "No proper end found to PDF catalog section"
95
+ end
96
+ catalog_obj.sub!("endobj", "")
97
+ break
98
+ end
99
+ cur_num += 1
100
+ elsif $_ =~ /^trailer/
101
+ raise "Couldn't find root xref in PDF"
102
+ else
103
+ raise "Invalid xref line in PDF: '#{$_}'"
104
+ end
105
+ end
106
+ raise "Could not find PDF catalog section" unless catalog_obj
107
+ end
108
+
109
+ # Append the updated catalog section and new action objects
110
+ File.open(path, "a") do |fh|
111
+ new_catalog_pos = fh.pos
112
+ fh.puts catalog_obj.sub(">>", " /OpenAction #{orig_size} 0 R\n>>")
113
+ fh.puts "endobj"
114
+
115
+ print_action_pos = fh.pos
116
+ fh.puts "#{orig_size} 0 obj"
117
+ fh.puts "<<"
118
+ fh.puts " /Type /Action"
119
+ fh.puts " /S /Named"
120
+ fh.puts " /N /Print"
121
+ fh.puts " /Next #{(orig_size+1).to_s} 0 R"
122
+ fh.puts ">>"
123
+ fh.puts "endobj"
124
+
125
+ uri_action_pos = fh.pos
126
+ fh.puts "#{orig_size+1} 0 obj"
127
+ fh.puts "<<"
128
+ fh.puts " /Type /Action"
129
+ fh.puts " /S /URI"
130
+ fh.puts " /URI (#{from_url})"
131
+ fh.puts ">>"
132
+ fh.puts "endobj"
133
+
134
+ xref_pos = fh.pos
135
+ fh.puts "xref"
136
+ fh.puts "#{catalog_obj_num} 1"
137
+ fh.puts "%010u %05u n " % [new_catalog_pos, 0]
138
+ fh.puts "#{orig_size} 2"
139
+ fh.puts "%010u %05u n " % [print_action_pos, 0]
140
+ fh.puts "%010u %05u n " % [uri_action_pos, 0]
141
+
142
+ fh.puts file_trailer.sub(
143
+ ">>", "/Prev #{orig_xref_pos}\n>>"
144
+ ).sub(
145
+ /Size \d+/, "Size #{orig_size.to_i+2}" # 2 new objs
146
+ ).sub(
147
+ /startxref\s*^\d+/m, "startxref\n#{xref_pos}"
148
+ )
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,14 @@
1
+ <p>
2
+ Welcome to the Mochigome demo app (which also serves as the app used in the unit tests).
3
+ </p>
4
+
5
+ <p>
6
+ The code in Mochigome itself is moderately clean, but this demo app's view and controller
7
+ code is quite messy, so be careful if you decide to copy it into your own app and adapt.
8
+ Furthermore, a lot of this demo app code will eventually be moved into some API-type stuff
9
+ in the library proper.
10
+ </p>
11
+
12
+ <p>
13
+ Anyways: <a href="/report">on to the demo!</a>
14
+ </p>