Skip to content

Commit

Permalink
Merge pull request #27 from kylemacfarlane/py3
Browse files Browse the repository at this point in the history
Python 3 support
  • Loading branch information
kedder committed Oct 1, 2015
2 parents 5bf609a + 06b8726 commit 814865e
Show file tree
Hide file tree
Showing 165 changed files with 1,990 additions and 290 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Expand Up @@ -10,6 +10,8 @@ addons:
- fonts-takao-mincho
env:
- TOXENV=py27
- TOXENV=py33
- TOXENV=py34
install:
- travis_retry pip install tox
script:
Expand Down
5 changes: 5 additions & 0 deletions AUTHORS.txt
@@ -1,3 +1,8 @@
- Stephan Richter (stephan.richter_at_gmail.com)

This package is based on ReportLab Inc.'s ReportLab library.


The package svg2rlg was rolled into this package for Python 3 compatibility.

- Runar Tenfjord (svg2rlg)
9 changes: 6 additions & 3 deletions setup.py
Expand Up @@ -61,6 +61,9 @@ def alltests():
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: Implementation :: CPython',
'Natural Language :: English',
'Operating System :: OS Independent'],
Expand All @@ -76,10 +79,10 @@ def alltests():
install_requires=[
'Pygments',
'lxml',
'PyPDF2>=1.21',
'reportlab>=3.0',
'PyPDF2>=1.25.1',
'reportlab>=3.1.44',
'setuptools',
'svg2rlg',
'six',
'zope.interface',
'zope.schema',
],
Expand Down
77 changes: 46 additions & 31 deletions src/z3c/rml/attr.py
Expand Up @@ -13,21 +13,20 @@
##############################################################################
"""RML Attribute Implementation
"""
import cStringIO
import logging
import os
import re
import six
import reportlab.graphics.widgets.markers
import reportlab.lib.colors
import reportlab.lib.pagesizes
import reportlab.lib.styles
import reportlab.lib.units
import reportlab.lib.utils
import urllib
import zope.interface
import zope.schema
from lxml import etree

from importlib import import_module
from z3c.rml import interfaces, SampleStyleSheet

MISSING = object()
Expand Down Expand Up @@ -77,7 +76,7 @@ def fromUnicode(self, ustr):
"""See zope.schema.interfaces.IField"""
if self.context is None:
raise ValueError('Attribute not bound to a context.')
return super(RMLAttribute, self).fromUnicode(unicode(ustr))
return super(RMLAttribute, self).fromUnicode(six.text_type(ustr))

def get(self):
"""See zope.schema.interfaces.IField"""
Expand All @@ -86,7 +85,7 @@ def get(self):
if (interfaces.IDeprecated.providedBy(self) and
self.deprecatedName in self.context.element.attrib):
name = self.deprecatedName
logger.warn(
logger.warning(
u'Deprecated attribute "%s": %s %s' % (
name, self.deprecatedReason, getFileInfo(self.context)))
else:
Expand Down Expand Up @@ -142,14 +141,18 @@ def fromUnicode(self, value):
value, getFileInfo(self.context)))


class String(RMLAttribute, zope.schema.Bytes):
"""A simple Bytes string."""


class Text(RMLAttribute, zope.schema.Text):
"""A simple unicode string."""


if six.PY2:
class String(RMLAttribute, zope.schema.Bytes):
"""A simple Bytes string."""
else:
class String(Text):
"""Formerly a simple Bytes string, now the same as Text."""


class Integer(RMLAttribute, zope.schema.Int):
"""An integer. A minimum and maximum value can be specified."""
# By making min and max simple attributes, we avoid some validation
Expand Down Expand Up @@ -228,7 +231,7 @@ class Choice(BaseChoice):

def __init__(self, choices=None, doLower=True, *args, **kw):
super(Choice, self).__init__(*args, **kw)
if isinstance(choices, (tuple, list)):
if not isinstance(choices, dict):
choices = dict(
[(val.lower() if doLower else val, val) for val in choices])
else:
Expand Down Expand Up @@ -283,12 +286,12 @@ def __init__(self, allowPercentage=False, allowStar=False, *args, **kw):
self.allowStar = allowStar

units = [
(re.compile('^(-?[0-9\.]+)\s*in$'), reportlab.lib.units.inch),
(re.compile('^(-?[0-9\.]+)\s*cm$'), reportlab.lib.units.cm),
(re.compile('^(-?[0-9\.]+)\s*mm$'), reportlab.lib.units.mm),
(re.compile('^(-?[0-9\.]+)\s*pt$'), 1),
(re.compile('^(-?[0-9\.]+)\s*$'), 1)
]
(re.compile('^(-?[0-9\.]+)\s*in$'), reportlab.lib.units.inch),
(re.compile('^(-?[0-9\.]+)\s*cm$'), reportlab.lib.units.cm),
(re.compile('^(-?[0-9\.]+)\s*mm$'), reportlab.lib.units.mm),
(re.compile('^(-?[0-9\.]+)\s*pt$'), 1),
(re.compile('^(-?[0-9\.]+)\s*$'), 1)
]

allowPercentage = False
allowStar = False
Expand All @@ -300,7 +303,7 @@ def fromUnicode(self, value):
return value
if value.endswith('%') and self.allowPercentage:
return value
for unit in self.units:
for unit in self.units:
res = unit[0].search(value, 0)
if res:
return unit[1]*float(res.group(1))
Expand All @@ -315,7 +318,6 @@ class File(Text):
The value itself can eith be be a relative or absolute path. Additionally
the following syntax is supported: [path.to.python.mpackage]/path/to/file
"""
open = staticmethod(urllib.urlopen)
packageExtract = re.compile('^\[([0-9A-z_.]*)\]/(.*)$')

doNotOpen = False
Expand All @@ -334,18 +336,28 @@ def fromUnicode(self, value):
'The package-path-pair you specified was incorrect. %s' %(
getFileInfo(self.context)))
modulepath, path = result.groups()
module = __import__(modulepath, {}, {}, (modulepath))
value = os.path.join(os.path.dirname(module.__file__), path)
# If there is a drive name in the path, then we want a local file to
# be opened. This is only interesting for Windows of course.
if os.path.splitdrive(value)[0]:
value = 'file:///' + value
module = import_module(modulepath)
if six.PY2:
value = os.path.join(os.path.dirname(module.__file__), path)
else:
# PEP 420 namespace support means that a module can have
# multiple paths
for module_path in module.__path__:
value = os.path.join(module_path, path)
if os.path.exists(value):
break
# Under Python 3 all platforms need a protocol for local files
if not six.moves.urllib.parse.urlparse(value).scheme:
value = 'file:///' + os.path.abspath(value)
# If the file is not to be opened, simply return the path.
if self.doNotOpen:
return value
# Open/Download the file
fileObj = self.open(value)
sio = cStringIO.StringIO(fileObj.read())
# We can't use urlopen directly because it has problems with data URIs
# in 2.7 and 3.3 which are resolved in 3.4. Fortunately Reportlab
# already has a utility function to help us work around this issue.
fileObj = reportlab.lib.utils.open_for_read(value)
sio = six.BytesIO(fileObj.read())
fileObj.close()
sio.seek(0)
return sio
Expand Down Expand Up @@ -386,12 +398,12 @@ def _load_svg(self, value):

from gzip import GzipFile
from reportlab.graphics import renderPM
from svg2rlg import Renderer
from xml.etree import cElementTree
from z3c.rml.svg2rlg import Renderer

fileObj = super(Image, self).fromUnicode(value)
svg = fileObj.getvalue()
if svg[:2] == '\037\213':
if svg[:2] == b'\037\213':
svg = GzipFile(fileobj=fileObj).read()
svg = cElementTree.fromstring(svg)
svg = Renderer(value).render(svg)
Expand Down Expand Up @@ -433,13 +445,16 @@ class Color(RMLAttribute):
specification.
"""

def __init__(self, acceptNone=False, *args, **kw):
def __init__(self, acceptNone=False, acceptAuto=False, *args, **kw):
super(Color, self).__init__(*args, **kw)
self.acceptNone = acceptNone
self.acceptAuto = acceptAuto

def fromUnicode(self, value):
if self.acceptNone and value.lower() == 'none':
return None
if self.acceptAuto and value.lower() == 'auto':
return 'auto'
manager = getManager(self.context)

if value.startswith('rml:'):
Expand Down Expand Up @@ -541,7 +556,7 @@ class TextNode(RMLAttribute):
def get(self):
if self.context.element.text is None:
return u''
return unicode(self.context.element.text).strip()
return six.text_type(self.context.element.text).strip()


class FirstLevelTextNode(TextNode):
Expand Down Expand Up @@ -580,7 +595,7 @@ def fromUnicode(self, ustr):
'Number of elements must be divisible by %i. %s' %(
self.columns, getFileInfo(self.context)))
return [result[i*self.columns:(i+1)*self.columns]
for i in range(len(result)/self.columns)]
for i in range(len(result)//self.columns)]


class RawXMLContent(RMLAttribute):
Expand Down
9 changes: 7 additions & 2 deletions src/z3c/rml/canvas.py
Expand Up @@ -341,6 +341,13 @@ class IImage(interfaces.IRMLDirectiveSignature):
default=False,
required=False)

mask = attr.Color(
title=u'Mask',
description=u'The color mask used to render the image, or "auto" to use the alpha channel if available.',
default='auto',
required=False,
acceptAuto=True)

class Image(CanvasRMLDirective):
signature = IImage
callable = 'drawImage'
Expand All @@ -365,8 +372,6 @@ def process(self):
else:
kwargs['height'] = imgY * kwargs['width'] / imgX

kwargs['mask'] = 'auto' # Support transparent images too

canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
getattr(canvas, self.callable)(**kwargs)

Expand Down
6 changes: 3 additions & 3 deletions src/z3c/rml/directive.py
Expand Up @@ -39,8 +39,8 @@ def getFileInfo(directive, element=None):
element = directive.element
return '(file %s, line %i)' %(root.filename, element.sourceline)

@zope.interface.implementer(interfaces.IRMLDirective)
class RMLDirective(object):
zope.interface.implements(interfaces.IRMLDirective)
signature = None
factories = {}

Expand Down Expand Up @@ -83,7 +83,7 @@ def getAttributeValues(self, ignore=None, select=None, attrMapping=None,
# Sort the items based on the section
if select is not None:
select = list(select)
items = sorted(items, key=lambda (n, v): select.index(n))
items = sorted(items, key=lambda n: select.index(n[0]))

# If the attribute name does not match the internal API
# name, then convert the name to the internal one
Expand All @@ -108,7 +108,7 @@ def processSubDirectives(self, select=None, ignore=None):
msg = "Directive %r could not be processed and was " \
"ignored. %s" %(element.tag, getFileInfo(self, element))
# Record any tags/elements that could not be processed.
logger.warn(msg)
logger.warning(msg)
if ABORT_ON_INVALID_DIRECTIVE:
raise ValueError(msg)
continue
Expand Down
28 changes: 15 additions & 13 deletions src/z3c/rml/document.py
Expand Up @@ -13,8 +13,8 @@
##############################################################################
"""RML ``document`` element
"""
import cStringIO
import logging
import six
import sys
import zope.interface
import reportlab.pdfgen.canvas
Expand Down Expand Up @@ -399,15 +399,8 @@ class LogConfig(directive.RMLDirective):

def process(self):
args = dict(self.getAttributeValues())

# cleanup win paths like:
# ....\\input\\file:///D:\\trunk\\...
if sys.platform[:3].lower() == "win":
if args['filename'].startswith('file:///'):
args['filename'] = args['filename'][len('file:///'):]

logger = logging.getLogger(LOGGER_NAME)
handler = logging.FileHandler(args['filename'], args['filemode'])
handler = logging.FileHandler(args['filename'][8:], args['filemode'])
formatter = logging.Formatter(
args.get('format'), args.get('datefmt'))
handler.setFormatter(formatter)
Expand Down Expand Up @@ -617,11 +610,11 @@ class IDocument(interfaces.IRMLDirectiveSignature):
u'the exact contents.'),
required=False)

@zope.interface.implementer(interfaces.IManager,
interfaces.IPostProcessorManager,
interfaces.ICanvasManager)
class Document(directive.RMLDirective):
signature = IDocument
zope.interface.implements(interfaces.IManager,
interfaces.IPostProcessorManager,
interfaces.ICanvasManager)

factories = {
'docinit': DocInit,
Expand Down Expand Up @@ -693,7 +686,7 @@ def process(self, outputFile=None, maxPasses=2):

# Create a temporary output file, so that post-processors can
# massage the output
self.outputFile = tempOutput = cStringIO.StringIO()
self.outputFile = tempOutput = six.BytesIO()

# Process common sub-directives
self.processSubDirectives(select=('docinit', 'stylesheet'))
Expand All @@ -710,12 +703,21 @@ def process(self, outputFile=None, maxPasses=2):
self.canvas = reportlab.pdfgen.canvas.Canvas(tempOutput, **kwargs)
self._initCanvas(self.canvas)
self.processSubDirectives(select=('pageInfo', 'pageDrawing'))

if hasattr(self.canvas, 'AcroForm'):
# Makes default values appear in ReportLab >= 3.1.44
self.canvas.AcroForm.needAppearances = 'true'

self.canvas.save()

# Handle Flowable-based documents.
elif self.element.find('template') is not None:
self.processSubDirectives(select=('template', 'story'))
self.doc.beforeDocument = self._beforeDocument
def callback(event, value):
if event == 'PASS':
self.doc.current_pass = value
self.doc.setProgressCallBack(callback)
self.doc.multiBuild(self.flowables, maxPasses=maxPasses)

# Process all post processors
Expand Down
3 changes: 2 additions & 1 deletion src/z3c/rml/dtd.py
Expand Up @@ -13,6 +13,7 @@
##############################################################################
"""Generate a DTD from the code
"""
import six
import zope.schema

from z3c.rml import attr, document, occurence
Expand Down Expand Up @@ -73,4 +74,4 @@ def generate(useWrapper=False):
return text

def main():
print generate()
six.print_(generate())

0 comments on commit 814865e

Please sign in to comment.