blob: 602142b36ef08f0c3cc544b3c57689c69791a5c9 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// 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 Google Inc. 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 THE COPYRIGHT
// OWNER OR CONTRIBUTORS 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.
#include "net/jsonpath.h"
#include <cctype>
#include <sstream>
#include <string>
#include <vector>
#include "base/logging.h"
#include "base/port.h"
#include "base/util.h"
namespace mozc {
namespace net {
namespace {
struct JsonPathNode {
enum Type {
UNDEFINED_INDEX,
OBJECT_INDEX,
ARRAY_INDEX,
SLICE_INDEX
};
Type type;
string object_index;
int array_index;
int slice_start;
int slice_end;
int slice_step;
static const int kSliceUndef = kint32max;
static bool IsUndef(int n) {
return n == kSliceUndef;
}
string DebugString() const {
ostringstream os;
os << "{" << type << ":" << array_index << ":" << object_index
<< ":(" << slice_start << ":" << slice_end << ":" << slice_step << ")}";
return os.str();
}
JsonPathNode() :
type(UNDEFINED_INDEX), array_index(0),
slice_start(0), slice_end(0), slice_step(0) {}
~JsonPathNode() {}
};
bool GetDigit(const string &str, int *output) {
DCHECK(output);
if (str.empty()) {
return false;
}
const char *begin = str.data();
const char *end = str.data() + str.size();
if (str[0] == '-') {
if (str.size() == 1) {
return false;
}
++begin;
}
while (begin < end) {
if (!isdigit(*begin)) {
return false;
}
++begin;
}
*output = atoi32(str.c_str());
return true;
}
bool GetSliceDigit(const string &str, int *output) {
if (str.empty()) {
*output = JsonPathNode::kSliceUndef;
return true;
}
return GetDigit(str, output);
}
bool GetQuotedString(const string &str, const char c,
string *output) {
if (str.size() >= 2 &&
str[0] == c && str[str.size() - 1] == c) {
*output = str.substr(1, str.size() - 2);
return true;
}
return false;
}
typedef vector<JsonPathNode> JsonPathNodes;
class JsonPathExp : public vector<vector<JsonPathNode> > {
public:
bool Parse(const string &jsonpath) {
clear();
if (jsonpath.size() <= 1 || jsonpath[0] != '$') {
LOG(ERROR) << "Not starts with $";
return false;
}
if (Util::EndsWith(jsonpath, ".") ||
jsonpath.find("...") != string::npos) {
LOG(ERROR) << "Parse error: " << jsonpath;
return false;
}
if (jsonpath.find("(") != string::npos ||
jsonpath.find(")") != string::npos ||
jsonpath.find("@") != string::npos ||
jsonpath.find("?") != string::npos) {
LOG(ERROR) << "script expression/current node are not supported: "
<< jsonpath;
return false;
}
const char *begin = jsonpath.data() + 1;
const char *end = jsonpath.data() + jsonpath.size();
string item;
for (; begin < end; ++begin) {
if (*begin == ']') {
return false;
}
if (*begin == '.' || *begin == '[') {
if (!item.empty()) {
if (!AddNodes(item, OUT_BRACKET)) {
return false;
}
item.clear();
}
if (*begin == '.' && begin + 1 < end && *(begin + 1) == '.') {
++begin;
if (!AddNodes(".", OUT_BRACKET)) {
return false;
}
} else if (*begin == '[') {
++begin;
string exp;
while (*begin != ']') {
if (begin == end) {
LOG(ERROR) << "Cannot find closing \"]\"";
return false;
}
exp += *begin;
++begin;
}
if (!AddNodes(exp, IN_BRACKET)) {
return false;
}
}
} else {
item += *begin;
}
}
if (!item.empty()) {
if (!AddNodes(item, OUT_BRACKET)) {
return false;
}
}
return !empty();
}
string DebugString() const {
ostringstream os;
for (size_t i = 0; i < size(); ++i) {
os << "[";
for (size_t j = 0; j < (*this)[i].size(); ++j) {
os << (*this)[i][j].DebugString();
}
os << "]";
}
return os.str();
}
private:
enum NodesType {
IN_BRACKET,
OUT_BRACKET
};
bool AddNodes(const string &nodes_exp, NodesType nodes_type) {
if (nodes_exp.empty()) {
return false;
}
resize(size() + 1);
JsonPathNodes *nodes = &(back());
DCHECK(nodes);
if (nodes_type == OUT_BRACKET) {
JsonPathNode node;
node.type = JsonPathNode::OBJECT_INDEX;
node.object_index = nodes_exp;
nodes->push_back(node);
} else if (nodes_type == IN_BRACKET) {
vector<string> nodes_exps;
Util::SplitStringUsing(nodes_exp, ",", &nodes_exps);
for (size_t i = 0; i < nodes_exps.size(); ++i) {
JsonPathNode node;
node.type = JsonPathNode::UNDEFINED_INDEX;
string in_nodes_exp;
if (GetQuotedString(nodes_exps[i], '\'', &in_nodes_exp) ||
GetQuotedString(nodes_exps[i], '\"', &in_nodes_exp)) {
node.type = JsonPathNode::OBJECT_INDEX;
node.object_index = in_nodes_exp;
} else if (nodes_exps[i] == "*") {
node.type = JsonPathNode::OBJECT_INDEX;
node.object_index = "*";
} else {
vector<string> slice;
Util::SplitStringAllowEmpty(nodes_exps[i], ":", &slice);
if (slice.size() == 1) {
if (GetDigit(slice[0], &node.array_index)) {
node.type = JsonPathNode::ARRAY_INDEX;
} else {
// fallback to OBJECT_INDEX.
node.type = JsonPathNode::OBJECT_INDEX;
node.object_index = slice[0];
}
} else if (slice.size() == 2 &&
GetSliceDigit(slice[0], &node.slice_start) &&
GetSliceDigit(slice[1], &node.slice_end)) {
node.slice_step = JsonPathNode::kSliceUndef;
node.type = JsonPathNode::SLICE_INDEX;
} else if (slice.size() == 3 &&
GetSliceDigit(slice[0], &node.slice_start) &&
GetSliceDigit(slice[1], &node.slice_end) &&
GetSliceDigit(slice[2], &node.slice_step)) {
node.type = JsonPathNode::SLICE_INDEX;
}
}
if (node.type == JsonPathNode::UNDEFINED_INDEX) {
return false;
}
nodes->push_back(node);
}
} else {
LOG(FATAL) << "Unknown nodes type";
}
return true;
}
};
// Find all Json::Value(s) from root node |value|,
// which matches to |nodes|.
void CollectValuesRecursively(const Json::Value &value,
const JsonPathNodes &nodes,
vector<const Json::Value *> *output) {
DCHECK(output);
for (size_t node_index = 0; node_index < nodes.size(); ++node_index) {
if (nodes[node_index].type != JsonPathNode::OBJECT_INDEX) {
continue;
}
if (value.isObject()) {
const Json::Value::Members members = value.getMemberNames();
const string &object_index = nodes[node_index].object_index;
if (object_index != "*" && value.isMember(object_index)) {
output->push_back(&value[object_index]);
}
for (size_t i = 0; i < members.size(); ++i) {
const Json::Value &v = value[members[i]];
if (object_index == "*") {
output->push_back(&v);
}
CollectValuesRecursively(v, nodes, output);
}
} else if (value.isArray()) {
for (Json::ArrayIndex i = 0; i < value.size(); ++i) {
CollectValuesRecursively(value[i], nodes, output);
}
}
}
}
void CollectNodesFromJson(const Json::Value &value,
const JsonPathExp &jsonpathexp,
size_t depth,
vector<const Json::Value *> *output) {
if (depth >= jsonpathexp.size()) {
output->push_back(&value);
return;
}
const JsonPathNodes &nodes = jsonpathexp[depth];
for (size_t node_index = 0; node_index < nodes.size(); ++node_index) {
const JsonPathNode &node = nodes[node_index];
if (node.type == JsonPathNode::OBJECT_INDEX) {
if (node.object_index == "*") {
if (value.isObject()) {
const Json::Value::Members members = value.getMemberNames();
for (size_t i = 0; i < members.size(); ++i) {
CollectNodesFromJson(value[members[i]],
jsonpathexp, depth + 1, output);
}
} else if (value.isArray()) {
for (Json::ArrayIndex i = 0; i < value.size(); ++i) {
CollectNodesFromJson(value[i], jsonpathexp, depth + 1, output);
}
} else {
CollectNodesFromJson(value, jsonpathexp, depth + 1, output);
}
} else if (node.object_index == "." && depth + 1 < jsonpathexp.size()) {
vector<const Json::Value *> matched_values;
CollectValuesRecursively(value, jsonpathexp[depth + 1],
&matched_values);
for (size_t i = 0; i < matched_values.size(); ++i) {
CollectNodesFromJson(*(matched_values[i]),
jsonpathexp, depth + 2, output);
}
} else if (value.isObject() && value.isMember(node.object_index)) {
CollectNodesFromJson(value[node.object_index],
jsonpathexp, depth + 1, output);
}
} else if (node.type == JsonPathNode::ARRAY_INDEX) {
const int i = node.array_index >= 0 ?
node.array_index : value.size() + node.array_index;
if (value.isArray() && value.isValidIndex(i)) {
CollectNodesFromJson(value[i], jsonpathexp, depth + 1, output);
}
} else if (node.type == JsonPathNode::SLICE_INDEX) {
if (value.isArray()) {
const int size = static_cast<int>(value.size());
int start = JsonPathNode::IsUndef(node.slice_start) ?
0 : node.slice_start;
int end = JsonPathNode::IsUndef(node.slice_end) ?
size : node.slice_end;
const int step = JsonPathNode::IsUndef(node.slice_step) ?
1 : node.slice_step;
start = (start < 0) ? max(0, start + size) : min(size, start);
end = (end < 0) ? max(0, end + size) : min(size, end);
if (step > 0 && end > start) {
for (int i = start; i < end; i += step) {
if (value.isValidIndex(i)) {
CollectNodesFromJson(value[i], jsonpathexp, depth + 1, output);
}
}
} else if (step < 0 && end < start) {
for (int i = start; i > end; i += step) {
if (value.isValidIndex(i)) {
CollectNodesFromJson(value[i], jsonpathexp, depth + 1, output);
}
}
}
}
} else {
LOG(FATAL) << "Unknown type";
}
}
}
} // namespace
// static
bool JsonPath::Parse(const Json::Value &root,
const string &jsonpath,
vector<const Json::Value *> *output) {
JsonPathExp jsonpathexp;
if (!jsonpathexp.Parse(jsonpath)) {
LOG(WARNING) << "Parsing JsonPath error: " << jsonpath;
return false;
}
VLOG(1) << jsonpathexp.DebugString();
DCHECK(output);
CollectNodesFromJson(root, jsonpathexp, 0, output);
return true;
}
} // namespace net
} // namespace mozc