text: ﻿// ----------------------------------------
  // Data
  
  pub.JSON = {
      /**
       * Function parses and validates a string as JSON-object. Usage:
       * var obj = b.JSON.decode(str);
       * var str = b.JSON.encode(obj);
       *
       * @cat Data
       * @subcat JSON
       * @method JSON.decode
       * @param  {String} String to be parsed as JSON-object.
       * @return {Object} Returns JSON-object or throws an error if invalid JSON has been provided.
      */
      // From: jQuery JavaScript Library v1.7.1 http://jquery.com/
      decode: function (data) {
          if (typeof data !== "string" || !data) {
              return null;
          }
          var rvalidchars = /^[\],:{}\s]*$/,
              rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
              rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
              rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g;
  
          // Make sure the incoming data is actual JSON
          // Logic borrowed from http://json.org/json2.js
          if (rvalidchars.test(data.replace(rvalidescape, "@")
              .replace(rvalidtokens, "]")
              .replace(rvalidbraces, ""))) {
              return (new Function("return " + data))();
          }
          error("b.JSON.decode(), invalid JSON: " + data);
      },
      /**
       * Function convert an javascript object to a JSON-string. Usage:
       * var str = b.JSON.encode(obj);
       * var obj = b.JSON.decode(str);
       *
       * @cat Data
       * @subcat JSON
       * @method JSON.encode
       * @param  {Object} Object to be converted to a JSON-string
       * @return {String} Returns JSON-string
       */
      // From: https://gist.github.com/754454
      encode: function (obj) {
          var t = typeof (obj);
          if (t !== "object" || obj === null) {
              // simple data type
              if (t === "string") obj = '"' + obj + '"';
              return String(obj);
          } else {
              // recurse array or object
              var n, v, json = [], arr = (obj && obj.constructor === Array);
  
              for (n in obj) {
                  v = obj[n];
                  t = typeof (v);
                  if (obj.hasOwnProperty(n)) {
                      if (t === "string") v = '"' + v + '"'; else if (t === "object" && v !== null) v = pub.JSON.encode(v);
                      json.push((arr ? "" : '"' + n + '":') + String(v));
                  }
              }
              return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
          }
      }
  };
  
  // Taken and hijacked from d3.js robust csv parser. Hopefully Michael Bostock won't mind.
  // https://github.com/mbostock/d3/tree/master/src/dsv
  pub.CSV = new CSV();
  function CSV() {
      var reParse = null,
          reFormat = null,
          delimiterStr = null,
          delimiterCode = null;
  
      initDelimiter(',');
      function initDelimiter(delimiter) {
          reParse = new RegExp("\r\n|[" + delimiter + "\r\n]", "g"), // field separator regex
              reFormat = new RegExp("[\"" + delimiter + "\n]"),
              delimiterCode = delimiter.charCodeAt(0);
          delimiterStr = delimiter;
      };
  
      /**
       * Sets the delimiter of the CSV decode and encode function.
       *
       * @cat Data
       * @subcat CSV
       * @method CSV.delimiter
       * @param  {String} [delimiter] Optional Sets the delimiter for CSV parsing
       * @return {String} Returns the current delimiter if called without argument
      */
      this.delimiter = function (delimiter) {
          if (arguments.length === 0) return delimiterStr;
          if (typeof delimiter === 'string') {
              initDelimiter(delimiter);
          } else {
              error("b.CSV.delimiter, separator has to be a character or string");
          }
      };
  
      /**
       * Function parses a string as CSV-object Array. Usage:
       * var arr = b.CSV.decode(str);
       * var str = b.CSV.encode(arr);
       *
       * @cat Data
       * @subcat CSV
       * @method CSV.decode
       * @param  {String} String to be parsed as CSV-object.
       * @return {Array} Returns CSV-object Array
      */
      this.decode = function (text) {
          var header;
          return parseRows(text, function (row, i) {
              if (i) {
                  var o = {}, j = -1, m = header.length;
                  while (++j < m) o[header[j]] = row[j];
                  return o;
              } else {
                  header = row;
                  return null;
              }
          });
      };
  
      /**
       * Function convert an javascript array of objects to a CSV-string. Usage:
       * var str = b.CSV.encode(arr);
       * var arr = b.CSV.decode(str);
       *
       * @cat Data
       * @subcat CSV
       * @method CSV.encode
       * @param  {Array} Array to be converted to a CSV-string
       * @return {String} Returns CSV-string
       */
      this.encode = function (rows) {
          var csvStrings = [];
          var header = [];
          var firstRow = rows[0]; // all rows have to have the same properties keys
          // gather infos for the header
          for (var propname in firstRow) {
              if (firstRow.hasOwnProperty(propname)) {
                  header.push(propname);
              };
          };
          csvStrings.push(formatRow(header));
          for (var i = 0; i < rows.length; i++) {
              var row = rows[i];
              var tokens = [];
              for (var ii = 0; ii < header.length; ii++) {
                  tokens.push(row[header[ii]]);
              };
              csvStrings.push(formatRow(tokens));
          };
          return csvStrings.join("\n");
      };
  
      function formatRow(row) {
          return row.map(formatValue).join(delimiterStr);
      }
  
      function formatValue(text) {
          return reFormat.test(text) ? "\"" + text.replace(/\"/g, "\"\"") + "\"" : text;
      }
  
      function parseRows(text, f) {
          var EOL = {}, // sentinel value for end-of-line
              EOF = {}, // sentinel value for end-of-file
              rows = [], // output rows
              n = 0, // the current line number
              t, // the current token
              eol; // is the current token followed by EOL?
  
          reParse.lastIndex = 0; // work-around bug in FF 3.6
  
          function token() {
              if (reParse.lastIndex >= text.length) return EOF; // special case: end of file
              if (eol) { eol = false; return EOL; } // special case: end of line
  
              // special case: quotes
              var j = reParse.lastIndex;
              if (text.charCodeAt(j) === 34) {
                  var i = j;
                  while (i++ < text.length) {
                      if (text.charCodeAt(i) === 34) {
                          if (text.charCodeAt(i + 1) !== 34) break;
                          i++;
                      }
                  }
                  reParse.lastIndex = i + 2;
                  var c = text.charCodeAt(i + 1);
                  if (c === 13) {
                      eol = true;
                      if (text.charCodeAt(i + 2) === 10) reParse.lastIndex++;
                  } else if (c === 10) {
                      eol = true;
                  }
                  return text.substring(j + 1, i).replace(/""/g, "\"");
              }
  
              // common case
              var m = reParse.exec(text);
              if (m) {
                  eol = m[0].charCodeAt(0) !== delimiterCode;
                  return text.substring(j, m.index);
              }
              reParse.lastIndex = text.length;
              return text.substring(j);
          }
  
          while ((t = token()) !== EOF) {
              var a = [];
              while (t !== EOL && t !== EOF) {
                  a.push(t);
                  t = token();
              }
              if (f && !(a = f(a, n++))) continue;
              rows.push(a);
          }
  
          return rows;
      };
  };
  
  // -- Conversion --
  
  /**  @class b */
  
  /**
   * Converts a byte, char, int, or color to a String containing the
   * equivalent binary notation. For example color(0, 102, 153, 255)
   * will convert to the String "11111111000000000110011010011001". This
   * function can help make your geeky debugging sessions much happier.
   *
  
   * @cat Data
   * @subcat Conversion
   * @method binary
   * @param {Number} num value to convert
   * @param {Number} [numBits] number of digits to return
   * @return {String} A formatted string
   */
  // From: http://processingjs.org/reference/binary_/
  pub.binary = function (num, numBits) {
      var bit;
      if (numBits > 0) bit = numBits;
      else if (num instanceof Char) {
          bit = 16;
          num |= 0;
      } else {
          bit = 32;
          while (bit > 1 && !(num >>> bit - 1 & 1)) bit--;
      }
      var result = "";
      while (bit > 0) result += num >>> --bit & 1 ? "1" : "0";
      return result;
  };
  
  /**
   * Converts a String representation of a binary number to its
   * equivalent integer value. For example, unbinary("00001000") will
   * return 8.
   *
   * @cat Data
   * @subcat Conversion
   * @method unbinary
   * @param {String} binaryString value to convert
   * @return {Number} The integer representation
   */
  // From: http://processingjs.org/reference/unbinary_/
  pub.unbinary = function (binaryString) {
      var i = binaryString.length - 1,
          mask = 1,
          result = 0;
      while (i >= 0) {
          var ch = binaryString[i--];
          if (ch !== "0" && ch !== "1") throw "the value passed into unbinary was not an 8 bit binary number";
          if (ch === "1") result += mask;
          mask <<= 1;
      }
      return result;
  };
  
  
  var decimalToHex = function (d, padding) {
      padding = padding === undef || padding === null ? padding = 8 : padding;
      if (d < 0) d = 4294967295 + d + 1;
      var hex = Number(d).toString(16).toUpperCase();
      while (hex.length < padding) hex = "0" + hex;
      if (hex.length >= padding) hex = hex.substring(hex.length - padding, hex.length);
      return hex;
  };
  
  /**
   * Convert a number to a hex representation.
   *
   * @cat Data
   * @subcat Conversion
   * @method hex
   * @param {Number} value The number to convert
   * @param {Number} [len] The length of the hex number to be created, default: 8
   * @return {String} The hex representation as a string
   */
  pub.hex = function (value, len) {
      if (arguments.length === 1) len = 8;
      return decimalToHex(value, len);
  };
  
  var unhexScalar = function (hex) {
      var value = parseInt("0x" + hex, 16);
      if (value > 2147483647) value -= 4294967296;
      return value;
  }
  
  /**
   * Convert a hex representation to a number.
   *
   * @cat Data
   * @subcat Conversion
   * @method unhex
   * @param {String} hex The hex representation
   * @return {Number} The number
   */
  pub.unhex = function (hex) {
      if (hex instanceof Array) {
          var arr = [];
          for (var i = 0; i < hex.length; i++) arr.push(unhexScalar(hex[i]));
          return arr;
      }
      return unhexScalar(hex);
  };
  
  
  // -- String Functions --
  
  
  
  /**
   * Removes multiple, leading or trailing spaces and punctuation from "words". E.g. converts "word!" to "word". Especially useful together with b.words();
   *
   * @method trimWord
   * @cat Data
   * @subcat String Functions
   * @param {String} s The String to trim
   * @param
   */
  // from: http://www.qodo.co.uk/blog/javascript-trim-leading-and-trailing-spaces/
  pub.trimWord = function (s) {
      s = s.replace(/(^[,.!?-]*)|([-,.!?]*$)/gi, "");
      s = s.replace(/\s*/gi, "");
      //    s = s.replace(/[ ]{2,}/gi," ");
      s = s.replace(/\n*/, "");
      return s;
  };
  
  /**
   * Combines an array of Strings into one String, each separated by
   * the character(s) used for the separator parameter. To join arrays
   * of ints or floats, it's necessary to first convert them to strings
   * using nf() or nfs().
   *
   * @method join
   * @cat Data
   * @subcat String Functions
   * @param {Array} array A string array
   * @param {String} separator The separator to be inserted
   * @return {String} The joined string
   */
  // http://processingjs.org/reference/join_/
  pub.join = function (array, separator) {
      return array.join(separator);
  };
  
  /**
   * The split() function breaks a string into pieces using a
   * character or string as the divider. The delim parameter specifies the
   * character or characters that mark the boundaries between each piece. A
   * String[] array is returned that contains each of the pieces.
   *
   * If the result is a set of numbers, you can convert the String[] array
   * to to a float[] or int[] array using the datatype conversion functions
   * int() and float() (see example above).
   *
   * The splitTokens() function works in a similar fashion, except that it
   * splits using a range of characters instead of a specific character or
   * sequence.
   *
   * @cat Data
   * @subcat String Functions
   * @method split
   * @param {String} str the String to be split
   * @param {String} [delim] The string used to separate the data
   * @return {Array} Array of strings
   */
  // http://processingjs.org/reference/split_/
  pub.split = function (str, delim) {
      return str.split(delim);
  };
  
  /**
   * The splitTokens() function splits a String at one or many character
   * "tokens." The tokens parameter specifies the character or characters
   * to be used as a boundary.
   *
   * If no tokens character is specified, any whitespace character is used
   * to split. Whitespace characters include tab (\t), line feed (\n),
   * carriage return (\r), form feed (\f), and space. To convert a String
   * to an array of integers or floats, use the datatype conversion functions
   * int() and float() to convert the array of Strings.
   *
   * @cat Data
   * @subcat String Functions
   * @method splitTokens
   * @param {String} str the String to be split
   * @param {String} [tokens] list of individual characters that will be used as separators
   * @return {Array} Array of strings
   */
  // From: http://processingjs.org/reference/splitTokens_/
  pub.splitTokens = function (str, tokens) {
      if (arguments.length === 1) tokens = "\n\t\r\u000c ";
      tokens = "[" + tokens + "]";
      var ary = [];
      var index = 0;
      var pos = str.search(tokens);
      while (pos >= 0) {
          if (pos === 0) str = str.substring(1);
          else {
              ary[index] = str.substring(0, pos);
              index++;
              str = str.substring(pos);
          }
          pos = str.search(tokens);
      }
      if (str.length > 0) ary[index] = str;
      if (ary.length === 0) ary = undef;
      return ary;
  };
  
  /* todo */
  pub.match = function (str, regexp) {
      return str.match(regexp);
  };
  
  /* todo */
  pub.matchAll = function (aString, aRegExp) {
      var results = [],
          latest;
      var regexp = new RegExp(aRegExp, "g");
      while ((latest = regexp.exec(aString)) !== null) {
          results.push(latest);
          if (latest[0].length === 0)++regexp.lastIndex;
      }
      return results.length > 0 ? results : null;
  };
  
  function nfCoreScalar(value, plus, minus, leftDigits, rightDigits, group) {
      var sign = value < 0 ? minus : plus;
      var autoDetectDecimals = rightDigits === 0;
      var rightDigitsOfDefault = rightDigits === undef || rightDigits < 0 ? 0 : rightDigits;
      var absValue = Math.abs(value);
      if (autoDetectDecimals) {
          rightDigitsOfDefault = 1;
          absValue *= 10;
          while (Math.abs(Math.round(absValue) - absValue) > 1.0E-6 && rightDigitsOfDefault < 7) {
              ++rightDigitsOfDefault;
              absValue *= 10;
          }
      } else if (rightDigitsOfDefault !== 0) absValue *= Math.pow(10, rightDigitsOfDefault);
      var number, doubled = absValue * 2;
      if (Math.floor(absValue) === absValue) number = absValue;
      else if (Math.floor(doubled) === doubled) {
          var floored = Math.floor(absValue);
          number = floored + floored % 2;
      } else number = Math.round(absValue);
      var buffer = "";
      var totalDigits = leftDigits + rightDigitsOfDefault;
      while (totalDigits > 0 || number > 0) {
          totalDigits--;
          buffer = "" + number % 10 + buffer;
          number = Math.floor(number / 10);
      }
      if (group !== undef) {
          var i = buffer.length - 3 - rightDigitsOfDefault;
          while (i > 0) {
              buffer = buffer.substring(0, i) + group + buffer.substring(i);
              i -= 3;
          }
      }
      if (rightDigitsOfDefault > 0) return sign + buffer.substring(0, buffer.length - rightDigitsOfDefault) + "." + buffer.substring(buffer.length - rightDigitsOfDefault, buffer.length);
      return sign + buffer;
  }
  function nfCore(value, plus, minus, leftDigits, rightDigits, group) {
      if (value instanceof Array) {
          var arr = [];
          for (var i = 0, len = value.length; i < len; i++) arr.push(nfCoreScalar(value[i], plus, minus, leftDigits, rightDigits, group));
          return arr;
      }
      return nfCoreScalar(value, plus, minus, leftDigits, rightDigits, group);
  }
  
  /**
   * Utility function for formatting numbers into strings. There
   * are two versions, one for formatting floats and one for formatting
   * ints. The values for the digits, left, and right parameters should
   * always be positive integers.
  
   * As shown in the above example, nf() is used to add zeros to the
   * left and/or right of a number. This is typically for aligning a list
   * of numbers. To remove digits from a floating-point number, use the
   * int(), ceil(), floor(), or round() functions.
   *
   * @cat Data
   * @subcat String Functions
   * @method nf
   * @param {Number} value The Number to convert
   * @param {Number} leftDigits
   * @param {Number} rightDigits
   * @return {String} The formatted string
   */
  // From: http://processingjs.org/reference/nf_/
  pub.nf = function (value, leftDigits, rightDigits) {
      return nfCore(value, "", "-", leftDigits, rightDigits);
  };
  
  /**
   * Utility function for formatting numbers into strings. Similar to nf()
   * but leaves a blank space in front of positive numbers so they align
   * with negative numbers in spite of the minus symbol. There are two
   * versions, one for formatting floats and one for formatting ints. The
   * values for the digits, left, and right parameters should always be
   * positive integers.
   *
   * @cat Data
   * @subcat String Functions
   * @method nfs
   * @param {Number} value The Number to convert
   * @param {Number} leftDigits
   * @param {Number} rightDigits
   * @return {String} The formatted string
   */
  // From: http://processingjs.org/reference/nfs_/
  pub.nfs = function (value, leftDigits, rightDigits) {
      return nfCore(value, " ", "-", leftDigits, rightDigits);
  };
  
  /**
   * Utility function for formatting numbers into strings. Similar to nf()
   * but puts a "+" in front of positive numbers and a "-" in front of
   * negative numbers. There are two versions, one for formatting floats
   * and one for formatting ints. The values for the digits, left, and right
   * parameters should always be positive integers.
   *
   * @cat Data
   * @subcat String Functions
   * @method nfp
   * @param {Number} value The Number to convert
   * @param {Number} leftDigits
   * @param {Number} rightDigits
   * @return {String} The formatted string
   */
  // From: http://processingjs.org/reference/nfp_/
  pub.nfp = function (value, leftDigits, rightDigits) {
      return nfCore(value, "+", "-", leftDigits, rightDigits);
  };
  
  /**
   * Utility function for formatting numbers into strings and placing
   * appropriate commas to mark units of 1000. There are two versions, one
   * for formatting ints and one for formatting an array of ints. The value
   * for the digits parameter should always be a positive integer.
   *
   * @cat Data
   * @subcat String Functions
   * @method nfc
   * @param {Number} value The Number to convert
   * @param {Number} leftDigits
   * @param {Number} rightDigits
   * @return {String} The formatted string
   */
  // From: http://processingjs.org/reference/nfc_/
  pub.nfc = function (value, leftDigits, rightDigits) {
      return nfCore(value, "", "-", leftDigits, rightDigits, ",");
  };
  
  
  /**
   * Removes whitespace characters from the beginning and end of a String.
   * In addition to standard whitespace characters such as space, carriage
   * return, and tab, this function also removes the Unicode "nbsp" character.
   *
   * @cat Data
   * @subcat String Functions
   * @method trim
   * @param {String|Array} str A string or an array of strings to be trimmed
   * @return {String|Array} Returns the input in a trimmed way
   */
  // From: http://processingjs.org/reference/trim_/
  pub.trim = function (str) {
      if (str instanceof Array) {
          var arr = [];
          for (var i = 0; i < str.length; i++) arr.push(str[i].replace(/^\s*/, "").replace(/\s*$/, "").replace(/\r*$/, ""));
          return arr;
      }
      return str.replace(/^\s*/, "").replace(/\s*$/, "").replace(/\r*$/, "");
  };
  
  /**
   * Checks whether an URL string is valid.
   *
   * @cat Data
   * @subcat String Functions
   * @method isURL
   * @param {String} url An url string to be checked
   * @return {Boolean} Returns either true or false
   */
  var isURL = pub.isURL = function (url) {
      var pattern = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
      return pattern.test(url);
  };
  
  /**
   * Checks whether a string ends with a specific character or string.
   *
   * @cat Data
   * @subcat String Functions
   * @method endsWith
   * @param {String} str A string to be checked
   * @param {String} suffix The string to look for
   * @return {Boolean} Returns either true or false
   */
  var endsWith = pub.endsWith = function (str, suffix) {
      return str.indexOf(suffix, str.length - suffix.length) !== -1;
  };
  
  /**
   * Checks whether a string starts with a specific character or string.
   *
   * @cat Data
   * @subcat String Functions
   * @method startsWith
   * @param {String} str A string to be checked
   * @param {String} prefix The string to look for
   * @return {Boolean} Returns either true or false
   */
  var startsWith = pub.startsWith = function (str, prefix) {
      return str.indexOf(prefix) === 0;
  };
  
  
  /**
   * Checks whether a var is an Array, returns true if this is the case
   *
   * @cat Data
   * @subcat Type-Check
   * @method isArray
   * @param  {Object|String|Number|Boolean} obj The object to check
   * @return {Boolean} returns true if this is the case
   */
  var isArray = pub.isArray = function (obj) {
      return Object.prototype.toString.call(obj) === '[object Array]';
  };
  
  /**
   * Checks whether a var is a number, returns true if this is the case
   *
   * @cat Data
   * @subcat Type-Check
   * @method isNumber
   * @param  {Object|String|Number|Boolean}  num The number to check
   * @return {Boolean} returns true if this is the case
   */
  var isNumber = pub.isNumber = function (num) {
      return !isNaN(parseFloat(num)) && isFinite(num);
  };
  
  /**
   * Checks whether a var is a string, returns true if this is the case
   *
   * @cat Data
   * @subcat Type-Check
   * @method isString
   * @param  {Object|String|Number|Boolean} str The string to check
   * @return {Boolean} returns true if this is the case
   */
  var isString = pub.isString = function (str) {
      return Object.prototype.toString.call(str) === '[object String]';
  };
  
  var initDataFile = function (file, mustExist) {
      var result = null;
      if (file instanceof File) {
          result = file;
      } else {
          //var folder = new Folder(projectFolder().absoluteURI + '/data');
          //Adapt function for After Effects
          var folder = new Folder(projectFolder() + '/data');
          if (!folder.exists)
              folder.create(); // creates data folder if not existing, otherwise it just skips
          result = new File(folder.absoluteURI + '/' + file);
      }
      if (mustExist && !result.exists) {
          error('The file "' + result + '" does not exist.');
      }
      return result;
  };
  
  var initExportFile = function (file, mustExist) {
      var result = null;
      if (file instanceof File) {
          result = file;
      } else {
  
          // get rid of some special cases the user might specify
          var pathNormalized = file.split("/");
          for (var i = 0; i < pathNormalized.length; i++) {
              if (pathNormalized[i] === "" || pathNormalized[i] === ".") {
                  pathNormalized.splice(i, 1);
              };
          };
          // var tmpPath = projectFolder().absoluteURI;
          //Adapt function for After Effects
          var tmpPath = projectFolder() + '/data';
          var fileName = pathNormalized[pathNormalized.length - 1];
  
          // contains the path folders? if so create them ...
          if (pathNormalized.length > 1) {
              var folders = pathNormalized.slice(0, -1);
              for (var i = 0; i < folders.length; i++) {
                  tmpPath += "/" + folders[i]
                  var f = new Folder(tmpPath);
                  if (!f.exists) f.create();
              };
          }
  
          // result = new File(projectFolder().absoluteURI + '/' + file);
          result = new File(tmpPath + '/' + fileName);
      }
      if (mustExist && !result.exists) {
          error('The file "' + result + '" does not exist.');
      }
      return result;
  };
  
  /**
   * Get the folder of the active document as a Folder object. Use .absoluteURI to access a string representation of the folder path.
   *
   * @cat Document
   * @subcat Misc
   * @method projectFolder
   * @return {Folder} The folder of the the active document
   */
  var projectFolder = pub.projectFolder = function () {
      var docPath = null;
      try {
          //Adapt function for After Effects
          docPath = app.project.file.path;
      } catch (e) {
          error("The current document must be saved before its project directory can be accessed.");
      }
      return docPath;
  };
  
  /**
   * Reads the contents of a file or loads an URL into a String.
   * If the file is specified by name as String, it must be located in the document's data directory.
   *
   * @cat Data
   * @subcat Input
   * @method loadString
   * @param  {String|File} fileOrString The text file name in the document's data directory or a File instance or an URL
   * @return {String}  String file or URL content.
   */
  pub.loadString = function (fileOrString) {
      if (isURL(fileOrString)) {
          return getURL(fileOrString);
      } else {
          var inputFile = initDataFile(fileOrString, true),
              data = null;
          inputFile.open('r');
          data = inputFile.read();
          inputFile.close();
          return data;
      }
  };
  
  var getURL = function (url) {
      if (isURL(url)) {
          if (Folder.fs === "Macintosh") {
              return pub.shellExecute("curl -m 15 -L '" + url + "'");
          } else {
              error("Loading of strings via an URL is a Mac only feature at the moment. Sorry!")
          }
      } else {
          error("The url " + url + " is not a valid one. Please double check!")
      }
  };
  
  /**
   * Reads the contents of a file or loads an URL and creates a String array of its individual lines.
   * If the file is specified by name as String, it must be located in the document's data directory.
   *
   * @cat Data
   * @subcat Input
   * @method loadStrings
   * @param  {String|File} file The text file name in the document's data directory or a File instance or an URL
   * @return {String[]}  Array of the individual lines in the given File or URL
   */
  pub.loadStrings = function (file) {
      if (isURL(file)) {
          var result = getURL(file);
          return result.match(/[^\r\n]+/g);
      } else {
          var inputFile = initDataFile(file, true),
              result = [];
          inputFile.open('r');
          while (!inputFile.eof) {
              result.push(inputFile.readln());
          }
          inputFile.close();
          return result;
      }
  };
  
  
  // ----------------------------------------
  // Output
  
  /**
   * Prints a message line to the console output in the ExtendScript editor.
   *
   * @cat Output
   * @method println
   * @param {String} msg The message to print
   */
  var println = pub.println = function (msg) {
      $.writeln(msg);
  };
  
  /**
   * Prints a message to the console output in the ExtendScript editor, but unlike b.println() it doesn't return the carriage to a new line at the end.
   *
   * @cat Output
   * @method print
   * @param {String} msg The message to print
   */
  pub.print = function (msg) {
      $.write(msg);
  };
  
  /**
   * Print numerous information about the current environment to the console
   *
   * @cat Output
   * @method printInfo
   */
  pub.printInfo = function () {
  
      pub.println("###");
      pub.println("OS: " + $.os);
      pub.println("ExtendScript Build: " + $.build);
      pub.println("ExtendScript Version:" + $.version);
      pub.println("Engine: " + $.engineName);
      pub.println("memCache: " + $.memCache + " bytes");
      pub.println("###");
  
  };
  
  /**
   * Writes an array of strings to a file, one line per string.
   * If the given file exists it gets overridden.
   *
   * @cat Output
   * @method saveStrings
   * @param  {String|File} file The file name or a File instance
   * @param  {String[]} strings The string array to be written
   */
  pub.saveStrings = function (file, strings) {
      var outputFile = initDataFile(file);
      outputFile.open('w');
      forEach(strings, function (s) {
          outputFile.writeln(s);
      });
      outputFile.close();
  };
  
  /**
   * Writes a string to a file.
   * If the given file exists it gets overridden.
   *
   * @cat Output
   * @method saveString
   * @param  {String|File} file The file name or a File instance
   * @param  {String} string The string to be written
   */
  pub.saveString = function (file, string) {
      var outputFile = initDataFile(file);
      outputFile.open('w');
      outputFile.write(string);
      outputFile.close();
  };