Design models in the model.py.
- Edit dev.cfg to use a different backend, or start with a pre-configured SQLite database.
- Use script tg-admin sql create to create the database tables.
Edit controllers.py and build your
- website structure with the simplicity of Python objects.
- TurboGears will automatically reload itself when you modify your project.
-
-
-
If you create something cool, please let people know, and consider contributing something back to the community.
-
-
-
-
+
+
+
+
+Welcome to TurboGears
+
+
+
+
+
Learn more
+ Learn more about TurboGears and take part in its
+ development
+
Design models in the model.py.
+ Edit dev.cfg to use a different backend, or start with a pre-configured SQLite database.
+ Use script tg-admin sql create to create the database tables.
Edit controllers.py and build your
+ website structure with the simplicity of Python objects.
+ TurboGears will automatically reload itself when you modify your project.
+
+
+
If you create something cool, please let people know, and consider contributing something back to the community.
+
+
+
+
diff --git a/demo/wsgi/pisawsgidemo.py b/demo/wsgi/pisawsgidemo.py
index e7e3026c..73fec202 100644
--- a/demo/wsgi/pisawsgidemo.py
+++ b/demo/wsgi/pisawsgidemo.py
@@ -1,5 +1,5 @@
#!/bin/python2.5
-# -*- coding: UTF-8 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
diff --git a/ho/__init__.py b/ho/__init__.py
index a92eec1b..f54e6771 100644
--- a/ho/__init__.py
+++ b/ho/__init__.py
@@ -1,25 +1,25 @@
-# -*- coding: ISO-8859-1 -*-
-
-# Copyright 2010 Dirk Holtwick, holtwick.it
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-__version__ = "$Revision: 128 $"
-__author__ = "$Author: holtwick $"
-__date__ = "$Date: 2008-01-10 21:26:42 +0100 (Do, 10 Jan 2008) $"
-__svnid__ = "$Id: __init__.py 128 2008-01-10 20:26:42Z holtwick $"
-
-# Also look in other packages with the same name
-
-from pkgutil import extend_path
-__path__ = extend_path(__path__, __name__)
+# -*- coding: utf-8 -*-
+
+# Copyright 2010 Dirk Holtwick, holtwick.it
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__version__ = "$Revision: 128 $"
+__author__ = "$Author: holtwick $"
+__date__ = "$Date: 2008-01-10 21:26:42 +0100 (Do, 10 Jan 2008) $"
+__svnid__ = "$Id: __init__.py 128 2008-01-10 20:26:42Z holtwick $"
+
+# Also look in other packages with the same name
+
+from pkgutil import extend_path
+__path__ = extend_path(__path__, __name__)
diff --git a/ho/pisa/__init__.py b/ho/pisa/__init__.py
index d5e16e75..eee9e08f 100644
--- a/ho/pisa/__init__.py
+++ b/ho/pisa/__init__.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
diff --git a/pisa.py b/pisa.py
index 663d4f48..c036b39c 100644
--- a/pisa.py
+++ b/pisa.py
@@ -1,24 +1,24 @@
-#!/usr/local/bin/python
-# -*- coding: ISO-8859-1 -*-
-
-# Copyright 2010 Dirk Holtwick, holtwick.it
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-__version__ = "$Revision: 221 $"
-__author__ = "$Author: holtwick $"
-__date__ = "$Date: 2008-05-31 18:56:27 +0200 (Sa, 31 Mai 2008) $"
-__svnid__ = "$Id: pisa.py 221 2008-05-31 16:56:27Z holtwick $"
-
-import ho.pisa as pisa
-pisa.command()
+#!/usr/local/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright 2010 Dirk Holtwick, holtwick.it
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__version__ = "$Revision: 221 $"
+__author__ = "$Author: holtwick $"
+__date__ = "$Date: 2008-05-31 18:56:27 +0200 (Sa, 31 Mai 2008) $"
+__svnid__ = "$Id: pisa.py 221 2008-05-31 16:56:27Z holtwick $"
+
+import ho.pisa as pisa
+pisa.command()
diff --git a/setup.py b/setup.py
index c298f24d..201aeaa9 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
@@ -38,7 +38,7 @@
download_url = "http://pypi.python.org/pypi/pisa/",
keywords = "PDF, HTML, XHTML, XML, CSS",
- requires = ["html5lib", "pypdf", "pil"], #, "reportlab"],
+ install_requires = ["html5lib", "pypdf", "pil", "reportlab"],
include_package_data = False,
diff --git a/setup_exe.py b/setup_exe.py
index af7edfb3..25bcebb8 100644
--- a/setup_exe.py
+++ b/setup_exe.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
diff --git a/setup_version.py b/setup_version.py
index 47d6275e..c6a25ede 100644
--- a/setup_version.py
+++ b/setup_version.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
diff --git a/sx/__init__.py b/sx/__init__.py
index a92eec1b..f54e6771 100644
--- a/sx/__init__.py
+++ b/sx/__init__.py
@@ -1,25 +1,25 @@
-# -*- coding: ISO-8859-1 -*-
-
-# Copyright 2010 Dirk Holtwick, holtwick.it
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-__version__ = "$Revision: 128 $"
-__author__ = "$Author: holtwick $"
-__date__ = "$Date: 2008-01-10 21:26:42 +0100 (Do, 10 Jan 2008) $"
-__svnid__ = "$Id: __init__.py 128 2008-01-10 20:26:42Z holtwick $"
-
-# Also look in other packages with the same name
-
-from pkgutil import extend_path
-__path__ = extend_path(__path__, __name__)
+# -*- coding: utf-8 -*-
+
+# Copyright 2010 Dirk Holtwick, holtwick.it
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__version__ = "$Revision: 128 $"
+__author__ = "$Author: holtwick $"
+__date__ = "$Date: 2008-01-10 21:26:42 +0100 (Do, 10 Jan 2008) $"
+__svnid__ = "$Id: __init__.py 128 2008-01-10 20:26:42Z holtwick $"
+
+# Also look in other packages with the same name
+
+from pkgutil import extend_path
+__path__ = extend_path(__path__, __name__)
diff --git a/sx/pisa3/__init__.py b/sx/pisa3/__init__.py
index f4883024..bb76d85e 100644
--- a/sx/pisa3/__init__.py
+++ b/sx/pisa3/__init__.py
@@ -1,50 +1,50 @@
-# -*- coding: ISO-8859-1 -*-
-
-# Copyright 2010 Dirk Holtwick, holtwick.it
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-__reversion__ = "$Revision: 238 $"
-__author__ = "$Author: holtwick $"
-__date__ = "$Date: 2008-06-26 20:06:02 +0200 (Do, 26 Jun 2008) $"
-
-REQUIRED_INFO = """
-****************************************************
-IMPORT ERROR!
-%s
-****************************************************
-
-The following Python packages are required for PISA:
-- Reportlab Toolkit >= 2.2
-- HTML5lib >= 0.11.1
-
-Optional packages:
-- pyPDF
-- PIL
-
-""".lstrip()
-
-import logging
-log = logging.getLogger(__name__)
-
-try:
- from pisa import *
- if not REPORTLAB22:
- raise ImportError, "Reportlab Toolkit Version 2.2 or higher needed"
-except ImportError, e:
- import sys
- sys.stderr.write(REQUIRED_INFO % e)
- log.error(REQUIRED_INFO % e)
- raise
-
-__version__ = VERSION
+# -*- coding: utf-8 -*-
+
+# Copyright 2010 Dirk Holtwick, holtwick.it
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__reversion__ = "$Revision: 238 $"
+__author__ = "$Author: holtwick $"
+__date__ = "$Date: 2008-06-26 20:06:02 +0200 (Do, 26 Jun 2008) $"
+
+REQUIRED_INFO = """
+****************************************************
+IMPORT ERROR!
+%s
+****************************************************
+
+The following Python packages are required for PISA:
+- Reportlab Toolkit >= 2.2
+- HTML5lib >= 0.11.1
+
+Optional packages:
+- pyPDF
+- PIL
+
+""".lstrip()
+
+import logging
+log = logging.getLogger(__name__)
+
+try:
+ from pisa import *
+ if not REPORTLAB22:
+ raise ImportError, "Reportlab Toolkit Version 2.2 or higher needed"
+except ImportError, e:
+ import sys
+ sys.stderr.write(REQUIRED_INFO % e)
+ log.error(REQUIRED_INFO % e)
+ raise
+
+__version__ = VERSION
diff --git a/sx/pisa3/pisa.py b/sx/pisa3/pisa.py
index 01de4a7c..053d7d18 100644
--- a/sx/pisa3/pisa.py
+++ b/sx/pisa3/pisa.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
diff --git a/sx/pisa3/pisa_context.py b/sx/pisa3/pisa_context.py
index 9542a664..70a0eaf9 100644
--- a/sx/pisa3/pisa_context.py
+++ b/sx/pisa3/pisa_context.py
@@ -1,1060 +1,1063 @@
-# -*- coding: utf-8 -*-
-
-# Copyright 2010 Dirk Holtwick, holtwick.it
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-__reversion__ = "$Revision: 20 $"
-__author__ = "$Author: holtwick $"
-__date__ = "$Date: 2007-10-09 12:58:24 +0200 (Di, 09 Okt 2007) $"
-
-from pisa_util import *
-from pisa_reportlab import *
-
-import pisa_default
-import pisa_parser
-import re
-import urlparse
-import types
-
-from reportlab.platypus.paraparser import ParaParser, ParaFrag, ps2tt, tt2ps, ABag
-from reportlab.platypus.paragraph import cleanBlockQuotedText
-from reportlab.lib.styles import ParagraphStyle
-
-import reportlab.rl_config
-reportlab.rl_config.warnOnMissingFontGlyphs = 0
-
-from reportlab.pdfbase import pdfmetrics
-from reportlab.pdfbase.ttfonts import TTFont
-from reportlab.lib.fonts import addMapping
-
-from sx.w3c import css, cssDOMElementInterface
-
-from html5lib.sanitizer import *
-
-import logging
-log = logging.getLogger("ho.pisa")
-
-sizeDelta = 2 # amount to reduce font size by for super and sub script
-subFraction = 0.4 # fraction of font size that a sub script should be lowered
-superFraction = 0.4
-
-NBSP = u"\u00a0"
-
-def clone(self, **kwargs):
- n = ParaFrag(**self.__dict__)
- if kwargs:
- d = n.__dict__
- d.update(kwargs)
- # This else could cause trouble in Paragraphs with images etc.
- if "cbDefn" in d:
- del d["cbDefn"]
- n.bulletText = None
- return n
-
-ParaFrag.clone = clone
-
-def getParaFrag(style):
- frag = ParaFrag()
- frag.sub = 0
- frag.super = 0
- frag.rise = 0
- frag.underline = 0 # XXX Need to be able to set color to fit CSS tests
- frag.strike = 0
- frag.greek = 0
- frag.link = None
- frag.text = ""
-
- # frag.lineBreak = 0
- #if bullet:
- # frag.fontName, frag.bold, frag.italic = ps2tt(style.bulletFontName)
- # frag.fontSize = style.bulletFontSize
- # frag.textColor = hasattr(style,'bulletColor') and style.bulletColor or style.textColor
- #else:
-
- frag.fontName = "Times-Roman"
- frag.fontName, frag.bold, frag.italic = ps2tt(style.fontName)
- frag.fontSize = style.fontSize
- frag.textColor = style.textColor
-
- # Extras
- frag.leading = 0
- frag.leadingSource = "150%"
- frag.leadingSpace = 0
- frag.backColor = None
- frag.spaceBefore = 0
- frag.spaceAfter = 0
- frag.leftIndent = 0
- frag.rightIndent = 0
- frag.firstLineIndent = 0
- frag.keepWithNext = False
- frag.alignment = TA_LEFT
- frag.vAlign = None
-
- frag.borderWidth = 1
- frag.borderStyle = None
- frag.borderPadding = 0
- frag.borderColor = None
-
- frag.borderLeftWidth = frag.borderWidth
- frag.borderLeftColor = frag.borderColor
- frag.borderLeftStyle = frag.borderStyle
- frag.borderRightWidth = frag.borderWidth
- frag.borderRightColor = frag.borderColor
- frag.borderRightStyle = frag.borderStyle
- frag.borderTopWidth = frag.borderWidth
- frag.borderTopColor = frag.borderColor
- frag.borderTopStyle = frag.borderStyle
- frag.borderBottomWidth = frag.borderWidth
- frag.borderBottomColor = frag.borderColor
- frag.borderBottomStyle = frag.borderStyle
-
- frag.paddingLeft = 0
- frag.paddingRight = 0
- frag.paddingTop = 0
- frag.paddingBottom = 0
-
- frag.listStyleType = None
- frag.listStyleImage = None
- frag.whiteSpace = "normal"
-
- frag.pageNumber = False
- frag.height = None
- frag.width = None
-
- frag.bulletIndent = 0
- frag.bulletText = None
- frag.bulletFontName = "Helvetica"
-
- frag.zoom = 1.0
-
- frag.outline = False
- frag.outlineLevel = 0
- frag.outlineOpen = False
-
- frag.keepInFrameMode = "shrink"
- #frag.keepInFrameMaxWidth = None
- #frag.keepInFrameMaxHeight = None
-
- frag.insideStaticFrame = 0
-
- return frag
-
-def getDirName(path):
- if path and not (path.lower().startswith("http:") or path.lower().startswith("https:")):
- return os.path.dirname(os.path.abspath(path))
- return path
-
-class pisaCSSBuilder(css.CSSBuilder):
-
- def atFontFace(self, declarations):
- " Embed fonts "
- result = self.ruleset([self.selector('*')], declarations)
- # print "@FONT-FACE", declarations, result
- try:
- data = result[0].values()[0]
- names = data["font-family"]
-
- # Font weight
- fweight = str(data.get("font-weight", "normal")).lower()
- bold = fweight in ("bold", "bolder", "500", "600", "700", "800", "900")
- if not bold and fweight <> "normal":
- log.warn(self.c.warning("@fontface, unknown value font-weight '%s'", fweight))
-
- # Font style
- italic = str(data.get("font-style", "")).lower() in ("italic", "oblique")
-
- src = self.c.getFile(data["src"])
- self.c.loadFont(
- names,
- src,
- bold=bold,
- italic=italic)
- except Exception, e:
- log.warn(self.c.warning("@fontface"), exc_info=1)
- return {}, {}
-
- def _pisaDimensions(self, data, width, height):
- " Calculate dimensions of a box "
- # print data, width, height
- box = data.get("-pdf-frame-box", [])
- # print 123, box
- if len(box) == 4:
- return [getSize(x) for x in box]
- top = getSize(data.get("top", 0), height)
- left = getSize(data.get("left", 0), width)
- bottom = - getSize(data.get("bottom", 0), height)
- right = - getSize(data.get("right", 0), width)
- w = getSize(data.get("width", 0), width, default=None)
- h = getSize(data.get("height", 0), height, default=None)
- #print width, height, top, left, bottom, right, w, h
- if "height" in data:
- if "bottom" in data:
- top = bottom - h
- else:
- bottom = top + h
- if "width" in data:
- if "right" in data:
- # print right, w
- left = right - w
- else:
- right = left + w
- top += getSize(data.get("margin-top", 0), height)
- left += getSize(data.get("margin-left", 0), width)
- bottom -= getSize(data.get("margin-bottom", 0), height)
- right -= getSize(data.get("margin-right", 0), width)
- # box = getCoords(left, top, width, height, self.c.pageSize)
- # print "BOX", box
- # print top, left, w, h
- return left, top, right, bottom
-
- def _pisaAddFrame(self, name, data, first=False, border=None, size=(0,0)):
- c = self.c
- if not name:
- name = "-pdf-frame-%d" % c.UID()
- x, y, w, h = self._pisaDimensions(data, size[0], size[1])
- # print name, x, y, w, h
- #if not (w and h):
- # return None
- if first:
- return (
- name,
- None,
- data.get("-pdf-frame-border", border),
- x, y, w, h)
- return (
- name,
- data.get("-pdf-frame-content", None),
- data.get("-pdf-frame-border", border),
- x, y, w, h)
-
- def atPage(self, name, pseudopage, declarations):
- try:
-
- c = self.c
- data = {}
- name = name or "body"
- pageBorder = None
-
- if declarations:
- result = self.ruleset([self.selector('*')], declarations)
- # print "@PAGE", name, pseudopage, declarations, result
-
- if declarations:
- data = result[0].values()[0]
- pageBorder = data.get("-pdf-frame-border", None)
-
- if c.templateList.has_key(name):
- log.warn(self.c.warning("template '%s' has already been defined", name))
-
- if data.has_key("-pdf-page-size"):
- c.pageSize = pisa_default.PML_PAGESIZES.get(str(data["-pdf-page-size"]).lower(), c.pageSize)
-
- if data.has_key("size"):
- size = data["size"]
- # print size, c.pageSize
- if type(size) is not types.ListType:
- size = [size]
- isLandscape = False
- sizeList = []
- for value in size:
- valueStr = str(value).lower()
- if type(value) is types.TupleType:
- sizeList.append(getSize(value))
- elif valueStr == "landscape":
- isLandscape = True
- elif pisa_default.PML_PAGESIZES.has_key(valueStr):
- c.pageSize = pisa_default.PML_PAGESIZES[valueStr]
- else:
- log.warn(c.warning("Unknown size value for @page"))
-
- if len(sizeList) == 2:
- c.pageSize = sizeList
- if isLandscape:
- c.pageSize = landscape(c.pageSize)
-
- for prop in [
- "margin-top",
- "margin-left",
- "margin-right",
- "margin-bottom",
- "top",
- "left",
- "right",
- "bottom",
- "width",
- "height"
- ]:
- if data.has_key(prop):
- c.frameList.append(self._pisaAddFrame(name, data, first=True, border=pageBorder, size=c.pageSize))
- break
- # self._drawing = PmlPageDrawing(self._pagesize)
-
- #if not c.frameList:
- # c.warning("missing frame definitions for template")
- # return {}, {}
-
- # Frames have to be calculated after we know the pagesize
- frameList = []
- staticList = []
- for fname, static, border, x, y, w, h in c.frameList:
- x, y, w, h = getCoords(x, y, w, h, c.pageSize)
- if w <= 0 or h <= 0:
- log.warn(self.c.warning("Negative width or height of frame. Check @frame definitions."))
- frame = Frame(
- x, y, w, h,
- id=fname,
- leftPadding=0,
- rightPadding=0,
- bottomPadding=0,
- topPadding=0,
- showBoundary=border or pageBorder)
- if static:
- frame.pisaStaticStory = []
- c.frameStatic[static] = [frame] + c.frameStatic.get(static, [])
- staticList.append(frame)
- else:
- frameList.append(frame)
-
- background = data.get("background-image", None)
- if background:
- background = self.c.getFile(background)
- # print background
-
- # print frameList
- if not frameList:
- # print 999
- log.warn(c.warning("missing explicit frame definition for content or just static frames"))
- fname, static, border, x, y, w, h = self._pisaAddFrame(name, data, first=True, border=pageBorder, size=c.pageSize)
- x, y, w, h = getCoords(x, y, w, h, c.pageSize)
- if w <= 0 or h <= 0:
- log.warn(c.warning("Negative width or height of frame. Check @page definitions."))
- frameList.append(Frame(
- x, y, w, h,
- id=fname,
- leftPadding=0,
- rightPadding=0,
- bottomPadding=0,
- topPadding=0,
- showBoundary=border or pageBorder))
-
- pt = PmlPageTemplate(
- id=name,
- frames=frameList,
- pagesize=c.pageSize,
- )
- pt.pisaStaticList = staticList
- pt.pisaBackground = background
- pt.pisaBackgroundList = c.pisaBackgroundList
-
- # self._pagesize)
- # pt.pml_statics = self._statics
- # pt.pml_draw = self._draw
- # pt.pml_drawing = self._drawing
- # pt.pml_background = attrs.background
- # pt.pml_bgstory = self._bgstory
-
- c.templateList[name] = pt
- c.template = None
- c.frameList = []
- c.frameStaticList = []
-
- except Exception, e:
- log.warn(self.c.warning("@page"), exc_info=1)
-
- return {}, {}
-
- def atFrame(self, name, declarations):
- if declarations:
- result = self.ruleset([self.selector('*')], declarations)
- # print "@BOX", name, declarations, result
- try:
- data = result[0]
- if data:
- data = data.values()[0]
- self.c.frameList.append(
- self._pisaAddFrame(
- name,
- data,
- size=self.c.pageSize))
- except Exception, e:
- log.warn(self.c.warning("@frame"), exc_info=1)
- return {}, {}
-
-class pisaCSSParser(css.CSSParser):
-
- def parseExternal(self, cssResourceName):
-
- # print "@import", self.rootPath, cssResourceName
- oldRootPath = self.rootPath
- cssFile = self.c.getFile(cssResourceName, relative=self.rootPath)
- result = []
- if not cssFile:
- return None
- if self.rootPath and (self.rootPath.startswith("http:") or self.rootPath.startswith("https:")):
- self.rootPath = urlparse.urljoin(self.rootPath, cssResourceName)
- else:
- self.rootPath = getDirName(cssFile.uri)
- # print "###", self.rootPath
- result = self.parse(cssFile.getData())
- self.rootPath = oldRootPath
- return result
-
-class pisaContext:
-
- """
- Helper class for creation of reportlab story and container for
- varoius data.
- """
-
- def __init__(self, path, debug=0, capacity=-1):
- self.fontList = copy.copy(pisa_default.DEFAULT_FONT)
- self.path = []
- self.capacity=capacity
-
- self.node = None
- self.toc = PmlTableOfContents()
- self.story = []
- self.text = []
- self.log = []
- self.err = 0
- self.warn = 0
- self.text = u""
- self.uidctr = 0
- self.multiBuild = False
-
- self.pageSize = A4
- self.template = None
- self.templateList = {}
-
- self.frameList = []
- self.frameStatic = {}
- self.frameStaticList = []
- self.pisaBackgroundList = []
-
- self.baseFontSize = getSize("12pt")
-
- self.anchorFrag = []
- self.anchorName = []
-
- self.tableData = None
-
- self.frag = self.fragBlock = getParaFrag(ParagraphStyle('default%d' % self.UID()))
- self.fragList = []
- self.fragAnchor = []
- self.fragStack = []
- self.fragStrip = True
-
- self.listCounter = 0
-
- self.cssText = ""
-
- self.image = None
- self.imageData = {}
- self.force = False
-
- self.pathCallback = None # External callback function for path calculations
-
- # Store path to document
- self.pathDocument = path or "__dummy__"
- if not (self.pathDocument.lower().startswith("http:") or self.pathDocument.lower().startswith("https:")):
- self.pathDocument = os.path.abspath(self.pathDocument)
- self.pathDirectory = getDirName(self.pathDocument)
-
- self.meta = dict(
- author="",
- title="",
- subject="",
- keywords="",
- pagesize=A4,
- )
-
- def UID(self):
- self.uidctr += 1
- return self.uidctr
-
- # METHODS FOR CSS
-
- def addCSS(self, value):
- value = value.strip()
- if value.startswith("", "\n")
- #self.cssText = self.cssText.replace("", "\n")
-
- #self.debug(9, self.cssText)
-
- # print repr(self.cssText)
- # file("pisa.css", "wb").write(self.cssText.encode("utf8"))
-
- # self.cssText = re.compile(r"url\((.*?\))", re.M).sub('"\1"', self.cssText)
- # self.cssText = re.compile(r"\-moz\-.*?([\;\}]+)", re.M).sub(r"\1", self.cssText)
-
- # XXX Import has to be implemented!
- # self.cssText = re.compile(r"\@import.*;", re.M).sub("", self.cssText)
-
-# if 0:
-# try:
-# # Sanitize CSS
-# import cssutils
-# import logging
-# cssutils.log.setlog(logging.getLogger('csslog'))
-# cssutils.log.setloglevel(logging.DEBUG)
-# sheet = cssutils.parseString(self.cssText)
-# self.cssText = sheet.cssText
-# #err = csslog.getvalue()
-# except ImportError, e:
-# pass
-# except Exception, e:
-# log.exception(self.error("Error parsing CSS by cssutils"))
-
- # print self.cssText
- # file("pisa-sanitized.css", "w").write(self.cssText.encode("utf8"))
- # print self.cssText
-
-
- self.cssBuilder = pisaCSSBuilder(mediumSet=["all", "print", "pdf"])
- self.cssBuilder.c = self
- self.cssParser = pisaCSSParser(self.cssBuilder)
- self.cssParser.rootPath = self.pathDirectory
- self.cssParser.c = self
-
- self.css = self.cssParser.parse(self.cssText)
- self.cssCascade = css.CSSCascadeStrategy(self.css)
- self.cssCascade.parser = self.cssParser
-
- # METHODS FOR STORY
-
- def addStory(self, data):
- self.story.append(data)
-
- def swapStory(self, story=[]):
- self.story, story = copy.copy(story), copy.copy(self.story)
- return story
-
- def toParagraphStyle(self, first):
- style = ParagraphStyle('default%d' % self.UID(), keepWithNext=first.keepWithNext)
- style.fontName = first.fontName
- style.fontSize = first.fontSize
- style.leading = max(first.leading + first.leadingSpace, first.fontSize * 1.25)
- style.backColor = first.backColor
- style.spaceBefore = first.spaceBefore
- style.spaceAfter = first.spaceAfter
- style.leftIndent = first.leftIndent
- style.rightIndent = first.rightIndent
- style.firstLineIndent = first.firstLineIndent
- style.textColor = first.textColor
- style.alignment = first.alignment
- style.bulletFontName = first.bulletFontName or first.fontName
- style.bulletFontSize = first.fontSize
- style.bulletIndent = first.bulletIndent
-
- # Border handling for Paragraph
-
- # Transfer the styles for each side of the border, *not* the whole
- # border values that reportlab supports. We'll draw them ourselves in
- # PmlParagraph.
- style.borderTopStyle = first.borderTopStyle
- style.borderTopWidth = first.borderTopWidth
- style.borderTopColor = first.borderTopColor
- style.borderBottomStyle = first.borderBottomStyle
- style.borderBottomWidth = first.borderBottomWidth
- style.borderBottomColor = first.borderBottomColor
- style.borderLeftStyle = first.borderLeftStyle
- style.borderLeftWidth = first.borderLeftWidth
- style.borderLeftColor = first.borderLeftColor
- style.borderRightStyle = first.borderRightStyle
- style.borderRightWidth = first.borderRightWidth
- style.borderRightColor = first.borderRightColor
-
- # If no border color is given, the text color is used (XXX Tables!)
- if (style.borderTopColor is None) and style.borderTopWidth:
- style.borderTopColor = first.textColor
- if (style.borderBottomColor is None) and style.borderBottomWidth:
- style.borderBottomColor = first.textColor
- if (style.borderLeftColor is None) and style.borderLeftWidth:
- style.borderLeftColor = first.textColor
- if (style.borderRightColor is None) and style.borderRightWidth:
- style.borderRightColor = first.textColor
-
- style.borderPadding = first.borderPadding
-
- style.paddingTop = first.paddingTop
- style.paddingBottom = first.paddingBottom
- style.paddingLeft = first.paddingLeft
- style.paddingRight = first.paddingRight
-
- # This is the old code replaced by the above, kept for reference
- #style.borderWidth = 0
- #if getBorderStyle(first.borderTopStyle):
- # style.borderWidth = max(first.borderLeftWidth, first.borderRightWidth, first.borderTopWidth, first.borderBottomWidth)
- # style.borderPadding = first.borderPadding # + first.borderWidth
- # style.borderColor = first.borderTopColor
- # # If no border color is given, the text color is used (XXX Tables!)
- # if (style.borderColor is None) and style.borderWidth:
- # style.borderColor = first.textColor
-
- style.fontName = tt2ps(first.fontName, first.bold, first.italic)
- return style
-
- def addTOC(self):
- # style = copy.deepcopy(self.toParagraphStyle(self.fragBlock))
- #cssAttrs = copy.deepcopy(self.node.cssAttrs)
- #frag = copy.deepcopy(self.frag)
- styles = []
- for i in range(0, 20):
- self.node.attributes["class"] = "pdftoclevel%d" % i
- #self.node.cssAttrs = copy.deepcopy(cssAttrs)
- #self.frag = copy.deepcopy(frag)
- self.cssAttr = pisa_parser.CSSCollect(self.node, self)
- pisa_parser.CSS2Frag(self, {
- "margin-top": 0,
- "margin-bottom": 0,
- "margin-left": 0,
- "margin-right": 0,
- }, True)
- pstyle = self.toParagraphStyle(self.frag)
- #styles.append(copy.deepcopy(pstyle))
- styles.append(pstyle)
-
- # log.warn("%r", self.fragBlock.textColor)
- self.toc.levelStyles = styles
- self.addStory(self.toc)
-
- def dumpPara(self, frags, style):
- return
-
- print "%s/%s %s *** PARA" % (style.fontSize, style.leading, style.fontName)
- for frag in frags:
- print "%s/%s %r %r" % (
- frag.fontSize,
- frag.leading,
- getattr(frag, "cbDefn", None),
- frag.text)
- print
-
- def addPara(self, force=False):
-
- # print self.force, repr(self.text)
- force = (force or self.force)
- self.force = False
-
- # Cleanup the trail
- try:
- rfragList = reversed(self.fragList)
- except:
- # For Python 2.3 compatibility
- rfragList = copy.copy(self.fragList)
- rfragList.reverse()
-
- #for frag in rfragList:
- # frag.text = frag.text.rstrip()
- # if frag.text:
- # break
-
- # Find maximum lead
- maxLeading = 0
- #fontSize = 0
- for frag in self.fragList:
- leading = getSize(frag.leadingSource, frag.fontSize) + frag.leadingSpace
- maxLeading = max(leading, frag.fontSize + frag.leadingSpace, maxLeading)
- frag.leading = leading
-
- if force or (self.text.strip() and self.fragList):
-
- # Strip trailing whitespaces
- #for f in self.fragList:
- # f.text = f.text.lstrip()
- # if f.text:
- # break
- #self.fragList[-1].lineBreak = self.fragList[-1].text.rstrip()
-
- # Update paragraph style by style of first fragment
- first = self.fragBlock
- style = self.toParagraphStyle(first)
- # style.leading = first.leading + first.leadingSpace
- if first.leadingSpace:
- style.leading = maxLeading
- else:
- style.leading = getSize(first.leadingSource, first.fontSize) + first.leadingSpace
- # style.leading = maxLeading # + first.leadingSpace
- #style.fontSize = fontSize
-
- # borderRadius: None,
- # print repr(self.text.strip()), style.leading, "".join([repr(x.text) for x in self.fragList])
- # print first.leftIndent, first.listStyleType,repr(self.text)
-
- bulletText = copy.copy(first.bulletText)
- first.bulletText = None
-
- # Add paragraph to story
- if force or len(self.fragAnchor + self.fragList) > 0:
-
- # We need this empty fragment to work around problems in
- # Reportlab paragraphs regarding backGround etc.
- if self.fragList:
- self.fragList.append(self.fragList[ - 1].clone(text=''))
- else:
- blank = self.frag.clone()
- blank.fontName = "Helvetica"
- blank.text = ''
- self.fragList.append(blank)
-
- self.dumpPara(self.fragAnchor + self.fragList, style)
- para = PmlParagraph(
- self.text,
- style,
- frags=self.fragAnchor + self.fragList,
- bulletText=bulletText)
-
- # Mirrored and BIDI
- #import unicodedata
- #for c in self.text:
- # print unicodedata.bidirectional(c),
-
- para.outline = first.outline
- para.outlineLevel = first.outlineLevel
- para.outlineOpen = first.outlineOpen
- para.keepWithNext = first.keepWithNext
- para.autoLeading = "max"
-
- if self.image:
- para = PmlParagraphAndImage(
- para,
- self.image,
- side=self.imageData.get("align", "left"))
-
- self.addStory(para)
-
- self.fragAnchor = []
- first.bulletText = None
-
- # Reset data
-
- self.image = None
- self.imageData = {}
-
- self.clearFrag()
-
- # METHODS FOR FRAG
-
- def clearFrag(self):
- self.fragList = []
- self.fragStrip = True
- self.text = u""
-
- def copyFrag(self, **kw):
- return self.frag.clone(**kw)
-
- def newFrag(self, **kw):
- self.frag = self.frag.clone(**kw)
- return self.frag
-
- def _appendFrag(self, frag):
- if frag.link and frag.link.startswith("#"):
- self.anchorFrag.append((frag, frag.link[1:]))
- self.fragList.append(frag)
-
- # XXX Argument frag is useless!
- def addFrag(self, text="", frag=None):
-
- frag = baseFrag = self.frag.clone()
-
- # if sub and super are both on they will cancel each other out
- if frag.sub == 1 and frag.super == 1:
- frag.sub = 0
- frag.super = 0
-
- # XXX Has to be replaced by CSS styles like vertical-align and font-size
- if frag.sub:
- frag.rise = - frag.fontSize * subFraction
- frag.fontSize = max(frag.fontSize - sizeDelta, 3)
- elif frag.super:
- frag.rise = frag.fontSize * superFraction
- frag.fontSize = max(frag.fontSize - sizeDelta, 3)
-
- # XXX Unused?
- #if frag.greek:
- # frag.fontName = 'symbol'
- # text = _greekConvert(text)
-
- # bold, italic, and underline
- frag.fontName = frag.bulletFontName = tt2ps(frag.fontName, frag.bold, frag.italic)
- # print frag.bulletFontName
-
- # Modify text for optimal whitespace handling
- # XXX Support Unicode whitespaces?
- # XXX What about images?
-
- # XXX Doesn't work with Reportlab > 2.1
- # NBSP = '\xc2\xa0' # u"_"
-
- #if REPORTLAB22:
- # NBSP = u" "
-
- # Replace with empty and normalize NBSP
- text = (text
- .replace(u"\xad", u"")
- .replace(u"\xc2\xa0", NBSP)
- .replace(u"\xa0", NBSP))
-
- # log.debug("> %r", text)
-
- if frag.whiteSpace == "pre":
-
- # Handle by lines
- for text in re.split(r'(\r\n|\n|\r)', text):
- # This is an exceptionally expensive piece of code
- self.text += text
- if ("\n" in text) or ("\r" in text):
- # If EOL insert a linebreak
- frag = baseFrag.clone()
- frag.text = ""
- frag.lineBreak = 1
- self._appendFrag(frag)
- else:
- # Handle tabs in a simple way
- text = text.replace(u"\t", 8 * u" ")
- # Somehow for Reportlab NBSP have to be inserted
- # as single character fragments
- for text in re.split(r'(\ )', text):
- frag = baseFrag.clone()
- if text == " ":
- text = NBSP
- frag.text = text
- self._appendFrag(frag)
- else:
- for text in re.split(u'(' + NBSP + u')', text):
- frag = baseFrag.clone()
- if text == NBSP:
- self.force = True
- frag.text = NBSP
- self.text += text
- self._appendFrag(frag)
- else:
- frag.text = " ".join(("x" + text + "x").split())[1: - 1]
- if self.fragStrip:
- frag.text = frag.text.lstrip()
- if frag.text:
- self.fragStrip = False
- self.text += frag.text
- self._appendFrag(frag)
- # print frag.fontName, repr(frag.text), frag.bulletText
-
- def pushFrag(self):
- self.fragStack.append(self.frag)
- self.newFrag()
-
- def pullFrag(self):
- self.frag = self.fragStack.pop()
-
- # XXX
-
- def _getFragment(self, l=20):
- try:
- return repr(" ".join(self.node.toxml().split()[:l]))
- except:
- return ""
-
- def _getLineNumber(self):
- return 0
-
- def context(self, msg):
- return "%s\n%s" % (
- str(msg),
- self._getFragment(50))
-# return "line %s: %s\n%s" % (
-# self._getLineNumber(),
-# str(msg),
-# self._getFragment(50))
-
- def warning(self, msg, *args):
- self.warn += 1
- self.log.append((pisa_default.PML_WARNING, self._getLineNumber(), str(msg), self._getFragment(50)))
- try:
- return self.context(msg % args)
- except:
- return self.context(msg)
-
- def error(self, msg, *args):
- self.err += 1
- self.log.append((pisa_default.PML_ERROR, self._getLineNumber(), str(msg), self._getFragment(50)))
- try:
- return self.context(msg % args)
- except:
- return self.context(msg)
-
- # UTILS
-
- def _getFileDeprecated(self, name, relative):
- try:
- if name.startswith("data:"):
- return name
- path = relative or self.pathDirectory
- if self.pathCallback is not None:
- nv = self.pathCallback(name, relative)
- else:
- if path is None:
- log.warn("Could not find main directory for getting filename. Use CWD")
- path = os.getcwd()
- nv = os.path.normpath(os.path.join(path, name))
- if not(nv and os.path.isfile(nv)):
- nv = None
- if nv is None:
- log.warn(self.warning("File '%s' does not exist", name))
- return nv
- except:
- log.warn(self.warning("getFile %r %r %r", name, relative, path), exc_info=1)
-
- def getFile(self, name, relative=None):
- """
- Returns a file name or None
- """
- if self.pathCallback is not None:
- return getFile(self._getFileDeprecated(name, relative))
- return getFile(name, relative or self.pathDirectory)
-
- def getFontName(self, names, default="helvetica"):
- """
- Name of a font
- """
- # print names, self.fontList
- if type(names) is not types.ListType:
- names = str(names).strip().split(",")
- for name in names:
- font = self.fontList.get(str(name).strip().lower(), None)
- if font is not None:
- return font
- return self.fontList.get(default, None)
-
- def registerFont(self, fontname, alias=[]):
- self.fontList[str(fontname).lower()] = str(fontname)
- for a in alias:
- self.fontList[str(a)] = str(fontname)
-
- def loadFont(self, names, src, encoding="WinAnsiEncoding", bold=0, italic=0):
-
- # XXX Just works for local filenames!
- if names and src: # and src.local:
-
- file = src
- src = file.uri
-
- log.debug("Load font %r", src)
-
- if type(names) is types.ListType:
- fontAlias = names
- else:
- fontAlias = [x.lower().strip() for x in names.split(",") if x]
-
- # XXX Problems with unicode here
- fontAlias = [str(x) for x in fontAlias]
-
- fontName = fontAlias[0]
- parts = src.split(".")
- baseName, suffix = ".".join(parts[: - 1]), parts[ - 1]
- suffix = suffix.lower()
-
- try:
-
- if suffix == "ttf":
-
- # determine full font name according to weight and style
- fullFontName = "%s_%d%d" % (fontName, bold, italic)
-
- # check if font has already been registered
- if fullFontName in self.fontList:
- log.warn(self.warning("Repeated font embed for %s, skip new embed ", fullFontName))
- else:
-
- # Register TTF font and special name
- filename = file.getNamedFile()
- pdfmetrics.registerFont(TTFont(fullFontName, filename))
-
- # Add or replace missing styles
- for bold in (0, 1):
- for italic in (0, 1):
- if ("%s_%d%d" % (fontName, bold, italic)) not in self.fontList:
- addMapping(fontName, bold, italic, fullFontName)
-
- # Register "normal" name and the place holder for style
- self.registerFont(fontName, fontAlias + [fullFontName])
-
- elif suffix in ("afm", "pfb"):
-
- if suffix == "afm":
- afm = file.getNamedFile()
- tfile = pisaFileObject(baseName + ".pfb")
- pfb = tfile.getNamedFile()
- else:
- pfb = file.getNamedFile()
- tfile = pisaFileObject(baseName + ".afm")
- afm = tfile.getNamedFile()
-
- #afm = baseName + ".afm"
- #pfb = baseName + ".pfb"
-
- # determine full font name according to weight and style
- fullFontName = "%s_%d%d" % (fontName, bold, italic)
-
- #fontNameOriginal = ""
- #for line in open(afm).readlines()[:-1]:
- # if line[:16] == 'StartCharMetrics':
- # self.error("Font name not found")
- # if line[:8] == 'FontName':
- # fontNameOriginal = line[9:].strip()
- # break
-
- # check if font has already been registered
- if fullFontName in self.fontList:
- log.warn(self.warning("Repeated font embed for %s, skip new embed", fontName))
- else:
-
- # Include font
- face = pdfmetrics.EmbeddedType1Face(afm, pfb)
- fontNameOriginal = face.name
- pdfmetrics.registerTypeFace(face)
- # print fontName, fontNameOriginal, fullFontName
- justFont = pdfmetrics.Font(fullFontName, fontNameOriginal, encoding)
- pdfmetrics.registerFont(justFont)
-
- # Add or replace missing styles
- for bold in (0, 1):
- for italic in (0, 1):
- if ("%s_%d%d" % (fontName, bold, italic)) not in self.fontList:
- addMapping(fontName, bold, italic, fontNameOriginal)
-
- # Register "normal" name and the place holder for style
- self.registerFont(fontName, fontAlias + [fullFontName, fontNameOriginal])
-
- #import pprint
- #pprint.pprint(self.fontList)
-
- else:
- log.warning(self.warning("wrong attributes for "))
-
- except Exception:
- log.warn(self.warning("Loading font '%s'", fontName), exc_info=1)
+# -*- coding: utf-8 -*-
+
+# Copyright 2010 Dirk Holtwick, holtwick.it
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__reversion__ = "$Revision: 20 $"
+__author__ = "$Author: holtwick $"
+__date__ = "$Date: 2007-10-09 12:58:24 +0200 (Di, 09 Okt 2007) $"
+
+from pisa_util import *
+from pisa_reportlab import *
+
+import pisa_default
+import pisa_parser
+import re
+import urlparse
+import types
+
+from reportlab.platypus.paraparser import ParaParser, ParaFrag, ps2tt, tt2ps, ABag
+from reportlab.platypus.paragraph import cleanBlockQuotedText
+from reportlab.lib.styles import ParagraphStyle
+
+import reportlab.rl_config
+reportlab.rl_config.warnOnMissingFontGlyphs = 0
+
+from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.ttfonts import TTFont
+from reportlab.lib.fonts import addMapping
+
+from sx.w3c import css, cssDOMElementInterface
+
+from html5lib.sanitizer import *
+
+import logging
+log = logging.getLogger("ho.pisa")
+
+sizeDelta = 2 # amount to reduce font size by for super and sub script
+subFraction = 0.4 # fraction of font size that a sub script should be lowered
+superFraction = 0.4
+
+NBSP = u"\u00a0"
+
+def clone(self, **kwargs):
+ n = ParaFrag(**self.__dict__)
+ if kwargs:
+ d = n.__dict__
+ d.update(kwargs)
+ # This else could cause trouble in Paragraphs with images etc.
+ if "cbDefn" in d:
+ del d["cbDefn"]
+ n.bulletText = None
+ return n
+
+ParaFrag.clone = clone
+
+def getParaFrag(style):
+ frag = ParaFrag()
+ frag.sub = 0
+ frag.super = 0
+ frag.rise = 0
+ frag.underline = 0 # XXX Need to be able to set color to fit CSS tests
+ frag.strike = 0
+ frag.greek = 0
+ frag.link = None
+ frag.text = ""
+
+ # frag.lineBreak = 0
+ #if bullet:
+ # frag.fontName, frag.bold, frag.italic = ps2tt(style.bulletFontName)
+ # frag.fontSize = style.bulletFontSize
+ # frag.textColor = hasattr(style,'bulletColor') and style.bulletColor or style.textColor
+ #else:
+
+ frag.fontName = "Times-Roman"
+ frag.fontName, frag.bold, frag.italic = ps2tt(style.fontName)
+ frag.fontSize = style.fontSize
+ frag.textColor = style.textColor
+
+ # Extras
+ frag.leading = 0
+ frag.leadingSource = "150%"
+ frag.leadingSpace = 0
+ frag.backColor = None
+ frag.spaceBefore = 0
+ frag.spaceAfter = 0
+ frag.leftIndent = 0
+ frag.rightIndent = 0
+ frag.firstLineIndent = 0
+ frag.keepWithNext = False
+ frag.alignment = TA_LEFT
+ frag.vAlign = None
+
+ frag.borderWidth = 1
+ frag.borderStyle = None
+ frag.borderPadding = 0
+ frag.borderColor = None
+
+ frag.borderLeftWidth = frag.borderWidth
+ frag.borderLeftColor = frag.borderColor
+ frag.borderLeftStyle = frag.borderStyle
+ frag.borderRightWidth = frag.borderWidth
+ frag.borderRightColor = frag.borderColor
+ frag.borderRightStyle = frag.borderStyle
+ frag.borderTopWidth = frag.borderWidth
+ frag.borderTopColor = frag.borderColor
+ frag.borderTopStyle = frag.borderStyle
+ frag.borderBottomWidth = frag.borderWidth
+ frag.borderBottomColor = frag.borderColor
+ frag.borderBottomStyle = frag.borderStyle
+
+ frag.paddingLeft = 0
+ frag.paddingRight = 0
+ frag.paddingTop = 0
+ frag.paddingBottom = 0
+
+ frag.listStyleType = None
+ frag.listStyleImage = None
+ frag.whiteSpace = "normal"
+
+ frag.wordWrap = None
+
+ frag.pageNumber = False
+ frag.height = None
+ frag.width = None
+
+ frag.bulletIndent = 0
+ frag.bulletText = None
+ frag.bulletFontName = "Helvetica"
+
+ frag.zoom = 1.0
+
+ frag.outline = False
+ frag.outlineLevel = 0
+ frag.outlineOpen = False
+
+ frag.keepInFrameMode = "shrink"
+ #frag.keepInFrameMaxWidth = None
+ #frag.keepInFrameMaxHeight = None
+
+ frag.insideStaticFrame = 0
+
+ return frag
+
+def getDirName(path):
+ if path and not (path.lower().startswith("http:") or path.lower().startswith("https:")):
+ return os.path.dirname(os.path.abspath(path))
+ return path
+
+class pisaCSSBuilder(css.CSSBuilder):
+
+ def atFontFace(self, declarations):
+ " Embed fonts "
+ result = self.ruleset([self.selector('*')], declarations)
+ # print "@FONT-FACE", declarations, result
+ try:
+ data = result[0].values()[0]
+ names = data["font-family"]
+
+ # Font weight
+ fweight = str(data.get("font-weight", "normal")).lower()
+ bold = fweight in ("bold", "bolder", "500", "600", "700", "800", "900")
+ if not bold and fweight <> "normal":
+ log.warn(self.c.warning("@fontface, unknown value font-weight '%s'", fweight))
+
+ # Font style
+ italic = str(data.get("font-style", "")).lower() in ("italic", "oblique")
+
+ src = self.c.getFile(data["src"])
+ self.c.loadFont(
+ names,
+ src,
+ bold=bold,
+ italic=italic)
+ except Exception, e:
+ log.warn(self.c.warning("@fontface"), exc_info=1)
+ return {}, {}
+
+ def _pisaDimensions(self, data, width, height):
+ " Calculate dimensions of a box "
+ # print data, width, height
+ box = data.get("-pdf-frame-box", [])
+ # print 123, box
+ if len(box) == 4:
+ return [getSize(x) for x in box]
+ top = getSize(data.get("top", 0), height)
+ left = getSize(data.get("left", 0), width)
+ bottom = - getSize(data.get("bottom", 0), height)
+ right = - getSize(data.get("right", 0), width)
+ w = getSize(data.get("width", 0), width, default=None)
+ h = getSize(data.get("height", 0), height, default=None)
+ #print width, height, top, left, bottom, right, w, h
+ if "height" in data:
+ if "bottom" in data:
+ top = bottom - h
+ else:
+ bottom = top + h
+ if "width" in data:
+ if "right" in data:
+ # print right, w
+ left = right - w
+ else:
+ right = left + w
+ top += getSize(data.get("margin-top", 0), height)
+ left += getSize(data.get("margin-left", 0), width)
+ bottom -= getSize(data.get("margin-bottom", 0), height)
+ right -= getSize(data.get("margin-right", 0), width)
+ # box = getCoords(left, top, width, height, self.c.pageSize)
+ # print "BOX", box
+ # print top, left, w, h
+ return left, top, right, bottom
+
+ def _pisaAddFrame(self, name, data, first=False, border=None, size=(0,0)):
+ c = self.c
+ if not name:
+ name = "-pdf-frame-%d" % c.UID()
+ x, y, w, h = self._pisaDimensions(data, size[0], size[1])
+ # print name, x, y, w, h
+ #if not (w and h):
+ # return None
+ if first:
+ return (
+ name,
+ None,
+ data.get("-pdf-frame-border", border),
+ x, y, w, h)
+ return (
+ name,
+ data.get("-pdf-frame-content", None),
+ data.get("-pdf-frame-border", border),
+ x, y, w, h)
+
+ def atPage(self, name, pseudopage, declarations):
+ try:
+
+ c = self.c
+ data = {}
+ name = name or "body"
+ pageBorder = None
+
+ if declarations:
+ result = self.ruleset([self.selector('*')], declarations)
+ # print "@PAGE", name, pseudopage, declarations, result
+
+ if declarations:
+ data = result[0].values()[0]
+ pageBorder = data.get("-pdf-frame-border", None)
+
+ if c.templateList.has_key(name):
+ log.warn(self.c.warning("template '%s' has already been defined", name))
+
+ if data.has_key("-pdf-page-size"):
+ c.pageSize = pisa_default.PML_PAGESIZES.get(str(data["-pdf-page-size"]).lower(), c.pageSize)
+
+ if data.has_key("size"):
+ size = data["size"]
+ # print size, c.pageSize
+ if type(size) is not types.ListType:
+ size = [size]
+ isLandscape = False
+ sizeList = []
+ for value in size:
+ valueStr = str(value).lower()
+ if type(value) is types.TupleType:
+ sizeList.append(getSize(value))
+ elif valueStr == "landscape":
+ isLandscape = True
+ elif pisa_default.PML_PAGESIZES.has_key(valueStr):
+ c.pageSize = pisa_default.PML_PAGESIZES[valueStr]
+ else:
+ log.warn(c.warning("Unknown size value for @page"))
+
+ if len(sizeList) == 2:
+ c.pageSize = sizeList
+ if isLandscape:
+ c.pageSize = landscape(c.pageSize)
+
+ for prop in [
+ "margin-top",
+ "margin-left",
+ "margin-right",
+ "margin-bottom",
+ "top",
+ "left",
+ "right",
+ "bottom",
+ "width",
+ "height"
+ ]:
+ if data.has_key(prop):
+ c.frameList.append(self._pisaAddFrame(name, data, first=True, border=pageBorder, size=c.pageSize))
+ break
+ # self._drawing = PmlPageDrawing(self._pagesize)
+
+ #if not c.frameList:
+ # c.warning("missing frame definitions for template")
+ # return {}, {}
+
+ # Frames have to be calculated after we know the pagesize
+ frameList = []
+ staticList = []
+ for fname, static, border, x, y, w, h in c.frameList:
+ x, y, w, h = getCoords(x, y, w, h, c.pageSize)
+ if w <= 0 or h <= 0:
+ log.warn(self.c.warning("Negative width or height of frame. Check @frame definitions."))
+ frame = Frame(
+ x, y, w, h,
+ id=fname,
+ leftPadding=0,
+ rightPadding=0,
+ bottomPadding=0,
+ topPadding=0,
+ showBoundary=border or pageBorder)
+ if static:
+ frame.pisaStaticStory = []
+ c.frameStatic[static] = [frame] + c.frameStatic.get(static, [])
+ staticList.append(frame)
+ else:
+ frameList.append(frame)
+
+ background = data.get("background-image", None)
+ if background:
+ background = self.c.getFile(background)
+ # print background
+
+ # print frameList
+ if not frameList:
+ # print 999
+ log.warn(c.warning("missing explicit frame definition for content or just static frames"))
+ fname, static, border, x, y, w, h = self._pisaAddFrame(name, data, first=True, border=pageBorder, size=c.pageSize)
+ x, y, w, h = getCoords(x, y, w, h, c.pageSize)
+ if w <= 0 or h <= 0:
+ log.warn(c.warning("Negative width or height of frame. Check @page definitions."))
+ frameList.append(Frame(
+ x, y, w, h,
+ id=fname,
+ leftPadding=0,
+ rightPadding=0,
+ bottomPadding=0,
+ topPadding=0,
+ showBoundary=border or pageBorder))
+
+ pt = PmlPageTemplate(
+ id=name,
+ frames=frameList,
+ pagesize=c.pageSize,
+ )
+ pt.pisaStaticList = staticList
+ pt.pisaBackground = background
+ pt.pisaBackgroundList = c.pisaBackgroundList
+
+ # self._pagesize)
+ # pt.pml_statics = self._statics
+ # pt.pml_draw = self._draw
+ # pt.pml_drawing = self._drawing
+ # pt.pml_background = attrs.background
+ # pt.pml_bgstory = self._bgstory
+
+ c.templateList[name] = pt
+ c.template = None
+ c.frameList = []
+ c.frameStaticList = []
+
+ except Exception, e:
+ log.warn(self.c.warning("@page"), exc_info=1)
+
+ return {}, {}
+
+ def atFrame(self, name, declarations):
+ if declarations:
+ result = self.ruleset([self.selector('*')], declarations)
+ # print "@BOX", name, declarations, result
+ try:
+ data = result[0]
+ if data:
+ data = data.values()[0]
+ self.c.frameList.append(
+ self._pisaAddFrame(
+ name,
+ data,
+ size=self.c.pageSize))
+ except Exception, e:
+ log.warn(self.c.warning("@frame"), exc_info=1)
+ return {}, {}
+
+class pisaCSSParser(css.CSSParser):
+
+ def parseExternal(self, cssResourceName):
+
+ # print "@import", self.rootPath, cssResourceName
+ oldRootPath = self.rootPath
+ cssFile = self.c.getFile(cssResourceName, relative=self.rootPath)
+ result = []
+ if not cssFile:
+ return None
+ if self.rootPath and (self.rootPath.startswith("http:") or self.rootPath.startswith("https:")):
+ self.rootPath = urlparse.urljoin(self.rootPath, cssResourceName)
+ else:
+ self.rootPath = getDirName(cssFile.uri)
+ # print "###", self.rootPath
+ result = self.parse(cssFile.getData())
+ self.rootPath = oldRootPath
+ return result
+
+class pisaContext:
+
+ """
+ Helper class for creation of reportlab story and container for
+ varoius data.
+ """
+
+ def __init__(self, path, debug=0, capacity=-1):
+ self.fontList = copy.copy(pisa_default.DEFAULT_FONT)
+ self.path = []
+ self.capacity=capacity
+
+ self.node = None
+ self.toc = PmlTableOfContents()
+ self.story = []
+ self.text = []
+ self.log = []
+ self.err = 0
+ self.warn = 0
+ self.text = u""
+ self.uidctr = 0
+ self.multiBuild = False
+
+ self.pageSize = A4
+ self.template = None
+ self.templateList = {}
+
+ self.frameList = []
+ self.frameStatic = {}
+ self.frameStaticList = []
+ self.pisaBackgroundList = []
+
+ self.baseFontSize = getSize("12pt")
+
+ self.anchorFrag = []
+ self.anchorName = []
+
+ self.tableData = None
+
+ self.frag = self.fragBlock = getParaFrag(ParagraphStyle('default%d' % self.UID()))
+ self.fragList = []
+ self.fragAnchor = []
+ self.fragStack = []
+ self.fragStrip = True
+
+ self.listCounter = 0
+
+ self.cssText = ""
+
+ self.image = None
+ self.imageData = {}
+ self.force = False
+
+ self.pathCallback = None # External callback function for path calculations
+
+ # Store path to document
+ self.pathDocument = path or "__dummy__"
+ if not (self.pathDocument.lower().startswith("http:") or self.pathDocument.lower().startswith("https:")):
+ self.pathDocument = os.path.abspath(self.pathDocument)
+ self.pathDirectory = getDirName(self.pathDocument)
+
+ self.meta = dict(
+ author="",
+ title="",
+ subject="",
+ keywords="",
+ pagesize=A4,
+ )
+
+ def UID(self):
+ self.uidctr += 1
+ return self.uidctr
+
+ # METHODS FOR CSS
+
+ def addCSS(self, value):
+ value = value.strip()
+ if value.startswith("", "\n")
+ #self.cssText = self.cssText.replace("", "\n")
+
+ #self.debug(9, self.cssText)
+
+ # print repr(self.cssText)
+ # file("pisa.css", "wb").write(self.cssText.encode("utf8"))
+
+ # self.cssText = re.compile(r"url\((.*?\))", re.M).sub('"\1"', self.cssText)
+ # self.cssText = re.compile(r"\-moz\-.*?([\;\}]+)", re.M).sub(r"\1", self.cssText)
+
+ # XXX Import has to be implemented!
+ # self.cssText = re.compile(r"\@import.*;", re.M).sub("", self.cssText)
+
+# if 0:
+# try:
+# # Sanitize CSS
+# import cssutils
+# import logging
+# cssutils.log.setlog(logging.getLogger('csslog'))
+# cssutils.log.setloglevel(logging.DEBUG)
+# sheet = cssutils.parseString(self.cssText)
+# self.cssText = sheet.cssText
+# #err = csslog.getvalue()
+# except ImportError, e:
+# pass
+# except Exception, e:
+# log.exception(self.error("Error parsing CSS by cssutils"))
+
+ # print self.cssText
+ # file("pisa-sanitized.css", "w").write(self.cssText.encode("utf8"))
+ # print self.cssText
+
+
+ self.cssBuilder = pisaCSSBuilder(mediumSet=["all", "print", "pdf"])
+ self.cssBuilder.c = self
+ self.cssParser = pisaCSSParser(self.cssBuilder)
+ self.cssParser.rootPath = self.pathDirectory
+ self.cssParser.c = self
+
+ self.css = self.cssParser.parse(self.cssText)
+ self.cssCascade = css.CSSCascadeStrategy(self.css)
+ self.cssCascade.parser = self.cssParser
+
+ # METHODS FOR STORY
+
+ def addStory(self, data):
+ self.story.append(data)
+
+ def swapStory(self, story=[]):
+ self.story, story = copy.copy(story), copy.copy(self.story)
+ return story
+
+ def toParagraphStyle(self, first):
+ style = ParagraphStyle('default%d' % self.UID(), keepWithNext=first.keepWithNext)
+ style.fontName = first.fontName
+ style.fontSize = first.fontSize
+ style.leading = max(first.leading + first.leadingSpace, first.fontSize * 1.25)
+ style.backColor = first.backColor
+ style.spaceBefore = first.spaceBefore
+ style.spaceAfter = first.spaceAfter
+ style.leftIndent = first.leftIndent
+ style.rightIndent = first.rightIndent
+ style.firstLineIndent = first.firstLineIndent
+ style.textColor = first.textColor
+ style.alignment = first.alignment
+ style.bulletFontName = first.bulletFontName or first.fontName
+ style.bulletFontSize = first.fontSize
+ style.bulletIndent = first.bulletIndent
+ style.wordWrap = first.wordWrap
+
+ # Border handling for Paragraph
+
+ # Transfer the styles for each side of the border, *not* the whole
+ # border values that reportlab supports. We'll draw them ourselves in
+ # PmlParagraph.
+ style.borderTopStyle = first.borderTopStyle
+ style.borderTopWidth = first.borderTopWidth
+ style.borderTopColor = first.borderTopColor
+ style.borderBottomStyle = first.borderBottomStyle
+ style.borderBottomWidth = first.borderBottomWidth
+ style.borderBottomColor = first.borderBottomColor
+ style.borderLeftStyle = first.borderLeftStyle
+ style.borderLeftWidth = first.borderLeftWidth
+ style.borderLeftColor = first.borderLeftColor
+ style.borderRightStyle = first.borderRightStyle
+ style.borderRightWidth = first.borderRightWidth
+ style.borderRightColor = first.borderRightColor
+
+ # If no border color is given, the text color is used (XXX Tables!)
+ if (style.borderTopColor is None) and style.borderTopWidth:
+ style.borderTopColor = first.textColor
+ if (style.borderBottomColor is None) and style.borderBottomWidth:
+ style.borderBottomColor = first.textColor
+ if (style.borderLeftColor is None) and style.borderLeftWidth:
+ style.borderLeftColor = first.textColor
+ if (style.borderRightColor is None) and style.borderRightWidth:
+ style.borderRightColor = first.textColor
+
+ style.borderPadding = first.borderPadding
+
+ style.paddingTop = first.paddingTop
+ style.paddingBottom = first.paddingBottom
+ style.paddingLeft = first.paddingLeft
+ style.paddingRight = first.paddingRight
+
+ # This is the old code replaced by the above, kept for reference
+ #style.borderWidth = 0
+ #if getBorderStyle(first.borderTopStyle):
+ # style.borderWidth = max(first.borderLeftWidth, first.borderRightWidth, first.borderTopWidth, first.borderBottomWidth)
+ # style.borderPadding = first.borderPadding # + first.borderWidth
+ # style.borderColor = first.borderTopColor
+ # # If no border color is given, the text color is used (XXX Tables!)
+ # if (style.borderColor is None) and style.borderWidth:
+ # style.borderColor = first.textColor
+
+ style.fontName = tt2ps(first.fontName, first.bold, first.italic)
+ return style
+
+ def addTOC(self):
+ # style = copy.deepcopy(self.toParagraphStyle(self.fragBlock))
+ #cssAttrs = copy.deepcopy(self.node.cssAttrs)
+ #frag = copy.deepcopy(self.frag)
+ styles = []
+ for i in range(0, 20):
+ self.node.attributes["class"] = "pdftoclevel%d" % i
+ #self.node.cssAttrs = copy.deepcopy(cssAttrs)
+ #self.frag = copy.deepcopy(frag)
+ self.cssAttr = pisa_parser.CSSCollect(self.node, self)
+ pisa_parser.CSS2Frag(self, {
+ "margin-top": 0,
+ "margin-bottom": 0,
+ "margin-left": 0,
+ "margin-right": 0,
+ }, True)
+ pstyle = self.toParagraphStyle(self.frag)
+ #styles.append(copy.deepcopy(pstyle))
+ styles.append(pstyle)
+
+ # log.warn("%r", self.fragBlock.textColor)
+ self.toc.levelStyles = styles
+ self.addStory(self.toc)
+
+ def dumpPara(self, frags, style):
+ return
+
+ print "%s/%s %s *** PARA" % (style.fontSize, style.leading, style.fontName)
+ for frag in frags:
+ print "%s/%s %r %r" % (
+ frag.fontSize,
+ frag.leading,
+ getattr(frag, "cbDefn", None),
+ frag.text)
+ print
+
+ def addPara(self, force=False):
+
+ # print self.force, repr(self.text)
+ force = (force or self.force)
+ self.force = False
+
+ # Cleanup the trail
+ try:
+ rfragList = reversed(self.fragList)
+ except:
+ # For Python 2.3 compatibility
+ rfragList = copy.copy(self.fragList)
+ rfragList.reverse()
+
+ #for frag in rfragList:
+ # frag.text = frag.text.rstrip()
+ # if frag.text:
+ # break
+
+ # Find maximum lead
+ maxLeading = 0
+ #fontSize = 0
+ for frag in self.fragList:
+ leading = getSize(frag.leadingSource, frag.fontSize) + frag.leadingSpace
+ maxLeading = max(leading, frag.fontSize + frag.leadingSpace, maxLeading)
+ frag.leading = leading
+
+ if force or (self.text.strip() and self.fragList):
+
+ # Strip trailing whitespaces
+ #for f in self.fragList:
+ # f.text = f.text.lstrip()
+ # if f.text:
+ # break
+ #self.fragList[-1].lineBreak = self.fragList[-1].text.rstrip()
+
+ # Update paragraph style by style of first fragment
+ first = self.fragBlock
+ style = self.toParagraphStyle(first)
+ # style.leading = first.leading + first.leadingSpace
+ if first.leadingSpace:
+ style.leading = maxLeading
+ else:
+ style.leading = getSize(first.leadingSource, first.fontSize) + first.leadingSpace
+ # style.leading = maxLeading # + first.leadingSpace
+ #style.fontSize = fontSize
+
+ # borderRadius: None,
+ # print repr(self.text.strip()), style.leading, "".join([repr(x.text) for x in self.fragList])
+ # print first.leftIndent, first.listStyleType,repr(self.text)
+
+ bulletText = copy.copy(first.bulletText)
+ first.bulletText = None
+
+ # Add paragraph to story
+ if force or len(self.fragAnchor + self.fragList) > 0:
+
+ # We need this empty fragment to work around problems in
+ # Reportlab paragraphs regarding backGround etc.
+ if self.fragList:
+ self.fragList.append(self.fragList[ - 1].clone(text=''))
+ else:
+ blank = self.frag.clone()
+ blank.fontName = "Helvetica"
+ blank.text = ''
+ self.fragList.append(blank)
+
+ self.dumpPara(self.fragAnchor + self.fragList, style)
+ para = PmlParagraph(
+ self.text,
+ style,
+ frags=self.fragAnchor + self.fragList,
+ bulletText=bulletText)
+
+ # Mirrored and BIDI
+ #import unicodedata
+ #for c in self.text:
+ # print unicodedata.bidirectional(c),
+
+ para.outline = first.outline
+ para.outlineLevel = first.outlineLevel
+ para.outlineOpen = first.outlineOpen
+ para.keepWithNext = first.keepWithNext
+ para.autoLeading = "max"
+
+ if self.image:
+ para = PmlParagraphAndImage(
+ para,
+ self.image,
+ side=self.imageData.get("align", "left"))
+
+ self.addStory(para)
+
+ self.fragAnchor = []
+ first.bulletText = None
+
+ # Reset data
+
+ self.image = None
+ self.imageData = {}
+
+ self.clearFrag()
+
+ # METHODS FOR FRAG
+
+ def clearFrag(self):
+ self.fragList = []
+ self.fragStrip = True
+ self.text = u""
+
+ def copyFrag(self, **kw):
+ return self.frag.clone(**kw)
+
+ def newFrag(self, **kw):
+ self.frag = self.frag.clone(**kw)
+ return self.frag
+
+ def _appendFrag(self, frag):
+ if frag.link and frag.link.startswith("#"):
+ self.anchorFrag.append((frag, frag.link[1:]))
+ self.fragList.append(frag)
+
+ # XXX Argument frag is useless!
+ def addFrag(self, text="", frag=None):
+
+ frag = baseFrag = self.frag.clone()
+
+ # if sub and super are both on they will cancel each other out
+ if frag.sub == 1 and frag.super == 1:
+ frag.sub = 0
+ frag.super = 0
+
+ # XXX Has to be replaced by CSS styles like vertical-align and font-size
+ if frag.sub:
+ frag.rise = - frag.fontSize * subFraction
+ frag.fontSize = max(frag.fontSize - sizeDelta, 3)
+ elif frag.super:
+ frag.rise = frag.fontSize * superFraction
+ frag.fontSize = max(frag.fontSize - sizeDelta, 3)
+
+ # XXX Unused?
+ #if frag.greek:
+ # frag.fontName = 'symbol'
+ # text = _greekConvert(text)
+
+ # bold, italic, and underline
+ frag.fontName = frag.bulletFontName = tt2ps(frag.fontName, frag.bold, frag.italic)
+ # print frag.bulletFontName
+
+ # Modify text for optimal whitespace handling
+ # XXX Support Unicode whitespaces?
+ # XXX What about images?
+
+ # XXX Doesn't work with Reportlab > 2.1
+ # NBSP = '\xc2\xa0' # u"_"
+
+ #if REPORTLAB22:
+ # NBSP = u" "
+
+ # Replace with empty and normalize NBSP
+ text = (text
+ .replace(u"\xad", u"")
+ .replace(u"\xc2\xa0", NBSP)
+ .replace(u"\xa0", NBSP))
+
+ # log.debug("> %r", text)
+
+ if frag.whiteSpace == "pre":
+
+ # Handle by lines
+ for text in re.split(r'(\r\n|\n|\r)', text):
+ # This is an exceptionally expensive piece of code
+ self.text += text
+ if ("\n" in text) or ("\r" in text):
+ # If EOL insert a linebreak
+ frag = baseFrag.clone()
+ frag.text = ""
+ frag.lineBreak = 1
+ self._appendFrag(frag)
+ else:
+ # Handle tabs in a simple way
+ text = text.replace(u"\t", 8 * u" ")
+ # Somehow for Reportlab NBSP have to be inserted
+ # as single character fragments
+ for text in re.split(r'(\ )', text):
+ frag = baseFrag.clone()
+ if text == " ":
+ text = NBSP
+ frag.text = text
+ self._appendFrag(frag)
+ else:
+ for text in re.split(u'(' + NBSP + u')', text):
+ frag = baseFrag.clone()
+ if text == NBSP:
+ self.force = True
+ frag.text = NBSP
+ self.text += text
+ self._appendFrag(frag)
+ else:
+ frag.text = " ".join(("x" + text + "x").split())[1: - 1]
+ if self.fragStrip:
+ frag.text = frag.text.lstrip()
+ if frag.text:
+ self.fragStrip = False
+ self.text += frag.text
+ self._appendFrag(frag)
+ # print frag.fontName, repr(frag.text), frag.bulletText
+
+ def pushFrag(self):
+ self.fragStack.append(self.frag)
+ self.newFrag()
+
+ def pullFrag(self):
+ self.frag = self.fragStack.pop()
+
+ # XXX
+
+ def _getFragment(self, l=20):
+ try:
+ return repr(" ".join(self.node.toxml().split()[:l]))
+ except:
+ return ""
+
+ def _getLineNumber(self):
+ return 0
+
+ def context(self, msg):
+ return "%s\n%s" % (
+ str(msg),
+ self._getFragment(50))
+# return "line %s: %s\n%s" % (
+# self._getLineNumber(),
+# str(msg),
+# self._getFragment(50))
+
+ def warning(self, msg, *args):
+ self.warn += 1
+ self.log.append((pisa_default.PML_WARNING, self._getLineNumber(), str(msg), self._getFragment(50)))
+ try:
+ return self.context(msg % args)
+ except:
+ return self.context(msg)
+
+ def error(self, msg, *args):
+ self.err += 1
+ self.log.append((pisa_default.PML_ERROR, self._getLineNumber(), str(msg), self._getFragment(50)))
+ try:
+ return self.context(msg % args)
+ except:
+ return self.context(msg)
+
+ # UTILS
+
+ def _getFileDeprecated(self, name, relative):
+ try:
+ if name.startswith("data:"):
+ return name
+ path = relative or self.pathDirectory
+ if self.pathCallback is not None:
+ nv = self.pathCallback(name, relative)
+ else:
+ if path is None:
+ log.warn("Could not find main directory for getting filename. Use CWD")
+ path = os.getcwd()
+ nv = os.path.normpath(os.path.join(path, name))
+ if not(nv and os.path.isfile(nv)):
+ nv = None
+ if nv is None:
+ log.warn(self.warning("File '%s' does not exist", name))
+ return nv
+ except:
+ log.warn(self.warning("getFile %r %r %r", name, relative, path), exc_info=1)
+
+ def getFile(self, name, relative=None):
+ """
+ Returns a file name or None
+ """
+ if self.pathCallback is not None:
+ return getFile(self._getFileDeprecated(name, relative))
+ return getFile(name, relative or self.pathDirectory)
+
+ def getFontName(self, names, default="helvetica"):
+ """
+ Name of a font
+ """
+ # print names, self.fontList
+ if type(names) is not types.ListType:
+ names = str(names).strip().split(",")
+ for name in names:
+ font = self.fontList.get(str(name).strip().lower(), None)
+ if font is not None:
+ return font
+ return self.fontList.get(default, None)
+
+ def registerFont(self, fontname, alias=[]):
+ self.fontList[str(fontname).lower()] = str(fontname)
+ for a in alias:
+ self.fontList[str(a)] = str(fontname)
+
+ def loadFont(self, names, src, encoding="WinAnsiEncoding", bold=0, italic=0):
+
+ # XXX Just works for local filenames!
+ if names and src: # and src.local:
+
+ file = src
+ src = file.uri
+
+ log.debug("Load font %r", src)
+
+ if type(names) is types.ListType:
+ fontAlias = names
+ else:
+ fontAlias = [x.lower().strip() for x in names.split(",") if x]
+
+ # XXX Problems with unicode here
+ fontAlias = [str(x) for x in fontAlias]
+
+ fontName = fontAlias[0]
+ parts = src.split(".")
+ baseName, suffix = ".".join(parts[: - 1]), parts[ - 1]
+ suffix = suffix.lower()
+
+ try:
+
+ if suffix == "ttf":
+
+ # determine full font name according to weight and style
+ fullFontName = "%s_%d%d" % (fontName, bold, italic)
+
+ # check if font has already been registered
+ if fullFontName in self.fontList:
+ log.warn(self.warning("Repeated font embed for %s, skip new embed ", fullFontName))
+ else:
+
+ # Register TTF font and special name
+ filename = file.getNamedFile()
+ pdfmetrics.registerFont(TTFont(fullFontName, filename))
+
+ # Add or replace missing styles
+ for bold in (0, 1):
+ for italic in (0, 1):
+ if ("%s_%d%d" % (fontName, bold, italic)) not in self.fontList:
+ addMapping(fontName, bold, italic, fullFontName)
+
+ # Register "normal" name and the place holder for style
+ self.registerFont(fontName, fontAlias + [fullFontName])
+
+ elif suffix in ("afm", "pfb"):
+
+ if suffix == "afm":
+ afm = file.getNamedFile()
+ tfile = pisaFileObject(baseName + ".pfb")
+ pfb = tfile.getNamedFile()
+ else:
+ pfb = file.getNamedFile()
+ tfile = pisaFileObject(baseName + ".afm")
+ afm = tfile.getNamedFile()
+
+ #afm = baseName + ".afm"
+ #pfb = baseName + ".pfb"
+
+ # determine full font name according to weight and style
+ fullFontName = "%s_%d%d" % (fontName, bold, italic)
+
+ #fontNameOriginal = ""
+ #for line in open(afm).readlines()[:-1]:
+ # if line[:16] == 'StartCharMetrics':
+ # self.error("Font name not found")
+ # if line[:8] == 'FontName':
+ # fontNameOriginal = line[9:].strip()
+ # break
+
+ # check if font has already been registered
+ if fullFontName in self.fontList:
+ log.warn(self.warning("Repeated font embed for %s, skip new embed", fontName))
+ else:
+
+ # Include font
+ face = pdfmetrics.EmbeddedType1Face(afm, pfb)
+ fontNameOriginal = face.name
+ pdfmetrics.registerTypeFace(face)
+ # print fontName, fontNameOriginal, fullFontName
+ justFont = pdfmetrics.Font(fullFontName, fontNameOriginal, encoding)
+ pdfmetrics.registerFont(justFont)
+
+ # Add or replace missing styles
+ for bold in (0, 1):
+ for italic in (0, 1):
+ if ("%s_%d%d" % (fontName, bold, italic)) not in self.fontList:
+ addMapping(fontName, bold, italic, fontNameOriginal)
+
+ # Register "normal" name and the place holder for style
+ self.registerFont(fontName, fontAlias + [fullFontName, fontNameOriginal])
+
+ #import pprint
+ #pprint.pprint(self.fontList)
+
+ else:
+ log.warning(self.warning("wrong attributes for "))
+
+ except Exception:
+ log.warn(self.warning("Loading font '%s'", fontName), exc_info=1)
diff --git a/sx/pisa3/pisa_default.py b/sx/pisa3/pisa_default.py
index 1a4f460e..c186fe8d 100644
--- a/sx/pisa3/pisa_default.py
+++ b/sx/pisa3/pisa_default.py
@@ -1,645 +1,646 @@
-# -*- coding: ISO-8859-1 -*-
-
-# Copyright 2010 Dirk Holtwick, holtwick.it
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-__reversion__ = "$Revision: 20 $"
-__author__ = "$Author: holtwick $"
-__date__ = "$Date: 2007-10-09 12:58:24 +0200 (Di, 09 Okt 2007) $"
-
-from reportlab.lib.pagesizes import *
-
-PML_WARNING = "warning"
-PML_ERROR = "error"
-PML_EXCEPTION = "PML Exception"
-PML_PREFIX = "pdf:"
-
-#CLASS = 1
-BOOL = 2
-FONT = 3
-COLOR = 4
-FILE = 5
-SIZE = 6
-INT = 7
-STRING = 8
-BOX = 9
-POS = 10
-#STYLE = 11
-MUST = 23
-
-"""
-Definition of all known tags. Also used for building the reference
-"""
-
-TAGS = {
-
- # FORMAT
-
- #"document": (1, {
- # "format": (["a0", "a1", "a2", "a3", "a4", "a5", "a6",
- # "b0", "b1", "b2", "b3", "b4", "b5", "b6",
- # "letter", "legal", "elevenseventeen"], "a4"),
- # "orientation": ["portrait", "landscape"],
- # "fullscreen": (BOOL, "0"),
- # "author": (STRING, ""),
- # "subject": (STRING, ""),
- # "title": (STRING, ""),
- # "duration": INT,
- # "showoutline": (BOOL, "0"),
- # "outline": INT,
- # }),
-
- "pdftemplate": (1, {
- "name": (STRING, "body"),
- "format": (["a0", "a1", "a2", "a3", "a4", "a5", "a6",
- "b0", "b1", "b2", "b3", "b4", "b5", "b6",
- "letter", "legal", "elevenseventeen"], "a4"),
- "orientation": ["portrait", "landscape"],
- "background": FILE,
- }),
-
- "pdfframe": (0, {
- "name": (STRING, ""),
- "box": (BOX, MUST),
- "border": (BOOL, "0"),
- "static": (BOOL, "0"),
- }),
-
- #"static": (1, {
- # "name": STRING,
- # "box": (BOX, MUST),
- # "border": (BOOL, "0"),
- # }),
-
- "pdfnexttemplate": (0, {
- "name": (STRING, "body"),
- }),
-
- "pdfnextpage": (0, {
- "name": (STRING, ""),
- # "background": FILE,
- }),
-
- "pdfnextframe": (0, {}),
-
- "pdffont": (0, {
- "src": (FILE, MUST),
- "name": (STRING, MUST),
- # "print": (BOOL, "0"),
- "encoding": (STRING, "WinAnsiEncoding"),
- }),
-
- "pdfdrawline": (0, {
- "from": (POS, MUST),
- "to": (POS, MUST),
- "color": (COLOR, "#000000"),
- "width": (SIZE, 1),
- }),
-
- "drawpoint": (0, {
- "pos": (POS, MUST),
- "color": (COLOR, "#000000"),
- "width": (SIZE, 1),
- }),
-
- "pdfdrawlines": (0, {
- "coords": (STRING, MUST),
- "color": (COLOR, "#000000"),
- "width": (SIZE, 1),
- }),
-
- "pdfdrawstring": (0, {
- "pos": (POS, MUST),
- "text": (STRING, MUST),
- "color": (COLOR, "#000000"),
- "align": (["left", "center", "right"], "right"),
- "valign": (["top", "middle", "bottom"], "bottom"),
- # "class": CLASS,
- "rotate": (INT, "0"),
- }),
-
- "pdfdrawimg": (0, {
- "pos": (POS, MUST),
- "src": (FILE, MUST),
- "width": SIZE,
- "height": SIZE,
- "align": (["left", "center", "right"], "right"),
- "valign": (["top", "middle", "bottom"], "bottom"),
- }),
-
- "pdfspacer" : (0, {
- "height": (SIZE, MUST),
- }),
-
- "pdfpagenumber": (0, {
- "example": (STRING, "0"),
- }),
-
- "pdftoc": (0, {
- }),
-
- "pdfversion": (0, {
- }),
-
- "pdfkeeptogether": (1, {
- }),
-
- "pdfkeepinframe": (1, {
- "maxwidth": SIZE,
- "maxheight": SIZE,
- "mergespace": (INT, 1),
- "mode": (["error", "overflow", "shrink", "truncate"], "shrink"),
- "name": (STRING, "")
- }),
-
- # The chart example, see pml_charts
- "pdfchart": (1, {
- "type": (["spider","bar"], "bar"),
- "strokecolor": (COLOR, "#000000"),
- "width": (SIZE, MUST),
- "height": (SIZE, MUST),
- }),
-
- "pdfchartdata": (0, {
- "set": (STRING, MUST),
- "value": (STRING),
- # "label": (STRING),
- "strokecolor": (COLOR),
- "fillcolor": (COLOR),
- "strokewidth": (SIZE),
- }),
-
- "pdfchartlabel": (0, {
- "value": (STRING, MUST),
- }),
-
- "pdfbarcode": (0, {
- "value": (STRING, MUST),
- "type": (["i2of5", "itf",
- "code39", "extendedcode39",
- "code93", "extendedcode93",
- "msi",
- "codabar", "nw7",
- "code11",
- "fim",
- "postnet",
- "usps4s",
- "code128",
- "ean13", "ean8",
- ], "code128"),
- "humanreadable": (STRING, ""),
- "barwidth": SIZE,
- "barheight": SIZE,
- "align": (["baseline", "top", "middle", "bottom"], "baseline"),
- }),
-
- # ========================================================
-
- "link": (0, {
- "href": (STRING, MUST),
- "rel": (STRING, ""),
- "type": (STRING, ""),
- "media": (STRING, "all"),
- "charset": (STRING, "latin1"), # XXX Must be something else...
- }),
-
- "meta": (0, {
- "name": (STRING, ""),
- "content": (STRING, ""),
- }),
-
- "style": (0, {
- "type": (STRING, ""),
- "media": (STRING, "all"),
- }),
-
- "img": (0, {
- "src": (FILE, MUST),
- "width": SIZE,
- "height": SIZE,
- "align": ["top", "middle", "bottom", "left", "right",
- "texttop", "absmiddle", "absbottom", "baseline"],
- }),
-
- "table": (1, {
- "align": (["left", "center", "right"], "left"),
- "valign": (["top", "bottom", "middle"], "middle"),
- "border": (SIZE, "0"),
- "bordercolor": (COLOR, "#000000"),
- "bgcolor": COLOR,
- "cellpadding": (SIZE, "0"),
- "cellspacing": (SIZE, "0"),
- "repeat": (INT, "0"), # XXX Remove this! Set to 0
- "width": STRING,
- #"keepmaxwidth": SIZE,
- #"keepmaxheight": SIZE,
- #"keepmergespace": (INT, 1),
- #"keepmode": (["error", "overflow", "shrink", "truncate"], "shrink"),
- }),
-
- "tr": (1, {
- "bgcolor": COLOR,
- "valign": ["top", "bottom", "middle"],
- "border": SIZE,
- "bordercolor": (COLOR, "#000000"),
- }),
-
- "td": (1, {
- "align": ["left", "center", "right", "justify"],
- "valign": ["top", "bottom", "middle"],
- "width": STRING,
- "bgcolor": COLOR,
- "border": SIZE,
- "bordercolor": (COLOR, "#000000"),
- "colspan": INT,
- "rowspan": INT,
- }),
-
- "th": (1, {
- "align": ["left", "center", "right", "justify"],
- "valign": ["top", "bottom", "middle"],
- "width": STRING,
- "bgcolor": COLOR,
- "border": SIZE,
- "bordercolor": (COLOR, "#000000"),
- "colspan": INT,
- "rowspan": INT,
- }),
-
- "dl": (1, {
- }),
-
- "dd": (1, {
- }),
-
- "dt": (1, {
- }),
-
- "ol": (1, {
- "type": (["1", "a", "A", "i", "I"], "1"),
- }),
-
- "ul": (1, {
- "type": (["circle", "disk", "square"], "disk"),
- }),
-
- "li": (1, {
- }),
-
- "hr": (0, {
- "color": (COLOR, "#000000"),
- "size": (SIZE, "1"),
- "align": ["left", "center", "right", "justify"],
- }),
-
- "div": (1, {
- "align": ["left", "center", "right", "justify"],
- }),
-
- "p": (1, {
- "align": ["left", "center", "right", "justify"],
- }),
-
- "br": (0, {
- }),
-
- "h1": (1, {
- "outline": STRING,
- "closed": (INT, 0),
- "align": ["left", "center", "right", "justify"],
- }),
-
- "h2": (1, {
- "outline": STRING,
- "closed": (INT, 0),
- "align": ["left", "center", "right", "justify"],
- }),
-
- "h3": (1, {
- "outline": STRING,
- "closed": (INT, 0),
- "align": ["left", "center", "right", "justify"],
- }),
-
- "h4": (1, {
- "outline": STRING,
- "closed": (INT, 0),
- "align": ["left", "center", "right", "justify"],
- }),
-
- "h5": (1, {
- "outline": STRING,
- "closed": (INT, 0),
- "align": ["left", "center", "right", "justify"],
- }),
-
- "h6": (1, {
- "outline": STRING,
- "closed": (INT, 0),
- "align": ["left", "center", "right", "justify"],
- }),
-
- "font": (1, {
- "face": FONT,
- "color": COLOR,
- "size": STRING,
- }),
-
- "a": (1, {
- "href": STRING,
- "name": STRING,
- }),
-
- "input": (0, {
- "name": STRING,
- "value": STRING,
- "type": (["text", "hidden", "checkbox"], "text"),
- }),
-
- "textarea": (1, {
- "name": STRING,
- }),
-
- "select": (1, {
- "name": STRING,
- "value": STRING,
- }),
-
- "option": (0, {
- "value": STRING,
- }),
- }
-
-# XXX use "html" not "*" as default!
-DEFAULT_CSS = """
-html {
- font-family: Helvetica;
- font-size: 10px;
- font-weight: normal;
- color: #000000;
- background-color: transparent;
- margin: 0;
- padding: 0;
- line-height: 150%;
- border: 1px none;
- display: inline;
- width: auto;
- height: auto;
- white-space: normal;
-}
-
-b,
-strong {
- font-weight: bold;
-}
-
-i,
-em {
- font-style: italic;
-}
-
-u {
- text-decoration: underline;
-}
-
-s,
-strike {
- text-decoration: line-through;
-}
-
-a {
- text-decoration: underline;
- color: blue;
-}
-
-ins {
- color: green;
- text-decoration: underline;
-}
-del {
- color: red;
- text-decoration: line-through;
-}
-
-pre,
-code,
-kbd,
-samp,
-tt {
- font-family: "Courier New";
-}
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
- font-weight:bold;
- -pdf-outline: true;
- -pdf-outline-open: false;
-}
-
-h1 {
- /*18px via YUI Fonts CSS foundation*/
- font-size:138.5%;
- -pdf-outline-level: 0;
-}
-
-h2 {
- /*16px via YUI Fonts CSS foundation*/
- font-size:123.1%;
- -pdf-outline-level: 1;
-}
-
-h3 {
- /*14px via YUI Fonts CSS foundation*/
- font-size:108%;
- -pdf-outline-level: 2;
-}
-
-h4 {
- -pdf-outline-level: 3;
-}
-
-h5 {
- -pdf-outline-level: 4;
-}
-
-h6 {
- -pdf-outline-level: 5;
-}
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-p,
-pre,
-hr {
- margin:1em 0;
-}
-
-address,
-blockquote,
-body,
-center,
-dl,
-dir,
-div,
-fieldset,
-form,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-hr,
-isindex,
-menu,
-noframes,
-noscript,
-ol,
-p,
-pre,
-table,
-th,
-tr,
-td,
-ul,
-li,
-dd,
-dt,
-pdftoc {
- display: block;
-}
-
-table {
- -pdf-keep-in-frame-mode: shrink;
-}
-
-tr,
-th,
-td {
-
- vertical-align: middle;
- width: auto;
-}
-
-th {
- text-align: center;
- font-weight: bold;
-}
-
-center {
- text-align: center;
-}
-
-big {
- font-size: 125%;
-}
-
-small {
- font-size: 75%;
-}
-
-
-ul {
- margin-left: 1.5em;
- list-style-type: disc;
-}
-
-ul ul {
- list-style-type: circle;
-}
-
-ul ul ul {
- list-style-type: square;
-}
-
-ol {
- list-style-type: decimal;
- margin-left: 1.5em;
-}
-
-pre {
- white-space: pre;
-}
-
-blockquote {
- margin-left: 1.5em;
- margin-right: 1.5em;
-}
-
-noscript {
- display: none;
-}
-"""
-
-DEFAULT_FONT = {
- "courier": "Courier",
- "courier-bold": "Courier-Bold",
- "courier-boldoblique": "Courier-BoldOblique",
- "courier-oblique": "Courier-Oblique",
- "helvetica": "Helvetica",
- "helvetica-bold": "Helvetica-Bold",
- "helvetica-boldoblique": "Helvetica-BoldOblique",
- "helvetica-oblique": "Helvetica-Oblique",
- "times": "Times-Roman",
- "times-roman": "Times-Roman",
- "times-bold": "Times-Bold",
- "times-boldoblique": "Times-BoldOblique",
- "times-oblique": "Times-Oblique",
- "symbol": "Symbol",
- "zapfdingbats": "ZapfDingbats",
- "zapf-dingbats": "ZapfDingbats",
-
- # Alias
- "arial": "Helvetica",
- "times new roman": "Times-Roman",
- "georgia": "Times-Roman",
- 'serif':'Times-Roman',
- 'sansserif':'Helvetica',
- 'sans':'Helvetica',
- 'monospaced':'Courier',
- 'monospace':'Courier',
- 'mono':'Courier',
- 'courier new':'Courier',
- 'verdana':'Helvetica',
- 'geneva':'Helvetica',
- }
-
-PML_PAGESIZES = {
- "a0": A0,
- "a1": A1,
- "a2": A2,
- "a3": A3,
- "a4": A4,
- "a5": A5,
- "a6": A6,
- "b0": B0,
- "b1": B1,
- "b2": B2,
- "b3": B3,
- "b4": B4,
- "b5": B5,
- "b6": B6,
- "letter": LETTER,
- "legal": LEGAL,
- "ledger": ELEVENSEVENTEEN,
- "elevenseventeen": ELEVENSEVENTEEN,
- }
+# -*- coding: utf-8 -*-
+
+# Copyright 2010 Dirk Holtwick, holtwick.it
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__reversion__ = "$Revision: 20 $"
+__author__ = "$Author: holtwick $"
+__date__ = "$Date: 2007-10-09 12:58:24 +0200 (Di, 09 Okt 2007) $"
+
+from reportlab.lib.pagesizes import *
+
+PML_WARNING = "warning"
+PML_ERROR = "error"
+PML_EXCEPTION = "PML Exception"
+PML_PREFIX = "pdf:"
+
+#CLASS = 1
+BOOL = 2
+FONT = 3
+COLOR = 4
+FILE = 5
+SIZE = 6
+INT = 7
+STRING = 8
+BOX = 9
+POS = 10
+#STYLE = 11
+MUST = 23
+
+"""
+Definition of all known tags. Also used for building the reference
+"""
+
+TAGS = {
+
+ # FORMAT
+
+ #"document": (1, {
+ # "format": (["a0", "a1", "a2", "a3", "a4", "a5", "a6",
+ # "b0", "b1", "b2", "b3", "b4", "b5", "b6",
+ # "letter", "legal", "elevenseventeen"], "a4"),
+ # "orientation": ["portrait", "landscape"],
+ # "fullscreen": (BOOL, "0"),
+ # "author": (STRING, ""),
+ # "subject": (STRING, ""),
+ # "title": (STRING, ""),
+ # "duration": INT,
+ # "showoutline": (BOOL, "0"),
+ # "outline": INT,
+ # }),
+
+ "pdftemplate": (1, {
+ "name": (STRING, "body"),
+ "format": (["a0", "a1", "a2", "a3", "a4", "a5", "a6",
+ "b0", "b1", "b2", "b3", "b4", "b5", "b6",
+ "letter", "legal", "elevenseventeen"], "a4"),
+ "orientation": ["portrait", "landscape"],
+ "background": FILE,
+ }),
+
+ "pdfframe": (0, {
+ "name": (STRING, ""),
+ "box": (BOX, MUST),
+ "border": (BOOL, "0"),
+ "static": (BOOL, "0"),
+ }),
+
+ #"static": (1, {
+ # "name": STRING,
+ # "box": (BOX, MUST),
+ # "border": (BOOL, "0"),
+ # }),
+
+ "pdfnexttemplate": (0, {
+ "name": (STRING, "body"),
+ }),
+
+ "pdfnextpage": (0, {
+ "name": (STRING, ""),
+ # "background": FILE,
+ }),
+
+ "pdfnextframe": (0, {}),
+
+ "pdffont": (0, {
+ "src": (FILE, MUST),
+ "name": (STRING, MUST),
+ # "print": (BOOL, "0"),
+ "encoding": (STRING, "WinAnsiEncoding"),
+ }),
+
+ "pdfdrawline": (0, {
+ "from": (POS, MUST),
+ "to": (POS, MUST),
+ "color": (COLOR, "#000000"),
+ "width": (SIZE, 1),
+ }),
+
+ "drawpoint": (0, {
+ "pos": (POS, MUST),
+ "color": (COLOR, "#000000"),
+ "width": (SIZE, 1),
+ }),
+
+ "pdfdrawlines": (0, {
+ "coords": (STRING, MUST),
+ "color": (COLOR, "#000000"),
+ "width": (SIZE, 1),
+ }),
+
+ "pdfdrawstring": (0, {
+ "pos": (POS, MUST),
+ "text": (STRING, MUST),
+ "color": (COLOR, "#000000"),
+ "align": (["left", "center", "right"], "right"),
+ "valign": (["top", "middle", "bottom"], "bottom"),
+ # "class": CLASS,
+ "rotate": (INT, "0"),
+ }),
+
+ "pdfdrawimg": (0, {
+ "pos": (POS, MUST),
+ "src": (FILE, MUST),
+ "width": SIZE,
+ "height": SIZE,
+ "align": (["left", "center", "right"], "right"),
+ "valign": (["top", "middle", "bottom"], "bottom"),
+ }),
+
+ "pdfspacer" : (0, {
+ "height": (SIZE, MUST),
+ }),
+
+ "pdfpagenumber": (0, {
+ "example": (STRING, "0"),
+ "offset": (STRING, "0"),
+ }),
+
+ "pdftoc": (0, {
+ }),
+
+ "pdfversion": (0, {
+ }),
+
+ "pdfkeeptogether": (1, {
+ }),
+
+ "pdfkeepinframe": (1, {
+ "maxwidth": SIZE,
+ "maxheight": SIZE,
+ "mergespace": (INT, 1),
+ "mode": (["error", "overflow", "shrink", "truncate"], "shrink"),
+ "name": (STRING, "")
+ }),
+
+ # The chart example, see pml_charts
+ "pdfchart": (1, {
+ "type": (["spider","bar"], "bar"),
+ "strokecolor": (COLOR, "#000000"),
+ "width": (SIZE, MUST),
+ "height": (SIZE, MUST),
+ }),
+
+ "pdfchartdata": (0, {
+ "set": (STRING, MUST),
+ "value": (STRING),
+ # "label": (STRING),
+ "strokecolor": (COLOR),
+ "fillcolor": (COLOR),
+ "strokewidth": (SIZE),
+ }),
+
+ "pdfchartlabel": (0, {
+ "value": (STRING, MUST),
+ }),
+
+ "pdfbarcode": (0, {
+ "value": (STRING, MUST),
+ "type": (["i2of5", "itf",
+ "code39", "extendedcode39",
+ "code93", "extendedcode93",
+ "msi",
+ "codabar", "nw7",
+ "code11",
+ "fim",
+ "postnet",
+ "usps4s",
+ "code128",
+ "ean13", "ean8",
+ ], "code128"),
+ "humanreadable": (STRING, ""),
+ "barwidth": SIZE,
+ "barheight": SIZE,
+ "align": (["baseline", "top", "middle", "bottom"], "baseline"),
+ }),
+
+ # ========================================================
+
+ "link": (0, {
+ "href": (STRING, MUST),
+ "rel": (STRING, ""),
+ "type": (STRING, ""),
+ "media": (STRING, "all"),
+ "charset": (STRING, "latin1"), # XXX Must be something else...
+ }),
+
+ "meta": (0, {
+ "name": (STRING, ""),
+ "content": (STRING, ""),
+ }),
+
+ "style": (0, {
+ "type": (STRING, ""),
+ "media": (STRING, "all"),
+ }),
+
+ "img": (0, {
+ "src": (FILE, MUST),
+ "width": SIZE,
+ "height": SIZE,
+ "align": ["top", "middle", "bottom", "left", "right",
+ "texttop", "absmiddle", "absbottom", "baseline"],
+ }),
+
+ "table": (1, {
+ "align": (["left", "center", "right"], "left"),
+ "valign": (["top", "bottom", "middle"], "middle"),
+ "border": (SIZE, "0"),
+ "bordercolor": (COLOR, "#000000"),
+ "bgcolor": COLOR,
+ "cellpadding": (SIZE, "0"),
+ "cellspacing": (SIZE, "0"),
+ "repeat": (INT, "0"), # XXX Remove this! Set to 0
+ "width": STRING,
+ #"keepmaxwidth": SIZE,
+ #"keepmaxheight": SIZE,
+ #"keepmergespace": (INT, 1),
+ #"keepmode": (["error", "overflow", "shrink", "truncate"], "shrink"),
+ }),
+
+ "tr": (1, {
+ "bgcolor": COLOR,
+ "valign": ["top", "bottom", "middle"],
+ "border": SIZE,
+ "bordercolor": (COLOR, "#000000"),
+ }),
+
+ "td": (1, {
+ "align": ["left", "center", "right", "justify"],
+ "valign": ["top", "bottom", "middle"],
+ "width": STRING,
+ "bgcolor": COLOR,
+ "border": SIZE,
+ "bordercolor": (COLOR, "#000000"),
+ "colspan": INT,
+ "rowspan": INT,
+ }),
+
+ "th": (1, {
+ "align": ["left", "center", "right", "justify"],
+ "valign": ["top", "bottom", "middle"],
+ "width": STRING,
+ "bgcolor": COLOR,
+ "border": SIZE,
+ "bordercolor": (COLOR, "#000000"),
+ "colspan": INT,
+ "rowspan": INT,
+ }),
+
+ "dl": (1, {
+ }),
+
+ "dd": (1, {
+ }),
+
+ "dt": (1, {
+ }),
+
+ "ol": (1, {
+ "type": (["1", "a", "A", "i", "I"], "1"),
+ }),
+
+ "ul": (1, {
+ "type": (["circle", "disk", "square"], "disk"),
+ }),
+
+ "li": (1, {
+ }),
+
+ "hr": (0, {
+ "color": (COLOR, "#000000"),
+ "size": (SIZE, "1"),
+ "align": ["left", "center", "right", "justify"],
+ }),
+
+ "div": (1, {
+ "align": ["left", "center", "right", "justify"],
+ }),
+
+ "p": (1, {
+ "align": ["left", "center", "right", "justify"],
+ }),
+
+ "br": (0, {
+ }),
+
+ "h1": (1, {
+ "outline": STRING,
+ "closed": (INT, 0),
+ "align": ["left", "center", "right", "justify"],
+ }),
+
+ "h2": (1, {
+ "outline": STRING,
+ "closed": (INT, 0),
+ "align": ["left", "center", "right", "justify"],
+ }),
+
+ "h3": (1, {
+ "outline": STRING,
+ "closed": (INT, 0),
+ "align": ["left", "center", "right", "justify"],
+ }),
+
+ "h4": (1, {
+ "outline": STRING,
+ "closed": (INT, 0),
+ "align": ["left", "center", "right", "justify"],
+ }),
+
+ "h5": (1, {
+ "outline": STRING,
+ "closed": (INT, 0),
+ "align": ["left", "center", "right", "justify"],
+ }),
+
+ "h6": (1, {
+ "outline": STRING,
+ "closed": (INT, 0),
+ "align": ["left", "center", "right", "justify"],
+ }),
+
+ "font": (1, {
+ "face": FONT,
+ "color": COLOR,
+ "size": STRING,
+ }),
+
+ "a": (1, {
+ "href": STRING,
+ "name": STRING,
+ }),
+
+ "input": (0, {
+ "name": STRING,
+ "value": STRING,
+ "type": (["text", "hidden", "checkbox"], "text"),
+ }),
+
+ "textarea": (1, {
+ "name": STRING,
+ }),
+
+ "select": (1, {
+ "name": STRING,
+ "value": STRING,
+ }),
+
+ "option": (0, {
+ "value": STRING,
+ }),
+ }
+
+# XXX use "html" not "*" as default!
+DEFAULT_CSS = """
+html {
+ font-family: Helvetica;
+ font-size: 10px;
+ font-weight: normal;
+ color: #000000;
+ background-color: transparent;
+ margin: 0;
+ padding: 0;
+ line-height: 150%;
+ border: 1px none;
+ display: inline;
+ width: auto;
+ height: auto;
+ white-space: normal;
+}
+
+b,
+strong {
+ font-weight: bold;
+}
+
+i,
+em {
+ font-style: italic;
+}
+
+u {
+ text-decoration: underline;
+}
+
+s,
+strike {
+ text-decoration: line-through;
+}
+
+a {
+ text-decoration: underline;
+ color: blue;
+}
+
+ins {
+ color: green;
+ text-decoration: underline;
+}
+del {
+ color: red;
+ text-decoration: line-through;
+}
+
+pre,
+code,
+kbd,
+samp,
+tt {
+ font-family: "Courier New";
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-weight:bold;
+ -pdf-outline: true;
+ -pdf-outline-open: false;
+}
+
+h1 {
+ /*18px via YUI Fonts CSS foundation*/
+ font-size:138.5%;
+ -pdf-outline-level: 0;
+}
+
+h2 {
+ /*16px via YUI Fonts CSS foundation*/
+ font-size:123.1%;
+ -pdf-outline-level: 1;
+}
+
+h3 {
+ /*14px via YUI Fonts CSS foundation*/
+ font-size:108%;
+ -pdf-outline-level: 2;
+}
+
+h4 {
+ -pdf-outline-level: 3;
+}
+
+h5 {
+ -pdf-outline-level: 4;
+}
+
+h6 {
+ -pdf-outline-level: 5;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+pre,
+hr {
+ margin:1em 0;
+}
+
+address,
+blockquote,
+body,
+center,
+dl,
+dir,
+div,
+fieldset,
+form,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+isindex,
+menu,
+noframes,
+noscript,
+ol,
+p,
+pre,
+table,
+th,
+tr,
+td,
+ul,
+li,
+dd,
+dt,
+pdftoc {
+ display: block;
+}
+
+table {
+ -pdf-keep-in-frame-mode: shrink;
+}
+
+tr,
+th,
+td {
+
+ vertical-align: middle;
+ width: auto;
+}
+
+th {
+ text-align: center;
+ font-weight: bold;
+}
+
+center {
+ text-align: center;
+}
+
+big {
+ font-size: 125%;
+}
+
+small {
+ font-size: 75%;
+}
+
+
+ul {
+ margin-left: 1.5em;
+ list-style-type: disc;
+}
+
+ul ul {
+ list-style-type: circle;
+}
+
+ul ul ul {
+ list-style-type: square;
+}
+
+ol {
+ list-style-type: decimal;
+ margin-left: 1.5em;
+}
+
+pre {
+ white-space: pre;
+}
+
+blockquote {
+ margin-left: 1.5em;
+ margin-right: 1.5em;
+}
+
+noscript {
+ display: none;
+}
+"""
+
+DEFAULT_FONT = {
+ "courier": "Courier",
+ "courier-bold": "Courier-Bold",
+ "courier-boldoblique": "Courier-BoldOblique",
+ "courier-oblique": "Courier-Oblique",
+ "helvetica": "Helvetica",
+ "helvetica-bold": "Helvetica-Bold",
+ "helvetica-boldoblique": "Helvetica-BoldOblique",
+ "helvetica-oblique": "Helvetica-Oblique",
+ "times": "Times-Roman",
+ "times-roman": "Times-Roman",
+ "times-bold": "Times-Bold",
+ "times-boldoblique": "Times-BoldOblique",
+ "times-oblique": "Times-Oblique",
+ "symbol": "Symbol",
+ "zapfdingbats": "ZapfDingbats",
+ "zapf-dingbats": "ZapfDingbats",
+
+ # Alias
+ "arial": "Helvetica",
+ "times new roman": "Times-Roman",
+ "georgia": "Times-Roman",
+ 'serif':'Times-Roman',
+ 'sansserif':'Helvetica',
+ 'sans':'Helvetica',
+ 'monospaced':'Courier',
+ 'monospace':'Courier',
+ 'mono':'Courier',
+ 'courier new':'Courier',
+ 'verdana':'Helvetica',
+ 'geneva':'Helvetica',
+ }
+
+PML_PAGESIZES = {
+ "a0": A0,
+ "a1": A1,
+ "a2": A2,
+ "a3": A3,
+ "a4": A4,
+ "a5": A5,
+ "a6": A6,
+ "b0": B0,
+ "b1": B1,
+ "b2": B2,
+ "b3": B3,
+ "b4": B4,
+ "b5": B5,
+ "b6": B6,
+ "letter": LETTER,
+ "legal": LEGAL,
+ "ledger": ELEVENSEVENTEEN,
+ "elevenseventeen": ELEVENSEVENTEEN,
+ }
diff --git a/sx/pisa3/pisa_document.py b/sx/pisa3/pisa_document.py
index f75c73db..2c1e24d1 100644
--- a/sx/pisa3/pisa_document.py
+++ b/sx/pisa3/pisa_document.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
diff --git a/sx/pisa3/pisa_parser.py b/sx/pisa3/pisa_parser.py
index 6927523b..659d9baa 100644
--- a/sx/pisa3/pisa_parser.py
+++ b/sx/pisa3/pisa_parser.py
@@ -1,642 +1,645 @@
-# -*- coding: ISO-8859-1 -*-
-
-# Copyright 2010 Dirk Holtwick, holtwick.it
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-__reversion__ = "$Revision: 20 $"
-__author__ = "$Author: holtwick $"
-__date__ = "$Date: 2007-10-09 12:58:24 +0200 (Di, 09 Okt 2007) $"
-
-import pprint
-import copy
-import types
-import re
-import os
-import os.path
-
-import html5lib
-from html5lib import treebuilders, serializer, treewalkers, inputstream
-from xml.dom import Node
-import xml.dom.minidom
-
-from pisa_default import *
-from pisa_util import *
-from pisa_tags import *
-from pisa_tables import *
-
-import sx.w3c.css as css
-import sx.w3c.cssDOMElementInterface as cssDOMElementInterface
-
-import logging
-log = logging.getLogger("ho.pisa")
-
-rxhttpstrip = re.compile("https?://[^/]+(.*)", re.M | re.I)
-
-class AttrContainer(dict):
-
- def __getattr__(self, name):
- try:
- return dict.__getattr__(self, name)
- except:
- return self[name]
-
-def pisaGetAttributes(c, tag, attributes):
- global TAGS
-
- attrs = {}
- if attributes:
- for k, v in attributes.items():
- try:
- attrs[str(k)] = str(v) # XXX no Unicode! Reportlab fails with template names
- except:
- attrs[k] = v
-
- nattrs = {}
- if TAGS.has_key(tag):
- block, adef = TAGS[tag]
- adef["id"] = STRING
- # print block, adef
- for k, v in adef.items():
- nattrs[k] = None
- # print k, v
- # defaults, wenn vorhanden
- if type(v) == types.TupleType:
- if v[1] == MUST:
- if not attrs.has_key(k):
- log.warn(c.warning("Attribute '%s' must be set!", k))
- nattrs[k] = None
- continue
- nv = attrs.get(k, v[1])
- dfl = v[1]
- v = v[0]
- else:
- nv = attrs.get(k, None)
- dfl = None
- try:
- if nv is not None:
-
- if type(v) == types.ListType:
- nv = nv.strip().lower()
- if nv not in v:
- #~ raise PML_EXCEPTION, "attribute '%s' of wrong value, allowed is one of: %s" % (k, repr(v))
- log.warn(c.warning("Attribute '%s' of wrong value, allowed is one of: %s", k, repr(v)))
- nv = dfl
-
- elif v == BOOL:
- nv = nv.strip().lower()
- nv = nv in ("1", "y", "yes", "true", str(k))
-
- elif v == SIZE:
- try:
- nv = getSize(nv)
- except:
- log.warn(c.warning("Attribute '%s' expects a size value", k))
-
- elif v == BOX:
- nv = getBox(nv, c.pageSize)
-
- elif v == POS:
- nv = getPos(nv, c.pageSize)
-
- elif v == INT:
- nv = int(nv)
-
- elif v == COLOR:
- nv = getColor(nv)
-
- elif v == FILE:
- nv = c.getFile(nv)
-
- elif v == FONT:
- nv = c.getFontName(nv)
-
- nattrs[k] = nv
-
- #for k in attrs.keys():
- # if not nattrs.has_key(k):
- # c.warning("attribute '%s' for tag <%s> not supported" % (k, tag))
-
- except Exception, e:
- log.exception(c.error("Tag handling"))
-
- #else:
- # c.warning("tag <%s> is not supported" % tag)
-
- return AttrContainer(nattrs)
-
-attrNames = '''
- color
- font-family
- font-size
- font-weight
- font-style
- text-decoration
- line-height
- background-color
- display
- margin-left
- margin-right
- margin-top
- margin-bottom
- padding-left
- padding-right
- padding-top
- padding-bottom
- border-top-color
- border-top-style
- border-top-width
- border-bottom-color
- border-bottom-style
- border-bottom-width
- border-left-color
- border-left-style
- border-left-width
- border-right-color
- border-right-style
- border-right-width
- text-align
- vertical-align
- width
- height
- zoom
- page-break-after
- page-break-before
- list-style-type
- list-style-image
- white-space
- text-indent
- -pdf-page-break
- -pdf-frame-break
- -pdf-next-page
- -pdf-keep-with-next
- -pdf-outline
- -pdf-outline-level
- -pdf-outline-open
- -pdf-line-spacing
- -pdf-keep-in-frame-mode
- '''.strip().split()
-
-def getCSSAttr(self, cssCascade, attrName, default=NotImplemented):
- if attrName in self.cssAttrs:
- return self.cssAttrs[attrName]
-
- try:
- result = cssCascade.findStyleFor(self.cssElement, attrName, default)
- except LookupError:
- result = None
-
- # XXX Workaround for inline styles
- try:
- style = self.cssStyle
- except:
- style = self.cssStyle = cssCascade.parser.parseInline(self.cssElement.getStyleAttr() or '')[0]
- if style.has_key(attrName):
- result = style[attrName]
-
- if result == 'inherit':
- if hasattr(self.parentNode, 'getCSSAttr'):
- result = self.parentNode.getCSSAttr(cssCascade, attrName, default)
- elif default is not NotImplemented:
- return default
- else:
- raise LookupError("Could not find inherited CSS attribute value for '%s'" % (attrName,))
-
- if result is not None:
- self.cssAttrs[attrName] = result
- return result
-
-xml.dom.minidom.Element.getCSSAttr = getCSSAttr
-
-def CSSCollect(node, c):
- #node.cssAttrs = {}
- #return node.cssAttrs
- if c.css:
- node.cssElement = cssDOMElementInterface.CSSDOMElementInterface(node)
- node.cssAttrs = {}
- # node.cssElement.onCSSParserVisit(c.cssCascade.parser)
- cssAttrMap = {}
- for cssAttrName in attrNames:
- try:
- cssAttrMap[cssAttrName] = node.getCSSAttr(c.cssCascade, cssAttrName)
- #except LookupError:
- # pass
- except Exception:
- log.debug("CSS error '%s'", cssAttrName, exc_info=1)
- return node.cssAttrs
-
-def CSS2Frag(c, kw, isBlock):
- # COLORS
- if c.cssAttr.has_key("color"):
- c.frag.textColor = getColor(c.cssAttr["color"])
- if c.cssAttr.has_key("background-color"):
- c.frag.backColor = getColor(c.cssAttr["background-color"])
- # FONT SIZE, STYLE, WEIGHT
- if c.cssAttr.has_key("font-family"):
- c.frag.fontName = c.getFontName(c.cssAttr["font-family"])
- if c.cssAttr.has_key("font-size"):
- # XXX inherit
- c.frag.fontSize = max(getSize("".join(c.cssAttr["font-size"]), c.frag.fontSize, c.baseFontSize), 1.0)
- if c.cssAttr.has_key("line-height"):
- leading = "".join(c.cssAttr["line-height"])
- c.frag.leading = getSize(leading, c.frag.fontSize)
- c.frag.leadingSource = leading
- else:
- c.frag.leading = getSize(c.frag.leadingSource, c.frag.fontSize)
- if c.cssAttr.has_key("-pdf-line-spacing"):
- c.frag.leadingSpace = getSize("".join(c.cssAttr["-pdf-line-spacing"]))
- # print "line-spacing", c.cssAttr["-pdf-line-spacing"], c.frag.leading
- if c.cssAttr.has_key("font-weight"):
- value = c.cssAttr["font-weight"].lower()
- if value in ("bold", "bolder", "500", "600", "700", "800", "900"):
- c.frag.bold = 1
- else:
- c.frag.bold = 0
- for value in toList(c.cssAttr.get("text-decoration", "")):
- if "underline" in value:
- c.frag.underline = 1
- if "line-through" in value:
- c.frag.strike = 1
- if "none" in value:
- c.frag.underline = 0
- c.frag.strike = 0
- if c.cssAttr.has_key("font-style"):
- value = c.cssAttr["font-style"].lower()
- if value in ("italic", "oblique"):
- c.frag.italic = 1
- else:
- c.frag.italic = 0
- if c.cssAttr.has_key("white-space"):
- # normal | pre | nowrap
- c.frag.whiteSpace = str(c.cssAttr["white-space"]).lower()
- # ALIGN & VALIGN
- if c.cssAttr.has_key("text-align"):
- c.frag.alignment = getAlign(c.cssAttr["text-align"])
- if c.cssAttr.has_key("vertical-align"):
- c.frag.vAlign = c.cssAttr["vertical-align"]
- # HEIGHT & WIDTH
- if c.cssAttr.has_key("height"):
- c.frag.height = "".join(toList(c.cssAttr["height"])) # XXX Relative is not correct!
- if c.frag.height in ("auto",):
- c.frag.height = None
- if c.cssAttr.has_key("width"):
- # print c.cssAttr["width"]
- c.frag.width = "".join(toList(c.cssAttr["width"])) # XXX Relative is not correct!
- if c.frag.width in ("auto",):
- c.frag.width = None
- # ZOOM
- if c.cssAttr.has_key("zoom"):
- # print c.cssAttr["width"]
- zoom = "".join(toList(c.cssAttr["zoom"])) # XXX Relative is not correct!
- if zoom.endswith("%"):
- zoom = float(zoom[: - 1]) / 100.0
- c.frag.zoom = float(zoom)
- # MARGINS & LIST INDENT, STYLE
- if isBlock:
- if c.cssAttr.has_key("margin-top"):
- c.frag.spaceBefore = getSize(c.cssAttr["margin-top"], c.frag.fontSize)
- if c.cssAttr.has_key("margin-bottom"):
- c.frag.spaceAfter = getSize(c.cssAttr["margin-bottom"], c.frag.fontSize)
- if c.cssAttr.has_key("margin-left"):
- c.frag.bulletIndent = kw["margin-left"] # For lists
- kw["margin-left"] += getSize(c.cssAttr["margin-left"], c.frag.fontSize)
- c.frag.leftIndent = kw["margin-left"]
- # print "MARGIN LEFT", kw["margin-left"], c.frag.bulletIndent
- if c.cssAttr.has_key("margin-right"):
- kw["margin-right"] += getSize(c.cssAttr["margin-right"], c.frag.fontSize)
- c.frag.rightIndent = kw["margin-right"]
- # print c.frag.rightIndent
- if c.cssAttr.has_key("text-indent"):
- c.frag.firstLineIndent = getSize(c.cssAttr["text-indent"], c.frag.fontSize)
- if c.cssAttr.has_key("list-style-type"):
- c.frag.listStyleType = str(c.cssAttr["list-style-type"]).lower()
- if c.cssAttr.has_key("list-style-image"):
- c.frag.listStyleImage = c.getFile(c.cssAttr["list-style-image"])
- # PADDINGS
- if isBlock:
- if c.cssAttr.has_key("padding-top"):
- c.frag.paddingTop = getSize(c.cssAttr["padding-top"], c.frag.fontSize)
- if c.cssAttr.has_key("padding-bottom"):
- c.frag.paddingBottom = getSize(c.cssAttr["padding-bottom"], c.frag.fontSize)
- if c.cssAttr.has_key("padding-left"):
- c.frag.paddingLeft = getSize(c.cssAttr["padding-left"], c.frag.fontSize)
- if c.cssAttr.has_key("padding-right"):
- c.frag.paddingRight = getSize(c.cssAttr["padding-right"], c.frag.fontSize)
- # BORDERS
- if isBlock:
- if c.cssAttr.has_key("border-top-width"):
- # log.debug(c.cssAttr["border-top-width"])
- c.frag.borderTopWidth = getSize(c.cssAttr["border-top-width"], c.frag.fontSize)
- if c.cssAttr.has_key("border-bottom-width"):
- c.frag.borderBottomWidth = getSize(c.cssAttr["border-bottom-width"], c.frag.fontSize)
- if c.cssAttr.has_key("border-left-width"):
- c.frag.borderLeftWidth = getSize(c.cssAttr["border-left-width"], c.frag.fontSize)
- if c.cssAttr.has_key("border-right-width"):
- c.frag.borderRightWidth = getSize(c.cssAttr["border-right-width"], c.frag.fontSize)
- if c.cssAttr.has_key("border-top-style"):
- c.frag.borderTopStyle = c.cssAttr["border-top-style"]
- if c.cssAttr.has_key("border-bottom-style"):
- c.frag.borderBottomStyle = c.cssAttr["border-bottom-style"]
- if c.cssAttr.has_key("border-left-style"):
- c.frag.borderLeftStyle = c.cssAttr["border-left-style"]
- if c.cssAttr.has_key("border-right-style"):
- c.frag.borderRightStyle = c.cssAttr["border-right-style"]
- if c.cssAttr.has_key("border-top-color"):
- c.frag.borderTopColor = getColor(c.cssAttr["border-top-color"])
- if c.cssAttr.has_key("border-bottom-color"):
- c.frag.borderBottomColor = getColor(c.cssAttr["border-bottom-color"])
- if c.cssAttr.has_key("border-left-color"):
- c.frag.borderLeftColor = getColor(c.cssAttr["border-left-color"])
- if c.cssAttr.has_key("border-right-color"):
- c.frag.borderRightColor = getColor(c.cssAttr["border-right-color"])
-
-def pisaPreLoop(node, c, collect=False):
- """
- Collect all CSS definitions
- """
-
- data = u""
- if node.nodeType == Node.TEXT_NODE and collect:
- data = node.data
-
- elif node.nodeType == Node.ELEMENT_NODE:
- name = node.tagName.lower()
-
- # print name, node.attributes.items()
- if name in ("style", "link"):
- attr = pisaGetAttributes(c, name, node.attributes)
- # print " ", attr
- media = [x.strip() for x in attr.media.lower().split(",") if x.strip()]
- # print repr(media)
-
- if (attr.get("type", "").lower() in ("", "text/css") and (
- not media or
- "all" in media or
- "print" in media or
- "pdf" in media)):
-
- if name == "style":
- for node in node.childNodes:
- data += pisaPreLoop(node, c, collect=True)
- c.addCSS(data)
- return u""
- #collect = True
-
- if name == "link" and attr.href and attr.rel.lower() == "stylesheet":
- # print "CSS LINK", attr
- c.addCSS('\n@import "%s" %s;' % (attr.href, ",".join(media)))
- # c.addCSS(unicode(file(attr.href, "rb").read(), attr.charset))
-
- #else:
- # print node.nodeType
-
- for node in node.childNodes:
- result = pisaPreLoop(node, c, collect=collect)
- if collect:
- data += result
-
- return data
-
-def pisaLoop(node, c, path=[], **kw):
-
- # Initialize KW
- if not kw:
- kw = {
- "margin-top": 0,
- "margin-bottom": 0,
- "margin-left": 0,
- "margin-right": 0,
- }
- else:
- kw = copy.copy(kw)
-
- indent = len(path) * " "
-
- # TEXT
- if node.nodeType == Node.TEXT_NODE:
- # print indent, "#", repr(node.data) #, c.frag
- c.addFrag(node.data)
- # c.text.append(node.value)
-
- # ELEMENT
- elif node.nodeType == Node.ELEMENT_NODE:
-
- node.tagName = node.tagName.replace(":", "").lower()
-
- if node.tagName in ("style", "script"):
- return
-
- path = copy.copy(path) + [node.tagName]
-
- # Prepare attributes
- attr = pisaGetAttributes(c, node.tagName, node.attributes)
- # log.debug(indent + "<%s %s>" % (node.tagName, attr) + repr(node.attributes.items())) #, path
-
- # Calculate styles
- c.cssAttr = CSSCollect(node, c)
- c.node = node
-
- # Block?
- PAGE_BREAK = 1
- PAGE_BREAK_RIGHT = 2
- PAGE_BREAK_LEFT = 3
-
- pageBreakAfter = False
- frameBreakAfter = False
- display = c.cssAttr.get("display", "inline").lower()
- # print indent, node.tagName, display, c.cssAttr.get("background-color", None), attr
- isBlock = (display == "block")
- if isBlock:
- c.addPara()
-
- # Page break by CSS
- if c.cssAttr.has_key("-pdf-next-page"):
- c.addStory(NextPageTemplate(str(c.cssAttr["-pdf-next-page"])))
- if c.cssAttr.has_key("-pdf-page-break"):
- if str(c.cssAttr["-pdf-page-break"]).lower() == "before":
- c.addStory(PageBreak())
- if c.cssAttr.has_key("-pdf-frame-break"):
- if str(c.cssAttr["-pdf-frame-break"]).lower() == "before":
- c.addStory(FrameBreak())
- if str(c.cssAttr["-pdf-frame-break"]).lower() == "after":
- frameBreakAfter = True
- if c.cssAttr.has_key("page-break-before"):
- if str(c.cssAttr["page-break-before"]).lower() == "always":
- c.addStory(PageBreak())
- if str(c.cssAttr["page-break-before"]).lower() == "right":
- c.addStory(PageBreak())
- c.addStory(PmlRightPageBreak())
- if str(c.cssAttr["page-break-before"]).lower() == "left":
- c.addStory(PageBreak())
- c.addStory(PmlLeftPageBreak())
- if c.cssAttr.has_key("page-break-after"):
- if str(c.cssAttr["page-break-after"]).lower() == "always":
- pageBreakAfter = PAGE_BREAK
- if str(c.cssAttr["page-break-after"]).lower() == "right":
- pageBreakAfter = PAGE_BREAK_RIGHT
- if str(c.cssAttr["page-break-after"]).lower() == "left":
- pageBreakAfter = PAGE_BREAK_LEFT
-
- if display == "none":
- # print "none!"
- return
-
- # Translate CSS to frags
-
- # Save previous frag styles
- c.pushFrag()
-
- # Map styles to Reportlab fragment properties
- CSS2Frag(c, kw, isBlock)
-
- # EXTRAS
- if c.cssAttr.has_key("-pdf-keep-with-next"):
- c.frag.keepWithNext = getBool(c.cssAttr["-pdf-keep-with-next"])
- if c.cssAttr.has_key("-pdf-outline"):
- c.frag.outline = getBool(c.cssAttr["-pdf-outline"])
- if c.cssAttr.has_key("-pdf-outline-level"):
- c.frag.outlineLevel = int(c.cssAttr["-pdf-outline-level"])
- if c.cssAttr.has_key("-pdf-outline-open"):
- c.frag.outlineOpen = getBool(c.cssAttr["-pdf-outline-open"])
- #if c.cssAttr.has_key("-pdf-keep-in-frame-max-width"):
- # c.frag.keepInFrameMaxWidth = getSize("".join(c.cssAttr["-pdf-keep-in-frame-max-width"]))
- #if c.cssAttr.has_key("-pdf-keep-in-frame-max-height"):
- # c.frag.keepInFrameMaxHeight = getSize("".join(c.cssAttr["-pdf-keep-in-frame-max-height"]))
- if c.cssAttr.has_key("-pdf-keep-in-frame-mode"):
- value = str(c.cssAttr["-pdf-keep-in-frame-mode"]).strip().lower()
- if value not in ("shrink", "error", "overflow", "shrink", "truncate"):
- value = None
- c.frag.keepInFrameMode = value
-
- # BEGIN tag
- klass = globals().get("pisaTag%s" % node.tagName.replace(":", "").upper(), None)
- obj = None
-
- # Static block
- elementId = attr.get("id", None)
- staticFrame = c.frameStatic.get(elementId, None)
- if staticFrame:
- c.frag.insideStaticFrame += 1
- oldStory = c.swapStory()
-
- # Tag specific operations
- if klass is not None:
- obj = klass(node, attr)
- obj.start(c)
-
- # Visit child nodes
- c.fragBlock = fragBlock = copy.copy(c.frag)
- for nnode in node.childNodes:
- pisaLoop(nnode, c, path, **kw)
- c.fragBlock = fragBlock
-
- # END tag
- if obj:
- obj.end(c)
-
- # Block?
- if isBlock:
- c.addPara()
-
- # XXX Buggy!
-
- # Page break by CSS
- if pageBreakAfter:
- c.addStory(PageBreak())
- if pageBreakAfter == PAGE_BREAK_RIGHT:
- c.addStory(PmlRightPageBreak())
- if pageBreakAfter == PAGE_BREAK_LEFT:
- c.addStory(PmlLeftPageBreak())
- if frameBreakAfter:
- c.addStory(FrameBreak())
-
- # Static block, END
- if staticFrame:
- c.addPara()
- for frame in staticFrame:
- frame.pisaStaticStory = c.story
- c.swapStory(oldStory)
- c.frag.insideStaticFrame -= 1
-
- # c.debug(1, indent, "%s>" % (node.tagName))
-
- # Reset frag style
- c.pullFrag()
-
- # Unknown or not handled
- else:
- # c.debug(1, indent, "???", node, node.nodeType, repr(node))
- # Loop over children
- for node in node.childNodes:
- pisaLoop(node, c, path, **kw)
-
-def pisaParser(src, c, default_css="", xhtml=False, encoding=None, xml_output=None):
- """
- - Parse HTML and get miniDOM
- - Extract CSS informations, add default CSS, parse CSS
- - Handle the document DOM itself and build reportlab story
- - Return Context object
- """
-
- if xhtml:
- parser = html5lib.XHTMLParser(tree=treebuilders.getTreeBuilder("dom"))
- else:
- parser = html5lib.HTMLParser(tree=treebuilders.getTreeBuilder("dom"))
-
- if type(src) in types.StringTypes:
- if type(src) is types.UnicodeType:
- encoding = "utf8"
- src = src.encode(encoding)
- src = pisaTempFile(src, capacity=c.capacity)
-
- # Test for the restrictions of html5lib
- if encoding:
- # Workaround for html5lib<0.11.1
- if hasattr(inputstream, "isValidEncoding"):
- if encoding.strip().lower() == "utf8":
- encoding = "utf-8"
- if not inputstream.isValidEncoding(encoding):
- log.error("%r is not a valid encoding e.g. 'utf8' is not valid but 'utf-8' is!", encoding)
- else:
- if inputstream.codecName(encoding) is None:
- log.error("%r is not a valid encoding", encoding)
-
- document = parser.parse(
- src,
- encoding=encoding)
-
- if xml_output:
- xml_output.write(document.toprettyxml(encoding="utf8"))
-
- if default_css:
- c.addCSS(default_css)
-
- pisaPreLoop(document, c)
- #try:
- c.parseCSS()
- #except:
- # c.cssText = DEFAULT_CSS
- # c.parseCSS()
- # c.debug(9, pprint.pformat(c.css))
- pisaLoop(document, c)
- return c
-
-# Shortcuts
-
-HTML2PDF = pisaParser
-
-def XHTML2PDF(*a, **kw):
- kw["xhtml"] = True
- return HTML2PDF(*a, **kw)
-
-XML2PDF = XHTML2PDF
+# -*- coding: utf-8 -*-
+
+# Copyright 2010 Dirk Holtwick, holtwick.it
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__reversion__ = "$Revision: 20 $"
+__author__ = "$Author: holtwick $"
+__date__ = "$Date: 2007-10-09 12:58:24 +0200 (Di, 09 Okt 2007) $"
+
+import pprint
+import copy
+import types
+import re
+import os
+import os.path
+
+import html5lib
+from html5lib import treebuilders, serializer, treewalkers, inputstream
+from xml.dom import Node
+import xml.dom.minidom
+
+from pisa_default import *
+from pisa_util import *
+from pisa_tags import *
+from pisa_tables import *
+
+import sx.w3c.css as css
+import sx.w3c.cssDOMElementInterface as cssDOMElementInterface
+
+import logging
+log = logging.getLogger("ho.pisa")
+
+rxhttpstrip = re.compile("https?://[^/]+(.*)", re.M | re.I)
+
+class AttrContainer(dict):
+
+ def __getattr__(self, name):
+ try:
+ return dict.__getattr__(self, name)
+ except:
+ return self[name]
+
+def pisaGetAttributes(c, tag, attributes):
+ global TAGS
+
+ attrs = {}
+ if attributes:
+ for k, v in attributes.items():
+ try:
+ attrs[str(k)] = str(v) # XXX no Unicode! Reportlab fails with template names
+ except:
+ attrs[k] = v
+
+ nattrs = {}
+ if TAGS.has_key(tag):
+ block, adef = TAGS[tag]
+ adef["id"] = STRING
+ # print block, adef
+ for k, v in adef.items():
+ nattrs[k] = None
+ # print k, v
+ # defaults, wenn vorhanden
+ if type(v) == types.TupleType:
+ if v[1] == MUST:
+ if not attrs.has_key(k):
+ log.warn(c.warning("Attribute '%s' must be set!", k))
+ nattrs[k] = None
+ continue
+ nv = attrs.get(k, v[1])
+ dfl = v[1]
+ v = v[0]
+ else:
+ nv = attrs.get(k, None)
+ dfl = None
+ try:
+ if nv is not None:
+
+ if type(v) == types.ListType:
+ nv = nv.strip().lower()
+ if nv not in v:
+ #~ raise PML_EXCEPTION, "attribute '%s' of wrong value, allowed is one of: %s" % (k, repr(v))
+ log.warn(c.warning("Attribute '%s' of wrong value, allowed is one of: %s", k, repr(v)))
+ nv = dfl
+
+ elif v == BOOL:
+ nv = nv.strip().lower()
+ nv = nv in ("1", "y", "yes", "true", str(k))
+
+ elif v == SIZE:
+ try:
+ nv = getSize(nv)
+ except:
+ log.warn(c.warning("Attribute '%s' expects a size value", k))
+
+ elif v == BOX:
+ nv = getBox(nv, c.pageSize)
+
+ elif v == POS:
+ nv = getPos(nv, c.pageSize)
+
+ elif v == INT:
+ nv = int(nv)
+
+ elif v == COLOR:
+ nv = getColor(nv)
+
+ elif v == FILE:
+ nv = c.getFile(nv)
+
+ elif v == FONT:
+ nv = c.getFontName(nv)
+
+ nattrs[k] = nv
+
+ #for k in attrs.keys():
+ # if not nattrs.has_key(k):
+ # c.warning("attribute '%s' for tag <%s> not supported" % (k, tag))
+
+ except Exception, e:
+ log.exception(c.error("Tag handling"))
+
+ #else:
+ # c.warning("tag <%s> is not supported" % tag)
+
+ return AttrContainer(nattrs)
+
+attrNames = '''
+ color
+ font-family
+ font-size
+ font-weight
+ font-style
+ text-decoration
+ line-height
+ background-color
+ display
+ margin-left
+ margin-right
+ margin-top
+ margin-bottom
+ padding-left
+ padding-right
+ padding-top
+ padding-bottom
+ border-top-color
+ border-top-style
+ border-top-width
+ border-bottom-color
+ border-bottom-style
+ border-bottom-width
+ border-left-color
+ border-left-style
+ border-left-width
+ border-right-color
+ border-right-style
+ border-right-width
+ text-align
+ vertical-align
+ width
+ height
+ zoom
+ page-break-after
+ page-break-before
+ list-style-type
+ list-style-image
+ white-space
+ text-indent
+ -pdf-page-break
+ -pdf-frame-break
+ -pdf-next-page
+ -pdf-keep-with-next
+ -pdf-outline
+ -pdf-outline-level
+ -pdf-outline-open
+ -pdf-line-spacing
+ -pdf-keep-in-frame-mode
+ -pdf-word-wrap
+ '''.strip().split()
+
+def getCSSAttr(self, cssCascade, attrName, default=NotImplemented):
+ if attrName in self.cssAttrs:
+ return self.cssAttrs[attrName]
+
+ try:
+ result = cssCascade.findStyleFor(self.cssElement, attrName, default)
+ except LookupError:
+ result = None
+
+ # XXX Workaround for inline styles
+ try:
+ style = self.cssStyle
+ except:
+ style = self.cssStyle = cssCascade.parser.parseInline(self.cssElement.getStyleAttr() or '')[0]
+ if style.has_key(attrName):
+ result = style[attrName]
+
+ if result == 'inherit':
+ if hasattr(self.parentNode, 'getCSSAttr'):
+ result = self.parentNode.getCSSAttr(cssCascade, attrName, default)
+ elif default is not NotImplemented:
+ return default
+ else:
+ raise LookupError("Could not find inherited CSS attribute value for '%s'" % (attrName,))
+
+ if result is not None:
+ self.cssAttrs[attrName] = result
+ return result
+
+xml.dom.minidom.Element.getCSSAttr = getCSSAttr
+
+def CSSCollect(node, c):
+ #node.cssAttrs = {}
+ #return node.cssAttrs
+ if c.css:
+ node.cssElement = cssDOMElementInterface.CSSDOMElementInterface(node)
+ node.cssAttrs = {}
+ # node.cssElement.onCSSParserVisit(c.cssCascade.parser)
+ cssAttrMap = {}
+ for cssAttrName in attrNames:
+ try:
+ cssAttrMap[cssAttrName] = node.getCSSAttr(c.cssCascade, cssAttrName)
+ #except LookupError:
+ # pass
+ except Exception:
+ log.debug("CSS error '%s'", cssAttrName, exc_info=1)
+ return node.cssAttrs
+
+def CSS2Frag(c, kw, isBlock):
+ # COLORS
+ if c.cssAttr.has_key("color"):
+ c.frag.textColor = getColor(c.cssAttr["color"])
+ if c.cssAttr.has_key("background-color"):
+ c.frag.backColor = getColor(c.cssAttr["background-color"])
+ # FONT SIZE, STYLE, WEIGHT
+ if c.cssAttr.has_key("font-family"):
+ c.frag.fontName = c.getFontName(c.cssAttr["font-family"])
+ if c.cssAttr.has_key("font-size"):
+ # XXX inherit
+ c.frag.fontSize = max(getSize("".join(c.cssAttr["font-size"]), c.frag.fontSize, c.baseFontSize), 1.0)
+ if c.cssAttr.has_key("line-height"):
+ leading = "".join(c.cssAttr["line-height"])
+ c.frag.leading = getSize(leading, c.frag.fontSize)
+ c.frag.leadingSource = leading
+ else:
+ c.frag.leading = getSize(c.frag.leadingSource, c.frag.fontSize)
+ if c.cssAttr.has_key("-pdf-line-spacing"):
+ c.frag.leadingSpace = getSize("".join(c.cssAttr["-pdf-line-spacing"]))
+ # print "line-spacing", c.cssAttr["-pdf-line-spacing"], c.frag.leading
+ if c.cssAttr.has_key("font-weight"):
+ value = c.cssAttr["font-weight"].lower()
+ if value in ("bold", "bolder", "500", "600", "700", "800", "900"):
+ c.frag.bold = 1
+ else:
+ c.frag.bold = 0
+ for value in toList(c.cssAttr.get("text-decoration", "")):
+ if "underline" in value:
+ c.frag.underline = 1
+ if "line-through" in value:
+ c.frag.strike = 1
+ if "none" in value:
+ c.frag.underline = 0
+ c.frag.strike = 0
+ if c.cssAttr.has_key("font-style"):
+ value = c.cssAttr["font-style"].lower()
+ if value in ("italic", "oblique"):
+ c.frag.italic = 1
+ else:
+ c.frag.italic = 0
+ if c.cssAttr.has_key("white-space"):
+ # normal | pre | nowrap
+ c.frag.whiteSpace = str(c.cssAttr["white-space"]).lower()
+ # ALIGN & VALIGN
+ if c.cssAttr.has_key("text-align"):
+ c.frag.alignment = getAlign(c.cssAttr["text-align"])
+ if c.cssAttr.has_key("vertical-align"):
+ c.frag.vAlign = c.cssAttr["vertical-align"]
+ # HEIGHT & WIDTH
+ if c.cssAttr.has_key("height"):
+ c.frag.height = "".join(toList(c.cssAttr["height"])) # XXX Relative is not correct!
+ if c.frag.height in ("auto",):
+ c.frag.height = None
+ if c.cssAttr.has_key("width"):
+ # print c.cssAttr["width"]
+ c.frag.width = "".join(toList(c.cssAttr["width"])) # XXX Relative is not correct!
+ if c.frag.width in ("auto",):
+ c.frag.width = None
+ # ZOOM
+ if c.cssAttr.has_key("zoom"):
+ # print c.cssAttr["width"]
+ zoom = "".join(toList(c.cssAttr["zoom"])) # XXX Relative is not correct!
+ if zoom.endswith("%"):
+ zoom = float(zoom[: - 1]) / 100.0
+ c.frag.zoom = float(zoom)
+ # MARGINS & LIST INDENT, STYLE
+ if isBlock:
+ if c.cssAttr.has_key("margin-top"):
+ c.frag.spaceBefore = getSize(c.cssAttr["margin-top"], c.frag.fontSize)
+ if c.cssAttr.has_key("margin-bottom"):
+ c.frag.spaceAfter = getSize(c.cssAttr["margin-bottom"], c.frag.fontSize)
+ if c.cssAttr.has_key("margin-left"):
+ c.frag.bulletIndent = kw["margin-left"] # For lists
+ kw["margin-left"] += getSize(c.cssAttr["margin-left"], c.frag.fontSize)
+ c.frag.leftIndent = kw["margin-left"]
+ # print "MARGIN LEFT", kw["margin-left"], c.frag.bulletIndent
+ if c.cssAttr.has_key("margin-right"):
+ kw["margin-right"] += getSize(c.cssAttr["margin-right"], c.frag.fontSize)
+ c.frag.rightIndent = kw["margin-right"]
+ # print c.frag.rightIndent
+ if c.cssAttr.has_key("text-indent"):
+ c.frag.firstLineIndent = getSize(c.cssAttr["text-indent"], c.frag.fontSize)
+ if c.cssAttr.has_key("list-style-type"):
+ c.frag.listStyleType = str(c.cssAttr["list-style-type"]).lower()
+ if c.cssAttr.has_key("list-style-image"):
+ c.frag.listStyleImage = c.getFile(c.cssAttr["list-style-image"])
+ # PADDINGS
+ if isBlock:
+ if c.cssAttr.has_key("padding-top"):
+ c.frag.paddingTop = getSize(c.cssAttr["padding-top"], c.frag.fontSize)
+ if c.cssAttr.has_key("padding-bottom"):
+ c.frag.paddingBottom = getSize(c.cssAttr["padding-bottom"], c.frag.fontSize)
+ if c.cssAttr.has_key("padding-left"):
+ c.frag.paddingLeft = getSize(c.cssAttr["padding-left"], c.frag.fontSize)
+ if c.cssAttr.has_key("padding-right"):
+ c.frag.paddingRight = getSize(c.cssAttr["padding-right"], c.frag.fontSize)
+ # BORDERS
+ if isBlock:
+ if c.cssAttr.has_key("border-top-width"):
+ # log.debug(c.cssAttr["border-top-width"])
+ c.frag.borderTopWidth = getSize(c.cssAttr["border-top-width"], c.frag.fontSize)
+ if c.cssAttr.has_key("border-bottom-width"):
+ c.frag.borderBottomWidth = getSize(c.cssAttr["border-bottom-width"], c.frag.fontSize)
+ if c.cssAttr.has_key("border-left-width"):
+ c.frag.borderLeftWidth = getSize(c.cssAttr["border-left-width"], c.frag.fontSize)
+ if c.cssAttr.has_key("border-right-width"):
+ c.frag.borderRightWidth = getSize(c.cssAttr["border-right-width"], c.frag.fontSize)
+ if c.cssAttr.has_key("border-top-style"):
+ c.frag.borderTopStyle = c.cssAttr["border-top-style"]
+ if c.cssAttr.has_key("border-bottom-style"):
+ c.frag.borderBottomStyle = c.cssAttr["border-bottom-style"]
+ if c.cssAttr.has_key("border-left-style"):
+ c.frag.borderLeftStyle = c.cssAttr["border-left-style"]
+ if c.cssAttr.has_key("border-right-style"):
+ c.frag.borderRightStyle = c.cssAttr["border-right-style"]
+ if c.cssAttr.has_key("border-top-color"):
+ c.frag.borderTopColor = getColor(c.cssAttr["border-top-color"])
+ if c.cssAttr.has_key("border-bottom-color"):
+ c.frag.borderBottomColor = getColor(c.cssAttr["border-bottom-color"])
+ if c.cssAttr.has_key("border-left-color"):
+ c.frag.borderLeftColor = getColor(c.cssAttr["border-left-color"])
+ if c.cssAttr.has_key("border-right-color"):
+ c.frag.borderRightColor = getColor(c.cssAttr["border-right-color"])
+
+def pisaPreLoop(node, c, collect=False):
+ """
+ Collect all CSS definitions
+ """
+
+ data = u""
+ if node.nodeType == Node.TEXT_NODE and collect:
+ data = node.data
+
+ elif node.nodeType == Node.ELEMENT_NODE:
+ name = node.tagName.lower()
+
+ # print name, node.attributes.items()
+ if name in ("style", "link"):
+ attr = pisaGetAttributes(c, name, node.attributes)
+ # print " ", attr
+ media = [x.strip() for x in attr.media.lower().split(",") if x.strip()]
+ # print repr(media)
+
+ if (attr.get("type", "").lower() in ("", "text/css") and (
+ not media or
+ "all" in media or
+ "print" in media or
+ "pdf" in media)):
+
+ if name == "style":
+ for node in node.childNodes:
+ data += pisaPreLoop(node, c, collect=True)
+ c.addCSS(data)
+ return u""
+ #collect = True
+
+ if name == "link" and attr.href and attr.rel.lower() == "stylesheet":
+ # print "CSS LINK", attr
+ c.addCSS('\n@import "%s" %s;' % (attr.href, ",".join(media)))
+ # c.addCSS(unicode(file(attr.href, "rb").read(), attr.charset))
+
+ #else:
+ # print node.nodeType
+
+ for node in node.childNodes:
+ result = pisaPreLoop(node, c, collect=collect)
+ if collect:
+ data += result
+
+ return data
+
+def pisaLoop(node, c, path=[], **kw):
+
+ # Initialize KW
+ if not kw:
+ kw = {
+ "margin-top": 0,
+ "margin-bottom": 0,
+ "margin-left": 0,
+ "margin-right": 0,
+ }
+ else:
+ kw = copy.copy(kw)
+
+ indent = len(path) * " "
+
+ # TEXT
+ if node.nodeType == Node.TEXT_NODE:
+ # print indent, "#", repr(node.data) #, c.frag
+ c.addFrag(node.data)
+ # c.text.append(node.value)
+
+ # ELEMENT
+ elif node.nodeType == Node.ELEMENT_NODE:
+
+ node.tagName = node.tagName.replace(":", "").lower()
+
+ if node.tagName in ("style", "script"):
+ return
+
+ path = copy.copy(path) + [node.tagName]
+
+ # Prepare attributes
+ attr = pisaGetAttributes(c, node.tagName, node.attributes)
+ # log.debug(indent + "<%s %s>" % (node.tagName, attr) + repr(node.attributes.items())) #, path
+
+ # Calculate styles
+ c.cssAttr = CSSCollect(node, c)
+ c.node = node
+
+ # Block?
+ PAGE_BREAK = 1
+ PAGE_BREAK_RIGHT = 2
+ PAGE_BREAK_LEFT = 3
+
+ pageBreakAfter = False
+ frameBreakAfter = False
+ display = c.cssAttr.get("display", "inline").lower()
+ # print indent, node.tagName, display, c.cssAttr.get("background-color", None), attr
+ isBlock = (display == "block")
+ if isBlock:
+ c.addPara()
+
+ # Page break by CSS
+ if c.cssAttr.has_key("-pdf-next-page"):
+ c.addStory(NextPageTemplate(str(c.cssAttr["-pdf-next-page"])))
+ if c.cssAttr.has_key("-pdf-page-break"):
+ if str(c.cssAttr["-pdf-page-break"]).lower() == "before":
+ c.addStory(PageBreak())
+ if c.cssAttr.has_key("-pdf-frame-break"):
+ if str(c.cssAttr["-pdf-frame-break"]).lower() == "before":
+ c.addStory(FrameBreak())
+ if str(c.cssAttr["-pdf-frame-break"]).lower() == "after":
+ frameBreakAfter = True
+ if c.cssAttr.has_key("page-break-before"):
+ if str(c.cssAttr["page-break-before"]).lower() == "always":
+ c.addStory(PageBreak())
+ if str(c.cssAttr["page-break-before"]).lower() == "right":
+ c.addStory(PageBreak())
+ c.addStory(PmlRightPageBreak())
+ if str(c.cssAttr["page-break-before"]).lower() == "left":
+ c.addStory(PageBreak())
+ c.addStory(PmlLeftPageBreak())
+ if c.cssAttr.has_key("page-break-after"):
+ if str(c.cssAttr["page-break-after"]).lower() == "always":
+ pageBreakAfter = PAGE_BREAK
+ if str(c.cssAttr["page-break-after"]).lower() == "right":
+ pageBreakAfter = PAGE_BREAK_RIGHT
+ if str(c.cssAttr["page-break-after"]).lower() == "left":
+ pageBreakAfter = PAGE_BREAK_LEFT
+
+ if display == "none":
+ # print "none!"
+ return
+
+ # Translate CSS to frags
+
+ # Save previous frag styles
+ c.pushFrag()
+
+ # Map styles to Reportlab fragment properties
+ CSS2Frag(c, kw, isBlock)
+
+ # EXTRAS
+ if c.cssAttr.has_key("-pdf-keep-with-next"):
+ c.frag.keepWithNext = getBool(c.cssAttr["-pdf-keep-with-next"])
+ if c.cssAttr.has_key("-pdf-outline"):
+ c.frag.outline = getBool(c.cssAttr["-pdf-outline"])
+ if c.cssAttr.has_key("-pdf-outline-level"):
+ c.frag.outlineLevel = int(c.cssAttr["-pdf-outline-level"])
+ if c.cssAttr.has_key("-pdf-outline-open"):
+ c.frag.outlineOpen = getBool(c.cssAttr["-pdf-outline-open"])
+ if c.cssAttr.has_key("-pdf-word-wrap"):
+ c.frag.wordWrap = c.cssAttr["-pdf-word-wrap"]
+ #if c.cssAttr.has_key("-pdf-keep-in-frame-max-width"):
+ # c.frag.keepInFrameMaxWidth = getSize("".join(c.cssAttr["-pdf-keep-in-frame-max-width"]))
+ #if c.cssAttr.has_key("-pdf-keep-in-frame-max-height"):
+ # c.frag.keepInFrameMaxHeight = getSize("".join(c.cssAttr["-pdf-keep-in-frame-max-height"]))
+ if c.cssAttr.has_key("-pdf-keep-in-frame-mode"):
+ value = str(c.cssAttr["-pdf-keep-in-frame-mode"]).strip().lower()
+ if value not in ("shrink", "error", "overflow", "shrink", "truncate"):
+ value = None
+ c.frag.keepInFrameMode = value
+
+ # BEGIN tag
+ klass = globals().get("pisaTag%s" % node.tagName.replace(":", "").upper(), None)
+ obj = None
+
+ # Static block
+ elementId = attr.get("id", None)
+ staticFrame = c.frameStatic.get(elementId, None)
+ if staticFrame:
+ c.frag.insideStaticFrame += 1
+ oldStory = c.swapStory()
+
+ # Tag specific operations
+ if klass is not None:
+ obj = klass(node, attr)
+ obj.start(c)
+
+ # Visit child nodes
+ c.fragBlock = fragBlock = copy.copy(c.frag)
+ for nnode in node.childNodes:
+ pisaLoop(nnode, c, path, **kw)
+ c.fragBlock = fragBlock
+
+ # END tag
+ if obj:
+ obj.end(c)
+
+ # Block?
+ if isBlock:
+ c.addPara()
+
+ # XXX Buggy!
+
+ # Page break by CSS
+ if pageBreakAfter:
+ c.addStory(PageBreak())
+ if pageBreakAfter == PAGE_BREAK_RIGHT:
+ c.addStory(PmlRightPageBreak())
+ if pageBreakAfter == PAGE_BREAK_LEFT:
+ c.addStory(PmlLeftPageBreak())
+ if frameBreakAfter:
+ c.addStory(FrameBreak())
+
+ # Static block, END
+ if staticFrame:
+ c.addPara()
+ for frame in staticFrame:
+ frame.pisaStaticStory = c.story
+ c.swapStory(oldStory)
+ c.frag.insideStaticFrame -= 1
+
+ # c.debug(1, indent, "%s>" % (node.tagName))
+
+ # Reset frag style
+ c.pullFrag()
+
+ # Unknown or not handled
+ else:
+ # c.debug(1, indent, "???", node, node.nodeType, repr(node))
+ # Loop over children
+ for node in node.childNodes:
+ pisaLoop(node, c, path, **kw)
+
+def pisaParser(src, c, default_css="", xhtml=False, encoding=None, xml_output=None):
+ """
+ - Parse HTML and get miniDOM
+ - Extract CSS informations, add default CSS, parse CSS
+ - Handle the document DOM itself and build reportlab story
+ - Return Context object
+ """
+
+ if xhtml:
+ parser = html5lib.XHTMLParser(tree=treebuilders.getTreeBuilder("dom"))
+ else:
+ parser = html5lib.HTMLParser(tree=treebuilders.getTreeBuilder("dom"))
+
+ if type(src) in types.StringTypes:
+ if type(src) is types.UnicodeType:
+ encoding = "utf8"
+ src = src.encode(encoding)
+ src = pisaTempFile(src, capacity=c.capacity)
+
+ # Test for the restrictions of html5lib
+ if encoding:
+ # Workaround for html5lib<0.11.1
+ if hasattr(inputstream, "isValidEncoding"):
+ if encoding.strip().lower() == "utf8":
+ encoding = "utf-8"
+ if not inputstream.isValidEncoding(encoding):
+ log.error("%r is not a valid encoding e.g. 'utf8' is not valid but 'utf-8' is!", encoding)
+ else:
+ if inputstream.codecName(encoding) is None:
+ log.error("%r is not a valid encoding", encoding)
+
+ document = parser.parse(
+ src,
+ encoding=encoding)
+
+ if xml_output:
+ xml_output.write(document.toprettyxml(encoding="utf8"))
+
+ if default_css:
+ c.addCSS(default_css)
+
+ pisaPreLoop(document, c)
+ #try:
+ c.parseCSS()
+ #except:
+ # c.cssText = DEFAULT_CSS
+ # c.parseCSS()
+ # c.debug(9, pprint.pformat(c.css))
+ pisaLoop(document, c)
+ return c
+
+# Shortcuts
+
+HTML2PDF = pisaParser
+
+def XHTML2PDF(*a, **kw):
+ kw["xhtml"] = True
+ return HTML2PDF(*a, **kw)
+
+XML2PDF = XHTML2PDF
diff --git a/sx/pisa3/pisa_pdf.py b/sx/pisa3/pisa_pdf.py
index 287945dd..76456f5b 100644
--- a/sx/pisa3/pisa_pdf.py
+++ b/sx/pisa3/pisa_pdf.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
diff --git a/sx/pisa3/pisa_reportlab.py b/sx/pisa3/pisa_reportlab.py
index a24ff883..e3a518ce 100644
--- a/sx/pisa3/pisa_reportlab.py
+++ b/sx/pisa3/pisa_reportlab.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
@@ -167,28 +167,25 @@ def beforeDrawPage(self, canvas, doc):
# # print "drawing exception", str(e)
# pass
+ def pageNumbering(objList):
+ for obj in objList:
+ if isinstance(obj, PmlParagraph):
+ for frag in obj.frags:
+ if frag.pageNumber:
+ frag.text = pagenumber
+ #import pdb; pdb.set_trace()
+
+ elif isinstance(obj, PmlTable):
+ pageNumbering(flatten(obj._cellvalues))
try:
# Paint static frames
- pagenumber = str(canvas.getPageNumber())
+ pagenumber = canvas.getPageNumber()
for frame in self.pisaStaticList:
frame = copy.deepcopy(frame)
story = frame.pisaStaticStory
-
- # Modify page number
- for obj in story:
- if isinstance(obj, PmlParagraph):
- for frag in obj.frags:
- if frag.pageNumber:
- frag.text = pagenumber
- elif isinstance(obj, PmlTable):
- # Accessing private member, but is there any other way?
- for subobj in flatten(obj._cellvalues):
- if isinstance(subobj, PmlParagraph):
- for frag in subobj.frags:
- if frag.pageNumber:
- frag.text = pagenumber
+ pageNumbering(story)
frame.addFromList(story, canvas)
@@ -321,6 +318,7 @@ def getRGBData(self):
im = self._image
mode = self.mode = im.mode
if mode == 'RGBA':
+ if PILImage.VERSION.startswith('1.1.7'): im.load()
self._dataA = PmlImageReader(im.split()[3])
im = im.convert('RGB')
self.mode = 'RGB'
@@ -906,4 +904,4 @@ def draw(self):
canvas.rotate(90)
canvas.scale(self.scale, self.scale)
hand(canvas, debug=0, fill=1)
-"""
\ No newline at end of file
+"""
diff --git a/sx/pisa3/pisa_tables.py b/sx/pisa3/pisa_tables.py
index cb707d37..8bfd227f 100644
--- a/sx/pisa3/pisa_tables.py
+++ b/sx/pisa3/pisa_tables.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
@@ -37,6 +37,14 @@ def _width(value=None):
return value
return getSize(value)
+def _heigth(value=None):
+ if value is None:
+ return None
+ value = str(value)
+ if value.endswith("%"):
+ return value
+ return getSize(value)
+
class TableData:
def __init__(self):
@@ -202,10 +210,17 @@ def end(self, c):
for i, row in enumerate(data):
data[i] += [''] * (maxcols - len(row))
+
+ cols_with_no_width = len(filter(lambda col: col is None, tdata.colw))
+ if cols_with_no_width: # any col width not defined
+ bad_cols = filter(lambda tup: tup[1] is None, enumerate(tdata.colw))
+ fair_division = str(100/float(cols_with_no_width))+'%' # get fair %
+ for i,col in bad_cols:
+ tdata.colw[i] = fair_division # fix empty with fair %
try:
if tdata.data:
- # log.debug("Table sryles %r", tdata.styles)
+ # log.debug("Table styles %r", tdata.styles)
t = PmlTable(
data,
colWidths=tdata.colw,
@@ -317,7 +332,7 @@ def start(self, c):
if not rspan:
height = None #self._getStyle(None, attrs, "height", "height", mode)
if height is not None:
- tdata.rowh[row] = _width(height)
+ tdata.rowh[row] = _heigth(height)
tdata.add_style(('FONTSIZE', begin, end, 1.0))
tdata.add_style(('LEADING', begin, end, 1.0))
@@ -400,4 +415,4 @@ def end_keepinframe(self):
self.next_para()
self.keepinframe["content"] = self.story.pop()
self.add_story(KeepInFrame(**self.keepinframe))
-'''
\ No newline at end of file
+'''
diff --git a/sx/pisa3/pisa_tags.py b/sx/pisa3/pisa_tags.py
index a0eebbaf..93c4e444 100644
--- a/sx/pisa3/pisa_tags.py
+++ b/sx/pisa3/pisa_tags.py
@@ -1,672 +1,678 @@
-# -*- coding: utf-8 -*-
-
-# Copyright 2010 Dirk Holtwick, holtwick.it
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-__reversion__ = "$Revision: 20 $"
-__author__ = "$Author: holtwick $"
-__date__ = "$Date: 2007-10-09 12:58:24 +0200 (Di, 09 Okt 2007) $"
-
-from pisa_default import DEFAULT_CSS
-from pisa_reportlab import *
-from pisa_util import *
-
-from reportlab.graphics.barcode import createBarcodeDrawing
-from reportlab.lib.styles import ParagraphStyle
-from reportlab.platypus.flowables import *
-from reportlab.platypus.paraparser import tt2ps, ABag
-
-from reportlab_paragraph import cleanBlockQuotedText
-
-import reportlab.lib.utils
-
-import os
-import pprint
-import re
-import warnings
-
-import logging
-log = logging.getLogger("ho.pisa")
-
-def deprecation(message):
- warnings.warn("<" + message + "> is deprecated!", DeprecationWarning, stacklevel=2)
-
-class pisaTag:
-
- """
- The default class for a tag definition
- """
-
- def __init__(self, node, attr):
- self.node = node
- self.tag = node.tagName
- self.attr = attr
-
- def start(self, c):
- pass
-
- def end(self, c):
- pass
-
-class pisaTagBODY(pisaTag):
-
- """
- We can also asume that there is a BODY tag because html5lib
- adds it for us. Here we take the base font size for later calculations
- in the FONT tag.
- """
-
- def start(self, c):
- c.baseFontSize = c.frag.fontSize
- # print "base font size", c.baseFontSize
-
-class pisaTagTITLE(pisaTag):
- def end(self, c):
- c.meta["title"] = c.text
- c.clearFrag()
-
-class pisaTagSTYLE(pisaTag):
- def start(self, c):
- c.addPara()
- def end(self, c):
- c.clearFrag()
-
-class pisaTagMETA(pisaTag):
- def start(self, c):
- name = self.attr.name.lower()
- if name in ("author" , "subject", "keywords"):
- c.meta[name] = self.attr.content
-
-class pisaTagSUP(pisaTag):
- def start(self, c):
- c.frag.super = 1
-
-class pisaTagSUB(pisaTag):
- def start(self, c):
- c.frag.sub = 1
-
-class pisaTagA(pisaTag):
-
- rxLink = re.compile("^(#|[a-z]+\:).*")
-
- def start(self, c):
- attr = self.attr
- # XXX Also support attr.id ?
- if attr.name:
- # Important! Make sure that cbDefn is not inherited by other
- # fragments because of a bug in Reportlab!
- afrag = c.frag.clone()
- # These 3 lines are needed to fix an error with non internal fonts
- afrag.fontName = "Helvetica"
- afrag.bold = 0
- afrag.italic = 0
- afrag.cbDefn = ABag(
- kind="anchor",
- name=attr.name,
- label="anchor")
- c.fragAnchor.append(afrag)
- c.anchorName.append(attr.name)
- if attr.href and self.rxLink.match(attr.href):
- c.frag.link = attr.href
-
- def end(self, c):
- pass
-
-class pisaTagFONT(pisaTag):
-
- # Source: http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
-
- def start(self, c):
- if self.attr["color"] is not None:
- c.frag.textColor = getColor(self.attr["color"])
- if self.attr["face"] is not None:
- c.frag.fontName = c.getFontName(self.attr["face"])
- if self.attr["size"] is not None:
- size = getSize(self.attr["size"], c.frag.fontSize, c.baseFontSize)
- c.frag.fontSize = max(size, 1.0)
-
- def end(self, c):
- pass
-
-class pisaTagP(pisaTag):
- def start(self, c):
- # save the type of tag; it's used in PmlBaseDoc.afterFlowable()
- # to check if we need to add an outline-entry
- # c.frag.tag = self.tag
- if self.attr.align is not None:
- #print self.attr.align, getAlign(self.attr.align)
- c.frag.alignment = getAlign(self.attr.align)
-
-class pisaTagDIV(pisaTagP): pass
-class pisaTagH1(pisaTagP): pass
-class pisaTagH2(pisaTagP): pass
-class pisaTagH3(pisaTagP): pass
-class pisaTagH4(pisaTagP): pass
-class pisaTagH5(pisaTagP): pass
-class pisaTagH6(pisaTagP): pass
-
-def listDecimal(c):
- c.listCounter += 1
- return unicode("%d." % c.listCounter)
-
-_bullet = u"\u2022"
-_list_style_type = {
- "none": u"",
- "disc": _bullet,
- "circle": _bullet, # XXX PDF has no equivalent
- "square": _bullet, # XXX PDF has no equivalent
- "decimal": listDecimal,
- "decimal-leading-zero": listDecimal,
- "lower-roman": listDecimal,
- "upper-roman": listDecimal,
- "hebrew": listDecimal,
- "georgian": listDecimal,
- "armenian": listDecimal,
- "cjk-ideographic": listDecimal,
- "hiragana": listDecimal,
- "katakana": listDecimal,
- "hiragana-iroha": listDecimal,
- "katakana-iroha": listDecimal,
- "lower-latin": listDecimal,
- "lower-alpha": listDecimal,
- "upper-latin": listDecimal,
- "upper-alpha": listDecimal,
- "lower-greek": listDecimal,
-}
-
-class pisaTagUL(pisaTagP):
-
- def start(self, c):
- self.counter, c.listCounter = c.listCounter, 0
-
- def end(self, c):
- c.addPara()
- # XXX Simulate margin for the moment
- c.addStory(Spacer(width=1, height=c.fragBlock.spaceAfter))
- c.listCounter = self.counter
-
-class pisaTagOL(pisaTagUL):
- pass
-
-class pisaTagLI(pisaTag):
-
- def start(self, c):
- lst = _list_style_type.get(c.frag.listStyleType or "disc", _bullet)
-
- #log.debug("frag %r", c.copyFrag(
- # text=lst,
- # bulletFontName=c.getFontName("helvetica"),
- # fontName=c.getFontName("helvetica")))
- # c.addFrag("")
-
- #frag = ParaFrag()
- #frag.fontName = frag.bulletFontName = c.getFontName("helvetica")
- #frag.fontSize = c.frag.fontSize
- #c.frag.fontName = c.getFontName("helvetica")
-
- frag = copy.copy(c.frag)
- #print "###", c.frag.fontName
- #frag.fontName = "au_00" # c.getFontName("helvetica")
- #frag.bulletFontName = "au_00" # c.getFontName("helvetica")
-
- self.offset = 0
- if frag.listStyleImage is not None:
- frag.text = u""
- f = frag.listStyleImage
- if f and (not f.notFound()):
- img = PmlImage(
- f.getData(),
- width=None,
- height=None)
- img.drawHeight *= dpi96
- img.drawWidth *= dpi96
- img.pisaZoom = frag.zoom
- img.drawWidth *= img.pisaZoom
- img.drawHeight *= img.pisaZoom
- frag.image = img
- self.offset = max(0, img.drawHeight - c.frag.fontSize)
- else:
- if type(lst) == type(u""):
- frag.text = lst
- else:
- # XXX This should be the recent font, but it throws errors in Reportlab!
- frag.text = lst(c)
-
- # XXX This should usually be done in the context!!!
- frag.fontName = frag.bulletFontName = tt2ps(frag.fontName, frag.bold, frag.italic)
- c.frag.bulletText = [frag]
-
- def end(self, c):
- c.fragBlock.spaceBefore += self.offset
-
- #c.fragBlock.bulletText = self.bulletText
- #print 999, self.bulletText
- # c.addPara()
-
-class pisaTagBR(pisaTag):
-
- def start(self, c):
- # print "BR", c.text[-40:]
- c.frag.lineBreak = 1
- c.addFrag()
- c.fragStrip = True
- del c.frag.lineBreak
- c.force = True
-
-class pisaTagIMG(pisaTag):
-
- def start(self, c):
- attr = self.attr
- if attr.src and (not attr.src.notFound()):
-
- try:
- align = attr.align or c.frag.vAlign or "baseline"
- # print "align", align, attr.align, c.frag.vAlign
-
- width = c.frag.width
- height = c.frag.height
-
- if attr.width:
- width = attr.width * dpi96
- if attr.height:
- height = attr.height * dpi96
-
- img = PmlImage(
- attr.src.getData(),
- width=None,
- height=None)
-
- img.pisaZoom = c.frag.zoom
-
- img.drawHeight *= dpi96
- img.drawWidth *= dpi96
-
- if (width is None) and (height is not None):
- factor = float(height) / img.drawHeight
- img.drawWidth *= factor
- img.drawHeight = height
- elif (height is None) and (width is not None):
- factor = float(width) / img.drawWidth
- img.drawHeight *= factor
- img.drawWidth = width
- elif (width is not None) and (height is not None):
- img.drawWidth = width
- img.drawHeight = height
-
- img.drawWidth *= img.pisaZoom
- img.drawHeight *= img.pisaZoom
-
- img.spaceBefore = c.frag.spaceBefore
- img.spaceAfter = c.frag.spaceAfter
-
- # print "image", id(img), img.drawWidth, img.drawHeight
-
- '''
- TODO:
-
- - Apply styles
- - vspace etc.
- - Borders
- - Test inside tables
- '''
-
- c.force = True
- if align in ["left", "right"]:
-
- c.image = img
- c.imageData = dict(
- align=align
- )
-
- else:
-
- # Important! Make sure that cbDefn is not inherited by other
- # fragments because of a bug in Reportlab!
- # afrag = c.frag.clone()
-
- valign = align
- if valign in ["texttop"]:
- valign = "top"
- elif valign in ["absmiddle"]:
- valign = "middle"
- elif valign in ["absbottom", "baseline"]:
- valign = "bottom"
-
- afrag = c.frag.clone()
- afrag.text = ""
- afrag.fontName="Helvetica" # Fix for a nasty bug!!!
- afrag.cbDefn = ABag(
- kind="img",
- image=img, #.getImage(), # XXX Inline?
- valign=valign,
- fontName="Helvetica",
- fontSize=img.drawHeight,
- width=img.drawWidth,
- height=img.drawHeight)
- # print "add frag", id(afrag), img.drawWidth, img.drawHeight
- c.fragList.append(afrag)
- c.fontSize = img.drawHeight
-
- except Exception:
- log.warn(c.warning("Error in handling image"), exc_info=1)
- else:
- log.warn(c.warning("Need a valid file name!"))
-
-class pisaTagHR(pisaTag):
-
- def start(self, c):
- c.addPara()
- c.addStory(HRFlowable(
- color=self.attr.color,
- thickness=self.attr.size,
- width="100%",
- spaceBefore=c.frag.spaceBefore,
- spaceAfter=c.frag.spaceAfter
- ))
-
-# --- Forms
-
-import pisa_reportlab
-
-if 0:
-
- class pisaTagINPUT(pisaTag):
-
- def _render(self, c, attr):
- width = 10
- height = 10
- if attr.type == "text":
- width = 100
- height = 12
- c.addStory(pisa_reportlab.PmlInput(attr.name,
- type=attr.type,
- default=attr.value,
- width=width,
- height=height,
- ))
-
- def end(self, c):
- c.addPara()
- attr = self.attr
- if attr.name:
- self._render(c, attr)
- c.addPara()
-
- class pisaTagTEXTAREA(pisaTagINPUT):
-
- def _render(self, c, attr):
- c.addStory(pisa_reportlab.PmlInput(attr.name,
- default="",
- width=100,
- height=100))
-
- class pisaTagSELECT(pisaTagINPUT):
-
- def start(self, c):
- c.select_options = ["One", "Two", "Three"]
-
- def _render(self, c, attr):
- c.addStory(pisa_reportlab.PmlInput(attr.name,
- type="select",
- default=c.select_options[0],
- options=c.select_options,
- width=100,
- height=40))
- c.select_options = None
-
- class pisaTagOPTION(pisaTag):
-
- pass
-
-# ============================================
-
-class pisaTagPDFNEXTPAGE(pisaTag):
- """
-
- """
- def start(self, c):
- # deprecation("pdf:nextpage")
- c.addPara()
- if self.attr.name:
- c.addStory(NextPageTemplate(self.attr.name))
- c.addStory(PageBreak())
-
-class pisaTagPDFNEXTTEMPLATE(pisaTag):
- """
-
- """
- def start(self, c):
- # deprecation("pdf:frame")
- c.addStory(NextPageTemplate(self.attr["name"]))
-
-class pisaTagPDFNEXTFRAME(pisaTag):
- """
-
- """
- def start(self, c):
- c.addPara()
- c.addStory(FrameBreak())
-
-class pisaTagPDFSPACER(pisaTag):
- """
-
- """
- def start(self, c):
- c.addPara()
- c.addStory(Spacer(1, self.attr.height))
-
-class pisaTagPDFPAGENUMBER(pisaTag):
- """
-
- """
- def start(self, c):
- c.frag.pageNumber = True
- c.addFrag(self.attr.example)
- c.frag.pageNumber = False
-
-class pisaTagPDFTOC(pisaTag):
- """
-
- """
- def end(self, c):
- c.multiBuild = True
- c.addTOC()
-
-class pisaTagPDFFRAME(pisaTag):
- """
-
- """
- def start(self, c):
- deprecation("pdf:frame")
- attrs = self.attr
-
- name = attrs["name"]
- if name is None:
- name = "frame%d" % c.UID()
-
- x, y, w, h = attrs.box
- self.frame = Frame(
- x, y, w, h,
- id=name,
- leftPadding=0,
- rightPadding=0,
- bottomPadding=0,
- topPadding=0,
- showBoundary=attrs.border)
-
- self.static = False
- if self.attr.static:
- self.static = True
- c.addPara()
- self.story = c.swapStory()
- else:
- c.frameList.append(self.frame)
-
- def end(self, c):
- if self.static:
- c.addPara()
- self.frame.pisaStaticStory = c.story
- c.frameStaticList.append(self.frame)
- c.swapStory(self.story)
-
-class pisaTagPDFTEMPLATE(pisaTag):
- """
-
-
-
- """
- def start(self, c):
- deprecation("pdf:template")
- attrs = self.attr
- #print attrs
- name = attrs["name"]
- c.frameList = []
- c.frameStaticList = []
- if c.templateList.has_key(name):
- log.warn(c.warning("template '%s' has already been defined", name))
-
- '''
- self.oldpagesize = A4 # self._pagesize
-
- self._pagesize = PML_PAGESIZES[attrs.format]
- if attrs.orientation is not None:
- if attrs.orientation == "landscape":
- self._pagesize = landscape(self._pagesize)
- elif attrs.orientation == "portrait":
- self._pagesize = portrait(self._pagesize)
- '''
-
- # self._drawing = PmlPageDrawing(self._pagesize)
-
- def end(self, c):
- attrs = self.attr
- name = attrs["name"]
- if len(c.frameList) <= 0:
- log.warn(c.warning("missing frame definitions for template"))
-
- pt = PmlPageTemplate(
- id=name,
- frames=c.frameList,
- pagesize=A4,
- )
- pt.pisaStaticList = c.frameStaticList
- pt.pisaBackgroundList = c.pisaBackgroundList
- pt.pisaBackground = self.attr.background
-
- # self._pagesize)
- # pt.pml_statics = self._statics
- # pt.pml_draw = self._draw
- # pt.pml_drawing = self._drawing
- # pt.pml_background = attrs.background
- # pt.pml_bgstory = self._bgstory
-
- c.templateList[name] = pt
- c.template = None
- c.frameList = []
- c.frameStaticList = []
-
-class pisaTagPDFFONT(pisaTag):
- """
-
- """
- def start(self, c):
- deprecation("pdf:font")
- c.loadFont(self.attr.name, self.attr.src, self.attr.encoding)
-
-class pisaTagPDFBARCODE(pisaTag):
-
- _codeName = {
- "I2OF5": "I2of5",
- "ITF": "I2of5",
- "CODE39": "Standard39",
- "EXTENDEDCODE39": "Extended39",
- "CODE93": "Standard93",
- "EXTENDEDCODE93": "Extended93",
- "MSI": "MSI",
- "CODABAR": "Codabar",
- "NW7": "Codabar",
- "CODE11": "Code11",
- "FIM": "FIM",
- "POSTNET": "POSTNET",
- "USPS4S": "USPS_4State",
- "CODE128": "Code128",
- "EAN13": "EAN13",
- "EAN8": "EAN8",
- }
-
- class _barcodeWrapper(Flowable):
- """Wrapper for barcode widget
- """
- def __init__(self, codeName="Code128", value="", **kw):
- self.widget = createBarcodeDrawing(codeName, value=value, **kw)
-
- def draw(self, canvas, xoffset=0, **kw):
- # NOTE: `canvas' is mutable, so canvas.restoreState() is a MUST.
- canvas.saveState()
- canvas.translate(xoffset, 0)
- self.widget.canv = canvas
- self.widget.draw()
- canvas.restoreState()
-
- def wrap(self, aW, aH):
- return self.widget.wrap(aW, aH)
-
- def start(self, c):
- attr = self.attr
- codeName = attr.type or "Code128"
- codeName = pisaTagPDFBARCODE._codeName[codeName.upper().replace("-", "")]
- humanReadable = bool(attr.humanreadable)
- barWidth = attr.barwidth or 0.01*inch
- barHeight = attr.barheight or 0.5*inch
-
- # Assure minimal size.
- barWidth = max(barWidth, 0.33*mm if codeName in ("EAN13", "EAN8") else 0.0075*inch)
- #barHeight = max(barHeight, 25.93*mm)
-
- barcode = pisaTagPDFBARCODE._barcodeWrapper(
- codeName=codeName,
- value=attr.value,
- barWidth=barWidth,
- barHeight=barHeight,
- humanReadable=humanReadable,
- fontName="Courier",
- )
-
- width, height = barcode.wrap(c.frag.width, c.frag.height)
-
- #barcode.spaceBefore = c.frag.spaceBefore
- #barcode.spaceAfter = c.frag.spaceAfter
-
- c.force = True
-
- valign = attr.align or c.frag.vAlign or "baseline"
- if valign in ["texttop"]:
- valign = "top"
- elif valign in ["absmiddle"]:
- valign = "middle"
- elif valign in ["absbottom", "baseline"]:
- valign = "bottom"
-
- afrag = c.frag.clone()
- afrag.text = ""
- afrag.fontName="Courier"
- afrag.cbDefn = ABag(
- kind="barcode",
- barcode=barcode,
- width=width,
- height=height,
- valign=valign,
- )
- c.fragList.append(afrag)
-
+# -*- coding: utf-8 -*-
+
+# Copyright 2010 Dirk Holtwick, holtwick.it
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__reversion__ = "$Revision: 20 $"
+__author__ = "$Author: holtwick $"
+__date__ = "$Date: 2007-10-09 12:58:24 +0200 (Di, 09 Okt 2007) $"
+
+from pisa_default import DEFAULT_CSS
+from pisa_reportlab import *
+from pisa_util import *
+
+from reportlab.graphics.barcode import createBarcodeDrawing
+from reportlab.lib.styles import ParagraphStyle
+from reportlab.platypus.flowables import *
+from reportlab.platypus.paraparser import tt2ps, ABag
+
+from reportlab_paragraph import cleanBlockQuotedText
+
+import reportlab.lib.utils
+
+import os
+import pprint
+import re
+import warnings
+
+import logging
+log = logging.getLogger("ho.pisa")
+
+def deprecation(message):
+ warnings.warn("<" + message + "> is deprecated!", DeprecationWarning, stacklevel=2)
+
+class pisaTag:
+
+ """
+ The default class for a tag definition
+ """
+
+ def __init__(self, node, attr):
+ self.node = node
+ self.tag = node.tagName
+ self.attr = attr
+
+ def start(self, c):
+ pass
+
+ def end(self, c):
+ pass
+
+class pisaTagBODY(pisaTag):
+
+ """
+ We can also asume that there is a BODY tag because html5lib
+ adds it for us. Here we take the base font size for later calculations
+ in the FONT tag.
+ """
+
+ def start(self, c):
+ c.baseFontSize = c.frag.fontSize
+ # print "base font size", c.baseFontSize
+
+class pisaTagTITLE(pisaTag):
+ def end(self, c):
+ c.meta["title"] = c.text
+ c.clearFrag()
+
+class pisaTagSTYLE(pisaTag):
+ def start(self, c):
+ c.addPara()
+ def end(self, c):
+ c.clearFrag()
+
+class pisaTagMETA(pisaTag):
+ def start(self, c):
+ name = self.attr.name.lower()
+ if name in ("author" , "subject", "keywords"):
+ c.meta[name] = self.attr.content
+
+class pisaTagSUP(pisaTag):
+ def start(self, c):
+ c.frag.super = 1
+
+class pisaTagSUB(pisaTag):
+ def start(self, c):
+ c.frag.sub = 1
+
+class pisaTagA(pisaTag):
+
+ rxLink = re.compile("^(#|[a-z]+\:).*")
+
+ def start(self, c):
+ attr = self.attr
+ # XXX Also support attr.id ?
+ if attr.name:
+ # Important! Make sure that cbDefn is not inherited by other
+ # fragments because of a bug in Reportlab!
+ afrag = c.frag.clone()
+ # These 3 lines are needed to fix an error with non internal fonts
+ afrag.fontName = "Helvetica"
+ afrag.bold = 0
+ afrag.italic = 0
+ afrag.cbDefn = ABag(
+ kind="anchor",
+ name=attr.name,
+ label="anchor")
+ c.fragAnchor.append(afrag)
+ c.anchorName.append(attr.name)
+ if attr.href and self.rxLink.match(attr.href):
+ c.frag.link = attr.href
+
+ def end(self, c):
+ pass
+
+class pisaTagFONT(pisaTag):
+
+ # Source: http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
+
+ def start(self, c):
+ if self.attr["color"] is not None:
+ c.frag.textColor = getColor(self.attr["color"])
+ if self.attr["face"] is not None:
+ c.frag.fontName = c.getFontName(self.attr["face"])
+ if self.attr["size"] is not None:
+ size = getSize(self.attr["size"], c.frag.fontSize, c.baseFontSize)
+ c.frag.fontSize = max(size, 1.0)
+
+ def end(self, c):
+ pass
+
+class pisaTagP(pisaTag):
+ def start(self, c):
+ # save the type of tag; it's used in PmlBaseDoc.afterFlowable()
+ # to check if we need to add an outline-entry
+ # c.frag.tag = self.tag
+ if self.attr.align is not None:
+ #print self.attr.align, getAlign(self.attr.align)
+ c.frag.alignment = getAlign(self.attr.align)
+
+class pisaTagDIV(pisaTagP): pass
+class pisaTagH1(pisaTagP): pass
+class pisaTagH2(pisaTagP): pass
+class pisaTagH3(pisaTagP): pass
+class pisaTagH4(pisaTagP): pass
+class pisaTagH5(pisaTagP): pass
+class pisaTagH6(pisaTagP): pass
+
+def listDecimal(c):
+ c.listCounter += 1
+ return unicode("%d." % c.listCounter)
+
+_bullet = u"\u2022"
+_list_style_type = {
+ "none": u"",
+ "disc": _bullet,
+ "circle": _bullet, # XXX PDF has no equivalent
+ "square": _bullet, # XXX PDF has no equivalent
+ "decimal": listDecimal,
+ "decimal-leading-zero": listDecimal,
+ "lower-roman": listDecimal,
+ "upper-roman": listDecimal,
+ "hebrew": listDecimal,
+ "georgian": listDecimal,
+ "armenian": listDecimal,
+ "cjk-ideographic": listDecimal,
+ "hiragana": listDecimal,
+ "katakana": listDecimal,
+ "hiragana-iroha": listDecimal,
+ "katakana-iroha": listDecimal,
+ "lower-latin": listDecimal,
+ "lower-alpha": listDecimal,
+ "upper-latin": listDecimal,
+ "upper-alpha": listDecimal,
+ "lower-greek": listDecimal,
+}
+
+class pisaTagUL(pisaTagP):
+
+ def start(self, c):
+ self.counter, c.listCounter = c.listCounter, 0
+
+ def end(self, c):
+ c.addPara()
+ # XXX Simulate margin for the moment
+ c.addStory(Spacer(width=1, height=c.fragBlock.spaceAfter))
+ c.listCounter = self.counter
+
+class pisaTagOL(pisaTagUL):
+ pass
+
+class pisaTagLI(pisaTag):
+
+ def start(self, c):
+ lst = _list_style_type.get(c.frag.listStyleType or "disc", _bullet)
+
+ #log.debug("frag %r", c.copyFrag(
+ # text=lst,
+ # bulletFontName=c.getFontName("helvetica"),
+ # fontName=c.getFontName("helvetica")))
+ # c.addFrag("")
+
+ #frag = ParaFrag()
+ #frag.fontName = frag.bulletFontName = c.getFontName("helvetica")
+ #frag.fontSize = c.frag.fontSize
+ #c.frag.fontName = c.getFontName("helvetica")
+
+ frag = copy.copy(c.frag)
+ #print "###", c.frag.fontName
+ #frag.fontName = "au_00" # c.getFontName("helvetica")
+ #frag.bulletFontName = "au_00" # c.getFontName("helvetica")
+
+ self.offset = 0
+ if frag.listStyleImage is not None:
+ frag.text = u""
+ f = frag.listStyleImage
+ if f and (not f.notFound()):
+ img = PmlImage(
+ f.getData(),
+ width=None,
+ height=None)
+ img.drawHeight *= dpi96
+ img.drawWidth *= dpi96
+ img.pisaZoom = frag.zoom
+ img.drawWidth *= img.pisaZoom
+ img.drawHeight *= img.pisaZoom
+ frag.image = img
+ self.offset = max(0, img.drawHeight - c.frag.fontSize)
+ else:
+ if type(lst) == type(u""):
+ frag.text = lst
+ else:
+ # XXX This should be the recent font, but it throws errors in Reportlab!
+ frag.text = lst(c)
+
+ # XXX This should usually be done in the context!!!
+ frag.fontName = frag.bulletFontName = tt2ps(frag.fontName, frag.bold, frag.italic)
+ c.frag.bulletText = [frag]
+
+ def end(self, c):
+ c.fragBlock.spaceBefore += self.offset
+
+ #c.fragBlock.bulletText = self.bulletText
+ #print 999, self.bulletText
+ # c.addPara()
+
+class pisaTagBR(pisaTag):
+
+ def start(self, c):
+ # print "BR", c.text[-40:]
+ c.frag.lineBreak = 1
+ c.addFrag()
+ c.fragStrip = True
+ del c.frag.lineBreak
+ c.force = True
+
+class pisaTagIMG(pisaTag):
+
+ def start(self, c):
+ attr = self.attr
+ if attr.src and (not attr.src.notFound()):
+
+ try:
+ align = attr.align or c.frag.vAlign or "baseline"
+ # print "align", align, attr.align, c.frag.vAlign
+
+ width = c.frag.width
+ height = c.frag.height
+
+ if attr.width:
+ width = attr.width * dpi96
+ if attr.height:
+ height = attr.height * dpi96
+
+ img = PmlImage(
+ attr.src.getData(),
+ width=None,
+ height=None)
+
+ img.pisaZoom = c.frag.zoom
+
+ img.drawHeight *= dpi96
+ img.drawWidth *= dpi96
+
+ if (width is None) and (height is not None):
+ factor = float(height) / img.drawHeight
+ img.drawWidth *= factor
+ img.drawHeight = height
+ elif (height is None) and (width is not None):
+ factor = float(width) / img.drawWidth
+ img.drawHeight *= factor
+ img.drawWidth = width
+ elif (width is not None) and (height is not None):
+ img.drawWidth = width
+ img.drawHeight = height
+
+ img.drawWidth *= img.pisaZoom
+ img.drawHeight *= img.pisaZoom
+
+ img.spaceBefore = c.frag.spaceBefore
+ img.spaceAfter = c.frag.spaceAfter
+
+ # print "image", id(img), img.drawWidth, img.drawHeight
+
+ '''
+ TODO:
+
+ - Apply styles
+ - vspace etc.
+ - Borders
+ - Test inside tables
+ '''
+
+ c.force = True
+ if align in ["left", "right"]:
+
+ c.image = img
+ c.imageData = dict(
+ align=align
+ )
+
+ else:
+
+ # Important! Make sure that cbDefn is not inherited by other
+ # fragments because of a bug in Reportlab!
+ # afrag = c.frag.clone()
+
+ valign = align
+ if valign in ["texttop"]:
+ valign = "top"
+ elif valign in ["absmiddle"]:
+ valign = "middle"
+ elif valign in ["absbottom", "baseline"]:
+ valign = "bottom"
+
+ afrag = c.frag.clone()
+ afrag.text = ""
+ afrag.fontName="Helvetica" # Fix for a nasty bug!!!
+ afrag.cbDefn = ABag(
+ kind="img",
+ image=img, #.getImage(), # XXX Inline?
+ valign=valign,
+ fontName="Helvetica",
+ fontSize=img.drawHeight,
+ width=img.drawWidth,
+ height=img.drawHeight)
+ # print "add frag", id(afrag), img.drawWidth, img.drawHeight
+ c.fragList.append(afrag)
+ c.fontSize = img.drawHeight
+
+ except Exception:
+ log.warn(c.warning("Error in handling image"), exc_info=1)
+ else:
+ log.warn(c.warning("Need a valid file name!"))
+
+class pisaTagHR(pisaTag):
+
+ def start(self, c):
+ c.addPara()
+ c.addStory(HRFlowable(
+ color=self.attr.color,
+ thickness=self.attr.size,
+ width="100%",
+ spaceBefore=c.frag.spaceBefore,
+ spaceAfter=c.frag.spaceAfter
+ ))
+
+# --- Forms
+
+import pisa_reportlab
+
+if 0:
+
+ class pisaTagINPUT(pisaTag):
+
+ def _render(self, c, attr):
+ width = 10
+ height = 10
+ if attr.type == "text":
+ width = 100
+ height = 12
+ c.addStory(pisa_reportlab.PmlInput(attr.name,
+ type=attr.type,
+ default=attr.value,
+ width=width,
+ height=height,
+ ))
+
+ def end(self, c):
+ c.addPara()
+ attr = self.attr
+ if attr.name:
+ self._render(c, attr)
+ c.addPara()
+
+ class pisaTagTEXTAREA(pisaTagINPUT):
+
+ def _render(self, c, attr):
+ c.addStory(pisa_reportlab.PmlInput(attr.name,
+ default="",
+ width=100,
+ height=100))
+
+ class pisaTagSELECT(pisaTagINPUT):
+
+ def start(self, c):
+ c.select_options = ["One", "Two", "Three"]
+
+ def _render(self, c, attr):
+ c.addStory(pisa_reportlab.PmlInput(attr.name,
+ type="select",
+ default=c.select_options[0],
+ options=c.select_options,
+ width=100,
+ height=40))
+ c.select_options = None
+
+ class pisaTagOPTION(pisaTag):
+
+ pass
+
+# ============================================
+
+class pisaTagPDFNEXTPAGE(pisaTag):
+ """
+
+ """
+ def start(self, c):
+ # deprecation("pdf:nextpage")
+ c.addPara()
+ if self.attr.name:
+ c.addStory(NextPageTemplate(self.attr.name))
+ c.addStory(PageBreak())
+
+class pisaTagPDFNEXTTEMPLATE(pisaTag):
+ """
+
+ """
+ def start(self, c):
+ # deprecation("pdf:frame")
+ c.addStory(NextPageTemplate(self.attr["name"]))
+
+class pisaTagPDFNEXTFRAME(pisaTag):
+ """
+
+ """
+ def start(self, c):
+ c.addPara()
+ c.addStory(FrameBreak())
+
+class pisaTagPDFSPACER(pisaTag):
+ """
+
+ """
+ def start(self, c):
+ c.addPara()
+ c.addStory(Spacer(1, self.attr.height))
+
+class pisaTagPDFPAGENUMBER(pisaTag):
+ """
+
+ """
+ def start(self, c):
+ c.frag.pageNumber = True
+ c.frag.offset = self.attr.offset
+ c.addFrag(self.attr.example)
+ c.frag.pageNumber = False
+
+class pisaTagPDFTOC(pisaTag):
+ """
+
+ """
+ def end(self, c):
+ c.multiBuild = True
+ c.addTOC()
+
+class pisaTagPDFFRAME(pisaTag):
+ """
+
+ """
+ def start(self, c):
+ deprecation("pdf:frame")
+ attrs = self.attr
+
+ name = attrs["name"]
+ if name is None:
+ name = "frame%d" % c.UID()
+
+ x, y, w, h = attrs.box
+ self.frame = Frame(
+ x, y, w, h,
+ id=name,
+ leftPadding=0,
+ rightPadding=0,
+ bottomPadding=0,
+ topPadding=0,
+ showBoundary=attrs.border)
+
+ self.static = False
+ if self.attr.static:
+ self.static = True
+ c.addPara()
+ self.story = c.swapStory()
+ else:
+ c.frameList.append(self.frame)
+
+ def end(self, c):
+ if self.static:
+ c.addPara()
+ self.frame.pisaStaticStory = c.story
+ c.frameStaticList.append(self.frame)
+ c.swapStory(self.story)
+
+class pisaTagPDFTEMPLATE(pisaTag):
+ """
+
+
+
+ """
+ def start(self, c):
+ deprecation("pdf:template")
+ attrs = self.attr
+ #print attrs
+ name = attrs["name"]
+ c.frameList = []
+ c.frameStaticList = []
+ if c.templateList.has_key(name):
+ log.warn(c.warning("template '%s' has already been defined", name))
+
+ '''
+ self.oldpagesize = A4 # self._pagesize
+
+ self._pagesize = PML_PAGESIZES[attrs.format]
+ if attrs.orientation is not None:
+ if attrs.orientation == "landscape":
+ self._pagesize = landscape(self._pagesize)
+ elif attrs.orientation == "portrait":
+ self._pagesize = portrait(self._pagesize)
+ '''
+
+ # self._drawing = PmlPageDrawing(self._pagesize)
+
+ def end(self, c):
+ attrs = self.attr
+ name = attrs["name"]
+ if len(c.frameList) <= 0:
+ log.warn(c.warning("missing frame definitions for template"))
+
+ pt = PmlPageTemplate(
+ id=name,
+ frames=c.frameList,
+ pagesize=A4,
+ )
+ pt.pisaStaticList = c.frameStaticList
+ pt.pisaBackgroundList = c.pisaBackgroundList
+ pt.pisaBackground = self.attr.background
+
+ # self._pagesize)
+ # pt.pml_statics = self._statics
+ # pt.pml_draw = self._draw
+ # pt.pml_drawing = self._drawing
+ # pt.pml_background = attrs.background
+ # pt.pml_bgstory = self._bgstory
+
+ c.templateList[name] = pt
+ c.template = None
+ c.frameList = []
+ c.frameStaticList = []
+
+class pisaTagPDFFONT(pisaTag):
+ """
+
+ """
+ def start(self, c):
+ deprecation("pdf:font")
+ c.loadFont(self.attr.name, self.attr.src, self.attr.encoding)
+
+class pisaTagPDFBARCODE(pisaTag):
+ _codeName = {
+ "I2OF5": "I2of5",
+ "ITF": "I2of5",
+ "CODE39": "Standard39",
+ "EXTENDEDCODE39": "Extended39",
+ "CODE93": "Standard93",
+ "EXTENDEDCODE93": "Extended93",
+ "MSI": "MSI",
+ "CODABAR": "Codabar",
+ "NW7": "Codabar",
+ "CODE11": "Code11",
+ "FIM": "FIM",
+ "POSTNET": "POSTNET",
+ "USPS4S": "USPS_4State",
+ "CODE128": "Code128",
+ "EAN13": "EAN13",
+ "EAN8": "EAN8",
+ }
+
+ class _barcodeWrapper(Flowable):
+ """Wrapper for barcode widget
+ """
+ def __init__(self, codeName="Code128", value="", **kw):
+ self.widget = createBarcodeDrawing(codeName, value=value, **kw)
+
+ def draw(self, canvas, xoffset=0, **kw):
+ # NOTE: `canvas' is mutable, so canvas.restoreState() is a MUST.
+ canvas.saveState()
+ canvas.translate(xoffset, 0)
+ self.widget.canv = canvas
+ self.widget.draw()
+ canvas.restoreState()
+
+ def wrap(self, aW, aH):
+ return self.widget.wrap(aW, aH)
+
+ def start(self, c):
+ attr = self.attr
+ codeName = attr.type or "Code128"
+ codeName = pisaTagPDFBARCODE._codeName[codeName.upper().replace("-", "")]
+ humanReadable = bool(attr.humanreadable)
+ barWidth = attr.barwidth or 0.01*inch
+ barHeight = attr.barheight or 0.5*inch
+ fontName = c.getFontName("OCRB10,OCR-B,OCR B,OCRB") # or "Helvetica"
+ fontSize = 2.75*mm
+
+ # Assure minimal size.
+ if codeName in ("EAN13", "EAN8"):
+ barWidth = max(barWidth, 0.264*mm)
+ fontSize = max(fontSize, 2.75*mm)
+ else: # Code39 etc.
+ barWidth = max(barWidth, 0.0075*inch)
+ #barHeight = max(barHeight, 25.93*mm)
+
+ barcode = pisaTagPDFBARCODE._barcodeWrapper(
+ codeName=codeName,
+ value=attr.value,
+ barWidth=barWidth,
+ barHeight=barHeight,
+ humanReadable=humanReadable,
+ fontName=fontName,
+ fontSize=fontSize,
+ )
+
+ width, height = barcode.wrap(c.frag.width, c.frag.height)
+
+ #barcode.spaceBefore = c.frag.spaceBefore
+ #barcode.spaceAfter = c.frag.spaceAfter
+
+ c.force = True
+
+ valign = attr.align or c.frag.vAlign or "baseline"
+ if valign in ["texttop"]:
+ valign = "top"
+ elif valign in ["absmiddle"]:
+ valign = "middle"
+ elif valign in ["absbottom", "baseline"]:
+ valign = "bottom"
+
+ afrag = c.frag.clone()
+ afrag.text = ""
+ afrag.fontName = fontName
+ afrag.cbDefn = ABag(
+ kind="barcode",
+ barcode=barcode,
+ width=width,
+ height=height,
+ valign=valign,
+ )
+ c.fragList.append(afrag)
diff --git a/sx/pisa3/pisa_turbogears.py b/sx/pisa3/pisa_turbogears.py
index 54bcf2b9..d498cd22 100644
--- a/sx/pisa3/pisa_turbogears.py
+++ b/sx/pisa3/pisa_turbogears.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
diff --git a/sx/pisa3/pisa_util.py b/sx/pisa3/pisa_util.py
index 4502e45b..27fa7e55 100644
--- a/sx/pisa3/pisa_util.py
+++ b/sx/pisa3/pisa_util.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
@@ -396,6 +396,8 @@ def __init__(self, buffer="", capacity=CAPACITY):
# Fallback for Google AppEnginge etc.
self._delegate = self.STRATEGIES[0]()
self.write(buffer)
+ # we must set the file's position for preparing to read
+ self.seek(0)
def makeTempFile(self):
" Switch to next startegy. If an error occured stay with the first strategy "
@@ -509,7 +511,7 @@ def __init__(self, uri, basepath=None):
# log.debug("HTTP %r %r %r %r", server, path, uri, r1)
if (r1.status, r1.reason) == (200, "OK"):
# data = r1.read()
- self.mimetype = r1.getheader("Content-Type", None).split(";")[0]
+ self.mimetype = r1.getheader("Content-Type", '').split(";")[0]
self.uri = uri
if r1.getheader("content-encoding") == "gzip":
# zbuf = cStringIO.StringIO(data)
@@ -522,7 +524,7 @@ def __init__(self, uri, basepath=None):
# self.file = urlResponse
else:
urlResponse = urllib2.urlopen(uri)
- self.mimetype = urlResponse.info().get("Content-Type", None).split(";")[0]
+ self.mimetype = urlResponse.info().get("Content-Type", '').split(";")[0]
self.uri = urlResponse.geturl()
self.file = urlResponse
diff --git a/sx/pisa3/pisa_version.py b/sx/pisa3/pisa_version.py
index 2af78e3a..cb14cff6 100644
--- a/sx/pisa3/pisa_version.py
+++ b/sx/pisa3/pisa_version.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
diff --git a/sx/pisa3/pisa_wsgi.py b/sx/pisa3/pisa_wsgi.py
index 78126a58..00621242 100644
--- a/sx/pisa3/pisa_wsgi.py
+++ b/sx/pisa3/pisa_wsgi.py
@@ -1,4 +1,4 @@
-# -*- coding: UTF-8 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
@@ -97,4 +97,4 @@ def filter(self,
]
body = dst.getvalue()
return status, headers, body
-
\ No newline at end of file
+
diff --git a/sx/pisa3/reportlab_paragraph.py b/sx/pisa3/reportlab_paragraph.py
index e8050bd3..fb0c113a 100644
--- a/sx/pisa3/reportlab_paragraph.py
+++ b/sx/pisa3/reportlab_paragraph.py
@@ -1,4 +1,4 @@
-# -*- coding: UTF-8 -*-
+# -*- coding: utf-8 -*-
# Copyright ReportLab Europe Ltd. 2000-2008
# see license.txt for license details
# history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/paragraph.py
diff --git a/sx/w3c/__init__.py b/sx/w3c/__init__.py
index 9769e507..cef81c01 100644
--- a/sx/w3c/__init__.py
+++ b/sx/w3c/__init__.py
@@ -1,12 +1,12 @@
-#!/usr/bin/env python
-##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-##~ Copyright (C) 2002-2004 TechGame Networks, LLC.
-##~
-##~ This library is free software; you can redistribute it and/or
-##~ modify it under the terms of the BSD style License as found in the
-##~ LICENSE file included with this distribution.
-##
-## Modified by Dirk Holtwick , 2007
-##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-
+#!/usr/bin/env python
+##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+##~ Copyright (C) 2002-2004 TechGame Networks, LLC.
+##~
+##~ This library is free software; you can redistribute it and/or
+##~ modify it under the terms of the BSD style License as found in the
+##~ LICENSE file included with this distribution.
+##
+## Modified by Dirk Holtwick , 2007
+##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
diff --git a/sx/w3c/css.py b/sx/w3c/css.py
index 2a71342d..3285bdd3 100644
--- a/sx/w3c/css.py
+++ b/sx/w3c/css.py
@@ -1,767 +1,770 @@
-#!/usr/bin/env python
-##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-##~ Copyright (C) 2002-2004 TechGame Networks, LLC.
-##~
-##~ This library is free software; you can redistribute it and/or
-##~ modify it under the terms of the BSD style License as found in the
-##~ LICENSE file included with this distribution.
-##
-## Modified by Dirk Holtwick , 2007-2008
-##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-"""CSS-2.1 engine
-
-Primary classes:
- * CSSElementInterfaceAbstract
- Provide a concrete implementation for the XML element model used.
-
- * CSSCascadeStrategy
- Implements the CSS-2.1 engine's attribute lookup rules.
-
- * CSSParser
- Parses CSS source forms into usable results using CSSBuilder and
- CSSMutableSelector. You may want to override parseExternal()
-
- * CSSBuilder (and CSSMutableSelector)
- A concrete implementation for cssParser.CSSBuilderAbstract (and
- cssParser.CSSSelectorAbstract) to provide usable results to
- CSSParser requests.
-
-Dependencies:
- python 2.3 (or greater)
- sets, cssParser, re (via cssParser)
-"""
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ Imports
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-import copy
-import sets
-import cssParser
-import cssSpecial
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ Constants / Variables / Etc.
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-CSSParseError = cssParser.CSSParseError
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ Definitions
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSElementInterfaceAbstract(object):
- def getAttr(self, name, default=NotImplemented):
- raise NotImplementedError('Subclass responsibility')
- def getIdAttr(self):
- return self.getAttr('id', '')
- def getClassAttr(self):
- return self.getAttr('class', '')
-
- def getInlineStyle(self):
- raise NotImplementedError('Subclass responsibility')
-
- def matchesNode(self):
- raise NotImplementedError('Subclass responsibility')
-
- def inPseudoState(self, name, params=()):
- raise NotImplementedError('Subclass responsibility')
-
- def iterXMLParents(self):
- """Results must be compatible with CSSElementInterfaceAbstract"""
- raise NotImplementedError('Subclass responsibility')
-
- def getPreviousSibling(self):
- raise NotImplementedError('Subclass responsibility')
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSCascadeStrategy(object):
- author = None
- user = None
- userAgenr = None
-
- def __init__(self, author=None, user=None, userAgent=None):
- if author is not None:
- self.author = author
- if user is not None:
- self.user = user
- if userAgent is not None:
- self.userAgenr = userAgent
-
- def copyWithUpdate(self, author=None, user=None, userAgent=None):
- if author is None:
- author = self.author
- if user is None:
- user = self.user
- if userAgent is None:
- userAgent = self.userAgenr
- return self.__class__(author, user, userAgent)
-
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def iterCSSRulesets(self, inline=None):
- if self.userAgenr is not None:
- yield self.userAgenr[0]
- yield self.userAgenr[1]
-
- if self.user is not None:
- yield self.user[0]
-
- if self.author is not None:
- yield self.author[0]
- yield self.author[1]
-
- if inline:
- yield inline[0]
- yield inline[1]
-
- if self.user is not None:
- yield self.user[1]
-
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def findStyleFor(self, element, attrName, default=NotImplemented):
- """Attempts to find the style setting for attrName in the CSSRulesets.
-
- Note: This method does not attempt to resolve rules that return
- "inherited", "default", or values that have units (including "%").
- This is left up to the client app to re-query the CSS in order to
- implement these semantics.
- """
- rule = self.findCSSRulesFor(element, attrName)
- return self._extractStyleForRule(rule, attrName, default)
-
- def findStylesForEach(self, element, attrNames, default=NotImplemented):
- """Attempts to find the style setting for attrName in the CSSRulesets.
-
- Note: This method does not attempt to resolve rules that return
- "inherited", "default", or values that have units (including "%").
- This is left up to the client app to re-query the CSS in order to
- implement these semantics.
- """
- rules = self.findCSSRulesForEach(element, attrNames)
- return [(attrName, self._extractStyleForRule(rule, attrName, default))
- for attrName, rule in rules.iteritems()]
-
- def findCSSRulesFor(self, element, attrName):
- rules = []
-
- inline = element.getInlineStyle()
- for ruleset in self.iterCSSRulesets(inline):
- rules += ruleset.findCSSRuleFor(element, attrName)
-
- rules.sort()
- return rules
-
- def findCSSRulesForEach(self, element, attrNames):
- rules = dict([(name, []) for name in attrNames])
-
- inline = element.getInlineStyle()
- for ruleset in self.iterCSSRulesets(inline):
- for attrName, attrRules in rules.iteritems():
- attrRules += ruleset.findCSSRuleFor(element, attrName)
-
- for attrRules in rules.itervalues():
- attrRules.sort()
- return rules
-
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def _extractStyleForRule(self, rule, attrName, default=NotImplemented):
- if rule:
- # rule is packed in a list to differentiate from "no rule" vs "rule
- # whose value evalutates as False"
- style = rule[-1][1]
- return style[attrName]
- elif default is not NotImplemented:
- return default
- else:
- raise LookupError("Could not find style for '%s' in %r" % (attrName, rule))
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ CSS Selectors
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSSelectorBase(object):
- inline = False
- _hash = None
- _specificity = None
-
- def __init__(self, completeName='*'):
- if not isinstance(completeName, tuple):
- completeName = (None, '*', completeName)
- self.completeName = completeName
-
- def _updateHash(self):
- self._hash = hash((self.fullName, self.specificity(), self.qualifiers))
- def __hash__(self):
- if self._hash is None:
- return object.__hash__(self)
- else:
- return self._hash
-
- def getNSPrefix(self):
- return self.completeName[0]
- nsPrefix = property(getNSPrefix)
-
- def getName(self):
- return self.completeName[2]
- name = property(getName)
-
- def getNamespace(self):
- return self.completeName[1]
- namespace = property(getNamespace)
-
- def getFullName(self):
- return self.completeName[1:3]
- fullName = property(getFullName)
-
- def __repr__(self):
- strArgs = (self.__class__.__name__,)+self.specificity()+(self.asString(),)
- return '<%s %d:%d:%d:%d %s >' % strArgs
-
- def __str__(self):
- return self.asString()
-
- def __cmp__(self, other):
- result = cmp(self.specificity(), other.specificity())
- if result != 0:
- return result
- result = cmp(self.fullName, other.fullName)
- if result != 0:
- return result
- result = cmp(self.qualifiers, other.qualifiers)
- return result
-
- def specificity(self):
- if self._specificity is None:
- self._specificity = self._calcSpecificity()
- return self._specificity
-
- def _calcSpecificity(self):
- """from http://www.w3.org/TR/CSS21/cascade.html#specificity"""
- hashCount = 0
- qualifierCount = 0
- elementCount = int(self.name != '*')
- for q in self.qualifiers:
- if q.isHash(): hashCount += 1
- elif q.isClass(): qualifierCount += 1
- elif q.isAttr(): qualifierCount += 1
- elif q.isPseudo(): elementCount += 1
- elif q.isCombiner():
- i,h,q,e = q.selector.specificity()
- hashCount += h
- qualifierCount += q
- elementCount += e
- return self.inline, hashCount, qualifierCount, elementCount
-
- def matches(self, element=None):
- if element is None:
- return False
-
- if not element.matchesNode(self.fullName):
- return False
-
- for qualifier in self.qualifiers:
- if not qualifier.matches(element):
- return False
- else:
- return True
-
- def asString(self):
- result = []
- if self.nsPrefix is not None:
- result.append('%s|%s' % (self.nsPrefix, self.name))
- else: result.append(self.name)
-
- for q in self.qualifiers:
- if q.isCombiner():
- result.insert(0, q.asString())
- else:
- result.append(q.asString())
- return ''.join(result)
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSInlineSelector(CSSSelectorBase):
- inline = True
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSMutableSelector(CSSSelectorBase, cssParser.CSSSelectorAbstract):
- qualifiers = []
-
- def asImmutable(self):
- return CSSImmutableSelector(self.completeName, [q.asImmutable() for q in self.qualifiers])
-
- def combineSelectors(klass, selectorA, op, selectorB):
- selectorB.addCombination(op, selectorA)
- return selectorB
- combineSelectors = classmethod(combineSelectors)
-
- def addCombination(self, op, other):
- self._addQualifier(CSSSelectorCombinationQualifier(op, other))
- def addHashId(self, hashId):
- self._addQualifier(CSSSelectorHashQualifier(hashId))
- def addClass(self, class_):
- self._addQualifier(CSSSelectorClassQualifier(class_))
- def addAttribute(self, attrName):
- self._addQualifier(CSSSelectorAttributeQualifier(attrName))
- def addAttributeOperation(self, attrName, op, attrValue):
- self._addQualifier(CSSSelectorAttributeQualifier(attrName, op, attrValue))
- def addPseudo(self, name):
- self._addQualifier(CSSSelectorPseudoQualifier(name))
- def addPseudoFunction(self, name, params):
- self._addQualifier(CSSSelectorPseudoQualifier(name, params))
-
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def _addQualifier(self, qualifier):
- if self.qualifiers:
- self.qualifiers.append(qualifier)
- else:
- self.qualifiers = [qualifier]
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSImmutableSelector(CSSSelectorBase):
- def __init__(self, completeName='*', qualifiers=()):
- # print completeName, qualifiers
- self.qualifiers = tuple(qualifiers)
- CSSSelectorBase.__init__(self, completeName)
- self._updateHash()
-
- def fromSelector(klass, selector):
- return klass(selector.completeName, selector.qualifiers)
- fromSelector = classmethod(fromSelector)
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ CSS Selector Qualifiers -- see CSSImmutableSelector
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSSelectorQualifierBase(object):
- def isHash(self):
- return False
- def isClass(self):
- return False
- def isAttr(self):
- return False
- def isPseudo(self):
- return False
- def isCombiner(self):
- return False
- def asImmutable(self):
- return self
- def __str__(self):
- return self.asString()
-
-class CSSSelectorHashQualifier(CSSSelectorQualifierBase):
- def __init__(self, hashId):
- self.hashId = hashId
- def isHash(self):
- return True
- def __hash__(self):
- return hash((self.hashId,))
- def asString(self):
- return '#'+self.hashId
- def matches(self, element):
- return element.getIdAttr() == self.hashId
-
-class CSSSelectorClassQualifier(CSSSelectorQualifierBase):
- def __init__(self, classId):
- self.classId = classId
- def isClass(self):
- return True
- def __hash__(self):
- return hash((self.classId,))
- def asString(self):
- return '.'+self.classId
- def matches(self, element):
- return self.classId in element.getClassAttr().split()
-
-class CSSSelectorAttributeQualifier(CSSSelectorQualifierBase):
- name, op, value = None, None, NotImplemented
-
- def __init__(self, attrName, op=None, attrValue=NotImplemented):
- self.name = attrName
- if op is not self.op:
- self.op = op
- if attrValue is not self.value:
- self.value = attrValue
- def isAttr(self):
- return True
- def __hash__(self):
- return hash((self.name, self.op, self.value))
- def asString(self):
- if self.value is NotImplemented:
- return '[%s]' % (self.name,)
- else: return '[%s%s%s]' % (self.name, self.op, self.value)
- def matches(self, element):
- op = self.op
- if op is None:
- return element.getAttr(self.name, NotImplemented) != NotImplemented
- elif op == '=':
- return self.value == element.getAttr(self.name, NotImplemented)
- elif op == '~=':
- return self.value in element.getAttr(self.name, '').split()
- elif op == '|=':
- return self.value in element.getAttr(self.name, '').split('-')
- else:
- raise RuntimeError("Unknown operator %r for %r" % (self.op, self))
-
-class CSSSelectorPseudoQualifier(CSSSelectorQualifierBase):
- def __init__(self, attrName, params=()):
- self.name = attrName
- self.params = tuple(params)
- def isPseudo(self):
- return True
- def __hash__(self):
- return hash((self.name, self.params))
- def asString(self):
- if self.params:
- return ':'+self.name
- else:
- return ':%s(%s)' % (self.name, self.params)
- def matches(self, element):
- return element.inPseudoState(self.name, self.params)
-
-class CSSSelectorCombinationQualifier(CSSSelectorQualifierBase):
- def __init__(self, op, selector):
- self.op = op
- self.selector = selector
- def isCombiner(self):
- return True
- def __hash__(self):
- return hash((self.op, self.selector))
- def asImmutable(self):
- return self.__class__(self.op, self.selector.asImmutable())
- def asString(self):
- return '%s%s' % (self.selector.asString(), self.op)
- def matches(self, element):
- op, selector = self.op, self.selector
- if op == ' ':
- for parent in element.iterXMLParents():
- if selector.matches(parent):
- return True
- else:
- return False
- elif op == '>':
- for parent in element.iterXMLParents():
- if selector.matches(parent):
- return True
- else:
- return False
- elif op == '+':
- return selector.matches(element.getPreviousSibling())
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ CSS Misc
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSTerminalFunction(object):
- def __init__(self, name, params):
- self.name = name
- self.params = params
-
- def __repr__(self):
- return '' % (self.name, ', '.join(self.params))
-
-class CSSTerminalOperator(tuple):
- def __new__(klass, *args):
- return tuple.__new__(klass, args)
-
- def __repr__(self):
- return 'op' + tuple.__repr__(self)
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ CSS Objects
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSDeclarations(dict):
- pass
-
-class CSSRuleset(dict):
- def findCSSRulesFor(self, element, attrName):
- ruleResults = []
- append = ruleResults.append
- for nodeFilter, declarations in self.iteritems():
- if (attrName in declarations) and (nodeFilter.matches(element)):
- append((nodeFilter, declarations))
- ruleResults.sort()
- return ruleResults
-
- def findCSSRuleFor(self, *args, **kw):
- # rule is packed in a list to differentiate from "no rule" vs "rule
- # whose value evalutates as False"
- return self.findCSSRulesFor(*args, **kw)[-1:]
-
- def mergeStyles(self, styles):
- " XXX Bugfix for use in PISA "
- for k, v in styles.items():
- if self.has_key(k) and self[k]:
- self[k] = copy.copy(self[k])
- self[k].update(v)
- else:
- self[k] = v
-
-class CSSInlineRuleset(CSSRuleset, CSSDeclarations):
- def findCSSRulesFor(self, element, attrName):
- if attrName in self:
- return [(CSSInlineSelector(), self)]
- else:
- return []
- def findCSSRuleFor(self, *args, **kw):
- # rule is packed in a list to differentiate from "no rule" vs "rule
- # whose value evalutates as False"
- return self.findCSSRulesFor(*args, **kw)[-1:]
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ CSS Builder
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSBuilder(cssParser.CSSBuilderAbstract):
- RulesetFactory = CSSRuleset
- SelectorFactory = CSSMutableSelector
- MediumSetFactory = sets.Set
- DeclarationsFactory = CSSDeclarations
- TermFunctionFactory = CSSTerminalFunction
- TermOperatorFactory = CSSTerminalOperator
- xmlnsSynonyms = {}
- mediumSet = None
- trackImportance = True
- charset = None
-
- def __init__(self, mediumSet=mediumSet, trackImportance=trackImportance):
- self.setMediumSet(mediumSet)
- self.setTrackImportance(trackImportance)
-
- def isValidMedium(self, mediums):
- if not mediums:
- return False
- if 'all' in mediums:
- return True
-
- mediums = self.MediumSetFactory(mediums)
- return bool(self.getMediumSet().intersection(mediums))
-
- def getMediumSet(self):
- return self.mediumSet
- def setMediumSet(self, mediumSet):
- self.mediumSet = self.MediumSetFactory(mediumSet)
- def updateMediumSet(self, mediumSet):
- self.getMediumSet().update(mediumSet)
-
- def getTrackImportance(self):
- return self.trackImportance
- def setTrackImportance(self, trackImportance=True):
- self.trackImportance = trackImportance
-
- #~ helpers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def _pushState(self):
- _restoreState = self.__dict__
- self.__dict__ = self.__dict__.copy()
- self._restoreState = _restoreState
- self.namespaces = {}
- def _popState(self):
- self.__dict__ = self._restoreState
-
- def _declarations(self, declarations, DeclarationsFactory=None):
- DeclarationsFactory = DeclarationsFactory or self.DeclarationsFactory
- if self.trackImportance:
- normal, important = [], []
- for d in declarations:
- if d[-1]:
- important.append(d[:-1])
- else: normal.append(d[:-1])
- return DeclarationsFactory(normal), DeclarationsFactory(important)
- else:
- return DeclarationsFactory(declarations)
-
- def _xmlnsGetSynonym(self, uri):
- # Don't forget to substitute our namespace synonyms!
- return self.xmlnsSynonyms.get(uri or None, uri) or None
-
- #~ css results ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def beginStylesheet(self):
- self._pushState()
- def endStylesheet(self):
- self._popState()
- def stylesheet(self, stylesheetElements, stylesheetImports):
- # XXX Updated for PISA
- if self.trackImportance:
- normal, important = self.RulesetFactory(), self.RulesetFactory()
- for normalStylesheet, importantStylesheet in stylesheetImports:
- normal.mergeStyles(normalStylesheet)
- important.mergeStyles(importantStylesheet)
- for normalStyleElement, importantStyleElement in stylesheetElements:
- normal.mergeStyles(normalStyleElement)
- important.mergeStyles(importantStyleElement)
- return normal, important
- else:
- result = self.RulesetFactory()
- for stylesheet in stylesheetImports:
- result.mergeStyles(stylesheet)
-
- for styleElement in stylesheetElements:
- result.mergeStyles(styleElement)
- return result
-
- def beginInline(self):
- self._pushState()
- def endInline(self):
- self._popState()
-
- def specialRules(self, declarations):
- return cssSpecial.parseSpecialRules(declarations)
-
- def inline(self, declarations):
- declarations = self.specialRules(declarations)
- return self._declarations(declarations, CSSInlineRuleset)
-
- def ruleset(self, selectors, declarations):
-
- # XXX Modified for pisa!
- declarations = self.specialRules(declarations)
- # XXX Modified for pisa!
-
- if self.trackImportance:
- normalDecl, importantDecl = self._declarations(declarations)
- normal, important = self.RulesetFactory(), self.RulesetFactory()
- for s in selectors:
- s = s.asImmutable()
- if normalDecl:
- normal[s] = normalDecl
- if importantDecl:
- important[s] = importantDecl
- return normal, important
- else:
- declarations = self._declarations(declarations)
- result = [(s.asImmutable(), declarations) for s in selectors]
- return self.RulesetFactory(result)
-
- #~ css namespaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def resolveNamespacePrefix(self, nsPrefix, name):
- if nsPrefix == '*':
- return (nsPrefix, '*', name)
- xmlns = self.namespaces.get(nsPrefix, None)
- xmlns = self._xmlnsGetSynonym(xmlns)
- return (nsPrefix, xmlns, name)
-
- #~ css @ directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def atCharset(self, charset):
- self.charset = charset
-
- def atImport(self, import_, mediums, cssParser):
- if self.isValidMedium(mediums):
- return cssParser.parseExternal(import_)
- return None
-
- def atNamespace(self, nsprefix, uri):
- self.namespaces[nsprefix] = uri
-
- def atMedia(self, mediums, ruleset):
- if self.isValidMedium(mediums):
- return ruleset
- return None
-
- def atPage(self, page, pseudopage, declarations):
- return self.ruleset([self.selector('*')], declarations)
-
- def atFontFace(self, declarations):
- return self.ruleset([self.selector('*')], declarations)
-
- def atIdent(self, atIdent, cssParser, src):
- return src, NotImplemented
-
- #~ css selectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def selector(self, name):
- return self.SelectorFactory(name)
-
- def combineSelectors(self, selectorA, op, selectorB):
- return self.SelectorFactory.combineSelectors(selectorA, op, selectorB)
-
- #~ css declarations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def property(self, name, value, important=False):
- if self.trackImportance:
- return (name, value, important)
- else:
- return (name, value)
-
- def combineTerms(self, termA, op, termB):
- if op in (',', ' '):
- if isinstance(termA, list):
- termA.append(termB)
- return termA
- else:
- return [termA, termB]
- elif op is None and termB is None:
- return [termA]
- else:
- if isinstance(termA, list):
- # Bind these "closer" than the list operators -- i.e. work on
- # the (recursively) last element of the list
- termA[-1] = self.combineTerms(termA[-1], op, termB)
- return termA
- else:
- return self.TermOperatorFactory(termA, op, termB)
-
- def termIdent(self, value):
- return value
-
- def termNumber(self, value, units=None):
- if units:
- return value, units
- else:
- return value
-
- def termRGB(self, value):
- return value
-
- def termURI(self, value):
- return value
-
- def termString(self, value):
- return value
-
- def termUnicodeRange(self, value):
- return value
-
- def termFunction(self, name, value):
- return self.TermFunctionFactory(name, value)
-
- def termUnknown(self, src):
- return src, NotImplemented
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ CSS Parser -- finally!
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSParser(cssParser.CSSParser):
- CSSBuilderFactory = CSSBuilder
-
- def __init__(self, cssBuilder=None, create=True, **kw):
- if not cssBuilder and create:
- assert cssBuilder is None
- cssBuilder = self.createCSSBuilder(**kw)
- cssParser.CSSParser.__init__(self, cssBuilder)
-
- def createCSSBuilder(self, **kw):
- return self.CSSBuilderFactory(**kw)
-
- def parseExternal(self, cssResourceName):
- if os.path.isfile(cssResourceName):
- cssFile = file(cssResourceName, 'r')
- return self.parseFile(cssFile, True)
- else:
- raise RuntimeError("Cannot resolve external CSS file: \"%s\"" % cssResourceName)
-
+#!/usr/bin/env python
+##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+##~ Copyright (C) 2002-2004 TechGame Networks, LLC.
+##~
+##~ This library is free software; you can redistribute it and/or
+##~ modify it under the terms of the BSD style License as found in the
+##~ LICENSE file included with this distribution.
+##
+## Modified by Dirk Holtwick , 2007-2008
+##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+"""CSS-2.1 engine
+
+Primary classes:
+ * CSSElementInterfaceAbstract
+ Provide a concrete implementation for the XML element model used.
+
+ * CSSCascadeStrategy
+ Implements the CSS-2.1 engine's attribute lookup rules.
+
+ * CSSParser
+ Parses CSS source forms into usable results using CSSBuilder and
+ CSSMutableSelector. You may want to override parseExternal()
+
+ * CSSBuilder (and CSSMutableSelector)
+ A concrete implementation for cssParser.CSSBuilderAbstract (and
+ cssParser.CSSSelectorAbstract) to provide usable results to
+ CSSParser requests.
+
+Dependencies:
+ python 2.3 (or greater)
+ sets, cssParser, re (via cssParser)
+"""
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Imports
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+import copy
+try:
+ set
+except NameError:
+ from sets import Set as set
+import cssParser
+import cssSpecial
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Constants / Variables / Etc.
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+CSSParseError = cssParser.CSSParseError
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Definitions
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSElementInterfaceAbstract(object):
+ def getAttr(self, name, default=NotImplemented):
+ raise NotImplementedError('Subclass responsibility')
+ def getIdAttr(self):
+ return self.getAttr('id', '')
+ def getClassAttr(self):
+ return self.getAttr('class', '')
+
+ def getInlineStyle(self):
+ raise NotImplementedError('Subclass responsibility')
+
+ def matchesNode(self):
+ raise NotImplementedError('Subclass responsibility')
+
+ def inPseudoState(self, name, params=()):
+ raise NotImplementedError('Subclass responsibility')
+
+ def iterXMLParents(self):
+ """Results must be compatible with CSSElementInterfaceAbstract"""
+ raise NotImplementedError('Subclass responsibility')
+
+ def getPreviousSibling(self):
+ raise NotImplementedError('Subclass responsibility')
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSCascadeStrategy(object):
+ author = None
+ user = None
+ userAgenr = None
+
+ def __init__(self, author=None, user=None, userAgent=None):
+ if author is not None:
+ self.author = author
+ if user is not None:
+ self.user = user
+ if userAgent is not None:
+ self.userAgenr = userAgent
+
+ def copyWithUpdate(self, author=None, user=None, userAgent=None):
+ if author is None:
+ author = self.author
+ if user is None:
+ user = self.user
+ if userAgent is None:
+ userAgent = self.userAgenr
+ return self.__class__(author, user, userAgent)
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def iterCSSRulesets(self, inline=None):
+ if self.userAgenr is not None:
+ yield self.userAgenr[0]
+ yield self.userAgenr[1]
+
+ if self.user is not None:
+ yield self.user[0]
+
+ if self.author is not None:
+ yield self.author[0]
+ yield self.author[1]
+
+ if inline:
+ yield inline[0]
+ yield inline[1]
+
+ if self.user is not None:
+ yield self.user[1]
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def findStyleFor(self, element, attrName, default=NotImplemented):
+ """Attempts to find the style setting for attrName in the CSSRulesets.
+
+ Note: This method does not attempt to resolve rules that return
+ "inherited", "default", or values that have units (including "%").
+ This is left up to the client app to re-query the CSS in order to
+ implement these semantics.
+ """
+ rule = self.findCSSRulesFor(element, attrName)
+ return self._extractStyleForRule(rule, attrName, default)
+
+ def findStylesForEach(self, element, attrNames, default=NotImplemented):
+ """Attempts to find the style setting for attrName in the CSSRulesets.
+
+ Note: This method does not attempt to resolve rules that return
+ "inherited", "default", or values that have units (including "%").
+ This is left up to the client app to re-query the CSS in order to
+ implement these semantics.
+ """
+ rules = self.findCSSRulesForEach(element, attrNames)
+ return [(attrName, self._extractStyleForRule(rule, attrName, default))
+ for attrName, rule in rules.iteritems()]
+
+ def findCSSRulesFor(self, element, attrName):
+ rules = []
+
+ inline = element.getInlineStyle()
+ for ruleset in self.iterCSSRulesets(inline):
+ rules += ruleset.findCSSRuleFor(element, attrName)
+
+ rules.sort()
+ return rules
+
+ def findCSSRulesForEach(self, element, attrNames):
+ rules = dict([(name, []) for name in attrNames])
+
+ inline = element.getInlineStyle()
+ for ruleset in self.iterCSSRulesets(inline):
+ for attrName, attrRules in rules.iteritems():
+ attrRules += ruleset.findCSSRuleFor(element, attrName)
+
+ for attrRules in rules.itervalues():
+ attrRules.sort()
+ return rules
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def _extractStyleForRule(self, rule, attrName, default=NotImplemented):
+ if rule:
+ # rule is packed in a list to differentiate from "no rule" vs "rule
+ # whose value evalutates as False"
+ style = rule[-1][1]
+ return style[attrName]
+ elif default is not NotImplemented:
+ return default
+ else:
+ raise LookupError("Could not find style for '%s' in %r" % (attrName, rule))
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ CSS Selectors
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSSelectorBase(object):
+ inline = False
+ _hash = None
+ _specificity = None
+
+ def __init__(self, completeName='*'):
+ if not isinstance(completeName, tuple):
+ completeName = (None, '*', completeName)
+ self.completeName = completeName
+
+ def _updateHash(self):
+ self._hash = hash((self.fullName, self.specificity(), self.qualifiers))
+ def __hash__(self):
+ if self._hash is None:
+ return object.__hash__(self)
+ else:
+ return self._hash
+
+ def getNSPrefix(self):
+ return self.completeName[0]
+ nsPrefix = property(getNSPrefix)
+
+ def getName(self):
+ return self.completeName[2]
+ name = property(getName)
+
+ def getNamespace(self):
+ return self.completeName[1]
+ namespace = property(getNamespace)
+
+ def getFullName(self):
+ return self.completeName[1:3]
+ fullName = property(getFullName)
+
+ def __repr__(self):
+ strArgs = (self.__class__.__name__,)+self.specificity()+(self.asString(),)
+ return '<%s %d:%d:%d:%d %s >' % strArgs
+
+ def __str__(self):
+ return self.asString()
+
+ def __cmp__(self, other):
+ result = cmp(self.specificity(), other.specificity())
+ if result != 0:
+ return result
+ result = cmp(self.fullName, other.fullName)
+ if result != 0:
+ return result
+ result = cmp(self.qualifiers, other.qualifiers)
+ return result
+
+ def specificity(self):
+ if self._specificity is None:
+ self._specificity = self._calcSpecificity()
+ return self._specificity
+
+ def _calcSpecificity(self):
+ """from http://www.w3.org/TR/CSS21/cascade.html#specificity"""
+ hashCount = 0
+ qualifierCount = 0
+ elementCount = int(self.name != '*')
+ for q in self.qualifiers:
+ if q.isHash(): hashCount += 1
+ elif q.isClass(): qualifierCount += 1
+ elif q.isAttr(): qualifierCount += 1
+ elif q.isPseudo(): elementCount += 1
+ elif q.isCombiner():
+ i,h,q,e = q.selector.specificity()
+ hashCount += h
+ qualifierCount += q
+ elementCount += e
+ return self.inline, hashCount, qualifierCount, elementCount
+
+ def matches(self, element=None):
+ if element is None:
+ return False
+
+ if not element.matchesNode(self.fullName):
+ return False
+
+ for qualifier in self.qualifiers:
+ if not qualifier.matches(element):
+ return False
+ else:
+ return True
+
+ def asString(self):
+ result = []
+ if self.nsPrefix is not None:
+ result.append('%s|%s' % (self.nsPrefix, self.name))
+ else: result.append(self.name)
+
+ for q in self.qualifiers:
+ if q.isCombiner():
+ result.insert(0, q.asString())
+ else:
+ result.append(q.asString())
+ return ''.join(result)
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSInlineSelector(CSSSelectorBase):
+ inline = True
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSMutableSelector(CSSSelectorBase, cssParser.CSSSelectorAbstract):
+ qualifiers = []
+
+ def asImmutable(self):
+ return CSSImmutableSelector(self.completeName, [q.asImmutable() for q in self.qualifiers])
+
+ def combineSelectors(klass, selectorA, op, selectorB):
+ selectorB.addCombination(op, selectorA)
+ return selectorB
+ combineSelectors = classmethod(combineSelectors)
+
+ def addCombination(self, op, other):
+ self._addQualifier(CSSSelectorCombinationQualifier(op, other))
+ def addHashId(self, hashId):
+ self._addQualifier(CSSSelectorHashQualifier(hashId))
+ def addClass(self, class_):
+ self._addQualifier(CSSSelectorClassQualifier(class_))
+ def addAttribute(self, attrName):
+ self._addQualifier(CSSSelectorAttributeQualifier(attrName))
+ def addAttributeOperation(self, attrName, op, attrValue):
+ self._addQualifier(CSSSelectorAttributeQualifier(attrName, op, attrValue))
+ def addPseudo(self, name):
+ self._addQualifier(CSSSelectorPseudoQualifier(name))
+ def addPseudoFunction(self, name, params):
+ self._addQualifier(CSSSelectorPseudoQualifier(name, params))
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def _addQualifier(self, qualifier):
+ if self.qualifiers:
+ self.qualifiers.append(qualifier)
+ else:
+ self.qualifiers = [qualifier]
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSImmutableSelector(CSSSelectorBase):
+ def __init__(self, completeName='*', qualifiers=()):
+ # print completeName, qualifiers
+ self.qualifiers = tuple(qualifiers)
+ CSSSelectorBase.__init__(self, completeName)
+ self._updateHash()
+
+ def fromSelector(klass, selector):
+ return klass(selector.completeName, selector.qualifiers)
+ fromSelector = classmethod(fromSelector)
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ CSS Selector Qualifiers -- see CSSImmutableSelector
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSSelectorQualifierBase(object):
+ def isHash(self):
+ return False
+ def isClass(self):
+ return False
+ def isAttr(self):
+ return False
+ def isPseudo(self):
+ return False
+ def isCombiner(self):
+ return False
+ def asImmutable(self):
+ return self
+ def __str__(self):
+ return self.asString()
+
+class CSSSelectorHashQualifier(CSSSelectorQualifierBase):
+ def __init__(self, hashId):
+ self.hashId = hashId
+ def isHash(self):
+ return True
+ def __hash__(self):
+ return hash((self.hashId,))
+ def asString(self):
+ return '#'+self.hashId
+ def matches(self, element):
+ return element.getIdAttr() == self.hashId
+
+class CSSSelectorClassQualifier(CSSSelectorQualifierBase):
+ def __init__(self, classId):
+ self.classId = classId
+ def isClass(self):
+ return True
+ def __hash__(self):
+ return hash((self.classId,))
+ def asString(self):
+ return '.'+self.classId
+ def matches(self, element):
+ return self.classId in element.getClassAttr().split()
+
+class CSSSelectorAttributeQualifier(CSSSelectorQualifierBase):
+ name, op, value = None, None, NotImplemented
+
+ def __init__(self, attrName, op=None, attrValue=NotImplemented):
+ self.name = attrName
+ if op is not self.op:
+ self.op = op
+ if attrValue is not self.value:
+ self.value = attrValue
+ def isAttr(self):
+ return True
+ def __hash__(self):
+ return hash((self.name, self.op, self.value))
+ def asString(self):
+ if self.value is NotImplemented:
+ return '[%s]' % (self.name,)
+ else: return '[%s%s%s]' % (self.name, self.op, self.value)
+ def matches(self, element):
+ op = self.op
+ if op is None:
+ return element.getAttr(self.name, NotImplemented) != NotImplemented
+ elif op == '=':
+ return self.value == element.getAttr(self.name, NotImplemented)
+ elif op == '~=':
+ return self.value in element.getAttr(self.name, '').split()
+ elif op == '|=':
+ return self.value in element.getAttr(self.name, '').split('-')
+ else:
+ raise RuntimeError("Unknown operator %r for %r" % (self.op, self))
+
+class CSSSelectorPseudoQualifier(CSSSelectorQualifierBase):
+ def __init__(self, attrName, params=()):
+ self.name = attrName
+ self.params = tuple(params)
+ def isPseudo(self):
+ return True
+ def __hash__(self):
+ return hash((self.name, self.params))
+ def asString(self):
+ if self.params:
+ return ':'+self.name
+ else:
+ return ':%s(%s)' % (self.name, self.params)
+ def matches(self, element):
+ return element.inPseudoState(self.name, self.params)
+
+class CSSSelectorCombinationQualifier(CSSSelectorQualifierBase):
+ def __init__(self, op, selector):
+ self.op = op
+ self.selector = selector
+ def isCombiner(self):
+ return True
+ def __hash__(self):
+ return hash((self.op, self.selector))
+ def asImmutable(self):
+ return self.__class__(self.op, self.selector.asImmutable())
+ def asString(self):
+ return '%s%s' % (self.selector.asString(), self.op)
+ def matches(self, element):
+ op, selector = self.op, self.selector
+ if op == ' ':
+ for parent in element.iterXMLParents():
+ if selector.matches(parent):
+ return True
+ else:
+ return False
+ elif op == '>':
+ for parent in element.iterXMLParents():
+ if selector.matches(parent):
+ return True
+ else:
+ return False
+ elif op == '+':
+ return selector.matches(element.getPreviousSibling())
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ CSS Misc
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSTerminalFunction(object):
+ def __init__(self, name, params):
+ self.name = name
+ self.params = params
+
+ def __repr__(self):
+ return '' % (self.name, ', '.join(self.params))
+
+class CSSTerminalOperator(tuple):
+ def __new__(klass, *args):
+ return tuple.__new__(klass, args)
+
+ def __repr__(self):
+ return 'op' + tuple.__repr__(self)
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ CSS Objects
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSDeclarations(dict):
+ pass
+
+class CSSRuleset(dict):
+ def findCSSRulesFor(self, element, attrName):
+ ruleResults = []
+ append = ruleResults.append
+ for nodeFilter, declarations in self.iteritems():
+ if (attrName in declarations) and (nodeFilter.matches(element)):
+ append((nodeFilter, declarations))
+ ruleResults.sort()
+ return ruleResults
+
+ def findCSSRuleFor(self, *args, **kw):
+ # rule is packed in a list to differentiate from "no rule" vs "rule
+ # whose value evalutates as False"
+ return self.findCSSRulesFor(*args, **kw)[-1:]
+
+ def mergeStyles(self, styles):
+ " XXX Bugfix for use in PISA "
+ for k, v in styles.items():
+ if self.has_key(k) and self[k]:
+ self[k] = copy.copy(self[k])
+ self[k].update(v)
+ else:
+ self[k] = v
+
+class CSSInlineRuleset(CSSRuleset, CSSDeclarations):
+ def findCSSRulesFor(self, element, attrName):
+ if attrName in self:
+ return [(CSSInlineSelector(), self)]
+ else:
+ return []
+ def findCSSRuleFor(self, *args, **kw):
+ # rule is packed in a list to differentiate from "no rule" vs "rule
+ # whose value evalutates as False"
+ return self.findCSSRulesFor(*args, **kw)[-1:]
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ CSS Builder
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSBuilder(cssParser.CSSBuilderAbstract):
+ RulesetFactory = CSSRuleset
+ SelectorFactory = CSSMutableSelector
+ MediumSetFactory = set
+ DeclarationsFactory = CSSDeclarations
+ TermFunctionFactory = CSSTerminalFunction
+ TermOperatorFactory = CSSTerminalOperator
+ xmlnsSynonyms = {}
+ mediumSet = None
+ trackImportance = True
+ charset = None
+
+ def __init__(self, mediumSet=mediumSet, trackImportance=trackImportance):
+ self.setMediumSet(mediumSet)
+ self.setTrackImportance(trackImportance)
+
+ def isValidMedium(self, mediums):
+ if not mediums:
+ return False
+ if 'all' in mediums:
+ return True
+
+ mediums = self.MediumSetFactory(mediums)
+ return bool(self.getMediumSet().intersection(mediums))
+
+ def getMediumSet(self):
+ return self.mediumSet
+ def setMediumSet(self, mediumSet):
+ self.mediumSet = self.MediumSetFactory(mediumSet)
+ def updateMediumSet(self, mediumSet):
+ self.getMediumSet().update(mediumSet)
+
+ def getTrackImportance(self):
+ return self.trackImportance
+ def setTrackImportance(self, trackImportance=True):
+ self.trackImportance = trackImportance
+
+ #~ helpers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def _pushState(self):
+ _restoreState = self.__dict__
+ self.__dict__ = self.__dict__.copy()
+ self._restoreState = _restoreState
+ self.namespaces = {}
+ def _popState(self):
+ self.__dict__ = self._restoreState
+
+ def _declarations(self, declarations, DeclarationsFactory=None):
+ DeclarationsFactory = DeclarationsFactory or self.DeclarationsFactory
+ if self.trackImportance:
+ normal, important = [], []
+ for d in declarations:
+ if d[-1]:
+ important.append(d[:-1])
+ else: normal.append(d[:-1])
+ return DeclarationsFactory(normal), DeclarationsFactory(important)
+ else:
+ return DeclarationsFactory(declarations)
+
+ def _xmlnsGetSynonym(self, uri):
+ # Don't forget to substitute our namespace synonyms!
+ return self.xmlnsSynonyms.get(uri or None, uri) or None
+
+ #~ css results ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def beginStylesheet(self):
+ self._pushState()
+ def endStylesheet(self):
+ self._popState()
+ def stylesheet(self, stylesheetElements, stylesheetImports):
+ # XXX Updated for PISA
+ if self.trackImportance:
+ normal, important = self.RulesetFactory(), self.RulesetFactory()
+ for normalStylesheet, importantStylesheet in stylesheetImports:
+ normal.mergeStyles(normalStylesheet)
+ important.mergeStyles(importantStylesheet)
+ for normalStyleElement, importantStyleElement in stylesheetElements:
+ normal.mergeStyles(normalStyleElement)
+ important.mergeStyles(importantStyleElement)
+ return normal, important
+ else:
+ result = self.RulesetFactory()
+ for stylesheet in stylesheetImports:
+ result.mergeStyles(stylesheet)
+
+ for styleElement in stylesheetElements:
+ result.mergeStyles(styleElement)
+ return result
+
+ def beginInline(self):
+ self._pushState()
+ def endInline(self):
+ self._popState()
+
+ def specialRules(self, declarations):
+ return cssSpecial.parseSpecialRules(declarations)
+
+ def inline(self, declarations):
+ declarations = self.specialRules(declarations)
+ return self._declarations(declarations, CSSInlineRuleset)
+
+ def ruleset(self, selectors, declarations):
+
+ # XXX Modified for pisa!
+ declarations = self.specialRules(declarations)
+ # XXX Modified for pisa!
+
+ if self.trackImportance:
+ normalDecl, importantDecl = self._declarations(declarations)
+ normal, important = self.RulesetFactory(), self.RulesetFactory()
+ for s in selectors:
+ s = s.asImmutable()
+ if normalDecl:
+ normal[s] = normalDecl
+ if importantDecl:
+ important[s] = importantDecl
+ return normal, important
+ else:
+ declarations = self._declarations(declarations)
+ result = [(s.asImmutable(), declarations) for s in selectors]
+ return self.RulesetFactory(result)
+
+ #~ css namespaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def resolveNamespacePrefix(self, nsPrefix, name):
+ if nsPrefix == '*':
+ return (nsPrefix, '*', name)
+ xmlns = self.namespaces.get(nsPrefix, None)
+ xmlns = self._xmlnsGetSynonym(xmlns)
+ return (nsPrefix, xmlns, name)
+
+ #~ css @ directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def atCharset(self, charset):
+ self.charset = charset
+
+ def atImport(self, import_, mediums, cssParser):
+ if self.isValidMedium(mediums):
+ return cssParser.parseExternal(import_)
+ return None
+
+ def atNamespace(self, nsprefix, uri):
+ self.namespaces[nsprefix] = uri
+
+ def atMedia(self, mediums, ruleset):
+ if self.isValidMedium(mediums):
+ return ruleset
+ return None
+
+ def atPage(self, page, pseudopage, declarations):
+ return self.ruleset([self.selector('*')], declarations)
+
+ def atFontFace(self, declarations):
+ return self.ruleset([self.selector('*')], declarations)
+
+ def atIdent(self, atIdent, cssParser, src):
+ return src, NotImplemented
+
+ #~ css selectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def selector(self, name):
+ return self.SelectorFactory(name)
+
+ def combineSelectors(self, selectorA, op, selectorB):
+ return self.SelectorFactory.combineSelectors(selectorA, op, selectorB)
+
+ #~ css declarations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def property(self, name, value, important=False):
+ if self.trackImportance:
+ return (name, value, important)
+ else:
+ return (name, value)
+
+ def combineTerms(self, termA, op, termB):
+ if op in (',', ' '):
+ if isinstance(termA, list):
+ termA.append(termB)
+ return termA
+ else:
+ return [termA, termB]
+ elif op is None and termB is None:
+ return [termA]
+ else:
+ if isinstance(termA, list):
+ # Bind these "closer" than the list operators -- i.e. work on
+ # the (recursively) last element of the list
+ termA[-1] = self.combineTerms(termA[-1], op, termB)
+ return termA
+ else:
+ return self.TermOperatorFactory(termA, op, termB)
+
+ def termIdent(self, value):
+ return value
+
+ def termNumber(self, value, units=None):
+ if units:
+ return value, units
+ else:
+ return value
+
+ def termRGB(self, value):
+ return value
+
+ def termURI(self, value):
+ return value
+
+ def termString(self, value):
+ return value
+
+ def termUnicodeRange(self, value):
+ return value
+
+ def termFunction(self, name, value):
+ return self.TermFunctionFactory(name, value)
+
+ def termUnknown(self, src):
+ return src, NotImplemented
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ CSS Parser -- finally!
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSParser(cssParser.CSSParser):
+ CSSBuilderFactory = CSSBuilder
+
+ def __init__(self, cssBuilder=None, create=True, **kw):
+ if not cssBuilder and create:
+ assert cssBuilder is None
+ cssBuilder = self.createCSSBuilder(**kw)
+ cssParser.CSSParser.__init__(self, cssBuilder)
+
+ def createCSSBuilder(self, **kw):
+ return self.CSSBuilderFactory(**kw)
+
+ def parseExternal(self, cssResourceName):
+ if os.path.isfile(cssResourceName):
+ cssFile = file(cssResourceName, 'r')
+ return self.parseFile(cssFile, True)
+ else:
+ raise RuntimeError("Cannot resolve external CSS file: \"%s\"" % cssResourceName)
+
diff --git a/sx/w3c/cssDOMElementInterface.py b/sx/w3c/cssDOMElementInterface.py
index 7e872528..bdd03a1b 100644
--- a/sx/w3c/cssDOMElementInterface.py
+++ b/sx/w3c/cssDOMElementInterface.py
@@ -1,126 +1,126 @@
-#!/usr/bin/env python
-##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-##~ Copyright (C) 2002-2004 TechGame Networks, LLC.
-##~
-##~ This library is free software; you can redistribute it and/or
-##~ modify it under the terms of the BSD style License as found in the
-##~ LICENSE file included with this distribution.
-##
+#!/usr/bin/env python
+##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+##~ Copyright (C) 2002-2004 TechGame Networks, LLC.
+##~
+##~ This library is free software; you can redistribute it and/or
+##~ modify it under the terms of the BSD style License as found in the
+##~ LICENSE file included with this distribution.
+##
## Modified by Dirk Holtwick , 2007-2008
-##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ Imports
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-import css
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ Definitions
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSDOMElementInterface(css.CSSElementInterfaceAbstract):
- """An implementation of css.CSSElementInterfaceAbstract for xml.dom Element Nodes"""
-
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- #~ Constants / Variables / Etc.
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- style = None
-
- _pseudoStateHandlerLookup = {
- 'first-child':
- lambda self: not bool(self.getPreviousSibling()),
- 'not-first-child':
- lambda self: bool(self.getPreviousSibling()),
-
- 'last-child':
- lambda self: not bool(self.getNextSibling()),
- 'not-last-child':
- lambda self: bool(self.getNextSibling()),
-
- 'middle-child':
- lambda self: not bool(self.getPreviousSibling()) and not bool(self.getNextSibling()),
- 'not-middle-child':
- lambda self: bool(self.getPreviousSibling()) or bool(self.getNextSibling()),
-
- # XXX 'first-line':
-
- }
-
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- #~ Definitions
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def __init__(self, domElement, cssParser=None):
- self.domElement = domElement
- # print self.domElement.attributes
- if cssParser is not None:
- self.onCSSParserVisit(cssParser)
-
- def onCSSParserVisit(self, cssParser):
- styleSrc = self.getStyleAttr()
- if styleSrc:
- style = cssParser.parseInline(styleSrc)
- self.setInlineStyle(style)
-
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def matchesNode(self, (namespace, tagName)):
- if tagName not in ('*', self.domElement.tagName):
- return False
- if namespace in (None, '', '*'):
- # matches any namespace
- return True
- else: # full compare
- return namespace == self.domElement.namespaceURI
-
- def getAttr(self, name, default=NotImplemented):
- attrValue = self.domElement.attributes.get(name)
- if attrValue is not None:
- return attrValue.value
- else:
- return default
-
- def getIdAttr(self):
- return self.getAttr('id', '')
- def getClassAttr(self):
- return self.getAttr('class', '')
- def getStyleAttr(self):
- return self.getAttr('style', None)
-
- def inPseudoState(self, name, params=()):
- handler = self._pseudoStateHandlerLookup.get(name, lambda self: False)
- return handler(self)
-
- def iterXMLParents(self, includeSelf=False):
- klass = self.__class__
- current = self.domElement
- if not includeSelf:
- current = current.parentNode
- while (current is not None) and (current.nodeType == current.ELEMENT_NODE):
- yield klass(current)
- current = current.parentNode
-
- def getPreviousSibling(self):
- sibling = self.domElement.previousSibling
- while sibling:
- if sibling.nodeType == sibling.ELEMENT_NODE:
- return sibling
- else:
- sibling = sibling.previousSibling
- return None
- def getNextSibling(self):
- sibling = self.domElement.nextSibling
- while sibling:
- if sibling.nodeType == sibling.ELEMENT_NODE:
- return sibling
- else:
- sibling = sibling.nextSibling
- return None
-
- def getInlineStyle(self):
- return self.style
- def setInlineStyle(self, style):
- self.style = style
-
+##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Imports
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+import css
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Definitions
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSDOMElementInterface(css.CSSElementInterfaceAbstract):
+ """An implementation of css.CSSElementInterfaceAbstract for xml.dom Element Nodes"""
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ #~ Constants / Variables / Etc.
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ style = None
+
+ _pseudoStateHandlerLookup = {
+ 'first-child':
+ lambda self: not bool(self.getPreviousSibling()),
+ 'not-first-child':
+ lambda self: bool(self.getPreviousSibling()),
+
+ 'last-child':
+ lambda self: not bool(self.getNextSibling()),
+ 'not-last-child':
+ lambda self: bool(self.getNextSibling()),
+
+ 'middle-child':
+ lambda self: not bool(self.getPreviousSibling()) and not bool(self.getNextSibling()),
+ 'not-middle-child':
+ lambda self: bool(self.getPreviousSibling()) or bool(self.getNextSibling()),
+
+ # XXX 'first-line':
+
+ }
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ #~ Definitions
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def __init__(self, domElement, cssParser=None):
+ self.domElement = domElement
+ # print self.domElement.attributes
+ if cssParser is not None:
+ self.onCSSParserVisit(cssParser)
+
+ def onCSSParserVisit(self, cssParser):
+ styleSrc = self.getStyleAttr()
+ if styleSrc:
+ style = cssParser.parseInline(styleSrc)
+ self.setInlineStyle(style)
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def matchesNode(self, (namespace, tagName)):
+ if tagName not in ('*', self.domElement.tagName):
+ return False
+ if namespace in (None, '', '*'):
+ # matches any namespace
+ return True
+ else: # full compare
+ return namespace == self.domElement.namespaceURI
+
+ def getAttr(self, name, default=NotImplemented):
+ attrValue = self.domElement.attributes.get(name)
+ if attrValue is not None:
+ return attrValue.value
+ else:
+ return default
+
+ def getIdAttr(self):
+ return self.getAttr('id', '')
+ def getClassAttr(self):
+ return self.getAttr('class', '')
+ def getStyleAttr(self):
+ return self.getAttr('style', None)
+
+ def inPseudoState(self, name, params=()):
+ handler = self._pseudoStateHandlerLookup.get(name, lambda self: False)
+ return handler(self)
+
+ def iterXMLParents(self, includeSelf=False):
+ klass = self.__class__
+ current = self.domElement
+ if not includeSelf:
+ current = current.parentNode
+ while (current is not None) and (current.nodeType == current.ELEMENT_NODE):
+ yield klass(current)
+ current = current.parentNode
+
+ def getPreviousSibling(self):
+ sibling = self.domElement.previousSibling
+ while sibling:
+ if sibling.nodeType == sibling.ELEMENT_NODE:
+ return sibling
+ else:
+ sibling = sibling.previousSibling
+ return None
+ def getNextSibling(self):
+ sibling = self.domElement.nextSibling
+ while sibling:
+ if sibling.nodeType == sibling.ELEMENT_NODE:
+ return sibling
+ else:
+ sibling = sibling.nextSibling
+ return None
+
+ def getInlineStyle(self):
+ return self.style
+ def setInlineStyle(self, style):
+ self.style = style
+
diff --git a/sx/w3c/cssParser.py b/sx/w3c/cssParser.py
index 7d125cc5..b75cc597 100644
--- a/sx/w3c/cssParser.py
+++ b/sx/w3c/cssParser.py
@@ -1,1076 +1,1076 @@
-#!/usr/bin/env python
-##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-##~ Copyright (C) 2002-2004 TechGame Networks, LLC.
-##~
-##~ This library is free software; you can redistribute it and/or
-##~ modify it under the terms of the BSD style License as found in the
-##~ LICENSE file included with this distribution.
-##
-## Modified by Dirk Holtwick , 2007-2008
-##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-"""CSS-2.1 parser.
-
-The CSS 2.1 Specification this parser was derived from can be found at http://www.w3.org/TR/CSS21/
-
-Primary Classes:
- * CSSParser
- Parses CSS source forms into results using a Builder Pattern. Must
- provide concrete implemenation of CSSBuilderAbstract.
-
- * CSSBuilderAbstract
- Outlines the interface between CSSParser and it's rule-builder.
- Compose CSSParser with a concrete implementation of the builder to get
- usable results from the CSS parser.
-
-Dependencies:
- python 2.3 (or greater)
- re
-"""
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ Imports
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-import re
-import cssSpecial
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ Definitions
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-def isAtRuleIdent(src, ident):
- return re.match(r'^@' + ident + r'\s*', src)
-
-def stripAtRuleIdent(src):
- return re.sub(r'^@[a-z\-]+\s*', '', src)
-
-class CSSSelectorAbstract(object):
- """Outlines the interface between CSSParser and it's rule-builder for selectors.
-
- CSSBuilderAbstract.selector and CSSBuilderAbstract.combineSelectors must
- return concrete implementations of this abstract.
-
- See css.CSSMutableSelector for an example implementation.
- """
-
- def addHashId(self, hashId):
- raise NotImplementedError('Subclass responsibility')
- def addClass(self, class_):
- raise NotImplementedError('Subclass responsibility')
- def addAttribute(self, attrName):
- raise NotImplementedError('Subclass responsibility')
- def addAttributeOperation(self, attrName, op, attrValue):
- raise NotImplementedError('Subclass responsibility')
- def addPseudo(self, name):
- raise NotImplementedError('Subclass responsibility')
- def addPseudoFunction(self, name, value):
- raise NotImplementedError('Subclass responsibility')
-
-class CSSBuilderAbstract(object):
- """Outlines the interface between CSSParser and it's rule-builder. Compose
- CSSParser with a concrete implementation of the builder to get usable
- results from the CSS parser.
-
- See css.CSSBuilder for an example implementation
- """
-
- def setCharset(self, charset):
- raise NotImplementedError('Subclass responsibility')
-
- #~ css results ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def beginStylesheet(self):
- raise NotImplementedError('Subclass responsibility')
- def stylesheet(self, elements):
- raise NotImplementedError('Subclass responsibility')
- def endStylesheet(self):
- raise NotImplementedError('Subclass responsibility')
-
- def beginInline(self):
- raise NotImplementedError('Subclass responsibility')
- def inline(self, declarations):
- raise NotImplementedError('Subclass responsibility')
- def endInline(self):
- raise NotImplementedError('Subclass responsibility')
-
- def ruleset(self, selectors, declarations):
- raise NotImplementedError('Subclass responsibility')
-
- #~ css namespaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def resolveNamespacePrefix(self, nsPrefix, name):
- raise NotImplementedError('Subclass responsibility')
-
- #~ css @ directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def atCharset(self, charset):
- raise NotImplementedError('Subclass responsibility')
- def atImport(self, import_, mediums, cssParser):
- raise NotImplementedError('Subclass responsibility')
- def atNamespace(self, nsPrefix, uri):
- raise NotImplementedError('Subclass responsibility')
- def atMedia(self, mediums, ruleset):
- raise NotImplementedError('Subclass responsibility')
- def atPage(self, page, pseudopage, declarations):
- raise NotImplementedError('Subclass responsibility')
- def atFontFace(self, declarations):
- raise NotImplementedError('Subclass responsibility')
- def atIdent(self, atIdent, cssParser, src):
- return src, NotImplemented
-
- #~ css selectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def combineSelectors(self, selectorA, combiner, selectorB):
- """Return value must implement CSSSelectorAbstract"""
- raise NotImplementedError('Subclass responsibility')
- def selector(self, name):
- """Return value must implement CSSSelectorAbstract"""
- raise NotImplementedError('Subclass responsibility')
-
- #~ css declarations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def property(self, name, value, important=False):
- raise NotImplementedError('Subclass responsibility')
- def combineTerms(self, termA, combiner, termB):
- raise NotImplementedError('Subclass responsibility')
- def termIdent(self, value):
- raise NotImplementedError('Subclass responsibility')
- def termNumber(self, value, units=None):
- raise NotImplementedError('Subclass responsibility')
- def termRGB(self, value):
- raise NotImplementedError('Subclass responsibility')
- def termURI(self, value):
- raise NotImplementedError('Subclass responsibility')
- def termString(self, value):
- raise NotImplementedError('Subclass responsibility')
- def termUnicodeRange(self, value):
- raise NotImplementedError('Subclass responsibility')
- def termFunction(self, name, value):
- raise NotImplementedError('Subclass responsibility')
- def termUnknown(self, src):
- raise NotImplementedError('Subclass responsibility')
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#~ CSS Parser
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSParseError(Exception):
- src = None
- ctxsrc = None
- fullsrc = None
- inline = False
- srcCtxIdx = None
- srcFullIdx = None
- ctxsrcFullIdx = None
-
- def __init__(self, msg, src, ctxsrc=None):
- Exception.__init__(self, msg)
- self.src = src
- self.ctxsrc = ctxsrc or src
- if self.ctxsrc:
- self.srcCtxIdx = self.ctxsrc.find(self.src)
- if self.srcCtxIdx < 0:
- del self.srcCtxIdx
-
- def __str__(self):
- if self.ctxsrc:
- return Exception.__str__(self) + ':: (' + repr(self.ctxsrc[:self.srcCtxIdx]) + ', ' + repr(self.ctxsrc[self.srcCtxIdx:self.srcCtxIdx+20]) + ')'
- else:
- return Exception.__str__(self) + ':: ' + repr(self.src[:40])
-
- def setFullCSSSource(self, fullsrc, inline=False):
- self.fullsrc = fullsrc
- if inline:
- self.inline = inline
- if self.fullsrc:
- self.srcFullIdx = self.fullsrc.find(self.src)
- if self.srcFullIdx < 0:
- del self.srcFullIdx
- self.ctxsrcFullIdx = self.fullsrc.find(self.ctxsrc)
- if self.ctxsrcFullIdx < 0:
- del self.ctxsrcFullIdx
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-class CSSParser(object):
- """CSS-2.1 parser dependent only upon the re module.
-
- Implemented directly from http://www.w3.org/TR/CSS21/grammar.html
- Tested with some existing CSS stylesheets for portability.
-
- CSS Parsing API:
- * setCSSBuilder()
- To set your concrete implementation of CSSBuilderAbstract
-
- * parseFile()
- Use to parse external stylesheets using a file-like object
-
- >>> cssFile = open('test.css', 'r')
- >>> stylesheets = myCSSParser.parseFile(cssFile)
-
- * parse()
- Use to parse embedded stylesheets using source string
-
- >>> cssSrc = '''
- body,body.body {
- font: 110%, "Times New Roman", Arial, Verdana, Helvetica, serif;
- background: White;
- color: Black;
- }
- a {text-decoration: underline;}
- '''
- >>> stylesheets = myCSSParser.parse(cssSrc)
-
- * parseInline()
- Use to parse inline stylesheets using attribute source string
-
- >>> style = 'font: 110%, "Times New Roman", Arial, Verdana, Helvetica, serif; background: White; color: Black'
- >>> stylesheets = myCSSParser.parseInline(style)
-
- * parseAttributes()
- Use to parse attribute string values into inline stylesheets
-
- >>> stylesheets = myCSSParser.parseAttributes(
- font='110%, "Times New Roman", Arial, Verdana, Helvetica, serif',
- background='White',
- color='Black')
-
- * parseSingleAttr()
- Use to parse a single string value into a CSS expression
-
- >>> fontValue = myCSSParser.parseSingleAttr('110%, "Times New Roman", Arial, Verdana, Helvetica, serif')
- """
-
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- #~ Constants / Variables / Etc.
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- ParseError = CSSParseError
-
- AttributeOperators = ['=', '~=', '|=', '&=', '^=', '!=', '<>']
- SelectorQualifiers = ('#', '.', '[', ':')
- SelectorCombiners = ['+', '>']
- ExpressionOperators = ('/', '+', ',')
-
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- #~ Regular expressions
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- if True: # makes the following code foldable
- _orRule = lambda *args: '|'.join(args)
- _reflags = re.I | re.M | re.U
- i_hex = '[0-9a-fA-F]'
- i_nonascii = u'[\200-\377]'
- i_unicode = '\\\\(?:%s){1,6}\s?' % i_hex
- i_escape = _orRule(i_unicode, u'\\\\[ -~\200-\377]')
- # i_nmstart = _orRule('[A-Za-z_]', i_nonascii, i_escape)
- i_nmstart = _orRule('\-[^0-9]|[A-Za-z_]', i_nonascii, i_escape) # XXX Added hyphen, http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
- i_nmchar = _orRule('[-0-9A-Za-z_]', i_nonascii, i_escape)
- i_ident = '((?:%s)(?:%s)*)' % (i_nmstart,i_nmchar)
- re_ident = re.compile(i_ident, _reflags)
- i_element_name = '((?:%s)|\*)' % (i_ident[1:-1],)
- re_element_name = re.compile(i_element_name, _reflags)
- i_namespace_selector = '((?:%s)|\*|)\|(?!=)' % (i_ident[1:-1],)
- re_namespace_selector = re.compile(i_namespace_selector, _reflags)
- i_class = '\\.' + i_ident
- re_class = re.compile(i_class, _reflags)
- i_hash = '#((?:%s)+)' % i_nmchar
- re_hash = re.compile(i_hash, _reflags)
- i_rgbcolor = '(#%s{6}|#%s{3})' % (i_hex, i_hex)
- re_rgbcolor = re.compile(i_rgbcolor, _reflags)
- i_nl = u'\n|\r\n|\r|\f'
- i_escape_nl = u'\\\\(?:%s)' % i_nl
- i_string_content = _orRule(u'[\t !#$%&(-~]', i_escape_nl, i_nonascii, i_escape)
- i_string1 = u'\"((?:%s|\')*)\"' % i_string_content
- i_string2 = u'\'((?:%s|\")*)\'' % i_string_content
- i_string = _orRule(i_string1, i_string2)
- re_string = re.compile(i_string, _reflags)
- i_uri = (u'url\\(\s*(?:(?:%s)|((?:%s)+))\s*\\)'
- % (i_string, _orRule('[!#$%&*-~]', i_nonascii, i_escape)))
- # XXX For now
- # i_uri = u'(url\\(.*?\\))'
- re_uri = re.compile(i_uri, _reflags)
- i_num = u'(([-+]?[0-9]+(?:\\.[0-9]+)?)|([-+]?\\.[0-9]+))' # XXX Added out paranthesis, because e.g. .5em was not parsed correctly
- re_num = re.compile(i_num, _reflags)
- i_unit = '(%%|%s)?' % i_ident
- re_unit = re.compile(i_unit, _reflags)
- i_function = i_ident + '\\('
- re_function = re.compile(i_function, _reflags)
- i_functionterm = u'[-+]?' + i_function
- re_functionterm = re.compile(i_functionterm, _reflags)
- i_unicoderange1 = "(?:U\\+%s{1,6}-%s{1,6})" % (i_hex, i_hex)
- i_unicoderange2 = "(?:U\\+\?{1,6}|{h}(\?{0,5}|{h}(\?{0,4}|{h}(\?{0,3}|{h}(\?{0,2}|{h}(\??|{h}))))))"
- i_unicoderange = i_unicoderange1 # u'(%s|%s)' % (i_unicoderange1, i_unicoderange2)
- re_unicoderange = re.compile(i_unicoderange, _reflags)
-
- # i_comment = u'(?:\/\*[^*]*\*+([^/*][^*]*\*+)*\/)|(?://.*)'
- # gabriel: only C convention for comments is allowed in CSS
- i_comment = u'(?:\/\*[^*]*\*+([^/*][^*]*\*+)*\/)'
- re_comment = re.compile(i_comment, _reflags)
- i_important = u'!\s*(important)'
- re_important = re.compile(i_important, _reflags)
- del _orRule
-
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- #~ Public
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def __init__(self, cssBuilder=None):
- self.setCSSBuilder(cssBuilder)
-
- #~ CSS Builder to delegate to ~~~~~~~~~~~~~~~~~~~~~~~~
-
- def getCSSBuilder(self):
- """A concrete instance implementing CSSBuilderAbstract"""
- return self._cssBuilder
- def setCSSBuilder(self, cssBuilder):
- """A concrete instance implementing CSSBuilderAbstract"""
- self._cssBuilder = cssBuilder
- cssBuilder = property(getCSSBuilder, setCSSBuilder)
-
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- #~ Public CSS Parsing API
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def parseFile(self, srcFile, closeFile=False):
- """Parses CSS file-like objects using the current cssBuilder.
- Use for external stylesheets."""
-
- try:
- result = self.parse(srcFile.read())
- finally:
- if closeFile:
- srcFile.close()
- return result
-
- def parse(self, src):
- """Parses CSS string source using the current cssBuilder.
- Use for embedded stylesheets."""
-
- self.cssBuilder.beginStylesheet()
- try:
-
- # XXX Some simple preprocessing
- src = cssSpecial.cleanupCSS(src)
-
- try:
- src, stylesheet = self._parseStylesheet(src)
- except self.ParseError, err:
- err.setFullCSSSource(src)
- raise
- finally:
- self.cssBuilder.endStylesheet()
- return stylesheet
-
- def parseInline(self, src):
- """Parses CSS inline source string using the current cssBuilder.
- Use to parse a tag's 'sytle'-like attribute."""
-
- self.cssBuilder.beginInline()
- try:
- try:
- src, properties = self._parseDeclarationGroup(src.strip(), braces=False)
- except self.ParseError, err:
- err.setFullCSSSource(src, inline=True)
- raise
-
- result = self.cssBuilder.inline(properties)
- finally:
- self.cssBuilder.endInline()
- return result
-
- def parseAttributes(self, attributes={}, **kwAttributes):
- """Parses CSS attribute source strings, and return as an inline stylesheet.
- Use to parse a tag's highly CSS-based attributes like 'font'.
-
- See also: parseSingleAttr
- """
- if attributes:
- kwAttributes.update(attributes)
-
- self.cssBuilder.beginInline()
- try:
- properties = []
- try:
- for propertyName, src in kwAttributes.iteritems():
- src, property = self._parseDeclarationProperty(src.strip(), propertyName)
- properties.append(property)
-
- except self.ParseError, err:
- err.setFullCSSSource(src, inline=True)
- raise
-
- result = self.cssBuilder.inline(properties)
- finally:
- self.cssBuilder.endInline()
- return result
-
- def parseSingleAttr(self, attrValue):
- """Parse a single CSS attribute source string, and returns the built CSS expression.
- Use to parse a tag's highly CSS-based attributes like 'font'.
-
- See also: parseAttributes
- """
-
- results = self.parseAttributes(temp=attrValue)
- if 'temp' in results[1]:
- return results[1]['temp']
- else:
- return results[0]['temp']
-
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- #~ Internal _parse methods
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def _parseStylesheet(self, src):
- """stylesheet
- : [ CHARSET_SYM S* STRING S* ';' ]?
- [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
- [ [ ruleset | media | page | font_face ] [S|CDO|CDC]* ]*
- ;
- """
- # Get rid of the comments
- src = self.re_comment.sub(u'', src)
-
- # [ CHARSET_SYM S* STRING S* ';' ]?
- src = self._parseAtCharset(src)
-
- # [S|CDO|CDC]*
- src = self._parseSCDOCDC(src)
- # [ import [S|CDO|CDC]* ]*
- src, stylesheetImports = self._parseAtImports(src)
-
- # [ namespace [S|CDO|CDC]* ]*
- src = self._parseAtNamespace(src)
-
- stylesheetElements = []
-
- # [ [ ruleset | atkeywords ] [S|CDO|CDC]* ]*
- while src: # due to ending with ]*
- if src.startswith('@'):
- # @media, @page, @font-face
- src, atResults = self._parseAtKeyword(src)
- if atResults is not None:
- stylesheetElements.extend(atResults)
- else:
- # ruleset
- src, ruleset = self._parseRuleset(src)
- stylesheetElements.append(ruleset)
-
- # [S|CDO|CDC]*
- src = self._parseSCDOCDC(src)
-
- stylesheet = self.cssBuilder.stylesheet(stylesheetElements, stylesheetImports)
- return src, stylesheet
-
- def _parseSCDOCDC(self, src):
- """[S|CDO|CDC]*"""
- while 1:
- src = src.lstrip()
- if src.startswith(''):
- src = src[3:]
- else:
- break
- return src
-
- #~ CSS @ directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def _parseAtCharset(self, src):
- """[ CHARSET_SYM S* STRING S* ';' ]?"""
- if isAtRuleIdent(src, 'charset'):
- src = stripAtRuleIdent(src)
- charset, src = self._getString(src)
- src = src.lstrip()
- if src[:1] != ';':
- raise self.ParseError('@charset expected a terminating \';\'', src, ctxsrc)
- src = src[1:].lstrip()
-
- self.cssBuilder.atCharset(charset)
- return src
-
- def _parseAtImports(self, src):
- """[ import [S|CDO|CDC]* ]*"""
- result = []
- while isAtRuleIdent(src, 'import'):
- ctxsrc = src
- src = stripAtRuleIdent(src)
-
- import_, src = self._getStringOrURI(src)
- if import_ is None:
- raise self.ParseError('Import expecting string or url', src, ctxsrc)
-
- mediums = []
- medium, src = self._getIdent(src.lstrip())
- while medium is not None:
- mediums.append(medium)
- if src[:1] == ',':
- src = src[1:].lstrip()
- medium, src = self._getIdent(src)
- else:
- break
-
- # XXX No medium inherits and then "all" is appropriate
- if not mediums:
- mediums = ["all"]
-
- if src[:1] != ';':
- raise self.ParseError('@import expected a terminating \';\'', src, ctxsrc)
- src = src[1:].lstrip()
-
- stylesheet = self.cssBuilder.atImport(import_, mediums, self)
- if stylesheet is not None:
- result.append(stylesheet)
-
- src = self._parseSCDOCDC(src)
- return src, result
-
- def _parseAtNamespace(self, src):
- """namespace :
-
- @namespace S* [IDENT S*]? [STRING|URI] S* ';' S*
- """
-
- src = self._parseSCDOCDC(src)
- while isAtRuleIdent(src, 'namespace'):
- ctxsrc = src
- src = stripAtRuleIdent(src)
-
- namespace, src = self._getStringOrURI(src)
- if namespace is None:
- nsPrefix, src = self._getIdent(src)
- if nsPrefix is None:
- raise self.ParseError('@namespace expected an identifier or a URI', src, ctxsrc)
- namespace, src = self._getStringOrURI(src.lstrip())
- if namespace is None:
- raise self.ParseError('@namespace expected a URI', src, ctxsrc)
- else:
- nsPrefix = None
-
- src = src.lstrip()
- if src[:1] != ';':
- raise self.ParseError('@namespace expected a terminating \';\'', src, ctxsrc)
- src = src[1:].lstrip()
-
- self.cssBuilder.atNamespace(nsPrefix, namespace)
-
- src = self._parseSCDOCDC(src)
- return src
-
- def _parseAtKeyword(self, src):
- """[media | page | font_face | unknown_keyword]"""
- ctxsrc = src
- if isAtRuleIdent(src, 'media'):
- src, result = self._parseAtMedia(src)
- elif isAtRuleIdent(src, 'page'):
- src, result = self._parseAtPage(src)
- elif isAtRuleIdent(src, 'font-face'):
- src, result = self._parseAtFontFace(src)
- # XXX added @import, was missing!
- elif isAtRuleIdent(src, 'import'):
- src, result = self._parseAtImports(src)
- elif isAtRuleIdent(src, 'frame'):
- src, result = self._parseAtFrame(src)
- elif src.startswith('@'):
- src, result = self._parseAtIdent(src)
- else:
- raise self.ParseError('Unknown state in atKeyword', src, ctxsrc)
- return src, result
-
- def _parseAtMedia(self, src):
- """media
- : MEDIA_SYM S* medium [ ',' S* medium ]* '{' S* ruleset* '}' S*
- ;
- """
- ctxsrc = src
- src = src[len('@media '):].lstrip()
- mediums = []
- while src and src[0] != '{':
- medium, src = self._getIdent(src)
- if medium is None:
- raise self.ParseError('@media rule expected media identifier', src, ctxsrc)
- mediums.append(medium)
- if src[0] == ',':
- src = src[1:].lstrip()
- else:
- src = src.lstrip()
-
- if not src.startswith('{'):
- raise self.ParseError('Ruleset opening \'{\' not found', src, ctxsrc)
- src = src[1:].lstrip()
-
- stylesheetElements = []
- #while src and not src.startswith('}'):
- # src, ruleset = self._parseRuleset(src)
- # stylesheetElements.append(ruleset)
- # src = src.lstrip()
-
- # Containing @ where not found and parsed
- while src and not src.startswith('}'):
- if src.startswith('@'):
- # @media, @page, @font-face
- src, atResults = self._parseAtKeyword(src)
- if atResults is not None:
- stylesheetElements.extend(atResults)
- else:
- # ruleset
- src, ruleset = self._parseRuleset(src)
- stylesheetElements.append(ruleset)
- src = src.lstrip()
-
- if not src.startswith('}'):
- raise self.ParseError('Ruleset closing \'}\' not found', src, ctxsrc)
- else:
- src = src[1:].lstrip()
-
- result = self.cssBuilder.atMedia(mediums, stylesheetElements)
- return src, result
-
- def _parseAtPage(self, src):
- """page
- : PAGE_SYM S* IDENT? pseudo_page? S*
- '{' S* declaration [ ';' S* declaration ]* '}' S*
- ;
- """
- ctxsrc = src
- src = src[len('@page '):].lstrip()
- page, src = self._getIdent(src)
- if src[:1] == ':':
- pseudopage, src = self._getIdent(src[1:])
- else:
- pseudopage = None
-
- #src, properties = self._parseDeclarationGroup(src.lstrip())
-
- # Containing @ where not found and parsed
- stylesheetElements = []
- src = src.lstrip()
- properties = []
-
- # XXX Extended for PDF use
- if not src.startswith('{'):
- raise self.ParseError('Ruleset opening \'{\' not found', src, ctxsrc)
- else:
- src = src[1:].lstrip()
-
- while src and not src.startswith('}'):
- if src.startswith('@'):
- # @media, @page, @font-face
- src, atResults = self._parseAtKeyword(src)
- if atResults is not None:
- stylesheetElements.extend(atResults)
- else:
- src, nproperties = self._parseDeclarationGroup(src.lstrip(), braces=False)
- properties += nproperties
- src = src.lstrip()
-
- result = [self.cssBuilder.atPage(page, pseudopage, properties)]
-
- return src[1:].lstrip(), result
-
- def _parseAtFrame(self, src):
- """
- XXX Proprietary for PDF
- """
- ctxsrc = src
- src = src[len('@frame '):].lstrip()
- box, src = self._getIdent(src)
- src, properties = self._parseDeclarationGroup(src.lstrip())
- result = [self.cssBuilder.atFrame(box, properties)]
- return src.lstrip(), result
-
- def _parseAtFontFace(self, src):
- ctxsrc = src
- src = src[len('@font-face '):].lstrip()
- src, properties = self._parseDeclarationGroup(src)
- result = [self.cssBuilder.atFontFace(properties)]
- return src, result
-
- def _parseAtIdent(self, src):
- ctxsrc = src
- atIdent, src = self._getIdent(src[1:])
- if atIdent is None:
- raise self.ParseError('At-rule expected an identifier for the rule', src, ctxsrc)
-
- src, result = self.cssBuilder.atIdent(atIdent, self, src)
-
- if result is NotImplemented:
- # An at-rule consists of everything up to and including the next semicolon (;) or the next block, whichever comes first
-
- semiIdx = src.find(';')
- if semiIdx < 0:
- semiIdx = None
- blockIdx = src[:semiIdx].find('{')
- if blockIdx < 0:
- blockIdx = None
-
- if semiIdx is not None and semiIdx < blockIdx:
- src = src[semiIdx+1:].lstrip()
- elif blockIdx is None:
- # consume the rest of the content since we didn't find a block or a semicolon
- src = src[-1:-1]
- elif blockIdx is not None:
- # expecing a block...
- src = src[blockIdx:]
- try:
- # try to parse it as a declarations block
- src, declarations = self._parseDeclarationGroup(src)
- except self.ParseError:
- # try to parse it as a stylesheet block
- src, stylesheet = self._parseStylesheet(src)
- else:
- raise self.ParserError('Unable to ignore @-rule block', src, ctxsrc)
-
- return src.lstrip(), result
-
- #~ ruleset - see selector and declaration groups ~~~~
-
- def _parseRuleset(self, src):
- """ruleset
- : selector [ ',' S* selector ]*
- '{' S* declaration [ ';' S* declaration ]* '}' S*
- ;
- """
- src, selectors = self._parseSelectorGroup(src)
- src, properties = self._parseDeclarationGroup(src.lstrip())
- result = self.cssBuilder.ruleset(selectors, properties)
- return src, result
-
- #~ selector parsing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def _parseSelectorGroup(self, src):
- selectors = []
- while src[:1] not in ('{','}', ']','(',')', ';', ''):
- src, selector = self._parseSelector(src)
- if selector is None:
- break
- selectors.append(selector)
- if src.startswith(','):
- src = src[1:].lstrip()
- return src, selectors
-
- def _parseSelector(self, src):
- """selector
- : simple_selector [ combinator simple_selector ]*
- ;
- """
- src, selector = self._parseSimpleSelector(src)
- srcLen = len(src) # XXX
- while src[:1] not in ('', ',', ';', '{','}', '[',']','(',')'):
- for combiner in self.SelectorCombiners:
- if src.startswith(combiner):
- src = src[len(combiner):].lstrip()
- break
- else:
- combiner = ' '
- src, selectorB = self._parseSimpleSelector(src)
-
- # XXX Fix a bug that occured here e.g. : .1 {...}
- if len(src) >= srcLen:
- src = src[1:]
- while src and (src[:1] not in ('', ',', ';', '{','}', '[',']','(',')')):
- src = src[1:]
- return src.lstrip(), None
-
- selector = self.cssBuilder.combineSelectors(selector, combiner, selectorB)
-
- return src.lstrip(), selector
-
- def _parseSimpleSelector(self, src):
- """simple_selector
- : [ namespace_selector ]? element_name? [ HASH | class | attrib | pseudo ]* S*
- ;
- """
- ctxsrc = src.lstrip()
- nsPrefix, src = self._getMatchResult(self.re_namespace_selector, src)
- name, src = self._getMatchResult(self.re_element_name, src)
- if name:
- pass # already *successfully* assigned
- elif src[:1] in self.SelectorQualifiers:
- name = '*'
- else:
- raise self.ParseError('Selector name or qualifier expected', src, ctxsrc)
-
- name = self.cssBuilder.resolveNamespacePrefix(nsPrefix, name)
- selector = self.cssBuilder.selector(name)
- while src and src[:1] in self.SelectorQualifiers:
- hash_, src = self._getMatchResult(self.re_hash, src)
- if hash_ is not None:
- selector.addHashId(hash_)
- continue
-
- class_, src = self._getMatchResult(self.re_class, src)
- if class_ is not None:
- selector.addClass(class_)
- continue
-
- if src.startswith('['):
- src, selector = self._parseSelectorAttribute(src, selector)
- elif src.startswith(':'):
- src, selector = self._parseSelectorPseudo(src, selector)
- else:
- break
-
- return src.lstrip(), selector
-
- def _parseSelectorAttribute(self, src, selector):
- """attrib
- : '[' S* [ namespace_selector ]? IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
- [ IDENT | STRING ] S* ]? ']'
- ;
- """
- ctxsrc = src
- if not src.startswith('['):
- raise self.ParseError('Selector Attribute opening \'[\' not found', src, ctxsrc)
- src = src[1:].lstrip()
-
- nsPrefix, src = self._getMatchResult(self.re_namespace_selector, src)
- attrName, src = self._getIdent(src)
-
- src=src.lstrip()
-
- if attrName is None:
- raise self.ParseError('Expected a selector attribute name', src, ctxsrc)
- if nsPrefix is not None:
- attrName = self.cssBuilder.resolveNamespacePrefix(nsPrefix, attrName)
-
- for op in self.AttributeOperators:
- if src.startswith(op):
- break
- else:
- op = ''
- src = src[len(op):].lstrip()
-
- if op:
- attrValue, src = self._getIdent(src)
- if attrValue is None:
- attrValue, src = self._getString(src)
- if attrValue is None:
- raise self.ParseError('Expected a selector attribute value', src, ctxsrc)
- else:
- attrValue = None
-
- if not src.startswith(']'):
- raise self.ParseError('Selector Attribute closing \']\' not found', src, ctxsrc)
- else:
- src = src[1:]
-
- if op:
- selector.addAttributeOperation(attrName, op, attrValue)
- else:
- selector.addAttribute(attrName)
- return src, selector
-
- def _parseSelectorPseudo(self, src, selector):
- """pseudo
- : ':' [ IDENT | function ]
- ;
- """
- ctxsrc = src
- if not src.startswith(':'):
- raise self.ParseError('Selector Pseudo \':\' not found', src, ctxsrc)
- src = src[1:]
-
- name, src = self._getIdent(src)
- if not name:
- raise self.ParseError('Selector Pseudo identifier not found', src, ctxsrc)
-
- if src.startswith('('):
- # function
- src = src[1:].lstrip()
- src, term = self._parseExpression(src, True)
- if not src.startswith(')'):
- raise self.ParseError('Selector Pseudo Function closing \')\' not found', src, ctxsrc)
- src = src[1:]
- selector.addPseudoFunction(name, term)
- else:
- selector.addPseudo(name)
-
- return src, selector
-
- #~ declaration and expression parsing ~~~~~~~~~~~~~~~
-
- def _parseDeclarationGroup(self, src, braces=True):
- ctxsrc = src
- if src.startswith('{'):
- src, braces = src[1:], True
- elif braces:
- raise self.ParseError('Declaration group opening \'{\' not found', src, ctxsrc)
-
- properties = []
- src = src.lstrip()
- while src[:1] not in ('', ',', '{','}', '[',']','(',')','@'): # XXX @?
- src, property = self._parseDeclaration(src)
-
- # XXX Workaround for styles like "*font: smaller"
- if src.startswith("*"):
- src = "-nothing-" + src[1:]
- continue
-
- if property is None:
- break
- properties.append(property)
- if src.startswith(';'):
- src = src[1:].lstrip()
- else:
- break
-
- if braces:
- if not src.startswith('}'):
- raise self.ParseError('Declaration group closing \'}\' not found', src, ctxsrc)
- src = src[1:]
-
- return src.lstrip(), properties
-
- def _parseDeclaration(self, src):
- """declaration
- : ident S* ':' S* expr prio?
- | /* empty */
- ;
- """
- # property
- propertyName, src = self._getIdent(src)
-
- if propertyName is not None:
- src = src.lstrip()
- # S* : S*
- if src[:1] in (':', '='):
- # Note: we are being fairly flexable here... technically, the
- # ":" is *required*, but in the name of flexibility we
- # suppor a null transition, as well as an "=" transition
- src = src[1:].lstrip()
-
- src, property = self._parseDeclarationProperty(src, propertyName)
- else:
- property = None
-
- return src, property
-
- def _parseDeclarationProperty(self, src, propertyName):
- # expr
- src, expr = self._parseExpression(src)
-
- # prio?
- important, src = self._getMatchResult(self.re_important, src)
- src = src.lstrip()
-
- property = self.cssBuilder.property(propertyName, expr, important)
- return src, property
-
- def _parseExpression(self, src, returnList=False):
- """
- expr
- : term [ operator term ]*
- ;
- """
- src, term = self._parseExpressionTerm(src)
- operator = None
- while src[:1] not in ('', ';', '{','}', '[',']', ')'):
- for operator in self.ExpressionOperators:
- if src.startswith(operator):
- src = src[len(operator):]
- break
- else:
- operator = ' '
- src, term2 = self._parseExpressionTerm(src.lstrip())
- if term2 is NotImplemented:
- break
- else:
- term = self.cssBuilder.combineTerms(term, operator, term2)
-
- if operator is None and returnList:
- term = self.cssBuilder.combineTerms(term, None, None)
- return src, term
- else:
- return src, term
-
- def _parseExpressionTerm(self, src):
- """term
- : unary_operator?
- [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
- TIME S* | FREQ S* | function ]
- | STRING S* | IDENT S* | URI S* | RGB S* | UNICODERANGE S* | hexcolor
- ;
- """
- ctxsrc = src
-
- result, src = self._getMatchResult(self.re_num, src)
- if result is not None:
- units, src = self._getMatchResult(self.re_unit, src)
- term = self.cssBuilder.termNumber(result, units)
- return src.lstrip(), term
-
- result, src = self._getString(src, self.re_uri)
- if result is not None:
- # XXX URL!!!!
- term = self.cssBuilder.termURI(result)
- return src.lstrip(), term
-
- result, src = self._getString(src)
- if result is not None:
- term = self.cssBuilder.termString(result)
- return src.lstrip(), term
-
- result, src = self._getMatchResult(self.re_functionterm, src)
- if result is not None:
- src, params = self._parseExpression(src, True)
- if src[0] != ')':
- raise self.ParseError('Terminal function expression expected closing \')\'', src, ctxsrc)
- src = src[1:].lstrip()
- term = self.cssBuilder.termFunction(result, params)
- return src, term
-
- result, src = self._getMatchResult(self.re_rgbcolor, src)
- if result is not None:
- term = self.cssBuilder.termRGB(result)
- return src.lstrip(), term
-
- result, src = self._getMatchResult(self.re_unicoderange, src)
- if result is not None:
- term = self.cssBuilder.termUnicodeRange(result)
- return src.lstrip(), term
-
- nsPrefix, src = self._getMatchResult(self.re_namespace_selector, src)
- result, src = self._getIdent(src)
- if result is not None:
- if nsPrefix is not None:
- result = self.cssBuilder.resolveNamespacePrefix(nsPrefix, result)
- term = self.cssBuilder.termIdent(result)
- return src.lstrip(), term
-
- return self.cssBuilder.termUnknown(src)
-
- #~ utility methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- def _getIdent(self, src, default=None):
- return self._getMatchResult(self.re_ident, src, default)
-
- def _getString(self, src, rexpression=None, default=None):
- if rexpression is None:
- rexpression = self.re_string
- result = rexpression.match(src)
- if result:
- strres = filter(None, result.groups())
- if strres:
- strres = strres[0]
- else:
- strres = ''
- return strres, src[result.end():]
- else:
- return default, src
-
- def _getStringOrURI(self, src):
- result, src = self._getString(src, self.re_uri)
- if result is None:
- result, src = self._getString(src)
- return result, src
-
- def _getMatchResult(self, rexpression, src, default=None, group=1):
- result = rexpression.match(src)
- if result:
- return result.group(group), src[result.end():]
- else:
- return default, src
-
+#!/usr/bin/env python
+##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+##~ Copyright (C) 2002-2004 TechGame Networks, LLC.
+##~
+##~ This library is free software; you can redistribute it and/or
+##~ modify it under the terms of the BSD style License as found in the
+##~ LICENSE file included with this distribution.
+##
+## Modified by Dirk Holtwick , 2007-2008
+##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+"""CSS-2.1 parser.
+
+The CSS 2.1 Specification this parser was derived from can be found at http://www.w3.org/TR/CSS21/
+
+Primary Classes:
+ * CSSParser
+ Parses CSS source forms into results using a Builder Pattern. Must
+ provide concrete implemenation of CSSBuilderAbstract.
+
+ * CSSBuilderAbstract
+ Outlines the interface between CSSParser and it's rule-builder.
+ Compose CSSParser with a concrete implementation of the builder to get
+ usable results from the CSS parser.
+
+Dependencies:
+ python 2.3 (or greater)
+ re
+"""
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Imports
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+import re
+import cssSpecial
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Definitions
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+def isAtRuleIdent(src, ident):
+ return re.match(r'^@' + ident + r'\s*', src)
+
+def stripAtRuleIdent(src):
+ return re.sub(r'^@[a-z\-]+\s*', '', src)
+
+class CSSSelectorAbstract(object):
+ """Outlines the interface between CSSParser and it's rule-builder for selectors.
+
+ CSSBuilderAbstract.selector and CSSBuilderAbstract.combineSelectors must
+ return concrete implementations of this abstract.
+
+ See css.CSSMutableSelector for an example implementation.
+ """
+
+ def addHashId(self, hashId):
+ raise NotImplementedError('Subclass responsibility')
+ def addClass(self, class_):
+ raise NotImplementedError('Subclass responsibility')
+ def addAttribute(self, attrName):
+ raise NotImplementedError('Subclass responsibility')
+ def addAttributeOperation(self, attrName, op, attrValue):
+ raise NotImplementedError('Subclass responsibility')
+ def addPseudo(self, name):
+ raise NotImplementedError('Subclass responsibility')
+ def addPseudoFunction(self, name, value):
+ raise NotImplementedError('Subclass responsibility')
+
+class CSSBuilderAbstract(object):
+ """Outlines the interface between CSSParser and it's rule-builder. Compose
+ CSSParser with a concrete implementation of the builder to get usable
+ results from the CSS parser.
+
+ See css.CSSBuilder for an example implementation
+ """
+
+ def setCharset(self, charset):
+ raise NotImplementedError('Subclass responsibility')
+
+ #~ css results ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def beginStylesheet(self):
+ raise NotImplementedError('Subclass responsibility')
+ def stylesheet(self, elements):
+ raise NotImplementedError('Subclass responsibility')
+ def endStylesheet(self):
+ raise NotImplementedError('Subclass responsibility')
+
+ def beginInline(self):
+ raise NotImplementedError('Subclass responsibility')
+ def inline(self, declarations):
+ raise NotImplementedError('Subclass responsibility')
+ def endInline(self):
+ raise NotImplementedError('Subclass responsibility')
+
+ def ruleset(self, selectors, declarations):
+ raise NotImplementedError('Subclass responsibility')
+
+ #~ css namespaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def resolveNamespacePrefix(self, nsPrefix, name):
+ raise NotImplementedError('Subclass responsibility')
+
+ #~ css @ directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def atCharset(self, charset):
+ raise NotImplementedError('Subclass responsibility')
+ def atImport(self, import_, mediums, cssParser):
+ raise NotImplementedError('Subclass responsibility')
+ def atNamespace(self, nsPrefix, uri):
+ raise NotImplementedError('Subclass responsibility')
+ def atMedia(self, mediums, ruleset):
+ raise NotImplementedError('Subclass responsibility')
+ def atPage(self, page, pseudopage, declarations):
+ raise NotImplementedError('Subclass responsibility')
+ def atFontFace(self, declarations):
+ raise NotImplementedError('Subclass responsibility')
+ def atIdent(self, atIdent, cssParser, src):
+ return src, NotImplemented
+
+ #~ css selectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def combineSelectors(self, selectorA, combiner, selectorB):
+ """Return value must implement CSSSelectorAbstract"""
+ raise NotImplementedError('Subclass responsibility')
+ def selector(self, name):
+ """Return value must implement CSSSelectorAbstract"""
+ raise NotImplementedError('Subclass responsibility')
+
+ #~ css declarations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def property(self, name, value, important=False):
+ raise NotImplementedError('Subclass responsibility')
+ def combineTerms(self, termA, combiner, termB):
+ raise NotImplementedError('Subclass responsibility')
+ def termIdent(self, value):
+ raise NotImplementedError('Subclass responsibility')
+ def termNumber(self, value, units=None):
+ raise NotImplementedError('Subclass responsibility')
+ def termRGB(self, value):
+ raise NotImplementedError('Subclass responsibility')
+ def termURI(self, value):
+ raise NotImplementedError('Subclass responsibility')
+ def termString(self, value):
+ raise NotImplementedError('Subclass responsibility')
+ def termUnicodeRange(self, value):
+ raise NotImplementedError('Subclass responsibility')
+ def termFunction(self, name, value):
+ raise NotImplementedError('Subclass responsibility')
+ def termUnknown(self, src):
+ raise NotImplementedError('Subclass responsibility')
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ CSS Parser
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSParseError(Exception):
+ src = None
+ ctxsrc = None
+ fullsrc = None
+ inline = False
+ srcCtxIdx = None
+ srcFullIdx = None
+ ctxsrcFullIdx = None
+
+ def __init__(self, msg, src, ctxsrc=None):
+ Exception.__init__(self, msg)
+ self.src = src
+ self.ctxsrc = ctxsrc or src
+ if self.ctxsrc:
+ self.srcCtxIdx = self.ctxsrc.find(self.src)
+ if self.srcCtxIdx < 0:
+ del self.srcCtxIdx
+
+ def __str__(self):
+ if self.ctxsrc:
+ return Exception.__str__(self) + ':: (' + repr(self.ctxsrc[:self.srcCtxIdx]) + ', ' + repr(self.ctxsrc[self.srcCtxIdx:self.srcCtxIdx+20]) + ')'
+ else:
+ return Exception.__str__(self) + ':: ' + repr(self.src[:40])
+
+ def setFullCSSSource(self, fullsrc, inline=False):
+ self.fullsrc = fullsrc
+ if inline:
+ self.inline = inline
+ if self.fullsrc:
+ self.srcFullIdx = self.fullsrc.find(self.src)
+ if self.srcFullIdx < 0:
+ del self.srcFullIdx
+ self.ctxsrcFullIdx = self.fullsrc.find(self.ctxsrc)
+ if self.ctxsrcFullIdx < 0:
+ del self.ctxsrcFullIdx
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class CSSParser(object):
+ """CSS-2.1 parser dependent only upon the re module.
+
+ Implemented directly from http://www.w3.org/TR/CSS21/grammar.html
+ Tested with some existing CSS stylesheets for portability.
+
+ CSS Parsing API:
+ * setCSSBuilder()
+ To set your concrete implementation of CSSBuilderAbstract
+
+ * parseFile()
+ Use to parse external stylesheets using a file-like object
+
+ >>> cssFile = open('test.css', 'r')
+ >>> stylesheets = myCSSParser.parseFile(cssFile)
+
+ * parse()
+ Use to parse embedded stylesheets using source string
+
+ >>> cssSrc = '''
+ body,body.body {
+ font: 110%, "Times New Roman", Arial, Verdana, Helvetica, serif;
+ background: White;
+ color: Black;
+ }
+ a {text-decoration: underline;}
+ '''
+ >>> stylesheets = myCSSParser.parse(cssSrc)
+
+ * parseInline()
+ Use to parse inline stylesheets using attribute source string
+
+ >>> style = 'font: 110%, "Times New Roman", Arial, Verdana, Helvetica, serif; background: White; color: Black'
+ >>> stylesheets = myCSSParser.parseInline(style)
+
+ * parseAttributes()
+ Use to parse attribute string values into inline stylesheets
+
+ >>> stylesheets = myCSSParser.parseAttributes(
+ font='110%, "Times New Roman", Arial, Verdana, Helvetica, serif',
+ background='White',
+ color='Black')
+
+ * parseSingleAttr()
+ Use to parse a single string value into a CSS expression
+
+ >>> fontValue = myCSSParser.parseSingleAttr('110%, "Times New Roman", Arial, Verdana, Helvetica, serif')
+ """
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ #~ Constants / Variables / Etc.
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ ParseError = CSSParseError
+
+ AttributeOperators = ['=', '~=', '|=', '&=', '^=', '!=', '<>']
+ SelectorQualifiers = ('#', '.', '[', ':')
+ SelectorCombiners = ['+', '>']
+ ExpressionOperators = ('/', '+', ',')
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ #~ Regular expressions
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ if True: # makes the following code foldable
+ _orRule = lambda *args: '|'.join(args)
+ _reflags = re.I | re.M | re.U
+ i_hex = '[0-9a-fA-F]'
+ i_nonascii = u'[\200-\377]'
+ i_unicode = '\\\\(?:%s){1,6}\s?' % i_hex
+ i_escape = _orRule(i_unicode, u'\\\\[ -~\200-\377]')
+ # i_nmstart = _orRule('[A-Za-z_]', i_nonascii, i_escape)
+ i_nmstart = _orRule('\-[^0-9]|[A-Za-z_]', i_nonascii, i_escape) # XXX Added hyphen, http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ i_nmchar = _orRule('[-0-9A-Za-z_]', i_nonascii, i_escape)
+ i_ident = '((?:%s)(?:%s)*)' % (i_nmstart,i_nmchar)
+ re_ident = re.compile(i_ident, _reflags)
+ i_element_name = '((?:%s)|\*)' % (i_ident[1:-1],)
+ re_element_name = re.compile(i_element_name, _reflags)
+ i_namespace_selector = '((?:%s)|\*|)\|(?!=)' % (i_ident[1:-1],)
+ re_namespace_selector = re.compile(i_namespace_selector, _reflags)
+ i_class = '\\.' + i_ident
+ re_class = re.compile(i_class, _reflags)
+ i_hash = '#((?:%s)+)' % i_nmchar
+ re_hash = re.compile(i_hash, _reflags)
+ i_rgbcolor = '(#%s{6}|#%s{3})' % (i_hex, i_hex)
+ re_rgbcolor = re.compile(i_rgbcolor, _reflags)
+ i_nl = u'\n|\r\n|\r|\f'
+ i_escape_nl = u'\\\\(?:%s)' % i_nl
+ i_string_content = _orRule(u'[\t !#$%&(-~]', i_escape_nl, i_nonascii, i_escape)
+ i_string1 = u'\"((?:%s|\')*)\"' % i_string_content
+ i_string2 = u'\'((?:%s|\")*)\'' % i_string_content
+ i_string = _orRule(i_string1, i_string2)
+ re_string = re.compile(i_string, _reflags)
+ i_uri = (u'url\\(\s*(?:(?:%s)|((?:%s)+))\s*\\)'
+ % (i_string, _orRule('[!#$%&*-~]', i_nonascii, i_escape)))
+ # XXX For now
+ # i_uri = u'(url\\(.*?\\))'
+ re_uri = re.compile(i_uri, _reflags)
+ i_num = u'(([-+]?[0-9]+(?:\\.[0-9]+)?)|([-+]?\\.[0-9]+))' # XXX Added out paranthesis, because e.g. .5em was not parsed correctly
+ re_num = re.compile(i_num, _reflags)
+ i_unit = '(%%|%s)?' % i_ident
+ re_unit = re.compile(i_unit, _reflags)
+ i_function = i_ident + '\\('
+ re_function = re.compile(i_function, _reflags)
+ i_functionterm = u'[-+]?' + i_function
+ re_functionterm = re.compile(i_functionterm, _reflags)
+ i_unicoderange1 = "(?:U\\+%s{1,6}-%s{1,6})" % (i_hex, i_hex)
+ i_unicoderange2 = "(?:U\\+\?{1,6}|{h}(\?{0,5}|{h}(\?{0,4}|{h}(\?{0,3}|{h}(\?{0,2}|{h}(\??|{h}))))))"
+ i_unicoderange = i_unicoderange1 # u'(%s|%s)' % (i_unicoderange1, i_unicoderange2)
+ re_unicoderange = re.compile(i_unicoderange, _reflags)
+
+ # i_comment = u'(?:\/\*[^*]*\*+([^/*][^*]*\*+)*\/)|(?://.*)'
+ # gabriel: only C convention for comments is allowed in CSS
+ i_comment = u'(?:\/\*[^*]*\*+([^/*][^*]*\*+)*\/)'
+ re_comment = re.compile(i_comment, _reflags)
+ i_important = u'!\s*(important)'
+ re_important = re.compile(i_important, _reflags)
+ del _orRule
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ #~ Public
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def __init__(self, cssBuilder=None):
+ self.setCSSBuilder(cssBuilder)
+
+ #~ CSS Builder to delegate to ~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def getCSSBuilder(self):
+ """A concrete instance implementing CSSBuilderAbstract"""
+ return self._cssBuilder
+ def setCSSBuilder(self, cssBuilder):
+ """A concrete instance implementing CSSBuilderAbstract"""
+ self._cssBuilder = cssBuilder
+ cssBuilder = property(getCSSBuilder, setCSSBuilder)
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ #~ Public CSS Parsing API
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def parseFile(self, srcFile, closeFile=False):
+ """Parses CSS file-like objects using the current cssBuilder.
+ Use for external stylesheets."""
+
+ try:
+ result = self.parse(srcFile.read())
+ finally:
+ if closeFile:
+ srcFile.close()
+ return result
+
+ def parse(self, src):
+ """Parses CSS string source using the current cssBuilder.
+ Use for embedded stylesheets."""
+
+ self.cssBuilder.beginStylesheet()
+ try:
+
+ # XXX Some simple preprocessing
+ src = cssSpecial.cleanupCSS(src)
+
+ try:
+ src, stylesheet = self._parseStylesheet(src)
+ except self.ParseError, err:
+ err.setFullCSSSource(src)
+ raise
+ finally:
+ self.cssBuilder.endStylesheet()
+ return stylesheet
+
+ def parseInline(self, src):
+ """Parses CSS inline source string using the current cssBuilder.
+ Use to parse a tag's 'sytle'-like attribute."""
+
+ self.cssBuilder.beginInline()
+ try:
+ try:
+ src, properties = self._parseDeclarationGroup(src.strip(), braces=False)
+ except self.ParseError, err:
+ err.setFullCSSSource(src, inline=True)
+ raise
+
+ result = self.cssBuilder.inline(properties)
+ finally:
+ self.cssBuilder.endInline()
+ return result
+
+ def parseAttributes(self, attributes={}, **kwAttributes):
+ """Parses CSS attribute source strings, and return as an inline stylesheet.
+ Use to parse a tag's highly CSS-based attributes like 'font'.
+
+ See also: parseSingleAttr
+ """
+ if attributes:
+ kwAttributes.update(attributes)
+
+ self.cssBuilder.beginInline()
+ try:
+ properties = []
+ try:
+ for propertyName, src in kwAttributes.iteritems():
+ src, property = self._parseDeclarationProperty(src.strip(), propertyName)
+ properties.append(property)
+
+ except self.ParseError, err:
+ err.setFullCSSSource(src, inline=True)
+ raise
+
+ result = self.cssBuilder.inline(properties)
+ finally:
+ self.cssBuilder.endInline()
+ return result
+
+ def parseSingleAttr(self, attrValue):
+ """Parse a single CSS attribute source string, and returns the built CSS expression.
+ Use to parse a tag's highly CSS-based attributes like 'font'.
+
+ See also: parseAttributes
+ """
+
+ results = self.parseAttributes(temp=attrValue)
+ if 'temp' in results[1]:
+ return results[1]['temp']
+ else:
+ return results[0]['temp']
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ #~ Internal _parse methods
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def _parseStylesheet(self, src):
+ """stylesheet
+ : [ CHARSET_SYM S* STRING S* ';' ]?
+ [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
+ [ [ ruleset | media | page | font_face ] [S|CDO|CDC]* ]*
+ ;
+ """
+ # Get rid of the comments
+ src = self.re_comment.sub(u'', src)
+
+ # [ CHARSET_SYM S* STRING S* ';' ]?
+ src = self._parseAtCharset(src)
+
+ # [S|CDO|CDC]*
+ src = self._parseSCDOCDC(src)
+ # [ import [S|CDO|CDC]* ]*
+ src, stylesheetImports = self._parseAtImports(src)
+
+ # [ namespace [S|CDO|CDC]* ]*
+ src = self._parseAtNamespace(src)
+
+ stylesheetElements = []
+
+ # [ [ ruleset | atkeywords ] [S|CDO|CDC]* ]*
+ while src: # due to ending with ]*
+ if src.startswith('@'):
+ # @media, @page, @font-face
+ src, atResults = self._parseAtKeyword(src)
+ if atResults is not None:
+ stylesheetElements.extend(atResults)
+ else:
+ # ruleset
+ src, ruleset = self._parseRuleset(src)
+ stylesheetElements.append(ruleset)
+
+ # [S|CDO|CDC]*
+ src = self._parseSCDOCDC(src)
+
+ stylesheet = self.cssBuilder.stylesheet(stylesheetElements, stylesheetImports)
+ return src, stylesheet
+
+ def _parseSCDOCDC(self, src):
+ """[S|CDO|CDC]*"""
+ while 1:
+ src = src.lstrip()
+ if src.startswith(''):
+ src = src[3:]
+ else:
+ break
+ return src
+
+ #~ CSS @ directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def _parseAtCharset(self, src):
+ """[ CHARSET_SYM S* STRING S* ';' ]?"""
+ if isAtRuleIdent(src, 'charset'):
+ src = stripAtRuleIdent(src)
+ charset, src = self._getString(src)
+ src = src.lstrip()
+ if src[:1] != ';':
+ raise self.ParseError('@charset expected a terminating \';\'', src, ctxsrc)
+ src = src[1:].lstrip()
+
+ self.cssBuilder.atCharset(charset)
+ return src
+
+ def _parseAtImports(self, src):
+ """[ import [S|CDO|CDC]* ]*"""
+ result = []
+ while isAtRuleIdent(src, 'import'):
+ ctxsrc = src
+ src = stripAtRuleIdent(src)
+
+ import_, src = self._getStringOrURI(src)
+ if import_ is None:
+ raise self.ParseError('Import expecting string or url', src, ctxsrc)
+
+ mediums = []
+ medium, src = self._getIdent(src.lstrip())
+ while medium is not None:
+ mediums.append(medium)
+ if src[:1] == ',':
+ src = src[1:].lstrip()
+ medium, src = self._getIdent(src)
+ else:
+ break
+
+ # XXX No medium inherits and then "all" is appropriate
+ if not mediums:
+ mediums = ["all"]
+
+ if src[:1] != ';':
+ raise self.ParseError('@import expected a terminating \';\'', src, ctxsrc)
+ src = src[1:].lstrip()
+
+ stylesheet = self.cssBuilder.atImport(import_, mediums, self)
+ if stylesheet is not None:
+ result.append(stylesheet)
+
+ src = self._parseSCDOCDC(src)
+ return src, result
+
+ def _parseAtNamespace(self, src):
+ """namespace :
+
+ @namespace S* [IDENT S*]? [STRING|URI] S* ';' S*
+ """
+
+ src = self._parseSCDOCDC(src)
+ while isAtRuleIdent(src, 'namespace'):
+ ctxsrc = src
+ src = stripAtRuleIdent(src)
+
+ namespace, src = self._getStringOrURI(src)
+ if namespace is None:
+ nsPrefix, src = self._getIdent(src)
+ if nsPrefix is None:
+ raise self.ParseError('@namespace expected an identifier or a URI', src, ctxsrc)
+ namespace, src = self._getStringOrURI(src.lstrip())
+ if namespace is None:
+ raise self.ParseError('@namespace expected a URI', src, ctxsrc)
+ else:
+ nsPrefix = None
+
+ src = src.lstrip()
+ if src[:1] != ';':
+ raise self.ParseError('@namespace expected a terminating \';\'', src, ctxsrc)
+ src = src[1:].lstrip()
+
+ self.cssBuilder.atNamespace(nsPrefix, namespace)
+
+ src = self._parseSCDOCDC(src)
+ return src
+
+ def _parseAtKeyword(self, src):
+ """[media | page | font_face | unknown_keyword]"""
+ ctxsrc = src
+ if isAtRuleIdent(src, 'media'):
+ src, result = self._parseAtMedia(src)
+ elif isAtRuleIdent(src, 'page'):
+ src, result = self._parseAtPage(src)
+ elif isAtRuleIdent(src, 'font-face'):
+ src, result = self._parseAtFontFace(src)
+ # XXX added @import, was missing!
+ elif isAtRuleIdent(src, 'import'):
+ src, result = self._parseAtImports(src)
+ elif isAtRuleIdent(src, 'frame'):
+ src, result = self._parseAtFrame(src)
+ elif src.startswith('@'):
+ src, result = self._parseAtIdent(src)
+ else:
+ raise self.ParseError('Unknown state in atKeyword', src, ctxsrc)
+ return src, result
+
+ def _parseAtMedia(self, src):
+ """media
+ : MEDIA_SYM S* medium [ ',' S* medium ]* '{' S* ruleset* '}' S*
+ ;
+ """
+ ctxsrc = src
+ src = src[len('@media '):].lstrip()
+ mediums = []
+ while src and src[0] != '{':
+ medium, src = self._getIdent(src)
+ if medium is None:
+ raise self.ParseError('@media rule expected media identifier', src, ctxsrc)
+ mediums.append(medium)
+ if src[0] == ',':
+ src = src[1:].lstrip()
+ else:
+ src = src.lstrip()
+
+ if not src.startswith('{'):
+ raise self.ParseError('Ruleset opening \'{\' not found', src, ctxsrc)
+ src = src[1:].lstrip()
+
+ stylesheetElements = []
+ #while src and not src.startswith('}'):
+ # src, ruleset = self._parseRuleset(src)
+ # stylesheetElements.append(ruleset)
+ # src = src.lstrip()
+
+ # Containing @ where not found and parsed
+ while src and not src.startswith('}'):
+ if src.startswith('@'):
+ # @media, @page, @font-face
+ src, atResults = self._parseAtKeyword(src)
+ if atResults is not None:
+ stylesheetElements.extend(atResults)
+ else:
+ # ruleset
+ src, ruleset = self._parseRuleset(src)
+ stylesheetElements.append(ruleset)
+ src = src.lstrip()
+
+ if not src.startswith('}'):
+ raise self.ParseError('Ruleset closing \'}\' not found', src, ctxsrc)
+ else:
+ src = src[1:].lstrip()
+
+ result = self.cssBuilder.atMedia(mediums, stylesheetElements)
+ return src, result
+
+ def _parseAtPage(self, src):
+ """page
+ : PAGE_SYM S* IDENT? pseudo_page? S*
+ '{' S* declaration [ ';' S* declaration ]* '}' S*
+ ;
+ """
+ ctxsrc = src
+ src = src[len('@page '):].lstrip()
+ page, src = self._getIdent(src)
+ if src[:1] == ':':
+ pseudopage, src = self._getIdent(src[1:])
+ else:
+ pseudopage = None
+
+ #src, properties = self._parseDeclarationGroup(src.lstrip())
+
+ # Containing @ where not found and parsed
+ stylesheetElements = []
+ src = src.lstrip()
+ properties = []
+
+ # XXX Extended for PDF use
+ if not src.startswith('{'):
+ raise self.ParseError('Ruleset opening \'{\' not found', src, ctxsrc)
+ else:
+ src = src[1:].lstrip()
+
+ while src and not src.startswith('}'):
+ if src.startswith('@'):
+ # @media, @page, @font-face
+ src, atResults = self._parseAtKeyword(src)
+ if atResults is not None:
+ stylesheetElements.extend(atResults)
+ else:
+ src, nproperties = self._parseDeclarationGroup(src.lstrip(), braces=False)
+ properties += nproperties
+ src = src.lstrip()
+
+ result = [self.cssBuilder.atPage(page, pseudopage, properties)]
+
+ return src[1:].lstrip(), result
+
+ def _parseAtFrame(self, src):
+ """
+ XXX Proprietary for PDF
+ """
+ ctxsrc = src
+ src = src[len('@frame '):].lstrip()
+ box, src = self._getIdent(src)
+ src, properties = self._parseDeclarationGroup(src.lstrip())
+ result = [self.cssBuilder.atFrame(box, properties)]
+ return src.lstrip(), result
+
+ def _parseAtFontFace(self, src):
+ ctxsrc = src
+ src = src[len('@font-face '):].lstrip()
+ src, properties = self._parseDeclarationGroup(src)
+ result = [self.cssBuilder.atFontFace(properties)]
+ return src, result
+
+ def _parseAtIdent(self, src):
+ ctxsrc = src
+ atIdent, src = self._getIdent(src[1:])
+ if atIdent is None:
+ raise self.ParseError('At-rule expected an identifier for the rule', src, ctxsrc)
+
+ src, result = self.cssBuilder.atIdent(atIdent, self, src)
+
+ if result is NotImplemented:
+ # An at-rule consists of everything up to and including the next semicolon (;) or the next block, whichever comes first
+
+ semiIdx = src.find(';')
+ if semiIdx < 0:
+ semiIdx = None
+ blockIdx = src[:semiIdx].find('{')
+ if blockIdx < 0:
+ blockIdx = None
+
+ if semiIdx is not None and semiIdx < blockIdx:
+ src = src[semiIdx+1:].lstrip()
+ elif blockIdx is None:
+ # consume the rest of the content since we didn't find a block or a semicolon
+ src = src[-1:-1]
+ elif blockIdx is not None:
+ # expecing a block...
+ src = src[blockIdx:]
+ try:
+ # try to parse it as a declarations block
+ src, declarations = self._parseDeclarationGroup(src)
+ except self.ParseError:
+ # try to parse it as a stylesheet block
+ src, stylesheet = self._parseStylesheet(src)
+ else:
+ raise self.ParserError('Unable to ignore @-rule block', src, ctxsrc)
+
+ return src.lstrip(), result
+
+ #~ ruleset - see selector and declaration groups ~~~~
+
+ def _parseRuleset(self, src):
+ """ruleset
+ : selector [ ',' S* selector ]*
+ '{' S* declaration [ ';' S* declaration ]* '}' S*
+ ;
+ """
+ src, selectors = self._parseSelectorGroup(src)
+ src, properties = self._parseDeclarationGroup(src.lstrip())
+ result = self.cssBuilder.ruleset(selectors, properties)
+ return src, result
+
+ #~ selector parsing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def _parseSelectorGroup(self, src):
+ selectors = []
+ while src[:1] not in ('{','}', ']','(',')', ';', ''):
+ src, selector = self._parseSelector(src)
+ if selector is None:
+ break
+ selectors.append(selector)
+ if src.startswith(','):
+ src = src[1:].lstrip()
+ return src, selectors
+
+ def _parseSelector(self, src):
+ """selector
+ : simple_selector [ combinator simple_selector ]*
+ ;
+ """
+ src, selector = self._parseSimpleSelector(src)
+ srcLen = len(src) # XXX
+ while src[:1] not in ('', ',', ';', '{','}', '[',']','(',')'):
+ for combiner in self.SelectorCombiners:
+ if src.startswith(combiner):
+ src = src[len(combiner):].lstrip()
+ break
+ else:
+ combiner = ' '
+ src, selectorB = self._parseSimpleSelector(src)
+
+ # XXX Fix a bug that occured here e.g. : .1 {...}
+ if len(src) >= srcLen:
+ src = src[1:]
+ while src and (src[:1] not in ('', ',', ';', '{','}', '[',']','(',')')):
+ src = src[1:]
+ return src.lstrip(), None
+
+ selector = self.cssBuilder.combineSelectors(selector, combiner, selectorB)
+
+ return src.lstrip(), selector
+
+ def _parseSimpleSelector(self, src):
+ """simple_selector
+ : [ namespace_selector ]? element_name? [ HASH | class | attrib | pseudo ]* S*
+ ;
+ """
+ ctxsrc = src.lstrip()
+ nsPrefix, src = self._getMatchResult(self.re_namespace_selector, src)
+ name, src = self._getMatchResult(self.re_element_name, src)
+ if name:
+ pass # already *successfully* assigned
+ elif src[:1] in self.SelectorQualifiers:
+ name = '*'
+ else:
+ raise self.ParseError('Selector name or qualifier expected', src, ctxsrc)
+
+ name = self.cssBuilder.resolveNamespacePrefix(nsPrefix, name)
+ selector = self.cssBuilder.selector(name)
+ while src and src[:1] in self.SelectorQualifiers:
+ hash_, src = self._getMatchResult(self.re_hash, src)
+ if hash_ is not None:
+ selector.addHashId(hash_)
+ continue
+
+ class_, src = self._getMatchResult(self.re_class, src)
+ if class_ is not None:
+ selector.addClass(class_)
+ continue
+
+ if src.startswith('['):
+ src, selector = self._parseSelectorAttribute(src, selector)
+ elif src.startswith(':'):
+ src, selector = self._parseSelectorPseudo(src, selector)
+ else:
+ break
+
+ return src.lstrip(), selector
+
+ def _parseSelectorAttribute(self, src, selector):
+ """attrib
+ : '[' S* [ namespace_selector ]? IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
+ [ IDENT | STRING ] S* ]? ']'
+ ;
+ """
+ ctxsrc = src
+ if not src.startswith('['):
+ raise self.ParseError('Selector Attribute opening \'[\' not found', src, ctxsrc)
+ src = src[1:].lstrip()
+
+ nsPrefix, src = self._getMatchResult(self.re_namespace_selector, src)
+ attrName, src = self._getIdent(src)
+
+ src=src.lstrip()
+
+ if attrName is None:
+ raise self.ParseError('Expected a selector attribute name', src, ctxsrc)
+ if nsPrefix is not None:
+ attrName = self.cssBuilder.resolveNamespacePrefix(nsPrefix, attrName)
+
+ for op in self.AttributeOperators:
+ if src.startswith(op):
+ break
+ else:
+ op = ''
+ src = src[len(op):].lstrip()
+
+ if op:
+ attrValue, src = self._getIdent(src)
+ if attrValue is None:
+ attrValue, src = self._getString(src)
+ if attrValue is None:
+ raise self.ParseError('Expected a selector attribute value', src, ctxsrc)
+ else:
+ attrValue = None
+
+ if not src.startswith(']'):
+ raise self.ParseError('Selector Attribute closing \']\' not found', src, ctxsrc)
+ else:
+ src = src[1:]
+
+ if op:
+ selector.addAttributeOperation(attrName, op, attrValue)
+ else:
+ selector.addAttribute(attrName)
+ return src, selector
+
+ def _parseSelectorPseudo(self, src, selector):
+ """pseudo
+ : ':' [ IDENT | function ]
+ ;
+ """
+ ctxsrc = src
+ if not src.startswith(':'):
+ raise self.ParseError('Selector Pseudo \':\' not found', src, ctxsrc)
+ src = src[1:]
+
+ name, src = self._getIdent(src)
+ if not name:
+ raise self.ParseError('Selector Pseudo identifier not found', src, ctxsrc)
+
+ if src.startswith('('):
+ # function
+ src = src[1:].lstrip()
+ src, term = self._parseExpression(src, True)
+ if not src.startswith(')'):
+ raise self.ParseError('Selector Pseudo Function closing \')\' not found', src, ctxsrc)
+ src = src[1:]
+ selector.addPseudoFunction(name, term)
+ else:
+ selector.addPseudo(name)
+
+ return src, selector
+
+ #~ declaration and expression parsing ~~~~~~~~~~~~~~~
+
+ def _parseDeclarationGroup(self, src, braces=True):
+ ctxsrc = src
+ if src.startswith('{'):
+ src, braces = src[1:], True
+ elif braces:
+ raise self.ParseError('Declaration group opening \'{\' not found', src, ctxsrc)
+
+ properties = []
+ src = src.lstrip()
+ while src[:1] not in ('', ',', '{','}', '[',']','(',')','@'): # XXX @?
+ src, property = self._parseDeclaration(src)
+
+ # XXX Workaround for styles like "*font: smaller"
+ if src.startswith("*"):
+ src = "-nothing-" + src[1:]
+ continue
+
+ if property is None:
+ break
+ properties.append(property)
+ if src.startswith(';'):
+ src = src[1:].lstrip()
+ else:
+ break
+
+ if braces:
+ if not src.startswith('}'):
+ raise self.ParseError('Declaration group closing \'}\' not found', src, ctxsrc)
+ src = src[1:]
+
+ return src.lstrip(), properties
+
+ def _parseDeclaration(self, src):
+ """declaration
+ : ident S* ':' S* expr prio?
+ | /* empty */
+ ;
+ """
+ # property
+ propertyName, src = self._getIdent(src)
+
+ if propertyName is not None:
+ src = src.lstrip()
+ # S* : S*
+ if src[:1] in (':', '='):
+ # Note: we are being fairly flexable here... technically, the
+ # ":" is *required*, but in the name of flexibility we
+ # suppor a null transition, as well as an "=" transition
+ src = src[1:].lstrip()
+
+ src, property = self._parseDeclarationProperty(src, propertyName)
+ else:
+ property = None
+
+ return src, property
+
+ def _parseDeclarationProperty(self, src, propertyName):
+ # expr
+ src, expr = self._parseExpression(src)
+
+ # prio?
+ important, src = self._getMatchResult(self.re_important, src)
+ src = src.lstrip()
+
+ property = self.cssBuilder.property(propertyName, expr, important)
+ return src, property
+
+ def _parseExpression(self, src, returnList=False):
+ """
+ expr
+ : term [ operator term ]*
+ ;
+ """
+ src, term = self._parseExpressionTerm(src)
+ operator = None
+ while src[:1] not in ('', ';', '{','}', '[',']', ')'):
+ for operator in self.ExpressionOperators:
+ if src.startswith(operator):
+ src = src[len(operator):]
+ break
+ else:
+ operator = ' '
+ src, term2 = self._parseExpressionTerm(src.lstrip())
+ if term2 is NotImplemented:
+ break
+ else:
+ term = self.cssBuilder.combineTerms(term, operator, term2)
+
+ if operator is None and returnList:
+ term = self.cssBuilder.combineTerms(term, None, None)
+ return src, term
+ else:
+ return src, term
+
+ def _parseExpressionTerm(self, src):
+ """term
+ : unary_operator?
+ [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
+ TIME S* | FREQ S* | function ]
+ | STRING S* | IDENT S* | URI S* | RGB S* | UNICODERANGE S* | hexcolor
+ ;
+ """
+ ctxsrc = src
+
+ result, src = self._getMatchResult(self.re_num, src)
+ if result is not None:
+ units, src = self._getMatchResult(self.re_unit, src)
+ term = self.cssBuilder.termNumber(result, units)
+ return src.lstrip(), term
+
+ result, src = self._getString(src, self.re_uri)
+ if result is not None:
+ # XXX URL!!!!
+ term = self.cssBuilder.termURI(result)
+ return src.lstrip(), term
+
+ result, src = self._getString(src)
+ if result is not None:
+ term = self.cssBuilder.termString(result)
+ return src.lstrip(), term
+
+ result, src = self._getMatchResult(self.re_functionterm, src)
+ if result is not None:
+ src, params = self._parseExpression(src, True)
+ if src[0] != ')':
+ raise self.ParseError('Terminal function expression expected closing \')\'', src, ctxsrc)
+ src = src[1:].lstrip()
+ term = self.cssBuilder.termFunction(result, params)
+ return src, term
+
+ result, src = self._getMatchResult(self.re_rgbcolor, src)
+ if result is not None:
+ term = self.cssBuilder.termRGB(result)
+ return src.lstrip(), term
+
+ result, src = self._getMatchResult(self.re_unicoderange, src)
+ if result is not None:
+ term = self.cssBuilder.termUnicodeRange(result)
+ return src.lstrip(), term
+
+ nsPrefix, src = self._getMatchResult(self.re_namespace_selector, src)
+ result, src = self._getIdent(src)
+ if result is not None:
+ if nsPrefix is not None:
+ result = self.cssBuilder.resolveNamespacePrefix(nsPrefix, result)
+ term = self.cssBuilder.termIdent(result)
+ return src.lstrip(), term
+
+ return self.cssBuilder.termUnknown(src)
+
+ #~ utility methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def _getIdent(self, src, default=None):
+ return self._getMatchResult(self.re_ident, src, default)
+
+ def _getString(self, src, rexpression=None, default=None):
+ if rexpression is None:
+ rexpression = self.re_string
+ result = rexpression.match(src)
+ if result:
+ strres = filter(None, result.groups())
+ if strres:
+ strres = strres[0]
+ else:
+ strres = ''
+ return strres, src[result.end():]
+ else:
+ return default, src
+
+ def _getStringOrURI(self, src):
+ result, src = self._getString(src, self.re_uri)
+ if result is None:
+ result, src = self._getString(src)
+ return result, src
+
+ def _getMatchResult(self, rexpression, src, default=None, group=1):
+ result = rexpression.match(src)
+ if result:
+ return result.group(group), src[result.end():]
+ else:
+ return default, src
+
diff --git a/sx/w3c/cssSpecial.py b/sx/w3c/cssSpecial.py
index 26aff0f6..7fd9167b 100644
--- a/sx/w3c/cssSpecial.py
+++ b/sx/w3c/cssSpecial.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
diff --git a/test/cookbook.py b/test/cookbook.py
index 00b02ee0..91118a34 100644
--- a/test/cookbook.py
+++ b/test/cookbook.py
@@ -1,4 +1,4 @@
-# -*- coding: ISO-8859-1 -*-
+# -*- coding: utf-8 -*-
# Copyright 2010 Dirk Holtwick, holtwick.it
#
diff --git a/test/css/reset.css b/test/css/reset.css
index 646c9bc9..1b0c5ee5 100644
--- a/test/css/reset.css
+++ b/test/css/reset.css
@@ -1,217 +1,217 @@
-* {
- margin: 0;
- padding: 0;
- font-weight: normal;
- font-style: inherit;
- font-size: 100%;
- font-family: inherit;
- /* vertical-align: baseline; */
- list-style: none;
-}
-
-@media screen {
- html, body {
- height:100%;
- }
-}
-
-img,
-fieldset,
-legend,
-table,
-tr,
-td,
-th,
-tbody,
-tfoot,
-thead,
-applet,
-object,
-iframe,
-frame,
-frameset {
- border: 0;
-}
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-pre,
-table,
-blockquote {
- clear: both;
-}
-
-body {
- line-height: 1.5;
- color: #000;
- background: #fff;
- font:13px/1.22 arial,helvetica,clean,sans-serif;
- font-style: normal;
- *font-size:small;
- *font:x-small;
-}
-
-input,
-textarea,
-select {
- font:13px/1.22 arial,helvetica,clean,sans-serif;
- *font-size:13px;
-}
-
-pre,
-code,
-kbd,
-samp,
-tt {
- font-family:monospace;
- *font-size:108%;
- line-height:99%;
-}
-
-/* Tables still need 'cellspacing="0"' in the markup. */
-table {
- font-size:inherit;
- font:100%;
- table-layout: fixed;
- border-collapse: collapse;
- border-spacing: 0;
- empty-cells: show;
-}
-caption, th, td {
- text-align: left;
- font-weight: normal;
- empty-cells: show;
- vertical-align: top;
-}
-
-/* Remove possible quote marks (") from ,
Das ist ein einfaches Beispiel. Etwas Text zum üben
- und fertig. Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.
- Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.
- Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.
-
Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.Das ist
- ein
-einfaches Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches
-Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches Beispiel.
-Etwas Text zum üben und fertig.
-
Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.Das ist
- ein
-einfaches Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches
-Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches Beispiel.
-Etwas Text zum üben und fertig. g. Das ist ein einfaches Beispiel.
-Etwas Text zum üben und fertig. g. Das ist ein einfaches Beispiel.
-Etwas Text zum üben und fertig. g. Das ist ein einfaches Beispiel.
-Etwas Text zum üben und fertig. g. Das ist ein einfaches Beispiel.
-Etwas Text zum üben und fertig.
-
Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.Das ist
- ein
-einfaches Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches
-Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches Beispiel.
-Etwas Text zum üben und fertig.
-
Das ist ein einfaches Beispiel.
- Etwas Text zum üben und fertig.Das ist
- ein
-einfaches Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches
-Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches Beispiel.
-Etwas Text zum üben und fertig.
-
Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.Das ist
- ein
-einfaches Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches
-Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches Beispiel.
-Etwas Text zum üben und fertig.
-
Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.Das ist
- ein
-einfaches Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches
-Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches Beispiel.
-Etwas Text zum üben und fertig.
-
-
+
+
+
+
+
+
+
+
+
+
Das ist ein einfaches Beispiel. Etwas Text zum üben
+ und fertig. Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.
+ Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.
+ Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.
+
Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.Das ist
+ ein
+einfaches Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches
+Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches Beispiel.
+Etwas Text zum üben und fertig.
+
Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.Das ist
+ ein
+einfaches Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches
+Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches Beispiel.
+Etwas Text zum üben und fertig. g. Das ist ein einfaches Beispiel.
+Etwas Text zum üben und fertig. g. Das ist ein einfaches Beispiel.
+Etwas Text zum üben und fertig. g. Das ist ein einfaches Beispiel.
+Etwas Text zum üben und fertig. g. Das ist ein einfaches Beispiel.
+Etwas Text zum üben und fertig.
+
Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.Das ist
+ ein
+einfaches Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches
+Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches Beispiel.
+Etwas Text zum üben und fertig.
+
Das ist ein einfaches Beispiel.
+ Etwas Text zum üben und fertig.Das ist
+ ein
+einfaches Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches
+Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches Beispiel.
+Etwas Text zum üben und fertig.
+
Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.Das ist
+ ein
+einfaches Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches
+Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches Beispiel.
+Etwas Text zum üben und fertig.
+
Das ist ein einfaches Beispiel. Etwas Text zum üben und fertig.Das ist
+ ein
+einfaches Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches
+Beispiel. Etwas Text zum üben und fertig. Das ist ein einfaches Beispiel.
+Etwas Text zum üben und fertig.
+
A sentence.
\ No newline at end of file
diff --git a/test/test-loremipsum.html b/test/test-loremipsum.html
index f5dd1746..971cbac2 100644
--- a/test/test-loremipsum.html
+++ b/test/test-loremipsum.html
@@ -1,582 +1,582 @@
-
-
-
-
-Lorem ipsum - TEST
-
-
-
-
-
-
-
Ausreißer
-
-
CSS Testsuite
-
-
- Lorem ipsum sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
-
-
-
INDENT? Lorem ipsum sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseruntmollitanim id est laborum. H2O -> x2 Das ist weg und das ist neu!
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
<h1>Pre + code test</h1>
-<hr>
-
-
nochmal:
-
1: Eins
- 2: Zwei
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
-
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
Und noch mehr Zitate
-
-
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
Und noch mehr Zitate
-
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
-
Lorem Ipsum 1
-
Lorem Ipsum 2
-
Lorem Ipsum 3
-
Lorem Ipsum 4
-
Lorem Ipsum 5
-
Lorem Ipsum 6
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
Doppelte Einrückung
Und raus
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
-
-
Header
-
Header
-
Header
-
Header
-
-
-
DataData
-
Data
-
DataData
-
-
-
Data
-
DataDataDataData
-
-
-
Data
-
Data
-
-
-
-
-
-
Header
-
Header
-
Header
-
Header
-
-
-
DataData
-
Data
-
DataData
-
-
-
Data
-
DataDataDataData on Page
-
-
-
Data
-
Data
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Header
-
Header
-
Header
-
Header
-
-
-
DataData
-
Data
-
DataData
-
-
-
Data
-
DataDataDataData
-
-
-
Data
-
Data
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Header
-
Header
-
Header
-
Header
-
-
-
DataData
-
Data
-
DataData
-
-
-
Data
-
DataDataDataData
-
-
-
Data
-
Data
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Lorem Ipsum
-
Dolor sit amet
-
-
Lorem Ipsum
-
Dolor sit amet
-
Consectetur adipisicing
-
-
-
Consectetur adipisicing
-
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Überschrift1
-
Überschrift2
-
Überschrift3
-
Überschrift4
-
Überschrift5
-
Überschrift6
-
-
-
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce
- bibendum nisl pharetra ipsum. Curabitur ac diam. Aliquam vel lorem.
- Cras diam. Nunc placerat. Morbi ac metus eget sapien varius
- consectetuer. Aliquam sagittis luctus nunc.
-
-
Nullam ante nulla, mattis
- ac, imperdiet nec, cursus nec, urna. Nulla euismod, sem nec sagittis
- bibendum, nisl neque bibendum urna, non ultrices felis dui quis justo.
- Ut in diam vel urna vulputate tempor. Cras vehicula lectus quis nibh.
- Vestibulum dignissim tincidunt eros. Mauris eros diam, sagittis nec,
- tempus eget, iaculis sed, purus.
-
-
-
Überschrift1
-
-
- Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce
- bibendum nisl pharetra ipsum. Curabitur ac diam. Aliquam vel lorem.
- Cras diam. Nunc placerat. Morbi ac metus eget sapien varius
- consectetuer. Aliquam sagittis luctus nunc. Nullam ante nulla, mattis
- ac, imperdiet nec, cursus nec, urna. Nulla euismod, sem nec sagittis
- bibendum, nisl neque bibendum urna, non ultrices felis dui quis justo.
- Ut in diam vel urna vulputate tempor. Cras vehicula lectus quis nibh.
- Vestibulum dignissim tincidunt eros. Mauris eros diam, sagittis nec,
- tempus eget, iaculis sed, purus.
-
-
-
Überschrift2
-
-
- Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce
- bibendum nisl pharetra ipsum. Curabitur ac diam. Aliquam vel lorem.
- Cras diam. Nunc placerat. Morbi ac metus eget sapien varius
- consectetuer. Aliquam sagittis luctus nunc. Nullam ante nulla, mattis
- ac, imperdiet nec, cursus nec, urna. Nulla euismod, sem nec sagittis
- bibendum, nisl neque bibendum urna, non ultrices felis dui quis justo.
- Ut in diam vel urna vulputate tempor. Cras vehicula lectus quis nibh.
- Vestibulum dignissim tincidunt eros. Mauris eros diam, sagittis nec,
- tempus eget, iaculis sed, purus.
-
-
-
-
Etiam vitae massa sed velit vestibulum euismod. Maecenas
- condimentum, felis et semper ornare, ipsum turpis ultrices pede, eget
- vulputate nisl lectus at turpis. Integer vitae justo. Pellentesque
- ultrices, felis non dictum dictum, sagittis luctus nunc lacus est dapibus magna, in
- elementum justo risus vel odio. sagittis luctus nunc Etiam volutpat tincidunt augue. Nam nec
- ligula. Fusce eget eros. Sed blandit. In vel magna. Nullam luctus
- malesuada dui. Praesent ultricies urna. Phasellus nulla. Donec iaculis
- venenatis metus. Proin non leo vel arcu scelerisque malesuada. Sed
- rhoncus odio non ligula. Ut pharetra cursus massa. Nunc nibh sem,
- pulvinar eget, faucibus ac, posuere quis, nibh. Mauris nisi metus,
- facilisis at, ornare sed, semper nec, tortor. Integer nec mauris id
- tellus mollis pellentesque.
-
-
-
-
erster Punkt
-
zweiter Punkt
-
dritter Punkt
-
-
-
Text im Div
-
-
-
erster Punkt
-
zweiter Punkt
-
dritter Punkt
-
-
-
- Das ist ein Testtext für strong Tags und underline Tags
- und kursive Schrift und ..
-
-
-
-
- Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote.
-
-
-
-
-
Donec est ipsum, rhoncus nec, rhoncus at, posuere vitae, odio. Donec
- venenatis suscipit turpis. Aliquam viverra lorem nec pede. Quisque
- pellentesque. Phasellus nec ante. Donec a arcu. Proin feugiat odio
- lacinia velit. Nullam sagittis luctus nunc vitae nulla in risus dapibus imperdiet. Aliquam
- iaculis nonummy dolor. Sed in nisl sit amet sapien tristique malesuada.
- Pellentesque habitant morbi sagittis luctus nunc tristique senectus et netus et malesuada
- fames ac turpis egestas. Morbi vulputate purus ut enim. Quisque
- rhoncus, metus a molestie aliquet, ipsum est ullamcorper erat, at
- elementum velit velit in nisi. Nulla risus orci, consectetuer id,
- adipiscing id, semper adipiscing, tortor. Aliquam ullamcorper.
-
-
-
-
-
-
Kopfzelle
-
Kopfzelle
-
Kopfzelle
-
-
-
Datenzelle
-
Datenzelle
-
Donec est ipsum, rhoncus nec, rhoncus at, posuere vitae Donec est ipsum, rhoncus nec, rhoncus at, posuere Donec est ipsum, rhoncus nec, rhoncus at, posuere,
-
Datenzelle
-
-
-
Datenzelle
-
-
-
HeaderHeader
-
Header
-
Header
-
-
-
Data
-
-
-
Data
-
-
-
Data
-
-
-
Data
-
Data
-
-
-
Data
-
Data
-
Data
-
-
-
Datenzelle
-
-
-
Datenzelle
-
Datenzelle
-
Datenzelle
-
-
-
Datenzelle
-
Datenzelle
-
Datenzelle
-
-
-
Datenzelle
-
Datenzelle
-
Datenzelle
-
-
-
-
-
-
Donec est ipsum, rhoncus nec, rhoncus at, posuere vitae, odio. Donec
- venenatis suscipit turpis. Aliquam viverra lorem nec pede. Quisque
- pellentesque. Phasellus nec ante. Donec a arcu. Proin feugiat odio
- lacinia velit. Nullam sagittis luctus nunc vitae nulla in risus dapibus imperdiet. Aliquam
- iaculis nonummy dolor. Sed in nisl sit amet sapien tristique malesuada.
- Pellentesque habitant morbi sagittis luctus nunc tristique senectus et netus et malesuada
- fames ac turpis egestas. Morbi vulputate purus ut enim. Quisque
- rhoncus, metus a molestie aliquet, ipsum est ullamcorper erat, at
- elementum velit velit in nisi. Nulla risus orci, consectetuer id,
- adipiscing id, semper adipiscing, tortor. Aliquam ullamcorper.
-
-
Donec est ipsum, rhoncus nec, rhoncus at, posuere vitae, odio. Donec
- venenatis suscipit turpis. Aliquam viverra lorem nec pede. Quisque
- pellentesque. Phasellus nec ante. Donec a arcu. Proin feugiat odio
- lacinia velit. Nullam sagittis luctus nunc vitae nulla in risus dapibus imperdiet. Aliquam
- iaculis nonummy dolor. Sed in nisl sit amet sapien tristique malesuada.
- Pellentesque habitant morbi sagittis luctus nunc tristique senectus et netus et malesuada
- fames ac turpis egestas. Morbi vulputate purus ut enim. Quisque
- rhoncus, metus a molestie aliquet, ipsum est ullamcorper erat, at
- elementum velit velit in nisi. Nulla risus orci, consectetuer id,
- adipiscing id, semper adipiscing, tortor. Aliquam ullamcorper.
-
-
-
-
Donec est ipsum, rhoncus nec, rhoncus at, posuere vitae, odio. Donec
- venenatis suscipit turpis. Aliquam viverra lorem nec pede. Quisque
- pellentesque. Phasellus nec ante. Donec a arcu. Proin feugiat odio
- lacinia velit. Nullam sagittis luctus nunc vitae nulla in risus dapibus imperdiet. Aliquam
- iaculis nonummy dolor. Sed in nisl sit amet sapien tristique malesuada.
- Pellentesque habitant morbi sagittis luctus nunc tristique senectus et netus et malesuada
- fames ac turpis egestas. Morbi vulputate purus ut enim. Quisque
- rhoncus, metus a molestie aliquet, ipsum est ullamcorper erat, at
- elementum velit velit in nisi. Nulla risus orci, consectetuer id,
- adipiscing id, semper adipiscing, tortor. Aliquam ullamcorper.
-
-
-
-
-
Formular:
-
-
-
-
-
-
IFrame
-
-
-
-
-
-
+
+
+
+
+Lorem ipsum - TEST
+
+
+
+
+
+
+
Ausreißer
+
+
CSS Testsuite
+
+
+ Lorem ipsum sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+
+
+
INDENT? Lorem ipsum sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseruntmollitanim id est laborum. H2O -> x2 Das ist weg und das ist neu!
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
<h1>Pre + code test</h1>
+<hr>
+
+
nochmal:
+
1: Eins
+ 2: Zwei
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
+
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
Und noch mehr Zitate
+
+
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
Und noch mehr Zitate
+
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
Lorem Ipsum 1
+
Lorem Ipsum 2
+
Lorem Ipsum 3
+
Lorem Ipsum 4
+
Lorem Ipsum 5
+
Lorem Ipsum 6
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
Doppelte Einrückung
Und raus
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+
Header
+
Header
+
Header
+
Header
+
+
+
DataData
+
Data
+
DataData
+
+
+
Data
+
DataDataDataData
+
+
+
Data
+
Data
+
+
+
+
+
+
Header
+
Header
+
Header
+
Header
+
+
+
DataData
+
Data
+
DataData
+
+
+
Data
+
DataDataDataData on Page
+
+
+
Data
+
Data
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Header
+
Header
+
Header
+
Header
+
+
+
DataData
+
Data
+
DataData
+
+
+
Data
+
DataDataDataData
+
+
+
Data
+
Data
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Header
+
Header
+
Header
+
Header
+
+
+
DataData
+
Data
+
DataData
+
+
+
Data
+
DataDataDataData
+
+
+
Data
+
Data
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Lorem Ipsum
+
Dolor sit amet
+
+
Lorem Ipsum
+
Dolor sit amet
+
Consectetur adipisicing
+
+
+
Consectetur adipisicing
+
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Überschrift1
+
Überschrift2
+
Überschrift3
+
Überschrift4
+
Überschrift5
+
Überschrift6
+
+
+
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce
+ bibendum nisl pharetra ipsum. Curabitur ac diam. Aliquam vel lorem.
+ Cras diam. Nunc placerat. Morbi ac metus eget sapien varius
+ consectetuer. Aliquam sagittis luctus nunc.
+
+
Nullam ante nulla, mattis
+ ac, imperdiet nec, cursus nec, urna. Nulla euismod, sem nec sagittis
+ bibendum, nisl neque bibendum urna, non ultrices felis dui quis justo.
+ Ut in diam vel urna vulputate tempor. Cras vehicula lectus quis nibh.
+ Vestibulum dignissim tincidunt eros. Mauris eros diam, sagittis nec,
+ tempus eget, iaculis sed, purus.
+
+
+
Überschrift1
+
+
+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce
+ bibendum nisl pharetra ipsum. Curabitur ac diam. Aliquam vel lorem.
+ Cras diam. Nunc placerat. Morbi ac metus eget sapien varius
+ consectetuer. Aliquam sagittis luctus nunc. Nullam ante nulla, mattis
+ ac, imperdiet nec, cursus nec, urna. Nulla euismod, sem nec sagittis
+ bibendum, nisl neque bibendum urna, non ultrices felis dui quis justo.
+ Ut in diam vel urna vulputate tempor. Cras vehicula lectus quis nibh.
+ Vestibulum dignissim tincidunt eros. Mauris eros diam, sagittis nec,
+ tempus eget, iaculis sed, purus.
+
+
+
Überschrift2
+
+
+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce
+ bibendum nisl pharetra ipsum. Curabitur ac diam. Aliquam vel lorem.
+ Cras diam. Nunc placerat. Morbi ac metus eget sapien varius
+ consectetuer. Aliquam sagittis luctus nunc. Nullam ante nulla, mattis
+ ac, imperdiet nec, cursus nec, urna. Nulla euismod, sem nec sagittis
+ bibendum, nisl neque bibendum urna, non ultrices felis dui quis justo.
+ Ut in diam vel urna vulputate tempor. Cras vehicula lectus quis nibh.
+ Vestibulum dignissim tincidunt eros. Mauris eros diam, sagittis nec,
+ tempus eget, iaculis sed, purus.
+
+
+
+
Etiam vitae massa sed velit vestibulum euismod. Maecenas
+ condimentum, felis et semper ornare, ipsum turpis ultrices pede, eget
+ vulputate nisl lectus at turpis. Integer vitae justo. Pellentesque
+ ultrices, felis non dictum dictum, sagittis luctus nunc lacus est dapibus magna, in
+ elementum justo risus vel odio. sagittis luctus nunc Etiam volutpat tincidunt augue. Nam nec
+ ligula. Fusce eget eros. Sed blandit. In vel magna. Nullam luctus
+ malesuada dui. Praesent ultricies urna. Phasellus nulla. Donec iaculis
+ venenatis metus. Proin non leo vel arcu scelerisque malesuada. Sed
+ rhoncus odio non ligula. Ut pharetra cursus massa. Nunc nibh sem,
+ pulvinar eget, faucibus ac, posuere quis, nibh. Mauris nisi metus,
+ facilisis at, ornare sed, semper nec, tortor. Integer nec mauris id
+ tellus mollis pellentesque.
+
+
+
+
erster Punkt
+
zweiter Punkt
+
dritter Punkt
+
+
+
Text im Div
+
+
+
erster Punkt
+
zweiter Punkt
+
dritter Punkt
+
+
+
+ Das ist ein Testtext für strong Tags und underline Tags
+ und kursive Schrift und ..
+
+
+
+
+ Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote. Hier ist ein Blockquote.
+
+
+
+
+
Donec est ipsum, rhoncus nec, rhoncus at, posuere vitae, odio. Donec
+ venenatis suscipit turpis. Aliquam viverra lorem nec pede. Quisque
+ pellentesque. Phasellus nec ante. Donec a arcu. Proin feugiat odio
+ lacinia velit. Nullam sagittis luctus nunc vitae nulla in risus dapibus imperdiet. Aliquam
+ iaculis nonummy dolor. Sed in nisl sit amet sapien tristique malesuada.
+ Pellentesque habitant morbi sagittis luctus nunc tristique senectus et netus et malesuada
+ fames ac turpis egestas. Morbi vulputate purus ut enim. Quisque
+ rhoncus, metus a molestie aliquet, ipsum est ullamcorper erat, at
+ elementum velit velit in nisi. Nulla risus orci, consectetuer id,
+ adipiscing id, semper adipiscing, tortor. Aliquam ullamcorper.
+
+
+
+
+
+
Kopfzelle
+
Kopfzelle
+
Kopfzelle
+
+
+
Datenzelle
+
Datenzelle
+
Donec est ipsum, rhoncus nec, rhoncus at, posuere vitae Donec est ipsum, rhoncus nec, rhoncus at, posuere Donec est ipsum, rhoncus nec, rhoncus at, posuere,
+
Datenzelle
+
+
+
Datenzelle
+
+
+
HeaderHeader
+
Header
+
Header
+
+
+
Data
+
+
+
Data
+
+
+
Data
+
+
+
Data
+
Data
+
+
+
Data
+
Data
+
Data
+
+
+
Datenzelle
+
+
+
Datenzelle
+
Datenzelle
+
Datenzelle
+
+
+
Datenzelle
+
Datenzelle
+
Datenzelle
+
+
+
Datenzelle
+
Datenzelle
+
Datenzelle
+
+
+
+
+
+
Donec est ipsum, rhoncus nec, rhoncus at, posuere vitae, odio. Donec
+ venenatis suscipit turpis. Aliquam viverra lorem nec pede. Quisque
+ pellentesque. Phasellus nec ante. Donec a arcu. Proin feugiat odio
+ lacinia velit. Nullam sagittis luctus nunc vitae nulla in risus dapibus imperdiet. Aliquam
+ iaculis nonummy dolor. Sed in nisl sit amet sapien tristique malesuada.
+ Pellentesque habitant morbi sagittis luctus nunc tristique senectus et netus et malesuada
+ fames ac turpis egestas. Morbi vulputate purus ut enim. Quisque
+ rhoncus, metus a molestie aliquet, ipsum est ullamcorper erat, at
+ elementum velit velit in nisi. Nulla risus orci, consectetuer id,
+ adipiscing id, semper adipiscing, tortor. Aliquam ullamcorper.
+
+
Donec est ipsum, rhoncus nec, rhoncus at, posuere vitae, odio. Donec
+ venenatis suscipit turpis. Aliquam viverra lorem nec pede. Quisque
+ pellentesque. Phasellus nec ante. Donec a arcu. Proin feugiat odio
+ lacinia velit. Nullam sagittis luctus nunc vitae nulla in risus dapibus imperdiet. Aliquam
+ iaculis nonummy dolor. Sed in nisl sit amet sapien tristique malesuada.
+ Pellentesque habitant morbi sagittis luctus nunc tristique senectus et netus et malesuada
+ fames ac turpis egestas. Morbi vulputate purus ut enim. Quisque
+ rhoncus, metus a molestie aliquet, ipsum est ullamcorper erat, at
+ elementum velit velit in nisi. Nulla risus orci, consectetuer id,
+ adipiscing id, semper adipiscing, tortor. Aliquam ullamcorper.
+
+
+
+
Donec est ipsum, rhoncus nec, rhoncus at, posuere vitae, odio. Donec
+ venenatis suscipit turpis. Aliquam viverra lorem nec pede. Quisque
+ pellentesque. Phasellus nec ante. Donec a arcu. Proin feugiat odio
+ lacinia velit. Nullam sagittis luctus nunc vitae nulla in risus dapibus imperdiet. Aliquam
+ iaculis nonummy dolor. Sed in nisl sit amet sapien tristique malesuada.
+ Pellentesque habitant morbi sagittis luctus nunc tristique senectus et netus et malesuada
+ fames ac turpis egestas. Morbi vulputate purus ut enim. Quisque
+ rhoncus, metus a molestie aliquet, ipsum est ullamcorper erat, at
+ elementum velit velit in nisi. Nulla risus orci, consectetuer id,
+ adipiscing id, semper adipiscing, tortor. Aliquam ullamcorper.
+
Blender is a software that is used for creating 3D content.
-Primarily this means you can make three dimensional images. However,
-you can also add sound, and make it time-based and interactive. This
-makes Blender ideal for a wide range of uses including creating 3D
-models, rendering, post -production, story boarding, creating
-animations, making movies, creating 3D 'programs', and making
-interactive environments and games.
-
-
2. Blender & it's Unique Interface
-
ÂIt's no secret that the Blender interface breaks a lot of rules.
-Luckily Blender has good reasons for it and after time these reasons
-become clear. Blender's interface is built around providing you with
-the shortest routes to the most results. It's also particularly focused
-on reducing the kind of stress on wrists and hands that's often felt
-when using mouse-intensive graphical applications every day.
-
-
-
3. < Installing Blender on OSX
-
-
Software Name: Blender
- Homepage:http://www.blender.org
- Software version used for this installation: Blender 2.43
- Operating System used for thisinstalltion: OSX (10.4.8)
- Reccomended Hardware: Powerbook G4, Powermac G5, Mac Pro, MacBookPro, iMac (core Duo)
-
Blender is a software that is used for creating 3D content.
+Primarily this means you can make three dimensional images. However,
+you can also add sound, and make it time-based and interactive. This
+makes Blender ideal for a wide range of uses including creating 3D
+models, rendering, post -production, story boarding, creating
+animations, making movies, creating 3D 'programs', and making
+interactive environments and games.
+
+
2. Blender & it's Unique Interface
+
ÂIt's no secret that the Blender interface breaks a lot of rules.
+Luckily Blender has good reasons for it and after time these reasons
+become clear. Blender's interface is built around providing you with
+the shortest routes to the most results. It's also particularly focused
+on reducing the kind of stress on wrists and hands that's often felt
+when using mouse-intensive graphical applications every day.
+
+
+
3. < Installing Blender on OSX
+
+
Software Name: Blender
+ Homepage:http://www.blender.org
+ Software version used for this installation: Blender 2.43
+ Operating System used for thisinstalltion: OSX (10.4.8)
+ Reccomended Hardware: Powerbook G4, Powermac G5, Mac Pro, MacBookPro, iMac (core Duo)
+
Blender is a software that is used for creating 3D content.
-Primarily this means you can make three dimensional images. However,
-you can also add sound, and make it time-based and interactive. This
-makes Blender ideal for a wide range of uses including creating 3D
-models, rendering, post -production, story boarding, creating
-animations, making movies, creating 3D 'programs', and making
-interactive environments and games.
-
-
2. Blender's Unique Interface
-
ÂIt's no secret that the Blender interface breaks a lot of rules.
-Luckily Blender has good reasons for it and after time these reasons
-become clear. Blender's interface is built around providing you with
-the shortest routes to the most results. It's also particularly focused
-on reducing the kind of stress on wrists and hands that's often felt
-when using mouse-intensive graphical applications every day.
-
-
-
3. Installing Blender on OSX
-
-
Software Name: Blender
- Homepage:http://www.blender.org
- Software version used for this installation: Blender 2.43
- Operating System used for thisinstalltion: OSX (10.4.8)
- Reccomended Hardware: Powerbook G4, Powermac G5, Mac Pro, MacBookPro, iMac (core Duo)
-
Blender is a software that is used for creating 3D content.
+Primarily this means you can make three dimensional images. However,
+you can also add sound, and make it time-based and interactive. This
+makes Blender ideal for a wide range of uses including creating 3D
+models, rendering, post -production, story boarding, creating
+animations, making movies, creating 3D 'programs', and making
+interactive environments and games.
+
+
2. Blender's Unique Interface
+
ÂIt's no secret that the Blender interface breaks a lot of rules.
+Luckily Blender has good reasons for it and after time these reasons
+become clear. Blender's interface is built around providing you with
+the shortest routes to the most results. It's also particularly focused
+on reducing the kind of stress on wrists and hands that's often felt
+when using mouse-intensive graphical applications every day.
+
+
+
3. Installing Blender on OSX
+
+
Software Name: Blender
+ Homepage:http://www.blender.org
+ Software version used for this installation: Blender 2.43
+ Operating System used for thisinstalltion: OSX (10.4.8)
+ Reccomended Hardware: Powerbook G4, Powermac G5, Mac Pro, MacBookPro, iMac (core Duo)
+