/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsCOMPtr.h" #include "nsICollation.h" #include "nsIDOMNode.h" #include "nsIRDFNode.h" #include "nsIRDFObserver.h" #include "nsIRDFRemoteDataSource.h" #include "nsIRDFInferDataSource.h" #include "nsIRDFService.h" #include "nsRDFCID.h" #include "nsIServiceManager.h" #include "nsNameSpaceManager.h" #include "nsGkAtoms.h" #include "nsIDOMDocument.h" #include "nsAttrName.h" #include "rdf.h" #include "nsArrayUtils.h" #include "nsIURI.h" #include "nsContentTestNode.h" #include "nsRDFConInstanceTestNode.h" #include "nsRDFConMemberTestNode.h" #include "nsRDFPropertyTestNode.h" #include "nsInstantiationNode.h" #include "nsRDFTestNode.h" #include "nsXULContentUtils.h" #include "nsXULTemplateBuilder.h" #include "nsXULTemplateResultRDF.h" #include "nsXULTemplateResultSetRDF.h" #include "nsXULTemplateQueryProcessorRDF.h" #include "nsXULSortService.h" #include "nsIDocument.h" //---------------------------------------------------------------------- #define PARSE_TYPE_INTEGER "Integer" nsrefcnt nsXULTemplateQueryProcessorRDF::gRefCnt = 0; nsIRDFService* nsXULTemplateQueryProcessorRDF::gRDFService; nsIRDFContainerUtils* nsXULTemplateQueryProcessorRDF::gRDFContainerUtils; nsIRDFResource* nsXULTemplateQueryProcessorRDF::kNC_BookmarkSeparator; nsIRDFResource* nsXULTemplateQueryProcessorRDF::kRDF_type; NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULTemplateQueryProcessorRDF) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULTemplateQueryProcessorRDF) tmp->Done(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END static PLDHashOperator BindingDependenciesTraverser(nsISupports* key, nsXULTemplateQueryProcessorRDF::ResultArray* array, void* userArg) { nsCycleCollectionTraversalCallback *cb = static_cast(userArg); int32_t i, count = array->Length(); for (i = 0; i < count; ++i) { cb->NoteXPCOMChild(array->ElementAt(i)); } return PL_DHASH_NEXT; } static PLDHashOperator MemoryElementTraverser(const uint32_t& key, nsCOMArray* array, void* userArg) { nsCycleCollectionTraversalCallback *cb = static_cast(userArg); int32_t i, count = array->Count(); for (i = 0; i < count; ++i) { cb->NoteXPCOMChild(array->ObjectAt(i)); } return PL_DHASH_NEXT; } static PLDHashOperator RuleToBindingTraverser(nsISupports* key, RDFBindingSet* binding, void* userArg) { nsCycleCollectionTraversalCallback *cb = static_cast(userArg); cb->NoteXPCOMChild(key); return PL_DHASH_NEXT; } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULTemplateQueryProcessorRDF) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDB) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRef) tmp->mBindingDependencies.EnumerateRead(BindingDependenciesTraverser, &cb); tmp->mMemoryElementToResultMap.EnumerateRead(MemoryElementTraverser, &cb); tmp->mRuleToBindingsMap.EnumerateRead(RuleToBindingTraverser, &cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueries) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULTemplateQueryProcessorRDF) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULTemplateQueryProcessorRDF) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULTemplateQueryProcessorRDF) NS_INTERFACE_MAP_ENTRY(nsIXULTemplateQueryProcessor) NS_INTERFACE_MAP_ENTRY(nsIRDFObserver) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULTemplateQueryProcessor) NS_INTERFACE_MAP_END nsXULTemplateQueryProcessorRDF::nsXULTemplateQueryProcessorRDF(void) : mDB(nullptr), mBuilder(nullptr), mQueryProcessorRDFInited(false), mGenerationStarted(false), mUpdateBatchNest(0), mSimpleRuleMemberTest(nullptr) { gRefCnt++; } nsXULTemplateQueryProcessorRDF::~nsXULTemplateQueryProcessorRDF(void) { if (--gRefCnt == 0) { NS_IF_RELEASE(gRDFService); NS_IF_RELEASE(gRDFContainerUtils); NS_IF_RELEASE(kNC_BookmarkSeparator); NS_IF_RELEASE(kRDF_type); } } nsresult nsXULTemplateQueryProcessorRDF::InitGlobals() { nsresult rv; // Initialize the global shared reference to the service // manager and get some shared resource objects. if (!gRDFService) { NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); rv = CallGetService(kRDFServiceCID, &gRDFService); if (NS_FAILED(rv)) return rv; } if (!gRDFContainerUtils) { NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils); if (NS_FAILED(rv)) return rv; } if (!kNC_BookmarkSeparator) { gRDFService->GetResource( NS_LITERAL_CSTRING(NC_NAMESPACE_URI "BookmarkSeparator"), &kNC_BookmarkSeparator); } if (!kRDF_type) { gRDFService->GetResource( NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"), &kRDF_type); } return NS_OK; } //---------------------------------------------------------------------- // // nsIXULTemplateQueryProcessor interface // NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::GetDatasource(nsIArray* aDataSources, nsIDOMNode* aRootNode, bool aIsTrusted, nsIXULTemplateBuilder* aBuilder, bool* aShouldDelayBuilding, nsISupports** aResult) { nsCOMPtr compDB; nsCOMPtr root = do_QueryInterface(aRootNode); nsresult rv; *aResult = nullptr; *aShouldDelayBuilding = false; NS_ENSURE_TRUE(root, NS_ERROR_UNEXPECTED); // make sure the RDF service is set up rv = InitGlobals(); NS_ENSURE_SUCCESS(rv, rv); // create a database for the builder compDB = do_CreateInstance(NS_RDF_DATASOURCE_CONTRACTID_PREFIX "composite-datasource"); if (!compDB) { NS_ERROR("unable to construct new composite data source"); return NS_ERROR_UNEXPECTED; } // check for magical attributes. XXX move to ``flags''? if (root->AttrValueIs(kNameSpaceID_None, nsGkAtoms::coalesceduplicatearcs, nsGkAtoms::_false, eCaseMatters)) compDB->SetCoalesceDuplicateArcs(false); if (root->AttrValueIs(kNameSpaceID_None, nsGkAtoms::allownegativeassertions, nsGkAtoms::_false, eCaseMatters)) compDB->SetAllowNegativeAssertions(false); if (aIsTrusted) { // If we're a privileged (e.g., chrome) document, then add the // local store as the first data source in the db. Note that // we _might_ not be able to get a local store if we haven't // got a profile to read from yet. nsCOMPtr localstore; rv = gRDFService->GetDataSource("rdf:local-store", getter_AddRefs(localstore)); NS_ENSURE_SUCCESS(rv, rv); rv = compDB->AddDataSource(localstore); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to add local store to db"); NS_ENSURE_SUCCESS(rv, rv); } uint32_t length, index; rv = aDataSources->GetLength(&length); NS_ENSURE_SUCCESS(rv,rv); for (index = 0; index < length; index++) { nsCOMPtr uri = do_QueryElementAt(aDataSources, index); if (!uri) // we ignore other datasources than uri continue; nsCOMPtr ds; nsAutoCString uristrC; uri->GetSpec(uristrC); rv = gRDFService->GetDataSource(uristrC.get(), getter_AddRefs(ds)); if (NS_FAILED(rv)) { // This is only a warning because the data source may not // be accessible for any number of reasons, including // security, a bad URL, etc. #ifdef DEBUG nsAutoCString msg; msg.AppendLiteral("unable to load datasource '"); msg.Append(uristrC); msg.Append('\''); NS_WARNING(msg.get()); #endif continue; } compDB->AddDataSource(ds); } // check if we were given an inference engine type nsAutoString infer; nsCOMPtr db; root->GetAttr(kNameSpaceID_None, nsGkAtoms::infer, infer); if (!infer.IsEmpty()) { nsCString inferCID(NS_RDF_INFER_DATASOURCE_CONTRACTID_PREFIX); AppendUTF16toUTF8(infer, inferCID); nsCOMPtr inferDB = do_CreateInstance(inferCID.get()); if (inferDB) { inferDB->SetBaseDataSource(compDB); db = do_QueryInterface(inferDB); } else { NS_WARNING("failed to construct inference engine specified on template"); } } if (!db) db = compDB; return CallQueryInterface(db, aResult); } NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::InitializeForBuilding(nsISupports* aDatasource, nsIXULTemplateBuilder* aBuilder, nsIDOMNode* aRootNode) { if (!mQueryProcessorRDFInited) { nsresult rv = InitGlobals(); if (NS_FAILED(rv)) return rv; mQueryProcessorRDFInited = true; } // don't do anything if generation has already been done if (mGenerationStarted) return NS_ERROR_UNEXPECTED; mDB = do_QueryInterface(aDatasource); mBuilder = aBuilder; ComputeContainmentProperties(aRootNode); // Add ourselves as a datasource observer if (mDB) mDB->AddObserver(this); return NS_OK; } NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::Done() { if (!mQueryProcessorRDFInited) return NS_OK; if (mDB) mDB->RemoveObserver(this); mDB = nullptr; mBuilder = nullptr; mRefVariable = nullptr; mLastRef = nullptr; mGenerationStarted = false; mUpdateBatchNest = 0; mContainmentProperties.Clear(); for (ReteNodeSet::Iterator node = mAllTests.First(); node != mAllTests.Last(); ++node) delete *node; mAllTests.Clear(); mRDFTests.Clear(); mQueries.Clear(); mSimpleRuleMemberTest = nullptr; mBindingDependencies.Clear(); mMemoryElementToResultMap.Clear(); mRuleToBindingsMap.Clear(); return NS_OK; } NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::CompileQuery(nsIXULTemplateBuilder* aBuilder, nsIDOMNode* aQueryNode, nsIAtom* aRefVariable, nsIAtom* aMemberVariable, nsISupports** _retval) { nsRefPtr query = new nsRDFQuery(this); if (!query) return NS_ERROR_OUT_OF_MEMORY; query->mRefVariable = aRefVariable; if (!mRefVariable) mRefVariable = aRefVariable; if (!aMemberVariable) query->mMemberVariable = do_GetAtom("?"); else query->mMemberVariable = aMemberVariable; nsresult rv; TestNode *lastnode = nullptr; nsCOMPtr content = do_QueryInterface(aQueryNode); if (content->NodeInfo()->Equals(nsGkAtoms::_template, kNameSpaceID_XUL)) { // simplified syntax with no rules query->SetSimple(); NS_ASSERTION(!mSimpleRuleMemberTest, "CompileQuery called twice with the same template"); if (!mSimpleRuleMemberTest) rv = CompileSimpleQuery(query, content, &lastnode); else rv = NS_ERROR_FAILURE; } else if (content->NodeInfo()->Equals(nsGkAtoms::rule, kNameSpaceID_XUL)) { // simplified syntax with at least one rule query->SetSimple(); rv = CompileSimpleQuery(query, content, &lastnode); } else { rv = CompileExtendedQuery(query, content, &lastnode); } if (NS_FAILED(rv)) return rv; query->SetQueryNode(aQueryNode); nsInstantiationNode* instnode = new nsInstantiationNode(this, query); if (!instnode) return NS_ERROR_OUT_OF_MEMORY; // this and other functions always add nodes to mAllTests first. That // way if something fails, the node will just sit harmlessly in mAllTests // where it can be deleted later. rv = mAllTests.Add(instnode); if (NS_FAILED(rv)) { delete instnode; return rv; } rv = lastnode->AddChild(instnode); if (NS_FAILED(rv)) return rv; mQueries.AppendElement(query); *_retval = query; NS_ADDREF(*_retval); return NS_OK; } NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::GenerateResults(nsISupports* aDatasource, nsIXULTemplateResult* aRef, nsISupports* aQuery, nsISimpleEnumerator** aResults) { nsCOMPtr rdfquery = do_QueryInterface(aQuery); if (! rdfquery) return NS_ERROR_INVALID_ARG; mGenerationStarted = true; // should be safe to cast here since the query is a // non-scriptable nsITemplateRDFQuery nsRDFQuery* query = static_cast(aQuery); *aResults = nullptr; nsCOMPtr results; if (aRef) { // make sure that cached results were generated for this ref, and if not, // regenerate them. Otherwise, things will go wrong for templates bound to // an HTML element as they are not generated lazily. if (aRef == mLastRef) { query->UseCachedResults(getter_AddRefs(results)); } else { // clear the cached results int32_t count = mQueries.Length(); for (int32_t r = 0; r < count; r++) { mQueries[r]->ClearCachedResults(); } } if (! results) { if (! query->mRefVariable) query->mRefVariable = do_GetAtom("?uri"); nsCOMPtr refResource; aRef->GetResource(getter_AddRefs(refResource)); if (! refResource) return NS_ERROR_FAILURE; // Propagate the assignments through the network TestNode* root = query->GetRoot(); if (query->IsSimple() && mSimpleRuleMemberTest) { // get the top node in the simple rule tree root = mSimpleRuleMemberTest->GetParent(); mLastRef = aRef; } #ifdef PR_LOGGING if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { nsAutoString id; aRef->GetId(id); nsAutoString rvar; query->mRefVariable->ToString(rvar); nsAutoString mvar; query->mMemberVariable->ToString(mvar); PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, ("QueryProcessor::GenerateResults using ref %s and vars [ ref: %s member: %s]", NS_ConvertUTF16toUTF8(id).get(), NS_ConvertUTF16toUTF8(rvar).get(), NS_ConvertUTF16toUTF8(mvar).get())); } #endif if (root) { // the seed is the initial instantiation, which has a single // assignment holding the reference point Instantiation seed; seed.AddAssignment(query->mRefVariable, refResource); InstantiationSet* instantiations = new InstantiationSet(); if (!instantiations) return NS_ERROR_OUT_OF_MEMORY; instantiations->Append(seed); // if the propagation caused a match, then the results will be // cached in the query, retrieved below by calling // UseCachedResults. The matching result set owns the // instantiations and will delete them when results have been // iterated over. If the propagation did not match, the // instantiations need to be deleted. bool owned = false; nsresult rv = root->Propagate(*instantiations, false, owned); if (! owned) delete instantiations; if (NS_FAILED(rv)) return rv; query->UseCachedResults(getter_AddRefs(results)); } } } if (! results) { // no results were found so create an empty set results = new nsXULTemplateResultSetRDF(this, query, nullptr); if (! results) return NS_ERROR_OUT_OF_MEMORY; } results.swap(*aResults); return NS_OK; } NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::AddBinding(nsIDOMNode* aRuleNode, nsIAtom* aVar, nsIAtom* aRef, const nsAString& aExpr) { // add a to a rule. When a result is matched, the bindings are // examined to add additional variable assignments // bindings can't be added once result generation has started, otherwise // the array sizes will get out of sync if (mGenerationStarted) return NS_ERROR_UNEXPECTED; nsCOMPtr property; nsresult rv = gRDFService->GetUnicodeResource(aExpr, getter_AddRefs(property)); if (NS_FAILED(rv)) return rv; nsRefPtr bindings = mRuleToBindingsMap.GetWeak(aRuleNode); if (!bindings) { bindings = new RDFBindingSet(); mRuleToBindingsMap.Put(aRuleNode, bindings); } return bindings->AddBinding(aVar, aRef, property); } NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::TranslateRef(nsISupports* aDatasource, const nsAString& aRefString, nsIXULTemplateResult** aRef) { // make sure the RDF service is set up nsresult rv = InitGlobals(); if (NS_FAILED(rv)) return rv; nsCOMPtr uri; gRDFService->GetUnicodeResource(aRefString, getter_AddRefs(uri)); nsXULTemplateResultRDF* refresult = new nsXULTemplateResultRDF(uri); if (! refresult) return NS_ERROR_OUT_OF_MEMORY; *aRef = refresult; NS_ADDREF(*aRef); return NS_OK; } NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::CompareResults(nsIXULTemplateResult* aLeft, nsIXULTemplateResult* aRight, nsIAtom* aVar, uint32_t aSortHints, int32_t* aResult) { NS_ENSURE_ARG_POINTER(aLeft); NS_ENSURE_ARG_POINTER(aRight); *aResult = 0; // for natural order sorting, use the index in the RDF container for the // order. If there is no container, just sort them arbitrarily. if (!aVar) { // if a result has a negative index, just sort it first int32_t leftindex = GetContainerIndexOf(aLeft); int32_t rightindex = GetContainerIndexOf(aRight); *aResult = leftindex == rightindex ? 0 : leftindex > rightindex ? 1 : -1; return NS_OK; } nsDependentAtomString sortkey(aVar); nsCOMPtr leftNode, rightNode; if (!sortkey.IsEmpty() && sortkey[0] != '?' && !StringBeginsWith(sortkey, NS_LITERAL_STRING("rdf:")) && mDB) { // if the sort key is not a template variable, it should be an RDF // predicate. Get the targets and compare those instead. nsCOMPtr predicate; nsresult rv = gRDFService->GetUnicodeResource(sortkey, getter_AddRefs(predicate)); NS_ENSURE_SUCCESS(rv, rv); // create a predicate with '?sort=true' appended. This special // predicate may be used to have a different sort value than the // displayed value sortkey.AppendLiteral("?sort=true"); nsCOMPtr sortPredicate; rv = gRDFService->GetUnicodeResource(sortkey, getter_AddRefs(sortPredicate)); NS_ENSURE_SUCCESS(rv, rv); rv = GetSortValue(aLeft, predicate, sortPredicate, getter_AddRefs(leftNode)); NS_ENSURE_SUCCESS(rv, rv); rv = GetSortValue(aRight, predicate, sortPredicate, getter_AddRefs(rightNode)); NS_ENSURE_SUCCESS(rv, rv); } else { // get the values for the sort key from the results aLeft->GetBindingObjectFor(aVar, getter_AddRefs(leftNode)); aRight->GetBindingObjectFor(aVar, getter_AddRefs(rightNode)); } { // Literals? nsCOMPtr l = do_QueryInterface(leftNode); if (l) { nsCOMPtr r = do_QueryInterface(rightNode); if (r) { const char16_t *lstr, *rstr; l->GetValueConst(&lstr); r->GetValueConst(&rstr); *aResult = XULSortServiceImpl::CompareValues( nsDependentString(lstr), nsDependentString(rstr), aSortHints); } return NS_OK; } } { // Dates? nsCOMPtr l = do_QueryInterface(leftNode); if (l) { nsCOMPtr r = do_QueryInterface(rightNode); if (r) { PRTime ldate, rdate; l->GetValue(&ldate); r->GetValue(&rdate); int64_t delta = ldate - rdate; if (delta == 0) *aResult = 0; else if (delta >= 0) *aResult = 1; else *aResult = -1; } return NS_OK; } } { // Integers? nsCOMPtr l = do_QueryInterface(leftNode); if (l) { nsCOMPtr r = do_QueryInterface(rightNode); if (r) { int32_t lval, rval; l->GetValue(&lval); r->GetValue(&rval); *aResult = lval - rval; } return NS_OK; } } nsICollation* collation = nsXULContentUtils::GetCollation(); if (collation) { // Blobs? (We can only compare these reasonably if we have a // collation object.) nsCOMPtr l = do_QueryInterface(leftNode); if (l) { nsCOMPtr r = do_QueryInterface(rightNode); if (r) { const uint8_t *lval, *rval; int32_t llen, rlen; l->GetValue(&lval); l->GetLength(&llen); r->GetValue(&rval); r->GetLength(&rlen); collation->CompareRawSortKey(lval, llen, rval, rlen, aResult); } } } // if the results are none of the above, just pretend that they are equal return NS_OK; } //---------------------------------------------------------------------- // // nsIRDFObserver interface // NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::OnAssert(nsIRDFDataSource* aDataSource, nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aTarget) { // Ignore updates if we're batching if (mUpdateBatchNest) return(NS_OK); if (! mBuilder) return NS_OK; LOG("onassert", aSource, aProperty, aTarget); Propagate(aSource, aProperty, aTarget); SynchronizeAll(aSource, aProperty, nullptr, aTarget); return NS_OK; } NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::OnUnassert(nsIRDFDataSource* aDataSource, nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aTarget) { // Ignore updates if we're batching if (mUpdateBatchNest) return NS_OK; if (! mBuilder) return NS_OK; LOG("onunassert", aSource, aProperty, aTarget); Retract(aSource, aProperty, aTarget); SynchronizeAll(aSource, aProperty, aTarget, nullptr); return NS_OK; } NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::OnChange(nsIRDFDataSource* aDataSource, nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aOldTarget, nsIRDFNode* aNewTarget) { // Ignore updates if we're batching if (mUpdateBatchNest) return NS_OK; if (! mBuilder) return NS_OK; LOG("onchange", aSource, aProperty, aNewTarget); if (aOldTarget) { // Pull any old results that were relying on aOldTarget Retract(aSource, aProperty, aOldTarget); } if (aNewTarget) { // Fire any new results that are activated by aNewTarget Propagate(aSource, aProperty, aNewTarget); } // Synchronize any of the content model that may have changed. SynchronizeAll(aSource, aProperty, aOldTarget, aNewTarget); return NS_OK; } NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::OnMove(nsIRDFDataSource* aDataSource, nsIRDFResource* aOldSource, nsIRDFResource* aNewSource, nsIRDFResource* aProperty, nsIRDFNode* aTarget) { // Ignore updates if we're batching if (mUpdateBatchNest) return NS_OK; NS_NOTYETIMPLEMENTED("write me"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::OnBeginUpdateBatch(nsIRDFDataSource* aDataSource) { mUpdateBatchNest++; return NS_OK; } NS_IMETHODIMP nsXULTemplateQueryProcessorRDF::OnEndUpdateBatch(nsIRDFDataSource* aDataSource) { NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch"); if (--mUpdateBatchNest <= 0) { mUpdateBatchNest = 0; if (mBuilder) mBuilder->Rebuild(); } return NS_OK; } nsresult nsXULTemplateQueryProcessorRDF::Propagate(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aTarget) { // When a new assertion is added to the graph, determine any new matches // that must be added to the template builder. First, iterate through all // the RDF tests ( and tests), and find the topmost test // that would be affected by the new assertion. nsresult rv; ReteNodeSet livenodes; #ifdef PR_LOGGING if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { const char* sourceStr; aSource->GetValueConst(&sourceStr); const char* propertyStr; aProperty->GetValueConst(&propertyStr); nsAutoString targetStr; nsXULContentUtils::GetTextForNode(aTarget, targetStr); PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, ("nsXULTemplateQueryProcessorRDF::Propagate: [%s] -> [%s] -> [%s]\n", sourceStr, propertyStr, NS_ConvertUTF16toUTF8(targetStr).get())); } #endif { ReteNodeSet::Iterator last = mRDFTests.Last(); for (ReteNodeSet::Iterator i = mRDFTests.First(); i != last; ++i) { nsRDFTestNode* rdftestnode = static_cast(*i); Instantiation seed; if (rdftestnode->CanPropagate(aSource, aProperty, aTarget, seed)) { rv = livenodes.Add(rdftestnode); if (NS_FAILED(rv)) return rv; } } } // Now, we'll go through each, and any that aren't dominated by // another live node will be used to propagate the assertion // through the rule network { ReteNodeSet::Iterator last = livenodes.Last(); for (ReteNodeSet::Iterator i = livenodes.First(); i != last; ++i) { nsRDFTestNode* rdftestnode = static_cast(*i); // What happens here is we create an instantiation as if we were // at the found test in the rule network. For example, if the // found test was a member test (parent => child), the parent // and child variables are assigned the values provided by the new // RDF assertion in the graph. The Constrain call is used to go // up to earlier RDF tests, filling in variables as it goes. // Constrain will eventually get up to the top node, an // nsContentTestNode, which takes the value of the reference // variable and calls the template builder to see if a result has // been generated already for the reference value. If it hasn't, // the new assertion couldn't cause a new match. If the result // exists, call Propagate to continue to the later RDF tests to // fill in the rest of the variable assignments. // Bogus, to get the seed instantiation Instantiation seed; rdftestnode->CanPropagate(aSource, aProperty, aTarget, seed); InstantiationSet* instantiations = new InstantiationSet(); if (!instantiations) return NS_ERROR_OUT_OF_MEMORY; instantiations->Append(seed); rv = rdftestnode->Constrain(*instantiations); if (NS_FAILED(rv)) { delete instantiations; return rv; } bool owned = false; if (!instantiations->Empty()) rv = rdftestnode->Propagate(*instantiations, true, owned); // owned should always be false in update mode, but check just // to be sure if (!owned) delete instantiations; if (NS_FAILED(rv)) return rv; } } return NS_OK; } nsresult nsXULTemplateQueryProcessorRDF::Retract(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aTarget) { #ifdef PR_LOGGING if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { const char* sourceStr; aSource->GetValueConst(&sourceStr); const char* propertyStr; aProperty->GetValueConst(&propertyStr); nsAutoString targetStr; nsXULContentUtils::GetTextForNode(aTarget, targetStr); PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, ("nsXULTemplateQueryProcessorRDF::Retract: [%s] -> [%s] -> [%s]\n", sourceStr, propertyStr, NS_ConvertUTF16toUTF8(targetStr).get())); } #endif // Retract any currently active rules that will no longer be matched. ReteNodeSet::ConstIterator lastnode = mRDFTests.Last(); for (ReteNodeSet::ConstIterator node = mRDFTests.First(); node != lastnode; ++node) { const nsRDFTestNode* rdftestnode = static_cast(*node); rdftestnode->Retract(aSource, aProperty, aTarget); // Now fire any newly revealed rules // XXXwaterson yo. write me. // The intent here is to handle any rules that might be // "revealed" by the removal of an assertion from the datasource. // Waterson doesn't think we support negated conditions in a rule. // Nor is he sure that this is currently useful. } return NS_OK; } nsresult nsXULTemplateQueryProcessorRDF::SynchronizeAll(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aOldTarget, nsIRDFNode* aNewTarget) { // Update each match that contains . // Get all the matches whose assignments are currently supported // by aSource and aProperty: we'll need to recompute them. ResultArray* results; if (!mBindingDependencies.Get(aSource, &results) || !mBuilder) return NS_OK; uint32_t length = results->Length(); for (uint32_t r = 0; r < length; r++) { nsXULTemplateResultRDF* result = (*results)[r]; if (result) { // synchronize the result's bindings and then update the builder // so that content can be updated if (result->SyncAssignments(aSource, aProperty, aNewTarget)) { nsITemplateRDFQuery* query = result->Query(); if (query) { nsCOMPtr querynode; query->GetQueryNode(getter_AddRefs(querynode)); mBuilder->ResultBindingChanged(result); } } } } return NS_OK; } #ifdef PR_LOGGING nsresult nsXULTemplateQueryProcessorRDF::Log(const char* aOperation, nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aTarget) { if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { nsresult rv; const char* sourceStr; rv = aSource->GetValueConst(&sourceStr); if (NS_FAILED(rv)) return rv; PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, ("xultemplate[%p] %8s [%s]--", this, aOperation, sourceStr)); const char* propertyStr; rv = aProperty->GetValueConst(&propertyStr); if (NS_FAILED(rv)) return rv; nsAutoString targetStr; rv = nsXULContentUtils::GetTextForNode(aTarget, targetStr); if (NS_FAILED(rv)) return rv; nsAutoCString targetstrC; targetstrC.AssignWithConversion(targetStr); PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, (" --[%s]-->[%s]", propertyStr, targetstrC.get())); } return NS_OK; } #endif nsresult nsXULTemplateQueryProcessorRDF::CheckContainer(nsIRDFResource* aResource, bool* aIsContainer) { NS_ENSURE_ARG_POINTER(aIsContainer); NS_ENSURE_STATE(mDB); // We have to look at all of the arcs extending out of the // resource: if any of them are that "containment" property, then // we know we'll have children. bool isContainer = false; for (nsResourceSet::ConstIterator property = mContainmentProperties.First(); property != mContainmentProperties.Last(); property++) { bool hasArc = false; mDB->HasArcOut(aResource, *property, &hasArc); if (hasArc) { // Well, it's a container... isContainer = true; break; } } // If we get here, and we're still not sure if it's a container, // then see if it's an RDF container if (! isContainer) { gRDFContainerUtils->IsContainer(mDB, aResource, &isContainer); } *aIsContainer = isContainer; return NS_OK; } nsresult nsXULTemplateQueryProcessorRDF::CheckEmpty(nsIRDFResource* aResource, bool* aIsEmpty) { NS_ENSURE_STATE(mDB); *aIsEmpty = true; for (nsResourceSet::ConstIterator property = mContainmentProperties.First(); property != mContainmentProperties.Last(); property++) { nsCOMPtr dummy; mDB->GetTarget(aResource, *property, true, getter_AddRefs(dummy)); if (dummy) { *aIsEmpty = false; break; } } if (*aIsEmpty){ return nsXULTemplateQueryProcessorRDF::gRDFContainerUtils-> IsEmpty(mDB, aResource, aIsEmpty); } return NS_OK; } nsresult nsXULTemplateQueryProcessorRDF::CheckIsSeparator(nsIRDFResource* aResource, bool* aIsSeparator) { NS_ENSURE_STATE(mDB); return mDB->HasAssertion(aResource, kRDF_type, kNC_BookmarkSeparator, true, aIsSeparator); } //---------------------------------------------------------------------- nsresult nsXULTemplateQueryProcessorRDF::ComputeContainmentProperties(nsIDOMNode* aRootNode) { // The 'containment' attribute on the root node is a // whitespace-separated list that tells us which properties we // should use to test for containment. nsresult rv; mContainmentProperties.Clear(); nsCOMPtr content = do_QueryInterface(aRootNode); nsAutoString containment; content->GetAttr(kNameSpaceID_None, nsGkAtoms::containment, containment); uint32_t len = containment.Length(); uint32_t offset = 0; while (offset < len) { while (offset < len && nsCRT::IsAsciiSpace(containment[offset])) ++offset; if (offset >= len) break; uint32_t end = offset; while (end < len && !nsCRT::IsAsciiSpace(containment[end])) ++end; nsAutoString propertyStr; containment.Mid(propertyStr, offset, end - offset); nsCOMPtr property; rv = gRDFService->GetUnicodeResource(propertyStr, getter_AddRefs(property)); if (NS_FAILED(rv)) return rv; rv = mContainmentProperties.Add(property); if (NS_FAILED(rv)) return rv; offset = end; } #define TREE_PROPERTY_HACK 1 #if defined(TREE_PROPERTY_HACK) if (! len) { // Some ever-present membership tests. mContainmentProperties.Add(nsXULContentUtils::NC_child); mContainmentProperties.Add(nsXULContentUtils::NC_Folder); } #endif return NS_OK; } nsresult nsXULTemplateQueryProcessorRDF::CompileExtendedQuery(nsRDFQuery* aQuery, nsIContent* aConditions, TestNode** aLastNode) { // Compile an extended query's children nsContentTestNode* idnode = new nsContentTestNode(this, aQuery->mRefVariable); if (! idnode) return NS_ERROR_OUT_OF_MEMORY; aQuery->SetRoot(idnode); nsresult rv = mAllTests.Add(idnode); if (NS_FAILED(rv)) { delete idnode; return rv; } TestNode* prevnode = idnode; for (nsIContent* condition = aConditions->GetFirstChild(); condition; condition = condition->GetNextSibling()) { // the condition should always be the first child if (condition->Tag() == nsGkAtoms::content) { if (condition != aConditions->GetFirstChild()) { nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_CONTENT_NOT_FIRST); continue; } // check for which indicates that matches // should only be generated for items inside content with that tag nsAutoString tagstr; condition->GetAttr(kNameSpaceID_None, nsGkAtoms::tag, tagstr); nsCOMPtr tag; if (! tagstr.IsEmpty()) { tag = do_GetAtom(tagstr); } nsCOMPtr doc = do_QueryInterface(condition->GetComposedDoc()); if (! doc) return NS_ERROR_FAILURE; idnode->SetTag(tag, doc); continue; } TestNode* testnode = nullptr; nsresult rv = CompileQueryChild(condition->Tag(), aQuery, condition, prevnode, &testnode); if (NS_FAILED(rv)) return rv; if (testnode) { rv = prevnode->AddChild(testnode); if (NS_FAILED(rv)) return rv; prevnode = testnode; } } *aLastNode = prevnode; return NS_OK; } nsresult nsXULTemplateQueryProcessorRDF::CompileQueryChild(nsIAtom* aTag, nsRDFQuery* aQuery, nsIContent* aCondition, TestNode* aParentNode, TestNode** aResult) { nsresult rv; if (aTag == nsGkAtoms::triple) { rv = CompileTripleCondition(aQuery, aCondition, aParentNode, aResult); } else if (aTag == nsGkAtoms::member) { rv = CompileMemberCondition(aQuery, aCondition, aParentNode, aResult); } else { #ifdef PR_LOGGING nsAutoString tagstr; aTag->ToString(tagstr); nsAutoCString tagstrC; tagstrC.AssignWithConversion(tagstr); PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS, ("xultemplate[%p] unrecognized condition test <%s>", this, tagstrC.get())); #endif rv = NS_OK; } return rv; } nsresult nsXULTemplateQueryProcessorRDF::ParseLiteral(const nsString& aParseType, const nsString& aValue, nsIRDFNode** aResult) { nsresult rv = NS_OK; *aResult = nullptr; if (aParseType.EqualsLiteral(PARSE_TYPE_INTEGER)) { nsCOMPtr intLiteral; nsresult errorCode; int32_t intValue = aValue.ToInteger(&errorCode); if (NS_FAILED(errorCode)) return NS_ERROR_FAILURE; rv = gRDFService->GetIntLiteral(intValue, getter_AddRefs(intLiteral)); if (NS_FAILED(rv)) return rv; rv = CallQueryInterface(intLiteral, aResult); } else { nsCOMPtr literal; rv = gRDFService->GetLiteral(aValue.get(), getter_AddRefs(literal)); if (NS_FAILED(rv)) return rv; rv = CallQueryInterface(literal, aResult); } return rv; } nsresult nsXULTemplateQueryProcessorRDF::CompileTripleCondition(nsRDFQuery* aQuery, nsIContent* aCondition, TestNode* aParentNode, TestNode** aResult) { // Compile a condition, which must be of the form: // // // // XXXwaterson Some day it would be cool to allow the 'predicate' // to be bound to a variable. // subject nsAutoString subject; aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject); nsCOMPtr svar; nsCOMPtr sres; if (subject.IsEmpty()) { nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_BAD_SUBJECT); return NS_OK; } if (subject[0] == char16_t('?')) svar = do_GetAtom(subject); else gRDFService->GetUnicodeResource(subject, getter_AddRefs(sres)); // predicate nsAutoString predicate; aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::predicate, predicate); nsCOMPtr pres; if (predicate.IsEmpty() || predicate[0] == char16_t('?')) { nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_BAD_PREDICATE); return NS_OK; } gRDFService->GetUnicodeResource(predicate, getter_AddRefs(pres)); // object nsAutoString object; aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::object, object); nsCOMPtr ovar; nsCOMPtr onode; if (object.IsEmpty()) { nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_BAD_OBJECT); return NS_OK; } if (object[0] == char16_t('?')) { ovar = do_GetAtom(object); } else if (object.FindChar(':') != -1) { // XXXwaterson evil. // treat as resource nsCOMPtr resource; gRDFService->GetUnicodeResource(object, getter_AddRefs(resource)); onode = do_QueryInterface(resource); } else { nsAutoString parseType; aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::parsetype, parseType); nsresult rv = ParseLiteral(parseType, object, getter_AddRefs(onode)); if (NS_FAILED(rv)) return rv; } nsRDFPropertyTestNode* testnode = nullptr; if (svar && ovar) { testnode = new nsRDFPropertyTestNode(aParentNode, this, svar, pres, ovar); } else if (svar) { testnode = new nsRDFPropertyTestNode(aParentNode, this, svar, pres, onode); } else if (ovar) { testnode = new nsRDFPropertyTestNode(aParentNode, this, sres, pres, ovar); } else { nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_NO_VAR); return NS_OK; } if (! testnode) return NS_ERROR_OUT_OF_MEMORY; // add testnode to mAllTests first. If adding to mRDFTests fails, just // leave it in the list so that it can be deleted later. nsresult rv = mAllTests.Add(testnode); if (NS_FAILED(rv)) { delete testnode; return rv; } rv = mRDFTests.Add(testnode); if (NS_FAILED(rv)) return rv; *aResult = testnode; return NS_OK; } nsresult nsXULTemplateQueryProcessorRDF::CompileMemberCondition(nsRDFQuery* aQuery, nsIContent* aCondition, TestNode* aParentNode, TestNode** aResult) { // Compile a condition, which must be of the form: // // // // container nsAutoString container; aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::container, container); if (!container.IsEmpty() && container[0] != char16_t('?')) { nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_MEMBER_NOCONTAINERVAR); return NS_OK; } nsCOMPtr containervar = do_GetAtom(container); // child nsAutoString child; aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::child, child); if (!child.IsEmpty() && child[0] != char16_t('?')) { nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_MEMBER_NOCHILDVAR); return NS_OK; } nsCOMPtr childvar = do_GetAtom(child); TestNode* testnode = new nsRDFConMemberTestNode(aParentNode, this, containervar, childvar); if (! testnode) return NS_ERROR_OUT_OF_MEMORY; // add testnode to mAllTests first. If adding to mRDFTests fails, just // leave it in the list so that it can be deleted later. nsresult rv = mAllTests.Add(testnode); if (NS_FAILED(rv)) { delete testnode; return rv; } rv = mRDFTests.Add(testnode); if (NS_FAILED(rv)) return rv; *aResult = testnode; return NS_OK; } nsresult nsXULTemplateQueryProcessorRDF::AddDefaultSimpleRules(nsRDFQuery* aQuery, TestNode** aChildNode) { // XXXndeakin should check for tag in query processor instead of builder? nsContentTestNode* idnode = new nsContentTestNode(this, aQuery->mRefVariable); if (! idnode) return NS_ERROR_OUT_OF_MEMORY; // Create (?container ^member ?member) nsRDFConMemberTestNode* membernode = new nsRDFConMemberTestNode(idnode, this, aQuery->mRefVariable, aQuery->mMemberVariable); if (! membernode) { delete idnode; return NS_ERROR_OUT_OF_MEMORY; } // add nodes to mAllTests first. If later calls fail, just leave them in // the list so that they can be deleted later. nsresult rv = mAllTests.Add(idnode); if (NS_FAILED(rv)) { delete idnode; delete membernode; return rv; } rv = mAllTests.Add(membernode); if (NS_FAILED(rv)) { delete membernode; return rv; } rv = mRDFTests.Add(membernode); if (NS_FAILED(rv)) return rv; rv = idnode->AddChild(membernode); if (NS_FAILED(rv)) return rv; mSimpleRuleMemberTest = membernode; *aChildNode = membernode; return NS_OK; } nsresult nsXULTemplateQueryProcessorRDF::CompileSimpleQuery(nsRDFQuery* aQuery, nsIContent* aQueryElement, TestNode** aLastNode) { // Compile a "simple" (or old-school style)