From 1f6d77fc280f2b797fd158f4cdce09daeca4831d Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 03:18:02 -0500 Subject: [PATCH 1/3] Bump version to 1.3.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bef8d59..2fa8669 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hackmyresume", - "version": "1.3.0-beta", + "version": "1.3.0", "description": "Generate polished résumés and CVs in HTML, Markdown, LaTeX, MS Word, PDF, plain text, JSON, XML, YAML, smoke signal, and carrier pigeon.", "repository": { "type": "git", From 069c02ddcc20bd47617f9de0e0d5ecf7fd3925e3 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 03:34:41 -0500 Subject: [PATCH 2/3] Interim changes supporting v1.3.0. --- package.json | 3 +- src/core/convert.js | 36 ++++++++++-- src/core/jrs-resume.js | 22 +++++++- src/core/resume-factory.js | 104 ++++++++++++++++++++++++++++------- src/index.js | 2 +- src/utils/syntax-error-ex.js | 40 ++++++++++++++ src/verbs/convert.js | 25 ++++++--- src/verbs/create.js | 6 ++ src/verbs/generate.js | 16 +++--- src/verbs/validate.js | 62 +++++++++------------ 10 files changed, 234 insertions(+), 82 deletions(-) create mode 100644 src/utils/syntax-error-ex.js diff --git a/package.json b/package.json index 2fa8669..4b22adb 100644 --- a/package.json +++ b/package.json @@ -47,12 +47,13 @@ "dependencies": { "colors": "^1.1.2", "copy": "^0.1.3", - "fresh-themes": "~0.9.3-beta", "fresca": "~0.2.2", + "fresh-themes": "~0.9.3-beta", "fs-extra": "^0.24.0", "handlebars": "^4.0.5", "html": "0.0.10", "is-my-json-valid": "^2.12.2", + "json-lint": "^0.1.0", "jst": "0.0.13", "lodash": "^3.10.1", "marked": "^0.3.5", diff --git a/src/core/convert.js b/src/core/convert.js index 29f1a47..b579cf9 100644 --- a/src/core/convert.js +++ b/src/core/convert.js @@ -1,11 +1,13 @@ /** FRESH to JSON Resume conversion routiens. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. +@license MIT. See LICENSE.md for details. @module convert.js */ (function(){ + var _ = require('underscore'); + /** Convert between FRESH and JRS resume/CV formats. @class FRESHConverter @@ -26,6 +28,8 @@ FRESH to JSON Resume conversion routiens. name: src.basics.name, + imp: src.basics.imp, + info: { label: src.basics.label, class: src.basics.class, // <--> round-trip @@ -92,7 +96,8 @@ FRESH to JSON Resume conversion routiens. countryCode: src.location.country, region: src.location.region }, - profiles: social( src.social, false ) + profiles: social( src.social, false ), + imp: src.imp }, work: employment( src.employment, false ), @@ -109,12 +114,30 @@ FRESH to JSON Resume conversion routiens. }; + }, + + toSTRING: function( src ) { + function replacerJRS( key,value ) { // Exclude these keys from stringification + return _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', + 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', + 'isModified', 'htmlPreview', 'display_progress_bar'], + function( val ) { return key.trim() === val; } + ) ? undefined : value; + } + function replacerFRESH( key,value ) { // Exclude these keys from stringification + return _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', + 'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'], + function( val ) { return key.trim() === val; } + ) ? undefined : value; + } + + return JSON.stringify( src, src.basics ? replacerJRS : replacerFRESH, 2 ); } }; function meta( direction, obj ) { - if( !obj ) return obj; // preserve null and undefined + //if( !obj ) return obj; // preserve null and undefined if( direction ) { obj = obj || { }; obj.format = obj.format || "FRESH@0.1.0"; @@ -151,7 +174,7 @@ FRESH to JSON Resume conversion routiens. start: job.startDate, end: job.endDate, url: job.website, - keywords: "", + keywords: [], highlights: job.highlights }; }) : undefined @@ -164,6 +187,7 @@ FRESH to JSON Resume conversion routiens. if( !obj ) return obj; if( direction ) { return obj && obj.length ? { + level: "", history: obj.map(function(edu){ return { institution: edu.institution, @@ -171,8 +195,8 @@ FRESH to JSON Resume conversion routiens. end: edu.endDate, grade: edu.gpa, curriculum: edu.courses, - url: edu.website || edu.url || null, - summary: null, + url: edu.website || edu.url || undefined, + summary: edu.summary || "", area: edu.area, studyType: edu.studyType }; diff --git a/src/core/jrs-resume.js b/src/core/jrs-resume.js index 49a8d86..5609a75 100644 --- a/src/core/jrs-resume.js +++ b/src/core/jrs-resume.js @@ -1,6 +1,6 @@ /** Definition of the JRSResume class. -@license MIT. Copyright (c) 2015 James Devlin / FluentDesk. +@license MIT. See LICENSE.md for details. @module jrs-resume.js */ @@ -12,6 +12,7 @@ Definition of the JRSResume class. , _ = require('underscore') , PATH = require('path') , MD = require('marked') + , CONVERTER = require('./convert') , moment = require('moment'); /** @@ -51,6 +52,24 @@ Definition of the JRSResume class. return this; }; + /** + Save the sheet to disk in a specific format, either FRESH or JRS. + */ + JRSResume.prototype.saveAs = function( filename, format ) { + + if( format === 'JRS' ) { + this.basics.imp.fileName = filename || this.imp.fileName; + FS.writeFileSync( this.basics.imp.fileName, this.stringify(), 'utf8' ); + } + else { + var newRep = CONVERTER.toFRESH( this ); + var stringRep = CONVERTER.toSTRING( newRep ); + FS.writeFileSync( filename, stringRep, 'utf8' ); + } + return this; + + }; + /** Convert this object to a JSON string, sanitizing meta-properties along the way. Don't override .toString(). @@ -92,6 +111,7 @@ Definition of the JRSResume class. if( opts.imp === undefined || opts.imp ) { this.basics.imp = this.basics.imp || { }; this.basics.imp.title = (opts.title || this.basics.imp.title) || this.basics.name; + this.basics.imp.orgFormat = 'JRS'; } // Parse dates, sort dates, and calculate computed values (opts.date === undefined || opts.date) && _parseDates.call( this ); diff --git a/src/core/resume-factory.js b/src/core/resume-factory.js index b72f8a9..b8d4265 100644 --- a/src/core/resume-factory.js +++ b/src/core/resume-factory.js @@ -1,43 +1,107 @@ /** -Core resume-loading logic for HackMyResume. +Definition of the ResumeFactory class. +@license MIT. See LICENSE.md for details. @module resume-factory.js */ + + (function(){ + + require('string.prototype.startswith'); var FS = require('fs'); var ResumeConverter = require('./convert'); + + /** A simple factory class for FRESH and JSON Resumes. @class ResumeFactory */ - module.exports = { + var ResumeFactory = module.exports = { + + /** - Load one or more resumes in a specific source format. + Load one or more resumes from disk. */ - load: function ( src, log, fn, toFormat ) { - - toFormat = toFormat && (toFormat.toLowerCase().trim()) || 'fresh'; - var ResumeClass = require('../core/' + toFormat + '-resume'); - - return src.map( function( res ) { - var rezJson = JSON.parse( FS.readFileSync( res ) ); - var orgFormat = ( rezJson.meta && rezJson.meta.format && - rezJson.meta.format.startsWith('FRESH@') ) ? - 'fresh' : 'jrs'; - if(orgFormat !== toFormat) { - rezJson = ResumeConverter[ 'to' + toFormat.toUpperCase() ]( rezJson ); - } - // TODO: Core should not log - log( 'Reading '.info + orgFormat.toUpperCase().infoBold + ' resume: '.info + res.cyan.bold ); - return (fn && fn(res)) || (new ResumeClass()).parseJSON( rezJson ); + load: function ( sources, log, toFormat, objectify ) { + // Loop over all inputs, parsing each to JSON and then to a FRESHResume + // or JRSResume object. + var that = this; + return sources.map( function( src ) { + return that.loadOne( src, log, toFormat, objectify ); }); - } + }, + + + /** + Load a single resume from disk. + */ + loadOne: function( src, log, toFormat, objectify ) { + + // Get the destination format. Can be 'fresh', 'jrs', or null/undefined. + toFormat && (toFormat = toFormat.toLowerCase().trim()); + + // Load and parse the resume JSON + var info = _parse( src, log, toFormat ); + if( info.error ) return info; + var json = info.json; + + // Determine the resume format: FRESH or JRS + var orgFormat = ( json.meta && json.meta.format && + json.meta.format.startsWith('FRESH@') ) ? + 'fresh' : 'jrs'; + + // Convert between formats if necessary + if( toFormat && (orgFormat !== toFormat) ) { + json = ResumeConverter[ 'to' + toFormat.toUpperCase() ]( json ); + } + + // Objectify the resume, that is, convert it from JSON to a FRESHResume + // or JRSResume object. + var rez; + if( objectify ) { + var ResumeClass = require('../core/' + (toFormat || orgFormat) + '-resume'); + rez = new ResumeClass().parseJSON( json ); + } + + return { + file: src, + json: info.json, + rez: rez + }; + } }; + + + function _parse( fileName, log, toFormat ) { + var rawData; + try { + + // TODO: Core should not log + log( 'Reading '.info + /*orgFormat.toUpperCase().infoBold +*/ + 'resume: '.info + fileName.cyan.bold ); + + rawData = FS.readFileSync( fileName, 'utf8' ); + return { + json: JSON.parse( rawData ) + }; + + } + catch(ex) { + return { + error: ex, + raw: rawData + }; + } + } + + + }()); diff --git a/src/index.js b/src/index.js index 9352d49..904d790 100644 --- a/src/index.js +++ b/src/index.js @@ -73,7 +73,7 @@ function main() { // Massage inputs and outputs var src = a._.slice(1, splitAt === -1 ? undefined : splitAt ); var dst = splitAt === -1 ? [] : a._.slice( splitAt + 1 ); - ( splitAt === -1 ) && src.length > 1 && dst.push( src.pop() ); // Allow omitting TO keyword + ( splitAt === -1 ) && (src.length > 1) && (verb !== 'validate') && dst.push( src.pop() ); // Allow omitting TO keyword // Invoke the action (FCMD.verbs[verb] || FCMD.alias[verb]).apply(null, [src, dst, opts, logMsg]); diff --git a/src/utils/syntax-error-ex.js b/src/utils/syntax-error-ex.js new file mode 100644 index 0000000..9b2834e --- /dev/null +++ b/src/utils/syntax-error-ex.js @@ -0,0 +1,40 @@ +/** +Definition of the SyntaxErrorEx class. +@module syntax-error-ex.js +*/ + +(function() { + + + /** + Represents a SyntaxError exception with line and column info. + Collect syntax error information from the provided exception object. The + JavaScript `SyntaxError` exception isn't interpreted uniformly across environ- + ments, so we first check for a .lineNumber and .columnNumber and, if that's + not present, fall back to the JSONLint library, which provides that info. + See: http://stackoverflow.com/q/13323356 + @class SyntaxErrorEx + */ + + module.exports = function SyntaxErrorEx( ex, rawData ) { + + var lineNum = null, colNum = null; + if( ex.lineNumber !== undefined && ex.lineNumber !== null ) { + lineNum = ex.lineNumber; + } + if( ex.columnNumber !== undefined && ex.columnNumber !== null ) { + colNum = ex.columnNumber; + } + if( lineNum === null || colNum === null ) { + var JSONLint = require('json-lint'); + var lint = JSONLint( rawData, { comments: false } ); + if( lineNum === null ) lineNum = (lint.error ? lint.line : '???'); + if( colNum === null ) colNum = (lint.error ? lint.character : '???'); + } + this.line = lineNum; + this.col = colNum; + + }; + + +}()); diff --git a/src/verbs/convert.js b/src/verbs/convert.js index 7386072..a2f80fe 100644 --- a/src/verbs/convert.js +++ b/src/verbs/convert.js @@ -1,3 +1,9 @@ +/** +Implementation of the 'convert' verb for HackMyResume. +@module convert.js +@license MIT. See LICENSE.md for details. +*/ + (function(){ var ResumeFactory = require('../core/resume-factory'); @@ -5,22 +11,23 @@ /** Convert between FRESH and JRS formats. */ - module.exports = function convert( src, dst, opts, logger ) { + module.exports = function convert( sources, dst, opts, logger ) { var _log = logger || console.log; - if( !src || !src.length ) { throw { fluenterror: 6 }; } + if( !sources || !sources.length ) { throw { fluenterror: 6 }; } if( !dst || !dst.length ) { - if( src.length === 1 ) { throw { fluenterror: 5 }; } - else if( src.length === 2 ) { dst = [ src[1] ]; src = [ src[0] ]; } + if( sources.length === 1 ) { throw { fluenterror: 5 }; } + else if( sources.length === 2 ) { dst = [ sources[1] ]; sources = [ sources[0] ]; } else { throw { fluenterror: 5 }; } } - if( src && dst && src.length && dst.length && src.length !== dst.length ) { + if( sources && dst && sources.length && dst.length && sources.length !== dst.length ) { throw { fluenterror: 7 }; } - var sheets = ResumeFactory.load( src, _log ); - sheets.forEach(function(sheet, idx){ - var sourceFormat = sheet.imp.orgFormat === 'JRS' ? 'JRS' : 'FRESH'; + var sourceResumes = ResumeFactory.load( sources, _log, null, true ); + sourceResumes.forEach(function( src, idx ) { + var sheet = src.rez; + var sourceFormat = ((sheet.basics && sheet.basics.imp) || sheet.imp).orgFormat === 'JRS' ? 'JRS' : 'FRESH'; var targetFormat = sourceFormat === 'JRS' ? 'FRESH' : 'JRS'; - _log( 'Converting '.useful + sheet.imp.fileName.useful.bold + (' (' + + _log( 'Converting '.useful + src.file.useful.bold + (' (' + sourceFormat + ') to ').useful + dst[0].useful.bold + (' (' + targetFormat + ').').useful ); sheet.saveAs( dst[idx], targetFormat ); diff --git a/src/verbs/create.js b/src/verbs/create.js index b5edf8e..1183320 100644 --- a/src/verbs/create.js +++ b/src/verbs/create.js @@ -1,3 +1,9 @@ +/** +Implementation of the 'create' verb for HackMyResume. +@module create.js +@license MIT. See LICENSE.md for details. +*/ + (function(){ var FLUENT = require('../hackmyapi') diff --git a/src/verbs/generate.js b/src/verbs/generate.js index 542ef55..7dea1a5 100644 --- a/src/verbs/generate.js +++ b/src/verbs/generate.js @@ -54,28 +54,28 @@ Implementation of the 'generate' verb for HackMyResume. // Load the theme... var tFolder = verify_theme( _opts.theme ); - var theTheme = load_theme( tFolder ); + var theme = load_theme( tFolder ); // Load input resumes... if( !src || !src.length ) { throw { fluenterror: 3 }; } - var sheets = ResumeFactory.load( src, _log, null, - theTheme.render ? 'JRS' : 'FRESH' ); + var sheets = ResumeFactory.load(src, _log, theme.render ? 'JRS' : 'FRESH', true); // Merge input resumes... var msg = ''; - rez = _.reduceRight( sheets, function( a, b, idx ) { + var rezRep = _.reduceRight( sheets, function( a, b, idx ) { msg += ((idx == sheets.length - 2) ? - 'Merging '.gray+ a.imp.fileName : '') + ' onto '.gray + b.imp.fileName; - return extend( true, b, a ); + 'Merging '.gray + a.rez.imp.fileName : '') + ' onto '.gray + b.rez.fileName; + return extend( true, b.rez, a.rez ); }); + rez = rezRep.rez; msg && _log(msg); // Expand output resumes... - var targets = expand( dst, theTheme ); + var targets = expand( dst, theme ); // Run the transformation! targets.forEach( function(t) { - t.final = single( t, theTheme, targets ); + t.final = single( t, theme, targets ); }); // Don't send the client back empty-handed diff --git a/src/verbs/validate.js b/src/verbs/validate.js index 239576c..a2c89f2 100644 --- a/src/verbs/validate.js +++ b/src/verbs/validate.js @@ -1,16 +1,23 @@ +/** +Implementation of the 'validate' verb for HackMyResume. +@module validate.js +@license MIT. See LICENSE.md for details. +*/ + (function() { var FS = require('fs'); var ResumeFactory = require('../core/resume-factory'); + var SyntaxErrorEx = require('../utils/syntax-error-ex'); module.exports = /** Validate 1 to N resumes in either FRESH or JSON Resume format. */ - function validate( src, unused, opts, logger ) { + function validate( sources, unused, opts, logger ) { var _log = logger || console.log; - if( !src || !src.length ) { throw { fluenterror: 6 }; } + if( !sources || !sources.length ) { throw { fluenterror: 6 }; } var isValid = true; var validator = require('is-my-json-valid'); @@ -20,67 +27,51 @@ }; // Load input resumes... - var sheets = ResumeFactory.load(src, _log, function( res ) { - try { - return { - file: res, - raw: FS.readFileSync( res, 'utf8' ) - }; - } - catch( ex ) { - throw ex; - } - }); + sources.forEach(function( src ) { - sheets.forEach( function( rep ) { + var result = ResumeFactory.loadOne( src, function(){}, null, false ); + if( result.error ) { + _log( 'Validating '.info + src.infoBold + ' against '.info + 'AUTO'.infoBold + ' schema:'.info + ' BROKEN'.red.bold ); - var rez; - try { - rez = JSON.parse( rep.raw ); - } - catch( ex ) { // Note [1] - _log('Validating '.info + rep.file.infoBold + - ' against FRESH/JRS schema: '.info + 'ERROR!'.error.bold); - - if (ex instanceof SyntaxError) { - // Invalid JSON - _log( '--> '.bold.red + rep.file.toUpperCase().red + - ' contains invalid JSON. Unable to validate.'.red ); - _log( (' INTERNAL: ' + ex).red ); + var ex = result.error; // alias + if ( ex instanceof SyntaxError) { + var info = new SyntaxErrorEx( ex, result.raw ); + _log( ('--> '.warn.bold + src.toUpperCase() + ' contains invalid JSON on line ' + + info.line + ' column ' + info.col + '.').warn + + ' Unable to validate.'.warn ); + _log( (' INTERNAL: ' + ex).warn ); } else { - - _log(('ERROR: ' + ex.toString()).red.bold); + _log(('ERROR: ' + ex.toString()).warn.bold); } return; } + var json = result.json; var isValid = false; var style = 'useful'; var errors = []; - var fmt = rez.meta && - (rez.meta.format === 'FRESH@0.1.0') ? 'fresh':'jars'; + var fmt = json.meta && (json.meta.format==='FRESH@0.1.0') ? 'fresh':'jars'; try { - var validate = validator( schemas[ fmt ], { // Note [1] formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ } }); - isValid = validate( rez ); + isValid = validate( json ); if( !isValid ) { style = 'warn'; errors = validate.errors; } } - catch(ex) { + catch(exc) { return; } - _log( 'Validating '.info + rep.file.infoBold + ' against '.info + + _log( 'Validating '.info + result.file.infoBold + ' against '.info + fmt.replace('jars','JSON Resume').toUpperCase().infoBold + ' schema: '.info + (isValid ? 'VALID!' : 'INVALID')[style].bold ); @@ -93,5 +84,4 @@ }); }; - }()); From b85d40b1b3323a223fc9db1d97380006bd47eb88 Mon Sep 17 00:00:00 2001 From: hacksalot Date: Thu, 31 Dec 2015 06:38:30 -0500 Subject: [PATCH 3/3] Improve XML encoding for Word docs. Fix various encoding errors. --- src/core/fresh-resume.js | 79 +++++++++++++++++++++++++++++++++ src/eng/generic-helpers.js | 9 ++-- src/eng/handlebars-generator.js | 7 ++- src/utils/html-to-wpml.js | 2 + 4 files changed, 93 insertions(+), 4 deletions(-) diff --git a/src/core/fresh-resume.js b/src/core/fresh-resume.js index 58c97a5..54d08ac 100644 --- a/src/core/fresh-resume.js +++ b/src/core/fresh-resume.js @@ -13,6 +13,7 @@ Definition of the FRESHResume class. , __ = require('lodash') , PATH = require('path') , moment = require('moment') + , XML = require('xml-escape') , MD = require('marked') , CONVERTER = require('./convert') , JRSResume = require('./jrs-resume'); @@ -81,12 +82,90 @@ Definition of the FRESHResume class. return JSON.stringify( obj, replacer, 2 ); }; + /** + Create a copy of this resume in which all string fields have been run through + a transformation function (such as a Markdown filter or XML encoder). + */ + FreshResume.prototype.transformStrings = function( filters, transformer ) { + + var that = this; + var ret = this.dupe(); + + // TODO: refactor recursion + function transformStringsInObject( obj ) { + + if( !obj ) return; + if( moment.isMoment( obj ) ) return; + + if( _.isArray( obj ) ) { + obj.forEach( function(elem, idx, ar) { + if( typeof elem === 'string' || elem instanceof String ) + ar[idx] = transformer( null, elem ); + else if (_.isObject(elem)) + transformStringsInObject( elem ); + }); + } + else if (_.isObject( obj )) { + Object.keys( obj ).forEach(function(k) { + var sub = obj[k]; + if( typeof sub === 'string' || sub instanceof String ) { + if( filters.length && _.contains(filters, k) ) + return; + obj[k] = transformer( k, sub ); + } + else if (_.isObject( sub )) + transformStringsInObject( sub ); + }); + } + + } + + Object.keys( ret ).forEach(function(member){ + transformStringsInObject( ret[ member ] ); + }); + + return ret; + }; + /** Create a copy of this resume in which all fields have been interpreted as Markdown. */ FreshResume.prototype.markdownify = function() { + function MDIN( txt ){ + return MD(txt || '' ).replace(/^\s*

|<\/p>\s*$/gi, ''); + } + + function trx(key, val) { + if( key === 'summary' ) { + return MD(val); + } + return MDIN(val); + } + + return this.transformStrings( ['skills','url','start','end','date'], trx ); + }; + + /** + Create a copy of this resume in which all fields have been interpreted as + Markdown. + */ + FreshResume.prototype.xmlify = function() { + + function trx(key, val) { + return XML(val); + } + + return this.transformStrings( [], trx ); + }; + + /** + Create a copy of this resume in which all fields have been interpreted as + Markdown. + */ + FreshResume.prototype.markdownify2 = function() { + var that = this; var ret = this.dupe(); diff --git a/src/eng/generic-helpers.js b/src/eng/generic-helpers.js index a930933..6b10dc8 100644 --- a/src/eng/generic-helpers.js +++ b/src/eng/generic-helpers.js @@ -9,6 +9,7 @@ Generic template helper definitions for HackMyResume / FluentCV. var MD = require('marked') , H2W = require('../utils/html-to-wpml') + , XML = require('xml-escape') , moment = require('moment') , _ = require('underscore'); @@ -33,10 +34,12 @@ Generic template helper definitions for HackMyResume / FluentCV. wpml: function( txt, inline ) { if(!txt) return ''; inline = (inline && !inline.hash) || false; + txt = XML(txt.trim()); txt = inline ? - MD(txt.trim()).replace(/^\s*

|<\/p>\s*$/gi, '') : - MD(txt.trim()); - txt = H2W( txt.trim() ); + MD(txt).replace(/^\s*

|<\/p>\s*$/gi, '') : + MD(txt); + txt = H2W( txt ); + console.log(txt); return txt; }, diff --git a/src/eng/handlebars-generator.js b/src/eng/handlebars-generator.js index 27a8f7d..b9895eb 100644 --- a/src/eng/handlebars-generator.js +++ b/src/eng/handlebars-generator.js @@ -35,8 +35,13 @@ Definition of the HandlebarsGenerator class. // Compile and run the Handlebars template. var template = HANDLEBARS.compile(jst); + + var encData = json; + ( format === 'html' || format === 'pdf' ) && (encData = json.markdownify()); + ( format === 'doc' ) && (encData = json.xmlify()); + return template({ - r: format === 'html' || format === 'pdf' || format === 'png' ? json.markdownify() : json, + r: encData, RAW: json, filt: opts.filters, cssInfo: cssInfo, diff --git a/src/utils/html-to-wpml.js b/src/utils/html-to-wpml.js index 89f1c51..243a18d 100644 --- a/src/utils/html-to-wpml.js +++ b/src/utils/html-to-wpml.js @@ -47,6 +47,7 @@ Definition of the Markdown to WordProcessingML conversion routine. break; case 'Chars': + if( tok.chars.trim().length ) { var style = is_bold ? '' : ''; style += is_italic ? '': ''; style += is_link ? '' : ''; @@ -54,6 +55,7 @@ Definition of the Markdown to WordProcessingML conversion routine. (is_link ? ('') : '') + '' + style + '' + tok.chars + '' + (is_link ? '' : ''); + } break; } });