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