mochigome 0.1.5 → 0.1.6

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