bool JsonLd::addContext( DynamicObject& context, DynamicObject& in, DynamicObject& out) { bool rval = true; // "a" is automatically shorthand for rdf type DynamicObject ctx = (context.isNull() ? DynamicObject() : context.clone()); ctx["a"] = RDF_TYPE; // TODO: should context simplification be an option? (ie: remove context // entries that are not used in the output) // setup output context DynamicObject& contextOut = out["#"]; contextOut->setType(Map); // apply context _applyContext(ctx, contextOut, NULL, in, out); // clean up if(contextOut->length() == 0) { out->removeMember("#"); } return rval; }
bool JsonLd::normalize(DynamicObject& in, DynamicObject& out) { bool rval = true; // initialize output out->setType(Map); out->clear(); // create map to store subjects DynamicObject subjects; subjects->setType(Map); // initialize context DynamicObject ctx(NULL); if(in->hasMember("#")) { ctx = in["#"]; } // put all subjects in an array for single code path DynamicObject input; input->setType(Array); if(in->hasMember("@") && in["@"]->getType() == Array) { input.merge(in["@"], true); } else { input->append(in); } // do normalization int bnodeId = 0; DynamicObjectIterator i = input.getIterator(); while(rval && i->hasNext()) { rval = _normalize(ctx, i->next(), &subjects, NULL, bnodeId); } // build output if(rval) { // single subject if(subjects->length() == 1) { DynamicObject subject = subjects.first(); out.merge(subject, false); // FIXME: will need to check predicates for blank nodes as well... // and fail if they aren't embeds? // strip blank node '@' if(_isBlankNode(out)) { out->removeMember("@"); } } // multiple subjects else { DynamicObject& array = out["@"]; array->setType(Array); i = subjects.getIterator(); while(i->hasNext()) { DynamicObject& next = i->next(); // FIXME: will need to check predicates for blank nodes as well... // and fail if they aren't embeds? // strip blank node '@' if(_isBlankNode(next)) { next->removeMember("@"); } array->append(next); } } } return rval; }
/** * Recursively normalizes the given input object. * * Input: A subject with predicates and possibly embedded other subjects. * Output: Either a map of normalized subjects OR a tree of normalized subjects. * * Normalization Algorithm: * * Replace the existing context if the input has '#'. * * For each key-value: * 1. Split the key on a colon and look for prefix in the context. If found, * expand the key to an IRI, else it is already an IRI, add <>, save the * new predicate to add to the output. * 2. If value is a Map, then it is a subject, set the predicate to the * subject's '@' IRI value and recurse into it. Else goto #3. * 3. Look up the key in the context to find type coercion info. If not found, * goto #4, else goto #5. * 4. Check the value for an integer, double, or boolean. If matched, set * type according to xsd types. If not matched, look for <>, if found, * do CURIE vs. IRI check like #1 and create appropriate value. * 5. If type coercion entry is a string, encode the value using the specific * type. If it is an array, check the type in order of preference. If an * unrecognized type (non-xsd, non-IRI) is provided, throw an exception. * * @param ctx the context to use (changes during recursion as necessary). * @param in the input object. * @param subjects a map of normalized subjects. * @param out a tree normalized objects. * @param bnodeId the last blank node ID used. * * @return true on success, false on failure with exception set. */ static bool _normalize( DynamicObject ctx, DynamicObject& in, DynamicObject* subjects, DynamicObject* out, int& bnodeId) { bool rval = true; // FIXME: validate context (check for non-xsd types in type coercion arrays) if(!in.isNull()) { // update context if(in->hasMember("#")) { ctx = in["#"]; } // vars for normalization state DynamicObject subject(NULL); if(subjects != NULL) { subject = DynamicObject(); subject->setType(Map); // assign blank node ID as needed if(!in->hasMember("@")) { string bnodeKey = _createBlankNodeId(++bnodeId); subject["@"] = bnodeKey.c_str(); } } string nKey; // iterate over key-values DynamicObjectIterator i = in.getIterator(); while(rval && i->hasNext()) { DynamicObject& value = i->next(); const char* key = i->getName(); // skip context keys if(key[0] == '#') { continue; } // get normalized key nKey = _normalizeValue(ctx, key, RDF_TYPE_IRI, NULL, NULL); // put values in an array for single code path DynamicObject values; values->setType(Array); if(value->getType() == Array) { values.merge(value, true); // preserve array structure when not using subjects map if(out != NULL) { (*out)[nKey.c_str()]->setType(Array); } } else { values->append(value); } // normalize all values DynamicObjectIterator vi = values.getIterator(); while(rval && vi->hasNext()) { DynamicObject v = vi->next(); if(v->getType() == Map) { if(subjects != NULL) { // get a normalized subject for the value string vSubject; if(v->hasMember("@")) { // normalize existing subject vSubject = _normalizeValue( ctx, v["@"], RDF_TYPE_IRI, NULL, NULL); } else { // generate the next blank node ID in order to preserve // the blank node embed in the code below vSubject = _createBlankNodeId(bnodeId + 1); } // determine if value is a blank node bool isBNode = _isBlankNode(v); // update non-blank node subject (use value's subject IRI) if(!isBNode) { _setPredicate(subject, nKey.c_str(), vSubject.c_str()); } // recurse rval = _normalize(ctx, v, subjects, out, bnodeId); // preserve embedded blank node if(rval && isBNode) { // remove embed from top-level subjects DynamicObject embed = (*subjects)[vSubject.c_str()]; (*subjects)->removeMember(vSubject.c_str()); embed->removeMember("@"); _setEmbed(subject, nKey.c_str(), embed); } } else { // update out and recurse DynamicObject next = (*out)[nKey.c_str()]; if(value->getType() == Array) { next = next->append(); } else { next->setType(Map); } rval = _normalize(ctx, v, subjects, &next, bnodeId); } } else { _setPredicate( (subjects != NULL) ? subject : *out, nKey.c_str(), _normalizeValue(ctx, v, RDF_TYPE_UNKNOWN, key, NULL).c_str()); } } } // add subject to map if(subjects != NULL) { (*subjects)[subject["@"]->getString()] = subject; } } return rval; }