latex 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +58 -0
- data/README +57 -0
- data/examples/test-latex.rb +40 -0
- data/lib/latex.rb +516 -0
- metadata +39 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.co.jp>.
|
2
|
+
You can redistribute it and/or modify it under either the terms of the GPL
|
3
|
+
(see COPYING.txt file), or the conditions below:
|
4
|
+
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
6
|
+
software without restriction, provided that you duplicate all of the
|
7
|
+
original copyright notices and associated disclaimers.
|
8
|
+
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
10
|
+
you do at least ONE of the following:
|
11
|
+
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
13
|
+
make them Freely Available, such as by posting said
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
15
|
+
the author to include your modifications in the software.
|
16
|
+
|
17
|
+
b) use the modified software only within your corporation or
|
18
|
+
organization.
|
19
|
+
|
20
|
+
c) rename any non-standard executables so the names do not conflict
|
21
|
+
with standard executables, which must also be provided.
|
22
|
+
|
23
|
+
d) make other distribution arrangements with the author.
|
24
|
+
|
25
|
+
3. You may distribute the software in object code or executable
|
26
|
+
form, provided that you do at least ONE of the following:
|
27
|
+
|
28
|
+
a) distribute the executables and library files of the software,
|
29
|
+
together with instructions (in the manual page or equivalent)
|
30
|
+
on where to get the original distribution.
|
31
|
+
|
32
|
+
b) accompany the distribution with the machine-readable source of
|
33
|
+
the software.
|
34
|
+
|
35
|
+
c) give non-standard executables non-standard names, with
|
36
|
+
instructions on where to get the original software distribution.
|
37
|
+
|
38
|
+
d) make other distribution arrangements with the author.
|
39
|
+
|
40
|
+
4. You may modify and include the part of the software into any other
|
41
|
+
software (possibly commercial). But some files in the distribution
|
42
|
+
are not written by the author, so that they are not under this terms.
|
43
|
+
|
44
|
+
They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
|
45
|
+
files under the ./missing directory. See each file for the copying
|
46
|
+
condition.
|
47
|
+
|
48
|
+
5. The scripts and library files supplied as input to or produced as
|
49
|
+
output from the software do not automatically fall under the
|
50
|
+
copyright of the software, but belong to whomever generated them,
|
51
|
+
and may be sold commercially, and may be aggregated with this
|
52
|
+
software.
|
53
|
+
|
54
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
55
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
56
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
57
|
+
PURPOSE.
|
58
|
+
|
data/README
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
Latex README
|
2
|
+
============
|
3
|
+
|
4
|
+
Latex is a ruby library and contains LaTeX text generation support for Ruby.
|
5
|
+
Its main class LatexFile contains:
|
6
|
+
- support for the subfigure environment => LatexFile.figures
|
7
|
+
- complex tables with nested headers and subheaders => LatexFile.table
|
8
|
+
- the method LatexFile.puts indents the given lines according to begin...end
|
9
|
+
blocks.
|
10
|
+
|
11
|
+
The directory examples/ contains a demonstration of the table generating
|
12
|
+
features.
|
13
|
+
|
14
|
+
|
15
|
+
Requirements
|
16
|
+
------------
|
17
|
+
|
18
|
+
* Ruby 1.8
|
19
|
+
|
20
|
+
|
21
|
+
Install
|
22
|
+
-------
|
23
|
+
|
24
|
+
De-compress archive and enter its top directory.
|
25
|
+
Then type:
|
26
|
+
|
27
|
+
($ su)
|
28
|
+
# ruby setup.rb
|
29
|
+
|
30
|
+
This simple step installs this program under the default
|
31
|
+
location of Ruby libraries. You can also install files into
|
32
|
+
your favorite directory by supplying setup.rb some options.
|
33
|
+
Try "ruby setup.rb --help".
|
34
|
+
|
35
|
+
|
36
|
+
Alternatively you can use the remote installer RubyGems
|
37
|
+
[http://rubygems.rubyforge.org/] for installation. Having RubyGems installed
|
38
|
+
on your system, just type:
|
39
|
+
|
40
|
+
($ su)
|
41
|
+
# gem install latex --remote
|
42
|
+
|
43
|
+
|
44
|
+
Usage
|
45
|
+
-----
|
46
|
+
|
47
|
+
In order to get an overview of the features you can generate
|
48
|
+
the RDoc documentation and hava a look at the examples/ directory.
|
49
|
+
|
50
|
+
|
51
|
+
License
|
52
|
+
-------
|
53
|
+
|
54
|
+
Ruby License
|
55
|
+
|
56
|
+
|
57
|
+
Christian Bang, cbang AT web.de
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'latex'
|
2
|
+
|
3
|
+
tex=LatexFile.new("latextest.tex")
|
4
|
+
rows = {}
|
5
|
+
rows["row1"] = {"col1" => "r1c1", "col2"=>"r1c2", "col3"=>["r1c3","3"]}
|
6
|
+
rows["row2"] = {"col1" => "r2c1", "col2"=>"r2c2", "Col3"=>["r2c3","1.4","2"]}
|
7
|
+
tex.table(rows,"common table")
|
8
|
+
|
9
|
+
File.open("saved_table.dat","w") do |f|
|
10
|
+
Marshal::dump(rows, f)
|
11
|
+
end
|
12
|
+
|
13
|
+
rows = {}
|
14
|
+
rows["A1"] = {'B'=>
|
15
|
+
{'A'=> ['5',6],
|
16
|
+
'C'=> {'F'=>'1','G'=>'2'},
|
17
|
+
'D'=> {'H'=>'3','I'=>'4'}
|
18
|
+
},
|
19
|
+
'X'=>[1,3,55.55]}
|
20
|
+
rows["A2"] = {'B'=>
|
21
|
+
{'C'=> {'F'=>'11','G2'=>'22'},
|
22
|
+
'D'=> {'H'=>'33','I2'=>{'a'=>'44','b'=>'66'}},
|
23
|
+
'E'=> '55'},
|
24
|
+
'X'=>[2.3,5.111,1,4]}
|
25
|
+
tex.table(rows,"hierarchical headers", "Rows")
|
26
|
+
|
27
|
+
File.open("saved_table.dat","r") do |f|
|
28
|
+
old_rows = Marshal::load(f)
|
29
|
+
rows.merge!(old_rows)
|
30
|
+
end
|
31
|
+
tex.table(rows,"both tables",:rowTitle=>"Instance") do |x,y|
|
32
|
+
order = %w{X B C D E G F H col1 col2 col3 Col3}
|
33
|
+
if order.index(x) && order.index(y)
|
34
|
+
order.index(x) <=> order.index(y)
|
35
|
+
else
|
36
|
+
x <=> y
|
37
|
+
end
|
38
|
+
end
|
39
|
+
tex.close
|
40
|
+
|
data/lib/latex.rb
ADDED
@@ -0,0 +1,516 @@
|
|
1
|
+
# Copyright (c) 2004 Christian Bang <cbang AT web.de> and Frank Hutter
|
2
|
+
#
|
3
|
+
# Version 0.1.1
|
4
|
+
#
|
5
|
+
# All rights reserved. You can redistribute and/or modify it under the same terms as Ruby.
|
6
|
+
#
|
7
|
+
# This file provides a LatexFile class for Ruby.
|
8
|
+
|
9
|
+
class String
|
10
|
+
#Escapes underscores. Use this if you have a string that contains
|
11
|
+
#such but you don't mean the latex semantic of underscore.
|
12
|
+
def escape_
|
13
|
+
gsub(/_/,"\\_")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
#examples: Figure.new("9cm", "image.eps", "Test image")
|
18
|
+
# Figure.new("angle=-90,width=9.5cm", "rotatedimage.eps", "Rotated image", "link01")
|
19
|
+
Figure = Struct.new(:format, :filename, :title, :label)
|
20
|
+
|
21
|
+
class FormatError < StandardError
|
22
|
+
end
|
23
|
+
|
24
|
+
# +LatexFile+ is a +File+ with special functions for easy generation of
|
25
|
+
# LaTeX code.
|
26
|
+
# Current mayor features are +table+ and +figures+ generation.
|
27
|
+
#
|
28
|
+
# You may want to quote underscores ('_') in some Latex code you create. For this use String.escape_ which
|
29
|
+
# is available with this file.
|
30
|
+
class LatexFile < File
|
31
|
+
Header = Struct.new(:name, :children)
|
32
|
+
|
33
|
+
# the default table font size, default is nil which means the Latex default. See #table for possible values
|
34
|
+
attr_accessor :defaultTableFontSize
|
35
|
+
|
36
|
+
@@defaultUsePackages = %w([latin1]{inputenc} [final]{graphics} [pdftex]{graphicx} [dvips]{color}
|
37
|
+
{amsfonts} {subfigure} {lscape} {hyperref})
|
38
|
+
@@default_extras = {:fontsize => "10pt" }
|
39
|
+
attr_reader :extras
|
40
|
+
|
41
|
+
# Open a new latex-file for writing.
|
42
|
+
# +extras+ is an optional hash that may contain the following flags
|
43
|
+
# <tt>:landscape</tt>:: turns all pages 90�
|
44
|
+
# <tt>:twocolumn</tt>:: the whole document has two columns
|
45
|
+
# <tt>extras[:fontsize]</tt>:: string containing the font size like "10pt"
|
46
|
+
# === Example:
|
47
|
+
# f = LatexFile.new("test.tex", {:twocolumn => true, :fontsize => "11pt"})
|
48
|
+
def initialize(filename, extras = nil)
|
49
|
+
super(filename,"w") # open a file for write access
|
50
|
+
@extras = extras || Hash.new
|
51
|
+
#use defaults for unused entries:
|
52
|
+
@extras.each_key{|key| @extras[key] |= @@default_extras[key] }
|
53
|
+
@indent = 0 # indent lines in blocks
|
54
|
+
|
55
|
+
@usePackages = @extras[:usePackages] || @@defaultUsePackages
|
56
|
+
|
57
|
+
writeDocumentHeader
|
58
|
+
@lastWasPrint = false
|
59
|
+
end
|
60
|
+
|
61
|
+
def writeDocumentHeader
|
62
|
+
twocolumn = @extras[:twocolumn] ? ",twocolumn" : ""
|
63
|
+
landscape = @extras[:landscape] ? "\\special{landscape}" : ""
|
64
|
+
puts "\\documentclass[#{@extras[:fontsize]}#{twocolumn}]{article}"
|
65
|
+
@usePackages.each{|package| puts "\\usepackage#{package}"}
|
66
|
+
puts "\\addtolength{\\oddsidemargin}{-3.5cm}"
|
67
|
+
puts "\\addtolength{\\textwidth}{7cm}"
|
68
|
+
puts "\\addtolength{\\topmargin}{-3cm}"
|
69
|
+
puts "\\addtolength{\\textheight}{5cm}"
|
70
|
+
puts "\\newcommand{\\hide}[1]{}"
|
71
|
+
puts "#{landscape}"
|
72
|
+
puts "\\begin{document}"
|
73
|
+
puts "\\DeclareGraphicsExtensions{.jpg,.pdf,.mps,.png}"
|
74
|
+
end
|
75
|
+
|
76
|
+
#Write the latex-file footer and closes the file.
|
77
|
+
def close
|
78
|
+
puts "\\end{document}"
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
#Prints a string to the latex file and indents each line with respect to the
|
83
|
+
#current indentation level that depends on nested blocks.
|
84
|
+
def puts(string)
|
85
|
+
lines = string.split("\n")
|
86
|
+
for line in lines
|
87
|
+
@indent -= 2 if line[0,5]=='\\end{' && @indent >= 2
|
88
|
+
if @lastWasPrint
|
89
|
+
super(line)
|
90
|
+
@lastWasPrint = false
|
91
|
+
else
|
92
|
+
super(" " * @indent + line)
|
93
|
+
end
|
94
|
+
@indent += 2 if line[0,7]=='\\begin{'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
#Insert spaces up to the current indentation level
|
99
|
+
def indent
|
100
|
+
print " "*@indent
|
101
|
+
end
|
102
|
+
|
103
|
+
#Other than puts, print does NOT indent the text. If you want to do so
|
104
|
+
#call +indent+ before.
|
105
|
+
def print(string)
|
106
|
+
@lastWasPrint = true
|
107
|
+
@indent -= 2 if string[/\\end/]
|
108
|
+
@indent += 2 if string[/\\begin/]
|
109
|
+
super(string)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Easy creation of environments.
|
113
|
+
# +name+:: the name of the environment.
|
114
|
+
# +args+:: an optional list of arguments that will be added after the environment definition.
|
115
|
+
# === Example:
|
116
|
+
# tex.env("table","[ht]") do
|
117
|
+
# tex.puts "<table body>"
|
118
|
+
# end
|
119
|
+
def env(name, *args)
|
120
|
+
puts "\\begin{#{name}}#{args}"
|
121
|
+
yield if block_given?
|
122
|
+
puts "\\end{#{name}}"
|
123
|
+
end
|
124
|
+
|
125
|
+
########### Figure generation #################################################
|
126
|
+
|
127
|
+
#Inserts one (ore more) figure block(s) with given figures as subfigures.
|
128
|
+
#+figures+::
|
129
|
+
# is an array consisting of +Figure+ instances.
|
130
|
+
# <tt>Figure = Struct.new(:format, :filename, :title)</tt>
|
131
|
+
# If +format+ begins with a number, it is assumed to be the width of the image.
|
132
|
+
# But you can also set e.g. <tt>"height=10.5cm,angle=-90"</tt>.
|
133
|
+
# For more info see the documentation to <tt>\includegraphics </tt> in the
|
134
|
+
# +graphicx+ package of _LaTeX_.
|
135
|
+
#+caption+:: is placed on each figure block that is created.
|
136
|
+
#+newPageThreshold+::
|
137
|
+
# is maximum the number of figures in a block.
|
138
|
+
# If more figures are given then a new block is created with the same
|
139
|
+
# caption.
|
140
|
+
#+placement+:: an optional placement for the figure blocks.
|
141
|
+
#+label+:: an optional label of the figures block ('fig:' is added)
|
142
|
+
# You can also use label="\\hypertarget{...}" if you like.
|
143
|
+
# Otherwise a \label{fig:...} is generated.
|
144
|
+
#=== Example:
|
145
|
+
# Experiment = Struct.new(:caption, :figures)
|
146
|
+
# thisExperiment = Experiment.new
|
147
|
+
# thisExperiment.caption = "Instance: #{instance}_1.dat".escape_
|
148
|
+
# thisExperiment.figures = [
|
149
|
+
# Figure.new("6.5cm", epsDirectory, "#{instance}-a.eps"),
|
150
|
+
# Figure.new("6.5cm", epsDirectory, "#{instance}-b.eps"),
|
151
|
+
# Figure.new("14cm", epsDirectory, "#{instance}-c.eps", "Title for image c)")
|
152
|
+
# ]
|
153
|
+
# tex.figures(thisExperiment.caption, thisExperiment.figures, 3)
|
154
|
+
def figures(caption, figures, newPageThreshold, placement = "htbp", label = nil)
|
155
|
+
lastimagepath = ""
|
156
|
+
figures.each_with_index do |figure,i|
|
157
|
+
figure.title = "[{#{figure.title}}]" unless figure.title == "" or figure.title == nil
|
158
|
+
if i % newPageThreshold == 0
|
159
|
+
puts "\\begin{figure}[#{placement}]"
|
160
|
+
puts "\\caption{#{caption}}"
|
161
|
+
if label
|
162
|
+
if label[0,1]=='\\' #you can use \hypertarget here
|
163
|
+
puts label
|
164
|
+
else
|
165
|
+
puts "\\label{fig:#{label}}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
puts "\\centering"
|
169
|
+
end
|
170
|
+
imagepath = File.dirname(figure.filename)
|
171
|
+
puts "\\graphicspath{{#{imagepath}}}" if imagepath != "" and imagepath != "." and lastimagepath != imagepath
|
172
|
+
lastimagepath = imagepath
|
173
|
+
figure.format = "width="+figure.format if figure.format =~ /^[0-9\.]/
|
174
|
+
if figure.label
|
175
|
+
if figure.label[0,1]=='\\'
|
176
|
+
puts figure.label
|
177
|
+
else
|
178
|
+
puts "\\label{#{figure.label}}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
puts "\\subfigure#{figure.title}{\\includegraphics[#{figure.format}]{#{File.basename(figure.filename)}}}"
|
182
|
+
if i % newPageThreshold == newPageThreshold-1 or figure == figures[-1]
|
183
|
+
puts "\\end{figure}"
|
184
|
+
puts "\\clearpage"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
########### Table Generation functions #################################################
|
190
|
+
|
191
|
+
#This is a helper function for table generation.
|
192
|
+
#It computes the number of columns that are leaves of the tree with root +header_node+.
|
193
|
+
#You can also say, it computes the number of leaves of the given tree.
|
194
|
+
#+header+:: is of type +Header+: (name, children)
|
195
|
+
def getNumColsOfHeader(header)
|
196
|
+
header.children.inject(0) do |numChildren, child|
|
197
|
+
numChildren + ((child.class == Header) ? getNumColsOfHeader(child) : 1)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
private :getNumColsOfHeader
|
201
|
+
|
202
|
+
#+hash+:: must contain hashes as values for all keys.
|
203
|
+
#These child hashes will be joined/merged to one big hash.
|
204
|
+
#The actual leafes will be ignored so that only the header remain.
|
205
|
+
def getHeaderMergedHash(list)
|
206
|
+
merged = {}
|
207
|
+
toMerge = Hash.new {|h,k| h[k] = [] }
|
208
|
+
nothingToMerge = true
|
209
|
+
for entry in list
|
210
|
+
if entry.class == Hash
|
211
|
+
entry.each_pair do |key,value|
|
212
|
+
toMerge[key] << value
|
213
|
+
end
|
214
|
+
nothingToMerge = false
|
215
|
+
end
|
216
|
+
end
|
217
|
+
#return list if nothingToMerge
|
218
|
+
return nil if nothingToMerge #return only header tree without any data
|
219
|
+
toMerge.each_pair do |key,value|
|
220
|
+
merged[key] = getHeaderMergedHash(value)
|
221
|
+
end
|
222
|
+
return merged
|
223
|
+
end
|
224
|
+
private :getHeaderMergedHash
|
225
|
+
|
226
|
+
@@default_sort = Proc.new { |x,y| x <=> y }
|
227
|
+
|
228
|
+
#Given a list of hashes, initially a hash-list of each row it
|
229
|
+
#returns a sorted tree representation - a headerList.
|
230
|
+
#+sort_block+:: a block that compares header names
|
231
|
+
def hash2HeaderList(hash, sort_block = @@default_sort)
|
232
|
+
headerList = []
|
233
|
+
entries = hash.to_a
|
234
|
+
entries.sort! {|x,y| sort_block.call(x[0],y[0])}
|
235
|
+
for key,value in entries
|
236
|
+
if value.class == Hash
|
237
|
+
subtree = hash2HeaderList(value, sort_block)
|
238
|
+
headerList << Header.new(key,subtree)
|
239
|
+
else
|
240
|
+
headerList << key
|
241
|
+
end
|
242
|
+
end
|
243
|
+
return headerList
|
244
|
+
end
|
245
|
+
private :hash2HeaderList
|
246
|
+
|
247
|
+
#Performes a breath first traversal of the given tree
|
248
|
+
#+headerList+:: a list of header nodes of class +Header+.
|
249
|
+
#Returns a list of headers (together with the number of columns they use) for each header line needed.
|
250
|
+
#example: <tt>[['B',['C',['F','G']], ['D',['H','I']],['E']]]</tt> =>
|
251
|
+
#<tt>[['B'], ['C','D','E'], ['F','G','H','I']]</tt> and with their column width information
|
252
|
+
#if the (sub)header covers more than one column =>
|
253
|
+
#<tt>[[['B',5]], [['C',2],['D',2],'E'], ['F','G','H','I']]</tt>
|
254
|
+
#The length of the returned list is the number of rows needed for the headers.
|
255
|
+
def getHeaderLines(headerList) #:nodoc:
|
256
|
+
line = []
|
257
|
+
nextLevel = [] # collects the children nodes of all nodes processed (BFS)
|
258
|
+
hasChildrenInNextLevel = false
|
259
|
+
for header in headerList
|
260
|
+
if header.class != Header
|
261
|
+
line << header #leaf node will not be put in an array with column width info
|
262
|
+
nextLevel += ["~"] # insert this for lines below this
|
263
|
+
else
|
264
|
+
line << [header.name, getNumColsOfHeader(header)]
|
265
|
+
nextLevel += header.children
|
266
|
+
hasChildrenInNextLevel = true
|
267
|
+
end
|
268
|
+
end
|
269
|
+
return hasChildrenInNextLevel ? [line] + getHeaderLines(nextLevel) : [line]
|
270
|
+
end
|
271
|
+
private :getHeaderLines
|
272
|
+
|
273
|
+
#Returns a list of paths to all leaf nodes of the given header-tree list. The Leaf nodes are the final
|
274
|
+
#columns. In order to compute the table value at (row,col) we want to access only the leaf cols and not
|
275
|
+
#the header columns.
|
276
|
+
#Example: let <tt>[['B',['C',['F','G']], ['D',['H','I']],['E']]]</tt> be our
|
277
|
+
#header tree. The real columns are F,G,H,I,E. In order to access e.g. 'I' we need to know the path [B,D,I].
|
278
|
+
#<tt> => [["B", "C", "F"], ["B", "C", "G"], ["B", "D", "H"], ["B", "D", "I"], ["B", "E"]]</tt>
|
279
|
+
def getLeafColumns(headerList, path_prefix = nil) #:nodoc:
|
280
|
+
paths = []
|
281
|
+
path_prefix = path_prefix || []
|
282
|
+
for header in headerList
|
283
|
+
if header.class == Header
|
284
|
+
paths += getLeafColumns(header.children, path_prefix + [header.name])
|
285
|
+
else #header is not of class Header but a leaf header (string)
|
286
|
+
paths << path_prefix + [header]
|
287
|
+
end
|
288
|
+
end
|
289
|
+
return paths
|
290
|
+
end
|
291
|
+
private :getLeafColumns
|
292
|
+
|
293
|
+
#Pretty print output of table entries. If an entry is an array then
|
294
|
+
#+prettyPrintCell+ will be called for every table item. You can override this
|
295
|
+
#function if you like a special behaviour.
|
296
|
+
def prettyPrintCell(x)
|
297
|
+
if x.kind_of?(Integer)
|
298
|
+
x = "%d" % x
|
299
|
+
elsif x.kind_of?(Float)
|
300
|
+
x = "%.2f" % x
|
301
|
+
else
|
302
|
+
x = x.to_s
|
303
|
+
end
|
304
|
+
raise(FormatError, "Invalid format: #{x.inspect} contains % that is not sufficiently quoted for latex.", caller) if x =~ /[^\\]%|^%/
|
305
|
+
x.gsub(/1.#J|Infinity|1.#INF0/, "$\\infty$")
|
306
|
+
end
|
307
|
+
|
308
|
+
|
309
|
+
# Generates a table with the specified entries.
|
310
|
+
# +entries+::
|
311
|
+
# the row-hash where the keys are the row names which are printed in the first column.
|
312
|
+
# The <tt>entries[row]</tt> is a column-hash. Its keys are the column names.
|
313
|
+
# A column-hash value can be either
|
314
|
+
# - an object (with a <tt>to_s</tt> method) that are printed
|
315
|
+
# - an array containing objects. In this case a new row is added for each array entry
|
316
|
+
# - a hash in which case the keys of this hash are subheaders.
|
317
|
+
# +caption+:: the caption of the table.
|
318
|
+
# +args+::
|
319
|
+
# is either a hash or further arguments
|
320
|
+
# Case 1::
|
321
|
+
# The hash is indexed by one of the argument symbols below, the values correspond to the
|
322
|
+
# argument values you want to set to the corresponding argument.
|
323
|
+
# Instead of symbols you can also use strings, e.g. <tt>:label</tt> or <tt>"label"<</tt>
|
324
|
+
# Case 2::
|
325
|
+
# args are some more arguments. Then they will be interpreted as the following arguments
|
326
|
+
# in the same order. You need not use all of them.
|
327
|
+
# <tt>:rowTitle</tt>:: the title of the first column that contains the row names.
|
328
|
+
# <tt>:label</tt>:: the label of the table. The "tab:" prefix is added automatically .
|
329
|
+
# <tt>:newTableThreshold</tt>::
|
330
|
+
# maximum number of lines per table. If reached then a new table will be created.
|
331
|
+
# Zero means no restriction. An empty row (entries[name] == nil) also toggles a new table.
|
332
|
+
# <tt>:placement</tt>:: the LaTeX placement for the table. See the LaTeX documentation for details.
|
333
|
+
# <tt>:empty</tt>:: a string that will be put in those fields that have no value.
|
334
|
+
# <tt>:sort</tt>::
|
335
|
+
# is used to sort the rows and columns. Default is alphabetical ordering.
|
336
|
+
# See the example below to get an idea on how to implement customized ordering.
|
337
|
+
# <tt>:landscape</tt>:: if true, it will turn the page by 90 degree.
|
338
|
+
# <tt>:fontSize</tt>::
|
339
|
+
# A latex fontsize modifier like tiny, scriptsize, footnotesize, small,
|
340
|
+
# normalsize (default), large, Large, LARGE, huge, Huge.
|
341
|
+
# <tt>:header_hlines</tt>::
|
342
|
+
# If +true+, then horizontal lines between headers of different depths are drawn.
|
343
|
+
# Default is +false+ (no horizontal header lines).
|
344
|
+
#
|
345
|
+
# === Example:
|
346
|
+
# require 'latex'
|
347
|
+
# tex = LatexFile.new("table.tex")
|
348
|
+
# rows["row1"] = {'Main Header'=>
|
349
|
+
# {'A'=> '5',
|
350
|
+
# 'B'=> {'D'=>'1','E'=>'2'},
|
351
|
+
# 'C'=> {'F'=>'3','G'=>'4'}},
|
352
|
+
# 'X'=> [1,3,55.55]}
|
353
|
+
# tex.table(rows,"table with hierarchical headers",:rowTitle=>"Instance")
|
354
|
+
# tableSort = Proc.new do |x,y|
|
355
|
+
# order = %w{A X B C D E}
|
356
|
+
# (order.index(x) && order.index(y)) ? order.index(x) <=> order.index(y) : x <=> y
|
357
|
+
# end
|
358
|
+
# tex.table(rows,"special ordering", :rowTitle=>"Instance", :sort => tableSort)
|
359
|
+
def table(entries, caption, *args)
|
360
|
+
tableValidArgs = [:rowTitle, :label, :newTableThreshold, :placement, :empty, :sort, :landscape, :fontSize, :grouping, :header_hlines]
|
361
|
+
# raise(ArgumentError,"Too many arguments Latex.table", caller) unless args.length <= 1
|
362
|
+
hash = {}
|
363
|
+
args = if args[0].kind_of?(Hash)
|
364
|
+
args[0].each_pair{|key,value| hash[key.to_sym]= value}; hash # allow string keys also
|
365
|
+
else
|
366
|
+
args.each_with_index{|arg,i| hash[tableValidArgs[i].to_sym] = arg}; hash
|
367
|
+
end
|
368
|
+
args.each_key{|key| raise(ArgumentError,"Invalid argument: #{key} for Latex.table", caller) unless tableValidArgs.include?(key)}
|
369
|
+
@tableCaption = caption
|
370
|
+
rowTitle = args[:rowTitle] || ""
|
371
|
+
@tableLabel = args[:label] || ""
|
372
|
+
newTableThreshold = args[:newTableThreshold] || (args[:landscape] ? 38 : 60)
|
373
|
+
placement = args[:placement] || "htbp"
|
374
|
+
empty = args[:empty] ||"-"
|
375
|
+
sort_block = args[:sort] ||@@default_sort
|
376
|
+
grouping = args[:grouping] || [entries.keys]
|
377
|
+
@tableLandscape = args[:landscape]
|
378
|
+
@tableFontSize = args[:fontSize] || @defaultTableFontSize
|
379
|
+
@header_hlines = args[:header_hlines]
|
380
|
+
|
381
|
+
#access an element in the table which is located in row _rowName_ and in a column
|
382
|
+
#that is defined by a _path_ through the headers that can be obtained with
|
383
|
+
#the function +getLeafColumns+.
|
384
|
+
def accessElement(entries, rowName, path) #:nodoc:
|
385
|
+
entry = entries[rowName]
|
386
|
+
for key in path
|
387
|
+
entry = entry[key]
|
388
|
+
return [] unless entry
|
389
|
+
end
|
390
|
+
return entry.is_a?(Array) ? entry : [entry]
|
391
|
+
end
|
392
|
+
|
393
|
+
def printTableHeader(placement, headerLines, leafColumns, rowTitle) #:nodoc:
|
394
|
+
puts "\\begin{landscape}" if @tableLandscape
|
395
|
+
puts "\n\\begin{table}[#{placement}]\n\\begin{center}"
|
396
|
+
puts "\\#{@tableFontSize}" if @tableFontSize
|
397
|
+
print " "*@indent + "\\begin{tabular}{|"
|
398
|
+
print "c|" * (leafColumns.length+1)
|
399
|
+
puts "}"
|
400
|
+
currentHeaderRowNumber = 0
|
401
|
+
puts "\\hline"
|
402
|
+
for headerLine in headerLines
|
403
|
+
print " "*@indent
|
404
|
+
if headerLine == headerLines[-1]
|
405
|
+
print "#{rowTitle} & " # row name title comes in the last header line
|
406
|
+
else
|
407
|
+
print "~ & "
|
408
|
+
end
|
409
|
+
currentColumn = 2
|
410
|
+
cline = ""
|
411
|
+
print headerLine.map {|headerName, columns|
|
412
|
+
raise(FormatError, "Invalid format: #{headerName.inspect} contains % that is not sufficiently quoted for latex.", caller) if headerName =~ /[^\\]%|^%/
|
413
|
+
if columns
|
414
|
+
cline += "\\cline{#{currentColumn}-#{(currentColumn+=columns)-1}}" if @header_hlines
|
415
|
+
"\\multicolumn{#{columns}}{c|}{#{headerName}}"
|
416
|
+
else
|
417
|
+
currentColumn += 1
|
418
|
+
if currentHeaderRowNumber == headerLines.length-1 || headerName == "~"
|
419
|
+
headerName
|
420
|
+
else
|
421
|
+
delta = "%.1f" % ((headerLines.length-currentHeaderRowNumber-1).to_f)
|
422
|
+
"\\raisebox{-#{delta}\\totalheight}[1ex][1ex]{#{headerName}}"
|
423
|
+
end
|
424
|
+
end
|
425
|
+
}.join(" & ")
|
426
|
+
puts "\\\\"+cline
|
427
|
+
currentHeaderRowNumber += 1
|
428
|
+
end
|
429
|
+
puts "\\hline\\hline"
|
430
|
+
end
|
431
|
+
|
432
|
+
def printTableFooter #:nodoc:
|
433
|
+
puts "\\end{tabular}"
|
434
|
+
puts "\\caption{#{@tableCaption}}\n\\label{tab:#@tableLabel}"
|
435
|
+
puts "\\end{center}\n\\end{table}\n"
|
436
|
+
puts "\\end{landscape}" if @tableLandscape
|
437
|
+
end
|
438
|
+
|
439
|
+
#====== Generate header
|
440
|
+
headerList = hash2HeaderList(getHeaderMergedHash(entries.values), sort_block)
|
441
|
+
headerLines = getHeaderLines(headerList)
|
442
|
+
leafColumns = getLeafColumns(headerList)
|
443
|
+
printTableHeader(placement, headerLines, leafColumns, rowTitle)
|
444
|
+
|
445
|
+
currentRow = 0
|
446
|
+
for row_names in grouping
|
447
|
+
#======= Compute table break for multiple groupings.
|
448
|
+
new_lines = 0
|
449
|
+
for row_name in row_names
|
450
|
+
max_lines_per_entry = 1
|
451
|
+
for col_header in leafColumns
|
452
|
+
max_lines_per_entry = [max_lines_per_entry, accessElement(entries,row_name,col_header).length].max
|
453
|
+
end
|
454
|
+
new_lines += max_lines_per_entry
|
455
|
+
end
|
456
|
+
|
457
|
+
if (currentRow > 0 and currentRow + new_lines >= newTableThreshold)
|
458
|
+
currentRow = 0
|
459
|
+
printTableFooter # close last table
|
460
|
+
printTableHeader(placement, headerLines, leafColumns, rowTitle) # open new table
|
461
|
+
else
|
462
|
+
unless currentRow == 0
|
463
|
+
puts "\\hline" unless row_names == []
|
464
|
+
currentRow += 0.5
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
row_names.sort! &sort_block
|
469
|
+
#====== Generate entries.
|
470
|
+
for row_name in row_names
|
471
|
+
if (newTableThreshold > 0 and currentRow >= newTableThreshold)
|
472
|
+
currentRow = 0
|
473
|
+
printTableFooter # close last table
|
474
|
+
printTableHeader(placement, headerLines, leafColumns, rowTitle) # open new table
|
475
|
+
end
|
476
|
+
max_lines_per_entry = 1
|
477
|
+
for col_header in leafColumns
|
478
|
+
max_lines_per_entry = [max_lines_per_entry, accessElement(entries,row_name,col_header).length].max
|
479
|
+
end
|
480
|
+
|
481
|
+
for i in 0...max_lines_per_entry
|
482
|
+
#print row-name
|
483
|
+
print " "*@indent
|
484
|
+
output = (i==0 ? row_name.to_s.gsub(/_/,"\\_") : "~")
|
485
|
+
delta = "%.1f" % ((max_lines_per_entry-1).to_f)
|
486
|
+
output = "\\raisebox{-#{delta}\\totalheight}[1ex][1ex]{#{output}}" if max_lines_per_entry > 1
|
487
|
+
print output
|
488
|
+
|
489
|
+
#print the other columns
|
490
|
+
for col_header in leafColumns
|
491
|
+
entry = accessElement(entries,row_name,col_header)
|
492
|
+
if entry[i]
|
493
|
+
output = prettyPrintCell(entry[i])
|
494
|
+
delta = "%.1f" % (max_lines_per_entry-entry.length).to_f
|
495
|
+
else
|
496
|
+
output = (i==0 ? empty : "~")
|
497
|
+
delta = max_lines_per_entry
|
498
|
+
end
|
499
|
+
#Center the entries vertically in the cell. Out-comment this if you don't like that.
|
500
|
+
if entry.length != max_lines_per_entry# && entry.length > 0
|
501
|
+
output = "\\raisebox{-#{delta}\\totalheight}[1ex][1ex]{#{output}}" unless output == "~"
|
502
|
+
end
|
503
|
+
print " & " + output
|
504
|
+
end
|
505
|
+
puts "\\\\"
|
506
|
+
end
|
507
|
+
puts "\\hline"
|
508
|
+
currentRow += max_lines_per_entry
|
509
|
+
end
|
510
|
+
end
|
511
|
+
#====== Generate footer.
|
512
|
+
printTableFooter
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
|
metadata
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.1
|
3
|
+
specification_version: 1
|
4
|
+
name: latex
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.1
|
7
|
+
date: 2004-09-30
|
8
|
+
summary: Latex is a LaTeX text generation library for Ruby.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
author: Christian Bang
|
12
|
+
email: cbang AT web.de
|
13
|
+
homepage: http://latex.rubyforge.org
|
14
|
+
rubyforge_project: latex
|
15
|
+
description:
|
16
|
+
autorequire: latex/latex
|
17
|
+
default_executable:
|
18
|
+
bindir: bin
|
19
|
+
has_rdoc: true
|
20
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
21
|
+
requirements:
|
22
|
+
-
|
23
|
+
- ">"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 0.0.0
|
26
|
+
version:
|
27
|
+
platform: ruby
|
28
|
+
files:
|
29
|
+
- lib/latex.rb
|
30
|
+
- README
|
31
|
+
- LICENSE.txt
|
32
|
+
- examples/test-latex.rb
|
33
|
+
test_files: []
|
34
|
+
rdoc_options: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
executables: []
|
37
|
+
extensions: []
|
38
|
+
requirements: []
|
39
|
+
dependencies: []
|