Adding demos
bzr revid: pinky-64aceb047a0e4da538512b7ddf33d458dd6f8eb3
This commit is contained in:
parent
963212514a
commit
1315ca0b97
|
@ -0,0 +1,105 @@
|
||||||
|
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||||
|
#see license.txt for license details
|
||||||
|
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/colors/colortest.py
|
||||||
|
import reportlab.pdfgen.canvas
|
||||||
|
from reportlab.lib import colors
|
||||||
|
from reportlab.lib.units import inch
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
c = reportlab.pdfgen.canvas.Canvas('colortest.pdf')
|
||||||
|
|
||||||
|
#do a test of CMYK interspersed with RGB
|
||||||
|
|
||||||
|
#first do RGB values
|
||||||
|
framePage(c, 'Color Demo - RGB Space and CMYK spaces interspersed' )
|
||||||
|
|
||||||
|
y = 700
|
||||||
|
|
||||||
|
c.setFillColorRGB(0,0,0)
|
||||||
|
c.drawString(100, y, 'cyan')
|
||||||
|
c.setFillColorCMYK(1,0,0,0)
|
||||||
|
c.rect(200, y, 300, 30, fill=1)
|
||||||
|
y = y - 40
|
||||||
|
|
||||||
|
c.setFillColorRGB(0,0,0)
|
||||||
|
c.drawString(100, y, 'red')
|
||||||
|
c.setFillColorRGB(1,0,0)
|
||||||
|
c.rect(200, y, 300, 30, fill=1)
|
||||||
|
y = y - 40
|
||||||
|
|
||||||
|
c.setFillColorRGB(0,0,0)
|
||||||
|
c.drawString(100, y, 'magenta')
|
||||||
|
c.setFillColorCMYK(0,1,0,0)
|
||||||
|
c.rect(200, y, 300, 30, fill=1)
|
||||||
|
y = y - 40
|
||||||
|
|
||||||
|
c.setFillColorRGB(0,0,0)
|
||||||
|
c.drawString(100, y, 'green')
|
||||||
|
c.setFillColorRGB(0,1,0)
|
||||||
|
c.rect(200, y, 300, 30, fill=1)
|
||||||
|
y = y - 40
|
||||||
|
|
||||||
|
c.setFillColorRGB(0,0,0)
|
||||||
|
c.drawString(100, y, 'yellow')
|
||||||
|
c.setFillColorCMYK(0,0,1,0)
|
||||||
|
c.rect(200, y, 300, 30, fill=1)
|
||||||
|
y = y - 40
|
||||||
|
|
||||||
|
c.setFillColorRGB(0,0,0)
|
||||||
|
c.drawString(100, y, 'blue')
|
||||||
|
c.setFillColorRGB(0,0,1)
|
||||||
|
c.rect(200, y, 300, 30, fill=1)
|
||||||
|
y = y - 40
|
||||||
|
|
||||||
|
c.setFillColorRGB(0,0,0)
|
||||||
|
c.drawString(100, y, 'black')
|
||||||
|
c.setFillColorCMYK(0,0,0,1)
|
||||||
|
c.rect(200, y, 300, 30, fill=1)
|
||||||
|
y = y - 40
|
||||||
|
|
||||||
|
|
||||||
|
c.showPage()
|
||||||
|
|
||||||
|
#do all named colors
|
||||||
|
framePage(c, 'Color Demo - RGB Space - page %d' % c.getPageNumber())
|
||||||
|
|
||||||
|
all_colors = reportlab.lib.colors.getAllNamedColors().items()
|
||||||
|
all_colors.sort() # alpha order by name
|
||||||
|
c.setFont('Times-Roman', 12)
|
||||||
|
c.drawString(72,730, 'This shows all the named colors in the HTML standard.')
|
||||||
|
y = 700
|
||||||
|
for (name, color) in all_colors:
|
||||||
|
c.setFillColor(colors.black)
|
||||||
|
c.drawString(100, y, name)
|
||||||
|
c.setFillColor(color)
|
||||||
|
c.rect(200, y-10, 300, 30, fill=1)
|
||||||
|
y = y - 40
|
||||||
|
if y < 100:
|
||||||
|
c.showPage()
|
||||||
|
framePage(c, 'Color Demo - RGB Space - page %d' % c.getPageNumber())
|
||||||
|
y = 700
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
c.save()
|
||||||
|
|
||||||
|
def framePage(canvas, title):
|
||||||
|
canvas.setFont('Times-BoldItalic',20)
|
||||||
|
canvas.drawString(inch, 10.5 * inch, title)
|
||||||
|
|
||||||
|
canvas.setFont('Times-Roman',10)
|
||||||
|
canvas.drawCentredString(4.135 * inch, 0.75 * inch,
|
||||||
|
'Page %d' % canvas.getPageNumber())
|
||||||
|
|
||||||
|
#draw a border
|
||||||
|
canvas.setStrokeColorRGB(1,0,0)
|
||||||
|
canvas.setLineWidth(5)
|
||||||
|
canvas.line(0.8 * inch, inch, 0.8 * inch, 10.75 * inch)
|
||||||
|
#reset carefully afterwards
|
||||||
|
canvas.setLineWidth(1)
|
||||||
|
canvas.setStrokeColorRGB(0,0,0)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run()
|
|
@ -0,0 +1,3 @@
|
||||||
|
This is Aaron Watters' first script;
|
||||||
|
it renders his paper for IPC8 into
|
||||||
|
PDF. A fascinating read, as well.
|
|
@ -0,0 +1,903 @@
|
||||||
|
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||||
|
#see license.txt for license details
|
||||||
|
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/gadflypaper/gfe.py
|
||||||
|
__version__=''' $Id: gfe.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
||||||
|
__doc__=''
|
||||||
|
|
||||||
|
#REPORTLAB_TEST_SCRIPT
|
||||||
|
import sys
|
||||||
|
from reportlab.platypus import *
|
||||||
|
from reportlab.lib.styles import getSampleStyleSheet
|
||||||
|
from reportlab.rl_config import defaultPageSize
|
||||||
|
PAGE_HEIGHT=defaultPageSize[1]
|
||||||
|
|
||||||
|
styles = getSampleStyleSheet()
|
||||||
|
|
||||||
|
Title = "Integrating Diverse Data Sources with Gadfly 2"
|
||||||
|
|
||||||
|
Author = "Aaron Watters"
|
||||||
|
|
||||||
|
URL = "http://www.chordate.com/"
|
||||||
|
|
||||||
|
email = "arw@ifu.net"
|
||||||
|
|
||||||
|
Abstract = """This paper describes the primative methods underlying the implementation
|
||||||
|
of SQL query evaluation in Gadfly 2, a database management system implemented
|
||||||
|
in Python [Van Rossum]. The major design goals behind
|
||||||
|
the architecture described here are to simplify the implementation
|
||||||
|
and to permit flexible and efficient extensions to the gadfly
|
||||||
|
engine. Using this architecture and its interfaces programmers
|
||||||
|
can add functionality to the engine such as alternative disk based
|
||||||
|
indexed table implementations, dynamic interfaces to remote data
|
||||||
|
bases or or other data sources, and user defined computations."""
|
||||||
|
|
||||||
|
from reportlab.lib.units import inch
|
||||||
|
|
||||||
|
pageinfo = "%s / %s / %s" % (Author, email, Title)
|
||||||
|
|
||||||
|
def myFirstPage(canvas, doc):
|
||||||
|
canvas.saveState()
|
||||||
|
#canvas.setStrokeColorRGB(1,0,0)
|
||||||
|
#canvas.setLineWidth(5)
|
||||||
|
#canvas.line(66,72,66,PAGE_HEIGHT-72)
|
||||||
|
canvas.setFont('Times-Bold',16)
|
||||||
|
canvas.drawString(108, PAGE_HEIGHT-108, Title)
|
||||||
|
canvas.setFont('Times-Roman',9)
|
||||||
|
canvas.drawString(inch, 0.75 * inch, "First Page / %s" % pageinfo)
|
||||||
|
canvas.restoreState()
|
||||||
|
|
||||||
|
def myLaterPages(canvas, doc):
|
||||||
|
#canvas.drawImage("snkanim.gif", 36, 36)
|
||||||
|
canvas.saveState()
|
||||||
|
#canvas.setStrokeColorRGB(1,0,0)
|
||||||
|
#canvas.setLineWidth(5)
|
||||||
|
#canvas.line(66,72,66,PAGE_HEIGHT-72)
|
||||||
|
canvas.setFont('Times-Roman',9)
|
||||||
|
canvas.drawString(inch, 0.75 * inch, "Page %d %s" % (doc.page, pageinfo))
|
||||||
|
canvas.restoreState()
|
||||||
|
|
||||||
|
def go():
|
||||||
|
Elements.insert(0,Spacer(0,inch))
|
||||||
|
doc = SimpleDocTemplate('gfe.pdf')
|
||||||
|
doc.build(Elements,onFirstPage=myFirstPage, onLaterPages=myLaterPages)
|
||||||
|
|
||||||
|
Elements = []
|
||||||
|
|
||||||
|
HeaderStyle = styles["Heading1"] # XXXX
|
||||||
|
|
||||||
|
def header(txt, style=HeaderStyle, klass=Paragraph, sep=0.3):
|
||||||
|
s = Spacer(0.2*inch, sep*inch)
|
||||||
|
Elements.append(s)
|
||||||
|
para = klass(txt, style)
|
||||||
|
Elements.append(para)
|
||||||
|
|
||||||
|
ParaStyle = styles["Normal"]
|
||||||
|
|
||||||
|
def p(txt):
|
||||||
|
return header(txt, style=ParaStyle, sep=0.1)
|
||||||
|
|
||||||
|
#pre = p # XXX
|
||||||
|
|
||||||
|
PreStyle = styles["Code"]
|
||||||
|
|
||||||
|
def pre(txt):
|
||||||
|
s = Spacer(0.1*inch, 0.1*inch)
|
||||||
|
Elements.append(s)
|
||||||
|
p = Preformatted(txt, PreStyle)
|
||||||
|
Elements.append(p)
|
||||||
|
|
||||||
|
#header(Title, sep=0.1. style=ParaStyle)
|
||||||
|
header(Author, sep=0.1, style=ParaStyle)
|
||||||
|
header(URL, sep=0.1, style=ParaStyle)
|
||||||
|
header(email, sep=0.1, style=ParaStyle)
|
||||||
|
header("ABSTRACT")
|
||||||
|
p(Abstract)
|
||||||
|
|
||||||
|
header("Backgrounder")
|
||||||
|
|
||||||
|
p("""\
|
||||||
|
The term "database" usually refers to a persistent
|
||||||
|
collection of data. Data is persistent if it continues
|
||||||
|
to exist whether or not it is associated with a running
|
||||||
|
process on the computer, or even if the computer is
|
||||||
|
shut down and restarted at some future time. Database
|
||||||
|
management systems provide support for constructing databases,
|
||||||
|
maintaining databases, and extracting information from databases.""")
|
||||||
|
p("""\
|
||||||
|
Relational databases manipulate and store persistent
|
||||||
|
table structures called relations, such as the following
|
||||||
|
three tables""")
|
||||||
|
|
||||||
|
pre("""\
|
||||||
|
-- drinkers who frequent bars (this is a comment)
|
||||||
|
select * from frequents
|
||||||
|
|
||||||
|
DRINKER | PERWEEK | BAR
|
||||||
|
============================
|
||||||
|
adam | 1 | lolas
|
||||||
|
woody | 5 | cheers
|
||||||
|
sam | 5 | cheers
|
||||||
|
norm | 3 | cheers
|
||||||
|
wilt | 2 | joes
|
||||||
|
norm | 1 | joes
|
||||||
|
lola | 6 | lolas
|
||||||
|
norm | 2 | lolas
|
||||||
|
woody | 1 | lolas
|
||||||
|
pierre | 0 | frankies
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
pre("""\
|
||||||
|
-- drinkers who like beers
|
||||||
|
select * from likes
|
||||||
|
|
||||||
|
DRINKER | PERDAY | BEER
|
||||||
|
===============================
|
||||||
|
adam | 2 | bud
|
||||||
|
wilt | 1 | rollingrock
|
||||||
|
sam | 2 | bud
|
||||||
|
norm | 3 | rollingrock
|
||||||
|
norm | 2 | bud
|
||||||
|
nan | 1 | sierranevada
|
||||||
|
woody | 2 | pabst
|
||||||
|
lola | 5 | mickies
|
||||||
|
|
||||||
|
""")
|
||||||
|
pre("""\
|
||||||
|
-- beers served from bars
|
||||||
|
select * from serves
|
||||||
|
|
||||||
|
BAR | QUANTITY | BEER
|
||||||
|
=================================
|
||||||
|
cheers | 500 | bud
|
||||||
|
cheers | 255 | samadams
|
||||||
|
joes | 217 | bud
|
||||||
|
joes | 13 | samadams
|
||||||
|
joes | 2222 | mickies
|
||||||
|
lolas | 1515 | mickies
|
||||||
|
lolas | 333 | pabst
|
||||||
|
winkos | 432 | rollingrock
|
||||||
|
frankies | 5 | snafu
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
The relational model for database structures makes
|
||||||
|
the simplifying assumption that all data in a database
|
||||||
|
can be represented in simple table structures
|
||||||
|
such as these. Although this assumption seems extreme
|
||||||
|
it provides a good foundation for defining solid and
|
||||||
|
well defined database management systems and some
|
||||||
|
of the most successful software companies in the
|
||||||
|
world, such as Oracle, Sybase, IBM, and Microsoft,
|
||||||
|
have marketed database management systems based on
|
||||||
|
the relational model quite successfully.
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
SQL stands for Structured Query Language.
|
||||||
|
The SQL language defines industry standard
|
||||||
|
mechanisms for creating, querying, and modified
|
||||||
|
relational tables. Several years ago SQL was one
|
||||||
|
of many Relational Database Management System
|
||||||
|
(RDBMS) query languages in use, and many would
|
||||||
|
argue not the best on. Now, largely due
|
||||||
|
to standardization efforts and the
|
||||||
|
backing of IBM, SQL is THE standard way to talk
|
||||||
|
to database systems.
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
There are many advantages SQL offers over other
|
||||||
|
database query languages and alternative paradigms
|
||||||
|
at this time (please see [O'Neill] or [Korth and Silberschatz]
|
||||||
|
for more extensive discussions and comparisons between the
|
||||||
|
SQL/relational approach and others.)
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
The chief advantage over all contenders at this time
|
||||||
|
is that SQL and the relational model are now widely
|
||||||
|
used as interfaces and back end data stores to many
|
||||||
|
different products with different performance characteristics,
|
||||||
|
user interfaces, and other qualities: Oracle, Sybase,
|
||||||
|
Ingres, SQL Server, Access, Outlook,
|
||||||
|
Excel, IBM DB2, Paradox, MySQL, MSQL, POSTgres, and many
|
||||||
|
others. For this reason a program designed to use
|
||||||
|
an SQL database as its data storage mechanism can
|
||||||
|
easily be ported from one SQL data manager to another,
|
||||||
|
possibly on different platforms. In fact the same
|
||||||
|
program can seamlessly use several backends and/or
|
||||||
|
import/export data between different data base platforms
|
||||||
|
with trivial ease.
|
||||||
|
No other paradigm offers such flexibility at the moment.
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
Another advantage which is not as immediately
|
||||||
|
obvious is that the relational model and the SQL
|
||||||
|
query language are easily understood by semi-technical
|
||||||
|
and non-technical professionals, such as business
|
||||||
|
people and accountants. Human resources managers
|
||||||
|
who would be terrified by an object model diagram
|
||||||
|
or a snippet of code that resembles a conventional
|
||||||
|
programming language will frequently feel quite at
|
||||||
|
ease with a relational model which resembles the
|
||||||
|
sort of tabular data they deal with on paper in
|
||||||
|
reports and forms on a daily basis. With a little training the
|
||||||
|
same HR managers may be able to translate the request
|
||||||
|
"Who are the drinkers who like bud and frequent cheers?"
|
||||||
|
into the SQL query
|
||||||
|
""")
|
||||||
|
pre("""
|
||||||
|
select drinker
|
||||||
|
from frequents
|
||||||
|
where bar='cheers'
|
||||||
|
and drinker in (
|
||||||
|
select drinker
|
||||||
|
from likes
|
||||||
|
where beer='bud')
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
(or at least they have some hope of understanding
|
||||||
|
the query once it is written by a technical person
|
||||||
|
or generated by a GUI interface tool). Thus the use
|
||||||
|
of SQL and the relational model enables communication
|
||||||
|
between different communities which must understand
|
||||||
|
and interact with stored information. In contrast
|
||||||
|
many other approaches cannot be understood easily
|
||||||
|
by people without extensive programming experience.
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
Furthermore the declarative nature of SQL
|
||||||
|
lends itself to automatic query optimization,
|
||||||
|
and engines such as Gadfly can automatically translate a user query
|
||||||
|
into an optimized query plan which takes
|
||||||
|
advantage of available indices and other data characteristics.
|
||||||
|
In contrast more navigational techniques require the application
|
||||||
|
program itself to optimize the accesses to the database and
|
||||||
|
explicitly make use of indices.
|
||||||
|
""")
|
||||||
|
|
||||||
|
# HACK
|
||||||
|
Elements.append(PageBreak())
|
||||||
|
|
||||||
|
p("""
|
||||||
|
While it must be admitted that there are application
|
||||||
|
domains such as computer aided engineering design where
|
||||||
|
the relational model is unnatural, it is also important
|
||||||
|
to recognize that for many application domains (such
|
||||||
|
as scheduling, accounting, inventory, finance, personal
|
||||||
|
information management, electronic mail) the relational
|
||||||
|
model is a very natural fit and the SQL query language
|
||||||
|
make most accesses to the underlying data (even sophisticated
|
||||||
|
ones) straightforward. """)
|
||||||
|
|
||||||
|
p("""For an example of a moderately
|
||||||
|
sophisticated query using the tables given above,
|
||||||
|
the following query lists the drinkers who frequent lolas bar
|
||||||
|
and like at least two beers not served by lolas
|
||||||
|
""")
|
||||||
|
|
||||||
|
if 0:
|
||||||
|
go()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
select f.drinker
|
||||||
|
from frequents f, likes l
|
||||||
|
where f.drinker=l.drinker and f.bar='lolas'
|
||||||
|
and l.beer not in
|
||||||
|
(select beer from serves where bar='lolas')
|
||||||
|
group by f.drinker
|
||||||
|
having count(distinct beer)>=2
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
yielding the result
|
||||||
|
""")
|
||||||
|
pre("""
|
||||||
|
DRINKER
|
||||||
|
=======
|
||||||
|
norm
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
Experience shows that queries of this sort are actually
|
||||||
|
quite common in many applications, and are often much more
|
||||||
|
difficult to formulate using some navigational database
|
||||||
|
organizations, such as some "object oriented" database
|
||||||
|
paradigms.
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
Certainly,
|
||||||
|
SQL does not provide all you need to interact with
|
||||||
|
databases -- in order to do "real work" with SQL you
|
||||||
|
need to use SQL and at least one other language
|
||||||
|
(such as C, Pascal, C++, Perl, Python, TCL, Visual Basic
|
||||||
|
or others) to do work (such as readable formatting a report
|
||||||
|
from raw data) that SQL was not designed to do.
|
||||||
|
""")
|
||||||
|
|
||||||
|
header("Why Gadfly 1?")
|
||||||
|
|
||||||
|
p("""Gadfly 1.0 is an SQL based relational database implementation
|
||||||
|
implemented entirely in the Python programming language, with
|
||||||
|
optional fast data structure accellerators implemented in the
|
||||||
|
C programming language. Gadfly is relatively small, highly portable,
|
||||||
|
very easy to use (especially for programmers with previous experience
|
||||||
|
with SQL databases such as MS Access or Oracle), and reasonably
|
||||||
|
fast (especially when the kjbuckets C accellerators are used).
|
||||||
|
For moderate sized problems Gadfly offers a fairly complete
|
||||||
|
set of features such as transaction semantics, failure recovery,
|
||||||
|
and a TCP/IP based client/server mode (Please see [Gadfly] for
|
||||||
|
detailed discussion).""")
|
||||||
|
|
||||||
|
|
||||||
|
header("Why Gadfly 2?")
|
||||||
|
|
||||||
|
p("""Gadfly 1.0 also has significant limitations. An active Gadfly
|
||||||
|
1.0 database keeps all data in (virtual) memory, and hence a Gadfly
|
||||||
|
1.0 database is limited in size to available virtual memory. Important
|
||||||
|
features such as date/time/interval operations, regular expression
|
||||||
|
matching and other standard SQL features are not implemented in
|
||||||
|
Gadfly 1.0. The optimizer and the query evaluator perform optimizations
|
||||||
|
using properties of the equality predicate but do not optimize
|
||||||
|
using properties of inequalities such as BETWEEN or less-than.
|
||||||
|
It is possible to add "extension views" to a Gadfly
|
||||||
|
1.0 database, but the mechanism is somewhat clumsy and indices
|
||||||
|
over extension views are not well supported. The features of Gadfly
|
||||||
|
2.0 discussed here attempt to address these deficiencies by providing
|
||||||
|
a uniform extension model that permits addition of alternate table,
|
||||||
|
function, and predicate implementations.""")
|
||||||
|
|
||||||
|
p("""Other deficiencies, such as missing constructs like "ALTER
|
||||||
|
TABLE" and the lack of outer joins and NULL values are not
|
||||||
|
addressed here, although they may be addressed in Gadfly 2.0 or
|
||||||
|
a later release. This paper also does not intend to explain
|
||||||
|
the complete operations of the internals; it is intended to provide
|
||||||
|
at least enough information to understand the basic mechanisms
|
||||||
|
for extending gadfly.""")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
p("""Some concepts and definitions provided next help with the description
|
||||||
|
of the gadfly interfaces. [Note: due to the terseness of this
|
||||||
|
format the ensuing is not a highly formal presentation, but attempts
|
||||||
|
to approach precision where precision is important.]""")
|
||||||
|
|
||||||
|
header("The semilattice of substitutions")
|
||||||
|
|
||||||
|
p("""Underlying the gadfly implementation are the basic concepts
|
||||||
|
associated with substitutions. A substitution is a mapping
|
||||||
|
of attribute names to values (implemented in gadfly using kjbuckets.kjDict
|
||||||
|
objects). Here an attribute refers to some sort of "descriptive
|
||||||
|
variable", such as NAME and a value is an assignment for that variable,
|
||||||
|
like "Dave Ascher". In Gadfly a table is implemented as a sequence
|
||||||
|
of substitutions, and substitutions are used in many other ways as well.
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
For example consider the substitutions""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
A = [DRINKER=>'sam']
|
||||||
|
B = [DRINKER=>'sam', BAR=>'cheers']
|
||||||
|
C = [DRINKER=>'woody', BEER=>'bud']
|
||||||
|
D = [DRINKER=>'sam', BEER=>'mickies']
|
||||||
|
E = [DRINKER=>'sam', BAR=>'cheers', BEER=>'mickies']
|
||||||
|
F = [DRINKER=>'sam', BEER=>'mickies']
|
||||||
|
G = [BEER=>'bud', BAR=>'lolas']
|
||||||
|
H = [] # the empty substitution
|
||||||
|
I = [BAR=>'cheers', CAPACITY=>300]""")
|
||||||
|
|
||||||
|
p("""A trivial but important observation is that since substitutions
|
||||||
|
are mappings, no attribute can assume more than one value in a
|
||||||
|
substitution. In the operations described below whenever an operator
|
||||||
|
"tries" to assign more than one value to an attribute
|
||||||
|
the operator yields an "overdefined" or "inconsistent"
|
||||||
|
result.""")
|
||||||
|
|
||||||
|
header("Information Semi-order:")
|
||||||
|
|
||||||
|
p("""Substitution B is said to be
|
||||||
|
more informative than A because B agrees with all assignments
|
||||||
|
in A (in addition to providing more information as well). Similarly
|
||||||
|
we say that E is more informative than A, B, D, F. and H but E
|
||||||
|
is not more informative than the others since, for example G disagrees
|
||||||
|
with E on the value assigned to the BEER attribute and I provides
|
||||||
|
additional CAPACITY information not provided in E.""")
|
||||||
|
|
||||||
|
header("Joins and Inconsistency:")
|
||||||
|
|
||||||
|
p("""A join of two substitutions
|
||||||
|
X and Y is the least informative substitution Z such that Z is
|
||||||
|
more informative (or equally informative) than both X and Y. For
|
||||||
|
example B is the join of B with A, E is the join of B with D and""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
E join I =
|
||||||
|
[DRINKER=>'sam', BAR=>'cheers', BEER=>'mickies', CAPACITY=>300]""")
|
||||||
|
|
||||||
|
p("""For any two substitutions either (1) they disagree on the value
|
||||||
|
assigned to some attribute and have no join or (2) they agree
|
||||||
|
on all common attributes (if there are any) and their join is
|
||||||
|
the union of all (name, value) assignments in both substitutions.
|
||||||
|
Written in terms of kjbucket.kjDict operations two kjDicts X and
|
||||||
|
Y have a join Z = (X+Y) if and only if Z.Clean() is not None.
|
||||||
|
Two substitutions that have no join are said to be inconsistent.
|
||||||
|
For example I and G are inconsistent since they disagree on
|
||||||
|
the value assigned to the BAR attribute and therefore have no
|
||||||
|
join. The algebra of substitutions with joins technically defines
|
||||||
|
an abstract algebraic structure called a semilattice.""")
|
||||||
|
|
||||||
|
header("Name space remapping")
|
||||||
|
|
||||||
|
p("""Another primitive operation over substitutions is the remap
|
||||||
|
operation S2 = S.remap(R) where S is a substitution and R is a
|
||||||
|
graph of attribute names and S2 is a substitution. This operation
|
||||||
|
is defined to produce the substitution S2 such that""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
Name=>Value in S2 if and only if
|
||||||
|
Name1=>Value in S and Name<=Name1 in R
|
||||||
|
""")
|
||||||
|
|
||||||
|
p("""or if there is no such substitution S2 the remap value is said
|
||||||
|
to be overdefined.""")
|
||||||
|
|
||||||
|
p("""For example the remap operation may be used to eliminate attributes
|
||||||
|
from a substitution. For example""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
E.remap([DRINKER<=DRINKER, BAR<=BAR])
|
||||||
|
= [DRINKER=>'sam', BAR=>'cheers']
|
||||||
|
""")
|
||||||
|
|
||||||
|
p("""Illustrating that remapping using the [DRINKER<=DRINKER,
|
||||||
|
BAR<=BAR] graph eliminates all attributes except DRINKER and
|
||||||
|
BAR, such as BEER. More generally remap can be used in this way
|
||||||
|
to implement the classical relational projection operation. (See [Korth and Silberschatz]
|
||||||
|
for a detailed discussion of the projection operator and other relational
|
||||||
|
algebra operators such as selection, rename, difference and joins.)""")
|
||||||
|
|
||||||
|
p("""The remap operation can also be used to implement "selection
|
||||||
|
on attribute equality". For example if we are interested
|
||||||
|
in the employee names of employees who are their own bosses we
|
||||||
|
can use the remapping graph""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
R1 = [NAME<=NAME, NAME<=BOSS]
|
||||||
|
""")
|
||||||
|
|
||||||
|
p("""and reject substitutions where remapping using R1 is overdefined.
|
||||||
|
For example""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
S1 = [NAME=>'joe', BOSS=>'joe']
|
||||||
|
S1.remap(R1) = [NAME=>'joe']
|
||||||
|
S2 = [NAME=>'fred', BOSS=>'joe']
|
||||||
|
S2.remap(R1) is overdefined.
|
||||||
|
""")
|
||||||
|
|
||||||
|
p("""The last remap is overdefined because the NAME attribute cannot
|
||||||
|
assume both the values 'fred' and 'joe' in a substitution.""")
|
||||||
|
|
||||||
|
p("""Furthermore, of course, the remap operation can be used to
|
||||||
|
"rename attributes" or "copy attribute values"
|
||||||
|
in substitutions. Note below that the missing attribute CAPACITY
|
||||||
|
in B is effectively ignored in the remapping operation.""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
B.remap([D<=DRINKER, B<=BAR, B2<=BAR, C<=CAPACITY])
|
||||||
|
= [D=>'sam', B=>'cheers', B2=>'cheers']
|
||||||
|
""")
|
||||||
|
|
||||||
|
p("""More interestingly, a single remap operation can be used to
|
||||||
|
perform a combination of renaming, projection, value copying,
|
||||||
|
and attribute equality selection as one operation. In kjbuckets the remapper
|
||||||
|
graph is implemented using a kjbuckets.kjGraph and the remap operation
|
||||||
|
is an intrinsic method of kjbuckets.kjDict objects.""")
|
||||||
|
|
||||||
|
header("Generalized Table Joins and the Evaluator Mainloop""")
|
||||||
|
|
||||||
|
p("""Strictly speaking the Gadfly 2.0 query evaluator only uses
|
||||||
|
the join and remap operations as its "basic assembly language"
|
||||||
|
-- all other computations, including inequality comparisons and
|
||||||
|
arithmetic, are implemented externally to the evaluator as "generalized
|
||||||
|
table joins." """)
|
||||||
|
|
||||||
|
p("""A table is a sequence of substitutions (which in keeping with
|
||||||
|
SQL semantics may contain redundant entries). The join between
|
||||||
|
two tables T1 and T2 is the sequence of all possible defined joins
|
||||||
|
between pairs of elements from the two tables. Procedurally we
|
||||||
|
might compute the join as""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
T1JoinT2 = empty
|
||||||
|
for t1 in T1:
|
||||||
|
for t2 in T2:
|
||||||
|
if t1 join t2 is defined:
|
||||||
|
add t1 join t2 to T1joinT2""")
|
||||||
|
|
||||||
|
p("""In general circumstances this intuitive implementation is a
|
||||||
|
very inefficient way to compute the join, and Gadfly almost always
|
||||||
|
uses other methods, particularly since, as described below, a
|
||||||
|
"generalized table" can have an "infinite"
|
||||||
|
number of entries.""")
|
||||||
|
|
||||||
|
p("""For an example of a table join consider the EMPLOYEES table
|
||||||
|
containing""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
[NAME=>'john', JOB=>'executive']
|
||||||
|
[NAME=>'sue', JOB=>'programmer']
|
||||||
|
[NAME=>'eric', JOB=>'peon']
|
||||||
|
[NAME=>'bill', JOB=>'peon']
|
||||||
|
""")
|
||||||
|
|
||||||
|
p("""and the ACTIVITIES table containing""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
[JOB=>'peon', DOES=>'windows']
|
||||||
|
[JOB=>'peon', DOES=>'floors']
|
||||||
|
[JOB=>'programmer', DOES=>'coding']
|
||||||
|
[JOB=>'secretary', DOES=>'phone']""")
|
||||||
|
|
||||||
|
p("""then the join between EMPLOYEES and ACTIVITIES must containining""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
[NAME=>'sue', JOB=>'programmer', DOES=>'coding']
|
||||||
|
[NAME=>'eric', JOB=>'peon', DOES=>'windows']
|
||||||
|
[NAME=>'bill', JOB=>'peon', DOES=>'windows']
|
||||||
|
[NAME=>'eric', JOB=>'peon', DOES=>'floors']
|
||||||
|
[NAME=>'bill', JOB=>'peon', DOES=>'floors']""")
|
||||||
|
|
||||||
|
p("""A compiled gadfly subquery ultimately appears to the evaluator
|
||||||
|
as a sequence of generalized tables that must be joined (in combination
|
||||||
|
with certain remapping operations that are beyond the scope of
|
||||||
|
this discussion). The Gadfly mainloop proceeds following the very
|
||||||
|
loose pseudocode:""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
Subs = [ [] ] # the unary sequence containing "true"
|
||||||
|
While some table hasn't been chosen yet:
|
||||||
|
Choose an unchosen table with the least cost join estimate.
|
||||||
|
Subs = Subs joined with the chosen table
|
||||||
|
return Subs""")
|
||||||
|
|
||||||
|
p("""[Note that it is a property of the join operation that the
|
||||||
|
order in which the joins are carried out will not affect the result,
|
||||||
|
so the greedy strategy of evaluating the "cheapest join next"
|
||||||
|
will not effect the result. Also note that the treatment of logical
|
||||||
|
OR and NOT as well as EXIST, IN, UNION, and aggregation and so
|
||||||
|
forth are not discussed here, even though they do fit into this
|
||||||
|
approach.]""")
|
||||||
|
|
||||||
|
p("""The actual implementation is a bit more complex than this,
|
||||||
|
but the above outline may provide some useful intuition. The "cost
|
||||||
|
estimation" step and the implementation of the join operation
|
||||||
|
itself are left up to the generalized table object implementation.
|
||||||
|
A table implementation has the ability to give an "infinite"
|
||||||
|
cost estimate, which essentially means "don't join me in
|
||||||
|
yet under any circumstances." """)
|
||||||
|
|
||||||
|
header("Implementing Functions")
|
||||||
|
|
||||||
|
p("""As mentioned above operations such as arithmetic are implemented
|
||||||
|
using generalized tables. For example the arithmetic Add operation
|
||||||
|
is implemented in Gadfly internally as an "infinite generalized
|
||||||
|
table" containing all possible substitutions""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
ARG0=>a, ARG1=>b, RESULT=>a+b]
|
||||||
|
""")
|
||||||
|
|
||||||
|
p("""Where a and b are all possible values which can be summed.
|
||||||
|
Clearly, it is not possible to enumerate this table, but given
|
||||||
|
a sequence of substitutions with defined values for ARG0 and ARG1
|
||||||
|
such as""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
[ARG0=>1, ARG1=-4]
|
||||||
|
[ARG0=>2.6, ARG1=50]
|
||||||
|
[ARG0=>99, ARG1=1]
|
||||||
|
""")
|
||||||
|
|
||||||
|
p("""it is possible to implement a "join operation" against
|
||||||
|
this sequence that performs the same augmentation as a join with
|
||||||
|
the infinite table defined above:""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
[ARG0=>1, ARG1=-4, RESULT=-3]
|
||||||
|
[ARG0=>2.6, ARG1=50, RESULT=52.6]
|
||||||
|
[ARG0=>99, ARG1=1, RESULT=100]
|
||||||
|
""")
|
||||||
|
|
||||||
|
p("""Furthermore by giving an "infinite estimate" for
|
||||||
|
all attempts to evaluate the join where ARG0 and ARG1 are not
|
||||||
|
available the generalized table implementation for the addition
|
||||||
|
operation can refuse to compute an "infinite join." """)
|
||||||
|
|
||||||
|
p("""More generally all functions f(a,b,c,d) are represented in
|
||||||
|
gadfly as generalized tables containing all possible relevant
|
||||||
|
entries""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
[ARG0=>a, ARG1=>b, ARG2=>c, ARG3=>d, RESULT=>f(a,b,c,d)]""")
|
||||||
|
|
||||||
|
p("""and the join estimation function refuses all attempts to perform
|
||||||
|
a join unless all the arguments are provided by the input substitution
|
||||||
|
sequence.""")
|
||||||
|
|
||||||
|
header("Implementing Predicates")
|
||||||
|
|
||||||
|
p("""Similarly to functions, predicates such as less-than and BETWEEN
|
||||||
|
and LIKE are implemented using the generalized table mechanism.
|
||||||
|
For example the "x BETWEEN y AND z" predicate is implemented
|
||||||
|
as a generalized table "containing" all possible""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
[ARG0=>a, ARG1=>b, ARG2=>c]""")
|
||||||
|
|
||||||
|
p("""where b<a<c. Furthermore joins with this table are not
|
||||||
|
permitted unless all three arguments are available in the sequence
|
||||||
|
of input substitutions.""")
|
||||||
|
|
||||||
|
header("Some Gadfly extension interfaces")
|
||||||
|
|
||||||
|
p("""A gadfly database engine may be extended with user defined
|
||||||
|
functions, predicates, and alternative table and index implementations.
|
||||||
|
This section snapshots several Gadfly 2.0 interfaces, currently under
|
||||||
|
development and likely to change before the package is released.""")
|
||||||
|
|
||||||
|
p("""The basic interface for adding functions and predicates (logical tests)
|
||||||
|
to a gadfly engine are relatively straightforward. For example to add the
|
||||||
|
ability to match a regular expression within a gadfly query use the
|
||||||
|
following implementation.""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
from re import match
|
||||||
|
|
||||||
|
def addrematch(gadflyinstance):
|
||||||
|
gadflyinstance.add_predicate("rematch", match)
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
Then upon connecting to the database execute
|
||||||
|
""")
|
||||||
|
pre("""
|
||||||
|
g = gadfly(...)
|
||||||
|
...
|
||||||
|
addrematch(g)
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
In this case the "semijoin operation" associated with the new predicate
|
||||||
|
"rematch" is automatically generated, and after the add_predicate
|
||||||
|
binding operation the gadfly instance supports queries such as""")
|
||||||
|
pre("""
|
||||||
|
select drinker, beer
|
||||||
|
from likes
|
||||||
|
where rematch('b*', beer) and drinker not in
|
||||||
|
(select drinker from frequents where rematch('c*', bar))
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
By embedding the "rematch" operation within the query the SQL
|
||||||
|
engine can do "more work" for the programmer and reduce or eliminate the
|
||||||
|
need to process the query result externally to the engine.
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
In a similar manner functions may be added to a gadfly instance,""")
|
||||||
|
pre("""
|
||||||
|
def modulo(x,y):
|
||||||
|
return x % y
|
||||||
|
|
||||||
|
def addmodulo(gadflyinstance):
|
||||||
|
gadflyinstance.add_function("modulo", modulo)
|
||||||
|
|
||||||
|
...
|
||||||
|
g = gadfly(...)
|
||||||
|
...
|
||||||
|
addmodulo(g)
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
Then after the binding the modulo function can be used whereever
|
||||||
|
an SQL expression can occur.
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
Adding alternative table implementations to a Gadfly instance
|
||||||
|
is more interesting and more difficult. An "extension table" implementation
|
||||||
|
must conform to the following interface:""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
# get the kjbuckets.kjSet set of attribute names for this table
|
||||||
|
names = table.attributes()
|
||||||
|
|
||||||
|
# estimate the difficulty of evaluating a join given known attributes
|
||||||
|
# return None for "impossible" or n>=0 otherwise with larger values
|
||||||
|
# indicating greater difficulty or expense
|
||||||
|
estimate = table.estimate(known_attributes)
|
||||||
|
|
||||||
|
# return the join of the rows of the table with
|
||||||
|
# the list of kjbuckets.kjDict mappings as a list of mappings.
|
||||||
|
resultmappings = table.join(listofmappings)
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
In this case add the table to a gadfly instance using""")
|
||||||
|
pre("""
|
||||||
|
gadflyinstance.add_table("table_name", table)
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
For example to add a table which automatically queries filenames
|
||||||
|
in the filesystems of the host computer a gadfly instance could
|
||||||
|
be augmented with a GLOB table implemented using the standard
|
||||||
|
library function glob.glob as follows:""")
|
||||||
|
pre("""
|
||||||
|
import kjbuckets
|
||||||
|
|
||||||
|
class GlobTable:
|
||||||
|
def __init__(self): pass
|
||||||
|
|
||||||
|
def attributes(self):
|
||||||
|
return kjbuckets.kjSet("PATTERN", "NAME")
|
||||||
|
|
||||||
|
def estimate(self, known_attributes):
|
||||||
|
if known_attributes.member("PATTERN"):
|
||||||
|
return 66 # join not too difficult
|
||||||
|
else:
|
||||||
|
return None # join is impossible (must have PATTERN)
|
||||||
|
|
||||||
|
def join(self, listofmappings):
|
||||||
|
from glob import glob
|
||||||
|
result = []
|
||||||
|
for m in listofmappings:
|
||||||
|
pattern = m["PATTERN"]
|
||||||
|
for name in glob(pattern):
|
||||||
|
newmapping = kjbuckets.kjDict(m)
|
||||||
|
newmapping["NAME"] = name
|
||||||
|
if newmapping.Clean():
|
||||||
|
result.append(newmapping)
|
||||||
|
return result
|
||||||
|
|
||||||
|
...
|
||||||
|
gadfly_instance.add_table("GLOB", GlobTable())
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
Then one could formulate queries such as "list the files in directories
|
||||||
|
associated with packages installed by guido"
|
||||||
|
""")
|
||||||
|
pre("""
|
||||||
|
select g.name as filename
|
||||||
|
from packages p, glob g
|
||||||
|
where p.installer = 'guido' and g.pattern=p.root_directory
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
Note that conceptually the GLOB table is an infinite table including
|
||||||
|
all filenames on the current computer in the "NAME" column, paired with
|
||||||
|
a potentially infinite number of patterns.
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
More interesting examples would allow queries to remotely access
|
||||||
|
data served by an HTTP server, or from any other resource.
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
Furthermore an extension table can be augmented with update methods
|
||||||
|
""")
|
||||||
|
pre("""
|
||||||
|
table.insert_rows(listofmappings)
|
||||||
|
table.update_rows(oldlist, newlist)
|
||||||
|
table.delete_rows(oldlist)
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
Note: at present the implementation does not enforce recovery or
|
||||||
|
transaction semantics for updates to extension tables, although this
|
||||||
|
may change in the final release.
|
||||||
|
""")
|
||||||
|
p("""
|
||||||
|
The table implementation is free to provide its own implementations of
|
||||||
|
indices which take advantage of data provided by the join argument.
|
||||||
|
""")
|
||||||
|
|
||||||
|
header("Efficiency Notes")
|
||||||
|
|
||||||
|
p("""The following thought experiment attempts to explain why the
|
||||||
|
Gadfly implementation is surprisingly fast considering that it
|
||||||
|
is almost entirely implemented in Python (an interpreted programming
|
||||||
|
language which is not especially fast when compared to alternatives).
|
||||||
|
Although Gadfly is quite complex, at an abstract level the process
|
||||||
|
of query evaluation boils down to a series of embedded loops.
|
||||||
|
Consider the following nested loops:""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
iterate 1000:
|
||||||
|
f(...) # fixed cost of outer loop
|
||||||
|
iterate 10:
|
||||||
|
g(...) # fixed cost of middle loop
|
||||||
|
iterate 10:
|
||||||
|
# the real work (string parse, matrix mul, query eval...)
|
||||||
|
h(...)""")
|
||||||
|
|
||||||
|
p("""In my experience many computations follow this pattern where
|
||||||
|
f, g, are complex, dynamic, special purpose and h is simple, general
|
||||||
|
purpose, static. Some example computations that follow this pattern
|
||||||
|
include: file massaging (perl), matrix manipulation (python, tcl),
|
||||||
|
database/cgi page generation, and vector graphics/imaging.""")
|
||||||
|
|
||||||
|
p("""Suppose implementing f, g, h in python is easy but result in
|
||||||
|
execution times10 times slower than a much harder implementation
|
||||||
|
in C, choosing arbitrary and debatable numbers assume each function
|
||||||
|
call consumes 1 tick in C, 5 ticks in java, 10 ticks in python
|
||||||
|
for a straightforward implementation of each function f, g, and
|
||||||
|
h. Under these conditions we get the following cost analysis,
|
||||||
|
eliminating some uninteresting combinations, of implementing the
|
||||||
|
function f, g, and h in combinations of Python, C and java:""")
|
||||||
|
|
||||||
|
pre("""
|
||||||
|
COST | FLANG | GLANG | HLANG
|
||||||
|
==================================
|
||||||
|
111000 | C | C | C
|
||||||
|
115000 | java | C | C
|
||||||
|
120000 | python | C | C
|
||||||
|
155000 | java | java | C
|
||||||
|
210000 | python | python | C
|
||||||
|
555000 | java | java | java
|
||||||
|
560000 | python | java | java
|
||||||
|
610000 | python | python | java
|
||||||
|
1110000 | python | python | python
|
||||||
|
""")
|
||||||
|
|
||||||
|
p("""Note that moving only the innermost loop to C (python/python/C)
|
||||||
|
speeds up the calculation by half an order of magnitude compared
|
||||||
|
to the python-only implementation and brings the speed to within
|
||||||
|
a factor of 2 of an implementation done entirely in C.""")
|
||||||
|
|
||||||
|
p("""Although this artificial and contrived thought experiment is
|
||||||
|
far from conclusive, we may be tempted to draw the conclusion
|
||||||
|
that generally programmers should focus first on obtaining a working
|
||||||
|
implementation (because as John Ousterhout is reported to have
|
||||||
|
said "the biggest performance improvement is the transition
|
||||||
|
from non-working to working") using the methodology that
|
||||||
|
is most likely to obtain a working solution the quickest (Python). Only then if the performance
|
||||||
|
is inadequate should the programmer focus on optimizing
|
||||||
|
the inner most loops, perhaps moving them to a very efficient
|
||||||
|
implementation (C). Optimizing the outer loops will buy little
|
||||||
|
improvement, and should be done later, if ever.""")
|
||||||
|
|
||||||
|
p("""This was precisely the strategy behind the gadfly implementations,
|
||||||
|
where most of the inner loops are implemented in the kjbuckets
|
||||||
|
C extension module and the higher level logic is all in Python.
|
||||||
|
This also explains why gadfly appears to be "slower"
|
||||||
|
for simple queries over small data sets, but seems to be relatively
|
||||||
|
"faster" for more complex queries over larger data sets,
|
||||||
|
since larger queries and data sets take better advantage of the
|
||||||
|
optimized inner loops.""")
|
||||||
|
|
||||||
|
header("A Gadfly variant for OLAP?")
|
||||||
|
|
||||||
|
p("""In private correspondence Andy Robinson points out that the
|
||||||
|
basic logical design underlying Gadfly could be adapted to provide
|
||||||
|
Online Analytical Processing (OLAP) and other forms of data warehousing
|
||||||
|
and data mining. Since SQL is not particularly well suited for
|
||||||
|
the kinds of requests common in these domains the higher level
|
||||||
|
interfaces would require modification, but the underlying logic
|
||||||
|
of substitutions and name mappings seems to be appropriate.""")
|
||||||
|
|
||||||
|
header("Conclusion")
|
||||||
|
|
||||||
|
p("""The revamped query engine design in Gadfly 2 supports
|
||||||
|
a flexible and general extension methodology that permits programmers
|
||||||
|
to extend the gadfly engine to include additional computations
|
||||||
|
and access to remote data sources. Among other possibilities this
|
||||||
|
will permit the gadfly engine to make use of disk based indexed
|
||||||
|
tables and to dynamically retrieve information from remote data
|
||||||
|
sources (such as an Excel spreadsheet or an Oracle database).
|
||||||
|
These features will make gadfly a very useful tool for data manipulation
|
||||||
|
and integration.""")
|
||||||
|
|
||||||
|
header("References")
|
||||||
|
|
||||||
|
p("""[Van Rossum] Van Rossum, Python Reference Manual, Tutorial, and Library Manuals,
|
||||||
|
please look to http://www.python.org
|
||||||
|
for the latest versions, downloads and links to printed versions.""")
|
||||||
|
|
||||||
|
p("""[O'Neill] O'Neill, P., Data Base Principles, Programming, Performance,
|
||||||
|
Morgan Kaufmann Publishers, San Francisco, 1994.""")
|
||||||
|
|
||||||
|
p("""[Korth and Silberschatz] Korth, H. and Silberschatz, A. and Sudarshan, S.
|
||||||
|
Data Base System Concepts, McGraw-Hill Series in Computer Science, Boston,
|
||||||
|
1997""")
|
||||||
|
|
||||||
|
p("""[Gadfly]Gadfly: SQL Relational Database in Python,
|
||||||
|
http://www.chordate.com/kwParsing/gadfly.html""")
|
||||||
|
|
||||||
|
go()
|
|
@ -0,0 +1,56 @@
|
||||||
|
This contains a number of benchmarks and demos
|
||||||
|
based on Homer's Odyssey (which is widely available
|
||||||
|
in plain, line-oriented text format). There are a large
|
||||||
|
selection of online books at:
|
||||||
|
http://classics.mit.edu/
|
||||||
|
|
||||||
|
|
||||||
|
Our distribution ships with just the first chapter
|
||||||
|
in odyssey.txt. For a more meaningful speed test,
|
||||||
|
download the full copy from
|
||||||
|
http://www.reportlab.com/ftp/odyssey.full.zip
|
||||||
|
or
|
||||||
|
ftp://ftp.reportlab.com/odyssey.full.zip
|
||||||
|
and unzip to extract odyssey.full.txt (608kb).
|
||||||
|
|
||||||
|
Benchmark speed depends quite critically
|
||||||
|
on the presence of our accelerator module,
|
||||||
|
_rl_accel, which is a C (or Java) extension.
|
||||||
|
Serious users ought to compile or download this!
|
||||||
|
|
||||||
|
The times quoted are from one machine (Andy Robinson's
|
||||||
|
home PC, approx 1.2Ghz 128Mb Ram, Win2k in Sep 2003)
|
||||||
|
in order to give a rough idea of what features cost
|
||||||
|
what performance.
|
||||||
|
|
||||||
|
|
||||||
|
The tests are as follows:
|
||||||
|
|
||||||
|
(1) odyssey.py (produces odyssey.pdf)
|
||||||
|
This demo takes a large volume of text and prints it
|
||||||
|
in the simplest way possible. It is a demo of the
|
||||||
|
basic technique of looping down a page manually and
|
||||||
|
breaking at the bottom. On my 1.2 Ghz machine this takes
|
||||||
|
1.91 seconds (124 pages per second)
|
||||||
|
|
||||||
|
(2) fodyssey.py (produces fodyssey.pdf)
|
||||||
|
This is a 'flowing document' we parse the file and
|
||||||
|
throw away line breaks to make proper paragraphs.
|
||||||
|
The Platypus framework renders these. This necessitates
|
||||||
|
measuring the width of every word in every paragraph
|
||||||
|
for wrapping purposes.
|
||||||
|
|
||||||
|
This takes 3.27 seconds on the same machine. Paragraph
|
||||||
|
wrapping basically doubles the work. The text is more
|
||||||
|
compact with about 50% more words per page. Very roughly,
|
||||||
|
we can wrap 40 pages of ten-point text per second and save
|
||||||
|
to PDF.
|
||||||
|
|
||||||
|
(3) dodyssey.py (produced dodyssey.pdf)
|
||||||
|
This is a slightly fancier version which uses different
|
||||||
|
page templates (one column for first page in a chapter,
|
||||||
|
two column for body poages). The additional layout logic
|
||||||
|
adds about 15%, going up to 3.8 seconds. This is probably
|
||||||
|
a realistic benchmark for a simple long text document
|
||||||
|
with a single pass. Documents doing cross-references
|
||||||
|
and a table of contents might need twice as long.
|
|
@ -0,0 +1,254 @@
|
||||||
|
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||||
|
#see license.txt for license details
|
||||||
|
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/odyssey/dodyssey.py
|
||||||
|
__version__=''' $Id: dodyssey.py 2856 2006-05-11 09:48:13Z rgbecker $ '''
|
||||||
|
__doc__=''
|
||||||
|
|
||||||
|
#REPORTLAB_TEST_SCRIPT
|
||||||
|
import sys, copy, string, os
|
||||||
|
from reportlab.platypus import *
|
||||||
|
_NEW_PARA=os.environ.get('NEW_PARA','0')[0] in ('y','Y','1')
|
||||||
|
_REDCAP=int(os.environ.get('REDCAP','0'))
|
||||||
|
_CALLBACK=os.environ.get('CALLBACK','0')[0] in ('y','Y','1')
|
||||||
|
if _NEW_PARA:
|
||||||
|
def Paragraph(s,style):
|
||||||
|
from rlextra.radxml.para import Paragraph as PPPP
|
||||||
|
return PPPP(s,style)
|
||||||
|
|
||||||
|
from reportlab.lib.units import inch
|
||||||
|
from reportlab.lib.styles import getSampleStyleSheet
|
||||||
|
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
|
||||||
|
|
||||||
|
import reportlab.rl_config
|
||||||
|
reportlab.rl_config.invariant = 1
|
||||||
|
|
||||||
|
styles = getSampleStyleSheet()
|
||||||
|
|
||||||
|
Title = "The Odyssey"
|
||||||
|
Author = "Homer"
|
||||||
|
|
||||||
|
def myTitlePage(canvas, doc):
|
||||||
|
canvas.saveState()
|
||||||
|
canvas.restoreState()
|
||||||
|
|
||||||
|
def myLaterPages(canvas, doc):
|
||||||
|
canvas.saveState()
|
||||||
|
canvas.setFont('Times-Roman',9)
|
||||||
|
canvas.drawString(inch, 0.75 * inch, "Page %d" % doc.page)
|
||||||
|
canvas.restoreState()
|
||||||
|
|
||||||
|
def go():
|
||||||
|
def myCanvasMaker(fn,**kw):
|
||||||
|
from reportlab.pdfgen.canvas import Canvas
|
||||||
|
canv = apply(Canvas,(fn,),kw)
|
||||||
|
# attach our callback to the canvas
|
||||||
|
canv.myOnDrawCB = myOnDrawCB
|
||||||
|
return canv
|
||||||
|
|
||||||
|
doc = BaseDocTemplate('dodyssey.pdf',showBoundary=0)
|
||||||
|
|
||||||
|
#normal frame as for SimpleFlowDocument
|
||||||
|
frameT = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id='normal')
|
||||||
|
|
||||||
|
#Two Columns
|
||||||
|
frame1 = Frame(doc.leftMargin, doc.bottomMargin, doc.width/2-6, doc.height, id='col1')
|
||||||
|
frame2 = Frame(doc.leftMargin+doc.width/2+6, doc.bottomMargin, doc.width/2-6,
|
||||||
|
doc.height, id='col2')
|
||||||
|
doc.addPageTemplates([PageTemplate(id='First',frames=frameT, onPage=myTitlePage),
|
||||||
|
PageTemplate(id='OneCol',frames=frameT, onPage=myLaterPages),
|
||||||
|
PageTemplate(id='TwoCol',frames=[frame1,frame2], onPage=myLaterPages),
|
||||||
|
])
|
||||||
|
doc.build(Elements,canvasmaker=myCanvasMaker)
|
||||||
|
|
||||||
|
Elements = []
|
||||||
|
|
||||||
|
ChapterStyle = copy.deepcopy(styles["Heading1"])
|
||||||
|
ChapterStyle.alignment = TA_CENTER
|
||||||
|
ChapterStyle.fontsize = 14
|
||||||
|
InitialStyle = copy.deepcopy(ChapterStyle)
|
||||||
|
InitialStyle.fontsize = 16
|
||||||
|
InitialStyle.leading = 20
|
||||||
|
PreStyle = styles["Code"]
|
||||||
|
|
||||||
|
def newPage():
|
||||||
|
Elements.append(PageBreak())
|
||||||
|
|
||||||
|
chNum = 0
|
||||||
|
def myOnDrawCB(canv,kind,label):
|
||||||
|
print 'myOnDrawCB(%s)'%kind, 'Page number=', canv.getPageNumber(), 'label value=', label
|
||||||
|
|
||||||
|
def chapter(txt, style=ChapterStyle):
|
||||||
|
global chNum
|
||||||
|
Elements.append(NextPageTemplate('OneCol'))
|
||||||
|
newPage()
|
||||||
|
chNum = chNum + 1
|
||||||
|
if _NEW_PARA or not _CALLBACK:
|
||||||
|
Elements.append(Paragraph(('chap %d'%chNum)+txt, style))
|
||||||
|
else:
|
||||||
|
Elements.append(Paragraph(('foo<onDraw name="myOnDrawCB" label="chap %d"/> '%chNum)+txt, style))
|
||||||
|
Elements.append(Spacer(0.2*inch, 0.3*inch))
|
||||||
|
if useTwoCol:
|
||||||
|
Elements.append(NextPageTemplate('TwoCol'))
|
||||||
|
|
||||||
|
def fTitle(txt,style=InitialStyle):
|
||||||
|
Elements.append(Paragraph(txt, style))
|
||||||
|
|
||||||
|
ParaStyle = copy.deepcopy(styles["Normal"])
|
||||||
|
ParaStyle.spaceBefore = 0.1*inch
|
||||||
|
if 'right' in sys.argv:
|
||||||
|
ParaStyle.alignment = TA_RIGHT
|
||||||
|
elif 'left' in sys.argv:
|
||||||
|
ParaStyle.alignment = TA_LEFT
|
||||||
|
elif 'justify' in sys.argv:
|
||||||
|
ParaStyle.alignment = TA_JUSTIFY
|
||||||
|
elif 'center' in sys.argv or 'centre' in sys.argv:
|
||||||
|
ParaStyle.alignment = TA_CENTER
|
||||||
|
else:
|
||||||
|
ParaStyle.alignment = TA_JUSTIFY
|
||||||
|
|
||||||
|
useTwoCol = 'notwocol' not in sys.argv
|
||||||
|
|
||||||
|
def spacer(inches):
|
||||||
|
Elements.append(Spacer(0.1*inch, inches*inch))
|
||||||
|
|
||||||
|
def p(txt, style=ParaStyle):
|
||||||
|
if _REDCAP:
|
||||||
|
fs, fe = '<font color="red" size="+2">', '</font>'
|
||||||
|
n = len(txt)
|
||||||
|
for i in xrange(n):
|
||||||
|
if 'a'<=txt[i]<='z' or 'A'<=txt[i]<='Z':
|
||||||
|
txt = (txt[:i]+(fs+txt[i]+fe))+txt[i+1:]
|
||||||
|
break
|
||||||
|
if _REDCAP>=2 and n>20:
|
||||||
|
j = i+len(fs)+len(fe)+1+int((n-1)/2)
|
||||||
|
while not ('a'<=txt[j]<='z' or 'A'<=txt[j]<='Z'): j += 1
|
||||||
|
txt = (txt[:j]+('<b><i><font size="+2" color="blue">'+txt[j]+'</font></i></b>'))+txt[j+1:]
|
||||||
|
|
||||||
|
if _REDCAP==3 and n>20:
|
||||||
|
n = len(txt)
|
||||||
|
fs = '<font color="green" size="+1">'
|
||||||
|
for i in xrange(n-1,-1,-1):
|
||||||
|
if 'a'<=txt[i]<='z' or 'A'<=txt[i]<='Z':
|
||||||
|
txt = txt[:i]+((fs+txt[i]+fe)+txt[i+1:])
|
||||||
|
break
|
||||||
|
|
||||||
|
Elements.append(Paragraph(txt, style))
|
||||||
|
|
||||||
|
firstPre = 1
|
||||||
|
def pre(txt, style=PreStyle):
|
||||||
|
global firstPre
|
||||||
|
if firstPre:
|
||||||
|
Elements.append(NextPageTemplate('OneCol'))
|
||||||
|
newPage()
|
||||||
|
firstPre = 0
|
||||||
|
|
||||||
|
spacer(0.1)
|
||||||
|
p = Preformatted(txt, style)
|
||||||
|
Elements.append(p)
|
||||||
|
|
||||||
|
def parseOdyssey(fn):
|
||||||
|
from time import time
|
||||||
|
E = []
|
||||||
|
t0=time()
|
||||||
|
L = open(fn,'r').readlines()
|
||||||
|
t1 = time()
|
||||||
|
print "open(%s,'r').readlines() took %.4f seconds" %(fn,t1-t0)
|
||||||
|
for i in xrange(len(L)):
|
||||||
|
if L[i][-1]=='\012':
|
||||||
|
L[i] = L[i][:-1]
|
||||||
|
t2 = time()
|
||||||
|
print "Removing all linefeeds took %.4f seconds" %(t2-t1)
|
||||||
|
L.append('')
|
||||||
|
L.append('-----')
|
||||||
|
|
||||||
|
def findNext(L, i):
|
||||||
|
while 1:
|
||||||
|
if string.strip(L[i])=='':
|
||||||
|
del L[i]
|
||||||
|
kind = 1
|
||||||
|
if i<len(L):
|
||||||
|
while string.strip(L[i])=='':
|
||||||
|
del L[i]
|
||||||
|
|
||||||
|
if i<len(L):
|
||||||
|
kind = L[i][-1]=='-' and L[i][0]=='-'
|
||||||
|
if kind:
|
||||||
|
del L[i]
|
||||||
|
if i<len(L):
|
||||||
|
while string.strip(L[i])=='':
|
||||||
|
del L[i]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
i = i + 1
|
||||||
|
|
||||||
|
return i, kind
|
||||||
|
|
||||||
|
f = s = 0
|
||||||
|
while 1:
|
||||||
|
f, k = findNext(L,0)
|
||||||
|
if k: break
|
||||||
|
|
||||||
|
E.append([spacer,2])
|
||||||
|
E.append([fTitle,'<font color="red">%s</font>' % Title, InitialStyle])
|
||||||
|
E.append([fTitle,'<font size="-4">by</font> <font color="green">%s</font>' % Author, InitialStyle])
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
if f>=len(L): break
|
||||||
|
|
||||||
|
if string.upper(L[f][0:5])=='BOOK ':
|
||||||
|
E.append([chapter,L[f]])
|
||||||
|
f=f+1
|
||||||
|
while string.strip(L[f])=='': del L[f]
|
||||||
|
style = ParaStyle
|
||||||
|
func = p
|
||||||
|
else:
|
||||||
|
style = PreStyle
|
||||||
|
func = pre
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
s=f
|
||||||
|
f, k=findNext(L,s)
|
||||||
|
sep= (func is pre) and '\012' or ' '
|
||||||
|
E.append([func,string.join(L[s:f],sep),style])
|
||||||
|
if k: break
|
||||||
|
t3 = time()
|
||||||
|
print "Parsing into memory took %.4f seconds" %(t3-t2)
|
||||||
|
del L
|
||||||
|
t4 = time()
|
||||||
|
print "Deleting list of lines took %.4f seconds" %(t4-t3)
|
||||||
|
for i in xrange(len(E)):
|
||||||
|
apply(E[i][0],E[i][1:])
|
||||||
|
t5 = time()
|
||||||
|
print "Moving into platypus took %.4f seconds" %(t5-t4)
|
||||||
|
del E
|
||||||
|
t6 = time()
|
||||||
|
print "Deleting list of actions took %.4f seconds" %(t6-t5)
|
||||||
|
go()
|
||||||
|
t7 = time()
|
||||||
|
print "saving to PDF took %.4f seconds" %(t7-t6)
|
||||||
|
print "Total run took %.4f seconds"%(t7-t0)
|
||||||
|
|
||||||
|
import md5
|
||||||
|
print 'file digest: %s' % md5.md5(open('dodyssey.pdf','rb').read()).hexdigest()
|
||||||
|
|
||||||
|
def run():
|
||||||
|
for fn in ('odyssey.full.txt','odyssey.txt'):
|
||||||
|
if os.path.isfile(fn):
|
||||||
|
parseOdyssey(fn)
|
||||||
|
break
|
||||||
|
|
||||||
|
def doProf(profname,func,*args,**kwd):
|
||||||
|
import hotshot, hotshot.stats
|
||||||
|
prof = hotshot.Profile(profname)
|
||||||
|
prof.runcall(func)
|
||||||
|
prof.close()
|
||||||
|
stats = hotshot.stats.load(profname)
|
||||||
|
stats.strip_dirs()
|
||||||
|
stats.sort_stats('time', 'calls')
|
||||||
|
stats.print_stats(20)
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
if '--prof' in sys.argv:
|
||||||
|
doProf('dodyssey.prof',run)
|
||||||
|
else:
|
||||||
|
run()
|
|
@ -0,0 +1,165 @@
|
||||||
|
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||||
|
#see license.txt for license details
|
||||||
|
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/odyssey/fodyssey.py
|
||||||
|
__version__=''' $Id: fodyssey.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
||||||
|
__doc__=''
|
||||||
|
|
||||||
|
#REPORTLAB_TEST_SCRIPT
|
||||||
|
import sys, copy, string, os
|
||||||
|
from reportlab.platypus import *
|
||||||
|
from reportlab.lib.units import inch
|
||||||
|
from reportlab.lib.styles import getSampleStyleSheet
|
||||||
|
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
|
||||||
|
|
||||||
|
styles = getSampleStyleSheet()
|
||||||
|
|
||||||
|
Title = "The Odyssey"
|
||||||
|
Author = "Homer"
|
||||||
|
|
||||||
|
def myFirstPage(canvas, doc):
|
||||||
|
canvas.saveState()
|
||||||
|
canvas.restoreState()
|
||||||
|
|
||||||
|
def myLaterPages(canvas, doc):
|
||||||
|
canvas.saveState()
|
||||||
|
canvas.setFont('Times-Roman',9)
|
||||||
|
canvas.drawString(inch, 0.75 * inch, "Page %d" % doc.page)
|
||||||
|
canvas.restoreState()
|
||||||
|
|
||||||
|
def go():
|
||||||
|
doc = SimpleDocTemplate('fodyssey.pdf',showBoundary='showboundary' in sys.argv)
|
||||||
|
doc.allowSplitting = not 'nosplitting' in sys.argv
|
||||||
|
doc.build(Elements,myFirstPage,myLaterPages)
|
||||||
|
|
||||||
|
Elements = []
|
||||||
|
|
||||||
|
ChapterStyle = copy.copy(styles["Heading1"])
|
||||||
|
ChapterStyle.alignment = TA_CENTER
|
||||||
|
ChapterStyle.fontsize = 16
|
||||||
|
InitialStyle = copy.deepcopy(ChapterStyle)
|
||||||
|
InitialStyle.fontsize = 16
|
||||||
|
InitialStyle.leading = 20
|
||||||
|
PreStyle = styles["Code"]
|
||||||
|
|
||||||
|
def newPage():
|
||||||
|
Elements.append(PageBreak())
|
||||||
|
|
||||||
|
def chapter(txt, style=ChapterStyle):
|
||||||
|
newPage()
|
||||||
|
Elements.append(Paragraph(txt, style))
|
||||||
|
Elements.append(Spacer(0.2*inch, 0.3*inch))
|
||||||
|
|
||||||
|
def fTitle(txt,style=InitialStyle):
|
||||||
|
Elements.append(Paragraph(txt, style))
|
||||||
|
|
||||||
|
ParaStyle = copy.deepcopy(styles["Normal"])
|
||||||
|
ParaStyle.spaceBefore = 0.1*inch
|
||||||
|
if 'right' in sys.argv:
|
||||||
|
ParaStyle.alignment = TA_RIGHT
|
||||||
|
elif 'left' in sys.argv:
|
||||||
|
ParaStyle.alignment = TA_LEFT
|
||||||
|
elif 'justify' in sys.argv:
|
||||||
|
ParaStyle.alignment = TA_JUSTIFY
|
||||||
|
elif 'center' in sys.argv or 'centre' in sys.argv:
|
||||||
|
ParaStyle.alignment = TA_CENTER
|
||||||
|
else:
|
||||||
|
ParaStyle.alignment = TA_JUSTIFY
|
||||||
|
|
||||||
|
def spacer(inches):
|
||||||
|
Elements.append(Spacer(0.1*inch, inches*inch))
|
||||||
|
|
||||||
|
def p(txt, style=ParaStyle):
|
||||||
|
Elements.append(Paragraph(txt, style))
|
||||||
|
|
||||||
|
def pre(txt, style=PreStyle):
|
||||||
|
spacer(0.1)
|
||||||
|
p = Preformatted(txt, style)
|
||||||
|
Elements.append(p)
|
||||||
|
|
||||||
|
def parseOdyssey(fn):
|
||||||
|
from time import time
|
||||||
|
E = []
|
||||||
|
t0=time()
|
||||||
|
L = open(fn,'r').readlines()
|
||||||
|
t1 = time()
|
||||||
|
print "open(%s,'r').readlines() took %.4f seconds" %(fn,t1-t0)
|
||||||
|
for i in xrange(len(L)):
|
||||||
|
if L[i][-1]=='\012':
|
||||||
|
L[i] = L[i][:-1]
|
||||||
|
t2 = time()
|
||||||
|
print "Removing all linefeeds took %.4f seconds" %(t2-t1)
|
||||||
|
L.append('')
|
||||||
|
L.append('-----')
|
||||||
|
|
||||||
|
def findNext(L, i):
|
||||||
|
while 1:
|
||||||
|
if string.strip(L[i])=='':
|
||||||
|
del L[i]
|
||||||
|
kind = 1
|
||||||
|
if i<len(L):
|
||||||
|
while string.strip(L[i])=='':
|
||||||
|
del L[i]
|
||||||
|
|
||||||
|
if i<len(L):
|
||||||
|
kind = L[i][-1]=='-' and L[i][0]=='-'
|
||||||
|
if kind:
|
||||||
|
del L[i]
|
||||||
|
if i<len(L):
|
||||||
|
while string.strip(L[i])=='':
|
||||||
|
del L[i]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
i = i + 1
|
||||||
|
|
||||||
|
return i, kind
|
||||||
|
|
||||||
|
f = s = 0
|
||||||
|
while 1:
|
||||||
|
f, k = findNext(L,0)
|
||||||
|
if k: break
|
||||||
|
|
||||||
|
E.append([spacer,2])
|
||||||
|
E.append([fTitle,'<font color=red>%s</font>' % Title, InitialStyle])
|
||||||
|
E.append([fTitle,'<font size=-4>by</font> <font color=green>%s</font>' % Author, InitialStyle])
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
if f>=len(L): break
|
||||||
|
|
||||||
|
if string.upper(L[f][0:5])=='BOOK ':
|
||||||
|
E.append([chapter,L[f]])
|
||||||
|
f=f+1
|
||||||
|
while string.strip(L[f])=='': del L[f]
|
||||||
|
style = ParaStyle
|
||||||
|
func = p
|
||||||
|
else:
|
||||||
|
style = PreStyle
|
||||||
|
func = pre
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
s=f
|
||||||
|
f, k=findNext(L,s)
|
||||||
|
sep= (func is pre) and '\012' or ' '
|
||||||
|
E.append([func,string.join(L[s:f],sep),style])
|
||||||
|
if k: break
|
||||||
|
t3 = time()
|
||||||
|
print "Parsing into memory took %.4f seconds" %(t3-t2)
|
||||||
|
del L
|
||||||
|
t4 = time()
|
||||||
|
print "Deleting list of lines took %.4f seconds" %(t4-t3)
|
||||||
|
for i in xrange(len(E)):
|
||||||
|
apply(E[i][0],E[i][1:])
|
||||||
|
t5 = time()
|
||||||
|
print "Moving into platypus took %.4f seconds" %(t5-t4)
|
||||||
|
del E
|
||||||
|
t6 = time()
|
||||||
|
print "Deleting list of actions took %.4f seconds" %(t6-t5)
|
||||||
|
go()
|
||||||
|
t7 = time()
|
||||||
|
print "saving to PDF took %.4f seconds" %(t7-t6)
|
||||||
|
print "Total run took %.4f seconds"%(t7-t0)
|
||||||
|
|
||||||
|
for fn in ('odyssey.full.txt','odyssey.txt'):
|
||||||
|
if os.path.isfile(fn):
|
||||||
|
break
|
||||||
|
if __name__=='__main__':
|
||||||
|
parseOdyssey(fn)
|
|
@ -0,0 +1,151 @@
|
||||||
|
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||||
|
#see license.txt for license details
|
||||||
|
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/odyssey/odyssey.py
|
||||||
|
__version__=''' $Id: odyssey.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
||||||
|
___doc__=''
|
||||||
|
#odyssey.py
|
||||||
|
#
|
||||||
|
#Demo/benchmark of PDFgen rendering Homer's Odyssey.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#results on my humble P266 with 64MB:
|
||||||
|
# Without page compression:
|
||||||
|
# 239 pages in 3.76 seconds = 77 pages per second
|
||||||
|
|
||||||
|
# With textOut rather than textLine, i.e. computing width
|
||||||
|
# of every word as we would for wrapping:
|
||||||
|
# 239 pages in 10.83 seconds = 22 pages per second
|
||||||
|
|
||||||
|
# With page compression and textLine():
|
||||||
|
# 239 pages in 39.39 seconds = 6 pages per second
|
||||||
|
|
||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
import time, os, sys
|
||||||
|
|
||||||
|
#find out what platform we are on and whether accelerator is
|
||||||
|
#present, in order to print this as part of benchmark info.
|
||||||
|
try:
|
||||||
|
import _rl_accel
|
||||||
|
ACCEL = 1
|
||||||
|
except ImportError:
|
||||||
|
ACCEL = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from reportlab.lib.units import inch, cm
|
||||||
|
from reportlab.lib.pagesizes import A4
|
||||||
|
|
||||||
|
#precalculate some basics
|
||||||
|
top_margin = A4[1] - inch
|
||||||
|
bottom_margin = inch
|
||||||
|
left_margin = inch
|
||||||
|
right_margin = A4[0] - inch
|
||||||
|
frame_width = right_margin - left_margin
|
||||||
|
|
||||||
|
|
||||||
|
def drawPageFrame(canv):
|
||||||
|
canv.line(left_margin, top_margin, right_margin, top_margin)
|
||||||
|
canv.setFont('Times-Italic',12)
|
||||||
|
canv.drawString(left_margin, top_margin + 2, "Homer's Odyssey")
|
||||||
|
canv.line(left_margin, top_margin, right_margin, top_margin)
|
||||||
|
|
||||||
|
|
||||||
|
canv.line(left_margin, bottom_margin, right_margin, bottom_margin)
|
||||||
|
canv.drawCentredString(0.5*A4[0], 0.5 * inch,
|
||||||
|
"Page %d" % canv.getPageNumber())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def run(verbose=1):
|
||||||
|
if sys.platform[0:4] == 'java':
|
||||||
|
impl = 'Jython'
|
||||||
|
else:
|
||||||
|
impl = 'Python'
|
||||||
|
verStr = '%d.%d' % (sys.version_info[0:2])
|
||||||
|
if ACCEL:
|
||||||
|
accelStr = 'with _rl_accel'
|
||||||
|
else:
|
||||||
|
accelStr = 'without _rl_accel'
|
||||||
|
print 'Benchmark of %s %s %s' % (impl, verStr, accelStr)
|
||||||
|
|
||||||
|
started = time.time()
|
||||||
|
canv = canvas.Canvas('odyssey.pdf', invariant=1)
|
||||||
|
canv.setPageCompression(1)
|
||||||
|
drawPageFrame(canv)
|
||||||
|
|
||||||
|
#do some title page stuff
|
||||||
|
canv.setFont("Times-Bold", 36)
|
||||||
|
canv.drawCentredString(0.5 * A4[0], 7 * inch, "Homer's Odyssey")
|
||||||
|
|
||||||
|
canv.setFont("Times-Bold", 18)
|
||||||
|
canv.drawCentredString(0.5 * A4[0], 5 * inch, "Translated by Samuel Burton")
|
||||||
|
|
||||||
|
canv.setFont("Times-Bold", 12)
|
||||||
|
tx = canv.beginText(left_margin, 3 * inch)
|
||||||
|
tx.textLine("This is a demo-cum-benchmark for PDFgen. It renders the complete text of Homer's Odyssey")
|
||||||
|
tx.textLine("from a text file. On my humble P266, it does 77 pages per secondwhile creating a 238 page")
|
||||||
|
tx.textLine("document. If it is asked to computer text metrics, measuring the width of each word as ")
|
||||||
|
tx.textLine("one would for paragraph wrapping, it still manages 22 pages per second.")
|
||||||
|
tx.textLine("")
|
||||||
|
tx.textLine("Andy Robinson, Robinson Analytics Ltd.")
|
||||||
|
canv.drawText(tx)
|
||||||
|
|
||||||
|
canv.showPage()
|
||||||
|
#on with the text...
|
||||||
|
drawPageFrame(canv)
|
||||||
|
|
||||||
|
canv.setFont('Times-Roman', 12)
|
||||||
|
tx = canv.beginText(left_margin, top_margin - 0.5*inch)
|
||||||
|
|
||||||
|
for fn in ('odyssey.full.txt','odyssey.txt'):
|
||||||
|
if os.path.isfile(fn):
|
||||||
|
break
|
||||||
|
|
||||||
|
data = open(fn,'r').readlines()
|
||||||
|
for line in data:
|
||||||
|
#this just does it the fast way...
|
||||||
|
tx.textLine(line)
|
||||||
|
#this forces it to do text metrics, which would be the slow
|
||||||
|
#part if we were wrappng paragraphs.
|
||||||
|
#canv.textOut(line)
|
||||||
|
#canv.textLine('')
|
||||||
|
|
||||||
|
#page breaking
|
||||||
|
y = tx.getY() #get y coordinate
|
||||||
|
if y < bottom_margin + 0.5*inch:
|
||||||
|
canv.drawText(tx)
|
||||||
|
canv.showPage()
|
||||||
|
drawPageFrame(canv)
|
||||||
|
canv.setFont('Times-Roman', 12)
|
||||||
|
tx = canv.beginText(left_margin, top_margin - 0.5*inch)
|
||||||
|
|
||||||
|
#page
|
||||||
|
pg = canv.getPageNumber()
|
||||||
|
if verbose and pg % 10 == 0:
|
||||||
|
print 'formatted page %d' % canv.getPageNumber()
|
||||||
|
|
||||||
|
if tx:
|
||||||
|
canv.drawText(tx)
|
||||||
|
canv.showPage()
|
||||||
|
drawPageFrame(canv)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print 'about to write to disk...'
|
||||||
|
|
||||||
|
canv.save()
|
||||||
|
|
||||||
|
finished = time.time()
|
||||||
|
elapsed = finished - started
|
||||||
|
pages = canv.getPageNumber()-1
|
||||||
|
speed = pages / elapsed
|
||||||
|
fileSize = os.stat('odyssey.pdf')[6] / 1024
|
||||||
|
print '%d pages in %0.2f seconds = %0.2f pages per second, file size %d kb' % (
|
||||||
|
pages, elapsed, speed, fileSize)
|
||||||
|
import md5
|
||||||
|
print 'file digest: %s' % md5.md5(open('odyssey.pdf','rb').read()).hexdigest()
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
quiet = ('-q' in sys.argv)
|
||||||
|
run(verbose = not quiet)
|
|
@ -0,0 +1,207 @@
|
||||||
|
Provided by The Internet Classics Archive.
|
||||||
|
See bottom for copyright. Available online at
|
||||||
|
http://classics.mit.edu//Homer/odyssey.html
|
||||||
|
|
||||||
|
The Odyssey
|
||||||
|
By Homer
|
||||||
|
|
||||||
|
|
||||||
|
Translated by Samuel Butler
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
BOOK I
|
||||||
|
<bullet indent="-18"><font name="courier" size="13" color="blue">I</font></bullet><font color="green"><b><i>Tell</i></b></font> me, O muse, of that ingenious hero who travelled far and wide
|
||||||
|
a b c &| & | <b>A</b>' <b>A</b> ' after he had sacked the famous town of <font color="red" size="12"><b>Troy</b></font>. Many cities did he visit,
|
||||||
|
and many were the nations with whose manners and customs he was acquainted;
|
||||||
|
moreover he suffered much by sea while trying to save his own life
|
||||||
|
and bring his men safely home; but do what he might he could not
|
||||||
|
save<super><font color="red">1</font></super>
|
||||||
|
his men, for they perished through their own sheer folly in eating
|
||||||
|
the cattle of the Sun-god Hyperion; so the god prevented them from
|
||||||
|
ever reaching home. Tell me, too, about all these things, O daughter
|
||||||
|
of Jove, from whatsoever source you may know them.
|
||||||
|
|
||||||
|
So now all who escaped death in battle or by shipwreck had got safely
|
||||||
|
home except Ulysses, and he, though he was longing to return to his
|
||||||
|
wife and country, was detained by the goddess Calypso, who had got
|
||||||
|
him into a large cave and wanted to marry him. But as years went by,
|
||||||
|
there came a time when the gods settled that he should go back to
|
||||||
|
Ithaca; even then, however, when he was among his own people, his
|
||||||
|
troubles were not yet over; nevertheless all the gods had now begun
|
||||||
|
to pity him except Neptune, who still persecuted him without ceasing
|
||||||
|
and would not let him get home.
|
||||||
|
|
||||||
|
<font color="green">Now Neptune had gone off to the Ethiopians, who are at the world's
|
||||||
|
end, and lie in two halves, the one looking West and the other East.
|
||||||
|
He had gone there to accept a hecatomb of sheep and oxen, and was
|
||||||
|
enjoying himself at his festival; but the other gods met in the house
|
||||||
|
of Olympian Jove, and the sire of gods and men spoke first. At that
|
||||||
|
moment he was thinking of Aegisthus, who had been killed by Agamemnon's
|
||||||
|
son Orestes; so he said to the other gods:</font>
|
||||||
|
|
||||||
|
"See now, how men lay blame upon us gods for what is after all nothing
|
||||||
|
but their own folly. Look at Aegisthus; he must needs make love to
|
||||||
|
Agamemnon's wife unrighteously and then kill Agamemnon, though he
|
||||||
|
knew it would be the death of him; for I sent Mercury to warn him
|
||||||
|
not to do either of these things, inasmuch as Orestes would be sure
|
||||||
|
to take his revenge when he grew up and wanted to return home. Mercury
|
||||||
|
told him this in all good will but he would not listen, and now he
|
||||||
|
has paid for everything in full."
|
||||||
|
|
||||||
|
Then Minerva said, "Father, son of Saturn, King of kings, it served
|
||||||
|
Aegisthus right, and so it would any one else who does as he did;
|
||||||
|
but Aegisthus is neither here nor there; it is for Ulysses that my
|
||||||
|
heart bleeds, when I think of his sufferings in that lonely sea-girt
|
||||||
|
island, far away, poor man, from all his friends. It is an island
|
||||||
|
covered with forest, in the very middle of the sea, and a goddess
|
||||||
|
lives there, daughter of the magician Atlas, who looks after the bottom
|
||||||
|
of the ocean, and carries the great columns that keep heaven and earth
|
||||||
|
asunder. This daughter of Atlas has got hold of poor unhappy Ulysses,
|
||||||
|
and keeps trying by every kind of blandishment to make him forget
|
||||||
|
his home, so that he is tired of life, and thinks of nothing but how
|
||||||
|
he may once more see the smoke of his own chimneys. You, sir, take
|
||||||
|
no heed of this, and yet when Ulysses was before Troy did he not propitiate
|
||||||
|
you with many a burnt sacrifice? Why then should you keep on being
|
||||||
|
so angry with him?"
|
||||||
|
|
||||||
|
And Jove said, "My child, what are you talking about? How can I forget
|
||||||
|
Ulysses than whom there is no more capable man on earth, nor more
|
||||||
|
liberal in his offerings to the immortal gods that live in heaven?
|
||||||
|
Bear in mind, however, that Neptune is still furious with Ulysses
|
||||||
|
for having blinded an eye of Polyphemus king of the Cyclopes. Polyphemus
|
||||||
|
is son to Neptune by the nymph Thoosa, daughter to the sea-king Phorcys;
|
||||||
|
therefore though he will not kill Ulysses outright, he torments him
|
||||||
|
by preventing him from getting home. Still, let us lay our heads together
|
||||||
|
and see how we can help him to return; Neptune will then be pacified,
|
||||||
|
for if we are all of a mind he can hardly stand out against us."
|
||||||
|
|
||||||
|
And Minerva said, "Father, son of Saturn, King of kings, if, then,
|
||||||
|
the gods now mean that Ulysses should get home, we should first send
|
||||||
|
Mercury to the Ogygian island to tell Calypso that we have made up
|
||||||
|
our minds and that he is to return. In the meantime I will go to Ithaca,
|
||||||
|
to put heart into Ulysses' son Telemachus; I will embolden him to
|
||||||
|
call the Achaeans in assembly, and speak out to the suitors of his
|
||||||
|
mother Penelope, who persist in eating up any number of his sheep
|
||||||
|
and oxen; I will also conduct him to Sparta and to Pylos, to see if
|
||||||
|
he can hear anything about the return of his dear father- for this
|
||||||
|
will make people speak well of him."
|
||||||
|
|
||||||
|
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
||||||
|
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
||||||
|
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
||||||
|
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
||||||
|
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
||||||
|
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
||||||
|
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
||||||
|
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
||||||
|
Ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis ellipsis.
|
||||||
|
|
||||||
|
"Men of Ithaca, it is all your own fault that things have turned out
|
||||||
|
as they have; you would not listen to me, nor yet to Mentor, when
|
||||||
|
we bade you check the folly of your sons who were doing much wrong
|
||||||
|
in the wantonness of their hearts- wasting the substance and dishonouring
|
||||||
|
the wife of a chieftain who they thought would not return. Now, however,
|
||||||
|
let it be as I say, and do as I tell you. Do not go out against Ulysses,
|
||||||
|
or you may find that you have been drawing down evil on your own heads."
|
||||||
|
|
||||||
|
This was what he said, and more than half raised a loud shout, and
|
||||||
|
at once left the assembly. But the rest stayed where they were, for
|
||||||
|
the speech of Halitherses displeased them, and they sided with Eupeithes;
|
||||||
|
they therefore hurried off for their armour, and when they had armed
|
||||||
|
themselves, they met together in front of the city, and Eupeithes
|
||||||
|
led them on in their folly. He thought he was going to avenge the
|
||||||
|
murder of his son, whereas in truth he was never to return, but was
|
||||||
|
himself to perish in his attempt.
|
||||||
|
|
||||||
|
Then Minerva said to Jove, "Father, son of Saturn, king of kings,
|
||||||
|
answer me this question- What do you propose to do? Will you set them
|
||||||
|
fighting still further, or will you make peace between them?"
|
||||||
|
|
||||||
|
And Jove answered, "My child, why should you ask me? Was it not by
|
||||||
|
your own arrangement that Ulysses came home and took his revenge upon
|
||||||
|
the suitors? Do whatever you like, but I will tell you what I think
|
||||||
|
will be most reasonable arrangement. Now that Ulysses is revenged,
|
||||||
|
let them swear to a solemn covenant, in virtue of which he shall continue
|
||||||
|
to rule, while we cause the others to forgive and forget the massacre
|
||||||
|
of their sons and brothers. Let them then all become friends as heretofore,
|
||||||
|
and let peace and plenty reign."
|
||||||
|
|
||||||
|
This was what Minerva was already eager to bring about, so down she
|
||||||
|
darted from off the topmost summits of Olympus.
|
||||||
|
|
||||||
|
Now when Laertes and the others had done dinner, Ulysses began by
|
||||||
|
saying, "Some of you go out and see if they are not getting close
|
||||||
|
up to us." So one of Dolius's sons went as he was bid. Standing on
|
||||||
|
the threshold he could see them all quite near, and said to Ulysses,
|
||||||
|
"Here they are, let us put on our armour at once."
|
||||||
|
|
||||||
|
They put on their armour as fast as they could- that is to say Ulysses,
|
||||||
|
his three men, and the six sons of Dolius. Laertes also and Dolius
|
||||||
|
did the same- warriors by necessity in spite of their grey hair. When
|
||||||
|
they had all put on their armour, they opened the gate and sallied
|
||||||
|
forth, Ulysses leading the way.
|
||||||
|
|
||||||
|
Then Jove's daughter Minerva came up to them, having assumed the form
|
||||||
|
and voice of Mentor. Ulysses was glad when he saw her, and said to
|
||||||
|
his son Telemachus, "Telemachus, now that are about to fight in an
|
||||||
|
engagement, which will show every man's mettle, be sure not to disgrace
|
||||||
|
your ancestors, who were eminent for their strength and courage all
|
||||||
|
the world over."
|
||||||
|
|
||||||
|
"You say truly, my dear father," answered Telemachus, "and you shall
|
||||||
|
see, if you will, that I am in no mind to disgrace your family."
|
||||||
|
|
||||||
|
Laertes was delighted when he heard this. "Good heavens, he exclaimed,
|
||||||
|
"what a day I am enjoying: I do indeed rejoice at it. My son and grandson
|
||||||
|
are vying with one another in the matter of valour."
|
||||||
|
|
||||||
|
On this Minerva came close up to him and said, "Son of Arceisius-
|
||||||
|
best friend I have in the world- pray to the blue-eyed damsel, and
|
||||||
|
to Jove her father; then poise your spear and hurl it."
|
||||||
|
|
||||||
|
As she spoke she infused fresh vigour into him, and when he had prayed
|
||||||
|
to her he poised his spear and hurled it. He hit Eupeithes' helmet,
|
||||||
|
and the spear went right through it, for the helmet stayed it not,
|
||||||
|
and his armour rang rattling round him as he fell heavily to the ground.
|
||||||
|
Meantime Ulysses and his son fell the front line of the foe and smote
|
||||||
|
them with their swords and spears; indeed, they would have killed
|
||||||
|
every one of them, and prevented them from ever getting home again,
|
||||||
|
only Minerva raised her voice aloud, and made every one pause. "Men
|
||||||
|
of Ithaca," she cried, cease this dreadful war, and settle the matter
|
||||||
|
at once without further bloodshed."
|
||||||
|
|
||||||
|
On this pale fear seized every one; they were so frightened that their
|
||||||
|
arms dropped from their hands and fell upon the ground at the sound
|
||||||
|
of the goddess's voice, and they fled back to the city for their lives.
|
||||||
|
But Ulysses gave a great cry, and gathering himself together swooped
|
||||||
|
down like a soaring eagle. Then the son of Saturn sent a thunderbolt
|
||||||
|
of fire that fell just in front of Minerva, so she said to Ulysses,
|
||||||
|
"Ulysses, noble son of Laertes, stop this warful strife, or Jove will
|
||||||
|
be angry with you."
|
||||||
|
|
||||||
|
Thus spoke Minerva, and Ulysses obeyed her gladly. Then Minerva assumed
|
||||||
|
the form and voice of Mentor, and presently made a covenant of peace
|
||||||
|
between the two contending parties.
|
||||||
|
|
||||||
|
THE END
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
Copyright statement:
|
||||||
|
The Internet Classics Archive by Daniel C. Stevenson, Web Atomics.
|
||||||
|
World Wide Web presentation is copyright (C) 1994-1998, Daniel
|
||||||
|
C. Stevenson, Web Atomics.
|
||||||
|
All rights reserved under international and pan-American copyright
|
||||||
|
conventions, including the right of reproduction in whole or in part
|
||||||
|
in any form. Direct permission requests to classics@classics.mit.edu.
|
||||||
|
Translation of "The Deeds of the Divine Augustus" by Augustus is
|
||||||
|
copyright (C) Thomas Bushnell, BSG.
|
||||||
|
|
||||||
|
|
||||||
|
To really test that reportlab can produce pages quickly download the
|
||||||
|
complete version of the test from http://classics.mit.edu//Homer/odyssey.html
|
||||||
|
and copy it to this directory as odyssey.full.txt.
|
||||||
|
|
||||||
|
A zipped version of the full text is available for download at
|
||||||
|
ftp://ftp.reportlab.com/odyssey.full.zip
|
|
@ -0,0 +1,73 @@
|
||||||
|
# rlzope : an external Zope method to show people how to use
|
||||||
|
# the ReportLab toolkit from within Zope.
|
||||||
|
#
|
||||||
|
# this method searches an image named 'logo' in the
|
||||||
|
# ZODB then prints it at the top of a simple PDF
|
||||||
|
# document made with ReportLab
|
||||||
|
#
|
||||||
|
# the resulting PDF document is returned to the
|
||||||
|
# user's web browser and, if possible, it is
|
||||||
|
# simultaneously saved into the ZODB.
|
||||||
|
#
|
||||||
|
# this method illustrates how to use both the platypus
|
||||||
|
# and canvas frameworks.
|
||||||
|
#
|
||||||
|
# License : The ReportLab Toolkit's license (similar to BSD)
|
||||||
|
#
|
||||||
|
# Author : Jerome Alet - alet@unice.fr
|
||||||
|
#
|
||||||
|
|
||||||
|
Installation instructions :
|
||||||
|
===========================
|
||||||
|
|
||||||
|
0 - If not installed then install Zope.
|
||||||
|
|
||||||
|
1 - Install reportlab in the Zope/lib/python/Shared directory by unpacking
|
||||||
|
the tarball and putting a reportlabs.pth file in site-packages for the Zope
|
||||||
|
used with Python. The path value in the reportlabs.pth file must be
|
||||||
|
relative. For a typical Zope installation, the path is "../../python/Shared".
|
||||||
|
Remember to restart Zope so the new path is instantiated.
|
||||||
|
|
||||||
|
2 - Install PIL in the Zope/lib/python/Shared directory. You need to
|
||||||
|
ensure that the _imaging.so or .pyd is also installed appropriately.
|
||||||
|
It should be compatible with the python running the zope site.
|
||||||
|
|
||||||
|
3 - Copy rlzope.py to your Zope installation's "Extensions"
|
||||||
|
subdirectory, e.g. /var/lib/zope/Extensions/ under Debian GNU/Linux.
|
||||||
|
|
||||||
|
4 - From within Zope's management interface, add an External Method with
|
||||||
|
these parameters :
|
||||||
|
|
||||||
|
Id : rlzope
|
||||||
|
Title : rlzope
|
||||||
|
Module Name : rlzope
|
||||||
|
Function Name : rlzope
|
||||||
|
|
||||||
|
5 - From within Zope's management interface, add an image called "logo"
|
||||||
|
in the same Folder than rlzope, or somewhere above in the Folder
|
||||||
|
hierarchy. For example you can use ReportLab's logo which you
|
||||||
|
can find in reportlab/docs/images/replogo.gif
|
||||||
|
|
||||||
|
6 - Point your web browser to rlzope, e.g. on my laptop under
|
||||||
|
Debian GNU/Linux :
|
||||||
|
|
||||||
|
http://localhost:9673/rlzope
|
||||||
|
|
||||||
|
This will send a simple PDF document named 'dummy.pdf' to your
|
||||||
|
web browser, and if possible save it as a File object in the
|
||||||
|
Zope Object DataBase, with this name. Note, however, that if
|
||||||
|
an object with the same name already exists then it won't
|
||||||
|
be replaced for security reasons.
|
||||||
|
|
||||||
|
You can optionally add a parameter called 'name' with
|
||||||
|
a filename as the value, to specify another filename,
|
||||||
|
e.g. :
|
||||||
|
logo
|
||||||
|
http://localhost:9673/rlzope?name=sample.pdf
|
||||||
|
|
||||||
|
7 - Adapt it to your own needs.
|
||||||
|
|
||||||
|
8 - Enjoy !
|
||||||
|
|
||||||
|
Send comments or bug reports at : alet@unice.fr
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
#
|
||||||
|
# Using the ReportLab toolkit from within Zope
|
||||||
|
#
|
||||||
|
# WARNING : The MyPDFDoc class deals with ReportLab's platypus framework,
|
||||||
|
# while the MyPageTemplate class directly deals with ReportLab's
|
||||||
|
# canvas, this way you know how to do with both...
|
||||||
|
#
|
||||||
|
# License : the ReportLab Toolkit's one
|
||||||
|
# see : http://www.reportlab.com
|
||||||
|
#
|
||||||
|
# Author : Jerome Alet - alet@unice.fr
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
import string, cStringIO
|
||||||
|
try :
|
||||||
|
from Shared.reportlab.platypus.paragraph import Paragraph
|
||||||
|
from Shared.reportlab.platypus.doctemplate import *
|
||||||
|
from Shared.reportlab.lib.units import inch
|
||||||
|
from Shared.reportlab.lib import styles
|
||||||
|
from Shared.reportlab.lib.utils import ImageReader
|
||||||
|
except ImportError :
|
||||||
|
from reportlab.platypus.paragraph import Paragraph
|
||||||
|
from reportlab.platypus.doctemplate import *
|
||||||
|
from reportlab.lib.units import inch
|
||||||
|
from reportlab.lib import styles
|
||||||
|
from reportlab.lib.utils import ImageReader
|
||||||
|
|
||||||
|
class MyPDFDoc :
|
||||||
|
class MyPageTemplate(PageTemplate) :
|
||||||
|
"""Our own page template."""
|
||||||
|
def __init__(self, parent) :
|
||||||
|
"""Initialise our page template."""
|
||||||
|
#
|
||||||
|
# we must save a pointer to our parent somewhere
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
# Our doc is made of a single frame
|
||||||
|
content = Frame(0.75 * inch, 0.5 * inch, parent.document.pagesize[0] - 1.25 * inch, parent.document.pagesize[1] - (1.5 * inch))
|
||||||
|
PageTemplate.__init__(self, "MyTemplate", [content])
|
||||||
|
|
||||||
|
# get all the images we need now, in case we've got
|
||||||
|
# several pages this will save some CPU
|
||||||
|
self.logo = self.getImageFromZODB("logo")
|
||||||
|
|
||||||
|
def getImageFromZODB(self, name) :
|
||||||
|
"""Retrieves an Image from the ZODB, converts it to PIL,
|
||||||
|
and makes it 0.75 inch high.
|
||||||
|
"""
|
||||||
|
try :
|
||||||
|
# try to get it from ZODB
|
||||||
|
logo = getattr(self.parent.context, name)
|
||||||
|
except AttributeError :
|
||||||
|
# not found !
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Convert it to PIL
|
||||||
|
image = ImageReader(cStringIO.StringIO(str(logo.data)))
|
||||||
|
(width, height) = image.getSize()
|
||||||
|
|
||||||
|
# scale it to be 0.75 inch high
|
||||||
|
multi = ((height + 0.0) / (0.75 * inch))
|
||||||
|
width = int(width / multi)
|
||||||
|
height = int(height / multi)
|
||||||
|
|
||||||
|
return ((width, height), image)
|
||||||
|
|
||||||
|
def beforeDrawPage(self, canvas, doc) :
|
||||||
|
"""Draws a logo and an contribution message on each page."""
|
||||||
|
canvas.saveState()
|
||||||
|
if self.logo is not None :
|
||||||
|
# draws the logo if it exists
|
||||||
|
((width, height), image) = self.logo
|
||||||
|
canvas.drawImage(image, inch, doc.pagesize[1] - inch, width, height)
|
||||||
|
canvas.setFont('Times-Roman', 10)
|
||||||
|
canvas.drawCentredString(inch + (doc.pagesize[0] - (1.5 * inch)) / 2, 0.25 * inch, "Contributed by Jerome Alet - alet@unice.fr")
|
||||||
|
canvas.restoreState()
|
||||||
|
|
||||||
|
def __init__(self, context, filename) :
|
||||||
|
# save some datas
|
||||||
|
self.context = context
|
||||||
|
self.built = 0
|
||||||
|
self.objects = []
|
||||||
|
|
||||||
|
# we will build an in-memory document
|
||||||
|
# instead of creating an on-disk file.
|
||||||
|
self.report = cStringIO.StringIO()
|
||||||
|
|
||||||
|
# initialise a PDF document using ReportLab's platypus
|
||||||
|
self.document = BaseDocTemplate(self.report)
|
||||||
|
|
||||||
|
# add our page template
|
||||||
|
# (we could add more than one, but I prefer to keep it simple)
|
||||||
|
self.document.addPageTemplates(self.MyPageTemplate(self))
|
||||||
|
|
||||||
|
# get the default style sheets
|
||||||
|
self.StyleSheet = styles.getSampleStyleSheet()
|
||||||
|
|
||||||
|
# then build a simple doc with ReportLab's platypus
|
||||||
|
sometext = "A sample script to show how to use ReportLab from within Zope"
|
||||||
|
url = self.escapexml(context.absolute_url())
|
||||||
|
urlfilename = self.escapexml(context.absolute_url() + '/%s' % filename)
|
||||||
|
self.append(Paragraph("Using ReportLab from within Zope", self.StyleSheet["Heading3"]))
|
||||||
|
self.append(Spacer(0, 10))
|
||||||
|
self.append(Paragraph("You launched it from : %s" % url, self.StyleSheet['Normal']))
|
||||||
|
self.append(Spacer(0, 40))
|
||||||
|
self.append(Paragraph("If possible, this report will be automatically saved as : %s" % urlfilename, self.StyleSheet['Normal']))
|
||||||
|
|
||||||
|
# generation du document PDF
|
||||||
|
self.document.build(self.objects)
|
||||||
|
self.built = 1
|
||||||
|
|
||||||
|
def __str__(self) :
|
||||||
|
"""Returns the PDF document as a string of text, or None if it's not ready yet."""
|
||||||
|
if self.built :
|
||||||
|
return self.report.getvalue()
|
||||||
|
else :
|
||||||
|
return None
|
||||||
|
|
||||||
|
def append(self, object) :
|
||||||
|
"""Appends an object to our platypus "story" (using ReportLab's terminology)."""
|
||||||
|
self.objects.append(object)
|
||||||
|
|
||||||
|
def escapexml(self, s) :
|
||||||
|
"""Escape some xml entities."""
|
||||||
|
s = string.strip(s)
|
||||||
|
s = string.replace(s, "&", "&")
|
||||||
|
s = string.replace(s, "<", "<")
|
||||||
|
return string.replace(s, ">", ">")
|
||||||
|
|
||||||
|
def rlzope(self) :
|
||||||
|
"""A sample external method to show people how to use ReportLab from within Zope."""
|
||||||
|
try:
|
||||||
|
#
|
||||||
|
# which file/object name to use ?
|
||||||
|
# append ?name=xxxxx to rlzope's url to
|
||||||
|
# choose another name
|
||||||
|
filename = self.REQUEST.get("name", "dummy.pdf")
|
||||||
|
if filename[-4:] != '.pdf' :
|
||||||
|
filename = filename + '.pdf'
|
||||||
|
|
||||||
|
# tell the browser we send some PDF document
|
||||||
|
# with the requested filename
|
||||||
|
|
||||||
|
# get the document's content itself as a string of text
|
||||||
|
content = str(MyPDFDoc(self, filename))
|
||||||
|
|
||||||
|
# we will return it to the browser, but before that we also want to
|
||||||
|
# save it into the ZODB into the current folder
|
||||||
|
try :
|
||||||
|
self.manage_addFile(id = filename, file = content, title = "A sample PDF document produced with ReportLab", precondition = '', content_type = "application/pdf")
|
||||||
|
except :
|
||||||
|
# it seems an object with this name already exists in the ZODB:
|
||||||
|
# it's more secure to not replace it, since we could possibly
|
||||||
|
# destroy an important PDF document of this name.
|
||||||
|
pass
|
||||||
|
self.REQUEST.RESPONSE.setHeader('Content-Type', 'application/pdf')
|
||||||
|
self.REQUEST.RESPONSE.setHeader('Content-Disposition', 'attachment; filename=%s' % filename)
|
||||||
|
except:
|
||||||
|
import traceback, sys, cgi
|
||||||
|
content = sys.stdout = sys.stderr = cStringIO.StringIO()
|
||||||
|
self.REQUEST.RESPONSE.setHeader('Content-Type', 'text/html')
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.stdout = sys.__stdout__
|
||||||
|
sys.stderr = sys.__stderr__
|
||||||
|
content = '<html><head></head><body><pre>%s</pre></body></html>' % cgi.escape(content.getvalue())
|
||||||
|
|
||||||
|
# then we also return the PDF content to the browser
|
||||||
|
return content
|
|
@ -0,0 +1,7 @@
|
||||||
|
This lists out the standard 14 fonts
|
||||||
|
in a very plain and simple fashion.
|
||||||
|
|
||||||
|
Notably, the output is huge - it makes
|
||||||
|
two separate text objects for each glyph.
|
||||||
|
Smarter programming would make tighter
|
||||||
|
PDF, but more lines of Python!
|
|
@ -0,0 +1,74 @@
|
||||||
|
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||||
|
#see license.txt for license details
|
||||||
|
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/stdfonts/stdfonts.py
|
||||||
|
__version__=''' $Id: stdfonts.py 2830 2006-04-05 15:18:32Z rgbecker $ '''
|
||||||
|
__doc__="""
|
||||||
|
This generates tables showing the 14 standard fonts in both
|
||||||
|
WinAnsi and MacRoman encodings, and their character codes.
|
||||||
|
Supply an argument of 'hex' or 'oct' to get code charts
|
||||||
|
in those encodings; octal is what you need for \\n escape
|
||||||
|
sequences in Python literals.
|
||||||
|
|
||||||
|
usage: standardfonts.py [dec|hex|oct]
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from reportlab.pdfbase import pdfmetrics
|
||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
import string
|
||||||
|
|
||||||
|
label_formats = {'dec':('%d=', 'Decimal'),
|
||||||
|
'oct':('%o=','Octal'),
|
||||||
|
'hex':('0x%x=', 'Hexadecimal')}
|
||||||
|
|
||||||
|
def run(mode):
|
||||||
|
|
||||||
|
label_formatter, caption = label_formats[mode]
|
||||||
|
|
||||||
|
for enc in ['MacRoman', 'WinAnsi']:
|
||||||
|
canv = canvas.Canvas(
|
||||||
|
'StandardFonts_%s.pdf' % enc,
|
||||||
|
)
|
||||||
|
canv.setPageCompression(0)
|
||||||
|
|
||||||
|
for faceName in pdfmetrics.standardFonts:
|
||||||
|
if faceName in ['Symbol', 'ZapfDingbats']:
|
||||||
|
encLabel = faceName+'Encoding'
|
||||||
|
else:
|
||||||
|
encLabel = enc + 'Encoding'
|
||||||
|
|
||||||
|
fontName = faceName + '-' + encLabel
|
||||||
|
pdfmetrics.registerFont(pdfmetrics.Font(fontName,
|
||||||
|
faceName,
|
||||||
|
encLabel)
|
||||||
|
)
|
||||||
|
|
||||||
|
canv.setFont('Times-Bold', 18)
|
||||||
|
canv.drawString(80, 744, fontName)
|
||||||
|
canv.setFont('Times-BoldItalic', 12)
|
||||||
|
canv.drawRightString(515, 744, 'Labels in ' + caption)
|
||||||
|
|
||||||
|
|
||||||
|
#for dingbats, we need to use another font for the numbers.
|
||||||
|
#do two parallel text objects.
|
||||||
|
for byt in range(32, 256):
|
||||||
|
col, row = divmod(byt - 32, 32)
|
||||||
|
x = 72 + (66*col)
|
||||||
|
y = 720 - (18*row)
|
||||||
|
canv.setFont('Helvetica', 14)
|
||||||
|
canv.drawString(x, y, label_formatter % byt)
|
||||||
|
canv.setFont(fontName, 14)
|
||||||
|
canv.drawString(x+44, y, chr(byt).decode(encLabel,'ignore').encode('utf8'))
|
||||||
|
canv.showPage()
|
||||||
|
canv.save()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv)==2:
|
||||||
|
mode = string.lower(sys.argv[1])
|
||||||
|
if mode not in ['dec','oct','hex']:
|
||||||
|
print __doc__
|
||||||
|
|
||||||
|
elif len(sys.argv) == 1:
|
||||||
|
mode = 'dec'
|
||||||
|
run(mode)
|
||||||
|
else:
|
||||||
|
print __doc__
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/env python
|
||||||
|
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||||
|
#see license.txt for license details
|
||||||
|
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/demos/tests/testdemos.py
|
||||||
|
__version__=''' $Id: testdemos.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
|
||||||
|
__doc__='Test all demos'
|
||||||
|
_globals=globals().copy()
|
||||||
|
import os, sys
|
||||||
|
from reportlab import pdfgen
|
||||||
|
|
||||||
|
for p in ('pythonpoint/pythonpoint.py','stdfonts/stdfonts.py','odyssey/odyssey.py', 'gadflypaper/gfe.py'):
|
||||||
|
fn = os.path.normcase(os.path.normpath(os.path.join(os.path.dirname(pdfgen.__file__),'..','demos',p)))
|
||||||
|
os.chdir(os.path.dirname(fn))
|
||||||
|
execfile(fn,_globals.copy())
|
Loading…
Reference in New Issue