diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..0af041f89 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.devcontainer +.github +.ropeproject +.vscode +build +dist +docker/alpine/var +docker/elastic +docker/solr +docker/ubuntu/var +docker/zeo/var +docker/zope/var diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3a996cd83..ff9124beb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,48 +2,49 @@ name: tests on: - push: - pull_request: - schedule: - - cron: '0 12 * * 0' # run once a week on Sunday - # Allow to run this workflow manually from the Actions tab - workflow_dispatch: + push: + pull_request: + schedule: + - cron: "0 12 * * 0" # run once a week on Sunday + # Allow to run this workflow manually from the Actions tab + workflow_dispatch: jobs: - build: - strategy: - # We want to see all failures: - fail-fast: false - matrix: - os: - - ubuntu - config: - - "3.8" - - "3.9" - - "3.10" - - "3.11" - - "3.12" - runs-on: ${{ matrix.os }}-latest - name: ${{ matrix.os }}-${{ matrix.config }} - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.config }} - - name: Pip cache - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ matrix.config }}-${{ hashFiles('setup.*', 'requirements*') }} - restore-keys: | - ${{ runner.os }}-pip-${{ matrix.config }}- - ${{ runner.os }}-pip- - - name: Install dependencies - run: | - python -m pip install --upgrade pip pytest - pip install -e . - - name: Test - run: | - tree -L 1 - pytest ./tests/ + build: + strategy: + # We want to see all failures: + fail-fast: false + matrix: + os: + - ubuntu + config: + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + runs-on: ${{ matrix.os }}-latest + name: ${{ matrix.os }}-${{ matrix.config }} + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.config }} + - name: Pip cache + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ matrix.config }}-${{ hashFiles('setup.*', 'requirements*') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.config }}- + ${{ runner.os }}-pip- + - name: Install dependencies + run: | + python -m pip install --upgrade pip pytest + pip install -e . + - name: Test + run: | + tree -L 1 + pytest ./tests/ diff --git a/.vscode/Native.code-workspace b/.vscode/Native.code-workspace index 65812c41b..25a745f4f 100644 --- a/.vscode/Native.code-workspace +++ b/.vscode/Native.code-workspace @@ -3,16 +3,47 @@ { "name": "ZMS5", "path": "../" - } + }, + // { + // "name": "ZMS4", + // "path": "../../ZMS4" + // }, + // { + // "name": "ZMS3", + // "path": "../../ZMS3" + // }, + // { + // "name": "Zope 5", + // "path": "/home/zope/src/zopefoundation/Zope" + // }, + // { + // "name": "ZMS5 Dev Instance", + // "path": "/home/zope/instance/zms5_dev" + // }, + // { + // "name": "OpenSearchServer", + // "path": "/home/zope/src/sntl-projects/opensearch_demo" + // }, + // { + // "name": "Zope-WebDAV Access", + // "uri": "webdav://admin:admin@localhost:8091/" + // }, + // { + // "path": "../../../../vpy38/lib/python3.8/site-packages/pydoctor" + // } ], + "settings": { - "python.defaultInterpreterPath": "~/vpy38/bin/python", + "python.defaultInterpreterPath": "~/vpy313/bin/python", "window.zoomLevel": 0, "git.ignoreMissingGitWarning": true, "editor.minimap.enabled": false, "editor.renderWhitespace": "all", "editor.renderControlCharacters": false, "workbench.iconTheme": "vs-minimal", + "workbench.colorTheme": "Visual Studio Light", + "files.eol": "\n", + "files.autoSave": "afterDelay", "files.associations": { "*.zpt": "html", "*.zcml": "xml" @@ -33,10 +64,47 @@ "**/Data.*": true, "**/docker/**/var/*": true }, - "files.eol": "\n", - "files.autoSave": "afterDelay", - "workbench.colorTheme": "Visual Studio Light", - "python.linting.enabled": true + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true, + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "test*.py" + ], }, - "remoteAuthority": "wsl+Ubuntu" + + "launch": { + "version": "0.2.0", + "configurations": [ + { + "name": "ZMS5-DEV", + "type": "debugpy", + "request": "launch", + "program": "~/vpy313/bin/runwsgi", + "justMyCode": false, + "console": "integratedTerminal", + "args": [ + "--debug", + "--verbose", + "~/instance/zms5_dev/etc/zope.ini", + "debug-mode=on" + ], + "env": { + "PYTHONUNBUFFERED": "1", + "CONFIG_FILE": "~/instance/zms5_dev/etc/zope.ini", + "INSTANCE_HOME": "~/instance/zms5_dev", + "CLIENT_HOME": "~/instance/zms5_dev", + "PYTHON": "~/vpy313/bin/python", + "SOFTWARE_HOME": "~/vpy313/bin/" + }, + "serverReadyAction": { + "pattern": "Serving on http://127.0.0.1:8081", + "uriFormat": "http://127.0.0.1:8081/manage_main", + "action": "openExternally" + } + } + ] + } } \ No newline at end of file diff --git a/.vscode/ZMS5.code-workspace b/.vscode/ZMS5.code-workspace deleted file mode 100644 index 6139bc265..000000000 --- a/.vscode/ZMS5.code-workspace +++ /dev/null @@ -1,70 +0,0 @@ -{ - // Copy of Native.code-workspace - "folders": [ - { - "name": "ZMS5", - "path": "../" - }, - // { - // "name": "ZMS4", - // "path": "../../ZMS4" - // }, - // { - // "name": "ZMS3", - // "path": "../../ZMS3" - // }, - { - "name": "Zope 5", - "path": "/home/zope/src/zopefoundation/Zope" - }, - { - "name": "ZMS5 Dev Instance", - "path": "/home/zope/instance/zms5_dev" - }, - { - "name": "OpenSearchServer", - "path": "/home/zope/src/sntl-projects/opensearch_demo" - }, - // { - // "name": "Zope-WebDAV Access", - // "uri": "webdav://admin:admin@localhost:8091/" - // }, - // { - // "path": "../../../../vpy38/lib/python3.8/site-packages/pydoctor" - // } - ], - "settings": { - "python.defaultInterpreterPath": "~/vpy38/bin/python", - "window.zoomLevel": 0, - "git.ignoreMissingGitWarning": true, - "editor.minimap.enabled": false, - "editor.renderWhitespace": "all", - "editor.renderControlCharacters": false, - "workbench.iconTheme": "vs-minimal", - "files.associations": { - "*.zpt": "html", - "*.zcml": "xml" - }, - "scm.alwaysShowActions": true, - "files.exclude": { - "*.pyc": true, - "*-all.min.*":true, - "**/cache/**": true, - "**/Data.*": true, - "**/docker/**/var/*": true - }, - "search.exclude": { - "**/apidocs/**": true, - "*.pyc": true, - "*-all.min.*":true, - "**/cache/**": true, - "**/Data.*": true, - "**/docker/**/var/*": true - }, - "files.eol": "\n", - "files.autoSave": "afterDelay", - "workbench.colorTheme": "Visual Studio Light", - "python.linting.enabled": true - }, - "remoteAuthority": "wsl+Ubuntu" -} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 5fa11c32d..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "version": "0.2.0", - - "configurations": [ - { - "name": "ZMS5-DEV", - "type": "python", - "request": "launch", - "program": "~/vpy38/bin/runwsgi", - "justMyCode": false, - "console": "integratedTerminal", - "args": [ - "--debug", - "--verbose", - "~/instance/zms5_dev/etc/zope.ini", - "debug-mode=on", - // "http_port=8086", - ], - "env": { - "PYTHONUNBUFFERED":"1", - "CONFIG_FILE": "~/instance/zms5_dev/etc/zope.ini", - "INSTANCE_HOME": "~/instance/zms5_dev", - "CLIENT_HOME": "~/instance/zms5_dev", - "PYTHON": "~/vpy38/bin/python", - "SOFTWARE_HOME": "~/vpy38/bin/" - }, - "serverReadyAction":{ - "pattern":"Serving on http://127.0.0.1:8081", - "uriFormat": "http://127.0.0.1:8081/manage_main", - "action": "openExternally", - }, - }, - - - - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d2c4db08f..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "python.defaultInterpreterPath": "~/vpy38/bin/python3", - "python.testing.pytestEnabled": false, - "python.testing.unittestEnabled": true, - "python.testing.unittestArgs": [ - "-v", - "-s", - "./tests", - "-p", - "test*.py" - ], -} diff --git a/Products/zms/ZMSMetaobjManager.py b/Products/zms/ZMSMetaobjManager.py index 7f88fbfb9..cc9ded410 100644 --- a/Products/zms/ZMSMetaobjManager.py +++ b/Products/zms/ZMSMetaobjManager.py @@ -1176,14 +1176,16 @@ def manage_changeProperties(self, lang, btn='', key='all', REQUEST=None, RESPONS # Upload resource. if newType in self.valid_uploadtypes or isinstance(newCustom, ZPublisher.HTTPRequest.FileUpload): if len(getattr(newCustom, 'filename', '')): - newCustom = _blobfields.createBlobField( self, _blobfields.MyFile, newCustom) + newCustom = _blobfields.createBlobField( self, _blobfields.MyFile, newCustom) else: - savedAttr = [x for x in savedAttrs if x['id'] == old_id][0] - syncZopeMetaobjAttr( self, newValue, savedAttr) - if savedAttr.get('ob'): - filename = savedAttr['ob'].title - data = bytes(zopeutil.readData(savedAttr['ob'])) - newCustom = _blobfields.createBlobField( self, _blobfields.MyFile, {'filename':filename,'data':data}) + old_savedAttrs = [x for x in savedAttrs if x['id'] == old_id] + if old_savedAttrs: + savedAttr = old_savedAttrs[0] + syncZopeMetaobjAttr( self, newValue, savedAttr) + if savedAttr.get('ob'): + filename = savedAttr['ob'].title + data = bytes(zopeutil.readData(savedAttr['ob'])) + newCustom = _blobfields.createBlobField( self, _blobfields.MyFile, {'filename':filename,'data':data}) # Change attribute. message += self.setMetaobjAttr( id, old_id, attr_id, newName, newMandatory, newMultilang, newRepetitive, newType, newKeys, newCustom, newDefault) # Return with message. diff --git a/Products/zms/ZMSZCatalogAdapter.py b/Products/zms/ZMSZCatalogAdapter.py index 921eb5660..7830c27a1 100644 --- a/Products/zms/ZMSZCatalogAdapter.py +++ b/Products/zms/ZMSZCatalogAdapter.py @@ -49,6 +49,7 @@ def get_default_data(node): d['lang'] = request.get('lang',node.getPrimaryLanguage()) d['created_dt'] = get_zoned_dt(node.attr('created_dt')) d['change_dt'] = get_zoned_dt(node.attr('change_dt')) or d['created_dt'] + d['indexing_dt'] = get_zoned_dt(time.gmtime()) return d def get_zoned_dt(struct_dt): @@ -169,7 +170,6 @@ def traverse(node, recursive): # ZMSZCatalogAdapter.reindex_node # -------------------------------------------------------------------------- def reindex_node(self, node): - standard.writeBlock(node, "[reindex_node]") connectors = [] fileparsing = False try: @@ -183,28 +183,31 @@ def reindex_node(self, node): container_nodes = standard.difference_list(breadcrumbs, page_nodes) container_nodes.append(container_page) filtered_container_nodes = [e for e in container_nodes if self.matches_ids_filter(e)] + # Hint: getCatalogAdapter prefers local adapter, otherwise root adapter. + connectors = node.getCatalogAdapter().get_connectors() if filtered_container_nodes: - # Hint: getCatalogAdapter prefers local adapter, otherwise root adapter. fileparsing = standard.pybool(node.getConfProperty('ZMS.CatalogAwareness.fileparsing', 1)) - connectors = node.getCatalogAdapter().get_connectors() # Reindex filtered container node's content by each connector. for connector in connectors: for filtered_container_node in filtered_container_nodes: self.reindex(connector, filtered_container_node, recursive=False, fileparsing=fileparsing) + else: + # Remove from catalog if editing leads to filter-not-matching. + for connector in connectors: + connector.manage_objects_remove([container_page]) return True except: standard.writeError( self, "can't reindex_node") return False # -------------------------------------------------------------------------- - # ZMSZCatalogAdapter.unindex_node + # ZMSZCatalogAdapter.unindex_nodes # -------------------------------------------------------------------------- def unindex_nodes(self, nodes=[], forced=False): # Is triggered by zmscontainerobject.moveObjsToTrashcan(). # Todo: ensure param 'nodes' does contain all ids to be indexed # to avoid sequentially unindexing leading to redundant reindexing # on the same page-node. - standard.writeBlock(self, "[unindex_nodes]") try: if self.getConfProperty('ZMS.CatalogAwareness.active', 1) or forced: # [1] Reindex page-container nodes of deleted page-elements. @@ -221,11 +224,12 @@ def unindex_nodes(self, nodes=[], forced=False): self.reindex_node(node=page_node) # [2] Unindex deleted nodes (from trashcan) if filter-match. delnodes = [delnode for delnode in nodes[0].getParentNode().getTrashcan().objectValues() if ( ( delnode in nodes) and self.matches_ids_filter(delnode) )] - for connector in self.getCatalogAdapter().get_connectors(): + connectors = self.getCatalogAdapter().get_connectors() + for connector in connectors: connector.manage_objects_remove(delnodes) return True except: - standard.writeError( self, "unindex_nodes not successful") + standard.writeError( self, "can't unindex_nodes") return False # -------------------------------------------------------------------------- @@ -255,7 +259,7 @@ def setIds(self, ids): # getter and setter for custom filter-function # -------------------------------------------------------------------------- def getCustomFilterFunction(self): - return getattr(self, '_custom_filter_function', '##\nreturn context.meta_id in meta_ids') + return getattr(self, '_custom_filter_function', '##\nreturn context.meta_id in meta_ids\\\n and (context.isVisible(context.REQUEST))') def setCustomFilterFunction(self, custom_filter_function): setattr(self, '_custom_filter_function', custom_filter_function) @@ -432,4 +436,3 @@ def manage_changeProperties(self, btn, lang, REQUEST, RESPONSE): return RESPONSE.redirect('manage_main?lang=%s&manage_tabs_message=%s#%s'%(lang, message, REQUEST.get('tab'))) ################################################################################ - diff --git a/Products/zms/_fileutil.py b/Products/zms/_fileutil.py index b13c37fe1..13bda40b3 100644 --- a/Products/zms/_fileutil.py +++ b/Products/zms/_fileutil.py @@ -419,6 +419,8 @@ def extractZipArchive(file): zf = zipfile.ZipFile( file, 'r') for name in zf.namelist(): + if name.startswith('__MACOSX/') or name.endswith('.DS_Store'): + continue dir = getOSPath( name) i = dir.rfind( os.sep) if i > 0: @@ -516,4 +518,4 @@ def tail_lines(filename,linesback=10,returnlist=0): for l in lines[start:len(lines)-1]: out=out + l + "\n" return out -################################################################################ \ No newline at end of file +################################################################################ diff --git a/Products/zms/_importable.py b/Products/zms/_importable.py index 248fd9b8e..f1fee72ef 100644 --- a/Products/zms/_importable.py +++ b/Products/zms/_importable.py @@ -74,8 +74,11 @@ def recurse_importContent(self, folder): self.setObjProperty(key, blob, lang) # Commit object. - self.onChangeObj( self.REQUEST, forced=1) - + try: + self.onChangeObj( self.REQUEST, forced=1) + except: + standard.writeBlock( self, '[recurse_importContent]: %s commitObject failed'%(self.getId())) + # Process children. for ob in self.getChildNodes(): recurse_importContent(ob, folder) diff --git a/Products/zms/_mediadb.py b/Products/zms/_mediadb.py index 032a22eb4..1cbe22c93 100644 --- a/Products/zms/_mediadb.py +++ b/Products/zms/_mediadb.py @@ -400,6 +400,7 @@ def retrieveFileStreamIterator(self, filename, REQUEST=None): standard.writeBlock(self, msg) filename = 'file_not_found_0.txt' mt, enc, data, fsize = 'text/plain', 'utf-8', msg, len(msg) + REQUEST.response.setStatus(404) else: # File found. fsize = os.path.getsize( local_filename) diff --git a/Products/zms/conf/metacmd_manager/manage_export_pydocx/__init__.py b/Products/zms/conf/metacmd_manager/manage_export_pydocx/__init__.py index 170ba132b..059bdc07f 100644 --- a/Products/zms/conf/metacmd_manager/manage_export_pydocx/__init__.py +++ b/Products/zms/conf/metacmd_manager/manage_export_pydocx/__init__.py @@ -34,7 +34,7 @@ class manage_export_pydocx: package = "com.zms.foundation.export" # Revision - revision = "5.0.0" + revision = "5.0.1" # Roles roles = ["ZMSAdministrator"] diff --git a/Products/zms/conf/metacmd_manager/manage_export_pydocx/manage_export_pydocx.py b/Products/zms/conf/metacmd_manager/manage_export_pydocx/manage_export_pydocx.py index 3b02d273b..781bb7b70 100644 --- a/Products/zms/conf/metacmd_manager/manage_export_pydocx/manage_export_pydocx.py +++ b/Products/zms/conf/metacmd_manager/manage_export_pydocx/manage_export_pydocx.py @@ -186,6 +186,7 @@ def add_hyperlink(docx_block, link_text, url): url_base = 'http://neon/' # Omit javascript links if not url.startswith('javascript:'): + url = url.replace('mailto:', '') # Fix missing domain name url = ('http' in url) and url.replace('http:///', url_base) or (url_base + (url.startswith('/') and url[1:] or url)) r_id = docx_block.part.relate_to(url, docx.opc.constants.RELATIONSHIP_TYPE.HYPERLINK, is_external=True) @@ -222,7 +223,7 @@ def add_hyperlink(docx_block, link_text, url): # ############################################# # Clean HTML -def clean_html(html): +def clean_html(html, wrap_trailling_text=False): """ Clean comments, styles, empty tags and handle special characters: left-to-right, triangle @@ -244,6 +245,9 @@ def clean_html(html): html = html.replace(left_to_right_char,'') html = html.replace('[[', triangle_char) html = html.replace(']]', '') + if wrap_trailling_text: + # Wrap untagged text following a block element into a paragraph + html = re.sub(r'(?i)(?m)(
\g<2>
', html)
return html
# ADD RUNS TO DOCX-BLOCK
@@ -276,6 +280,8 @@ def add_runs(docx_block, bs_element):
docx_block.add_run(u'\U0000F021', style='Icon')
elif elrun.has_attr('class') and 'fa-phone' in elrun['class']:
docx_block.add_run(u'\U0000F028', style='Icon')
+ elif elrun.has_attr('class') and 'fa-exclamation-triangle' in elrun['class']:
+ docx_block.add_run(u'\U0000F045', style='Icon')
elif elrun.text != '':
docx_block.add_run(elrun.text).italic = True
elif elrun.text != '':
@@ -294,6 +300,8 @@ def add_runs(docx_block, bs_element):
docx_block.add_run(elrun.text).font.subscript = True
elif elrun.name == 'sup':
docx_block.add_run(elrun.text).font.superscript = True
+ elif elrun.name == 'u':
+ docx_block.add_run(elrun.text).underline = True
elif elrun.name == 'a':
if elrun.has_attr('href'):
add_hyperlink(docx_block = docx_block, link_text = elrun.text, url = elrun.get('href'))
@@ -355,6 +363,7 @@ def add_tagged_content_as_paragraph(docx_doc, bs_element, style_name="Standard",
def add_htmlblock_to_docx(zmscontext, docx_doc, htmlblock, zmsid=None, zmsmetaid=None):
# Clean HTML
htmlblock = clean_html(htmlblock)
+ htmlblock = htmlblock.strip()
heading_text = ''
# Apply BeautifulSoup and iterate over elements
soup = BeautifulSoup(htmlblock, 'html.parser')
@@ -376,20 +385,18 @@ def add_htmlblock_to_docx(zmscontext, docx_doc, htmlblock, zmsid=None, zmsmetaid
prepend_bookmark(p, zmsid)
else:
# #############################################
- # HTML-Elements, element.name != None
- # #############################################
-
- # #############################################
+ # BLOCK-Elements, element.name != None
+ # ---------------------------------------------
# HEADINGS
# #############################################
- if element.name in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
+ if element.name in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8']:
heading_level = int(element.name[1])
heading_text = standard.pystr(element.text).strip()
p = add_heading(docx_doc, heading_text, level=heading_level)
if c==1 and zmsid:
prepend_bookmark(p, zmsid)
if element.text == 'Inhaltsverzeichnis':
- p.style = docx_doc.styles['TOC-Header']
+ p.style = doc.styles['TOC-Header']
# #############################################
# PARAGRAPH
# #############################################
@@ -401,11 +408,14 @@ def add_htmlblock_to_docx(zmscontext, docx_doc, htmlblock, zmsid=None, zmsmetaid
# htmlblock.__contains__('ZMSTable') or htmlblock.__contains__('img')
if element.has_attr('class'):
if 'caption' in element['class'] and zmsmetaid in ['ZMSGraphic', 'ZMSTable']:
- p.style = docx_doc.styles['caption']
+ p.style = doc.styles['caption']
else:
class_name = element['class'][0]
- style_name = (class_name in docx_doc.styles) and class_name or 'Normal'
- p.style = docx_doc.styles[style_name]
+ try:
+ style_name = (class_name in doc.styles) and class_name or 'Normal'
+ except:
+ style_name = 'Normal'
+ p.style = doc.styles[style_name]
add_runs(docx_block = p, bs_element = element)
## Remove empty paragraphs
@@ -495,7 +505,7 @@ def add_list(docx_obj, element, level=0, c=0):
# ------------------------------------------------
def convert_cell_html_to_docx(zmscontext, docx_cell, text_style='Normal'):
'''Convert cell html to docx'''
- cl_html = clean_html(docx_cell.text)
+ cl_html = clean_html(docx_cell.text, wrap_trailling_text=True)
cl_type = cl_html.startswith('[th:') and 'th' or 'td'
cl_html = re.sub(r'\[(th|td):\d:\d\] ','',cl_html)
cl = BeautifulSoup(cl_html, 'html.parser')
@@ -509,7 +519,14 @@ def convert_cell_html_to_docx(zmscontext, docx_cell, text_style='Normal'):
try:
if {'div','ol','ul','table','p'} & set([e.name for e in cl.children]):
# [A] Block elements
- add_htmlblock_to_docx(zmscontext, docx_cell, cl_html, zmsid=None)
+ try:
+ add_htmlblock_to_docx(zmscontext, docx_cell, cl_html, zmsid=None)
+ except:
+ p.add_run('Rendering Error Table-Cell: %s'%cl.text)
+ # Cleaning: remove first cell paragraph if empty
+ if docx_cell.paragraphs[0].text == '':
+ first_p = docx_cell.paragraphs[0]._element
+ docx_cell._tc.remove(first_p)
elif set([e.name for e in cl.children])==set([None]):
# [B] Just text
p.text = cl.text
@@ -547,7 +564,7 @@ def convert_cell_html_to_docx(zmscontext, docx_cell, text_style='Normal'):
img_src = zmscontext.operator_getattr(zmscontext,zmsid).attr('imghires').getHref(zmscontext.REQUEST)
except:
pass
- img_name = img_src.split('/')[-1]
+ img_name = img_src.split('?')[0].split('/')[-1]
if not img_src.startswith('http'):
src_url0 = zmscontext.absolute_url().split('/content/')[0]
src_url1 = img_src.split('/content/')[-1]
@@ -583,12 +600,34 @@ def convert_cell_html_to_docx(zmscontext, docx_cell, text_style='Normal'):
elif element.name == 'div':
if element.has_attr('class') and (('ZMSGraphic' in element['class']) or ('graphic' in element['class'])):
ZMSGraphic_html = standard.pystr(''.join([str(e) for e in element.children]))
+ zmsid = element.has_attr('id') and element['id'] or zmsid
+ zmscontext = zmscontext.operator_getattr(zmscontext,zmsid)
add_htmlblock_to_docx(zmscontext, docx_doc, ZMSGraphic_html, zmsid, zmsmetaid='ZMSGraphic')
elif element.has_attr('class') and ('ZMSTextarea' in element['class']):
ZMSTextarea_html = standard.pystr(''.join([str(e) for e in element.children]))
+ zmsid = element.has_attr('id') and element['id'] or zmsid
+ zmscontext = zmscontext.operator_getattr(zmscontext,zmsid)
add_htmlblock_to_docx(zmscontext, docx_doc, ZMSTextarea_html, zmsid, zmsmetaid='ZMSTextarea')
elif element.has_attr('class') and 'handlungsaufforderung' in element['class']:
- add_tagged_content_as_paragraph(docx_doc, element, 'Handlungsaufforderung', c, zmsid)
+ if len([e.name for e in element.children if e.name in ['ul','ol']])>0:
+ add_tagged_content_as_paragraph(docx_doc, element, 'Handlungsaufforderung', c, zmsid)
+ child_tag = [e.name for e in element.children if e.name][0]
+ # COPY add_list
+ def add_list(docx_obj, element, level=0, c=0):
+ for i, li in enumerate(element.find_all('li', recursive=False)):
+ if docx_obj.paragraphs and docx_obj.paragraphs[-1].text == '':
+ p = docx_obj.paragraphs[-1]
+ else:
+ p = docx_obj.add_paragraph()
+ p = set_block_as_listitem(p, list_type=element.name, level=level, i=i)
+ add_runs(docx_block = p, bs_element = li)
+ if c==1 and zmsid:
+ prepend_bookmark(p, zmsid)
+ for ul in li.find_all(['ul','ol'], recursive=False):
+ add_list(docx_doc, ul, level+1)
+ add_list(docx_doc, element.find(child_tag), level=1, c=c)
+ else:
+ add_tagged_content_as_paragraph(docx_doc, element, 'Handlungsaufforderung', c, zmsid)
elif element.has_attr('class') and 'grundsatz' in element['class']:
add_tagged_content_as_paragraph(docx_doc, element, 'Grundsatz', c, zmsid)
elif element.has_attr('style') and 'background: rgb(238, 238, 238)' in element['style'] \
@@ -601,14 +640,14 @@ def convert_cell_html_to_docx(zmscontext, docx_cell, text_style='Normal'):
add_runs(docx_block = p, bs_element = element)
else:
child_tags = [e.name for e in element.children if e.name]
- if {'em','strong','i', 'span'} & set(child_tags):
+ if {'em','strong','i','span','u'} & set(child_tags):
p = docx_doc.add_paragraph()
if c==1 and zmsid:
prepend_bookmark(p, zmsid)
if len(element.contents) == 1:
if element.has_attr('class'):
- style_name = (class_name in docx_doc.styles) and class_name or 'Normal'
- p.style = docx_doc.styles[style_name]
+ style_name = (class_name in doc.styles) and class_name or 'Normal'
+ p.style = doc.styles[style_name]
p.add_run(element.text)
elif len(element.contents) > 1:
for e in element.contents:
@@ -621,8 +660,15 @@ def convert_cell_html_to_docx(zmscontext, docx_cell, text_style='Normal'):
# p.add_run('Routing: ')
p.add_run(u'\U0000F028', style='Icon')
p.add_run(' ')
+ elif 'fa-exclamation-triangle' in class_name:
+ # p.add_run('Kommentar: ')
+ p.add_run(u'\U0000F045', style='Icon')
+ p.add_run(' ')
if list(e.children)!=[]:
- add_runs(docx_block = p, bs_element = e)
+ if [ch.name for ch in e.children if ch.name in ['p', 'ol', 'ul', 'div']]:
+ add_htmlblock_to_docx(zmscontext, docx_doc, standard.pystr(e), zmsid)
+ else:
+ add_runs(docx_block = p, bs_element = e)
else:
p.add_run(standard.pystr(e.text))
elif e.name:
@@ -678,8 +724,9 @@ def convert_cell_html_to_docx(zmscontext, docx_cell, text_style='Normal'):
for input_field in element.find_all('input', recursive=True):
input_field_count += 1
p.add_run('%s. : %s\n'%(input_field_count, input_field.get('name','')))
+
# #############################################
- # OTHERS
+ # OTHER ELEMENTS
# #############################################
elif element.name == 'hr':
# Omit horizontal rule
@@ -687,6 +734,9 @@ def convert_cell_html_to_docx(zmscontext, docx_cell, text_style='Normal'):
elif element.name == 'script':
# Omit javascript
pass
+ elif element.name == 'style':
+ # Omit style
+ pass
else:
try:
if element.has_text:
@@ -705,7 +755,7 @@ def add_breadcrumbs_as_runs(zmscontext, p):
c = 0
for obj in breadcrumbs:
c += 1
- link_text = obj.meta_id == 'ZMS' and standard.pystr(obj.attr('title')) or standard.pystr(obj.attr('titlealt'))
+ link_text = obj.meta_id == 'ZMS' and standard.pystr(obj.attr('title')) or standard.pystr(obj.getTitlealt(zmscontext.REQUEST))
add_hyperlink(docx_block = p, link_text = link_text, url = obj.getHref2IndexHtml(zmscontext.REQUEST))
if c < len(breadcrumbs):
p.add_run(' > ')
@@ -755,7 +805,9 @@ def apply_standard_json_docx(self):
zmscontext = self
request = zmscontext.REQUEST
+ # For debugging use preview content
# request.set('preview', 'preview')
+ # #################################
is_page = zmscontext.isPage()
id = zmscontext.id
@@ -794,7 +846,7 @@ def apply_standard_json_docx(self):
pageelements = [ \
e for e in zmscontext.getChildNodes(request) \
if ( ( e.getType() in [ 'ZMSObject', 'ZMSRecordSet'] ) \
- and not e.meta_id in [ 'LgChangeHistory','ZMSTeaserContainer','LgELearningBanner'] \
+ and not e.meta_id in [ 'LgChangeHistory','ZMSTeaserContainer'] \
and not e.isPage() ) \
or e.meta_id in [ 'ZMSLinkElement' ]
]
@@ -832,7 +884,7 @@ def apply_standard_json_docx(self):
'parent_id':parent_id,
'parent_meta_id':parent_meta_id,
'docx_format':'image',
- 'imgwidth': imgwidth,
+ 'imgwidth': imgwidth,
'imgheight':imgheight,
'content':img_url
},
@@ -938,7 +990,7 @@ def apply_standard_json_docx(self):
}]
# Give some customizing hints for standard_html
- if pageelement.meta_id in ['LgRegel','LgBedingung','LgELearningBanner','ZMSNote']:
+ if pageelement.meta_id in ['LgRegel','LgBedingung','LgELearningBanner','ZMSNote','ZMSTestarea']:
standard.writeStdout(None, 'IMPORTANT NOTE: %s.standard_html needs to be customized!'%(pageelement.meta_id))
# %<---- CUSTOMIZE LIKE THIS ---------------------
# zmi python:request['URL'].find('/manage')>0 and not request['URL'].find('pydocx')>0;
@@ -1057,6 +1109,7 @@ def add_heading(self, text, level=1):
# binary data of the DOCX file.
def manage_export_pydocx(self, save_file=True, file_name=None):
request = self.REQUEST
+ request.set('lang', self.getPrimaryLanguage())
docx_creator = request.AUTHENTICATED_USER.getUserName()
# PAGE_COUNTER: Counter for recursive export
@@ -1207,22 +1260,21 @@ def manage_export_pydocx(self, save_file=True, file_name=None):
# #############################################
# [4] CAPTION TEXT-BLOCK
elif v and block['docx_format']=='Caption':
- if re.match(r'^\[Abb. e\d+\] .*', v):
- capt_list = re.split(r'^\[Abb. e\d+\] ', v)
- if len(capt_list) > 1 and len(capt_list[1]) > 0:
- p = doc.add_paragraph(style='Caption')
- prepend_bookmark(p, block['id'])
- p.add_run('Abb. %s: '%block['id']).font.italic = False
- p.add_run(capt_list[1])
- elif re.match(r'^\[Abb. e\d+\] ', v):
- # Omit caption with empty text
- pass
+ p = doc.add_paragraph(style='Caption')
+ if re.match(r'^\[Abb\. e\d+\] .*', v):
+ re_list = re.split(r'^(\[Abb. e\d+\]) (.*)',v)
+ v1 = re_list[1]
+ v2 = BeautifulSoup(re_list[2], 'html.parser').get_text()
+ p.add_run(v1).font.italic = False
+ p.add_run(' ')
+ p.add_run(v2)
else:
- p = doc.add_paragraph(style='Caption')
- prepend_bookmark(p, block['id'])
+ p.add_run(v)
+ prepend_bookmark(p, block['id'])
+
# #############################################
# [5] TEXT-BLOCK with given block format (style)
- elif v and block['docx_format'] in [e.name for e in doc.styles]:
+ elif v and ( block['docx_format'] in [e.name for e in doc.styles] or block['docx_format'] in [e.name.replace(' ','') for e in doc.styles] ):
p = doc.add_paragraph(v, style=block['docx_format'])
prepend_bookmark(p, block['id'])
elif v:
diff --git a/Products/zms/conf/metacmd_manager/manage_export_pydocx/neon.docx b/Products/zms/conf/metacmd_manager/manage_export_pydocx/neon.docx
index 44770a8f2..0d3d8a091 100644
Binary files a/Products/zms/conf/metacmd_manager/manage_export_pydocx/neon.docx and b/Products/zms/conf/metacmd_manager/manage_export_pydocx/neon.docx differ
diff --git a/Products/zms/conf/metacmd_manager/manage_export_pydocx/readme.md b/Products/zms/conf/metacmd_manager/manage_export_pydocx/readme.md
index 255a4bdcd..85ca1862b 100644
--- a/Products/zms/conf/metacmd_manager/manage_export_pydocx/readme.md
+++ b/Products/zms/conf/metacmd_manager/manage_export_pydocx/readme.md
@@ -17,6 +17,19 @@ pip install python-docx
## Configuration and Customization
-Ensure that the script is configured correctly with the necessary parameters for your specific use case (especially the global variable `docx_tmpl` as filesystem path to the DOCX file that is used as a template). You may need to modify the script to fit your data source and desired output format.
-Some (complex) ZMS content objects may need another template `standard_json_docx` (Python script) to generate a normalized JSON representation of the object's content. The standard content model contains some examples of the script. For further details, please refer to the docstring of
+Ensure that the script is configured correctly with the necessary parameters for your specific use case, especially the global variable `docx_tmpl` as filesystem path to the DOCX file that is used as a template:
+
+```py
+# Set local path for docx-template
+docx_tmpl = open("/home/zope/src/zms-publishing/ZMS5/Products/zms/conf/metacmd_manager/manage_export_pydocx/neon.docx", "rb")
+```
+
+You may prefer to export not the committed but the working content, so set the REQUEST-variable:
+
+```py
+# For debugging use preview content
+request.set('preview', 'preview')
+```
+
+Furthermore You may need to modify the script to fit your data source and desired output format. Some (complex) ZMS content objects may need another template `standard_json_docx` (Python script) to generate a normalized JSON representation of the object's content. The standard content model contains some examples of the script. For further details, please refer to the docstring of
`manage_export_pydocx.apply_standard_json_docx()`.
diff --git a/Products/zms/conf/metacmd_manager/manage_export_pydocx/styles.xml b/Products/zms/conf/metacmd_manager/manage_export_pydocx/styles.xml
index 2f7d1fd40..981f0c4e9 100644
--- a/Products/zms/conf/metacmd_manager/manage_export_pydocx/styles.xml
+++ b/Products/zms/conf/metacmd_manager/manage_export_pydocx/styles.xml
@@ -1,12 +1,17 @@
-