Comparing version 1.1.0 to 1.2.0
@@ -11,3 +11,2 @@ { | ||
"extends": "eslint:recommended", | ||
"ignore-path" : ".gitignore", | ||
"rules": { | ||
@@ -14,0 +13,0 @@ "one-var" : [ "warn" , "never" ], // Disallow declaration of multiple var in one line |
@@ -0,1 +1,29 @@ | ||
### v1.2.0 | ||
- Release March 13, 2019 | ||
- Add new formatters | ||
- `convCurr(targetCurrency, sourceCurrency)` to convert from one currency to another | ||
- `formatN()` format number according to the locale (lang). Examples: | ||
- old `toFixed(2):toFR` can be replaced by `formatN(2)` | ||
- `formatC()` format currency according to the locale and the currency | ||
- old `toFixed(2)} {t(currency)}` can be replaced by `formatC(2)` | ||
- `formatD()` format date according to the locale. Same as `convDate`, but consider parameters are swapped | ||
for consistency with formatN. Moreover, `patternIn` is ISO8601 by default. | ||
- `convDate()` is deprecated | ||
- `add(valueToAdd)`, `mul(valueToMultiply)`, `sub(valueToSubstract)`,`div(value)` : mathematical operations | ||
- `substr(start, end)` : slice strings | ||
- `carbone.set` and `carbone.render` have new options | ||
- `currencySource` : default currency of source data. Ex 'EUR' | ||
- `currencyTarget` : default target currency when the formatter `convCurr` is used without target | ||
- `currencyRates` : rates, based on EUR { EUR : 1, USD : 1.14 } | ||
- Fix memory leaks: one file descriptor remains opened | ||
- Fix crash when template is not correct | ||
- Now `option.lang` must use the i18n format 'fr-fr' instead of just 'fr' | ||
- Add fallback to basic sorting when timsort crashes | ||
- Bump debug and moment to fix vulnerabilities | ||
- Remove Support of NodeJS 4 | ||
### v1.1.1 | ||
- Release October 11, 2018 | ||
- Better Windows support by improving path detection for `soffice` and `python` across all operating platforms. Done by Robert Kawecki (@rkaw92). | ||
### v1.1.0 | ||
@@ -2,0 +30,0 @@ - Release February 26, 2018 |
@@ -49,3 +49,3 @@ | ||
* @param {String} attributeSeparator [optional] attribute separator (`:` by default) | ||
* @param {String [, ...]} attributes [optional] list of object's attributes to print | ||
* @param {String} attributes [optional] list of object's attributes to print | ||
* @return {String} the computed result, or `d` if `d` is not an array | ||
@@ -90,6 +90,7 @@ */ | ||
/** | ||
* Count and print row (or column) number | ||
* Count and print row number of any array | ||
* | ||
* Usage example: `d[i].id:count()` will print a counter of the current row no matter the value of `id` | ||
* | ||
* @param {String} d Array passed by carbone | ||
* @param {Integer} loopId Loop ID generated by carbone | ||
* @param {String} start Number to start with (default: 1) | ||
@@ -108,3 +109,3 @@ * @return {String} Counter value | ||
arrayMap : arrayMap, | ||
count : count | ||
count : count | ||
}; |
@@ -5,3 +5,3 @@ var moment = require('moment'); | ||
/** | ||
* Format dates | ||
* Format dates (DEPRECATED, use formatD instead) | ||
* | ||
@@ -32,4 +32,42 @@ * @exampleContext {"lang":"en"} | ||
/** | ||
* Format dates | ||
* New since 1.2.0 | ||
* | ||
* @exampleContext {"lang":"en"} | ||
* @example ["20160131", "L"] | ||
* @example ["20160131", "LL"] | ||
* @example ["20160131", "LLLL"] | ||
* @example ["20160131", "dddd"] | ||
* @example [1410715640, "LLLL"] | ||
* | ||
* @exampleContext {"lang":"fr"} | ||
* @example ["2017-05-10T15:57:23.769561+03:00", "LLLL"] | ||
* @example ["2017-05-10 15:57:23.769561+03:00", "LLLL"] | ||
* @example ["20160131", "LLLL"] | ||
* @example ["20160131", "dddd"] | ||
* | ||
* @exampleContext {"lang":"fr"} | ||
* @example ["20160131", "dddd", "YYYYMMDD"] | ||
* @example [1410715640, "LLLL", "X" ] | ||
* | ||
* @param {String|Number} d date to format | ||
* @param {String} patternOut output format | ||
* @param {String} patternIn [optional] input format, ISO8601 by default | ||
* @return {String} return formatted date | ||
*/ | ||
function formatD (d, patternOut, patternIn) { | ||
if (d !== null && typeof d !== 'undefined') { | ||
moment.locale(this.lang); | ||
if (patternIn) { | ||
return moment(d + '', patternIn).format(patternOut); | ||
} | ||
return moment(d + '').format(patternOut); | ||
} | ||
return d; | ||
} | ||
module.exports = { | ||
formatD : formatD, | ||
convDate : convDate | ||
}; |
@@ -0,4 +1,246 @@ | ||
const locale = require('./_locale.js'); | ||
const currency = require('./_currency.js'); | ||
/** | ||
* Convert from one currency to another | ||
* | ||
* Fixed exchange rates are included by default in Carbone but you can provide a new echange rate | ||
* for one report in `options.currencyRates` of `Carbone.render` or globally with `Carbone.set` | ||
* | ||
* `convCurr()` without parameters converts automatically from `options.currencySource` to `options.currencyTarget`. | ||
* | ||
* @exampleContext {"currency": { "source":"EUR", "target":"USD", "rates": { "EUR":1, "USD":2 } } } | ||
* @example [10 ] | ||
* @example [1000 ] | ||
* @example [1000, "EUR" ] | ||
* @example [1000, "USD" ] | ||
* @example [1000, "USD", "USD" ] | ||
* | ||
* @param {Number} d Number to convert | ||
* @param {String} target [optional] convert to this currency ('EUR'). By default it equals `options.currencyTarget` | ||
* @param {String} source [optional] currency of source data ('USD'). By default it equals `options.currencySource` | ||
* @return {String} return converted values | ||
*/ | ||
function convCurr (d, target, source) { | ||
var _target = target ? target : this.currency.target; | ||
var _source = source ? source : this.currency.source; | ||
var _targetRate = this.currency.rates[_target] || 1; | ||
var _sourceRate = this.currency.rates[_source] || 1; | ||
var _valueInEuro = d / _sourceRate; | ||
this.modifiedCurrencyTarget = _target; | ||
return _valueInEuro * _targetRate; | ||
// raiseError(this.currency[_target] === undefined, 'Unknown rate for currency target "'+_target+'"'); | ||
// raiseError(this.currency[_base] === undefined , 'Unknown rate for currency base "'+_base+'"'); | ||
} | ||
/** | ||
* Round a number | ||
* | ||
* Same as toFixed(2) but it rounds number correclty `round(1.05, 1) = 1.1` | ||
* | ||
* @param {Number} num number | ||
* @param {Number} precision precision | ||
* @return {Number} | ||
*/ | ||
function round (num, precision) { | ||
// Source: https://stackoverflow.com/questions/11832914/round-to-at-most-2-decimal-places-only-if-necessary | ||
if (!('' + num).includes('e')) { | ||
return +(Math.round(num + 'e+' + precision) + 'e-' + precision); | ||
} | ||
else { | ||
var _arr = ('' + num).split('e'); | ||
var _sig = ''; | ||
if (+_arr[1] + precision > 0) { | ||
_sig = '+'; | ||
} | ||
return +(Math.round(+_arr[0] + 'e' + _sig + (+_arr[1] + precision)) + 'e-' + precision); | ||
} | ||
} | ||
/** | ||
* Fast method to format a number | ||
* Inspired by https://stackoverflow.com/questions/149055/how-can-i-format-numbers-as-dollars-currency-string-in-javascript | ||
* | ||
* Tested solutions (at least 5x slower to 10x slower): | ||
* - numeralJs | ||
* - https://github.com/BenjaminVanRyseghem/numbro | ||
* - http://mottie.github.io/javascript-number-formatter/ | ||
* - http://openexchangerates.github.io/accounting.js | ||
* - fastest method is native Intl.NumberFormat but locales are not included in Nodejs : https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/NumberFormat | ||
* | ||
* Other method to test and analyze | ||
* - https://jsperf.com/number-formatting-with-commas/5 | ||
* - https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript | ||
* | ||
* @param {Number} value | ||
* @param {Object} format | ||
* @param {Number} precision | ||
* @return {String} | ||
*/ | ||
function _format (value, format, precision = 3) { | ||
var _re = '\\d(?=(\\d{' + (format.group) + '})+' + (precision > 0 ? '\\D' : '$') + ')'; | ||
var _value = round(value, precision); | ||
var _num = _value.toFixed(Math.max(0, ~~precision)); | ||
return (format.decimal ? _num.replace('.', format.decimal) : _num).replace(new RegExp(_re, 'g'), '$&' + (format.separator || ',')); | ||
} | ||
/** | ||
* Format number according to the locale | ||
* | ||
* @exampleContext {"lang":"en-us"} | ||
* @example ["10" ] | ||
* @example ["1000.456" ] | ||
* | ||
* @param {Number} d Number to format | ||
* @param {Number} precision [optional] Number of decimal | ||
* @return {String} return converted values | ||
*/ | ||
function formatN (d, precision) { | ||
if (d !== null && typeof d !== 'undefined') { | ||
var _locale = locale[this.lang] || locale.en; | ||
return _format(d, _locale.number, precision); | ||
} | ||
return d; | ||
} | ||
/** | ||
* Format currency numbers | ||
* | ||
* Currencies are defined by the locale (`options.lang`). It can be overwritten by | ||
* `options.currencySource` and `options.currencyTarget` for one report in `Carbone.render` | ||
* or globally with `Carbone.set` | ||
* | ||
* When `options.lang === 'fr-FR'` the `currencySource` and `currencyTarget` equals by default `EUR`. | ||
* | ||
* If the formatter `convCurr() is used before, formatC prints the corresponding target currency used in `convCurr()`. | ||
* | ||
* By default, it prints with the currency symbol only, but you can use other output formats: | ||
* | ||
* `precisionOrFormat` can be | ||
* - Integer : change default precision of the currency | ||
* - M : print Major currency name without the number | ||
* - L : prints number with currency symbol (by default) | ||
* - LL : prints number with Major currency name | ||
* | ||
* @exampleContext {"lang":"en-us", "currency": { "source":"EUR", "target":"USD", "rates": { "EUR":1, "USD":2 } } } | ||
* @example ["1000.456" ] | ||
* @example ["1000.456", "M" ] | ||
* @example ["1" , "M" ] | ||
* @example ["1000" , "L" ] | ||
* @example ["1000" , "LL" ] | ||
* | ||
* @exampleContext {"lang":"fr-fr", "currency": { "source":"EUR", "target":"USD", "rates": { "EUR":1, "USD":2 } } } | ||
* @example ["1000.456" ] | ||
* | ||
* @param {Number} d Number to format | ||
* @param {Number} precisionOrFormat [optional] Number of decimal, or specific format | ||
* @return {String} return converted values | ||
*/ | ||
function formatC (d, precisionOrFormat) { | ||
if (d !== null && typeof d !== 'undefined') { | ||
var _locale = locale[this.lang] || locale.en; // TODO optimize, this test should be done before | ||
var _currency = this.modifiedCurrencyTarget || this.currency.target; | ||
var _currencyInfo = currency[_currency]; | ||
var _precision = _currencyInfo.precision; | ||
var _customPrec = parseInt(precisionOrFormat, 10); | ||
var _formatFn = _locale.currency.L; | ||
if (!isNaN(_customPrec)) { | ||
_precision = _customPrec; | ||
} | ||
else if ( _locale.currency[precisionOrFormat] instanceof Function ) { | ||
_formatFn = _locale.currency[precisionOrFormat]; | ||
} | ||
var _valueRaw = _format(convCurr.call(this, d), _locale.number, _precision); | ||
// reset modifiedCurrencyTarget for next use | ||
this.modifiedCurrencyTarget = null; | ||
return _formatFn(_valueRaw, | ||
_currencyInfo.symbol, | ||
_currencyInfo.minSymbol, | ||
(d != 1 ? _currencyInfo.major + 's' : _currencyInfo.major), | ||
(d != 1 ? _currencyInfo.minor + 's' : _currencyInfo.minor), | ||
_currencyInfo.name | ||
); | ||
} | ||
return d; | ||
} | ||
/** | ||
* Add two numbers | ||
* | ||
* @example [1000.4 , 2 ] | ||
* @example ["1000.4", "2" ] | ||
* | ||
* @param {Number} value Value to add | ||
* @return {Number} Result | ||
*/ | ||
function add (d, value) { | ||
if (d !== null && typeof d !== 'undefined') { | ||
return parseFloat(d) + parseFloat(value); | ||
} | ||
return d; | ||
} | ||
/** | ||
* Substract two numbers | ||
* | ||
* @example [1000.4 , 2 ] | ||
* @example ["1000.4", "2" ] | ||
* | ||
* @param {Number} value Value to substract | ||
* @return {Number} Result | ||
*/ | ||
function sub (d, value) { | ||
if (d !== null && typeof d !== 'undefined') { | ||
return parseFloat(d) - parseFloat(value); | ||
} | ||
return d; | ||
} | ||
/** | ||
* Multiply two numbers | ||
* | ||
* @example [1000.4 , 2 ] | ||
* @example ["1000.4", "2" ] | ||
* | ||
* @param {Number} value Value to multiply | ||
* @return {Number} Result | ||
*/ | ||
function mul (d, value) { | ||
if (d !== null && typeof d !== 'undefined') { | ||
return parseFloat(d) * parseFloat(value); | ||
} | ||
return d; | ||
} | ||
/** | ||
* Divide two numbers | ||
* | ||
* @example [1000.4 , 2 ] | ||
* @example ["1000.4", "2" ] | ||
* | ||
* @param {Number} value Value to divide | ||
* @return {Number} Result | ||
*/ | ||
function div (d, value) { | ||
if (d !== null && typeof d !== 'undefined' && parseFloat(value) !== 0) { | ||
return parseFloat(d) / parseFloat(value); | ||
} | ||
return d; | ||
} | ||
module.exports = { | ||
formatN : formatN, | ||
formatC : formatC, | ||
convCurr : convCurr, | ||
round : round, | ||
add : add, | ||
sub : sub, | ||
mul : mul, | ||
div : div, | ||
/** | ||
* Converts a number to an INT | ||
* DEPRECTAED | ||
* | ||
* @return {Number} | ||
@@ -9,4 +251,7 @@ */ | ||
}, | ||
/** | ||
* Converts a number with English specifications (decimal separator is '.') | ||
* DEPRECTAED | ||
* | ||
* @return {String} | ||
@@ -17,4 +262,7 @@ */ | ||
}, | ||
/** | ||
* Converts a number into string, keeping only <nb> decimals | ||
* DEPRECTAED | ||
* | ||
* @param {Number} nb | ||
@@ -26,4 +274,7 @@ * @return {String} | ||
}, | ||
/** | ||
* Converts a number with French specifications (decimal separator is ',') | ||
* DEPRECTAED | ||
* | ||
* @return {String} | ||
@@ -34,2 +285,3 @@ */ | ||
} | ||
}; |
@@ -155,8 +155,8 @@ | ||
* @exampleContext { "extension" : "odt" } | ||
* @example [ "my text \n contains Line Feed" , "my text <text:line-break/> contains Line Feed" ] | ||
* @example [ "my text \r\n contains Carriage Return" , "my text <text:line-break/> contains Line Feed" ] | ||
* @example [ "my blue \\n car" ] | ||
* @example [ "my blue \\r\\n car" ] | ||
* | ||
* @exampleContext { "extension" : "docx" } | ||
* @example [ "my text \n contains Line Feed" , "my text </w:t><w:br/><w:t> contains Line Feed" ] | ||
* @example [ "my text \r\n contains Carriage Return" , "my text </w:t><w:br/><w:t> contains Line Feed" ] | ||
* @example [ "my blue \\n car" ] | ||
* @example [ "my blue \\r\\n car" ] | ||
* | ||
@@ -178,2 +178,20 @@ * @param {Integer|String} d | ||
/** | ||
* Slice a string with a begin and an end | ||
* | ||
* @example ["foorbar", 0, 2] | ||
* @example ["foo"] | ||
* | ||
* @param {String} d | ||
* @param {Integer} begin | ||
* @param {Integer} end | ||
* @return {String} return the formatted string | ||
*/ | ||
function substr (d, begin, end) { | ||
if (typeof d === 'string') { | ||
return d.slice(begin, end); | ||
} | ||
return d; | ||
} | ||
module.exports = { | ||
@@ -187,3 +205,4 @@ lowerCase : lowerCase, | ||
unaccent : unaccent, | ||
print : print | ||
print : print, | ||
substr : substr | ||
}; |
@@ -16,3 +16,3 @@ var extracter = require('./extracter'); | ||
* 'lang' : selected lang, ex. "fr" | ||
* 'translations' : all translations {fr: {}, en: {}, es: {}} | ||
* 'translations' : all translations {fr: {}, en: {}, es: {}} | ||
* 'existingVariables' : existing variables (Array returned by parser.findVariables()) | ||
@@ -71,5 +71,5 @@ * 'extension' : template file extension | ||
* @param {Array} formatters. Example [ 'int', 'toFixed(2)' ] | ||
* @param {Object} existingFormatters | ||
* @param {Object} existingFormatters | ||
* @param {Boolean} onlyFormatterWhichInjectXML : if undefined|false, it returns only formatters which do not inject XML | ||
* if true, it returns only formatters which do inject XML. These formatters have | ||
* if true, it returns only formatters which do inject XML. These formatters have | ||
* the property canInjectXML = true | ||
@@ -243,3 +243,5 @@ * @return {String}. Example 'toFixed(int(d.number), 2)' | ||
} | ||
_rowInfo[_arrayLevel-1].rowShow |= _rowInfo[_arrayLevel].rowShow; | ||
if (_arrayLevel > 0) { | ||
_rowInfo[_arrayLevel-1].rowShow |= _rowInfo[_arrayLevel].rowShow; | ||
} | ||
_rowInfo[_arrayLevel].rowShow = 0; | ||
@@ -283,3 +285,4 @@ } | ||
sortXmlParts : function (arrayToSort) { | ||
timSort.sort(arrayToSort, function (a, b) { | ||
function compare (a, b) { | ||
var i = 0; | ||
@@ -302,3 +305,13 @@ var _a = a.pos[i]; | ||
return _aLength - _bLength; | ||
}); | ||
} | ||
// TimSort is bugged: https://github.com/mziccard/node-timsort/issues/14 | ||
// While we wait for this fix we use default sort when TimSort crashes | ||
try { | ||
return timSort.sort(arrayToSort, compare); | ||
} | ||
catch(e) { | ||
return arrayToSort.sort(compare); | ||
} | ||
}, | ||
@@ -369,3 +382,3 @@ | ||
_code.add('init', 'var _xmlPos = [0];\n'); | ||
_code.add('init', 'var _formatterOptions = { lang : options.lang, stopPropagation : false, enum : options.enum, extension : options.extension };\n'); | ||
_code.add('init', 'var _formatterOptions = { lang : options.lang, currency : options.currency, stopPropagation : false, enum : options.enum, extension : options.extension };\n'); | ||
_code.add('init', 'var formatters = options.formatters;\n'); // used by getFormatterString | ||
@@ -372,0 +385,0 @@ |
@@ -7,2 +7,4 @@ var path = require('path'); | ||
var debug = require('debug')('carbone:converter'); | ||
var convertToURL = require('./lopath').convertToURL; | ||
var which = require('which'); | ||
@@ -197,2 +199,5 @@ var pythonFile = path.join(__dirname, 'converter.py'); | ||
} | ||
// generate a URL in LibreOffice's format so that it's portable across OSes: | ||
// see: https://wiki.openoffice.org/wiki/URL_Basics | ||
var _userCacheURL = convertToURL(_userCachePath); | ||
@@ -203,3 +208,3 @@ // generate a unique pipe name | ||
var _officeParams = ['--headless', '--invisible', '--nocrashreport', '--nodefault', '--nologo', '--nofirststartwizard', '--norestore', | ||
'--quickstart', '--nolockcheck', '--accept='+_connectionString, '-env:UserInstallation=file://'+_userCachePath ]; | ||
'--quickstart', '--nolockcheck', '--accept='+_connectionString, '-env:UserInstallation='+_userCacheURL ]; | ||
@@ -420,35 +425,111 @@ // save unique name | ||
*/ | ||
function detectLibreOffice () { | ||
if (process.platform === 'darwin') { | ||
// it is better to use the python bundled with LibreOffice | ||
var _path = 'MacOS'; | ||
if (fs.existsSync('/Applications/LibreOffice.app/Contents/' + _path + '/python') === false) { | ||
_path = 'Resources'; | ||
function detectLibreOffice (additionalPaths) { | ||
function _findBundledPython(sofficePath, pythonName) { | ||
if (!sofficePath) { | ||
return null; | ||
} | ||
// Try finding a Python binary shipped alongside the soffice binary, | ||
// either in its actual directory, or - if it's a symbolic link - | ||
// in the directory it points to. | ||
var _symlinkDestination; | ||
try { | ||
_symlinkDestination = path.resolve(path.dirname(sofficePath), fs.readlinkSync(sofficePath)); | ||
// Assume symbolic link, will throw in case it's not: | ||
sofficeActualDirectory = path.dirname(_symlinkDestination); | ||
} catch (errorToIgnore) { | ||
// Not a symlink. | ||
sofficeActualDirectory = path.dirname(sofficePath); | ||
} | ||
// Check for the Python binary in the actual soffice path: | ||
try { | ||
return which.sync(pythonName, { path: sofficeActualDirectory }); | ||
} catch (errorToIgnore) { | ||
// No bundled Python found. | ||
return null; | ||
} | ||
} | ||
if (fs.existsSync('/Applications/LibreOffice.app/Contents/' + _path + '/python') === true) { | ||
isLibreOfficeFound = true; | ||
converterOptions.pythonExecPath = '/Applications/LibreOffice.app/Contents/' + _path + '/python'; | ||
converterOptions.sofficeExecPath = '/Applications/LibreOffice.app/Contents/MacOS/soffice'; | ||
function _findBinaries(paths, pythonName, sofficeName) { | ||
var _whichPython; | ||
var _whichSoffice; | ||
var _sofficeActualDirectory; | ||
// Look for the soffice binary - first in the well-known paths, then in | ||
// the system PATH. On Linux, this prioritizes "upstream" (TDF) packages | ||
// over distro-provided ones from the OS' repository. | ||
_whichSoffice = which.sync(sofficeName, { path: paths.join(':'), nothrow: true }) || which.sync(sofficeName, { nothrow: true }) || null; | ||
// Check for a Python binary bundled with soffice, fall back to system-wide: | ||
// This is a bit more complex, since we deal with some corner cases. | ||
// 1. Hopefully use the python from the original soffice package, same dir | ||
// (this might fail on Mac if python is not in MacOS/, but in Resources/). | ||
// 1a. Corner case: on Linux, if soffice was in /usr/bin/soffice and NOT | ||
// a symlink, then we would hit /usr/bin/python, which is probably python2. | ||
// This is why we try with python3 first, to defend against this. | ||
// 2. Try finding it in any of the well-known paths - this might result in | ||
// using Python from *another install* of LibreOffice, but it should be ok. | ||
// This is only attempted if the paths exist on this system to avoid | ||
// a fallback to system PATH that "which" does when passed an empty string. | ||
// 3. Fall back to system python (hopefully named python3). | ||
_whichPython = _findBundledPython(_whichSoffice, 'python3') || | ||
_findBundledPython(_whichSoffice, 'python') || | ||
(paths.length > 0 && which.sync('python3', { path: paths.join(':'), nothrow: true })) || | ||
(paths.length > 0 && which.sync('python', { path: paths.join(':'), nothrow: true })) || | ||
which.sync('python3', { nothrow: true }) || | ||
which.sync('python', { nothrow: true }) || null; | ||
return { | ||
soffice: _whichSoffice, | ||
python: _whichPython | ||
}; | ||
} | ||
function _listProgramDirectories(basePath, pattern) { | ||
try { | ||
return fs.readdirSync(basePath).filter(function _isLibreOfficeDirectory(dirname) { | ||
return pattern.test(dirname); | ||
}).map(function _buildFullProgramPath(dirname) { | ||
return path.join(basePath, dirname, 'program'); | ||
}); | ||
} catch (errorToIgnore) { | ||
return []; | ||
} | ||
} | ||
var _pathsToCheck = additionalPaths || []; | ||
// overridable file names to look for in the checked paths: | ||
var _pythonName = 'python'; | ||
var _sofficeName = 'soffice'; | ||
var _linuxDirnamePattern = /^libreoffice\d+\.\d+$/; | ||
var _windowsDirnamePattern = /^LibreOffice \d+(?:\.\d+)?$/i; | ||
if (process.platform === 'darwin') { | ||
_pathsToCheck = _pathsToCheck.concat([ | ||
// It is better to use the python bundled with LibreOffice: | ||
'/Applications/LibreOffice.app/Contents/MacOS', | ||
'/Applications/LibreOffice.app/Contents/Resources' | ||
]); | ||
} | ||
else if (process.platform === 'linux') { | ||
var directories = fs.readdirSync('/opt'); | ||
var patternLibreOffice = /libreoffice\d+\.\d+/; | ||
for (var i = 0; i < directories.length; i++) { | ||
if (patternLibreOffice.test(directories[i]) === true) { | ||
var directoryLibreOffice = '/opt/' + directories[i] + '/program'; | ||
if (fs.existsSync(directoryLibreOffice + '/python') === true) { | ||
converterOptions.pythonExecPath = directoryLibreOffice + '/python'; | ||
converterOptions.sofficeExecPath = directoryLibreOffice + '/soffice'; | ||
isLibreOfficeFound = true; | ||
} | ||
} | ||
} | ||
// The Document Foundation packages (.debs, at least) install to /opt, | ||
// into a directory named after the contained LibreOffice version. | ||
// Add any existing directories that match this to the list. | ||
_pathsToCheck = _pathsToCheck.concat(_listProgramDirectories('/opt', _linuxDirnamePattern)); | ||
} | ||
else if (process.platform === 'win32') { | ||
_pathsToCheck = _pathsToCheck | ||
.concat(_listProgramDirectories('C:\\Program Files', _windowsDirnamePattern)) | ||
.concat(_listProgramDirectories('C:\\Program Files (x86)', _windowsDirnamePattern)); | ||
_pythonName = 'python.exe'; | ||
} | ||
else { | ||
debug('your platform is not supported yet'); | ||
debug('your platform "%s" is not supported yet', process.platform); | ||
} | ||
// Common logic for all OSes: perform the search and save results as options: | ||
var _foundPaths = _findBinaries(_pathsToCheck, _pythonName, _sofficeName); | ||
if (_foundPaths.soffice) { | ||
debug('LibreOffice found: soffice at %s, python at %s', _foundPaths.soffice, _foundPaths.python); | ||
isLibreOfficeFound = true; | ||
converterOptions.pythonExecPath = _foundPaths.python; | ||
converterOptions.sofficeExecPath = _foundPaths.soffice; | ||
} | ||
if (isLibreOfficeFound === false) { | ||
@@ -467,1 +548,2 @@ debug('cannot find LibreOffice. Document conversion cannot be used'); | ||
module.exports = converter; | ||
module.exports.detectLibreOffice = detectLibreOffice; |
@@ -26,3 +26,5 @@ var path = require('path'); | ||
fs.read(fd, _buf, 0, 10, 0, function (err, bytesRead, buffer) { | ||
callback(err, (buffer.slice(0, 2).toString() === 'PK')); | ||
fs.close(fd, function () { | ||
callback(err, (buffer.slice(0, 2).toString() === 'PK')); | ||
}); | ||
}); | ||
@@ -29,0 +31,0 @@ }); |
@@ -14,2 +14,3 @@ var fs = require('fs'); | ||
var moment = require('moment'); | ||
var locale = require('../formatters/_locale.js'); | ||
var debug = require('debug')('carbone'); | ||
@@ -28,2 +29,6 @@ | ||
* translations : overwrite carbone translations object | ||
* currencySource : currency of data, it depends on the locale if empty | ||
* currencyTarget : default target currency when the formatter convCurr is used without target | ||
* it depends on the locale if empty | ||
* currencyRates : rates, based on EUR { EUR : 1, USD : 1.14 } | ||
* } | ||
@@ -78,2 +83,5 @@ */ | ||
params.translations = {}; | ||
params.currencySource = ''; | ||
params.currencyTarget = ''; | ||
params.currencyRates = { EUR : 1, USD : 1.14 }; | ||
}, | ||
@@ -152,2 +160,6 @@ | ||
* 'translations' : overwrite all loaded translations {fr: {}, en: {}, es: {}} | ||
* 'enum' : { ORDER_STATUS : ['open', 'close', 'sent'] | ||
* 'currencySource' : currency of data, 'EUR' | ||
* 'currencyTarget' : default target currency when the formatter convCurr is used without target | ||
* 'currencyRates' : rates, based on EUR { EUR : 1, USD : 1.14 } | ||
* } | ||
@@ -237,3 +249,4 @@ * @param {Function} callbackRaw(err, res, reportName) : Function called after generation with the result | ||
enum : options.enum, | ||
lang : options.lang || params.lang, | ||
currency : {}, | ||
lang : (options.lang || params.lang).toLowerCase(), // TODO test toLowerCase | ||
translations : options.translations || params.translations, | ||
@@ -247,2 +260,14 @@ complement : options.complement, | ||
}; | ||
var _currency = _options.currency; | ||
var _locale = locale[_options.lang] || locale.en; | ||
var _currencyFromLocale = _locale.currency.code; | ||
_currency.source = options.currencySource || params.currencySource; | ||
_currency.target = options.currencyTarget || params.currencyTarget; | ||
_currency.rates = options.currencyRates || params.currencyRates; | ||
if (!_currency.source) { | ||
_currency.source = _currencyFromLocale; | ||
} | ||
if (!_currency.target) { | ||
_currency.target = _currencyFromLocale; | ||
} | ||
return callback(_options, callbackFn); | ||
@@ -249,0 +274,0 @@ }); |
@@ -9,23 +9,63 @@ var os = require('os'); | ||
/* Temp directory */ | ||
tempPath : tmpDir, | ||
tempPath : tmpDir, | ||
/* Template path */ | ||
templatePath : process.cwd(), | ||
templatePath : process.cwd(), | ||
/* Number of LibreOffice + Python factory to start by default. One factory = 2 threads */ | ||
factories : 1, | ||
factories : 1, | ||
/* If LibreOffice fails to convert one document, how many times we re-try to convert this file? */ | ||
attempts : 3, | ||
attempts : 3, | ||
/* If true, it will start LibreOffice + Python factory immediately (true by default if the carbone server is used). | ||
If false, it will start LibreOffice + Python factory only when at least one document conversion is needed.*/ | ||
startFactory : false, | ||
startFactory : false, | ||
/* The method helper.getUID() add this prefix in the uid */ | ||
uidPrefix : 'c', | ||
uidPrefix : 'c', | ||
/* If multiple factories are used, the pipe name is generated automatically to avoid conflicts */ | ||
pipeNamePrefix : '_carbone', | ||
pipeNamePrefix : '_carbone', | ||
/* list of file parsed for translation and find tools */ | ||
extensionParsed : '(odt|ods|odp|xlsx|docx|pptx|xml|html)', | ||
extensionParsed : '(odt|ods|odp|xlsx|docx|pptx|xml|html)', | ||
/* lang of carbone and moment.js */ | ||
lang : 'en', | ||
lang : 'en', | ||
/* all locales are loaded in memory */ | ||
translations : {} | ||
translations : {}, | ||
/* currency of data, it depends on the locale if empty */ | ||
currencySource : '', | ||
/* default target currency when the formatter convCurr is used without target. It depends on the locale if empty */ | ||
currencyTarget : '', | ||
/* currency rates, always based on EUR. So EUR should always equals "1" */ | ||
currencyRates : { | ||
EUR : 1, | ||
USD : 1.1403, | ||
JPY : 123.20, | ||
BGN : 1.9558, | ||
CZK : 25.653, | ||
DKK : 7.4679, | ||
GBP : 0.854701, | ||
HUF : 321.45, | ||
PLN : 4.2957, | ||
RON : 4.6656, | ||
SEK : 10.2460, | ||
CHF : 1.1256, | ||
ISK : 134.00, | ||
NOK : 9.8648, | ||
HRK : 7.4335, | ||
RUB : 77.6790, | ||
TRY : 6.1707, | ||
AUD : 1.6189, | ||
BRL : 4.2889, | ||
CAD : 1.5328, | ||
CNY : 7.8280, | ||
HKD : 8.9325, | ||
IDR : 16241.35, | ||
ILS : 4.2320, | ||
INR : 79.4315, | ||
KRW : 1279.13, | ||
MXN : 22.3080, | ||
MYR : 4.7106, | ||
NZD : 1.7045, | ||
PHP : 59.809, | ||
SGD : 1.5525, | ||
THB : 36.587, | ||
ZAR : 16.1175 | ||
} | ||
}; | ||
{ | ||
"name": "carbone", | ||
"description": "Fast, Simple and Powerful report generator. Injects JSON and produces PDF, DOCX, XLSX, ODT, PPTX, ODS, ...!", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"bin": "bin/carbone", | ||
@@ -21,6 +21,7 @@ "main": "./lib", | ||
"dependencies": { | ||
"debug": "=2.6.8", | ||
"moment": "=2.18.1", | ||
"debug": "=2.6.9", | ||
"moment": "=2.19.3", | ||
"moxie-zip": "=0.0.3", | ||
"timsort": "=0.3.0", | ||
"which": "=1.3.1", | ||
"yauzl": "=2.8.0" | ||
@@ -27,0 +28,0 @@ }, |
@@ -64,3 +64,3 @@ Carbone.io | ||
- NodeJS 4.x+ | ||
- NodeJS 8.x+ | ||
- Runs on OSX, Linux (servers and desktop), and coming soon on Windows | ||
@@ -242,3 +242,3 @@ | ||
convertTo : 'pdf', // String|Object, to convert the document (pdf, xlsx, docx, ods, csv, txt, ...) | ||
lang : 'en', // String, output lang of the report | ||
lang : 'en-us', // String, output lang of the report | ||
complement : {}, // Object|Array, extra data accessible in the template with {c.} instead of {d.} | ||
@@ -255,4 +255,4 @@ variableStr : '{#def = d.id}', // String, predefined alias string, see designer's documentation | ||
translations : { // Object, dynamically overwrite all loaded translations for this rendering | ||
fr : {'one':'un' }, | ||
es : {'one':'uno'} | ||
'fr-fr' : {'one':'un' }, | ||
'es-es' : {'one':'uno'} | ||
} | ||
@@ -306,6 +306,6 @@ } | ||
templatePath : process.cwd(), // String, default template path, and lang path | ||
lang : 'fr', // String, set default lang of carbone, can be overwrite by carbone.render options | ||
lang : 'fr-fr', // String, set default lang of carbone, can be overwrite by carbone.render options | ||
translations : { // Object, in-memory loaded translations at startup. Can be overwritten here | ||
fr : {'one':'un' }, | ||
es : {'one':'uno'} | ||
'fr-fr' : {'one':'un' }, | ||
'es-es' : {'one':'uno'} | ||
}, | ||
@@ -321,3 +321,3 @@ factories : 1, // Number of LibreOffice worker | ||
carbone.set({ | ||
lang : 'en' | ||
lang : 'en-us' | ||
}); | ||
@@ -339,3 +339,3 @@ ``` | ||
yesOrNo : function (data) { // data = d.myBoolean | ||
if (this.lang === 'fr') { | ||
if (this.lang === 'fr-fr') { | ||
return data === true ? 'oui' : 'non'; | ||
@@ -377,3 +377,3 @@ } | ||
# example: | ||
carbone translate -l fr -p path/to/template_default_path | ||
carbone translate -l fr-fr -p path/to/template_default_path | ||
``` | ||
@@ -380,0 +380,0 @@ |
313155
32
4818
6
+ Addedwhich@=1.3.1
Updateddebug@=2.6.9
Updatedmoment@=2.19.3