private function _consume_char($c) { // valid whitespace characters in JSON (from RFC4627 for JSON) include: // space, horizontal tab, line feed or new line, and carriage return. // thanks: http://stackoverflow.com/questions/16042274/definition-of-whitespace-in-json if (($c === " " || $c === "\t" || $c === "\n" || $c === "\r") && !($this->_state === self::STATE_IN_STRING || $this->_state === self::STATE_UNICODE || $this->_state === self::STATE_START_ESCAPE || $this->_state === self::STATE_IN_NUMBER || $this->_state === self::STATE_START_DOCUMENT)) { // we wrap this so that we don't make a ton of unnecessary function calls // unless someone really, really cares about whitespace. if ($this->_emit_whitespace) { $this->_listener->whitespace($c); } return; } switch ($this->_state) { case self::STATE_IN_STRING: if ($c === '"') { $this->_end_string(); } elseif ($c === '\\') { $this->_state = self::STATE_START_ESCAPE; } elseif ($c < "" || $c === "") { throw new ParsingError($this->_line_number, $this->_char_number, "Unescaped control character encountered: " . $c); } else { $this->_buffer .= $c; } break; case self::STATE_IN_ARRAY: if ($c === ']') { $this->_end_array(); } else { $this->_start_value($c); } break; case self::STATE_IN_OBJECT: if ($c === '}') { $this->_end_object(); } elseif ($c === '"') { $this->_start_key(); } else { throw new ParsingError($this->_line_number, $this->_char_number, "Start of string expected for object key. Instead got: " . $c); } break; case self::STATE_END_KEY: if ($c !== ':') { throw new ParsingError($this->_line_number, $this->_char_number, "Expected ':' after key."); } $this->_state = self::STATE_AFTER_KEY; break; case self::STATE_AFTER_KEY: $this->_start_value($c); break; case self::STATE_START_ESCAPE: $this->_process_escape_character($c); break; case self::STATE_UNICODE: $this->_process_unicode_character($c); break; case self::STATE_UNICODE_SURROGATE: $this->_unicode_escape_buffer .= $c; if (mb_strlen($this->_unicode_escape_buffer) == 2) { $this->_end_unicode_surrogate_interstitial(); } break; case self::STATE_AFTER_VALUE: $within = end($this->_stack); if ($within === self::STACK_OBJECT) { if ($c === '}') { $this->_end_object(); } elseif ($c === ',') { $this->_state = self::STATE_IN_OBJECT; } else { throw new ParsingError($this->_line_number, $this->_char_number, "Expected ',' or '}' while parsing object. Got: " . $c); } } elseif ($within === self::STACK_ARRAY) { if ($c === ']') { $this->_end_array(); } elseif ($c === ',') { $this->_state = self::STATE_IN_ARRAY; } else { throw new ParsingError($this->_line_number, $this->_char_number, "Expected ',' or ']' while parsing array. Got: " . $c); } } else { throw new ParsingError($this->_line_number, $this->_char_number, "Finished a literal, but unclear what state to move to. Last state: " . $within); } break; case self::STATE_IN_NUMBER: if (ctype_digit($c)) { $this->_buffer .= $c; } elseif ($c === '.') { if (strpos($this->_buffer, '.') !== false) { throw new ParsingError($this->_line_number, $this->_char_number, "Cannot have multiple decimal points in a number."); } elseif (stripos($this->_buffer, 'e') !== false) { throw new ParsingError($this->_line_number, $this->_char_number, "Cannot have a decimal point in an exponent."); } $this->_buffer .= $c; } elseif ($c === 'e' || $c === 'E') { if (stripos($this->_buffer, 'e') !== false) { throw new ParsingError($this->_line_number, $this->_char_number, "Cannot have multiple exponents in a number."); } $this->_buffer .= $c; } elseif ($c === '+' || $c === '-') { $last = mb_substr($this->_buffer, -1); if (!($last === 'e' || $last === 'E')) { throw new ParsingError($this->_line_number, $this->_char_number, "Can only have '+' or '-' after the 'e' or 'E' in a number."); } $this->_buffer .= $c; } else { $this->_end_number(); // we have consumed one beyond the end of the number $this->_consume_char($c); } break; case self::STATE_IN_TRUE: $this->_buffer .= $c; if (mb_strlen($this->_buffer) === 4) { $this->_end_true(); } break; case self::STATE_IN_FALSE: $this->_buffer .= $c; if (mb_strlen($this->_buffer) === 5) { $this->_end_false(); } break; case self::STATE_IN_NULL: $this->_buffer .= $c; if (mb_strlen($this->_buffer) === 4) { $this->_end_null(); } break; case self::STATE_START_DOCUMENT: $this->_listener->start_document(); if ($c === '[') { $this->_start_array(); } elseif ($c === '{') { $this->_start_object(); } else { throw new ParsingError($this->_line_number, $this->_char_number, "Document must start with object or array."); } break; case self::STATE_DONE: throw new ParsingError($this->_line_number, $this->_char_number, "Expected end of document."); default: throw new ParsingError($this->_line_number, $this->_char_number, "Internal error. Reached an unknown state: " . $this->_state); } }