mochigome 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +7 -0
- data/Gemfile.lock +15 -0
- data/TODO +3 -0
- data/lib/mochigome_ver.rb +1 -1
- data/lib/model_extensions.rb +1 -1
- data/lib/query.rb +6 -2
- data/test/app_root/app/controllers/application_controller.rb +15 -0
- data/test/app_root/app/controllers/report_controller.rb +388 -0
- data/test/app_root/app/helpers/application_helper.rb +8 -0
- data/test/app_root/app/models/product.rb +2 -2
- data/test/app_root/app/models/sale.rb +1 -1
- data/test/app_root/app/models/store.rb +4 -0
- data/test/app_root/app/transforms/report.fo.via-html.xslt.haml +173 -0
- data/test/app_root/app/transforms/report.html.xslt.haml +198 -0
- data/test/app_root/app/views/layouts/application.html.haml +15 -0
- data/test/app_root/app/views/report/edit.html.haml +56 -0
- data/test/app_root/app/views/report/show.html.haml +6 -0
- data/test/app_root/config/database.yml +7 -0
- data/test/app_root/config/environments/development.rb +17 -0
- data/test/app_root/config/initializers/mime_types.rb +8 -0
- data/test/app_root/config/routes.rb +3 -2
- data/test/app_root/db/development.sqlite3 +0 -0
- data/test/app_root/lib/apache_fop.rb +151 -0
- data/test/app_root/public/index.html +14 -0
- data/test/app_root/public/javascripts/prototype.js +6084 -0
- data/test/app_root/public/javascripts/report_edit.js +153 -0
- data/test/app_root/public/stylesheets/common.css +183 -0
- data/test/app_root/vendor/plugins/mochigome/init.rb +3 -1
- data/test/server.rb +3 -0
- data/test/test_helper.rb +1 -1
- data/test/unit/query_test.rb +12 -3
- metadata +20 -5
- 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,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.
|
3
|
-
map.
|
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
|
Binary file
|
@@ -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>
|