(function () { 'use strict'; angular.module('OpenSlidesApp.motions.docx', []) .factory('MotionDocxExport', [ '$http', '$q', 'Config', 'gettextCatalog', 'FileSaver', function ($http, $q, Config, gettextCatalog, FileSaver) { var PAGEBREAK = ''; var TAGS_NO_PARAM = ['b', 'strong', 'em', 'i']; var images; var relationships; var contentTypes; // $scope.motionsFiltered, $scope.categories var getData = function (motions, categories) { var data = {}; // header var headerline1 = [ Config.translate(Config.get('general_event_name').value), Config.translate(Config.get('general_event_description').value) ].filter(Boolean).join(' – '); var headerline2 = [ Config.get('general_event_location').value, Config.get('general_event_date').value ].filter(Boolean).join(', '); data.header = [headerline1, headerline2].join('\n'); // motion catalog title/preamble data.title = Config.translate(Config.get('motions_export_title').value); data.preamble = Config.get('motions_export_preamble').value; // categories data.has_categories = categories.length === 0 ? false : true; data.categories_translation = gettextCatalog.getString('Categories'); data.categories = getCategoriesData(categories); data.no_categories = gettextCatalog.getString('No categories available.'); data.pagebreak_main = categories.length === 0 ? '' : PAGEBREAK; // motions data.tableofcontents_translation = gettextCatalog.getString('Table of contents'); data.motions = getMotionFullData(motions); data.motions_list = getMotionShortData(motions); data.no_motions = gettextCatalog.getString('No motions available.'); return data; }; var getCategoriesData = function (categories) { return _.map(categories, function (category) { return { prefix: category.prefix, name: category.name, }; }); }; var getMotionShortData = function (motions) { return _.map(motions, function (motion) { return { identifier: motion.identifier, title: motion.getTitle(), }; }); }; var getMotionFullData = function (motions) { 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) { 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(motion.getText()), reason_translation: motion.getReason().length === 0 ? '' : reason_translation, reason: html2docx(motion.getReason()), pagebreak: PAGEBREAK, }; }); if (data.length) { // clear pagebreak on last element data[data.length - 1].pagebreak = ''; } return data; }; var html2docx = function (html) { var docx = ''; var stack = []; var isTag = false; // Even if html starts with ')/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 += '' + '' + '' + '' + '' + '' + ''; // hasParagraph stays untouched, the documents paragraph state is restored here if (hasParagraph) { docx += ''; } // entries in images, relationships and contentTypes images.push({ url: img.src, zipPath: 'word/media/' + img.name }); relationships.push({ Id: 'rrId' + rrId, Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', Target: 'media/' + img.name }); contentTypes.push({ PartName: '/word/media/' + img.name, ContentType: img.mime }); } } } } else { /** No tag **/ if (!hasParagraph) { docx += ''; hasParagraph = true; } var docx_part = ''; var hyperlink = false; stack.forEach(function (tag) { switch (tag.tag) { case 'b': case 'strong': docx_part += ''; break; case 'em': case 'i': docx_part += ''; break; case 'span': for (var key in tag.attrs) { switch (key) { case 'color': docx_part += ''; break; case 'backgroundColor': docx_part += ''; break; case 'underline': docx_part += ''; break; case 'strike': docx_part += ''; break; } } break; case 'a': var id = relationships.length + 1; docx_part = '' + docx_part; docx_part += ''; // necessary? relationships.push({ Id: 'rrId' + id, Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', Target: tag.href, TargetMode: 'External' }); hyperlink = true; break; } }); docx_part += '' + part + ''; if (hyperlink) { docx_part += ''; } // append to docx docx += docx_part; } isTag = !isTag; } if (part === '' || part == '\n') { // just if two tags following eachother: --> ...,'>', '', '<',... // or there is a line break between: \n --> ...,'>', '\n', '<',... isTag = !isTag; } }); // for finishing close the last paragraph (if open) if (hasParagraph) { docx += ''; } // 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 rEntity = /\&(?!gt|lt|amp)\w+\;/g, matchEntry, indexes = []; while ((matchEntry = rEntity.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); } return docx; }; var updateRelationships = function (oldContent) { var content = oldContent.split('\n'); relationships.forEach(function (rel) { content[1] += '