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