Commits

ajung committed 675db3d

reorganized

  • Participants
  • Parent commits e9a2f0f

Comments (0)

Files changed (144)

File buildout.cfg

     zopeskel
     pytest
 
-extends = 
-    http://dist.plone.org/release/4.3-latest/versions.cfg
+package-name = pp.client-plone
 
-# Add additional egg download sources here. dist.plone.org contains archives
-# of Plone packages.
-find-links =
-    http://dist.plone.org/release/4.3-latest
-    http://dist.plone.org/thirdparty
+extends =
+    https://raw.github.com/collective/buildout.plonetest/master/test-4.x.cfg
 
 extensions = 
     mr.developer
 versions = versions
 
 auto-checkout = 
-    pp.client-plone
     pp.client-python 
     pp.plone-nimbudocs
     pp.core

File docs/Makefile

+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ProducePublishPloneClientConnector.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ProducePublishPloneClientConnector.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/ProducePublishPloneClientConnector"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ProducePublishPloneClientConnector"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	make -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."

File docs/make.bat

+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
+if NOT "%PAPER%" == "" (
+	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+	:help
+	echo.Please use `make ^<target^>` where ^<target^> is one of
+	echo.  html       to make standalone HTML files
+	echo.  dirhtml    to make HTML files named index.html in directories
+	echo.  singlehtml to make a single large HTML file
+	echo.  pickle     to make pickle files
+	echo.  json       to make JSON files
+	echo.  htmlhelp   to make HTML files and a HTML help project
+	echo.  qthelp     to make HTML files and a qthelp project
+	echo.  devhelp    to make HTML files and a Devhelp project
+	echo.  epub       to make an epub
+	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+	echo.  text       to make text files
+	echo.  man        to make manual pages
+	echo.  changes    to make an overview over all changed/added/deprecated items
+	echo.  linkcheck  to check all external links for integrity
+	echo.  doctest    to run all doctests embedded in the documentation if enabled
+	goto end
+)
+
+if "%1" == "clean" (
+	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+	del /q /s %BUILDDIR%\*
+	goto end
+)
+
+if "%1" == "html" (
+	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+	goto end
+)
+
+if "%1" == "dirhtml" (
+	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+	goto end
+)
+
+if "%1" == "singlehtml" (
+	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+	goto end
+)
+
+if "%1" == "pickle" (
+	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the pickle files.
+	goto end
+)
+
+if "%1" == "json" (
+	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the JSON files.
+	goto end
+)
+
+if "%1" == "htmlhelp" (
+	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+	goto end
+)
+
+if "%1" == "qthelp" (
+	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ProducePublishPloneClientConnector.qhcp
+	echo.To view the help file:
+	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ProducePublishPloneClientConnector.ghc
+	goto end
+)
+
+if "%1" == "devhelp" (
+	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished.
+	goto end
+)
+
+if "%1" == "epub" (
+	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The epub file is in %BUILDDIR%/epub.
+	goto end
+)
+
+if "%1" == "latex" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "text" (
+	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The text files are in %BUILDDIR%/text.
+	goto end
+)
+
+if "%1" == "man" (
+	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The manual pages are in %BUILDDIR%/man.
+	goto end
+)
+
+if "%1" == "changes" (
+	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.The overview file is in %BUILDDIR%/changes.
+	goto end
+)
+
+if "%1" == "linkcheck" (
+	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+	goto end
+)
+
+if "%1" == "doctest" (
+	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+	goto end
+)
+
+:end

File docs/source/HISTORY.rst

+Changelog
+=========
+
+0.2.1 (06-10-2013)
+------------------
+- added browser layer
+
+
+0.2.0 (03-10-2013)
+------------------
+- updated documentation 
+
+0.1.9 (30-09-2013)
+------------------
+
+- support for new-style collections (Plone 4.3+)
+  and images
+
+0.1.8 (30-09-2013)
+------------------
+
+- some fixes for folder aggregation
+
+0.1.7 (19-09-2013)
+------------------
+
+- images used from within a PDF template file directly
+  inside a resource should be marked with
+  <img internal-image="true" src="..." />
+
+0.1.6 (17-09-2013)
+------------------
+
+- merged https://bitbucket.org/ajung/pp.client-plone/pull-request/2/fixed-umlaut-problem-of-images-and/diff
+
+0.1.5 (21-08-2013)
+------------------
+
+- merged https://bitbucket.org/ajung/pp.client-plone/pull-request/1/add-missing-transformer-import-in/diff
+
+0.1.5 (21-08-2013)
+------------------
+
+- merged https://bitbucket.org/ajung/pp.client-plone/pull-request/1/add-missing-transformer-import-in/diff
+
+0.1.4 (12-07-2013)
+-------------------
+
+- major style and fonts cleanup
+
+0.1.3 (11-07-2013)
+-------------------
+
+- various fixes
+- Plone 4.0 - 4.2 compatibility
+
+0.1.0 (11-07-2013)
+-------------------
+
+- initial release

File docs/source/README.rst

+.. Produce & Publish Plone Client Connector documentation master file, created by
+   sphinx-quickstart on Sun Nov 13 15:03:42 2011.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Produce & Publish Plone Client Connector
+========================================
+
+The Produce & Publish Plone Client connector integrates the Plone
+CMS with the Produce & Publishing platform and supports the
+generation of PDF (requires other external PDF converters).
+
+Documentation
+-------------
+
+Primary documentation: http://pythonhosted.org/pp.client-plone
+
+Produce & Publish website: http://www.produce-and-publish.info
+
+Source code
+-----------
+See https://bitbucket.org/ajung/pp.client-plone
+
+Bugtracker
+----------
+See https://bitbucket.org/ajung/pp.client-plone/issues
+
+Licence
+-------
+Published under the GNU Public Licence Version 2 (GPL 2)
+
+Author
+------
+| ZOPYX Limited
+| Hundskapfklinge 33
+| D-72074 Tuebingen, Germany
+| info@zopyx.com
+| www.zopyx.com
+
+
+

File docs/source/conf.py

+# -*- coding: utf-8 -*-
+#
+# Produce & Publish Plone Client Connector documentation build configuration file, created by
+# sphinx-quickstart on Sun Nov 13 15:03:42 2011.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Produce & Publish Plone Client Connector'
+copyright = u'2013, ZOPYX Limited'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.0'
+# The full version, including alpha/beta/rc tags.
+release = '1.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'ProducePublishPloneClientConnectordoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'ProducePublishPloneClientConnector.tex', u'Produce \\& Publish Plone Client Connector Documentation',
+   u'Andreas Jung', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'producepublishploneclientconnector', u'Produce & Publish Plone Client Connector Documentation',
+     [u'Andreas Jung'], 1)
+]

File docs/source/content-types.rst

+Adding custom content-types to the Plone Client Connector
+=========================================================
+
+This documentation explains how to extend the Plone Client Connector with your
+own or custom Plone content-types.
+
+Custom content-types can be registered with the Produce & Publish server using
+the Zope Component Architecture. The one single contact of the P&P server with a
+content-type is the existence of a ``@@asHTML`` view for the related content-type.
+The ``@@asHTML`` view must return a HTML snippet that will be used by the P&P
+within the main body of its own rendering PDF template.
+
+As an example look at the ``@@asHTML`` view for Plone news items.
+
+The ``@@asHTML`` view is configured through ZCML (within your
+configure.zcml file):
+
+::
+
+        <browser:page
+          name="asHTML"
+          for="Products.ATContentTypes.interface.news.IATNewsItem"
+          permission="zope2.View"
+          class=".newsitem.HTMLView"
+          />
+
+and implemented as browser view (newsitem.py):
+
+::
+
+    from Globals import InitializeClass
+    from Products.Five.browser import BrowserView
+    from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+    
+    class HTMLView(BrowserView):
+        """ This view renders a HMTL fragment for the configured content type """
+    
+        template = ViewPageTemplateFile('newsitem_raw.pt')
+    
+        def __call__(self, *args, **kw):
+            return self.template(self.context)
+    
+    InitializeClass(HTMLView)
+
+The related templates renders a snippet of code for a news item
+object:
+::
+
+    <div class="type-newsitem document-body">
+        <h1 class="title bookmark-title" tal:content="context/Title" />
+        <div class="description" tal:content="context/Description" />
+        <div>
+            <div class="image-box" tal:condition="nocall: context/image | nothing">    
+                <img class="teaser-image" src="image" />
+                <div class="image-caption" tal:content="context/getImageCaption | nothing" />
+            </div>
+    
+            <div class="body" tal:content="structure context/getText" />
+        </div>
+    </div>
+
+In addition your content-type implementation **must** provide the
+``pp.client.plone.interfaces.IPPContent`` interface - either by
+specifying this interface as part of the class definition in your code
+
+::
+
+    class MyContentType(...):
+
+        implements(IPPContent)
+
+or you add the interfaces as a marker interface through ``ZCML``
+
+::
+
+    <five:implements
+        class="my.package.contents.mytype.MyContentType"
+        interface="pp.client.plone.interfaces.IPPContent"
+    />
+
+Only content objects providing the ``IPPContent`` interface are being considered
+during the aggregation phase of the Plone Client Connector.
+
+For further example code, please refer to the
+*pp/client/plone/browser* directory. The ``folder`` integration
+*(folder.py)* shows you a more complex example and involves aggregation of
+other content.
+

File docs/source/index.rst

+Produce & Publish Plone Client Connector
+========================================
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+   README.rst
+   HISTORY.rst
+   installation.rst
+   resource-directories.rst
+   content-types.rst
+   integration-ploneformgen.rst
+
+Indices and tables
+==================
+
+/ :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+

File docs/source/installation.rst

+Installation
+============
+
+This documentation assumes that your installation of Plone/Zope is based on
+zc.buildout.
+
+
+- edit your *buildout.cfg* -  add *pp.client-plone* to the 
+  **eggs** options of your buildout.cfg::
+
+    eggs = ...
+        pp.client-plone
+
+- restart Zope/Plone
+
+- When running the Produce & Publish server on a different server, you must
+  adjust the ``PP_SERVER`` environment variables within your *.bashrc* file (or
+  a similar file) or you put those variables into your buildout configuration
+  using the *<environment>* section.  Username and password are only needed
+  when you run the Produce & Publish server behind a reverse proxy (requiring
+  authentcation)::
+
+    export PP_SERVER=http://user:password@your.server:6543/api/1
+
+  or::
+
+    <environment>
+        PP_SERVER=http://user:password@your.server:6543/api/1
+    </environment>
+
+.. note:: This version of the Produce & Publish Plone Client Connector
+    requires an installation of the new ``pp.server`` Produce & Publish Server.
+    It will not work with the older ``zopyx.smartprintng.server`` server implementation.
+
+
+Supported Plone content-types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Archetypes-based content-types
+++++++++++++++++++++++++++++++
+
+- Document
+- Folder (nested structure)
+- News item
+- Collection (new-style collections Plone 4.3 only)
+- Image
+
+Dexterity-based content-types
++++++++++++++++++++++++++++++
+
+There is no direct support for Dexterity content-types available however the
+configuration contains an example configuration on how to register the
+interface of a Dexterity content-type with the Plone Client Connector. However
+support for Dexterity types will only work for filesystem-based Dexterity types
+with a related marker interface - there is no support for through-the-web
+defined Dexterity types.
+
+Usage
+~~~~~
+
+The Plone connector provides a dedicated @@asPDF view that can
+be added to the URL of any of the supported content-types of Plone
+(Document, Folder, Newsitem, PloneGlossary). So when your document
+is for example associated with the URL::
+
+::
+
+    http://your.server/plone/my-page
+
+you can generate a PDF by using the URL
+
+::
+
+    http://your.server/plone/my-page/@@asPDF
+
+Parameters
+~~~~~~~~~~
+
+The @@asPDF view accepts the following parameters controlling
+certain aspects of the PDF conversion:
+
+-  **language** - can be set to 'de', 'en', 'fr' etc. in order to
+   control language-specific aspects of the PDF conversion. Most
+   important: this parameter controls the hyphenation. The Plone
+   connector comes out-of-the-box with hypenation tables for several
+   languages.  You can omit this URL parameter if the **Language**
+   metadata parameter (of the top-level document) to be converted is
+   set within Plone.
+
+-  **converter** - if you are using the Produce & Publish server
+   with a converter backend other than PrinceXML you can specify a
+   different name (default is *princexml*). Possible values
+
+   - ``princexml``
+   - ``pdfreactor``
+   - ``phantomjs``
+
+- **resource** - can be set in order to specify a registered resource
+  directory to be used for  running the conversion. The ```resource``
+  parameter must be identical with the ``name`` parameter of
+  the related ZCML ``<pp:resourceDirectory>`` directive.
+
+- **template**  - can be used to specify the name of template to be
+  used for running the conversion. The ``template`` parameter usually
+  refers to a .pt filename inside the ``resource`` directory.  
+
+Miscellaneous
+~~~~~~~~~~~~~
+
+The environment varialble ``PP_ZIP_OUTPUT`` can be set to export
+all resources used for the conversion into a ZIP file for debugging purposes.
+The path of the generated ZIP file is logged within the standard Zope/Plone
+logfile (or the console if Plone is running in foreground).

File docs/source/integration-ploneformgen.rst

+Integration with PloneFormGen
+=============================
+
+Using Produce & Publish with PloneFormGen - generating PDF
+documents from form data.
+
+Installation
+~~~~~~~~~~~~
+
+-  Install **PloneFormGen**
+
+Converting form data to PDF
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+-  use a script adapter with the following code
+
+::
+
+    view = context.restrictedTraverse('@@asPFGPDF')
+    pdf =  view()
+    R = context.REQUEST.RESPONSE
+    R.setHeader('content-type', 'application/pdf')
+    R.setHeader('content-length', len(pdf))
+    R.setHeader('content-disposition', 'attachment; filename=%s.pdf' % context.getId())
+    R.write(pdf)
+
+
+e  You can access PFG form values within your PDF template using
+   (in this case we have a form parameter ``fullname``)
+
+::
+
+    <span tal:replace="options/request/fullname | nothing">Fullname</span>

File docs/source/resource-directories.rst

+Resource directories
+====================
+
+The Plone Client connector allows you to define your own resource directories
+containing
+
+-  the PDF main template
+-  style sheets
+-  font files
+-  hyphenation files
+
+Registering your own resource directory
+---------------------------------------
+
+First you need your own policy - e.g. *zopyx.theme*. Inside the configure.zcml
+file of your *zopyx.theme* you need to register a sub-directory using the
+``pp:resourceDirectory`` directive:
+
+::
+
+    <configure
+        xmlns="http://namespaces.zope.org/zope"
+        xmlns:zcml="http://namespaces.zope.org/zcml"
+        xmlns:pp="http://namespaces.zopyx.com/pp"
+        >
+    
+        <pp:resourceDirectory
+          name="zopyx_resource"
+          directory="resources_pdf"
+          />
+    
+    </configure>
+
+The registered ``resources_pdf`` directory must contain all resource files as
+flat structure (no sub-directories). The ``name`` parameter relates to the 
+optional ``resource`` URL parameter as use for the ``@@asPlainPDF`` browser 
+view.
+
+Naming conventions
+------------------
+
+- PDF template: .pt
+- Stylesheets: .css, .styles
+- Images: .gif, .jpg, .png
+- Hyphenation files: .hyp
+- Coverpage templates (only used with Authoring Environment): .cover
+- Font files: .otf, .ttf

File pp/__init__.py

+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)

File pp/client/__init__.py

+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)

File pp/client/plone/__init__.py

+################################################################
+# pp.client-plone
+# (C) 2013,  ZOPYX Limited, D-72074 Tuebingen, Germany
+################################################################
+
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)

File pp/client/plone/browser/__init__.py

+################################################################
+# pp.client-plone
+# (C) 2013,  ZOPYX Limited, D-72074 Tuebingen, Germany
+################################################################
+
+import transformations

File pp/client/plone/browser/compatible.py

+# pp.client-plone
+# (C) 2013,  ZOPYX Limited, D-72074 Tuebingen, Germany
+################################################################
+
+try:
+    from App.class_init import InitializeClass
+except ImportError:
+    from Globals import InitializeClass
+

File pp/client/plone/browser/configure.zcml

+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:zcml="http://namespaces.zope.org/zcml"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    >
+
+    <!-- docx importer -->
+
+    <browser:page
+        name="docx-import-form"
+        for="Products.ATContentTypes.interface.folder.IATFolder"
+        layer="pp.client.plone.interfaces.IPloneClientConnectorLayer"            
+        permission="cmf.ModifyPortalContent"
+        class=".docx_importer.DocxImporter"
+        template="docx_import.pt"
+    />
+
+    <browser:page
+        name="docx-import"
+        for="Products.ATContentTypes.interface.folder.IATFolder"
+        layer="pp.client.plone.interfaces.IPloneClientConnectorLayer"            
+        permission="cmf.ModifyPortalContent"
+        class=".docx_importer.DocxImporter"
+        attribute="docx_import"
+    />
+
+    <browser:page
+        name="docx-view"
+        for="Products.ATContentTypes.interface.folder.IATFolder"
+        layer="pp.client.plone.interfaces.IPloneClientConnectorLayer"            
+        permission="cmf.ModifyPortalContent"
+        class=".docx_importer.DocxImporter"
+        template="docx_view.pt"
+    />
+
+    <browser:page
+      name="asHTML"
+      for="Products.ATContentTypes.interface.folder.IATFolder"
+      layer="pp.client.plone.interfaces.IPloneClientConnectorLayer"            
+      permission="zope2.View"
+      class=".types.folder.HTMLView"
+      />
+
+    <browser:page
+      zcml:condition="installed plone.dexterity"
+      name="asHTML"
+      layer="pp.client.plone.interfaces.IPloneClientConnectorLayer"            
+      for="plone.dexterity.interfaces.IDexterityContainer"
+      permission="zope2.View"
+      class=".types.folder.HTMLView"
+      />
+
+    <browser:page
+      name="asHTML"
+      layer="pp.client.plone.interfaces.IPloneClientConnectorLayer"            
+      for="Products.ATContentTypes.interface.document.IATDocument"
+      permission="zope2.View"
+      class=".types.document.HTMLView"
+      />
+
+    <browser:page
+      name="asHTML"
+      layer="pp.client.plone.interfaces.IPloneClientConnectorLayer"            
+      for="Products.ATContentTypes.interface.news.IATNewsItem"
+      permission="zope2.View"
+      class=".types.newsitem.HTMLView"
+      />
+
+    <browser:page
+      name="asHTML"
+      layer="pp.client.plone.interfaces.IPloneClientConnectorLayer"            
+      for="Products.ATContentTypes.interface.topic.IATTopic"
+      permission="zope2.View"
+      class=".types.image.HTMLView"
+      />
+
+    <browser:page
+      name="asHTML"
+      for="plone.app.collection.interfaces.ICollection"
+      layer="pp.client.plone.interfaces.IPloneClientConnectorLayer"            
+      permission="zope2.View"
+      class=".types.topic.HTMLView"
+      />
+
+    <browser:page
+      zcml:condition="installed Products.PloneGlossary"
+      name="asHTML"
+      layer="pp.client.plone.interfaces.IPloneClientConnectorLayer"            
+      for="Products.PloneGlossary.interfaces.IPloneGlossary"
+      permission="zope2.View"
+      class=".types.glossary.GlossaryHTMLView"
+      />
+
+    <!-- a generic Dexterity view catching all Dexterity
+         content since TTW-generated Dexterity content
+         don't have specific interfaces.
+    -->
+
+    <browser:page
+      zcml:condition="installed plone.dexterity"
+      name="asHTML"
+      layer="pp.client.plone.interfaces.IPloneClientConnectorLayer"            
+      for="plone.dexterity.interfaces.IDexterityContent"
+      permission="zope2.View"
+      class=".types.dexterity.DexterityGenericView"
+      />
+
+    <browser:page
+      name="asPDF"
+      for="*"
+      layer="pp.client.plone.interfaces.IPloneClientConnectorLayer"            
+      permission="zope2.View"
+      class=".pdf.PDFDownloadView"
+    />
+
+</configure>

File pp/client/plone/browser/docx_import.pt

+<html metal:use-macro="context/main_template/macros/master">
+    <metal:slot fill-slot="main">
+        <div id="contentfolder-importform" i18n:domain="pp.client.plone">
+            <fieldset id="contentfolder-importform-fieldset">
+                <legend i18n:translate="label_import_office_document">Import DOCX</legend>
+                <form action="@@docx-import" method="post" enctype="multipart/form-data" id="content-import-form">
+
+                    <div class="form-row">
+                        <label i18n:translate="label_file">File</label>
+                        <input type="file" name="doc" value="" size="60" />
+                    </div>
+                    <div class="form-control">
+                        <input class="context" type="submit" value="Upload" i18n:attributes="value label_upload" />
+                    </div>
+                </form>
+            </fieldset>
+        </div>
+    </metal:slot>
+</html>

File pp/client/plone/browser/docx_importer.py

+################################################################
+# pp.client-plone
+# (C) 2013,  ZOPYX Limited, D-72074 Tuebingen, Germany
+################################################################
+
+import cssutils
+import os
+import lxml
+import base64
+from cStringIO import StringIO
+import PIL.Image
+from datetime import datetime
+from Products.Five.browser import BrowserView
+from pp.client.python.unoconv import unoconv
+from pp.client.plone.logger import LOG
+import tempfile
+import zipfile
+
+ignored_styles = (
+    'font-family',
+    'orphans',
+    'direction',
+    'widows',
+    'border',
+    'border-top',
+    'border-bottom',
+    'border-left',
+    'border-right',
+    'padding',
+    'padding-top',
+    'padding-bottom',
+    'padding-left',
+    'padding-right',
+    'margin',
+    'margin-top',
+    'margin-bottom',
+    'margin-left',
+    'margin-right',
+    'so-language',
+    'page-break-before',
+    'page-break-after',
+    'font-size', 
+    'text-indent',
+    'line-height',
+)
+
+def cleanup_css(css):
+    sheet = cssutils.parseString(css)
+    cssutils.ser.prefs.indent = '  '
+    
+    for i, rule in enumerate(sheet):
+        if isinstance(rule, cssutils.css.CSSStyleRule):
+            remove_props = list()
+            remove_props = [prop.name for prop in rule.style if prop.name.lower() in ignored_styles]
+            for name in remove_props:
+                rule.style.removeProperty(name)
+
+    for i, rule in enumerate(sheet):
+        if isinstance(rule, cssutils.css.CSSPageRule):
+            sheet.deleteRule(rule)
+            continue
+
+    return sheet.cssText
+
+class DocxImporter(BrowserView):
+
+    def view_docx(self):
+        styles = self.context.getFolderContents({
+            'portal_type': 'File',
+            'sort_on': 'getObjPositionInParent'},
+            full_objects=True)
+        styles = [s for s in styles if s.getId().endswith('.css')]
+        styles = u'\n'.join(str(s.getFile()) for s in styles)
+        html = self.context['index.html'].getText()
+        return dict(styles=styles, html=html)
+
+    def _cleanup_after_import(self, source_filename):
+        """ Cleanup import folder and change names """
+
+        html = self.context[source_filename].getRawText()
+        
+        # tidy first
+        filename = tempfile.mktemp()
+        filename_out = filename + '.out'
+        with open(filename, 'wb') as fp:
+            fp.write(html)
+        cmd = 'tidy -utf8 -c %s >%s' % (filename, filename_out)
+        LOG.info('Running %s' % cmd)
+        status = os.system(cmd)
+        LOG.info('tidy exit code: %d' % status)
+        if not os.path.exists(filename_out) or os.path.getsize(filename_out) == 0:
+            raise RuntimeError('Running "tidy" failed (cmd: %s)' % cmd)
+        with open(filename_out, 'rb') as fp:
+            html = fp.read()
+        os.unlink(filename)
+        os.unlink(filename_out)
+
+        # parse HTML into DOM
+        root = lxml.html.fromstring(html)
+
+        # export base64 encoded inline images
+        base64_marker = 'data:image/*;base64,'
+        count = 1
+        for img in root.xpath('//img'):
+            src = img.attrib['src']
+            if src.startswith(base64_marker):
+                img_data = base64.decodestring(src.replace(base64_marker, ''))
+                pil_image = PIL.Image.open(StringIO(img_data))
+                fmt = pil_image.format.lower()
+                if fmt in ('wmf'):
+                    raise RuntimeError('Word document must not contain embeed WMF files')
+                new_id = 'tmp{0:d}.png'.format(count)
+                self.context.invokeFactory('Image', id=new_id)
+                new_image = self.context[new_id]
+                new_image.setTitle(new_id)
+                out = StringIO()
+                pil_image.save(out, format='PNG')
+                new_image.setImage(out.getvalue())
+                new_image.reindexObject()
+                img.attrib['src'] = new_id
+                count += 1
+
+        # rename images
+        images_seen = dict()
+        count = 1
+        for img in root.xpath('//img'):
+            src = img.attrib['src']
+            base, ext = os.path.splitext(src)
+            if not src in images_seen:
+                new_id = '{0:d}{1:s}'.format(count, ext)
+                images_seen[src] = new_id
+                self.context[src].unindexObject()
+                self.context.manage_renameObject(src, new_id)
+                self.context[new_id].setTitle(new_id)
+                self.context[new_id].indexObject()
+                count += 1
+            else:
+                new_id = images_seen[src]
+            img.attrib['src'] = new_id
+
+        # export styles
+        for i, style in enumerate(root.xpath('//style')):
+            style_css = cleanup_css(style.text)
+            style_id = '{0:d}.css'.format(i + 1)
+            self.context.invokeFactory('File', id=style_id)
+            self.context[style_id].setFile(style_css)
+            self.context[style_id].setContentType('text/css')
+            self.context[style_id].reindexObject()
+
+        body = root.xpath('//body')[0]
+        self.context[source_filename].setText(lxml.html.tostring(body, unicode))
+        self.context[source_filename].unindexObject()
+        self.context.manage_renameObject(source_filename, 'index.html')
+        self.context['index.html'].setTitle('index.html')
+        self.context['index.html'].reindexObject()
+
+
+    def docx_import(self):
+
+        prefix = '{0}-{1}'.format(self.context.getId(), datetime.now().strftime('%Y%d%m-%H%M%S'))
+        zip_in = tempfile.mktemp(prefix=prefix, suffix='.zip')
+        with open(zip_in, 'wb') as zf:
+            zf.write(self.request.form['doc'].read())
+        
+        # convert using unoconv
+        zip_out = tempfile.mktemp(suffix='.zip')
+        result = unoconv(zip_in, format='xhtml', output=zip_out)
+        if result['status'] != 'OK':
+            self.context.plone_utils.addPortalMessage(u'Error during import')
+            return self.request.response.redirect(self.context.absolute_url() + '/docx-import-form')
+
+        # remove old content first
+        self.context.manage_delObjects(self.context.objectIds())
+
+        zf = zipfile.ZipFile(zip_out, 'r')
+        source_filename = None
+        for name in zf.namelist():
+            print name
+            base, ext = os.path.splitext(name)
+            if ext in ('.html',):
+                self.context.invokeFactory('Document', id=name)
+                source_filename = name
+                doc = self.context[name]
+                doc.setTitle(name)
+                doc.setText(zf.read(name))
+                doc.reindexObject()
+                LOG.info('Imported {0}'.format(name))
+            elif ext in ('.gif', '.png', '.jpeg', '.jpg'):
+                self.context.invokeFactory('Image', id=name)
+                doc = self.context[name]
+                doc.setTitle(name)
+                doc.setImage(zf.read(name))
+                doc.reindexObject()
+                LOG.info('Imported {0}'.format(name))
+
+        self._cleanup_after_import(source_filename)
+        self.context.plone_utils.addPortalMessage(u'Import successfull')
+        self.request.response.redirect(self.context.absolute_url() + '/folder_contents')

File pp/client/plone/browser/docx_view.pt

+<html metal:use-macro="context/main_template/macros/master">
+    <metal:slot fill-slot="main">
+        <tal:def define="result view/view_docx">
+            <style type="text/css" tal:content="result/styles" />
+            <div tal:content="structure result/html" />
+        </tal:def>
+    </metal:slot>
+</html>

File pp/client/plone/browser/images.py

+################################################################
+# pp.client-plone
+# (C) 2013,  ZOPYX Limited, D-72074 Tuebingen, Germany
+################################################################
+
+"""
+Image resolver
+"""
+
+from urllib2 import unquote, Request, urlopen, HTTPError
+from urlparse import urlparse 
+from Products.CMFCore.utils import getToolByName
+from Products.ATContentTypes.interfaces import IATImage
+from Products.Archetypes.Field import Image
+from plone.app.imaging.scale import ImageScale
+from pp.client.plone.logger import LOG
+try:
+    from zope.app.component.hooks import getSite
+except ImportError:
+    from zope.component.hooks import getSite
+
+
+def resolveImage(context, src):
+    """ Try to resolve an image based on its src which 
+        can be a relative URL, an absolute URL or an URL
+        using UIDs. Image scales can be annotated through
+        image_<scale> or using the newer plone.app.imaging
+        mechanism. Much fun :-P
+    """
+
+    if context is None:
+        context = getSite()
+    ref_catalog = getToolByName(context, 'reference_catalog')
+    parse_result = urlparse(unquote(src))
+    path = str(parse_result.path)
+    img_obj = None
+
+    if path.startswith('resolveuid'):
+        # can be resolveuid/<uid>/@@images/image/preview
+        path_parts = path.split('/')
+        img_obj = ref_catalog.lookupObject(path_parts[1])
+    else:
+
+        candidates = [path, path[1:]] # with and without leading '/' 
+        # check for a possible URL redirection
+        if src.startswith('http'):
+            req = Request(src)
+
+            try:
+                result = urlopen(req)
+            except HTTPError:
+                result = None 
+
+            if result and result.url != src: 
+                # a redirection happened
+                parse_result2 = urlparse(unquote(result.url))
+                path2 = str(parse_result2.path)
+                candidates.extend([path2, path2[1:]])
+
+        for p in candidates:
+            img_obj = context.restrictedTraverse(p, None)
+            if img_obj:
+                if img_obj.portal_type in ('Image',):
+                    # check if current image is a scale (having a parent image)
+                    if IATImage.providedBy(img_obj.aq_parent):
+                        img_obj = img_obj.aq_parent
+                    break
+                elif isinstance(img_obj, ImageScale):
+                    img_obj = img_obj.aq_parent
+                    break
+                elif isinstance(img_obj.aq_inner.aq_base, Image):
+                    img_obj = img_obj.aq_inner.aq_base
+                    break
+                elif isinstance(img_obj.aq_parent, Image):
+                    break
+
+            else:
+                img_obj = None
+    return dict(image=img_obj)

File pp/client/plone/browser/pdf.py

+################################################################
+# pp.client-plone
+# (C) 2013,  ZOPYX Limited, D-72074 Tuebingen, Germany
+################################################################
+
+import os
+import codecs
+import shutil
+import tempfile
+import zipfile
+
+from compatible import InitializeClass
+from Products.Five.browser import BrowserView
+from Products.ATContentTypes.interface.folder import IATFolder
+from ZPublisher.Iterators import filestream_iterator
+from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile 
+from zope.pagetemplate.pagetemplatefile import PageTemplate
+
+from pp.client.plone.logger import LOG
+from pp.core.transformation import Transformer
+from pp.core.resources_registry import resources_registry
+
+from util import getLanguageForObject
+
+cwd = os.path.dirname(os.path.abspath(__file__))
+
+# server host/port of the SmartPrintNG server
+DEFAULT_CONVERTER = os.environ.get('PP_CONVERTER', 'princexml')
+DEFAULT_RESOURCE = os.environ.get('PP_RESOURCE', 'pp-default')
+SERVER_URL = os.environ.get('PP_SERVER_URL', 'http://localhost:6543')
+ZIP_OUTPUT = 'PP_ZIP_OUTPUT' in os.environ
+
+
+class ProducePublishView(BrowserView):
+    """ Produce & Publish view (using Produce & Publish server) """
+
+    # default transformations used for the default PDF view.
+    # 'transformations' can be overriden within a derived ProducePublishView.
+    # If you don't need any transformation -> redefine 'transformations'
+    # as empty list or tuple
+
+    transformations = (
+        'makeImagesLocal',
+        'convertFootnotes',
+        'removeCrapFromHeadings',
+        'fixHierarchies',
+#        'addTableOfContents',
+        )
+
+    def __init__(self, context, request):
+        self.request = request
+        self.context = context
+
+    @property
+    def resource(self):
+        resource_id = self.request.get('resource', DEFAULT_RESOURCE)
+        if not resource_id in resources_registry:
+            raise KeyError(u'No resource "{}" registered'.format(resource_id))
+        return resources_registry[resource_id]
+
+    def copyResourceFiles(self, destdir):
+        """ Copy over resources for a global or local resources directory into the 
+            destination directory.
+        """
+
+        fslayer = self.resource.fslayer
+        for dirname, filenames in fslayer.walk():
+            for filename in filenames:
+                fullpath = os.path.join(dirname, filename)
+                with fslayer.open(fullpath, 'rb') as fp:
+                    content = fp.read()
+
+                if fullpath.startswith('/'):
+                    fullpath = fullpath[1:]
+                destpath = os.path.join(destdir, fullpath)
+                if not os.path.exists(os.path.dirname(destpath)):
+                    os.makedirs(os.path(dirname))
+                with open(destpath, 'wb') as fp:
+                    fp.write(content)
+
+    def transformHtml(self, html, destdir, transformations=None):
+        """ Perform post-rendering HTML transformations """
+
+        if transformations is None:
+            transformations = self.transformations
+
+        # the request can override transformations as well
+        if self.request.has_key('transformations'):
+            t_from_request = self.request['transformations']
+            if isinstance(t_from_request, basestring):
+                transformations = t_from_request and t_from_request.split(',') or []
+            else:
+                transformations = t_from_request
+
+        T = Transformer(transformations, 
+                        context=self.context, 
+                        destdir=destdir)
+        return T(html)
+
+    def __call__(self, *args, **kw):
+
+        try:
+            return self.__call2__(*args, **kw)
+        except:
+            LOG.error('Conversion failed', exc_info=True)
+            raise
+
+
+    def __call2__(self, *args, **kw):
+        """ URL parameters:
+            'language' -  'de', 'en'....used to override the language of the
+                          document
+            'converter' - default to on the converters registered with
+                          zopyx.convert2 (default: pdf-prince)
+            'resource' - the name of a registered resource (directory)
+            'template' - the name of a custom template name within the choosen
+                         'resource' 
+        """
+
+        # Output directory
+        tmpdir_prefix = os.path.join(tempfile.gettempdir(), 'produce-and-publish')
+        if not os.path.exists(tmpdir_prefix):
+            os.makedirs(tmpdir_prefix)
+        destdir = tempfile.mkdtemp(dir=tmpdir_prefix, prefix=self.context.getId() + '-')
+
+        # debug/logging
+        params = kw.copy()
+        params.update(self.request.form)
+        LOG.info('new job (%s, %s) - outdir: %s' % (args, params, destdir))
+
+        # get hold of the language (hyphenation support)
+        language = getLanguageForObject(self.context)
+        if params.get('language'):
+            language = params.get('language')
+
+        # call the dedicated @@asHTML on the top-level node. For a leaf document
+        # this will return either a HTML fragment for a single document or @@asHTML
+        # might be defined as an aggregator for a bunch of documents (e.g. if the
+        # top-level is a folderish object
+        html_view = self.context.restrictedTraverse('@@asHTML', None)
+        if not html_view:
+            raise RuntimeError('Object at does not provide @@asHTML view (%s, %s)' % 
+                               (self.context.absolute_url(1), self.context.portal_type))
+        html_fragment = html_view()
+
+        # arbitrary application data
+        data = params.get('data', None)
+
+        template_id = params.get('template', 'pdf_template.pt')
+        if not self.resource.fslayer.exists(template_id):
+            raise IOError('Resource does not contain template file {}'.format(template_id))
+        template = PageTemplate()
+        with self.resource.fslayer.open(template_id, 'rb') as fp:
+            template.write(fp.read())
+
+        # copy resource files
+        self.copyResourceFiles(destdir)
+
+        # Now render the complete HTML document
+        html = template(self,
+                        context=self.context,
+                        request=self.request,
+                        language=language,
+                        body=html_fragment,
+                        data=data,
+                        )
+
+        # and apply transformations
+        html = self.transformHtml(html, destdir)
+
+        # and store it in a dedicated working directory
+        dest_filename = os.path.join(destdir, 'index.html')
+        with codecs.open(dest_filename, 'wb', encoding='utf-8') as fp:
+            fp.write(html)
+
+        # create a local ZIP file containing all the data for the conversion
+        # basically for debugging purposes only.
+        if ZIP_OUTPUT or 'zip_output' in params:
+            archivename = tempfile.mktemp(suffix='.zip')
+            fp = zipfile.ZipFile(archivename, "w", zipfile.ZIP_DEFLATED) 
+            for root, dirs, files in os.walk(destdir):
+                for fn in files:
+                    absfn = os.path.join(root, fn)
+                    zfn = absfn[len(destdir)+len(os.sep):] #XXX: relative path
+                    fp.write(absfn, zfn)
+            fp.close()
+            LOG.info('ZIP file written to %s' % archivename)
+
+        if 'no_conversion' in params:
+            return destdir
+
+        converter = params.get('converter', DEFAULT_CONVERTER)
+        
+        # Produce & Publish server integration
+        from pp.client.python import pdf
+        result = pdf.pdf(destdir, converter, server_url=SERVER_URL)
+        output_filename = result['output_filename']
+        LOG.info('Output file: %s' % output_filename)
+        return output_filename
+
+InitializeClass(ProducePublishView)
+
+
+class PDFDownloadView(ProducePublishView):
+
+    def __call__(self, *args, **kw):
+        output_file = super(PDFDownloadView, self).__call__(*args, **kw)
+        mimetype = os.path.splitext(os.path.basename(output_file))[1]
+        R = self.request.response
+        R.setHeader('content-type', 'application/%s' % mimetype)
+        R.setHeader('content-disposition', 'attachment; filename="%s.%s"' % (self.context.getId(), mimetype))
+        R.setHeader('pragma', 'no-cache')
+        R.setHeader('cache-control', 'no-cache')
+        R.setHeader('Expires', 'Fri, 30 Oct 1998 14:19:41 GMT')
+        R.setHeader('content-length', os.path.getsize(output_file))
+        return filestream_iterator(output_file, 'rb').read()
+
+InitializeClass(PDFDownloadView)
+

File pp/client/plone/browser/preflight.pt

+<html metal:use-macro="context/main_template/macros/master">
+    <div metal:fill-slot="main">
+        <h1>Preflight - Structure Check</h1>
+        <div tal:define="result view/preflight">
+            <tal:loop repeat="row result">
+                <div tal:attributes="style python: 'margin-left: %dpx' % ((row['level']-1)*30) "
+                     tal:content="string:${row/level} - ${row/text}" />
+            </tal:loop>
+        </div>
+    </div>
+</html>

File pp/client/plone/browser/sorting.py

+# *-* encoding: iso-8859-15 *-*
+
+################################################################
+# pp.client-plone
+# (C) 2013,  ZOPYX Limited, D-72074 Tuebingen, Germany
+################################################################
+
+
+def normalize(s, encoding):
+    """ German normalization """
+
+    if not isinstance(s, unicode):
+        s = unicode(s, encoding, 'ignore')
+    s = s.lower()
+    s = s.replace(u'�', 'ae')
+    s = s.replace(u'�', 'oe')
+    s = s.replace(u'�', 'ue')
+    s = s.replace(u'�', 'ss')
+    return s
+
+def germanCmp(o1, o2, encoding='utf-8'):
+    return cmp(normalize(o1.Title(), encoding),
+               normalize(o2.Title(), encoding))
+
+def default_sort_method(o1, o2, encoding='utf-8'):
+    return cmp(o1.Title().lower(),
+               o2.Title().lower())
+
+
+sort_methods = {
+    'de' : germanCmp,
+}
+

File pp/client/plone/browser/splitter.py

+################################################################
+# pp.client-plone
+# (C) 2013,  ZOPYX Limited, D-72074 Tuebingen, Germany
+################################################################
+
+import os
+import codecs
+from cStringIO import StringIO
+from BeautifulSoup import BeautifulSoup, Tag
+
+from lxml.cssselect import CSSSelector
+import lxml.html 
+
+from util import _findTextInNode
+
+def split_html(html_filename, split_at_level=0):
+    """ Split aggregated and rendered HTML document at
+        some <hX> tag(s). split_at_level=0 -> split at
+        H1 tags, split_at_level=1 -> split at H1 and H2
+        tags.
+        Returns a list of dicts with keys 'html' referring
+        to the subdocument and 'level' indicating the split
+        point.
+    """
+
+    destdir = os.path.dirname(html_filename)
+    soup = BeautifulSoup(file(html_filename).read())
+    fp = StringIO(soup.__str__(prettyPrint=True))
+    docs = list()
+    current_doc = list()
+    for line in fp:
+        line = line.rstrip()
+        for level in range(split_at_level+1):
+            if '<h%d' % (level+1) in line.lower():
+                html = '\n'.join(current_doc)
+                root = lxml.html.fromstring(unicode(html, 'utf-8'))
+                title = u''
+                h1_nodes = root.xpath('//h1')
+                if h1_nodes:
+                    title = h1_nodes[0].text_content().strip()
+
+                # count tables and images
+                number_tables = len(root.xpath('//table'))
+                number_images = len(CSSSelector('div.image-caption')(root))
+
+                # find all linkable nodes with an ID attribute
+                node_ids = list()
+                for node in root.xpath('.//*'):
+                    node_id = node.get('id')
+                    if node_id:
+                        node_ids.append(node_id)
+
+                html = lxml.html.tostring(root, encoding=unicode)
+                docs.append(dict(html=html, 
+                                 level=level, 
+                                 title=title, 
+                                 node_ids=node_ids,
+                                 number_images=number_images,
+                                 number_tables=number_tables))
+                current_doc = []
+                break
+                
+        current_doc.append(line)
+
+    # now deal with the remaining part of the document
+    html = '\n'.join(current_doc)
+    root = lxml.html.fromstring(unicode(html, 'utf-8'))
+    title = u''
+    h1_nodes = root.xpath('//h1')
+    if h1_nodes:
+        title = h1_nodes[0].text_content().strip()
+
+    # count tables and images
+    # count tables and images
+    number_tables = len(root.xpath('//table'))
+    number_images = len(CSSSelector('div.image-caption')(root))
+
+    # find all linkable nodes with an ID attribute
+    node_ids = list()
+    for node in root.xpath('.//*'):
+        node_id = node.get('id')
+        if node_id:
+            node_ids.append(node_id)
+
+    html = lxml.html.tostring(root, encoding=unicode)
+    docs.append(dict(html=html, 
+                     level=0, 
+                     title=title, 
+                     node_ids=node_ids,
+                     number_images=number_images,
+                     number_tables=number_tables))
+
+    # now store files on the filesystem
+    ini_filename = os.path.join(destdir, 'documents.ini')
+    fp_ini = codecs.open(ini_filename, 'w', 'utf-8')
+
+    for count, d in enumerate(docs[1:]):
+        filename = os.path.join(destdir, 'split-0/%d-level-%d.html' % (count, d['level']))
+        if not os.path.exists(os.path.dirname(filename)):
+            os.makedirs(os.path.dirname(filename))                
+        file(filename, 'w').write(d['html'].encode('utf-8'))
+
+        print >>fp_ini, '[%d]' % count
+        print >>fp_ini, 'filename = %s' % filename
+        print >>fp_ini, 'title = %s' % d['title']
+        print >>fp_ini, 'number_tables= %d' % d['number_tables']
+        print >>fp_ini, 'number_images = %d' % d['number_images']
+        print >>fp_ini, 'node_ids = '
+        for node_id in d['node_ids']:
+            print >>fp_ini, '    ' + node_id
+        print >>fp_ini 
+
+    fp_ini.close()
+    return docs[1:]

File pp/client/plone/browser/tests/__init__.py

+# placeholder

File pp/client/plone/browser/transformations.py

+################################################################
+# pp.client-plone
+# (C) 2013,  ZOPYX Limited, D-72074 Tuebingen, Germany
+################################################################
+
+import os
+import lxml
+import cgi
+import re
+import PIL.Image
+from cStringIO import StringIO
+from pp.client.plone.logger import LOG