';
+ inParagraph = true;
+ }
+ var docxPart = '';
+ var hyperlink = false;
+ tagStack.forEach(function (tag) {
+ switch (tag.tag) {
+ case 'b':
+ case 'strong':
+ docxPart += '';
+ break;
+ case 'em':
+ case 'i':
+ docxPart += '';
+ break;
+ case 'span':
+ for (var key in tag.attrs) {
+ switch (key) {
+ case 'color':
+ docxPart += '';
+ break;
+ case 'backgroundColor':
+ docxPart += '';
+ break;
+ case 'underline':
+ docxPart += '';
+ break;
+ case 'strike':
+ docxPart += '';
+ break;
+ }
+ }
+ break;
+ case 'u':
+ docxPart += '';
+ break;
+ case 'strike':
+ docxPart += '';
+ break;
+ case 'a':
+ var id = converter.relationships.length + 1;
+ docxPart = '' + docxPart;
+ docxPart += '';
+ converter.relationships.push({
+ Id: 'rrId' + id,
+ Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
+ Target: tag.href,
+ TargetMode: 'External'
+ });
+ hyperlink = true;
+ break;
+ }
+ });
+ docxPart += '' + text + '';
+ if (hyperlink) {
+ docxPart += '';
+ }
+
+ // append to docx
+ docx += docxPart;
+ return docx;
+ };
+
+ var replaceEntities = function () {
+ // replacing of special symbols:
+ docx = docx.replace(new RegExp('\ä\;', 'g'), 'ä');
+ docx = docx.replace(new RegExp('\ü\;', 'g'), 'ü');
+ docx = docx.replace(new RegExp('\ö\;', 'g'), 'ö');
+ docx = docx.replace(new RegExp('\Ä\;', 'g'), 'Ä');
+ docx = docx.replace(new RegExp('\Ü\;', 'g'), 'Ü');
+ docx = docx.replace(new RegExp('\Ö\;', 'g'), 'Ö');
+ docx = docx.replace(new RegExp('\ß\;', 'g'), 'ß');
+ docx = docx.replace(new RegExp('\ \;', 'g'), ' ');
+ docx = docx.replace(new RegExp('\§\;', 'g'), '§');
+
+ // remove all entities except gt, lt and amp
+ var entityRegex = /\&(?!gt|lt|amp)\w+\;/g, matchEntry, indexes = [];
+ while ((matchEntry = entityRegex.exec(docx)) !== null) {
+ indexes.push({
+ startId: matchEntry.index,
+ stopId: matchEntry.index + matchEntry[0].length
+ });
+ }
+ for (var i = indexes.length - 1; i>=0; i--) {
+ docx = docx.substring(0, indexes[i].startId) + docx.substring(indexes[i].stopId, docx.length);
+ }
+ };
+
+ var parse = function () {
+ if (html.substring(0,3) != '') {
+ docx += '';
+ skipFirstParagraphClosing = false;
+ }
+ html = html.split(/(<|>)/g);
+ // remove whitespaces and > brackets. Leave < brackets in there to check, whether
+ // the following string is a tag or text.
+ html = _.filter(html, function (part) {
+ var skippedCharsRegex = new RegExp('^([\s\n\r]|>)*$', 'gm');
+ return !skippedCharsRegex.test(part);
+ });
+
+ for (var i = 0; i < html.length; i++) {
+ if (html[i] === '<') {
+ i++;
+ handleTag(html[i]);
+ } else {
+ handleText(html[i]);
+ }
+ }
+ // for finishing close the last paragraph (if open)
+ if (inParagraph) {
+ docx += '';
+ }
+
+ replaceEntities();
+
+ return docx;
+ };
+
+ return parse();
+ };
+
+ // return a wrapper function for html2docx, that fetches all the images.
+ converter.html2docx = function (html) {
+ var imageSources = _.map($(html).find('img'), function (element) {
+ return element.getAttribute('src');
+ });
+ // Don't get images multiple times; just if the converter has not seen them befor.
+ imageSources = _.filter(imageSources, function (src) {
+ return !converter.imageMap[src];
+ });
+ return $q(function (resolve) {
+ ImageConverter.toBase64(imageSources).then(function (_imageMap) {
+ _.forEach(_imageMap, function (value, key) {
+ converter.imageMap[key] = value;
+ });
+ var docx = html2docx(html);
+ resolve(docx);
+ });
+ });
+ };
+
+ converter.updateZipFile = function (zip) {
+ var updateRelationships = function (oldContent) {
+ var content = oldContent.split('\n');
+ _.forEach(converter.relationships, function (relationship) {
+ content[1] += '';
+ });
+ return content.join('\n');
+ };
+ var updateContentTypes = function (oldContent) {
+ var content = oldContent.split('\n');
+ _.forEach(converter.contentTypes, function (type) {
+ content[1] += '';
+ });
+ return content.join('\n');
+ };
+ // update relationships from 'relationships'
+ var relationships = updateRelationships(zip.file('word/_rels/document.xml.rels').asText());
+ zip.file('word/_rels/document.xml.rels', relationships);
+
+ // update content type from 'contentTypes'
+ var contentTypes = updateContentTypes(zip.file('[Content_Types].xml').asText());
+ zip.file('[Content_Types].xml', contentTypes);
+
+ converter.documentImages = _.uniqBy(converter.documentImages, 'src');
+ _.forEach(converter.documentImages, function (image) {
+ var dataUrl = converter.imageMap[image.src].data;
+ var base64 = dataUrl.split(',')[1];
+ zip.file(image.zipPath, base64, {base64: true});
+ });
+ return zip;
+ };
+
+ return converter;
+ };
+
+ return {
+ createInstance: createInstance,
+ };
+ }
+]);
+
+})();
diff --git a/openslides/core/static/js/core/pdf.js b/openslides/core/static/js/core/pdf.js
index 2c10ad50d..d96286ab7 100644
--- a/openslides/core/static/js/core/pdf.js
+++ b/openslides/core/static/js/core/pdf.js
@@ -143,8 +143,8 @@ angular.module('OpenSlidesApp.core.pdf', [])
'$q',
'Config',
'PDFLayout',
- 'PdfImageConverter',
- function($q, Config, PDFLayout, PdfImageConverter) {
+ 'ImageConverter',
+ function($q, Config, PDFLayout, ImageConverter) {
/**
* Provides the global document
* @constructor
@@ -328,7 +328,7 @@ angular.module('OpenSlidesApp.core.pdf', [])
logoHeaderUrl,
logoFooterUrl
];
- PdfImageConverter.toBase64(imageSources).then(function (_imageMap) {
+ ImageConverter.toBase64(imageSources).then(function (_imageMap) {
imageMap = _imageMap;
resolve({
getDocument: getDocument
@@ -948,7 +948,7 @@ angular.module('OpenSlidesApp.core.pdf', [])
}
])
-.factory('PdfImageConverter', [
+.factory('ImageConverter', [
'$q',
'PDFLayout',
function ($q, PDFLayout) {
diff --git a/openslides/motions/static/js/motions/docx.js b/openslides/motions/static/js/motions/docx.js
index ed262dd15..46b2803b9 100644
--- a/openslides/motions/static/js/motions/docx.js
+++ b/openslides/motions/static/js/motions/docx.js
@@ -2,24 +2,28 @@
'use strict';
-angular.module('OpenSlidesApp.motions.docx', [])
+angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
.factory('MotionDocxExport', [
'$http',
'$q',
+ 'operator',
'Config',
'Category',
'gettextCatalog',
'FileSaver',
'lineNumberingService',
- function ($http, $q, Config, Category, gettextCatalog, FileSaver, lineNumberingService) {
+ 'Html2DocxConverter',
+ function ($http, $q, operator, Config, Category, gettextCatalog, FileSaver, lineNumberingService, Html2DocxConverter) {
var PAGEBREAK = '';
- var TAGS_NO_PARAM = ['b', 'strong', 'em', 'i'];
+ /*var TAGS_NO_PARAM = ['b', 'strong', 'em', 'i'];
var images;
var relationships;
- var contentTypes;
+ var contentTypes; */
+
+ var converter;
var getData = function (motions, params) {
var data = {};
@@ -48,11 +52,15 @@ angular.module('OpenSlidesApp.motions.docx', [])
// motions
data.tableofcontents_translation = gettextCatalog.getString('Table of contents');
- data.motions = getMotionFullData(motions, params);
data.motions_list = getMotionShortData(motions);
data.no_motions = gettextCatalog.getString('No motions available.');
- return data;
+ return $q(function (resolve) {
+ getMotionFullData(motions, params).then(function (motionData) {
+ data.motions = motionData;
+ resolve(data);
+ });
+ });
};
var getCategoriesData = function (categories) {
@@ -74,289 +82,95 @@ angular.module('OpenSlidesApp.motions.docx', [])
};
var getMotionFullData = function (motions, params) {
+ // All translations
var translation = gettextCatalog.getString('Motion'),
sequential_translation = gettextCatalog.getString('Sequential number'),
submitters_translation = gettextCatalog.getString('Submitters'),
status_translation = gettextCatalog.getString('Status'),
reason_translation = gettextCatalog.getString('Reason'),
- data = _.map(motions, function (motion) {
- var text = motion.getTextByMode(params.changeRecommendationMode, null, null, false);
- var reason = params.includeReason ? motion.getReason() : '';
- return {
- motion_translation: translation,
- sequential_translation: sequential_translation,
- id: motion.id,
- identifier: motion.identifier,
- title: motion.getTitle(),
- submitters_translation: submitters_translation,
- submitters: _.map(motion.submitters, function (submitter) {
- return submitter.get_full_name();
- }).join(', '),
- status_translation: status_translation,
- status: motion.getStateName(),
- preamble: gettextCatalog.getString(Config.get('motions_preamble').value),
- text: html2docx(text),
- reason_translation: reason.length === 0 ? '' : reason_translation,
- reason: html2docx(reason),
- pagebreak: PAGEBREAK,
- };
- });
- if (data.length) {
- // clear pagebreak on last element
- data[data.length - 1].pagebreak = '';
- }
- return data;
- };
+ comment_translation = gettextCatalog.getString('Comments');
+ // promises for create the actual motion data
+ var promises = _.map(motions, function (motion) {
+ var text = motion.getTextByMode(params.changeRecommendationMode, null, null, false);
+ var reason = params.includeReason ? motion.getReason() : '';
+ var comments = params.includeComments ? getMotionComments(motion) : [];
- var html2docx = function (html) {
- var docx = '';
- var stack = [];
-
- var isTag = false; // Even if html starts with '
') {
- docx += '';
- skipFirstParagraphClosing = false;
- }
- html = html.split(/(<|>)/g);
-
- html.forEach(function (part) {
- if (part !== '' && part != '\n' && part != '<' && part != '>') {
- if (isTag) {
- if (part.startsWith('p')) { /** p **/
- // Special: begin new paragraph (only if its the first):
- if (hasParagraph && !skipFirstParagraphClosing) {
- // End, if there is one
- docx += '';
- }
- skipFirstParagraphClosing = false;
- docx += '';
- hasParagraph = true;
- } else if (part.startsWith('/p')) {
- // Special: end paragraph:
- docx += '';
- hasParagraph = false;
-
- } else if (part.charAt(0) == "/") {
- // remove from stack
- stack.pop();
- } else { // now all other tags
- var tag = {};
- if (_.indexOf(TAGS_NO_PARAM, part) > -1) { /** b, strong, em, i **/
- stack.push({tag: part});
- } else if (part.startsWith('span')) { /** span **/
- tag = {tag: 'span', attrs: {}};
- var rStyle = /(?:\"|\;\s?)([a-zA-z\-]+)\:\s?([a-zA-Z0-9\-\#]+)/g, matchSpan;
- while ((matchSpan = rStyle.exec(part)) !== null) {
- switch (matchSpan[1]) {
- case 'color':
- tag.attrs.color = matchSpan[2].slice(1); // cut off the #
- break;
- case 'background-color':
- tag.attrs.backgroundColor = matchSpan[2].slice(1); // cut off the #
- break;
- case 'text-decoration':
- if (matchSpan[2] === 'underline') {
- tag.attrs.underline = true;
- } else if (matchSpan[2] === 'line-through') {
- tag.attrs.strike = true;
- }
- break;
- }
- }
- stack.push(tag);
- } else if (part.startsWith('a')) { /** a **/
- var rHref = /href="([^"]+)"/g;
- var href = rHref.exec(part)[1];
- tag = {tag: 'a', href: href};
- stack.push(tag);
- } else if (part.startsWith('img')) {
- // images has to be placed instantly, so there is no use of 'tag'.
- var img = {}, rImg = /(\w+)=\"([^\"]*)\"/g, matchImg;
- while ((matchImg = rImg.exec(part)) !== null) {
- img[matchImg[1]] = matchImg[2];
- }
-
- // With and height and source have to be given!
- if (img.width && img.height && img.src) {
- var rrId = relationships.length + 1;
- var imgId = images.length + 1;
-
- // set name ('pic.jpg'), title, ext ('jpg'), mime ('image/jpeg')
- img.name = img.src.split('/');
- img.name = _.last(img.name);
-
- var tmp = img.name.split('.');
- // set name without extension as title if there isn't a title
- if (!img.title) {
- img.title = tmp[0];
- }
- img.ext = tmp[1];
-
- img.mime = 'image/' + img.ext;
- if (img.ext == 'jpe' || img.ext == 'jpg') {
- img.mime = 'image/jpeg';
- }
-
- // x and y for the container and picture size in EMU (assuming 96dpi)!
- var x = img.width * 914400 / 96;
- var y = img.height * 914400 / 96;
-
- // Own paragraph for the image
- if (hasParagraph) {
- docx += '';
- }
- docx += '