<commit_before>/*
  Copyright (c) 2013, Oleg Grenrus
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:
      * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
      * Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in the
        documentation and/or other materials provided with the distribution.
      * Neither the name of the Oleg Grenrus nor the
        names of its contributors may be used to endorse or promote products
        derived from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL OLEG GRENRUS BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function () {
  "use strict";

  // slightly different from ES5 some, without cast to boolean
  // [x, y, z].some(f):
  // ES5:  !! ( f(x) || f(y) || f(z) || false)
  // this:    ( f(x) || f(y) || f(z) || false)
  function some(array, f) {
    var acc = false;
    for (var i = 0; i < array.length; i++) {
      acc = f(array[i], i, array);
      if (acc) {
        return acc;
      }
    }
    return acc;
  }

  function makeLexer(tokenSpecs) {
    return function (contents) {
      var tokens = [];
      var line = 1;

      function findToken() {
        return some(tokenSpecs, function (tokenSpec) {
          var m = tokenSpec.re.exec(contents);
          if (m) {
            var raw = m[0];
            contents = contents.slice(raw.length);
            return {
              raw: raw,
              matched: tokenSpec.f(m, line),
            };
          }
        });
      }

      while (contents !== "") {
        var matched = findToken();

        if (!matched) {
          var err = new SyntaxError("Unexpected character: " + contents[0]);
          err.line = line;
          throw err;
        }

        // add line to token
        matched.matched.line = line;

        // count lines
        line += matched.raw.replace(/[^\n]/g, "").length;

        tokens.push(matched.matched);
      }

      return tokens;
    };
  }

  function fStringSingle(m) {
    // String in single quotes
    var content = m[1].replace(/([^'\\]|\\['bnrtf\\]|\\u[0-9a-fA-F]{4})/g, function (m) {
      if (m === "\"") {
        return "\\\"";
      } else if (m === "\\'") {
        return "'";
      } else {
        return m;
      }
    });

    return {
      type: "string",
      match: "\"" + content + "\"",
      value: JSON.parse("\"" + content + "\""), // abusing real JSON.parse to unquote string
    };
  }

  function fStringDouble(m) {
    return {
      type: "string",
      match: m[0],
      value: JSON.parse(m[0]),
    };
  }

  function fIdentifier(m) {
    // identifiers are transformed into strings
    return {
      type: "string",
      value: m[0],
      match: "\"" + m[0].replace(/./g, function (c) {
      return c === "\\" ? "\\\\" : c;
    }) + "\"" };
  }

  function fComment(m) {
    // comments are whitespace, leave only linefeeds
    return { type: " ", match: m[0].replace(/./g, function (c) {
      return (/\s/).test(c) ? c : " ";
    }) };
  }

  function fNumber(m) {
    return {
      type : "number",
      match: m[0],
      value: parseFloat(m[0]),
    };
  }

  function fKeyword(m) {
    var value;
    switch (m[1]) {
    case "null": value = null; break;
    case "true": value = true; break;
    case "false": value = false; break;
    }
    return {
      type: "atom",
      match: m[0],
      value: value,
    };
  }

  function tokenSpecs(relaxed) {
    function f(type) {
      return function(m) {
        return { type: type, match: m[0] };
      };
    }

    var ret = [
      { re: /^\s+/, f: f(" ") },
      { re: /^\{/, f: f("{") },
      { re: /^\}/, f: f("}") },
      { re: /^\[/, f: f("[") },
      { re: /^\]/, f: f("]") },
      { re: /^,/, f: f(",") },
      { re: /^:/, f: f(":") },
      { re: /^(true|false|null)/, f: fKeyword },
      { re: /^\-?\d+(\.\d+)?([eE][+-]?\d+)?/, f: fNumber },
      { re: /^"([^"\\]|\\["bnrtf\\]|\\u[0-9a-fA-F]{4})*"/, f: fStringDouble },
    ];

    // additional stuff
    if (relaxed) {
      ret = ret.concat([
        { re: /^'(([^'\\]|\\['bnrtf\\]|\\u[0-9a-fA-F]{4})*)'/, f: fStringSingle },
        { re: /^\/\/.*?\n/, f: fComment },
        { re: /^\/\*[\s\S]*?\*\//, f: fComment },
        { re: /^[a-zA-Z0-9_\-+\.\*\?!\|&%\^\/#\\]+/, f: fIdentifier },
      ]);
    }

    return ret;
  }

  var lexer = makeLexer(tokenSpecs(true));
  var strictLexer = makeLexer(tokenSpecs(false));

  function transformTokens(tokens) {
    return tokens.reduce(function (tokens, token) {
      // not so functional, js list aren't

      // do stuff only if curren token is ] or }
      if (tokens.length !== 0 && (token.type === "]" || token.type === "}")) {
        var i = tokens.length - 1;

        // go backwards as long as there is whitespace, until first comma
        while (true) {
          if (tokens[i].type === " ") {
            i -= 1;
            continue;
          } else if (tokens[i].type === ",") {
            // remove comma
            tokens.splice(i, 1);
          }
          break;
        }
      }

      // push current token in place
      tokens.push(token);

      return tokens;
    }, []);
  }

  function transform(text) {
    // Tokenize contents
    var tokens = lexer(text);

    // remove trailing commas
    tokens = transformTokens(tokens);

    // concat stuff
    return tokens.reduce(function (str, token) {
      return str + token.match;
    }, "");
  }

  function popToken(tokens, state) {
    var token = tokens[state.pos];
    state.pos += 1;

    if (!token) {
      var line = tokens.length !== 0 ? tokens[tokens.length - 1].line : 1;
      return { type: "eof", line: line };
    }

    return token;
  }

  function strToken(token) {
    switch (token.type) {
    case "atom":
    case "string":
    case "number":
      return token.type + " " + token.match;
    case "eof":
      return "end-of-file";
    default:
      return "'" + token.type + "'";
    }
  }

  function skipColon(tokens, state) {
    var colon = popToken(tokens, state);
    if (colon.type !== ":") {
      var message = "Unexpected token: " + strToken(colon) + ", expected ':'";
      if (state.tolerant) {
        state.warnings.push({
          message: message,
          line: colon.line,
        });

        state.pos -= 1;
      } else {
        var err = new SyntaxError(message);
        err.line = colon.line;
        throw err;
      }
    }
  }

  function skipPunctuation(tokens, state, valid) {
    var punctuation = [",", ":", "]", "}"];
    var token = popToken(tokens, state);
    while (true) {
      if (valid && valid.indexOf(token.type) !== -1) {
        return token;
      } else if (token.type === "eof") {
        return token;
      } else if (punctuation.indexOf(token.type) !== -1) {
        var message = "Unexpected token: " + strToken(token) + ", expected '[', '{', number, string or atom";
        if (state.tolerant) {
          state.warnings.push({
            message: message,
            line: token.line,
          });
          token = popToken(tokens, state);
        } else {
          var err = new SyntaxError(message);
          err.line = token.line;
          throw err;
        }
      } else {
        return token;
      }
    }
  }

  function raiseError(state, token, message) {
     if (state.tolerant) {
      state.warnings.push({
        message: message,
        line: token.line,
      });
    } else {
      var err = new SyntaxError(message);
      err.line = token.line;
      throw err;
    }
  }

  function raiseUnexpected(state, token, expected) {
    raiseError(state, token, "Unexpected token: " + strToken(token) + ", expected " + expected);
  }

  function checkDuplicates(state, obj, token) {
    var key = token.value;

    if (state.duplicate && Object.prototype.hasOwnProperty.call(obj, key)) {
      raiseError(state, token, "Duplicate key: " + key);
    }
  }

  function appendPair(state, obj, key, value) {
    value = state.reviver ? state.reviver(key, value) : value;
    if (value !== undefined) {
      obj[key] = value;
    }
  }

  function parsePair(tokens, state, obj) {
    var token = skipPunctuation(tokens, state, [":"]);
    var key, value;

    if (token.type !== "string") {
      raiseUnexpected(state, token, "string");
      switch (token.type) {
      case ":":
        token = {
          type: "string",
          value: "null",
          line:  token.line,
        };

        state.pos -= 1;
        break;

      case "number":
      case "atom":
        token = {
          type: "string",
          value: ""+token.value,
          line: token.line,
        };
        break;

      case "[":
      case "{":
        state.pos -= 1;
        value = parseAny(tokens, state);
        appendPair(state, obj, "null", value);
        return;
      }
    }

    checkDuplicates(state, obj, token);
    key = token.value;
    skipColon(tokens, state);
    value = parseAny(tokens, state);

    appendPair(state, obj, key, value);
  }

  function parseElement(tokens, state, arr) {
    var key = arr.length;
    var value = parseAny(tokens, state);
    arr[key] = state.reviver ? state.reviver("" + key, value) : value;
  }

  function parseObject(tokens, state) {
    return parseMany(tokens, state, {}, {
      skip: [":", "}"],
      elementParser: parsePair,
      elementName: "string",
      endSymbol: "}",
    });
  }

  function parseArray(tokens, state) {
    return parseMany(tokens, state, [], {
      skip: ["]"],
      elementParser: parseElement,
      elementName: "json object",
      endSymbol: "]",
    });
  }

  function parseMany(tokens, state, obj, opts) {
    var token = skipPunctuation(tokens, state, opts.skip);

    if (token.type === "eof") {
      raiseUnexpected(state, token, "'" + opts.endSymbol + "' or " + opts.elementName);

      token = {
        type: opts.endSymbol,
        line: token.line,
      };
    }

    switch (token.type) {
    case opts.endSymbol:
      return obj;

    default:
      state.pos -= 1; // push the token back
      opts.elementParser(tokens, state, obj);
      break;
    }

    // Rest
    while (true) {
      token = popToken(tokens, state);

      if (token.type !== opts.endSymbol && token.type !== ",") {
        raiseUnexpected(state, token, "',' or '" + opts.endSymbol + "'");

        token = {
          type: token.type === "eof" ? opts.endSymbol : ",",
          line: token.line,
        };

        state.pos -= 1;
      }

      switch (token.type) {
      case opts.endSymbol:
        return obj;

      case ",":
        opts.elementParser(tokens, state, obj);
        break;
      }
    }
  }

  function parseAny(tokens, state, end) {
    var token = skipPunctuation(tokens, state);
    var ret;
    var err;
    var message;

    if (token.type === "eof") {
      raiseUnexpected(state, token, "json object");
    }

    switch (token.type) {
    case "{":
      ret = parseObject(tokens, state);
      break;
    case "[":
      ret = parseArray(tokens, state);
      break;
    case "string":
    case "number":
    case "atom":
      ret = token.value;
      break;
    }

    if (end) {
      ret = state.reviver ? state.reviver("", ret) : ret;
    }

    if (end && state.pos < tokens.length) {
      message = "Unexpected token: " + strToken(tokens[state.pos]) + ", expected end-of-input";
      if (state.tolerant) {
        state.warnings.push({
          message: message,
          line: tokens[state.pos].line,
        });
      } else {
        err = new SyntaxError(message);
        err.line = tokens[state.pos].line;
        throw err;
      }
    }

    // Throw error at the end
    if (end && state.tolerant && state.warnings.length !== 0) {
      message = state.warnings.length === 1 ? state.warnings[0].message : state.warnings.length + " parse warnings";
      err = new SyntaxError(message);
      err.line = state.warnings[0].line;
      err.warnings = state.warnings;
      err.obj = ret;
      throw err;
    }

    return ret;
  }

  function parse(text, opts) {
    if (typeof opts === "function" || opts === undefined) {
      return JSON.parse(transform(text), opts);
    } else if (new Object(opts) !== opts) {
      throw new TypeError("opts/reviver should be undefined, a function or an object");
    }

    opts.relaxed = opts.relaxed !== undefined ? opts.relaxed : true;
    opts.warnings = opts.warnings || opts.tolerant || false;
    opts.tolerant = opts.tolerant || false;
    opts.duplicate = opts.duplicate || false;

    if (!opts.warnings && !opts.relaxed) {
      return JSON.parse(text, opts.reviver);
    }

    var tokens = opts.relaxed ? lexer(text) : strictLexer(text);

    if (opts.relaxed) {
      // Strip commas
      tokens = transformTokens(tokens);
    }

    if (opts.warnings) {
      // Strip whitespace
      tokens = tokens.filter(function (token) {
        return token.type !== " ";
      });

      var state = { pos: 0, reviver: opts.reviver, tolerant: opts.tolerant, duplicate: opts.duplicate, warnings: [] };
      return parseAny(tokens, state, true);
    } else {
      var newtext = tokens.reduce(function (str, token) {
        return str + token.match;
      }, "");

      return JSON.parse(newtext, opts.reviver);
    }
  }

  // Export  stuff
  var RJSON = {
    transform: transform,
    parse: parse,
  };

  /* global window, module */
  if (typeof window !== "undefined") {
    window.RJSON = RJSON;
  } else if (typeof module !== "undefined") {
    module.exports = RJSON;
  }
}());
<commit_msg>Simplify parseAny<commit_after>/*
  Copyright (c) 2013, Oleg Grenrus
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:
      * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
      * Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in the
        documentation and/or other materials provided with the distribution.
      * Neither the name of the Oleg Grenrus nor the
        names of its contributors may be used to endorse or promote products
        derived from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL OLEG GRENRUS BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function () {
  "use strict";

  // slightly different from ES5 some, without cast to boolean
  // [x, y, z].some(f):
  // ES5:  !! ( f(x) || f(y) || f(z) || false)
  // this:    ( f(x) || f(y) || f(z) || false)
  function some(array, f) {
    var acc = false;
    for (var i = 0; i < array.length; i++) {
      acc = f(array[i], i, array);
      if (acc) {
        return acc;
      }
    }
    return acc;
  }

  function makeLexer(tokenSpecs) {
    return function (contents) {
      var tokens = [];
      var line = 1;

      function findToken() {
        return some(tokenSpecs, function (tokenSpec) {
          var m = tokenSpec.re.exec(contents);
          if (m) {
            var raw = m[0];
            contents = contents.slice(raw.length);
            return {
              raw: raw,
              matched: tokenSpec.f(m, line),
            };
          }
        });
      }

      while (contents !== "") {
        var matched = findToken();

        if (!matched) {
          var err = new SyntaxError("Unexpected character: " + contents[0]);
          err.line = line;
          throw err;
        }

        // add line to token
        matched.matched.line = line;

        // count lines
        line += matched.raw.replace(/[^\n]/g, "").length;

        tokens.push(matched.matched);
      }

      return tokens;
    };
  }

  function fStringSingle(m) {
    // String in single quotes
    var content = m[1].replace(/([^'\\]|\\['bnrtf\\]|\\u[0-9a-fA-F]{4})/g, function (m) {
      if (m === "\"") {
        return "\\\"";
      } else if (m === "\\'") {
        return "'";
      } else {
        return m;
      }
    });

    return {
      type: "string",
      match: "\"" + content + "\"",
      value: JSON.parse("\"" + content + "\""), // abusing real JSON.parse to unquote string
    };
  }

  function fStringDouble(m) {
    return {
      type: "string",
      match: m[0],
      value: JSON.parse(m[0]),
    };
  }

  function fIdentifier(m) {
    // identifiers are transformed into strings
    return {
      type: "string",
      value: m[0],
      match: "\"" + m[0].replace(/./g, function (c) {
      return c === "\\" ? "\\\\" : c;
    }) + "\"" };
  }

  function fComment(m) {
    // comments are whitespace, leave only linefeeds
    return { type: " ", match: m[0].replace(/./g, function (c) {
      return (/\s/).test(c) ? c : " ";
    }) };
  }

  function fNumber(m) {
    return {
      type : "number",
      match: m[0],
      value: parseFloat(m[0]),
    };
  }

  function fKeyword(m) {
    var value;
    switch (m[1]) {
    case "null": value = null; break;
    case "true": value = true; break;
    case "false": value = false; break;
    }
    return {
      type: "atom",
      match: m[0],
      value: value,
    };
  }

  function tokenSpecs(relaxed) {
    function f(type) {
      return function(m) {
        return { type: type, match: m[0] };
      };
    }

    var ret = [
      { re: /^\s+/, f: f(" ") },
      { re: /^\{/, f: f("{") },
      { re: /^\}/, f: f("}") },
      { re: /^\[/, f: f("[") },
      { re: /^\]/, f: f("]") },
      { re: /^,/, f: f(",") },
      { re: /^:/, f: f(":") },
      { re: /^(true|false|null)/, f: fKeyword },
      { re: /^\-?\d+(\.\d+)?([eE][+-]?\d+)?/, f: fNumber },
      { re: /^"([^"\\]|\\["bnrtf\\]|\\u[0-9a-fA-F]{4})*"/, f: fStringDouble },
    ];

    // additional stuff
    if (relaxed) {
      ret = ret.concat([
        { re: /^'(([^'\\]|\\['bnrtf\\]|\\u[0-9a-fA-F]{4})*)'/, f: fStringSingle },
        { re: /^\/\/.*?\n/, f: fComment },
        { re: /^\/\*[\s\S]*?\*\//, f: fComment },
        { re: /^[a-zA-Z0-9_\-+\.\*\?!\|&%\^\/#\\]+/, f: fIdentifier },
      ]);
    }

    return ret;
  }

  var lexer = makeLexer(tokenSpecs(true));
  var strictLexer = makeLexer(tokenSpecs(false));

  function transformTokens(tokens) {
    return tokens.reduce(function (tokens, token) {
      // not so functional, js list aren't

      // do stuff only if curren token is ] or }
      if (tokens.length !== 0 && (token.type === "]" || token.type === "}")) {
        var i = tokens.length - 1;

        // go backwards as long as there is whitespace, until first comma
        while (true) {
          if (tokens[i].type === " ") {
            i -= 1;
            continue;
          } else if (tokens[i].type === ",") {
            // remove comma
            tokens.splice(i, 1);
          }
          break;
        }
      }

      // push current token in place
      tokens.push(token);

      return tokens;
    }, []);
  }

  function transform(text) {
    // Tokenize contents
    var tokens = lexer(text);

    // remove trailing commas
    tokens = transformTokens(tokens);

    // concat stuff
    return tokens.reduce(function (str, token) {
      return str + token.match;
    }, "");
  }

  function popToken(tokens, state) {
    var token = tokens[state.pos];
    state.pos += 1;

    if (!token) {
      var line = tokens.length !== 0 ? tokens[tokens.length - 1].line : 1;
      return { type: "eof", line: line };
    }

    return token;
  }

  function strToken(token) {
    switch (token.type) {
    case "atom":
    case "string":
    case "number":
      return token.type + " " + token.match;
    case "eof":
      return "end-of-file";
    default:
      return "'" + token.type + "'";
    }
  }

  function skipColon(tokens, state) {
    var colon = popToken(tokens, state);
    if (colon.type !== ":") {
      var message = "Unexpected token: " + strToken(colon) + ", expected ':'";
      if (state.tolerant) {
        state.warnings.push({
          message: message,
          line: colon.line,
        });

        state.pos -= 1;
      } else {
        var err = new SyntaxError(message);
        err.line = colon.line;
        throw err;
      }
    }
  }

  function skipPunctuation(tokens, state, valid) {
    var punctuation = [",", ":", "]", "}"];
    var token = popToken(tokens, state);
    while (true) {
      if (valid && valid.indexOf(token.type) !== -1) {
        return token;
      } else if (token.type === "eof") {
        return token;
      } else if (punctuation.indexOf(token.type) !== -1) {
        var message = "Unexpected token: " + strToken(token) + ", expected '[', '{', number, string or atom";
        if (state.tolerant) {
          state.warnings.push({
            message: message,
            line: token.line,
          });
          token = popToken(tokens, state);
        } else {
          var err = new SyntaxError(message);
          err.line = token.line;
          throw err;
        }
      } else {
        return token;
      }
    }
  }

  function raiseError(state, token, message) {
     if (state.tolerant) {
      state.warnings.push({
        message: message,
        line: token.line,
      });
    } else {
      var err = new SyntaxError(message);
      err.line = token.line;
      throw err;
    }
  }

  function raiseUnexpected(state, token, expected) {
    raiseError(state, token, "Unexpected token: " + strToken(token) + ", expected " + expected);
  }

  function checkDuplicates(state, obj, token) {
    var key = token.value;

    if (state.duplicate && Object.prototype.hasOwnProperty.call(obj, key)) {
      raiseError(state, token, "Duplicate key: " + key);
    }
  }

  function appendPair(state, obj, key, value) {
    value = state.reviver ? state.reviver(key, value) : value;
    if (value !== undefined) {
      obj[key] = value;
    }
  }

  function parsePair(tokens, state, obj) {
    var token = skipPunctuation(tokens, state, [":"]);
    var key, value;

    if (token.type !== "string") {
      raiseUnexpected(state, token, "string");
      switch (token.type) {
      case ":":
        token = {
          type: "string",
          value: "null",
          line:  token.line,
        };

        state.pos -= 1;
        break;

      case "number":
      case "atom":
        token = {
          type: "string",
          value: ""+token.value,
          line: token.line,
        };
        break;

      case "[":
      case "{":
        state.pos -= 1;
        value = parseAny(tokens, state);
        appendPair(state, obj, "null", value);
        return;
      }
    }

    checkDuplicates(state, obj, token);
    key = token.value;
    skipColon(tokens, state);
    value = parseAny(tokens, state);

    appendPair(state, obj, key, value);
  }

  function parseElement(tokens, state, arr) {
    var key = arr.length;
    var value = parseAny(tokens, state);
    arr[key] = state.reviver ? state.reviver("" + key, value) : value;
  }

  function parseObject(tokens, state) {
    return parseMany(tokens, state, {}, {
      skip: [":", "}"],
      elementParser: parsePair,
      elementName: "string",
      endSymbol: "}",
    });
  }

  function parseArray(tokens, state) {
    return parseMany(tokens, state, [], {
      skip: ["]"],
      elementParser: parseElement,
      elementName: "json object",
      endSymbol: "]",
    });
  }

  function parseMany(tokens, state, obj, opts) {
    var token = skipPunctuation(tokens, state, opts.skip);

    if (token.type === "eof") {
      raiseUnexpected(state, token, "'" + opts.endSymbol + "' or " + opts.elementName);

      token = {
        type: opts.endSymbol,
        line: token.line,
      };
    }

    switch (token.type) {
    case opts.endSymbol:
      return obj;

    default:
      state.pos -= 1; // push the token back
      opts.elementParser(tokens, state, obj);
      break;
    }

    // Rest
    while (true) {
      token = popToken(tokens, state);

      if (token.type !== opts.endSymbol && token.type !== ",") {
        raiseUnexpected(state, token, "',' or '" + opts.endSymbol + "'");

        token = {
          type: token.type === "eof" ? opts.endSymbol : ",",
          line: token.line,
        };

        state.pos -= 1;
      }

      switch (token.type) {
      case opts.endSymbol:
        return obj;

      case ",":
        opts.elementParser(tokens, state, obj);
        break;
      }
    }
  }

  function endChecks(tokens, state, ret) {
    if (state.pos < tokens.length) {
      raiseError(state, tokens[state.pos],
        "Unexpected token: " + strToken(tokens[state.pos]) + ", expected end-of-input");
    }

    // Throw error at the end
    if (state.tolerant && state.warnings.length !== 0) {
      var message = state.warnings.length === 1 ? state.warnings[0].message : state.warnings.length + " parse warnings";
      var err = new SyntaxError(message);
      err.line = state.warnings[0].line;
      err.warnings = state.warnings;
      err.obj = ret;
      throw err;
    }
  }

  function parseAny(tokens, state, end) {
    var token = skipPunctuation(tokens, state);
    var ret;

    if (token.type === "eof") {
      raiseUnexpected(state, token, "json object");
    }

    switch (token.type) {
    case "{":
      ret = parseObject(tokens, state);
      break;
    case "[":
      ret = parseArray(tokens, state);
      break;
    case "string":
    case "number":
    case "atom":
      ret = token.value;
      break;
    }

    if (end) {
      ret = state.reviver ? state.reviver("", ret) : ret;
      endChecks(tokens, state, ret);
    }

    return ret;
  }

  function parse(text, opts) {
    if (typeof opts === "function" || opts === undefined) {
      return JSON.parse(transform(text), opts);
    } else if (new Object(opts) !== opts) {
      throw new TypeError("opts/reviver should be undefined, a function or an object");
    }

    opts.relaxed = opts.relaxed !== undefined ? opts.relaxed : true;
    opts.warnings = opts.warnings || opts.tolerant || false;
    opts.tolerant = opts.tolerant || false;
    opts.duplicate = opts.duplicate || false;

    if (!opts.warnings && !opts.relaxed) {
      return JSON.parse(text, opts.reviver);
    }

    var tokens = opts.relaxed ? lexer(text) : strictLexer(text);

    if (opts.relaxed) {
      // Strip commas
      tokens = transformTokens(tokens);
    }

    if (opts.warnings) {
      // Strip whitespace
      tokens = tokens.filter(function (token) {
        return token.type !== " ";
      });

      var state = { pos: 0, reviver: opts.reviver, tolerant: opts.tolerant, duplicate: opts.duplicate, warnings: [] };
      return parseAny(tokens, state, true);
    } else {
      var newtext = tokens.reduce(function (str, token) {
        return str + token.match;
      }, "");

      return JSON.parse(newtext, opts.reviver);
    }
  }

  // Export  stuff
  var RJSON = {
    transform: transform,
    parse: parse,
  };

  /* global window, module */
  if (typeof window !== "undefined") {
    window.RJSON = RJSON;
  } else if (typeof module !== "undefined") {
    module.exports = RJSON;
  }
}());
<|endoftext|>


<commit_before>var auth = require('./myNetatmoAuth.js');
var netatmo = require('netatmo');
var http = require('http');
var dispatch = require('dispatch');
var schedule = require('node-schedule');
var apistatus = require('./meteoDataAPIStatus.js');
var lametricNetatmo = require('./lametricNetatmo.js');
var devices = require('./devices.js');
var util = require('./util.js');

const PORT = 8000;
const HEADERS = util.HEADERS;
var api = new netatmo(auth());
var result = {};

function readFromNetatmoAPI() {
  api.getMeasure(devices.optionsMainStation, function (err, measure) {
    /**
    measure object looks like this:
    { beg_time: 1452028500, value: [ [ 21.7, 1532, 61 ] ] }
    **/
    result.temperatureMain = measure[0].value[0][0];
    result.co2Main = measure[0].value[0][1];
    result.humidityMain = measure[0].value[0][2];
  });
  api.getMeasure(devices.optionsModuleRoom, function (err, measure) {
    /**
    measure object looks like this:
    { beg_time: 1452028500, value: [ [ 21.7, 1532, 61 ] ] }
    **/
    result.temperatureRoom = measure[0].value[0][0];
    result.co2Room = measure[0].value[0][1];
    result.humidityRoom = measure[0].value[0][2];
  });
  api.getMeasure(devices.optionsModuleOutside, function (err, measure) {
    /**
    measure object looks like this:
    { beg_time: 1452028500, value: [ [ 21.7, 1532, 61 ] ] }
    **/
    result.temperatureOutside = measure[0].value[0][0];
    result.humidityOutside = measure[0].value[0][1];
  });
}

var j = schedule.scheduleJob('60 * * * * *', function(){
  readFromNetatmoAPI();
});

//send netatmo data to lametric
var j = schedule.scheduleJob('30 * * * * *', function(){

    var data = lametricNetatmo.createLametricFormat(result);
	  console.log("netatmo for lametric: "+data);
    lametricNetatmo.optionsLametric.headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
    const req = http.request(lametricNetatmo.optionsLametric, (res) => {
      res.setEncoding('utf8');
      res.on('data', (chunk) => {
        console.log(`response from netatmo to lametric req.: ${chunk}`);
      });
      res.on('end', () => {
        console.log('No more data in response.');
      });
    });

    req.on('error', (e) => {
      console.log(`problem with request: ${e.message}`);
    });

    // write data to request body
    req.write(data);
    req.end();
});

//send sma station opendata  to lametric
var k = schedule.scheduleJob('40 * * * * *', function(){
 apistatus.meteoDataForLametric(lametricNetatmo.optionsLametric);
});

//todo get data from raspi, and display in loxone webpage, ev. auch was mit counter und time??? machen wie watchdog oder so
var server = http.createServer(
  dispatch({
    '/netatmo': function(request, response){
      response.writeHead(200, HEADERS);
      response.end(JSON.stringify(result));
    },
    '/test': function(request, response){
      response.writeHead(200, HEADERS);
      response.end("{testNull: null,testNullString: 'null',overflow: -9999}");
    },
    '/apistatus': function(request, response){
      response.writeHead(200, HEADERS);
      apistatus.checkApi(response);
    }

  }));

server.listen(PORT, function() {
  console.log("Server listening on: http://localhost:%s", PORT);
});

//initialize data
readFromNetatmoAPI();

<commit_msg>different schedule time<commit_after>var auth = require('./myNetatmoAuth.js');
var netatmo = require('netatmo');
var http = require('http');
var dispatch = require('dispatch');
var schedule = require('node-schedule');
var apistatus = require('./meteoDataAPIStatus.js');
var lametricNetatmo = require('./lametricNetatmo.js');
var devices = require('./devices.js');
var util = require('./util.js');

const PORT = 8000;
const HEADERS = util.HEADERS;
var api = new netatmo(auth());
//result object from netatmo devices and meteo api in one object
var result = {};

function readFromNetatmoAPI() {
  api.getMeasure(devices.optionsMainStation, function (err, measure) {
    /**
    measure object looks like this:
    { beg_time: 1452028500, value: [ [ 21.7, 1532, 61 ] ] }
    **/
    result.temperatureMain = measure[0].value[0][0];
    result.co2Main = measure[0].value[0][1];
    result.humidityMain = measure[0].value[0][2];
  });
  api.getMeasure(devices.optionsModuleRoom, function (err, measure) {
    /**
    measure object looks like this:
    { beg_time: 1452028500, value: [ [ 21.7, 1532, 61 ] ] }
    **/
    result.temperatureRoom = measure[0].value[0][0];
    result.co2Room = measure[0].value[0][1];
    result.humidityRoom = measure[0].value[0][2];
  });
  api.getMeasure(devices.optionsModuleOutside, function (err, measure) {
    /**
    measure object looks like this:
    { beg_time: 1452028500, value: [ [ 21.7, 1532, 61 ] ] }
    **/
    result.temperatureOutside = measure[0].value[0][0];
    result.humidityOutside = measure[0].value[0][1];
  });
}

var j = schedule.scheduleJob('120 * * * * *', function(){
  readFromNetatmoAPI();
});

//send netatmo data to lametric
var j = schedule.scheduleJob('30 * * * * *', function(){

    var data = lametricNetatmo.createLametricFormat(result);
	  console.log("netatmo for lametric: "+data);
    lametricNetatmo.optionsLametric.headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
    const req = http.request(lametricNetatmo.optionsLametric, (res) => {
      res.setEncoding('utf8');
      res.on('data', (chunk) => {
        console.log(`response from netatmo to lametric req.: ${chunk}`);
      });
      res.on('end', () => {
        console.log('No more data in response.');
      });
    });

    req.on('error', (e) => {
      console.log(`problem with request: ${e.message}`);
    });

    // write data to request body
    req.write(data);
    req.end();
});

//send sma station opendata  to lametric
var k = schedule.scheduleJob('40 * * * * *', function(){
 apistatus.meteoDataForLametric(lametricNetatmo.optionsLametric);
});

//todo get data from raspi, and display in loxone webpage, ev. auch was mit counter und time??? machen wie watchdog oder so
var server = http.createServer(
  dispatch({
    '/netatmo': function(request, response){
      response.writeHead(200, HEADERS);
      response.end(JSON.stringify(result));
    },
    '/test': function(request, response){
      response.writeHead(200, HEADERS);
      response.end("{testNull: null,testNullString: 'null',overflow: -9999}");
    },
    '/apistatus': function(request, response){
      response.writeHead(200, HEADERS);
      apistatus.checkApi(response);
    }

  }));

server.listen(PORT, function() {
  console.log("Server listening on: http://localhost:%s", PORT);
});

//initialize data
readFromNetatmoAPI();

<|endoftext|>


<commit_before>module.exports = {

//---------------------------------------------------------------------
// Action Name
//
// This is the name of the action displayed in the editor.
//---------------------------------------------------------------------

name: "Welcome",

//---------------------------------------------------------------------
// Action Section
//
// This is the section the action will fall into.
//---------------------------------------------------------------------

section: "#Mod Information",

//---------------------------------------------------------------------
// Action Subtitle
//
// This function generates the subtitle displayed next to the name.
//---------------------------------------------------------------------

subtitle: function(data) {
return `Put this into a Bot Initalization event for music!`;
},

//---------------------------------------------------------------------
// DBM Mods Manager Variables (Optional but nice to have!)
//
// These are variables that DBM Mods Manager uses to show information
// about the mods for people to see in the list.
//---------------------------------------------------------------------

// Who made the mod (If not set, defaults to "DBM Mods")
author: "DBM Mods",

// The version of the mod (Defaults to 1.0.0)
version: "1.9.4 ~ beta",

// A short description to show on the mod line for this mod.
short_description: "Information about the Mod Collection & Overwrites Bot.js",

// If it depends on any other mods by name, ex: WrexMODS if the mod uses something from WrexMods

//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Action Storage Function
//
// Stores the relevant variable info for the editor.
//---------------------------------------------------------------------

variableStorage: function(data, varType) {},

//---------------------------------------------------------------------
// Action Fields
//
// These are the fields for the action. These fields are customized
// by creating elements with corresponding IDs in the HTML. These
// are also the names of the fields stored in the action's JSON data.
//---------------------------------------------------------------------

fields: ["mods"],

//---------------------------------------------------------------------
// Command HTML
//
// This function returns a string containing the HTML used for
// editting actions.
//
// The "isEvent" parameter will be true if this action is being used
// for an event. Due to their nature, events lack certain information,
// so edit the HTML to reflect this.
//
// The "data" parameter stores constants for select elements to use.
// Each is an array: index 0 for commands, index 1 for events.
// The names are: sendTargets, members, roles, channels,
//                messages, servers, variables
//---------------------------------------------------------------------

html: function(isEvent, data) {
return `

Welcome!

Thank you for using the DBM Mod Collection!

If you want to tell us something, join the Discord Guild below.
And if something doesn't work feel free to create an issue on GitHub
or open #support and describe your problem.

Discord:

Join the Discord Guild to stay updated and be able to suggest things.

https://dbm-network.org/

Your version:

${this.version}

Our Donators:

        <embedleftline></embedleftline>

                Our Supporters

		Sam

		Zaserr

		papa goobs

		Danno3817

		GeT_DuCkT

		Nerd

		squiffy

		Noah

		Adam_V

		DoimptSopy

		Lasse

		Not Alien

		𝕯𝖔𝖒

		Leondre Devries [BAM]

		_iTrqPss

		NetLuis

		Almeida

		Orochimaru

		Max 🌟

		GAMER

		S h i r o ツ

		Quinten

		Rafied

		lucasboss45

		Dominus_Marceau

GitHub:

Visit us on GitHub! The whole mod collection is on GitHub
and everyone is invited to join us developing new mods!

Copy and paste the link to view the site in your browser.

https://github.com/Discord-Bot-Maker-Mods/DBM-Mods

Current List of Mods

Name
Section
Author(s)

`
},

//---------------------------------------------------------------------
// Action Editor Init Code
//
// When the HTML is first applied to the action editor, this code
// is also run. This helps add modifications or setup reactionary
// functions for the DOM elements.
//---------------------------------------------------------------------

init: function() {
const {glob, document} = this;

var path = require("path")

try {

	var mods = document.getElementById("mods");

	require("fs").readdirSync(__dirname).forEach(function(file) {
		if(file.match(/MOD.js/i)) {
			var action = require(path.join(__dirname, file));
			if(action.name && action.action !== null) {

				const tr = document.createElement('tr')
				tr.setAttribute('class', 'table-dark')

				const name = document.createElement('td')
				const headerText = document.createElement("b")
				headerText.innerHTML = action.name
				name.appendChild(headerText)

				name.setAttribute('scope', 'row')
				tr.appendChild(name)

				const section = document.createElement('td')
				section.appendChild(document.createTextNode(action.section))
				tr.appendChild(section)

				const author = document.createElement('td')
				author.appendChild(document.createTextNode(action.author ? action.author : "DBM"))
				tr.appendChild(author)
				mods.appendChild(tr);
			}
		}
	});
} catch (error) {
	// write any init errors to errors.txt in dbm's main directory
	require("fs").appendFile("errors.txt", error.stack ? error.stack : error + "\r\n");
}

var wrexlinks = document.getElementsByClassName("wrexlink")
	for(var x = 0; x < wrexlinks.length; x++) {
	    var wrexlink = wrexlinks[x];
	    var url = wrexlink.getAttribute('data-url');
	    if(url){
		    wrexlink.addEventListener("click", function(e){
		        e.stopImmediatePropagation();
		        console.log("Launching URL: [" + url + "] in your default browser.");
		        require('child_process').execSync('start ' + url);
		    });
	    }
	}

var wrexlinks2 = document.getElementsByClassName("wrexlink2")
	for(var x2 = 0; x2 < wrexlinks2.length; x2++) {
	    var wrexlink2 = wrexlinks2[x2];
	    var url2 = wrexlink2.getAttribute('data-url2');
	    if(url2){
		    wrexlink2.setAttribute("title", url2);
		    wrexlink2.addEventListener("click", function(e2){
		        e2.stopImmediatePropagation();
		        console.log("Launching URL: [" + url2 + "] in your default browser.");
		        require('child_process').execSync('start ' + url2);
		    });
	    }
	}

var wrexlinks3 = document.getElementsByClassName("wrexlink3")
	for(var x3 = 0; x3 < wrexlinks3.length; x3++) {
	    var wrexlink3 = wrexlinks3[x3];
	    var url3 = wrexlink3.getAttribute('data-url3');
	    if(url3){
		    wrexlink3.setAttribute("title", url3);
		    wrexlink3.addEventListener("click", function(e3){
		        e3.stopImmediatePropagation();
		        console.log("Launching URL: [" + url3 + "] in your default browser.");
		        require('child_process').execSync('start ' + url3);
		    });
	    }
	}
},

//---------------------------------------------------------------------
// Action Bot Function
//
// This is the function for the action within the Bot's Action class.
// Keep in mind event calls won't have access to the "msg" parameter,
// so be sure to provide checks for variable existance.
//---------------------------------------------------------------------

action: function(cache) {

console.log('Music function successfully overwritten.');

},

//---------------------------------------------------------------------
// Action Bot Mod
//
// Upon initialization of the bot, this code is run. Using the bot's
// DBM namespace, one can add/modify existing functions if necessary.
// In order to reduce conflictions between mods, be sure to alias
// functions you wish to overwrite.
//---------------------------------------------------------------------

mod: function(DBM) {
DBM.Audio.playingnow = [];

var oldFunc = DBM.Audio.playItem;
DBM.Audio.playItem = function(item, id) {
	oldFunc.apply(this, arguments);
	if(item[0] === "yt") {
		this.playingnow[id] = item;
	}
};
}

}; // End of module
<commit_msg>ignore this<commit_after>module.exports = {

//---------------------------------------------------------------------
// Action Name
//
// This is the name of the action displayed in the editor.
//---------------------------------------------------------------------

name: "Welcome",

//---------------------------------------------------------------------
// Action Section
//
// This is the section the action will fall into.
//---------------------------------------------------------------------

section: "#Mod Information",

//---------------------------------------------------------------------
// Action Subtitle
//
// This function generates the subtitle displayed next to the name.
//---------------------------------------------------------------------

subtitle: function(data) {
return `Put this into a Bot Initalization event for music!`;
},

//---------------------------------------------------------------------
// DBM Mods Manager Variables (Optional but nice to have!)
//
// These are variables that DBM Mods Manager uses to show information
// about the mods for people to see in the list.
//---------------------------------------------------------------------

// Who made the mod (If not set, defaults to "DBM Mods")
author: "DBM Mods",

// The version of the mod (Defaults to 1.0.0)
version: "1.9.4 ~ beta",

// A short description to show on the mod line for this mod.
short_description: "Information about the Mod Collection & Overwrites Bot.js",

// If it depends on any other mods by name, ex: WrexMODS if the mod uses something from WrexMods

//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Action Storage Function
//
// Stores the relevant variable info for the editor.
//---------------------------------------------------------------------

variableStorage: function(data, varType) {},

//---------------------------------------------------------------------
// Action Fields
//
// These are the fields for the action. These fields are customized
// by creating elements with corresponding IDs in the HTML. These
// are also the names of the fields stored in the action's JSON data.
//---------------------------------------------------------------------

fields: ["mods"],

//---------------------------------------------------------------------
// Command HTML
//
// This function returns a string containing the HTML used for
// editting actions.
//
// The "isEvent" parameter will be true if this action is being used
// for an event. Due to their nature, events lack certain information,
// so edit the HTML to reflect this.
//
// The "data" parameter stores constants for select elements to use.
// Each is an array: index 0 for commands, index 1 for events.
// The names are: sendTargets, members, roles, channels,
//                messages, servers, variables
//---------------------------------------------------------------------

html: function(isEvent, data) {
return `

Welcome!

Thank you for using the DBM Mod Collection!

If you want to tell us something, join the Discord Guild below.
And if something doesn't work feel free to create an issue on GitHub
or open #support and describe your problem.

Discord:

Join the Discord Guild to stay updated and be able to suggest things.

https://dbm-network.org/

Your version:

${this.version}

Our Donators:

        <embedleftline></embedleftline>

                Our Supporters

		Sam

		Zaserr

		papa goobs

		Danno3817

		GeT_DuCkT

		Nerd

		squiffy

		Noah

		Adam_V

		DoimptSopy

		Lasse

		Not Alien

		𝕯𝖔𝖒

		Leondre Devries [BAM]

		_iTrqPss

		NetLuis

		Almeida

		Orochimaru

		Max 🌟

		GAMER

		S h i r o ツ

		Quinten

		Rafied

		lucasboss45

		Dominus_Marceau

GitHub:

Visit us on GitHub! The whole mod collection is on GitHub
and everyone is invited to join us developing new mods!

Copy and paste the link to view the site in your browser.

https://github.com/Discord-Bot-Maker-Mods/DBM-Mods

Current List of Mods

Name
Section
Author(s)

`
},

//---------------------------------------------------------------------
// Action Editor Init Code
//
// When the HTML is first applied to the action editor, this code
// is also run. This helps add modifications or setup reactionary
// functions for the DOM elements.
//---------------------------------------------------------------------

init: function() {
const {glob, document} = this;

var path = require("path")

try {

	var mods = document.getElementById("mods");

	require("fs").readdirSync(__dirname).forEach(function(file) {
		if(file.match(/MOD.js/i)) {
			var action = require(path.join(__dirname, file));
			if(action.name && action.action !== null) {

				const tr = document.createElement('tr')
				tr.setAttribute('class', 'table-dark')

				const name = document.createElement('td')
				const headerText = document.createElement("b")
				headerText.innerHTML = action.name
				name.appendChild(headerText)

				name.setAttribute('scope', 'row')
				tr.appendChild(name)

				const section = document.createElement('td')
				section.appendChild(document.createTextNode(action.section))
				tr.appendChild(section)

				const author = document.createElement('td')
				author.appendChild(document.createTextNode(action.author ? action.author : "DBM"))
				tr.appendChild(author)
				mods.appendChild(tr);
			}
		}
	});
} catch (error) {
	// write any init errors to errors.txt in dbm's main directory
	require("fs").appendFile("errors.txt", error.stack ? error.stack : error + "\r\n");
}

var wrexlinks = document.getElementsByClassName("wrexlink")
	for(var x = 0; x < wrexlinks.length; x++) {
	    var wrexlink = wrexlinks[x];
	    var url = wrexlink.getAttribute('data-url');
	    if(url){
		    wrexlink.addEventListener("click", function(e){
		        e.stopImmediatePropagation();
		        console.log("Launching URL: [" + url + "] in your default browser.");
		        require('child_process').execSync('start ' + url);
		    });
	    }
	}

var wrexlinks2 = document.getElementsByClassName("wrexlink2")
	for(var x2 = 0; x2 < wrexlinks2.length; x2++) {
	    var wrexlink2 = wrexlinks2[x2];
	    var url2 = wrexlink2.getAttribute('data-url2');
	    if(url2){
		    wrexlink2.setAttribute("title", url2);
		    wrexlink2.addEventListener("click", function(e2){
		        e2.stopImmediatePropagation();
		        console.log("Launching URL: [" + url2 + "] in your default browser.");
		        require('child_process').execSync('start ' + url2);
		    });
	    }
	}

var wrexlinks3 = document.getElementsByClassName("wrexlink3")
	for(var x3 = 0; x3 < wrexlinks3.length; x3++) {
	    var wrexlink3 = wrexlinks3[x3];
	    var url3 = wrexlink3.getAttribute('data-url3');
	    if(url3){
		    wrexlink3.setAttribute("title", url3);
		    wrexlink3.addEventListener("click", function(e3){
		        e3.stopImmediatePropagation();
		        console.log("Launching URL: [" + url3 + "] in your default browser.");
		        require('child_process').execSync('start ' + url3);
		    });
	    }
	}
},

//---------------------------------------------------------------------
// Action Bot Function
//
// This is the function for the action within the Bot's Action class.
// Keep in mind event calls won't have access to the "msg" parameter,
// so be sure to provide checks for variable existance.
//---------------------------------------------------------------------

action: function(cache) {

console.log('Music function successfully overwritten.');

},

//---------------------------------------------------------------------
// Action Bot Mod
//
// Upon initialization of the bot, this code is run. Using the bot's
// DBM namespace, one can add/modify existing functions if necessary.
// In order to reduce conflictions between mods, be sure to alias
// functions you wish to overwrite.
//---------------------------------------------------------------------

mod: function(DBM) {
DBM.Audio.playingnow = [];

var oldFunc = DBM.Audio.playItem;
DBM.Audio.playItem = function(item, id) {
	oldFunc.apply(this, arguments);
	if(item[0] === "yt") {
		this.playingnow[id] = item;
	}
};
}

}; // End of module
<|endoftext|>


<commit_before>/*
    ***** BEGIN LICENSE BLOCK *****

    Copyright © 2009 Center for History and New Media
                     George Mason University, Fairfax, Virginia, USA
                     http://zotero.org

    This file is part of Zotero.

    Zotero is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Zotero is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Zotero.  If not, see <http://www.gnu.org/licenses/>.

    ***** END LICENSE BLOCK *****
*/

Zotero.Report = new function() {
	this.generateHTMLDetails = generateHTMLDetails;
	this.generateHTMLList = generateHTMLList;

	// Sites that don't need the query string
	// (full URL is kept for link but stripped for display)
	var _noQueryStringSites = [
		/^http:\/\/([^\.]*\.)?nytimes\.com/
	];

	var escapeXML = function (str) {
		str = str.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, '\u2B1A');
		return Zotero.Utilities.htmlSpecialChars(str);
	}

	function generateHTMLDetails(items, combineChildItems) {
		var content = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ';
        content += '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n';
		content += '\n';
		content += '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n';
		content += '<title>' + Zotero.getString('report.title.default') + '</title>\n';
		content += '<link rel="stylesheet" type="text/css" href="zotero://report/detail.css"/>\n';
		content += '<link rel="stylesheet" type="text/css" media="screen,projection" href="zotero://report/detail_screen.css"/>\n';
		content += '<link rel="stylesheet" type="text/css" media="print" href="zotero://report/detail_print.css"/>\n';
		content += '\n\n\n';

		content += '
\n';
		for each(var arr in items) {
			content += '\n
\n';

			if (arr.title) {
				// Top-level item matched search, so display title
				if (arr.reportSearchMatch) {
					content += '
' + escapeXML(arr.title) + '
\n';
				}
				// Non-matching parent, so display "Parent Item: [Title]"
				else {
					content += '
' + escapeXML(Zotero.getString('report.parentItem'))
						+ ' ' + escapeXML(arr.title) + '
';
				}
			}

			// If parent matches search, display parent item metadata table and tags
			if (arr.reportSearchMatch) {
				content += _generateMetadataTable(arr);

				content += _generateTagsList(arr);

				// Independent note
				if (arr['note']) {
					content += '\n';

					// If not valid XML, display notes with entities encoded
					var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
							.createInstance(Components.interfaces.nsIDOMParser);
					var doc = parser.parseFromString(arr.note, "application/xml");
					if (doc.documentElement.tagName == 'parsererror') {
						content += '
' + escapeXML(arr.note) + '
\n';
					}
					// Otherwise render markup normally
					else {
						content += arr.note + '\n';
					}
				}
			}

			// Children
			if (arr.reportChildren) {
				// Child notes
				if (arr.reportChildren.notes.length) {
					// Only display "Notes:" header if parent matches search
					if (arr.reportSearchMatch) {
						content += '
' + escapeXML(Zotero.getString('report.notes')) + '
\n';
					}
					content += '
\n';
					for each(var note in arr.reportChildren.notes) {
						content += '
\n';

						// If not valid XML, display notes with entities encoded
						var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
								.createInstance(Components.interfaces.nsIDOMParser);
						var doc = parser.parseFromString(note.note, "application/xml");
						if (doc.documentElement.tagName == 'parsererror') {
							content += '
' + escapeXML(note.note) + '
\n';
						}
						// Otherwise render markup normally
						else {
							content += note.note + '\n';
						}

						// Child note tags
						content += _generateTagsList(note);

						content += '
\n';
					}
					content += '
\n';
				}

				// Chid attachments
				content += _generateAttachmentsList(arr.reportChildren);
			}

			// Related
			if (arr.reportSearchMatch && arr.related && arr.related.length) {
				content += '
' + escapeXML(Zotero.getString('itemFields.related')) + '
\n';
				content += '
\n';
				var relateds = Zotero.Items.get(arr.related);
				for each(var related in relateds) {
					content += '
';
					content += escapeXML(related.getDisplayTitle());
					content += '
\n';
				}
				content += '
\n';
			}

			content += '
\n\n';
		}
		content += '
\n';
		content += '\n';

		return content;
	}

	function generateHTMLList(items) {

	}

	function _generateMetadataTable(arr) {
		var table = false;
		var content = '
\n';

		// Item type
		content += '
\n';
		content += ''
			+ escapeXML(Zotero.getString('itemFields.itemType'))
			+ '	\n';
		content += '' + escapeXML(Zotero.ItemTypes.getLocalizedString(arr.itemType)) + '	\n';
		content += '
\n';

		// Creators
		if (arr['creators']) {
			table = true;
			var displayText;

			for each(var creator in arr['creators']) {
				// Two fields
				if (creator['fieldMode']==0) {
					displayText = creator['firstName'] + ' ' + creator['lastName'];
				}
				// Single field
				else if (creator['fieldMode']==1) {
					displayText = creator['lastName'];
				}
				else {
					// TODO
				}

				content += '
\n';
				content += ''
					+ escapeXML(Zotero.getString('creatorTypes.' + creator.creatorType))
					+ '	\n';
				content += '' + escapeXML(displayText) + '	\n';
				content += '
\n';
			}
		}

		// Move dateAdded and dateModified to the end of the array
		var da = arr['dateAdded'];
		var dm = arr['dateModified'];
		delete arr['dateAdded'];
		delete arr['dateModified'];
		arr['dateAdded'] = da;
		arr['dateModified'] = dm;

		for (var i in arr) {
			// Skip certain fields
			switch (i) {
				case 'reportSearchMatch':
				case 'reportChildren':

				case 'libraryID':
				case 'key':
				case 'itemType':
				case 'itemID':
				case 'sourceItemID':
				case 'title':
				case 'firstCreator':
				case 'creators':
				case 'tags':
				case 'related':
				case 'notes':
				case 'note':
				case 'attachments':
					continue;
			}

			try {
				var localizedFieldName = Zotero.ItemFields.getLocalizedString(arr.itemType, i);
			}
			// Skip fields we don't have a localized string for
			catch (e) {
				Zotero.debug('Localized string not available for ' + 'itemFields.' + i, 2);
				continue;
			}

			arr[i] = Zotero.Utilities.trim(arr[i] + '');

			// Skip empty fields
			if (!arr[i]) {
				continue;
			}

			table = true;
			var fieldText;

			if (i == 'url' && arr[i].match(/^https?:\/\//)) {
				fieldText = arr[i];

				// Shorten long URLs manually until Firefox wraps at ?
				// (like Safari) or supports the CSS3 word-wrap property
				var firstSpace = fieldText.indexOf(' ');
				if ((firstSpace == -1 && arr[i].length > 29) || firstSpace > 29) {
					var stripped = false;

					// Strip query string for sites we know don't need it
					for each(var re in _noQueryStringSites) {
						if (re.test(arr[i])){
							var pos = arr[i].indexOf('?');
							if (pos != -1) {
								fieldText = arr[i].substr(0, pos);
								stripped = true;
							}
							break;
						}
					}

					if (!stripped) {
						// Add a line-break after the ? of long URLs
						fieldText = arr[i].replace('?', "?<ZOTEROBREAK/>");

						// Strip query string variables from the end while the
						// query string is longer than the main part
						var pos = fieldText.indexOf('?');
						if (pos != -1) {
							while (pos < (fieldText.length / 2)) {
								var lastAmp = fieldText.lastIndexOf('&');
								if (lastAmp == -1) {
									break;
								}
								fieldText = fieldText.substr(0, lastAmp);
								var shortened = true;
							}
							// Append '&...' to the end
							if (shortened) {
								fieldText += "&<ZOTEROHELLIP/>";
							}
						}
					}
				}

				fieldText = ''
					+ escapeXML(fieldText) + '';
			}
			// Remove SQL date from multipart dates
			// (e.g. '2006-00-00 Summer 2006' becomes 'Summer 2006')
			else if (i=='date') {
				fieldText = escapeXML(Zotero.Date.multipartToStr(arr[i]));
			}
			// Convert dates to local format
			else if (i=='accessDate' || i=='dateAdded' || i=='dateModified') {
				var date = Zotero.Date.sqlToDate(arr[i], true)
				fieldText = escapeXML(date.toLocaleString());
			}
			else {
				fieldText = escapeXML(arr[i]);
			}

			content += '
\n' + escapeXML(localizedFieldName)
				+ '	\n' + fieldText + '	\n
\n';
		}

		content += '
';

		return table ? content : '';
	}

	function _generateTagsList(arr) {
		var content = '';
		if (arr['tags'] && arr['tags'].length) {
			var str = Zotero.getString('report.tags');
			content += '
' + escapeXML(str) + '
\n';
			content += '
\n';
			for each(var tag in arr.tags) {
				content += '
' + escapeXML(tag.fields.name) + '
\n';
			}
			content += '
\n';
		}
		return content;
	}

	function _generateAttachmentsList(arr) {
		var content = '';
		if (arr.attachments && arr.attachments.length) {
			content += '
' + escapeXML(Zotero.getString('itemFields.attachments')) + '
\n';
			content += '
\n';
			for each(var attachment in arr.attachments) {
				content += '
';
				content += escapeXML(attachment.title);

				// Attachment tags
				content += _generateTagsList(attachment);

				// Attachment note
				if (attachment.note) {
					content += '
';
					if (attachment.note.substr(0, 1024).match(/
]*>/)) {
						content += attachment.note + '\n';
					}
					// Wrap plaintext notes in

					else {
						content += '
' + escapeXML(attachment.note) + '
\n';
					}
					content += '
';
				}

				content += '
\n';
			}
			content += '
\n';
		}
		return content;
	}
}
<commit_msg>Fix for HTML markup showing up in reports (since 21bf3000)<commit_after>/*
    ***** BEGIN LICENSE BLOCK *****

    Copyright © 2009 Center for History and New Media
                     George Mason University, Fairfax, Virginia, USA
                     http://zotero.org

    This file is part of Zotero.

    Zotero is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Zotero is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Zotero.  If not, see <http://www.gnu.org/licenses/>.

    ***** END LICENSE BLOCK *****
*/

Zotero.Report = new function() {
	this.generateHTMLDetails = generateHTMLDetails;
	this.generateHTMLList = generateHTMLList;

	// Sites that don't need the query string
	// (full URL is kept for link but stripped for display)
	var _noQueryStringSites = [
		/^http:\/\/([^\.]*\.)?nytimes\.com/
	];

	var escapeXML = function (str) {
		str = str.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, '\u2B1A');
		return Zotero.Utilities.htmlSpecialChars(str);
	}

	function generateHTMLDetails(items, combineChildItems) {
		var content = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ';
        content += '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n';
		content += '\n';
		content += '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n';
		content += '<title>' + Zotero.getString('report.title.default') + '</title>\n';
		content += '<link rel="stylesheet" type="text/css" href="zotero://report/detail.css"/>\n';
		content += '<link rel="stylesheet" type="text/css" media="screen,projection" href="zotero://report/detail_screen.css"/>\n';
		content += '<link rel="stylesheet" type="text/css" media="print" href="zotero://report/detail_print.css"/>\n';
		content += '\n\n\n';

		content += '
\n';
		for each(var arr in items) {
			content += '\n
\n';

			if (arr.title) {
				// Top-level item matched search, so display title
				if (arr.reportSearchMatch) {
					content += '
' + escapeXML(arr.title) + '
\n';
				}
				// Non-matching parent, so display "Parent Item: [Title]"
				else {
					content += '
' + escapeXML(Zotero.getString('report.parentItem'))
						+ ' ' + escapeXML(arr.title) + '
';
				}
			}

			// If parent matches search, display parent item metadata table and tags
			if (arr.reportSearchMatch) {
				content += _generateMetadataTable(arr);

				content += _generateTagsList(arr);

				// Independent note
				if (arr['note']) {
					content += '\n';

					// If not valid XML, display notes with entities encoded
					var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
							.createInstance(Components.interfaces.nsIDOMParser);
					var doc = parser.parseFromString('
' + arr.note + '
', "application/xml");
					if (doc.documentElement.tagName == 'parsererror') {
						content += '
' + escapeXML(arr.note) + '
\n';
					}
					// Otherwise render markup normally
					else {
						content += arr.note + '\n';
					}
				}
			}

			// Children
			if (arr.reportChildren) {
				// Child notes
				if (arr.reportChildren.notes.length) {
					// Only display "Notes:" header if parent matches search
					if (arr.reportSearchMatch) {
						content += '
' + escapeXML(Zotero.getString('report.notes')) + '
\n';
					}
					content += '
\n';
					for each(var note in arr.reportChildren.notes) {
						content += '
\n';

						// If not valid XML, display notes with entities encoded
						var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
								.createInstance(Components.interfaces.nsIDOMParser);
						var doc = parser.parseFromString('
' + note.note + '
', "application/xml");
						if (doc.documentElement.tagName == 'parsererror') {
							content += '
' + escapeXML(note.note) + '
\n';
						}
						// Otherwise render markup normally
						else {
							content += note.note + '\n';
						}

						// Child note tags
						content += _generateTagsList(note);

						content += '
\n';
					}
					content += '
\n';
				}

				// Chid attachments
				content += _generateAttachmentsList(arr.reportChildren);
			}

			// Related
			if (arr.reportSearchMatch && arr.related && arr.related.length) {
				content += '
' + escapeXML(Zotero.getString('itemFields.related')) + '
\n';
				content += '
\n';
				var relateds = Zotero.Items.get(arr.related);
				for each(var related in relateds) {
					content += '
';
					content += escapeXML(related.getDisplayTitle());
					content += '
\n';
				}
				content += '
\n';
			}

			content += '
\n\n';
		}
		content += '
\n';
		content += '\n';

		return content;
	}

	function generateHTMLList(items) {

	}

	function _generateMetadataTable(arr) {
		var table = false;
		var content = '
\n';

		// Item type
		content += '
\n';
		content += ''
			+ escapeXML(Zotero.getString('itemFields.itemType'))
			+ '	\n';
		content += '' + escapeXML(Zotero.ItemTypes.getLocalizedString(arr.itemType)) + '	\n';
		content += '
\n';

		// Creators
		if (arr['creators']) {
			table = true;
			var displayText;

			for each(var creator in arr['creators']) {
				// Two fields
				if (creator['fieldMode']==0) {
					displayText = creator['firstName'] + ' ' + creator['lastName'];
				}
				// Single field
				else if (creator['fieldMode']==1) {
					displayText = creator['lastName'];
				}
				else {
					// TODO
				}

				content += '
\n';
				content += ''
					+ escapeXML(Zotero.getString('creatorTypes.' + creator.creatorType))
					+ '	\n';
				content += '' + escapeXML(displayText) + '	\n';
				content += '
\n';
			}
		}

		// Move dateAdded and dateModified to the end of the array
		var da = arr['dateAdded'];
		var dm = arr['dateModified'];
		delete arr['dateAdded'];
		delete arr['dateModified'];
		arr['dateAdded'] = da;
		arr['dateModified'] = dm;

		for (var i in arr) {
			// Skip certain fields
			switch (i) {
				case 'reportSearchMatch':
				case 'reportChildren':

				case 'libraryID':
				case 'key':
				case 'itemType':
				case 'itemID':
				case 'sourceItemID':
				case 'title':
				case 'firstCreator':
				case 'creators':
				case 'tags':
				case 'related':
				case 'notes':
				case 'note':
				case 'attachments':
					continue;
			}

			try {
				var localizedFieldName = Zotero.ItemFields.getLocalizedString(arr.itemType, i);
			}
			// Skip fields we don't have a localized string for
			catch (e) {
				Zotero.debug('Localized string not available for ' + 'itemFields.' + i, 2);
				continue;
			}

			arr[i] = Zotero.Utilities.trim(arr[i] + '');

			// Skip empty fields
			if (!arr[i]) {
				continue;
			}

			table = true;
			var fieldText;

			if (i == 'url' && arr[i].match(/^https?:\/\//)) {
				fieldText = arr[i];

				// Shorten long URLs manually until Firefox wraps at ?
				// (like Safari) or supports the CSS3 word-wrap property
				var firstSpace = fieldText.indexOf(' ');
				if ((firstSpace == -1 && arr[i].length > 29) || firstSpace > 29) {
					var stripped = false;

					// Strip query string for sites we know don't need it
					for each(var re in _noQueryStringSites) {
						if (re.test(arr[i])){
							var pos = arr[i].indexOf('?');
							if (pos != -1) {
								fieldText = arr[i].substr(0, pos);
								stripped = true;
							}
							break;
						}
					}

					if (!stripped) {
						// Add a line-break after the ? of long URLs
						fieldText = arr[i].replace('?', "?<ZOTEROBREAK/>");

						// Strip query string variables from the end while the
						// query string is longer than the main part
						var pos = fieldText.indexOf('?');
						if (pos != -1) {
							while (pos < (fieldText.length / 2)) {
								var lastAmp = fieldText.lastIndexOf('&');
								if (lastAmp == -1) {
									break;
								}
								fieldText = fieldText.substr(0, lastAmp);
								var shortened = true;
							}
							// Append '&...' to the end
							if (shortened) {
								fieldText += "&<ZOTEROHELLIP/>";
							}
						}
					}
				}

				fieldText = ''
					+ escapeXML(fieldText) + '';
			}
			// Remove SQL date from multipart dates
			// (e.g. '2006-00-00 Summer 2006' becomes 'Summer 2006')
			else if (i=='date') {
				fieldText = escapeXML(Zotero.Date.multipartToStr(arr[i]));
			}
			// Convert dates to local format
			else if (i=='accessDate' || i=='dateAdded' || i=='dateModified') {
				var date = Zotero.Date.sqlToDate(arr[i], true)
				fieldText = escapeXML(date.toLocaleString());
			}
			else {
				fieldText = escapeXML(arr[i]);
			}

			content += '
\n' + escapeXML(localizedFieldName)
				+ '	\n' + fieldText + '	\n
\n';
		}

		content += '
';

		return table ? content : '';
	}

	function _generateTagsList(arr) {
		var content = '';
		if (arr['tags'] && arr['tags'].length) {
			var str = Zotero.getString('report.tags');
			content += '
' + escapeXML(str) + '
\n';
			content += '
\n';
			for each(var tag in arr.tags) {
				content += '
' + escapeXML(tag.fields.name) + '
\n';
			}
			content += '
\n';
		}
		return content;
	}

	function _generateAttachmentsList(arr) {
		var content = '';
		if (arr.attachments && arr.attachments.length) {
			content += '
' + escapeXML(Zotero.getString('itemFields.attachments')) + '
\n';
			content += '
\n';
			for each(var attachment in arr.attachments) {
				content += '
';
				content += escapeXML(attachment.title);

				// Attachment tags
				content += _generateTagsList(attachment);

				// Attachment note
				if (attachment.note) {
					content += '
';
					if (attachment.note.substr(0, 1024).match(/
]*>/)) {
						content += attachment.note + '\n';
					}
					// Wrap plaintext notes in

					else {
						content += '
' + escapeXML(attachment.note) + '
\n';
					}
					content += '
';
				}

				content += '
\n';
			}
			content += '
\n';
		}
		return content;
	}
}
<|endoftext|>