diff --git a/package.json b/package.json index a99624c..3dbbf1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fluentcv", - "version": "0.10.1", + "version": "0.10.2", "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", @@ -32,9 +32,9 @@ }, "homepage": "https://github.com/fluentdesk/fluentcv", "dependencies": { - "fresca": "~0.2.1", "colors": "^1.1.2", - "fluent-themes": "~0.6.1-beta", + "fluent-themes": "~0.6.2-beta", + "fresca": "~0.2.1", "fs-extra": "^0.24.0", "handlebars": "^4.0.5", "html": "0.0.10", @@ -45,6 +45,7 @@ "mkdirp": "^0.5.1", "moment": "^2.10.6", "recursive-readdir-sync": "^1.0.6", + "simple-html-tokenizer": "^0.2.0", "underscore": "^1.8.3", "wkhtmltopdf": "^0.1.5", "xml-escape": "^1.0.0", diff --git a/src/core/convert.js b/src/core/convert.js index f16aafc..97c8c5f 100644 --- a/src/core/convert.js +++ b/src/core/convert.js @@ -14,6 +14,7 @@ FRESH to JSON Resume conversion routiens. /** Convert from JSON Resume format to FRESH. + @method toFresh */ toFRESH: function( src, foreign ) { @@ -47,87 +48,15 @@ FRESH to JSON Resume conversion routiens. address: src.basics.location.address }, - employment: { - history: src.work.map( function( job ) { - return { - position: job.position, - employer: job.company, - summary: job.summary, - current: (!job.endDate || !job.endDate.trim() || job.endDate.trim().toLowerCase() === 'current') || undefined, - start: job.startDate, - end: job.endDate, - url: job.website, - keywords: "", - highlights: job.highlights - }; - }) - }, - - education: { - history: src.education.map(function(edu){ - return { - institution: edu.institution, - start: edu.startDate, - end: edu.endDate, - grade: edu.gpa, - curriculum: edu.courses, - url: edu.website || edu.url || null, - summary: null, - area: edu.area, - studyType: edu.studyType - }; - }) - }, - - service: { - history: src.volunteer.map(function(vol) { - return { - type: 'volunteer', - position: vol.position, - organization: vol.organization, - start: vol.startDate, - end: vol.endDate, - url: vol.website, - summary: vol.summary, - highlights: vol.highlights - }; - }) - }, - + employment: employment( src.work, true ), + education: education( src.education, true), + service: service( src.volunteer, true), skills: skillsToFRESH( src.skills ), - - writing: src.publications.map(function(pub){ - return { - title: pub.name, - flavor: undefined, - publisher: pub.publisher, - url: pub.website, - date: pub.releaseDate, - summary: pub.summary - }; - }), - - recognition: src.awards.map(function(awd){ - return { - title: awd.title, - date: awd.date, - summary: awd.summary, - from: awd.awarder, - url: null - }; - }), - - social: src.basics.profiles.map(function(pro){ - return { - label: pro.network, - network: pro.network, - url: pro.url, - user: pro.username - }; - }), - + writing: writing( src.publications, true), + recognition: recognition( src.awards, true, foreign ), + social: social( src.basics.profiles, true ), interests: src.interests, - references: src.references, + testimonials: references( src.references, true ), languages: src.languages, disposition: src.disposition // <--> round-trip }; @@ -160,77 +89,17 @@ FRESH to JSON Resume conversion routiens. countryCode: src.location.country, region: src.location.region }, - profiles: src.social.map(function(soc){ - return { - network: soc.network, - username: soc.user, - url: soc.url - }; - }) + profiles: social( src.social, false ) }, - work: src.employment.history.map(function(emp){ - return { - company: emp.employer, - website: emp.url, - position: emp.position, - startDate: emp.start, - endDate: emp.end, - summary: emp.summary, - highlights: emp.highlights - }; - }), - - education: src.education.history.map(function(edu){ - return { - institution: edu.institution, - gpa: edu.grade, - courses: edu.curriculum, - startDate: edu.start, - endDate: edu.end, - area: edu.area, - studyType: edu.studyType - }; - }), - - skills: skillsToJRS( src.skills ), - - volunteer: src.service.history.map(function(srv){ - return { - flavor: foreign ? srv.flavor : undefined, - organization: srv.organization, - position: srv.position, - startDate: srv.start, - endDate: srv.end, - website: srv.url, - summary: srv.summary, - highlights: srv.highlights - }; - }), - - awards: src.recognition.map(function(awd){ - return { - flavor: foreign ? awd.flavor : undefined, - url: foreign ? awd.url: undefined, - title: awd.title, - date: awd.date, - awarder: awd.from, - summary: awd.summary - }; - }), - - publications: src.writing.map(function(pub){ - return { - name: pub.title, - publisher: pub.publisher, - releaseDate: pub.date, - website: pub.url, - summary: pub.summary - }; - }), - + work: employment( src.employment, false ), + education: education( src.education, false ), + skills: skillsToJRS( src.skills, false ), + volunteer: service( src.service, false ), + awards: recognition( src.recognition, false, foreign ), + publications: writing( src.writing, false ), interests: src.interests, - references: src.references, + references: references( src.testimonials, false ), samples: foreign ? src.samples : undefined, disposition: foreign ? src.disposition : undefined, languages: src.languages @@ -250,6 +119,207 @@ FRESH to JSON Resume conversion routiens. return obj; } + function employment( obj, direction ) { + if( !direction ) { + return obj && obj.history ? + obj.history.map(function(emp){ + return { + company: emp.employer, + website: emp.url, + position: emp.position, + startDate: emp.start, + endDate: emp.end, + summary: emp.summary, + highlights: emp.highlights + }; + }) : undefined; + } + else { + return { + history: obj && obj.length ? + obj.map( function( job ) { + return { + position: job.position, + employer: job.company, + summary: job.summary, + current: (!job.endDate || !job.endDate.trim() || job.endDate.trim().toLowerCase() === 'current') || undefined, + start: job.startDate, + end: job.endDate, + url: job.website, + keywords: "", + highlights: job.highlights + }; + }) : undefined + }; + } + } + + + function education( obj, direction ) { + if( direction ) { + return obj && obj.length ? { + history: obj.map(function(edu){ + return { + institution: edu.institution, + start: edu.startDate, + end: edu.endDate, + grade: edu.gpa, + curriculum: edu.courses, + url: edu.website || edu.url || null, + summary: null, + area: edu.area, + studyType: edu.studyType + }; + }) + } : undefined; + } + else { + return obj && obj.history ? + obj.history.map(function(edu){ + return { + institution: edu.institution, + gpa: edu.grade, + courses: edu.curriculum, + startDate: edu.start, + endDate: edu.end, + area: edu.area, + studyType: edu.studyType + }; + }) : undefined; + } + } + + function service( obj, direction, foreign ) { + if( direction ) { + return { + history: obj && obj.length ? obj.map(function(vol) { + return { + type: 'volunteer', + position: vol.position, + organization: vol.organization, + start: vol.startDate, + end: vol.endDate, + url: vol.website, + summary: vol.summary, + highlights: vol.highlights + }; + }) : undefined + }; + } + else { + return obj && obj.history ? + obj.history.map(function(srv){ + return { + flavor: foreign ? srv.flavor : undefined, + organization: srv.organization, + position: srv.position, + startDate: srv.start, + endDate: srv.end, + website: srv.url, + summary: srv.summary, + highlights: srv.highlights + }; + }) : undefined; + } + } + + function social( obj, direction ) { + if( direction ) { + return obj.map(function(pro){ + return { + label: pro.network, + network: pro.network, + url: pro.url, + user: pro.username + }; + }); + } + else { + return obj.map( function( soc ) { + return { + network: soc.network, + username: soc.user, + url: soc.url + }; + }); + } + } + + function recognition( obj, direction, foreign ) { + if( direction ) { + return obj && obj.length ? obj.map( + function(awd){ + return { + flavor: foreign ? awd.flavor : undefined, + url: foreign ? awd.url: undefined, + title: awd.title, + date: awd.date, + from: awd.awarder, + summary: awd.summary + }; + }) : undefined; + } + else { + return obj && obj.length ? obj.map(function(awd){ + return { + flavor: foreign ? awd.flavor : undefined, + url: foreign ? awd.url: undefined, + title: awd.title, + date: awd.date, + awarder: awd.from, + summary: awd.summary + }; + }) : undefined; + } + } + + function references( obj, direction ) { + if( direction ) { + return obj && obj.length && obj.map(function(ref){ + return { + name: ref.name, + flavor: 'professional', + quote: ref.reference, + private: false + }; + }); + } + else { + return obj && obj.length && obj.map(function(ref){ + return { + name: ref.name, + reference: ref.quote + }; + }); + } + } + + function writing( obj, direction ) { + if( direction ) { + return obj.map(function( pub ) { + return { + title: pub.name, + flavor: undefined, + publisher: pub.publisher, + url: pub.website, + date: pub.releaseDate, + summary: pub.summary + }; + }); + } + else { + return obj && obj.length ? obj.map(function(pub){ + return { + name: pub.title, + publisher: pub.publisher && pub.publisher.name ? pub.publisher.name : pub.publisher, + releaseDate: pub.date, + website: pub.url, + summary: pub.summary + }; + }) : undefined; + } + } + function skillsToFRESH( skills ) { return { diff --git a/src/core/empty-fresh.json b/src/core/empty-fresh.json index 347def5..356ea50 100644 --- a/src/core/empty-fresh.json +++ b/src/core/empty-fresh.json @@ -47,6 +47,7 @@ "position": "", "summary": "", "start": "", + "end": "", "keywords": [], "highlights": [] } @@ -91,6 +92,7 @@ "sets": [ { "name": "", + "level": "", "skills": [] } ], diff --git a/src/core/fluent-date.js b/src/core/fluent-date.js index 7a3d967..26ba4be 100644 --- a/src/core/fluent-date.js +++ b/src/core/fluent-date.js @@ -66,7 +66,10 @@ FluentDate/*.prototype*/.fmt = function( dt ) { } } else { - if( dt.isValid && dt.isValid() ) + if( !dt ) { + return moment(); + } + else if( dt.isValid && dt.isValid() ) return dt; throw 'Unknown date object encountered.'; } diff --git a/src/core/fresh-resume.js b/src/core/fresh-resume.js index a0f5287..d21449c 100644 --- a/src/core/fresh-resume.js +++ b/src/core/fresh-resume.js @@ -12,7 +12,8 @@ Definition of the FRESHResume class. , PATH = require('path') , moment = require('moment') , MD = require('marked') - , CONVERTER = require('./convert'); + , CONVERTER = require('./convert') + , JRSResume = require('./jrs-resume'); /** A FRESH-style resume in JSON or YAML. @@ -46,13 +47,14 @@ Definition of the FRESHResume class. Save the sheet to disk in a specific format, either FRESH or JSON Resume. */ FreshResume.prototype.saveAs = function( filename, format ) { - this.imp.fileName = filename || this.imp.fileName; + if( format !== 'JRS' ) { + this.imp.fileName = filename || this.imp.fileName; FS.writeFileSync( this.imp.fileName, this.stringify(), 'utf8' ); } else { var newRep = CONVERTER.toJRS( this ); - FS.writeFileSync( this.imp.fileName, FreshResume.stringify( newRep ), 'utf8' ); + FS.writeFileSync( filename, JRSResume.stringify( newRep ), 'utf8' ); } return this; }; @@ -211,6 +213,16 @@ Definition of the FRESHResume class. delete this.social; }; + /** + Get a safe count of the number of things in a section. + */ + FreshResume.prototype.count = function( obj ) { + if( !obj ) return 0; + if( obj.history ) return obj.history.length; + if( obj.sets ) return obj.sets.length; + return obj.length || 0; + }; + /** Get the default (empty) sheet. */ @@ -224,9 +236,18 @@ Definition of the FRESHResume class. */ FreshResume.prototype.add = function( moniker ) { var defSheet = FreshResume.default(); - var newObject = $.extend( true, {}, defSheet[ moniker ][0] ); + var newObject = defSheet[moniker].history ? + $.extend( true, {}, defSheet[ moniker ].history[0] ) : + (moniker === 'skills' ? + $.extend( true, {}, defSheet.skills.sets[0] ) : + $.extend( true, {}, defSheet[ moniker ][0] )); this[ moniker ] = this[ moniker ] || []; - this[ moniker ].push( newObject ); + if( this[ moniker ].history ) + this[ moniker ].history.push( newObject ); + else if( moniker === 'skills' ) + this.skills.sets.push( newObject ); + else + this[ moniker ].push( newObject ); return newObject; }; @@ -300,14 +321,15 @@ Definition of the FRESHResume class. */ FreshResume.prototype.duration = function() { if( this.employment.history && this.employment.history.length ) { - var careerStart = this.employment.history[ this.employment.history.length - 1].safe.start; + var firstJob = _.last( this.employment.history ); + var careerStart = firstJob.start ? firstJob.safe.start : ''; if ((typeof careerStart === 'string' || careerStart instanceof String) && !careerStart.trim()) return 0; var careerLast = _.max( this.employment.history, function( w ) { - return w.safe.end.unix(); - }).safe.end; - return careerLast.diff( careerStart, 'years' ); + return( w.safe && w.safe.end ) ? w.safe.end.unix() : moment().unix(); + }); + return careerLast.safe.end.diff( careerStart, 'years' ); } return 0; }; @@ -366,7 +388,7 @@ Definition of the FRESHResume class. replaceDatesInObject( obj[key] ); }); ['start','end','date'].forEach( function(val) { - if( obj[val] && (!obj.safe || !obj.safe[val] )) { + if( (obj[val] !== undefined) && (!obj.safe || !obj.safe[val] )) { obj.safe = obj.safe || { }; obj.safe[ val ] = _fmt( obj[val] ); if( obj[val] && (val === 'start') && !obj.end ) { diff --git a/src/core/jrs-resume.js b/src/core/jrs-resume.js index 4ec8749..a1c068a 100644 --- a/src/core/jrs-resume.js +++ b/src/core/jrs-resume.js @@ -30,17 +30,22 @@ Definition of the JRSResume class. consistent format. Then sort each section by startDate descending. */ JRSResume.prototype.open = function( file, title ) { - this.imp = { fileName: file }; - this.imp.raw = FS.readFileSync( file, 'utf8' ); - return this.parse( this.imp.raw, title ); + //this.imp = { fileName: file }; <-- schema violation, tuck it into .basics instead + this.basics = { + imp: { + fileName: file, + raw: FS.readFileSync( file, 'utf8' ) + } + }; + return this.parse( this.basics.imp.raw, title ); }; /** Save the sheet to disk (for environments that have disk access). */ JRSResume.prototype.save = function( filename ) { - this.imp.fileName = filename || this.imp.fileName; - FS.writeFileSync( this.imp.fileName, this.stringify(), 'utf8' ); + this.basics.imp.fileName = filename || this.basics.imp.fileName; + FS.writeFileSync( this.basics.imp.fileName, this.stringify( this ), 'utf8' ); return this; }; @@ -48,7 +53,7 @@ Definition of the JRSResume class. Convert this object to a JSON string, sanitizing meta-properties along the way. Don't override .toString(). */ - JRSResume.prototype.stringify = function() { + JRSResume.stringify = function( obj ) { function replacer( key,value ) { // Exclude these keys from stringification return _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index', 'safeStartDate', 'safeEndDate', 'safeDate', 'safeReleaseDate', 'result', @@ -56,7 +61,11 @@ Definition of the JRSResume class. function( val ) { return key.trim() === val; } ) ? undefined : value; } - return JSON.stringify( this, replacer, 2 ); + return JSON.stringify( obj, replacer, 2 ); + }; + + JRSResume.prototype.stringify = function() { + return JRSResume.stringify( this ); }; /** @@ -67,16 +76,17 @@ Definition of the JRSResume class. JRSResume.prototype.parse = function( stringData, opts ) { opts = opts || { }; var rep = JSON.parse( stringData ); + extend( true, this, rep ); // Set up metadata if( opts.imp === undefined || opts.imp ) { - this.imp = this.imp || { }; - this.imp.title = (opts.title || this.imp.title) || this.basics.name; + this.basics.imp = this.basics.imp || { }; + this.basics.imp.title = (opts.title || this.basics.imp.title) || this.basics.name; } // Parse dates, sort dates, and calculate computed values (opts.date === undefined || opts.date) && _parseDates.call( this ); (opts.sort === undefined || opts.sort) && this.sort(); - (opts.compute === undefined || opts.compute) && (this.computed = { + (opts.compute === undefined || opts.compute) && (this.basics.computed = { numYears: this.duration(), keywords: this.keywords() }); @@ -111,7 +121,7 @@ Definition of the JRSResume class. JRSResume.prototype.clear = function( clearMeta ) { clearMeta = ((clearMeta === undefined) && true) || clearMeta; clearMeta && (delete this.imp); - delete this.computed; // Don't use Object.keys() here + delete this.basics.computed; // Don't use Object.keys() here delete this.work; delete this.volunteer; delete this.education; @@ -169,8 +179,15 @@ Definition of the JRSResume class. var schema = FS.readFileSync( PATH.join( __dirname, 'resume.json' ), 'utf8' ); var schemaObj = JSON.parse( schema ); var validator = require('is-my-json-valid'); - var validate = validator( schemaObj ); - return validate( this ); + var validate = validator( schemaObj, { // Note [1] + formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ } + }); + var ret = validate( this ); + if( !ret ) { + this.basics.imp = this.basics.imp || { }; + this.basics.imp.validationErrors = validate.errors; + } + return ret; }; /** diff --git a/src/core/theme.js b/src/core/theme.js index 3d971a0..cbdc548 100644 --- a/src/core/theme.js +++ b/src/core/theme.js @@ -99,9 +99,18 @@ Abstract theme representation. var outFmt = '', isMajor = false; var portion = pathInfo.dir.replace(tplFolder,''); if( portion && portion.trim() ) { - var reg = /^(?:\/|\\)(html|latex|doc|pdf)(?:\/|\\)?/ig; + var reg = /^(?:\/|\\)(html|latex|doc|pdf|partials)(?:\/|\\)?/ig; var res = reg.exec( portion ); - res && (outFmt = res[1]); + if( res ) { + if( res[1] !== 'partials' ) { + outFmt = res[1]; + } + else { + that.partials = that.partials || []; + that.partials.push( { name: pathInfo.name, path: absPath } ); + return null; + } + } } // Otherwise, the output format is inferred from the filename, as in @@ -138,7 +147,7 @@ Abstract theme representation. }); // Now, get all the CSS files... - (this.cssFiles = fmts.filter(function( fmt ){ return fmt.ext === 'css'; })) + (this.cssFiles = fmts.filter(function( fmt ){ return fmt && (fmt.ext === 'css'); })) .forEach(function( cssf ) { // For each CSS file, get its corresponding HTML file var idx = _.findIndex(fmts, function( fmt ) { @@ -151,7 +160,7 @@ Abstract theme representation. // Remove CSS files from the formats array fmts = fmts.filter( function( fmt) { - return fmt.ext !== 'css'; + return fmt && (fmt.ext !== 'css'); }); return formatsHash; diff --git a/src/eng/handlebars-generator.js b/src/eng/handlebars-generator.js index 4d04a2a..ef61ed5 100644 --- a/src/eng/handlebars-generator.js +++ b/src/eng/handlebars-generator.js @@ -7,11 +7,66 @@ Handlebars template generate for FluentCV. var _ = require('underscore'); var HANDLEBARS = require('handlebars'); + var FS = require('fs'); + var moment = require('moment'); + var MD = require('marked'); + var H2W = require('../utils/html-to-wpml'); - module.exports = function( json, jst, format, cssInfo, opts ) { + module.exports = function( json, jst, format, cssInfo, opts, theme ) { + + _.each( theme.partials, function( el ) { + var tplData = FS.readFileSync( el.path, 'utf8' ); + var compiledTemplate = HANDLEBARS.compile( tplData ); + HANDLEBARS.registerPartial( el.name, compiledTemplate ); + }); + + HANDLEBARS.registerHelper("formatDate", function(datetime, format) { + if( moment ) { + return moment( datetime ).format( format ); + } + else { + return datetime; + } + }); + + HANDLEBARS.registerHelper("wpml", function( txt, inline ) { + inline = (inline && !inline.hash) || false; + txt = inline ? MD(txt.trim()).replace(/^\s*
|<\/p>\s*$/gi, '') : MD(txt.trim());
+ txt = H2W( txt.trim() );
+ return txt;
+ });
+
+ // http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates/
+ HANDLEBARS.registerHelper('compare', function(lvalue, rvalue, options) {
+
+ if (arguments.length < 3)
+ throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
+
+ var operator = options.hash.operator || "==";
+ var operators = {
+ '==': function(l,r) { return l == r; },
+ '===': function(l,r) { return l === r; },
+ '!=': function(l,r) { return l != r; },
+ '<': function(l,r) { return l < r; },
+ '>': function(l,r) { return l > r; },
+ '<=': function(l,r) { return l <= r; },
+ '>=': function(l,r) { return l >= r; },
+ 'typeof': function(l,r) { return typeof l == r; }
+ };
+
+ if (!operators[operator])
+ throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator);
+ var result = operators[operator](lvalue,rvalue);
+ return result ? options.fn(this) : options.inverse(this);
+ });
var template = HANDLEBARS.compile(jst);
- return template( { r: json, filt: opts.filters, cssInfo: cssInfo, headFragment: opts.headFragment || '' } );
+ return template({
+ r: json,
+ filt: opts.filters,
+ cssInfo: cssInfo,
+ headFragment: opts.headFragment || ''
+ });
};
diff --git a/src/eng/underscore-generator.js b/src/eng/underscore-generator.js
index 54be147..341021c 100644
--- a/src/eng/underscore-generator.js
+++ b/src/eng/underscore-generator.js
@@ -7,7 +7,7 @@ Underscore template generate for FluentCV.
var _ = require('underscore');
- module.exports = function( json, jst, format, cssInfo, opts ) {
+ module.exports = function( json, jst, format, cssInfo, opts, theme ) {
// Tweak underscore's default template delimeters
var delims = (opts.themeObj && opts.themeObj.delimeters) || opts.template;
diff --git a/src/gen/template-generator.js b/src/gen/template-generator.js
index 1fd47db..26859b4 100644
--- a/src/gen/template-generator.js
+++ b/src/gen/template-generator.js
@@ -139,11 +139,11 @@ Template-based resume generator base for FluentCV.
@param cssInfo Needs to be refactored.
@param opts Options and passthrough data.
*/
- single: function( json, jst, format, cssInfo, opts ) {
+ single: function( json, jst, format, cssInfo, opts, theme ) {
this.opts.freezeBreaks && ( jst = freeze(jst) );
var eng = require( '../eng/' + ((opts.themeObj && opts.themeObj.engine) ||
opts.engine) + '-generator' );
- var result = eng( json, jst, format, cssInfo, opts );
+ var result = eng( json, jst, format, cssInfo, opts, theme );
this.opts.freezeBreaks && ( result = unfreeze(result) );
return result;
}
@@ -193,12 +193,13 @@ Template-based resume generator base for FluentCV.
*/
function transform( rez, f, tplInfo, theme, outFolder ) {
var cssInfo = { file: tplInfo.css ? tplInfo.cssPath : null, data: tplInfo.css || null };
- var mk = this.single( rez, tplInfo.data, this.format, cssInfo, this.opts );
+ var mk = this.single( rez, tplInfo.data, this.format, cssInfo, this.opts, theme );
this.onBeforeSave && (mk = this.onBeforeSave( { mk: mk, theme: theme, outputFile: f } ));
var thisFilePath = PATH.join( outFolder, tplInfo.orgPath );
try {
MKDIRP.sync( PATH.dirname( tplInfo.major ? f : thisFilePath) );
FS.writeFileSync( tplInfo.major ? f : thisFilePath, mk, { encoding: 'utf8', flags: 'w' } );
+ this.onAfterSave && (mk = this.onAfterSave( { outputFile: (tplInfo.major ? f : thisFilePath), mk: mk } ));
}
catch(ex) {
console.log(ex);
diff --git a/src/gen/word-generator.js b/src/gen/word-generator.js
index db6cbd8..0ca3f01 100644
--- a/src/gen/word-generator.js
+++ b/src/gen/word-generator.js
@@ -3,11 +3,16 @@ MS Word resume generator for FluentCV.
@license Copyright (c) 2015 by James M. Devlin. All rights reserved.
*/
-var TemplateGenerator = require('./template-generator');
-var WordGenerator = module.exports = TemplateGenerator.extend({
+(function() {
- init: function(){
- this._super( 'doc', 'xml' );
- },
+ var TemplateGenerator = require('./template-generator');
+ var WordGenerator = module.exports = TemplateGenerator.extend({
-});
+ init: function(){
+ this._super( 'doc', 'xml' );
+ }
+
+ });
+
+
+}());
diff --git a/src/utils/html-to-wpml.js b/src/utils/html-to-wpml.js
new file mode 100644
index 0000000..b0136ef
--- /dev/null
+++ b/src/utils/html-to-wpml.js
@@ -0,0 +1,59 @@
+
+(function(){
+
+ var _ = require('underscore');
+ var HTML5Tokenizer = require('simple-html-tokenizer');
+
+ module.exports = function( html ) {
+
+ var final = '';
+ var is_bold = false, is_italic = false;
+ var depth = 0;
+
+ var tokens = HTML5Tokenizer.tokenize( html );
+ _.each( tokens, function( tok ) {
+ switch( tok.type ) {
+ case 'StartTag':
+ switch( tok.tagName ) {
+ case 'p':
+ final += '