From 48beb28180a55a108f73f692a6eee9069fa7c927 Mon Sep 17 00:00:00 2001 From: Rijn Buve Date: Mon, 6 Mar 2017 12:08:42 +0100 Subject: [PATCH 01/18] Added CSV converter as an example --- csv/FileSave.js | 188 +++++++++ csv/converter.html | 161 ++++++++ csv/jquery.csv.js | 975 +++++++++++++++++++++++++++++++++++++++++++++ csv/sample.csv | 15 + 4 files changed, 1339 insertions(+) create mode 100644 csv/FileSave.js create mode 100644 csv/converter.html create mode 100644 csv/jquery.csv.js create mode 100644 csv/sample.csv diff --git a/csv/FileSave.js b/csv/FileSave.js new file mode 100644 index 0000000..940a516 --- /dev/null +++ b/csv/FileSave.js @@ -0,0 +1,188 @@ +/* FileSaver.js + * A saveAs() FileSaver implementation. + * 1.3.2 + * 2016-06-16 18:25:19 + * + * By Eli Grey, http://eligrey.com + * License: MIT + * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md + */ + +/*global self */ +/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ + +/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ + +var saveAs = saveAs || (function(view) { + "use strict"; + // IE <10 is explicitly unsupported + if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { + return; + } + var + doc = view.document + // only get URL when necessary in case Blob.js hasn't overridden it yet + , get_URL = function() { + return view.URL || view.webkitURL || view; + } + , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") + , can_use_save_link = "download" in save_link + , click = function(node) { + var event = new MouseEvent("click"); + node.dispatchEvent(event); + } + , is_safari = /constructor/i.test(view.HTMLElement) || view.safari + , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent) + , throw_outside = function(ex) { + (view.setImmediate || view.setTimeout)(function() { + throw ex; + }, 0); + } + , force_saveable_type = "application/octet-stream" + // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to + , arbitrary_revoke_timeout = 1000 * 40 // in ms + , revoke = function(file) { + var revoker = function() { + if (typeof file === "string") { // file is an object URL + get_URL().revokeObjectURL(file); + } else { // file is a File + file.remove(); + } + }; + setTimeout(revoker, arbitrary_revoke_timeout); + } + , dispatch = function(filesaver, event_types, event) { + event_types = [].concat(event_types); + var i = event_types.length; + while (i--) { + var listener = filesaver["on" + event_types[i]]; + if (typeof listener === "function") { + try { + listener.call(filesaver, event || filesaver); + } catch (ex) { + throw_outside(ex); + } + } + } + } + , auto_bom = function(blob) { + // prepend BOM for UTF-8 XML and text/* types (including HTML) + // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF + if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { + return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); + } + return blob; + } + , FileSaver = function(blob, name, no_auto_bom) { + if (!no_auto_bom) { + blob = auto_bom(blob); + } + // First try a.download, then web filesystem, then object URLs + var + filesaver = this + , type = blob.type + , force = type === force_saveable_type + , object_url + , dispatch_all = function() { + dispatch(filesaver, "writestart progress write writeend".split(" ")); + } + // on any filesys errors revert to saving with object URLs + , fs_error = function() { + if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { + // Safari doesn't allow downloading of blob urls + var reader = new FileReader(); + reader.onloadend = function() { + var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); + var popup = view.open(url, '_blank'); + if(!popup) view.location.href = url; + url=undefined; // release reference before dispatching + filesaver.readyState = filesaver.DONE; + dispatch_all(); + }; + reader.readAsDataURL(blob); + filesaver.readyState = filesaver.INIT; + return; + } + // don't create more object URLs than needed + if (!object_url) { + object_url = get_URL().createObjectURL(blob); + } + if (force) { + view.location.href = object_url; + } else { + var opened = view.open(object_url, "_blank"); + if (!opened) { + // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html + view.location.href = object_url; + } + } + filesaver.readyState = filesaver.DONE; + dispatch_all(); + revoke(object_url); + } + ; + filesaver.readyState = filesaver.INIT; + + if (can_use_save_link) { + object_url = get_URL().createObjectURL(blob); + setTimeout(function() { + save_link.href = object_url; + save_link.download = name; + click(save_link); + dispatch_all(); + revoke(object_url); + filesaver.readyState = filesaver.DONE; + }); + return; + } + + fs_error(); + } + , FS_proto = FileSaver.prototype + , saveAs = function(blob, name, no_auto_bom) { + return new FileSaver(blob, name || blob.name || "download", no_auto_bom); + } + ; + // IE 10+ (native saveAs) + if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { + return function(blob, name, no_auto_bom) { + name = name || blob.name || "download"; + + if (!no_auto_bom) { + blob = auto_bom(blob); + } + return navigator.msSaveOrOpenBlob(blob, name); + }; + } + + FS_proto.abort = function(){}; + FS_proto.readyState = FS_proto.INIT = 0; + FS_proto.WRITING = 1; + FS_proto.DONE = 2; + + FS_proto.error = + FS_proto.onwritestart = + FS_proto.onprogress = + FS_proto.onwrite = + FS_proto.onabort = + FS_proto.onerror = + FS_proto.onwriteend = + null; + + return saveAs; + }( + typeof self !== "undefined" && self + || typeof window !== "undefined" && window + || this.content + )); +// `self` is undefined in Firefox for Android content script context +// while `this` is nsIContentFrameMessageManager +// with an attribute `content` that corresponds to the window + +if (typeof module !== "undefined" && module.exports) { + module.exports.saveAs = saveAs; +} else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) { + define("FileSaver.js", function() { + return saveAs; + }); +} diff --git a/csv/converter.html b/csv/converter.html new file mode 100644 index 0000000..eec48f2 --- /dev/null +++ b/csv/converter.html @@ -0,0 +1,161 @@ + + + + + + + + Mapcode CSV Converter + + + +

Mapcode CSV Converter

+

+ Select a CSV file which has the following columns:
+ latitude, longitude, territory (optional)

+

The context is used to specify the territory for which the shortest mapcode should be displayed. + For example, for South Africa, this would be ZAF.

+

Example of a CSF file:

+

+ -28.700225, 28.602905, (note the territory is missing)
+ -28.700225, 28.602905, ZAF
+ -28.433676, 19.986191, ZAF +

+

This produces the following output:

+

+ -28.700225,28.602905,LSO MRG.8XX,8KYVG.4C8Y
+ -28.700225,28.602905,ZAF QNW2.Y72,8KYVG.4C8Y
+ -28.433676,19.986191,ZAF 6MQP.506,8K70Z.7PB4 +

+

The output is automatically saved to mapcodes.csv in your Downloads folder.

+ +
+ +
+
+ + +
+ +
+ + + + + + + + + + diff --git a/csv/jquery.csv.js b/csv/jquery.csv.js new file mode 100644 index 0000000..6a64af2 --- /dev/null +++ b/csv/jquery.csv.js @@ -0,0 +1,975 @@ +/** + * jQuery-csv (jQuery Plugin) + * + * This document is licensed as free software under the terms of the + * MIT License: http://www.opensource.org/licenses/mit-license.php + * + * Acknowledgements: + * The original design and influence to implement this library as a jquery + * plugin is influenced by jquery-json (http://code.google.com/p/jquery-json/). + * If you're looking to use native JSON.Stringify but want additional backwards + * compatibility for browsers that don't support it, I highly recommend you + * check it out. + * + * A special thanks goes out to rwk@acm.org for providing a lot of valuable + * feedback to the project including the core for the new FSM + * (Finite State Machine) parsers. If you're looking for a stable TSV parser + * be sure to take a look at jquery-tsv (http://code.google.com/p/jquery-tsv/). + + * For legal purposes I'll include the "NO WARRANTY EXPRESSED OR IMPLIED. + * USE AT YOUR OWN RISK.". Which, in 'layman's terms' means, by using this + * library you are accepting responsibility if it breaks your code. + * + * Legal jargon aside, I will do my best to provide a useful and stable core + * that can effectively be built on. + * + * Copyrighted 2012 by Evan Plaice. + */ + +RegExp.escape= function(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); +}; + +(function (undefined) { + 'use strict'; + + var $; + + // to keep backwards compatibility + if (typeof jQuery !== 'undefined' && jQuery) { + $ = jQuery; + } else { + $ = {}; + } + + + /** + * jQuery.csv.defaults + * Encapsulates the method paramater defaults for the CSV plugin module. + */ + + $.csv = { + defaults: { + separator:',', + delimiter:'"', + headers:true + }, + + hooks: { + castToScalar: function(value, state) { + var hasDot = /\./; + if (isNaN(value)) { + return value; + } else { + if (hasDot.test(value)) { + return parseFloat(value); + } else { + var integer = parseInt(value); + if(isNaN(integer)) { + return null; + } else { + return integer; + } + } + } + } + }, + + parsers: { + parse: function(csv, options) { + // cache settings + var separator = options.separator; + var delimiter = options.delimiter; + + // set initial state if it's missing + if(!options.state.rowNum) { + options.state.rowNum = 1; + } + if(!options.state.colNum) { + options.state.colNum = 1; + } + + // clear initial state + var data = []; + var entry = []; + var state = 0; + var value = ''; + var exit = false; + + function endOfEntry() { + // reset the state + state = 0; + value = ''; + + // if 'start' hasn't been met, don't output + if(options.start && options.state.rowNum < options.start) { + // update global state + entry = []; + options.state.rowNum++; + options.state.colNum = 1; + return; + } + + if(options.onParseEntry === undefined) { + // onParseEntry hook not set + data.push(entry); + } else { + var hookVal = options.onParseEntry(entry, options.state); // onParseEntry Hook + // false skips the row, configurable through a hook + if(hookVal !== false) { + data.push(hookVal); + } + } + //console.log('entry:' + entry); + + // cleanup + entry = []; + + // if 'end' is met, stop parsing + if(options.end && options.state.rowNum >= options.end) { + exit = true; + } + + // update global state + options.state.rowNum++; + options.state.colNum = 1; + } + + function endOfValue() { + if(options.onParseValue === undefined) { + // onParseValue hook not set + entry.push(value); + } else { + var hook = options.onParseValue(value, options.state); // onParseValue Hook + // false skips the row, configurable through a hook + if(hook !== false) { + entry.push(hook); + } + } + //console.log('value:' + value); + // reset the state + value = ''; + state = 0; + // update global state + options.state.colNum++; + } + + // escape regex-specific control chars + var escSeparator = RegExp.escape(separator); + var escDelimiter = RegExp.escape(delimiter); + + // compile the regEx str using the custom delimiter/separator + var match = /(D|S|\r\n|\n|\r|[^DS\r\n]+)/; + var matchSrc = match.source; + matchSrc = matchSrc.replace(/S/g, escSeparator); + matchSrc = matchSrc.replace(/D/g, escDelimiter); + match = new RegExp(matchSrc, 'gm'); + + // put on your fancy pants... + // process control chars individually, use look-ahead on non-control chars + csv.replace(match, function (m0) { + if(exit) { + return; + } + switch (state) { + // the start of a value + case 0: + // null last value + if (m0 === separator) { + value += ''; + endOfValue(); + break; + } + // opening delimiter + if (m0 === delimiter) { + state = 1; + break; + } + // null last value + if (/^(\r\n|\n|\r)$/.test(m0)) { + endOfValue(); + endOfEntry(); + break; + } + // un-delimited value + value += m0; + state = 3; + break; + + // delimited input + case 1: + // second delimiter? check further + if (m0 === delimiter) { + state = 2; + break; + } + // delimited data + value += m0; + state = 1; + break; + + // delimiter found in delimited input + case 2: + // escaped delimiter? + if (m0 === delimiter) { + value += m0; + state = 1; + break; + } + // null value + if (m0 === separator) { + endOfValue(); + break; + } + // end of entry + if (/^(\r\n|\n|\r)$/.test(m0)) { + endOfValue(); + endOfEntry(); + break; + } + // broken paser? + throw new Error('CSVDataError: Illegal State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + + // un-delimited input + case 3: + // null last value + if (m0 === separator) { + endOfValue(); + break; + } + // end of entry + if (/^(\r\n|\n|\r)$/.test(m0)) { + endOfValue(); + endOfEntry(); + break; + } + if (m0 === delimiter) { + // non-compliant data + throw new Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + } + // broken parser? + throw new Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + default: + // shenanigans + throw new Error('CSVDataError: Unknown State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + } + //console.log('val:' + m0 + ' state:' + state); + }); + + // submit the last entry + // ignore null last line + if(entry.length !== 0) { + endOfValue(); + endOfEntry(); + } + + return data; + }, + + // a csv-specific line splitter + splitLines: function(csv, options) { + // cache settings + var separator = options.separator; + var delimiter = options.delimiter; + + // set initial state if it's missing + if(!options.state.rowNum) { + options.state.rowNum = 1; + } + + // clear initial state + var entries = []; + var state = 0; + var entry = ''; + var exit = false; + + function endOfLine() { + // reset the state + state = 0; + + // if 'start' hasn't been met, don't output + if(options.start && options.state.rowNum < options.start) { + // update global state + entry = ''; + options.state.rowNum++; + return; + } + + if(options.onParseEntry === undefined) { + // onParseEntry hook not set + entries.push(entry); + } else { + var hookVal = options.onParseEntry(entry, options.state); // onParseEntry Hook + // false skips the row, configurable through a hook + if(hookVal !== false) { + entries.push(hookVal); + } + } + + // cleanup + entry = ''; + + // if 'end' is met, stop parsing + if(options.end && options.state.rowNum >= options.end) { + exit = true; + } + + // update global state + options.state.rowNum++; + } + + // escape regex-specific control chars + var escSeparator = RegExp.escape(separator); + var escDelimiter = RegExp.escape(delimiter); + + // compile the regEx str using the custom delimiter/separator + var match = /(D|S|\n|\r|[^DS\r\n]+)/; + var matchSrc = match.source; + matchSrc = matchSrc.replace(/S/g, escSeparator); + matchSrc = matchSrc.replace(/D/g, escDelimiter); + match = new RegExp(matchSrc, 'gm'); + + // put on your fancy pants... + // process control chars individually, use look-ahead on non-control chars + csv.replace(match, function (m0) { + if(exit) { + return; + } + switch (state) { + // the start of a value/entry + case 0: + // null value + if (m0 === separator) { + entry += m0; + state = 0; + break; + } + // opening delimiter + if (m0 === delimiter) { + entry += m0; + state = 1; + break; + } + // end of line + if (m0 === '\n') { + endOfLine(); + break; + } + // phantom carriage return + if (/^\r$/.test(m0)) { + break; + } + // un-delimit value + entry += m0; + state = 3; + break; + + // delimited input + case 1: + // second delimiter? check further + if (m0 === delimiter) { + entry += m0; + state = 2; + break; + } + // delimited data + entry += m0; + state = 1; + break; + + // delimiter found in delimited input + case 2: + // escaped delimiter? + var prevChar = entry.substr(entry.length - 1); + if (m0 === delimiter && prevChar === delimiter) { + entry += m0; + state = 1; + break; + } + // end of value + if (m0 === separator) { + entry += m0; + state = 0; + break; + } + // end of line + if (m0 === '\n') { + endOfLine(); + break; + } + // phantom carriage return + if (m0 === '\r') { + break; + } + // broken paser? + throw new Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']'); + + // un-delimited input + case 3: + // null value + if (m0 === separator) { + entry += m0; + state = 0; + break; + } + // end of line + if (m0 === '\n') { + endOfLine(); + break; + } + // phantom carriage return + if (m0 === '\r') { + break; + } + // non-compliant data + if (m0 === delimiter) { + throw new Error('CSVDataError: Illegal quote [Row:' + options.state.rowNum + ']'); + } + // broken parser? + throw new Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']'); + default: + // shenanigans + throw new Error('CSVDataError: Unknown state [Row:' + options.state.rowNum + ']'); + } + //console.log('val:' + m0 + ' state:' + state); + }); + + // submit the last entry + // ignore null last line + if(entry !== '') { + endOfLine(); + } + + return entries; + }, + + // a csv entry parser + parseEntry: function(csv, options) { + // cache settings + var separator = options.separator; + var delimiter = options.delimiter; + + // set initial state if it's missing + if(!options.state.rowNum) { + options.state.rowNum = 1; + } + if(!options.state.colNum) { + options.state.colNum = 1; + } + + // clear initial state + var entry = []; + var state = 0; + var value = ''; + + function endOfValue() { + if(options.onParseValue === undefined) { + // onParseValue hook not set + entry.push(value); + } else { + var hook = options.onParseValue(value, options.state); // onParseValue Hook + // false skips the value, configurable through a hook + if(hook !== false) { + entry.push(hook); + } + } + // reset the state + value = ''; + state = 0; + // update global state + options.state.colNum++; + } + + // checked for a cached regEx first + if(!options.match) { + // escape regex-specific control chars + var escSeparator = RegExp.escape(separator); + var escDelimiter = RegExp.escape(delimiter); + + // compile the regEx str using the custom delimiter/separator + var match = /(D|S|\n|\r|[^DS\r\n]+)/; + var matchSrc = match.source; + matchSrc = matchSrc.replace(/S/g, escSeparator); + matchSrc = matchSrc.replace(/D/g, escDelimiter); + options.match = new RegExp(matchSrc, 'gm'); + } + + // put on your fancy pants... + // process control chars individually, use look-ahead on non-control chars + csv.replace(options.match, function (m0) { + switch (state) { + // the start of a value + case 0: + // null last value + if (m0 === separator) { + value += ''; + endOfValue(); + break; + } + // opening delimiter + if (m0 === delimiter) { + state = 1; + break; + } + // skip un-delimited new-lines + if (m0 === '\n' || m0 === '\r') { + break; + } + // un-delimited value + value += m0; + state = 3; + break; + + // delimited input + case 1: + // second delimiter? check further + if (m0 === delimiter) { + state = 2; + break; + } + // delimited data + value += m0; + state = 1; + break; + + // delimiter found in delimited input + case 2: + // escaped delimiter? + if (m0 === delimiter) { + value += m0; + state = 1; + break; + } + // null value + if (m0 === separator) { + endOfValue(); + break; + } + // skip un-delimited new-lines + if (m0 === '\n' || m0 === '\r') { + break; + } + // broken paser? + throw new Error('CSVDataError: Illegal State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + + // un-delimited input + case 3: + // null last value + if (m0 === separator) { + endOfValue(); + break; + } + // skip un-delimited new-lines + if (m0 === '\n' || m0 === '\r') { + break; + } + // non-compliant data + if (m0 === delimiter) { + throw new Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + } + // broken parser? + throw new Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + default: + // shenanigans + throw new Error('CSVDataError: Unknown State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + } + //console.log('val:' + m0 + ' state:' + state); + }); + + // submit the last value + endOfValue(); + + return entry; + } + }, + + helpers: { + + /** + * $.csv.helpers.collectPropertyNames(objectsArray) + * Collects all unique property names from all passed objects. + * + * @param {Array} objects Objects to collect properties from. + * + * Returns an array of property names (array will be empty, + * if objects have no own properties). + */ + collectPropertyNames: function (objects) { + + var o, propName, props = []; + for (o in objects) { + for (propName in objects[o]) { + if ((objects[o].hasOwnProperty(propName)) && + (props.indexOf(propName) < 0) && + (typeof objects[o][propName] !== 'function')) { + + props.push(propName); + } + } + } + return props; + } + }, + + /** + * $.csv.toArray(csv) + * Converts a CSV entry string to a javascript array. + * + * @param {Array} csv The string containing the CSV data. + * @param {Object} [options] An object containing user-defined options. + * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). + * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). + * + * This method deals with simple CSV strings only. It's useful if you only + * need to parse a single entry. If you need to parse more than one line, + * use $.csv2Array instead. + */ + toArray: function(csv, options, callback) { + options = (options !== undefined ? options : {}); + var config = {}; + config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false); + config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; + config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; + var state = (options.state !== undefined ? options.state : {}); + + // setup + options = { + delimiter: config.delimiter, + separator: config.separator, + onParseEntry: options.onParseEntry, + onParseValue: options.onParseValue, + state: state + }; + + var entry = $.csv.parsers.parseEntry(csv, options); + + // push the value to a callback if one is defined + if(!config.callback) { + return entry; + } else { + config.callback('', entry); + } + }, + + /** + * $.csv.toArrays(csv) + * Converts a CSV string to a javascript array. + * + * @param {String} csv The string containing the raw CSV data. + * @param {Object} [options] An object containing user-defined options. + * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). + * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). + * + * This method deals with multi-line CSV. The breakdown is simple. The first + * dimension of the array represents the line (or entry/row) while the second + * dimension contains the values (or values/columns). + */ + toArrays: function(csv, options, callback) { + options = (options !== undefined ? options : {}); + var config = {}; + config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false); + config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; + config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; + + // setup + var data = []; + options = { + delimiter: config.delimiter, + separator: config.separator, + onPreParse: options.onPreParse, + onParseEntry: options.onParseEntry, + onParseValue: options.onParseValue, + onPostParse: options.onPostParse, + start: options.start, + end: options.end, + state: { + rowNum: 1, + colNum: 1 + } + }; + + // onPreParse hook + if(options.onPreParse !== undefined) { + options.onPreParse(csv, options.state); + } + + // parse the data + data = $.csv.parsers.parse(csv, options); + + // onPostParse hook + if(options.onPostParse !== undefined) { + options.onPostParse(data, options.state); + } + + // push the value to a callback if one is defined + if(!config.callback) { + return data; + } else { + config.callback('', data); + } + }, + + /** + * $.csv.toObjects(csv) + * Converts a CSV string to a javascript object. + * @param {String} csv The string containing the raw CSV data. + * @param {Object} [options] An object containing user-defined options. + * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). + * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). + * @param {Boolean} [headers] Indicates whether the data contains a header line. Defaults to true. + * + * This method deals with multi-line CSV strings. Where the headers line is + * used as the key for each value per entry. + */ + toObjects: function(csv, options, callback) { + options = (options !== undefined ? options : {}); + var config = {}; + config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false); + config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; + config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; + config.headers = 'headers' in options ? options.headers : $.csv.defaults.headers; + options.start = 'start' in options ? options.start : 1; + + // account for headers + if(config.headers) { + options.start++; + } + if(options.end && config.headers) { + options.end++; + } + + // setup + var lines = []; + var data = []; + + options = { + delimiter: config.delimiter, + separator: config.separator, + onPreParse: options.onPreParse, + onParseEntry: options.onParseEntry, + onParseValue: options.onParseValue, + onPostParse: options.onPostParse, + start: options.start, + end: options.end, + state: { + rowNum: 1, + colNum: 1 + }, + match: false, + transform: options.transform + }; + + // fetch the headers + var headerOptions = { + delimiter: config.delimiter, + separator: config.separator, + start: 1, + end: 1, + state: { + rowNum:1, + colNum:1 + } + }; + + // onPreParse hook + if(options.onPreParse !== undefined) { + options.onPreParse(csv, options.state); + } + + // parse the csv + var headerLine = $.csv.parsers.splitLines(csv, headerOptions); + var headers = $.csv.toArray(headerLine[0], options); + + // fetch the data + lines = $.csv.parsers.splitLines(csv, options); + + // reset the state for re-use + options.state.colNum = 1; + if(headers){ + options.state.rowNum = 2; + } else { + options.state.rowNum = 1; + } + + // convert data to objects + for(var i=0, len=lines.length; i -1) { + strValue = strValue.replace(config.delimiter, config.delimiter + config.delimiter); + } + + var escMatcher = '\n|\r|S|D'; + escMatcher = escMatcher.replace('S', config.separator); + escMatcher = escMatcher.replace('D', config.delimiter); + + if (strValue.search(escMatcher) > -1) { + strValue = config.delimiter + strValue + config.delimiter; + } + lineValues.push(strValue); + } + output += lineValues.join(config.separator) + '\r\n'; + } + + // push the value to a callback if one is defined + if(!config.callback) { + return output; + } else { + config.callback('', output); + } + }, + + /** + * $.csv.fromObjects(objects) + * Converts a javascript dictionary to a CSV string. + * + * @param {Object} objects An array of objects containing the data. + * @param {Object} [options] An object containing user-defined options. + * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). + * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). + * @param {Character} [sortOrder] Sort order of columns (named after + * object properties). Use 'alpha' for alphabetic. Default is 'declare', + * which means, that properties will _probably_ appear in order they were + * declared for the object. But without any guarantee. + * @param {Character or Array} [manualOrder] Manually order columns. May be + * a strin in a same csv format as an output or an array of header names + * (array items won't be parsed). All the properties, not present in + * `manualOrder` will be appended to the end in accordance with `sortOrder` + * option. So the `manualOrder` always takes preference, if present. + * + * This method generates a CSV file from an array of objects (name:value pairs). + * It starts by detecting the headers and adding them as the first line of + * the CSV file, followed by a structured dump of the data. + */ + fromObjects: function(objects, options, callback) { + options = (options !== undefined ? options : {}); + var config = {}; + config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false); + config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; + config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; + config.headers = 'headers' in options ? options.headers : $.csv.defaults.headers; + config.sortOrder = 'sortOrder' in options ? options.sortOrder : 'declare'; + config.manualOrder = 'manualOrder' in options ? options.manualOrder : []; + config.transform = options.transform; + + if (typeof config.manualOrder === 'string') { + config.manualOrder = $.csv.toArray(config.manualOrder, config); + } + + if (config.transform !== undefined) { + var origObjects = objects; + objects = []; + + var i; + for (i = 0; i < origObjects.length; i++) { + objects.push(config.transform.call(undefined, origObjects[i])); + } + } + + var props = $.csv.helpers.collectPropertyNames(objects); + + if (config.sortOrder === 'alpha') { + props.sort(); + } // else {} - nothing to do for 'declare' order + + if (config.manualOrder.length > 0) { + + var propsManual = [].concat(config.manualOrder); + var p; + for (p = 0; p < props.length; p++) { + if (propsManual.indexOf( props[p] ) < 0) { + propsManual.push( props[p] ); + } + } + props = propsManual; + } + + var o, p, line, output = [], propName; + if (config.headers) { + output.push(props); + } + + for (o = 0; o < objects.length; o++) { + line = []; + for (p = 0; p < props.length; p++) { + propName = props[p]; + if (propName in objects[o] && typeof objects[o][propName] !== 'function') { + line.push(objects[o][propName]); + } else { + line.push(''); + } + } + output.push(line); + } + + // push the value to a callback if one is defined + return $.csv.fromArrays(output, options, config.callback); + } + }; + + // Maintenance code to maintain backward-compatibility + // Will be removed in release 1.0 + $.csvEntry2Array = $.csv.toArray; + $.csv2Array = $.csv.toArrays; + $.csv2Dictionary = $.csv.toObjects; + + // CommonJS module is defined + if (typeof module !== 'undefined' && module.exports) { + module.exports = $.csv; + } + +}).call( this ); diff --git a/csv/sample.csv b/csv/sample.csv new file mode 100644 index 0000000..5b4e04c --- /dev/null +++ b/csv/sample.csv @@ -0,0 +1,15 @@ +-28.700225, 28.602905, +-28.700225, 28.602905, ZAF +-28.433676, 19.986191, ZAF +-28.433676, 19.986191, +-28.433676, 19.986191, NAM +13.661115, 144.560790000, +-20.529975, 57.356025000, +52.383984, 4.865401375, +26.904854, 95.138497, +-28.433676, 19.986191, +-28.433676, 19.986191, +13.661115, 144.560790000, ZAF +-20.529975, 57.356025000, ZAF +52.383984, 4.865401375, ZAF +26.904854, 95.138497, ZAF From c9027bac6b6f39540ba9e4332ad1d7476433cda8 Mon Sep 17 00:00:00 2001 From: Rijn Buve Date: Mon, 6 Mar 2017 12:38:13 +0100 Subject: [PATCH 02/18] Updated CSV converter --- csv/converter.html | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/csv/converter.html b/csv/converter.html index eec48f2..48742f1 100644 --- a/csv/converter.html +++ b/csv/converter.html @@ -19,17 +19,24 @@ - Mapcode CSV Converter + Simple Mapcode CSV Converter -

Mapcode CSV Converter

+

Simple Mapcode CSV Converter

- Select a CSV file which has the following columns:
- latitude, longitude, territory (optional)

-

The context is used to specify the territory for which the shortest mapcode should be displayed. - For example, for South Africa, this would be ZAF.

-

Example of a CSF file:

+ This page allows you to upload a CSV file with coordinates and convert + them to mapcodes. The CSV file needs to have 3 columns:
+ latitude (in degrees), longitude (in degrees), territory (optional)
+ Field delimiter: , (comma)
+ Decimal point: . (dot)
+

+

+ The context is used to specify the territory for which the shortest mapcode should be displayed. + For example, for South Africa, this would be ZAF. Note that if you leave the field empty, the result might + not be what you expected. +

+

Example of a CSV file:

-28.700225, 28.602905, (note the territory is missing)
-28.700225, 28.602905, ZAF
@@ -37,6 +44,7 @@

Mapcode CSV Converter

This produces the following output:

+ latitude,longitude,mapcode,international
-28.700225,28.602905,LSO MRG.8XX,8KYVG.4C8Y
-28.700225,28.602905,ZAF QNW2.Y72,8KYVG.4C8Y
-28.433676,19.986191,ZAF 6MQP.506,8K70Z.7PB4 @@ -94,10 +102,6 @@

Mapcode CSV Converter

// read the file metadata var output = '' - output += '' + escape(file.name) + '
\n'; - output += ' - FileType: ' + (file.type || 'n/a') + '
\n'; - output += ' - FileSize: ' + file.size + ' bytes
\n'; - output += ' - LastModified: ' + (file.lastModifiedDate ? file.lastModifiedDate.toLocaleDateString() : 'n/a') + '
\n'; // read the file contents printTable(file); @@ -113,8 +117,8 @@

Mapcode CSV Converter

var csv = event.target.result; var data = $.csv.toArrays(csv); var html = ''; - var csv = ''; - html += 'latitudelongitudemapcode<international/tr>'; + var csv = 'latitude,longitude,territory,mapcode,international\r\n'; + html += 'latitudelongitudeterritorymapcodeinternational/tr>'; for (var row in data) { var lat = Number(data[row][0]); var lon = Number(data[row][1]); @@ -131,12 +135,14 @@

Mapcode CSV Converter

} if (r.length > 0) { var shortest = r[0]; - html += '' + shortest.territoryAlphaCode + ' ' + shortest.mapcode + '\r\n'; - csv += shortest.territoryAlphaCode + ' ' + shortest.mapcode + ','; + html += '' + shortest.territoryAlphaCode + '\r\n'; + html += '' + shortest.mapcode + '\r\n'; + csv += shortest.territoryAlphaCode + ',' + shortest.mapcode + ','; } else { - html += "N/A" - csv += 'N/A,'; + html += "\r\n"; + html += "\r\n"; + csv += ",,"; } var international = encodeInternational(lat, lon)[0]; html += '' + international.mapcode + '\r\n'; From a66acb15b96d8a2844fbfc7b2ce4092588cb656d Mon Sep 17 00:00:00 2001 From: Rijn Buve Date: Mon, 6 Mar 2017 12:57:40 +0100 Subject: [PATCH 03/18] Added explanatory text --- csv/{FileSave.js => FileSaver.js} | 0 csv/converter.html | 14 +++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) rename csv/{FileSave.js => FileSaver.js} (100%) diff --git a/csv/FileSave.js b/csv/FileSaver.js similarity index 100% rename from csv/FileSave.js rename to csv/FileSaver.js diff --git a/csv/converter.html b/csv/converter.html index 48742f1..2ad4d7e 100644 --- a/csv/converter.html +++ b/csv/converter.html @@ -49,7 +49,15 @@

Simple Mapcode CSV Converter

-28.700225,28.602905,ZAF QNW2.Y72,8KYVG.4C8Y
-28.433676,19.986191,ZAF 6MQP.506,8K70Z.7PB4

-

The output is automatically saved to mapcodes.csv in your Downloads folder.

+

+ In some browsers the output will automatically be saved to mapcodes.csv in your Downloads folder. + However, not all browsers support this functionality and will shows the CSV file instead on your screen. + (Note: We've tried this with Google Chrome and it seems to work fine.) +

+

+ Troubleshooting: If you select a file and nothing happens, check the format of the file. It should contain exactly + 3 columns, separated by comma's and the latitude/longitudes must use decimal points. +

@@ -63,7 +71,7 @@

Simple Mapcode CSV Converter

- +