Back to index

lightning-sunbird  0.9+nobinonly
nsXULTemplateBuilder.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Robert Churchill <rjc@netscape.com>
00024  *   David Hyatt <hyatt@netscape.com>
00025  *   Chris Waterson <waterson@netscape.com>
00026  *   Pierre Phaneuf <pp@ludusdesign.com>
00027  *   Joe Hewitt <hewitt@netscape.com>
00028  *
00029  * Alternatively, the contents of this file may be used under the terms of
00030  * either of the GNU General Public License Version 2 or later (the "GPL"),
00031  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00032  * in which case the provisions of the GPL or the LGPL are applicable instead
00033  * of those above. If you wish to allow use of your version of this file only
00034  * under the terms of either the GPL or the LGPL, and not to allow others to
00035  * use your version of this file under the terms of the MPL, indicate your
00036  * decision by deleting the provisions above and replace them with the notice
00037  * and other provisions required by the GPL or the LGPL. If you do not delete
00038  * the provisions above, a recipient may use your version of this file under
00039  * the terms of any one of the MPL, the GPL or the LGPL.
00040  *
00041  * ***** END LICENSE BLOCK ***** */
00042 
00043 /*
00044 
00045   Builds content from an RDF graph using the XUL <template> tag.
00046 
00047   TO DO
00048 
00049   . Fix ContentTagTest's location in the network construction
00050 
00051   . We're a bit schizophrenic about whether we want to use
00052     nsXULTemplateBuilder's members to determine the container & member
00053     variables, or if we want to use the mRule member of a nsTemplateMatch. Which
00054     is right? Is there redundancy here?
00055 
00056   . MatchSet has extra information about the "best match" and "last
00057     match" that really seems like it should be a part of
00058     ConflictSet::ClusterEntry.
00059 
00060   To turn on logging for this module, set:
00061 
00062     NSPR_LOG_MODULES nsXULTemplateBuilder:5
00063 
00064  */
00065 
00066 #include "nsCOMPtr.h"
00067 #include "nsCRT.h"
00068 #include "nsFixedSizeAllocator.h"
00069 #include "nsIContent.h"
00070 #include "nsIDOMElement.h"
00071 #include "nsIDOMNode.h"
00072 #include "nsIDOMDocument.h"
00073 #include "nsIDOMXULElement.h"
00074 #include "nsIDocument.h"
00075 #include "nsIBindingManager.h"
00076 #include "nsIDOMNodeList.h"
00077 #include "nsINameSpaceManager.h"
00078 #include "nsIRDFCompositeDataSource.h"
00079 #include "nsIRDFInferDataSource.h"
00080 #include "nsIRDFContainerUtils.h" 
00081 #include "nsIXULDocument.h"
00082 #include "nsIXULTemplateBuilder.h"
00083 #include "nsIXULBuilderListener.h"
00084 #include "nsIRDFNode.h"
00085 #include "nsIRDFObserver.h"
00086 #include "nsIRDFRemoteDataSource.h"
00087 #include "nsIRDFService.h"
00088 #include "nsIScriptGlobalObject.h"
00089 #include "nsIServiceManager.h"
00090 #include "nsISimpleEnumerator.h"
00091 #include "nsISupportsArray.h"
00092 #include "nsITimer.h"
00093 #include "nsIURL.h"
00094 #include "nsIXPConnect.h"
00095 #include "nsIXULSortService.h"
00096 #include "nsContentCID.h"
00097 #include "nsRDFCID.h"
00098 #include "nsXULContentUtils.h"
00099 #include "nsRDFSort.h"
00100 #include "nsRuleNetwork.h"
00101 #include "nsString.h"
00102 #include "nsVoidArray.h"
00103 #include "nsXPIDLString.h"
00104 #include "nsXULAtoms.h"
00105 #include "nsXULElement.h"
00106 #include "jsapi.h"
00107 #include "prlog.h"
00108 #include "rdf.h"
00109 #include "pldhash.h"
00110 #include "plhash.h"
00111 
00112 #include "nsClusterKeySet.h"
00113 #include "nsConflictSet.h"
00114 #include "nsInstantiationNode.h"
00115 #include "nsNetUtil.h"
00116 #include "nsRDFConInstanceTestNode.h"
00117 #include "nsRDFConMemberTestNode.h"
00118 #include "nsRDFPropertyTestNode.h"
00119 #include "nsRDFTestNode.h"
00120 #include "nsResourceSet.h"
00121 #include "nsTemplateRule.h"
00122 #include "nsXULTemplateBuilder.h"
00123 
00124 //----------------------------------------------------------------------
00125 
00126 static NS_DEFINE_CID(kRDFContainerUtilsCID,      NS_RDFCONTAINERUTILS_CID);
00127 static NS_DEFINE_CID(kRDFServiceCID,             NS_RDFSERVICE_CID);
00128 
00129 #define PARSE_TYPE_INTEGER  "Integer"
00130 
00131 //----------------------------------------------------------------------
00132 //
00133 // nsXULTemplateBuilder
00134 //
00135 
00136 nsrefcnt                  nsXULTemplateBuilder::gRefCnt = 0;
00137 nsIRDFService*            nsXULTemplateBuilder::gRDFService;
00138 nsIRDFContainerUtils*     nsXULTemplateBuilder::gRDFContainerUtils;
00139 nsIScriptSecurityManager* nsXULTemplateBuilder::gScriptSecurityManager;
00140 nsIPrincipal*             nsXULTemplateBuilder::gSystemPrincipal;
00141 
00142 #ifdef PR_LOGGING
00143 PRLogModuleInfo* gXULTemplateLog;
00144 #endif
00145 
00146 //----------------------------------------------------------------------
00147 //
00148 // nsXULTempalteBuilder methods
00149 //
00150 
00151 nsXULTemplateBuilder::nsXULTemplateBuilder(void)
00152     : mDB(nsnull),
00153       mCompDB(nsnull),
00154       mRoot(nsnull),
00155       mUpdateBatchNest(0),
00156       mRulesCompiled(PR_FALSE),
00157       mFlags(0),
00158       mTop(nsnull)
00159 {
00160 }
00161 
00162 nsXULTemplateBuilder::~nsXULTemplateBuilder(void)
00163 {
00164     if (--gRefCnt == 0) {
00165         NS_IF_RELEASE(gRDFService);
00166         NS_IF_RELEASE(gRDFContainerUtils);
00167         NS_IF_RELEASE(gSystemPrincipal);
00168         NS_IF_RELEASE(gScriptSecurityManager);
00169     }
00170 }
00171 
00172 
00173 nsresult
00174 nsXULTemplateBuilder::Init()
00175 {
00176     if (gRefCnt++ == 0) {
00177         nsresult rv;
00178 
00179         // Initialize the global shared reference to the service
00180         // manager and get some shared resource objects.
00181         rv = CallGetService(kRDFServiceCID, &gRDFService);
00182         if (NS_FAILED(rv))
00183             return rv;
00184 
00185         rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils);
00186         if (NS_FAILED(rv))
00187             return rv;
00188 
00189         rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID,
00190                             &gScriptSecurityManager);
00191         if (NS_FAILED(rv))
00192             return rv;
00193 
00194         rv = gScriptSecurityManager->GetSystemPrincipal(&gSystemPrincipal);
00195         if (NS_FAILED(rv))
00196             return rv;
00197     }
00198 
00199 #ifdef PR_LOGGING
00200     if (! gXULTemplateLog)
00201         gXULTemplateLog = PR_NewLogModule("nsXULTemplateBuilder");
00202 #endif
00203 
00204     return NS_OK;
00205 }
00206 
00207 NS_IMPL_ADDREF(nsXULTemplateBuilder)
00208 NS_IMPL_RELEASE(nsXULTemplateBuilder)
00209 
00210 NS_INTERFACE_MAP_BEGIN(nsXULTemplateBuilder)
00211   NS_INTERFACE_MAP_ENTRY(nsIXULTemplateBuilder)
00212   NS_INTERFACE_MAP_ENTRY(nsIRDFObserver)
00213   NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
00214   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULTemplateBuilder)
00215   NS_INTERFACE_MAP_ENTRY_DOM_CLASSINFO(XULTemplateBuilder)
00216 NS_INTERFACE_MAP_END
00217 
00218 //----------------------------------------------------------------------
00219 //
00220 // nsIXULTemplateBuilder methods
00221 //
00222 
00223 NS_IMETHODIMP
00224 nsXULTemplateBuilder::GetRoot(nsIDOMElement** aResult)
00225 {
00226     return CallQueryInterface(mRoot, aResult);
00227 }
00228 
00229 NS_IMETHODIMP
00230 nsXULTemplateBuilder::GetDatabase(nsIRDFCompositeDataSource** aResult)
00231 {
00232     NS_IF_ADDREF(*aResult = mCompDB.get());
00233     return NS_OK;
00234 }
00235 
00236 NS_IMETHODIMP
00237 nsXULTemplateBuilder::Rebuild()
00238 {
00239     PRInt32 i;
00240 
00241     for (i = mListeners.Count() - 1; i >= 0; --i) {
00242         mListeners[i]->WillRebuild(this);
00243     }
00244 
00245     nsresult rv = RebuildAll();
00246 
00247     for (i = mListeners.Count() - 1; i >= 0; --i) {
00248         mListeners[i]->DidRebuild(this);
00249     }
00250 
00251     return rv;
00252 }
00253 
00254 NS_IMETHODIMP
00255 nsXULTemplateBuilder::Refresh()
00256 {
00257     nsresult rv;
00258 
00259     nsCOMPtr<nsISimpleEnumerator> dslist;
00260     rv = mCompDB->GetDataSources(getter_AddRefs(dslist));
00261     NS_ENSURE_SUCCESS(rv, rv);
00262 
00263     PRBool hasMore;
00264     nsCOMPtr<nsISupports> next;
00265     nsCOMPtr<nsIRDFRemoteDataSource> rds;
00266 
00267     while(NS_SUCCEEDED(dslist->HasMoreElements(&hasMore)) && hasMore) {
00268         dslist->GetNext(getter_AddRefs(next));
00269         if (next && (rds = do_QueryInterface(next))) {
00270             rds->Refresh(PR_FALSE);
00271         }
00272     }
00273 
00274     // XXXbsmedberg: it would be kinda nice to install an async nsIRDFXMLSink
00275     // observer and call rebuild() once the load is complete. See bug 254600.
00276 
00277     return NS_OK;
00278 }   
00279 
00280 NS_IMETHODIMP
00281 nsXULTemplateBuilder::Init(nsIContent* aElement)
00282 {
00283     NS_PRECONDITION(aElement, "null ptr");
00284     mRoot = aElement;
00285 
00286     nsCOMPtr<nsIDocument> doc = mRoot->GetDocument();
00287     NS_ASSERTION(doc, "element has no document");
00288     if (! doc)
00289         return NS_ERROR_UNEXPECTED;
00290 
00291     nsresult rv = LoadDataSources(doc);
00292 
00293     if (NS_SUCCEEDED(rv)) {
00294         // Add ourselves as a document observer
00295         doc->AddObserver(this);
00296     }
00297 
00298     return rv;
00299 }
00300 
00301 NS_IMETHODIMP
00302 nsXULTemplateBuilder::CreateContents(nsIContent* aElement)
00303 {
00304     return NS_OK;
00305 }
00306 
00307 NS_IMETHODIMP
00308 nsXULTemplateBuilder::AddListener(nsIXULBuilderListener* aListener)
00309 {
00310     NS_ENSURE_ARG(aListener);
00311 
00312     mListeners.AppendObject(aListener);
00313 
00314     return NS_OK;
00315 }
00316 
00317 NS_IMETHODIMP
00318 nsXULTemplateBuilder::RemoveListener(nsIXULBuilderListener* aListener)
00319 {
00320     NS_ENSURE_ARG(aListener);
00321 
00322     mListeners.RemoveObject(aListener);
00323 
00324     return NS_OK;
00325 }
00326 
00327 //----------------------------------------------------------------------
00328 //
00329 // nsIDocumentOberver interface
00330 //
00331 
00332 void
00333 nsXULTemplateBuilder::AttributeChanged(nsIDocument *aDocument,
00334                                        nsIContent*  aContent,
00335                                        PRInt32      aNameSpaceID,
00336                                        nsIAtom*     aAttribute,
00337                                        PRInt32      aModType)
00338 {
00339     if (aContent == mRoot) {
00340         // Check for a change to the 'ref' attribute on an atom, in which
00341         // case we may need to nuke and rebuild the entire content model
00342         // beneath the element.
00343         if (aAttribute == nsXULAtoms::ref)
00344             Rebuild();
00345 
00346         // Check for a change to the 'datasources' attribute. If so, setup
00347         // mDB by parsing the vew value and rebuild.
00348         else if (aAttribute == nsXULAtoms::datasources) {
00349             LoadDataSources(aDocument);
00350             Rebuild();
00351         }
00352     }        
00353 }
00354 
00355 void
00356 nsXULTemplateBuilder::DocumentWillBeDestroyed(nsIDocument *aDocument)
00357 {
00358     // The call to RemoveObserver could release the last reference to
00359     // |this|, so hold another reference.
00360     nsRefPtr<nsXULTemplateBuilder> kungFuDeathGrip(this);
00361 
00362     // Break circular references
00363     if (mDB) {
00364         mDB->RemoveObserver(this);
00365         mDB = nsnull;
00366         mCompDB = nsnull;
00367     }
00368 
00369     mRoot = nsnull;
00370 }
00371 
00372 
00373 //----------------------------------------------------------------------
00374 //
00375 // nsIRDFObserver interface
00376 //
00377 
00378 nsresult
00379 nsXULTemplateBuilder::Propagate(nsIRDFResource* aSource,
00380                                 nsIRDFResource* aProperty,
00381                                 nsIRDFNode* aTarget,
00382                                 nsClusterKeySet& aNewKeys)
00383 {
00384     // Find the "dominating" tests that could be used to propagate the
00385     // assertion we've just received. (Test A "dominates" test B if A
00386     // is an ancestor of B in the rule network).
00387     nsresult rv;
00388 
00389     // First, we'll go through and find all of the test nodes that can
00390     // propagate the assertion.
00391     ReteNodeSet livenodes;
00392 
00393     {
00394         ReteNodeSet::Iterator last = mRDFTests.Last();
00395         for (ReteNodeSet::Iterator i = mRDFTests.First(); i != last; ++i) {
00396             nsRDFTestNode* rdftestnode = NS_STATIC_CAST(nsRDFTestNode*, *i);
00397 
00398             Instantiation seed;
00399             if (rdftestnode->CanPropagate(aSource, aProperty, aTarget, seed))
00400                 livenodes.Add(rdftestnode);
00401         }
00402     }
00403 
00404     // Now, we'll go through each, and any that aren't dominated by
00405     // another live node will be used to propagate the assertion
00406     // through the rule network
00407     {
00408         ReteNodeSet::Iterator last = livenodes.Last();
00409         for (ReteNodeSet::Iterator i = livenodes.First(); i != last; ++i) {
00410             nsRDFTestNode* rdftestnode = NS_STATIC_CAST(nsRDFTestNode*, *i);
00411 
00412             PRBool isdominated = PR_FALSE;
00413 
00414             for (ReteNodeSet::ConstIterator j = livenodes.First(); j != last; ++j) {
00415                 // we can't be dominated by ourself
00416                 if (j == i)
00417                     continue;
00418 
00419                 if (rdftestnode->HasAncestor(*j)) {
00420                     isdominated = PR_TRUE;
00421                     break;
00422                 }
00423             }
00424 
00425             if (! isdominated) {
00426                 // Bogus, to get the seed instantiation
00427                 Instantiation seed;
00428                 rdftestnode->CanPropagate(aSource, aProperty, aTarget, seed);
00429 
00430                 InstantiationSet instantiations;
00431                 instantiations.Append(seed);
00432 
00433                 rv = rdftestnode->Constrain(instantiations, &mConflictSet);
00434                 if (NS_FAILED(rv)) return rv;
00435 
00436                 if (! instantiations.Empty()) {
00437                     rv = rdftestnode->Propagate(instantiations, &aNewKeys);
00438                     if (NS_FAILED(rv)) return rv;
00439                 }
00440             }
00441         }
00442     }
00443 
00444     return NS_OK;
00445 }
00446 
00447 
00448 nsresult
00449 nsXULTemplateBuilder::FireNewlyMatchedRules(const nsClusterKeySet& aNewKeys)
00450 {
00451     // Iterate through newly added keys to determine which rules fired.
00452     //
00453     // XXXwaterson Unfortunately, this could also lead to retractions;
00454     // e.g., (contaner ?a ^empty false) could become "unmatched". How
00455     // to track those?
00456     nsClusterKeySet::ConstIterator last = aNewKeys.Last();
00457     for (nsClusterKeySet::ConstIterator key = aNewKeys.First(); key != last; ++key) {
00458         nsConflictSet::MatchCluster* matches =
00459             mConflictSet.GetMatchesForClusterKey(*key);
00460 
00461         NS_ASSERTION(matches != nsnull, "no matched rules for new key");
00462         if (! matches)
00463             continue;
00464 
00465         nsTemplateMatch* bestmatch =
00466             mConflictSet.GetMatchWithHighestPriority(matches);
00467 
00468         NS_ASSERTION(bestmatch != nsnull, "no matches in match set");
00469         if (! bestmatch)
00470             continue;
00471 
00472         // If the new "bestmatch" is different from the last match,
00473         // then we need to yank some content out and rebuild it.
00474         const nsTemplateMatch* lastmatch = matches->mLastMatch;
00475         if (bestmatch != lastmatch) {
00476             ReplaceMatch(VALUE_TO_IRDFRESOURCE(key->mMemberValue), lastmatch, bestmatch);
00477 
00478             // Remember the best match as the new "last" match
00479             matches->mLastMatch = bestmatch;
00480         }
00481     }
00482 
00483     return NS_OK;
00484 }
00485 
00486 
00487 NS_IMETHODIMP
00488 nsXULTemplateBuilder::OnAssert(nsIRDFDataSource* aDataSource,
00489                                nsIRDFResource* aSource,
00490                                nsIRDFResource* aProperty,
00491                                nsIRDFNode* aTarget)
00492 {
00493     // Ignore updates if we're batching
00494     if (mUpdateBatchNest)
00495         return(NS_OK);
00496 
00497     // Ignore re-entrant builds for content that is currently in our
00498     // activation stack.
00499     if (IsActivated(aSource))
00500         return NS_OK;
00501 
00502        if (mCache)
00503         mCache->Assert(aSource, aProperty, aTarget, PR_TRUE /* XXX should be value passed in */);
00504 
00505     LOG("onassert", aSource, aProperty, aTarget);
00506 
00507     nsClusterKeySet newkeys;
00508     Propagate(aSource, aProperty, aTarget, newkeys);
00509     FireNewlyMatchedRules(newkeys);
00510     SynchronizeAll(aSource, aProperty, nsnull, aTarget);
00511     return NS_OK;
00512 }
00513 
00514 
00515 nsresult
00516 nsXULTemplateBuilder::Retract(nsIRDFResource* aSource,
00517                               nsIRDFResource* aProperty,
00518                               nsIRDFNode* aTarget)
00519 {
00520     // Retract any currently active rules that will no longer be
00521     // matched.
00522     ReteNodeSet::ConstIterator lastnode = mRDFTests.Last();
00523     for (ReteNodeSet::ConstIterator node = mRDFTests.First(); node != lastnode; ++node) {
00524         const nsRDFTestNode* rdftestnode = NS_STATIC_CAST(const nsRDFTestNode*, *node);
00525 
00526         nsTemplateMatchSet firings(mConflictSet.GetPool());
00527         nsTemplateMatchSet retractions(mConflictSet.GetPool());
00528         rdftestnode->Retract(aSource, aProperty, aTarget, firings, retractions);
00529 
00530         {
00531             nsTemplateMatchSet::ConstIterator last = retractions.Last();
00532             for (nsTemplateMatchSet::ConstIterator match = retractions.First(); match != last; ++match) {
00533                 Value memberval;
00534                 match->mAssignments.GetAssignmentFor(match->mRule->GetMemberVariable(), &memberval);
00535 
00536                 ReplaceMatch(VALUE_TO_IRDFRESOURCE(memberval), match.operator->(), nsnull);
00537             }
00538         }
00539 #if 0
00540         // Now fire any newly revealed rules
00541         {
00542             nsTemplateMatchSet::ConstIterator last = firings.Last();
00543             for (nsTemplateMatchSet::ConstIterator match = firings.First(); match != last; ++match) {
00544                 // XXXwaterson yo. write me.
00545                 // The intent here is to handle any rules that might be
00546                 // "revealed" by the removal of an assertion from the datasource.
00547                 // Waterson doesn't think we support negated conditions in a rule.
00548                 // Nor is he sure that this is currently useful.
00549             }
00550         }
00551 #endif
00552     }
00553 
00554     return NS_OK;
00555 }
00556 
00557 NS_IMETHODIMP
00558 nsXULTemplateBuilder::OnUnassert(nsIRDFDataSource* aDataSource,
00559                                  nsIRDFResource* aSource,
00560                                  nsIRDFResource* aProperty,
00561                                  nsIRDFNode* aTarget)
00562 {
00563     // Ignore updates if we're batching
00564     if (mUpdateBatchNest)
00565         return NS_OK;
00566 
00567     // Ignore re-entrant builds for content that is currently in our
00568     // activation stack.
00569     if (IsActivated(aSource))
00570         return NS_OK;
00571 
00572        if (mCache)
00573               mCache->Unassert(aSource, aProperty, aTarget);
00574 
00575     LOG("onunassert", aSource, aProperty, aTarget);
00576 
00577     Retract(aSource, aProperty, aTarget);
00578     SynchronizeAll(aSource, aProperty, aTarget, nsnull);
00579     return NS_OK;
00580 }
00581 
00582 
00583 NS_IMETHODIMP
00584 nsXULTemplateBuilder::OnChange(nsIRDFDataSource* aDataSource,
00585                                nsIRDFResource* aSource,
00586                                nsIRDFResource* aProperty,
00587                                nsIRDFNode* aOldTarget,
00588                                nsIRDFNode* aNewTarget)
00589 {
00590     // Ignore updates if we're batching
00591     if (mUpdateBatchNest)
00592         return NS_OK;
00593 
00594     // Ignore re-entrant builds for content that is currently in our
00595     // activation stack.
00596     if (IsActivated(aSource))
00597         return NS_OK;
00598 
00599        if (mCache) {
00600               if (aOldTarget)
00601                      // XXX fix this: in-memory DS doesn't like a null oldTarget
00602                      mCache->Change(aSource, aProperty, aOldTarget, aNewTarget);
00603               else
00604                      // XXX should get tv via observer interface
00605                      mCache->Assert(aSource, aProperty, aNewTarget, PR_TRUE);
00606        }
00607 
00608     LOG("onchange", aSource, aProperty, aNewTarget);
00609 
00610     if (aOldTarget) {
00611         // Pull any old rules that were relying on aOldTarget
00612         Retract(aSource, aProperty, aOldTarget);
00613     }
00614 
00615     if (aNewTarget) {
00616         // Fire any new rules that are activated by aNewTarget
00617         nsClusterKeySet newkeys;
00618         Propagate(aSource, aProperty, aNewTarget, newkeys);
00619         FireNewlyMatchedRules(newkeys);
00620     }
00621 
00622     // Synchronize any of the content model that may have changed.
00623     SynchronizeAll(aSource, aProperty, aOldTarget, aNewTarget);
00624     return NS_OK;
00625 }
00626 
00627 
00628 NS_IMETHODIMP
00629 nsXULTemplateBuilder::OnMove(nsIRDFDataSource* aDataSource,
00630                              nsIRDFResource* aOldSource,
00631                              nsIRDFResource* aNewSource,
00632                              nsIRDFResource* aProperty,
00633                              nsIRDFNode* aTarget)
00634 {
00635     // Ignore updates if we're batching
00636     if (mUpdateBatchNest)
00637         return NS_OK;
00638 
00639        if (mCache)
00640               mCache->Move(aOldSource, aNewSource, aProperty, aTarget);
00641 
00642     NS_NOTYETIMPLEMENTED("write me");
00643     return NS_ERROR_NOT_IMPLEMENTED;
00644 }
00645 
00646 
00647 NS_IMETHODIMP
00648 nsXULTemplateBuilder::OnBeginUpdateBatch(nsIRDFDataSource* aDataSource)
00649 {
00650     mUpdateBatchNest++;
00651     return NS_OK;
00652 }
00653 
00654 
00655 NS_IMETHODIMP
00656 nsXULTemplateBuilder::OnEndUpdateBatch(nsIRDFDataSource* aDataSource)
00657 {
00658     NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch");
00659     if (--mUpdateBatchNest == 0) {
00660         Rebuild();
00661     }
00662 
00663     return NS_OK;
00664 }
00665 
00666 
00667 //----------------------------------------------------------------------
00668 //
00669 // Implementation methods
00670 //
00671 
00672 nsresult
00673 nsXULTemplateBuilder::LoadDataSources(nsIDocument* doc)
00674 {
00675     NS_PRECONDITION(mRoot != nsnull, "not initialized");
00676 
00677     nsresult rv;
00678 
00679     // flush (delete) the cache when re-rerooting the generated content
00680     if (mCache)
00681        mCache = nsnull;
00682 
00683     if (mDB) {
00684         mDB->RemoveObserver(this);
00685 
00686         // we'll set it again later, after we create a new composite ds
00687         mDB = nsnull;
00688     }
00689 
00690     // create a database for the builder
00691     mCompDB = do_CreateInstance(NS_RDF_DATASOURCE_CONTRACTID_PREFIX "composite-datasource");
00692 
00693     if (! mCompDB) {
00694         NS_ERROR("unable to construct new composite data source");
00695         return NS_ERROR_UNEXPECTED;
00696     }
00697 
00698        // check for magical attributes. XXX move to ``flags''?
00699        nsAutoString coalesce;
00700        mRoot->GetAttr(kNameSpaceID_None, nsXULAtoms::coalesceduplicatearcs, coalesce);
00701     if (coalesce.EqualsLiteral("false"))
00702               mCompDB->SetCoalesceDuplicateArcs(PR_FALSE);
00703 
00704     nsAutoString allowneg;
00705     mRoot->GetAttr(kNameSpaceID_None, nsXULAtoms::allownegativeassertions, allowneg);
00706     if (allowneg.EqualsLiteral("false"))
00707               mCompDB->SetAllowNegativeAssertions(PR_FALSE);
00708 
00709     // Grab the doc's principal...
00710     nsIPrincipal *docPrincipal = doc->GetPrincipal();
00711     if (!docPrincipal)
00712         return NS_ERROR_FAILURE;
00713 
00714     PRBool isTrusted = PR_FALSE;
00715     rv = IsSystemPrincipal(docPrincipal, &isTrusted);
00716     if (NS_FAILED(rv)) return rv;
00717 
00718     if (isTrusted) {
00719         // If we're a privileged (e.g., chrome) document, then add the
00720         // local store as the first data source in the db. Note that
00721         // we _might_ not be able to get a local store if we haven't
00722         // got a profile to read from yet.
00723         nsCOMPtr<nsIRDFDataSource> localstore;
00724         rv = gRDFService->GetDataSource("rdf:local-store", getter_AddRefs(localstore));
00725         if (NS_SUCCEEDED(rv)) {
00726             rv = mCompDB->AddDataSource(localstore);
00727             NS_ASSERTION(NS_SUCCEEDED(rv), "unable to add local store to db");
00728             if (NS_FAILED(rv)) return rv;
00729         }
00730     }
00731 
00732     // Parse datasources: they are assumed to be a whitespace
00733     // separated list of URIs; e.g.,
00734     //
00735     //     rdf:bookmarks rdf:history http://foo.bar.com/blah.cgi?baz=9
00736     //
00737     nsIURI *docurl = doc->GetDocumentURI();
00738 
00739     nsAutoString datasources;
00740     mRoot->GetAttr(kNameSpaceID_None, nsXULAtoms::datasources, datasources);
00741 
00742     PRUint32 first = 0;
00743 
00744     while(1) {
00745         while (first < datasources.Length() && nsCRT::IsAsciiSpace(datasources.CharAt(first)))
00746             ++first;
00747 
00748         if (first >= datasources.Length())
00749             break;
00750 
00751         PRUint32 last = first;
00752         while (last < datasources.Length() && !nsCRT::IsAsciiSpace(datasources.CharAt(last)))
00753             ++last;
00754 
00755         nsAutoString uriStr;
00756         datasources.Mid(uriStr, first, last - first);
00757         first = last + 1;
00758 
00759         // A special 'dummy' datasource
00760         if (uriStr.EqualsLiteral("rdf:null"))
00761             continue;
00762 
00763         // N.B. that `failure' (e.g., because it's an unknown
00764         // protocol) leaves uriStr unaltered.
00765         NS_MakeAbsoluteURI(uriStr, uriStr, docurl);
00766 
00767         if (!isTrusted) {
00768             // Our document is untrusted, so check to see if we can
00769             // load the datasource that they've asked for.
00770             nsCOMPtr<nsIURI> uri;
00771             rv = NS_NewURI(getter_AddRefs(uri), uriStr);
00772             if (NS_FAILED(rv) || !uri)
00773                 continue; // Necko will barf if our URI is weird
00774 
00775             nsCOMPtr<nsIPrincipal> principal;
00776             rv = gScriptSecurityManager->GetCodebasePrincipal(uri, getter_AddRefs(principal));
00777             NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get codebase principal");
00778             if (NS_FAILED(rv)) return rv;
00779 
00780             PRBool same;
00781             rv = docPrincipal->Equals(principal, &same);
00782             NS_ASSERTION(NS_SUCCEEDED(rv), "unable to test same origin");
00783             if (NS_FAILED(rv)) return rv;
00784 
00785             if (! same)
00786                 continue;
00787 
00788             // If we get here, we've run the gauntlet, and the
00789             // datasource's URI has the same origin as our
00790             // document. Let it load!
00791         }
00792 
00793         nsCOMPtr<nsIRDFDataSource> ds;
00794         nsCAutoString uristrC;
00795         uristrC.AssignWithConversion(uriStr);
00796 
00797         rv = gRDFService->GetDataSource(uristrC.get(), getter_AddRefs(ds));
00798 
00799         if (NS_FAILED(rv)) {
00800             // This is only a warning because the data source may not
00801             // be accessable for any number of reasons, including
00802             // security, a bad URL, etc.
00803 #ifdef DEBUG
00804             nsCAutoString msg;
00805             msg.Append("unable to load datasource '");
00806             msg.AppendWithConversion(uriStr);
00807             msg.Append('\'');
00808             NS_WARNING(msg.get());
00809 #endif
00810             continue;
00811         }
00812 
00813         mCompDB->AddDataSource(ds);
00814     }
00815 
00816     // check if we were given an inference engine type
00817     nsAutoString infer;
00818     mRoot->GetAttr(kNameSpaceID_None, nsXULAtoms::infer, infer);
00819     if (!infer.IsEmpty()) {
00820         nsCString inferContractID(NS_RDF_INFER_DATASOURCE_CONTRACTID_PREFIX);
00821         AppendUTF16toUTF8(infer, inferContractID);
00822         nsCOMPtr<nsIRDFInferDataSource> inferDB = do_CreateInstance(inferContractID.get());
00823 
00824         if (inferDB) {
00825             inferDB->SetBaseDataSource(mCompDB);
00826             mDB = do_QueryInterface(inferDB);
00827         } else {
00828             NS_WARNING("failed to construct inference engine specified on template");
00829         }
00830     }
00831 
00832     if (!mDB)
00833         mDB = mCompDB;
00834 
00835     nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(doc);
00836     if (xuldoc)
00837         xuldoc->SetTemplateBuilderFor(mRoot, this);
00838 
00839     // Now set the database on the element, so that script writers can
00840     // access it.
00841     nsXULElement *xulcontent = nsXULElement::FromContent(mRoot);
00842     if (! xulcontent) {
00843         // Hmm. This must be an HTML element. Try to set it as a
00844         // JS property "by hand".
00845         InitHTMLTemplateRoot();
00846     }
00847 
00848     // Add ourselves as a datasource observer
00849     mDB->AddObserver(this);
00850 
00851     return NS_OK;
00852 }
00853 
00854 nsresult
00855 nsXULTemplateBuilder::InitHTMLTemplateRoot()
00856 {
00857     // Use XPConnect and the JS APIs to whack mDB and this as the
00858     // 'database' and 'builder' properties onto aElement.
00859     nsresult rv;
00860 
00861     nsCOMPtr<nsIDocument> doc = mRoot->GetDocument();
00862     NS_ASSERTION(doc, "no document");
00863     if (! doc)
00864         return NS_ERROR_UNEXPECTED;
00865 
00866     nsIScriptGlobalObject *global = doc->GetScriptGlobalObject();
00867     if (! global)
00868         return NS_ERROR_UNEXPECTED;
00869 
00870     JSObject *scope = global->GetGlobalJSObject();
00871 
00872     nsIScriptContext *context = global->GetContext();
00873     if (! context)
00874         return NS_ERROR_UNEXPECTED;
00875 
00876     JSContext* jscontext = NS_REINTERPRET_CAST(JSContext*, context->GetNativeContext());
00877     NS_ASSERTION(context != nsnull, "no jscontext");
00878     if (! jscontext)
00879         return NS_ERROR_UNEXPECTED;
00880 
00881     nsIXPConnect *xpc = nsContentUtils::XPConnect();
00882 
00883     JSObject* jselement = nsnull;
00884 
00885     nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
00886     rv = xpc->WrapNative(jscontext, scope, mRoot, NS_GET_IID(nsIDOMElement),
00887                          getter_AddRefs(wrapper));
00888     NS_ENSURE_SUCCESS(rv, rv);
00889 
00890     rv = wrapper->GetJSObject(&jselement);
00891     NS_ENSURE_SUCCESS(rv, rv);
00892 
00893     {
00894         // database
00895         rv = xpc->WrapNative(jscontext, scope, mDB,
00896                              NS_GET_IID(nsIRDFCompositeDataSource),
00897                              getter_AddRefs(wrapper));
00898         NS_ENSURE_SUCCESS(rv, rv);
00899 
00900         JSObject* jsobj;
00901         rv = wrapper->GetJSObject(&jsobj);
00902         NS_ENSURE_SUCCESS(rv, rv);
00903 
00904         jsval jsdatabase = OBJECT_TO_JSVAL(jsobj);
00905 
00906         PRBool ok;
00907         ok = JS_SetProperty(jscontext, jselement, "database", &jsdatabase);
00908         NS_ASSERTION(ok, "unable to set database property");
00909         if (! ok)
00910             return NS_ERROR_FAILURE;
00911     }
00912 
00913     {
00914         // builder
00915         nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
00916         rv = xpc->WrapNative(jscontext, jselement,
00917                              NS_STATIC_CAST(nsIXULTemplateBuilder*, this),
00918                              NS_GET_IID(nsIXULTemplateBuilder),
00919                              getter_AddRefs(wrapper));
00920         NS_ENSURE_SUCCESS(rv, rv);
00921 
00922         JSObject* jsobj;
00923         rv = wrapper->GetJSObject(&jsobj);
00924         NS_ENSURE_SUCCESS(rv, rv);
00925 
00926         jsval jsbuilder = OBJECT_TO_JSVAL(jsobj);
00927 
00928         PRBool ok;
00929         ok = JS_SetProperty(jscontext, jselement, "builder", &jsbuilder);
00930         if (! ok)
00931             return NS_ERROR_FAILURE;
00932     }
00933 
00934     return NS_OK;
00935 }
00936 
00937 void
00938 nsXULTemplateBuilder::ParseAttribute(const nsAString& aAttributeValue,
00939                                      void (*aVariableCallback)(nsXULTemplateBuilder*, const nsAString&, void*),
00940                                      void (*aTextCallback)(nsXULTemplateBuilder*, const nsAString&, void*),
00941                                      void* aClosure)
00942 {
00943     nsAString::const_iterator done_parsing;
00944     aAttributeValue.EndReading(done_parsing);
00945 
00946     nsAString::const_iterator iter;
00947     aAttributeValue.BeginReading(iter);
00948 
00949     nsAString::const_iterator mark(iter), backup(iter);
00950 
00951     for (; iter != done_parsing; backup = ++iter) {
00952         // A variable is either prefixed with '?' (in the extended
00953         // syntax) or "rdf:" (in the simple syntax).
00954         PRBool isvar;
00955         if (*iter == PRUnichar('?') && (++iter != done_parsing)) {
00956             isvar = PR_TRUE;
00957         }
00958         else if ((*iter == PRUnichar('r') && (++iter != done_parsing)) &&
00959                  (*iter == PRUnichar('d') && (++iter != done_parsing)) &&
00960                  (*iter == PRUnichar('f') && (++iter != done_parsing)) &&
00961                  (*iter == PRUnichar(':') && (++iter != done_parsing))) {
00962             isvar = PR_TRUE;
00963         }
00964         else {
00965             isvar = PR_FALSE;
00966         }
00967 
00968         if (! isvar) {
00969             // It's not a variable, or we ran off the end of the
00970             // string after the initial variable prefix. Since we may
00971             // have slurped down some characters before realizing that
00972             // fact, back up to the point where we started.
00973             iter = backup;
00974             continue;
00975         }
00976         else if (backup != mark && aTextCallback) {
00977             // Okay, we've found a variable, and there's some vanilla
00978             // text that's been buffered up. Flush it.
00979             (*aTextCallback)(this, Substring(mark, backup), aClosure);
00980         }
00981 
00982         if (*iter == PRUnichar('?')) {
00983             // Well, it was not really a variable, but "??". We use one
00984             // question mark (the second one, actually) literally.
00985             mark = iter;
00986             continue;
00987         }
00988 
00989         // Construct a substring that is the symbol we need to look up
00990         // in the rule's symbol table. The symbol is terminated by a
00991         // space character, a caret, or the end of the string,
00992         // whichever comes first.
00993         nsAString::const_iterator first(backup);
00994 
00995         PRUnichar c = 0;
00996         while (iter != done_parsing) {
00997             c = *iter;
00998             if ((c == PRUnichar(' ')) || (c == PRUnichar('^')))
00999                 break;
01000 
01001             ++iter;
01002         }
01003 
01004         nsAString::const_iterator last(iter);
01005 
01006         // Back up so we don't consume the terminating character
01007         // *unless* the terminating character was a caret: the caret
01008         // means "concatenate with no space in between".
01009         if (c != PRUnichar('^'))
01010             --iter;
01011 
01012         (*aVariableCallback)(this, Substring(first, last), aClosure);
01013         mark = iter;
01014         ++mark;
01015     }
01016 
01017     if (backup != mark && aTextCallback) {
01018         // If there's any text left over, then fire the text callback
01019         (*aTextCallback)(this, Substring(mark, backup), aClosure);
01020     }
01021 }
01022 
01023 
01024 struct SubstituteTextClosure {
01025     SubstituteTextClosure(nsTemplateMatch& aMatch, nsAString& aResult)
01026         : match(aMatch), result(aResult) {}
01027 
01028     nsTemplateMatch& match;
01029     nsAString& result;
01030 };
01031 
01032 nsresult
01033 nsXULTemplateBuilder::SubstituteText(nsTemplateMatch& aMatch,
01034                                      const nsAString& aAttributeValue,
01035                                      nsAString& aResult)
01036 {
01037     // See if it's the special value "..."
01038     if (aAttributeValue.EqualsLiteral("...")) {
01039         Value memberval;
01040         aMatch.GetAssignmentFor(mConflictSet, mMemberVar, &memberval);
01041 
01042         nsIRDFResource* member = VALUE_TO_IRDFRESOURCE(memberval);
01043         NS_ASSERTION(member != nsnull, "no member!");
01044         if (! member)
01045             return NS_ERROR_UNEXPECTED;
01046 
01047         const char *uri = nsnull;
01048         member->GetValueConst(&uri);
01049 
01050         CopyUTF8toUTF16(uri, aResult);
01051 
01052         return NS_OK;
01053     }
01054 
01055     // Reasonable guess at how big it should be
01056     aResult.SetCapacity(aAttributeValue.Length());
01057 
01058     SubstituteTextClosure closure(aMatch, aResult);
01059     ParseAttribute(aAttributeValue,
01060                    SubstituteTextReplaceVariable,
01061                    SubstituteTextAppendText,
01062                    &closure);
01063 
01064     return NS_OK;
01065 }
01066 
01067 
01068 void
01069 nsXULTemplateBuilder::SubstituteTextAppendText(nsXULTemplateBuilder* aThis,
01070                                                const nsAString& aText,
01071                                                void* aClosure)
01072 {
01073     // Append aString to the closure's result
01074     SubstituteTextClosure* c = NS_STATIC_CAST(SubstituteTextClosure*, aClosure);
01075     c->result.Append(aText);
01076 }
01077 
01078 void
01079 nsXULTemplateBuilder::SubstituteTextReplaceVariable(nsXULTemplateBuilder* aThis,
01080                                                     const nsAString& aVariable,
01081                                                     void* aClosure)
01082 {
01083     // Substitute the value for the variable and append to the
01084     // closure's result.
01085     SubstituteTextClosure* c = NS_STATIC_CAST(SubstituteTextClosure*, aClosure);
01086 
01087     // The symbol "rdf:*" is special, and means "this guy's URI"
01088     PRInt32 var = 0;
01089     if (aVariable.EqualsLiteral("rdf:*"))
01090         var = c->match.mRule->GetMemberVariable();
01091     else
01092         var = aThis->mRules.LookupSymbol(PromiseFlatString(aVariable).get());
01093 
01094     // No variable; treat as a variable with no substitution. (This
01095     // shouldn't ever happen, really...)
01096     if (! var)
01097         return;
01098 
01099     // Got a variable; get the value it's assigned to
01100     Value value;
01101     PRBool hasAssignment =
01102         c->match.GetAssignmentFor(aThis->mConflictSet, var, &value);
01103 
01104     // If there was no assignment for the variable, bail. This'll
01105     // leave the result string with an empty substitution for the
01106     // variable.
01107     if (! hasAssignment)
01108         return;
01109 
01110     // Got a value; substitute it.
01111     switch (value.GetType()) {
01112     case Value::eISupports:
01113         {
01114             nsISupports* isupports = NS_STATIC_CAST(nsISupports*, value);
01115 
01116             nsCOMPtr<nsIRDFNode> node = do_QueryInterface(isupports);
01117             if (node) {
01118                 // XXX ideally we'd just point this right at the
01119                 // substring which is the end of the string,
01120                 // turning this into an in-place append.
01121                 nsAutoString temp;
01122                 nsXULContentUtils::GetTextForNode(node, temp);
01123                 c->result += temp;
01124             }
01125         }
01126     break;
01127 
01128     case Value::eString:
01129         c->result += NS_STATIC_CAST(const PRUnichar*, value);
01130         break;
01131 
01132     default:
01133         break;
01134     }
01135     
01136 }
01137 
01138 struct IsVarInSetClosure {
01139     IsVarInSetClosure(nsTemplateMatch& aMatch, const VariableSet& aModifiedVars)
01140         : match(aMatch), modifiedVars(aModifiedVars), result(PR_FALSE) {}
01141 
01142     nsTemplateMatch& match;
01143     const VariableSet& modifiedVars;
01144     PRBool result;
01145 };
01146 
01147 
01148 void
01149 nsXULTemplateBuilder::IsVarInSet(nsXULTemplateBuilder* aThis,
01150                                  const nsAString& aVariable,
01151                                  void* aClosure)
01152 {
01153     IsVarInSetClosure* c = NS_STATIC_CAST(IsVarInSetClosure*, aClosure);
01154 
01155     PRInt32 var =
01156         aThis->mRules.LookupSymbol(PromiseFlatString(aVariable).get());
01157 
01158     // No variable; treat as a variable with no substitution. (This
01159     // shouldn't ever happen, really...)
01160     if (! var)
01161         return;
01162 
01163     // See if this was one of the variables that was modified. If it
01164     // *was*, then this attribute *will* be impacted by the modified
01165     // variable set...
01166     c->result = c->result || c->modifiedVars.Contains(var);
01167 }
01168 
01169 PRBool
01170 nsXULTemplateBuilder::IsAttrImpactedByVars(nsTemplateMatch& aMatch,
01171                                            const nsAString& aAttributeValue,
01172                                            const VariableSet& aModifiedVars)
01173 {
01174     // XXX at some point, it might be good to remember what attributes
01175     // are impacted by variable changes using information that we
01176     // could get at rule compilation time, rather than grovelling over
01177     // the attribute string.
01178     IsVarInSetClosure closure(aMatch, aModifiedVars);
01179     ParseAttribute(aAttributeValue, IsVarInSet, nsnull, &closure);
01180 
01181     return closure.result;
01182 }
01183 
01184 
01185 nsresult
01186 nsXULTemplateBuilder::SynchronizeAll(nsIRDFResource* aSource,
01187                                      nsIRDFResource* aProperty,
01188                                      nsIRDFNode* aOldTarget,
01189                                      nsIRDFNode* aNewTarget)
01190 {
01191     // Update each match that contains <aSource, aProperty, aOldTarget>.
01192 
01193     // Get all the matches whose assignments are currently supported
01194     // by aSource and aProperty: we'll need to recompute them.
01195     const nsTemplateMatchRefSet* matches =
01196         mConflictSet.GetMatchesWithBindingDependency(aSource);
01197 
01198     if (! matches || matches->Empty())
01199         return NS_OK;
01200 
01201     // Since we'll actually be manipulating the match set as we
01202     // iterate through it, we need to copy it into our own private
01203     // area before performing the iteration.
01204     nsTemplateMatchRefSet copy = *matches;
01205 
01206     nsTemplateMatchRefSet::ConstIterator last = copy.Last();
01207     for (nsTemplateMatchRefSet::ConstIterator match = copy.First(); match != last; ++match) {
01208         const nsTemplateRule* rule = match->mRule;
01209 
01210         // Recompute the assignments. This will replace aOldTarget with
01211         // aNewTarget, which will disrupt the match set.
01212         VariableSet modified;
01213         rule->RecomputeBindings(mConflictSet, match.operator->(),
01214                                 aSource, aProperty, aOldTarget, aNewTarget,
01215                                 modified);
01216 
01217         // If nothing changed, then continue on to the next match.
01218         if (0 == modified.GetCount())
01219             continue;
01220 
01221 #ifdef PR_LOGGING
01222         PR_LOG(gXULTemplateLog, PR_LOG_DEBUG,
01223                ("xultemplate[%p] match %p, %d modified binding(s)",
01224                 this, match.operator->(), modified.GetCount()));
01225 
01226         for (PRInt32 i = 0; i < modified.GetCount(); ++i) {
01227             PRInt32 var = modified.GetVariableAt(i);
01228             Value val;
01229             match->GetAssignmentFor(mConflictSet, var, &val);
01230 
01231             nsCAutoString str;
01232             val.ToCString(str);
01233 
01234             PR_LOG(gXULTemplateLog, PR_LOG_DEBUG,
01235                    ("xultemplate[%p]   %d <= %s", this, var, str.get()));
01236         }
01237 #endif
01238 
01239         SynchronizeMatch(match.operator->(), modified);
01240     }
01241 
01242     return NS_OK;
01243 }
01244 
01245 nsresult
01246 nsXULTemplateBuilder::CheckContainer(nsIRDFResource* aResource, PRBool* aIsContainer, PRBool* aIsEmpty)
01247 {
01248     // We have to look at all of the arcs extending out of the
01249     // resource: if any of them are that "containment" property, then
01250     // we know we'll have children.
01251     PRBool isContainer = PR_FALSE;
01252     PRBool isEmpty = PR_TRUE; // assume empty
01253 
01254     for (nsResourceSet::ConstIterator property = mContainmentProperties.First();
01255          property != mContainmentProperties.Last();
01256          property++) {
01257         PRBool hasArc = PR_FALSE;
01258         mDB->HasArcOut(aResource, *property, &hasArc);
01259 
01260         if (hasArc) {
01261             // Well, it's a container...
01262             isContainer = PR_TRUE;
01263 
01264             // ...should we check if it's empty?
01265             if (!aIsEmpty || (mFlags & eDontTestEmpty)) {
01266                 isEmpty = PR_FALSE;
01267                 break;
01268             }
01269 
01270             // Yes: call GetTarget() and see if there's anything on
01271             // the other side...
01272             nsCOMPtr<nsIRDFNode> dummy;
01273             mDB->GetTarget(aResource, *property, PR_TRUE, getter_AddRefs(dummy));
01274 
01275             if (dummy) {
01276                 isEmpty = PR_FALSE;
01277                 break;
01278             }
01279 
01280             // Even if there isn't a target for *this* containment
01281             // property, we have continue to check the other
01282             // properties: one of them may have a target.
01283         }
01284     }
01285 
01286     // If we get here, and we're still not sure if it's a container,
01287     // then see if it's an RDF container
01288     if (! isContainer) {
01289         gRDFContainerUtils->IsContainer(mDB, aResource, &isContainer);
01290 
01291         if (isContainer && aIsEmpty && !(mFlags & eDontTestEmpty))
01292             gRDFContainerUtils->IsEmpty(mDB, aResource, &isEmpty);
01293     }
01294 
01295     if (aIsContainer)
01296         *aIsContainer = isContainer;
01297 
01298     if (aIsEmpty)
01299         *aIsEmpty = isEmpty;
01300 
01301     return NS_OK;
01302 }
01303 
01304 #ifdef PR_LOGGING
01305 nsresult
01306 nsXULTemplateBuilder::Log(const char* aOperation,
01307                           nsIRDFResource* aSource,
01308                           nsIRDFResource* aProperty,
01309                           nsIRDFNode* aTarget)
01310 {
01311     if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) {
01312         nsresult rv;
01313 
01314         const char* sourceStr;
01315         rv = aSource->GetValueConst(&sourceStr);
01316         if (NS_FAILED(rv)) return rv;
01317 
01318         PR_LOG(gXULTemplateLog, PR_LOG_DEBUG,
01319                ("xultemplate[%p] %8s [%s]--", this, aOperation, sourceStr));
01320 
01321         const char* propertyStr;
01322         rv = aProperty->GetValueConst(&propertyStr);
01323         if (NS_FAILED(rv)) return rv;
01324 
01325         nsAutoString targetStr;
01326         rv = nsXULContentUtils::GetTextForNode(aTarget, targetStr);
01327         if (NS_FAILED(rv)) return rv;
01328 
01329         nsCAutoString targetstrC;
01330         targetstrC.AssignWithConversion(targetStr);
01331         PR_LOG(gXULTemplateLog, PR_LOG_DEBUG,
01332                ("                        --[%s]-->[%s]",
01333                 propertyStr,
01334                 targetstrC.get()));
01335     }
01336     return NS_OK;
01337 }
01338 #endif
01339 
01340 //----------------------------------------------------------------------
01341 
01342 nsresult
01343 nsXULTemplateBuilder::ComputeContainmentProperties()
01344 {
01345     // The 'containment' attribute on the root node is a
01346     // whitespace-separated list that tells us which properties we
01347     // should use to test for containment.
01348     nsresult rv;
01349 
01350     mContainmentProperties.Clear();
01351 
01352     nsAutoString containment;
01353     rv = mRoot->GetAttr(kNameSpaceID_None, nsXULAtoms::containment, containment);
01354     if (NS_FAILED(rv)) return rv;
01355 
01356     PRUint32 len = containment.Length();
01357     PRUint32 offset = 0;
01358     while (offset < len) {
01359         while (offset < len && nsCRT::IsAsciiSpace(containment[offset]))
01360             ++offset;
01361 
01362         if (offset >= len)
01363             break;
01364 
01365         PRUint32 end = offset;
01366         while (end < len && !nsCRT::IsAsciiSpace(containment[end]))
01367             ++end;
01368 
01369         nsAutoString propertyStr;
01370         containment.Mid(propertyStr, offset, end - offset);
01371 
01372         nsCOMPtr<nsIRDFResource> property;
01373         rv = gRDFService->GetUnicodeResource(propertyStr, getter_AddRefs(property));
01374         if (NS_FAILED(rv)) return rv;
01375 
01376         rv = mContainmentProperties.Add(property);
01377         if (NS_FAILED(rv)) return rv;
01378 
01379         offset = end;
01380     }
01381 
01382 #define TREE_PROPERTY_HACK 1
01383 #if defined(TREE_PROPERTY_HACK)
01384     if (! len) {
01385         // Some ever-present membership tests.
01386         mContainmentProperties.Add(nsXULContentUtils::NC_child);
01387         mContainmentProperties.Add(nsXULContentUtils::NC_Folder);
01388     }
01389 #endif
01390 
01391     return NS_OK;
01392 }
01393 
01394 PRBool
01395 nsXULTemplateBuilder::IsTemplateElement(nsIContent* aContent)
01396 {
01397     nsINodeInfo *ni = aContent->GetNodeInfo();
01398 
01399     return ni && ni->Equals(nsXULAtoms::Template, kNameSpaceID_XUL);
01400 }
01401 
01402 nsresult
01403 nsXULTemplateBuilder::InitializeRuleNetwork()
01404 {
01405     NS_PRECONDITION(mRoot != nsnull, "not initialized");
01406     if (! mRoot)
01407         return NS_ERROR_NOT_INITIALIZED;
01408 
01409     // Determine if there are any special settings we need to observe
01410     mFlags = 0;
01411 
01412     nsAutoString flags;
01413     mRoot->GetAttr(kNameSpaceID_None, nsXULAtoms::flags, flags);
01414 
01415     if (flags.Find(NS_LITERAL_STRING("dont-test-empty")) >= 0)
01416         mFlags |= eDontTestEmpty;
01417 
01418     // Initialize the rule network
01419     mRules.Clear();
01420     mRules.Clear();
01421     mRDFTests.Clear();
01422     ComputeContainmentProperties();
01423 
01424     mContainerVar = mRules.CreateAnonymousVariable();
01425     mMemberVar = mRules.CreateAnonymousVariable();
01426 
01427     return NS_OK;
01428 }
01429 
01430 nsresult
01431 nsXULTemplateBuilder::GetTemplateRoot(nsIContent** aResult)
01432 {
01433     NS_PRECONDITION(mRoot != nsnull, "not initialized");
01434     if (! mRoot)
01435         return NS_ERROR_NOT_INITIALIZED;
01436 
01437     // First, check and see if the root has a template attribute. This
01438     // allows a template to be specified "out of line"; e.g.,
01439     //
01440     //   <window>
01441     //     <foo template="MyTemplate">...</foo>
01442     //     <template id="MyTemplate">...</template>
01443     //   </window>
01444     //
01445     nsAutoString templateID;
01446     mRoot->GetAttr(kNameSpaceID_None, nsXULAtoms::templateAtom, templateID);
01447 
01448     if (!templateID.IsEmpty()) {
01449         nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mRoot->GetDocument());
01450         NS_ASSERTION(domDoc, "expected a XUL document");
01451         if (! domDoc)
01452             return NS_ERROR_FAILURE;
01453 
01454         nsCOMPtr<nsIDOMElement> domElement;
01455         domDoc->GetElementById(templateID, getter_AddRefs(domElement));
01456 
01457         if (domElement)
01458             return CallQueryInterface(domElement, aResult);
01459     }
01460 
01461 #if 1 // XXX hack to workaround bug with XBL insertion/removal?
01462     {
01463         // If root node has no template attribute, then look for a child
01464         // node which is a template tag
01465         PRUint32 count = mRoot->GetChildCount();
01466 
01467         for (PRUint32 i = 0; i < count; ++i) {
01468             nsIContent *child = mRoot->GetChildAt(i);
01469 
01470             if (IsTemplateElement(child)) {
01471                 NS_ADDREF(*aResult = child);
01472                 return NS_OK;
01473             }
01474         }
01475     }
01476 #endif
01477 
01478     // If we couldn't find a real child, look through the anonymous
01479     // kids, too.
01480     nsCOMPtr<nsIDocument> doc = mRoot->GetDocument();
01481     NS_ASSERTION(doc, "root element has no document");
01482     if (! doc)
01483         return NS_ERROR_FAILURE;
01484 
01485     nsCOMPtr<nsIDOMNodeList> kids;
01486     doc->BindingManager()->GetXBLChildNodesFor(mRoot, getter_AddRefs(kids));
01487 
01488     if (kids) {
01489         PRUint32 length;
01490         kids->GetLength(&length);
01491 
01492         for (PRUint32 i = 0; i < length; ++i) {
01493             nsCOMPtr<nsIDOMNode> node;
01494             kids->Item(i, getter_AddRefs(node));
01495             if (! node)
01496                 continue;
01497 
01498             nsCOMPtr<nsIContent> child = do_QueryInterface(node);
01499 
01500             if (IsTemplateElement(child)) {
01501                 NS_ADDREF(*aResult = child.get());
01502                 return NS_OK;
01503             }
01504         }
01505     }
01506 
01507     *aResult = nsnull;
01508     return NS_OK;
01509 }
01510 
01511 nsresult
01512 nsXULTemplateBuilder::CompileRules()
01513 {
01514     NS_PRECONDITION(mRoot != nsnull, "not initialized");
01515     if (! mRoot)
01516         return NS_ERROR_NOT_INITIALIZED;
01517 
01518     mRulesCompiled = PR_FALSE;
01519 
01520     // Initialize the rule network
01521     InitializeRuleNetwork();
01522 
01523     nsCOMPtr<nsIContent> tmpl;
01524     GetTemplateRoot(getter_AddRefs(tmpl));
01525     if (! tmpl)
01526         return NS_OK;
01527 
01528     // Used for simple rules, if there are any.
01529     InnerNode* childnode = nsnull;
01530 
01531     // Set the "container" and "member" variables, if the user has
01532     // specified them.
01533     mContainerSymbol.Truncate();
01534     tmpl->GetAttr(kNameSpaceID_None, nsXULAtoms::container, mContainerSymbol);
01535     if (!mContainerSymbol.IsEmpty())
01536         mRules.PutSymbol(mContainerSymbol.get(), mContainerVar);
01537 
01538     mMemberSymbol.Truncate();
01539     tmpl->GetAttr(kNameSpaceID_None, nsXULAtoms::member, mMemberSymbol);
01540     if (!mMemberSymbol.IsEmpty())
01541         mRules.PutSymbol(mMemberSymbol.get(), mMemberVar);
01542 
01543     // Compile the rules beneath the <template>
01544     PRUint32 count = tmpl->GetChildCount();
01545 
01546     PRUint32 nrules = 0;
01547 
01548     for (PRUint32 i = 0; i < count; i++) {
01549         nsIContent *rule = tmpl->GetChildAt(i);
01550         nsINodeInfo *ni = rule->GetNodeInfo();
01551 
01552         if (ni && ni->Equals(nsXULAtoms::rule, kNameSpaceID_XUL)) {
01553             ++nrules;
01554 
01555             // If the <rule> has a <conditions> element, then
01556             // compile it using the extended syntax.
01557             nsCOMPtr<nsIContent> conditions;
01558             nsXULContentUtils::FindChildByTag(rule,
01559                                               kNameSpaceID_XUL,
01560                                               nsXULAtoms::conditions,
01561                                               getter_AddRefs(conditions));
01562 
01563             if (conditions)
01564                 CompileExtendedRule(rule, nrules, mRules.GetRoot());
01565             else {
01566                 if (! childnode)
01567                     InitializeRuleNetworkForSimpleRules(&childnode);
01568 
01569                 CompileSimpleRule(rule, nrules, childnode);
01570             }
01571         }
01572     }
01573 
01574     if (nrules == 0) {
01575         // if no rules are specified in the template, then the
01576         // contents of the <template> tag are the one-and-only
01577         // template.
01578         InitializeRuleNetworkForSimpleRules(&childnode);
01579         CompileSimpleRule(tmpl, 1, childnode);
01580     }
01581 
01582     // XXXwaterson post-process the rule network to optimize
01583 
01584     mRulesCompiled = PR_TRUE;
01585     return NS_OK;
01586 }
01587 
01588 nsresult
01589 nsXULTemplateBuilder::CompileExtendedRule(nsIContent* aRuleElement,
01590                                           PRInt32 aPriority,
01591                                           InnerNode* aParentNode)
01592 {
01593     // Compile an "extended" <template> rule. An extended rule must
01594     // have a <conditions> child, and an <action> child, and may
01595     // optionally have a <bindings> child.
01596     nsresult rv;
01597 
01598     nsCOMPtr<nsIContent> conditions;
01599     nsXULContentUtils::FindChildByTag(aRuleElement,
01600                                       kNameSpaceID_XUL,
01601                                       nsXULAtoms::conditions,
01602                                       getter_AddRefs(conditions));
01603 
01604     if (! conditions) {
01605         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
01606                ("xultemplate[%p] no <conditions> element in extended rule", this));
01607 
01608         return NS_OK;
01609     }
01610 
01611     nsCOMPtr<nsIContent> action;
01612     nsXULContentUtils::FindChildByTag(aRuleElement,
01613                                       kNameSpaceID_XUL,
01614                                       nsXULAtoms::action,
01615                                       getter_AddRefs(action));
01616 
01617     if (! action) {
01618         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
01619                ("xultemplate[%p] no <action> element in extended rule", this));
01620 
01621         return NS_OK;
01622     }
01623 
01624     // If we've got <conditions> and <action>, we can make a rule.
01625     nsTemplateRule* rule = new nsTemplateRule(mDB, action, aPriority);
01626     if (! rule)
01627         return NS_ERROR_OUT_OF_MEMORY;
01628 
01629     rule->SetContainerVariable(mContainerVar);
01630 
01631     if (mMemberSymbol.IsEmpty()) {
01632         // If the member variable hasn't already been specified, then
01633         // grovel over <action> to find it. We'll use the first one
01634         // that we find in a breadth-first search.
01635         nsVoidArray unvisited;
01636         unvisited.AppendElement(action.get());
01637 
01638         while (unvisited.Count()) {
01639             nsIContent* next = NS_STATIC_CAST(nsIContent*, unvisited[0]);
01640             unvisited.RemoveElementAt(0);
01641 
01642             nsAutoString uri;
01643             next->GetAttr(kNameSpaceID_None, nsXULAtoms::uri, uri);
01644 
01645             if (!uri.IsEmpty() && uri[0] == PRUnichar('?')) {
01646                 // Found it.
01647                 mMemberSymbol = uri;
01648 
01649                 if (! mRules.LookupSymbol(mMemberSymbol.get()))
01650                     mRules.PutSymbol(mMemberSymbol.get(), mMemberVar);
01651 
01652                 break;
01653             }
01654 
01655             // otherwise, append the children to the unvisited list: this
01656             // results in a breadth-first search.
01657             PRUint32 count = next->GetChildCount();
01658 
01659             for (PRUint32 i = 0; i < count; ++i) {
01660                 nsIContent *child = next->GetChildAt(i);
01661 
01662                 unvisited.AppendElement(child);
01663             }
01664         }
01665     }
01666 
01667     // If we can't find a member symbol, then we're out of luck. Bail.
01668     if (mMemberSymbol.IsEmpty()) {
01669         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
01670                ("xultemplate[%p] could not deduce member variable", this));
01671 
01672         delete rule;
01673         return NS_OK;
01674     }
01675 
01676     rule->SetMemberVariable(mMemberVar);
01677 
01678     InnerNode* last;
01679     rv = CompileConditions(rule, conditions, aParentNode, &last);
01680 
01681     // If the rule compilation failed, or we don't have a container
01682     // symbol, then we have to bail.
01683     if (NS_FAILED(rv)) {
01684         delete rule;
01685         return rv;
01686     }
01687 
01688     if (mContainerSymbol.IsEmpty()) {
01689         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
01690                ("xultemplate[%p] could not deduce container variable", this));
01691 
01692         delete rule;
01693         return NS_OK;
01694     }
01695 
01696     // And now add the instantiation node: it owns the rule now.
01697     nsInstantiationNode* instnode =
01698         new nsInstantiationNode(mConflictSet, rule, mDB);
01699 
01700     if (! instnode) {
01701         delete rule;
01702         return NS_ERROR_OUT_OF_MEMORY;
01703     }
01704 
01705     last->AddChild(instnode);
01706     mRules.AddNode(instnode);
01707     
01708     // If we've got bindings, add 'em.
01709     nsCOMPtr<nsIContent> bindings;
01710     nsXULContentUtils::FindChildByTag(aRuleElement,
01711                                       kNameSpaceID_XUL,
01712                                       nsXULAtoms::bindings,
01713                                       getter_AddRefs(bindings));
01714 
01715     if (bindings) {
01716         rv = CompileBindings(rule, bindings);
01717         if (NS_FAILED(rv)) return rv;
01718     }
01719 
01720     return NS_OK;
01721 }
01722 
01723 nsresult
01724 nsXULTemplateBuilder::CompileConditions(nsTemplateRule* aRule,
01725                                         nsIContent* aConditions,
01726                                         InnerNode* aParentNode,
01727                                         InnerNode** aLastNode)
01728 {
01729     // Compile an extended rule's conditions.
01730     nsresult rv;
01731 
01732     PRUint32 count = aConditions->GetChildCount();
01733 
01734     for (PRUint32 i = 0; i < count; ++i) {
01735         nsIContent *condition = aConditions->GetChildAt(i);
01736 
01737         TestNode* testnode = nsnull;
01738         rv = CompileCondition(condition->Tag(), aRule, condition,
01739                               aParentNode, &testnode);
01740         if (NS_FAILED(rv)) return rv;
01741 
01742         // XXXwaterson proably wrong to just drill it straight down
01743         // like this.
01744         if (testnode) {
01745             aParentNode->AddChild(testnode);
01746             mRules.AddNode(testnode);
01747             aParentNode = testnode;
01748         }
01749     }
01750 
01751     *aLastNode = aParentNode;
01752     return NS_OK;
01753 }
01754 
01755 nsresult
01756 nsXULTemplateBuilder::CompileCondition(nsIAtom* aTag,
01757                                        nsTemplateRule* aRule,
01758                                        nsIContent* aCondition,
01759                                        InnerNode* aParentNode,
01760                                        TestNode** aResult)
01761 {
01762     nsresult rv;
01763 
01764     if (aTag == nsXULAtoms::triple) {
01765         rv = CompileTripleCondition(aRule, aCondition, aParentNode, aResult);
01766     }
01767     else if (aTag == nsXULAtoms::member) {
01768         rv = CompileMemberCondition(aRule, aCondition, aParentNode, aResult);
01769     }
01770     else {
01771 #ifdef PR_LOGGING
01772         nsAutoString tagstr;
01773         aTag->ToString(tagstr);
01774 
01775         nsCAutoString tagstrC;
01776         tagstrC.AssignWithConversion(tagstr);
01777         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
01778                ("xultemplate[%p] unrecognized condition test <%s>",
01779                 this, tagstrC.get()));
01780 #endif
01781 
01782         rv = NS_OK;
01783     }
01784 
01785     return rv;
01786 }
01787 
01788 nsresult
01789 nsXULTemplateBuilder::ParseLiteral(const nsString& aParseType, 
01790                                    const nsString& aValue,
01791                                    nsIRDFNode** aResult)
01792 {
01793     nsresult rv = NS_OK;
01794     *aResult = nsnull;
01795 
01796     if (aParseType.EqualsLiteral(PARSE_TYPE_INTEGER)) {
01797         nsCOMPtr<nsIRDFInt> intLiteral;
01798         PRInt32 errorCode;
01799         PRInt32 intValue = aValue.ToInteger(&errorCode);
01800         if (NS_FAILED(errorCode))
01801             return NS_ERROR_FAILURE;
01802         rv = gRDFService->GetIntLiteral(intValue, getter_AddRefs(intLiteral));
01803         if (NS_FAILED(rv)) 
01804             return rv;
01805         rv = CallQueryInterface(intLiteral, aResult);
01806     }
01807     else {
01808         nsCOMPtr<nsIRDFLiteral> literal;
01809         rv = gRDFService->GetLiteral(aValue.get(), getter_AddRefs(literal));
01810         if (NS_FAILED(rv)) 
01811             return rv;
01812         rv = CallQueryInterface(literal, aResult);
01813     }
01814     return rv;
01815 }
01816 
01817 nsresult
01818 nsXULTemplateBuilder::CompileTripleCondition(nsTemplateRule* aRule,
01819                                              nsIContent* aCondition,
01820                                              InnerNode* aParentNode,
01821                                              TestNode** aResult)
01822 {
01823     // Compile a <triple> condition, which must be of the form:
01824     //
01825     //   <triple subject="?var1|resource"
01826     //           predicate="resource"
01827     //           object="?var2|resource|literal" />
01828     //
01829     // XXXwaterson Some day it would be cool to allow the 'predicate'
01830     // to be bound to a variable.
01831 
01832     // subject
01833     nsAutoString subject;
01834     aCondition->GetAttr(kNameSpaceID_None, nsXULAtoms::subject, subject);
01835 
01836     PRInt32 svar = 0;
01837     nsCOMPtr<nsIRDFResource> sres;
01838     if (subject[0] == PRUnichar('?'))
01839         svar = mRules.LookupSymbol(subject.get(), PR_TRUE);
01840     else
01841         gRDFService->GetUnicodeResource(subject, getter_AddRefs(sres));
01842 
01843     // predicate
01844     nsAutoString predicate;
01845     aCondition->GetAttr(kNameSpaceID_None, nsXULAtoms::predicate, predicate);
01846 
01847     nsCOMPtr<nsIRDFResource> pres;
01848     if (predicate[0] == PRUnichar('?')) {
01849         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
01850                ("xultemplate[%p] cannot handle variables in <triple> 'predicate'", this));
01851 
01852         return NS_OK;
01853     }
01854     else {
01855         gRDFService->GetUnicodeResource(predicate, getter_AddRefs(pres));
01856     }
01857 
01858     // object
01859     nsAutoString object;
01860     aCondition->GetAttr(kNameSpaceID_None, nsXULAtoms::object, object);
01861 
01862     PRInt32 ovar = 0;
01863     nsCOMPtr<nsIRDFNode> onode;
01864     if (object[0] == PRUnichar('?')) {
01865         ovar = mRules.LookupSymbol(object.get(), PR_TRUE);
01866     }
01867     else if (object.FindChar(':') != -1) { // XXXwaterson evil.
01868         // treat as resource
01869         nsCOMPtr<nsIRDFResource> resource;
01870         gRDFService->GetUnicodeResource(object, getter_AddRefs(resource));
01871         onode = do_QueryInterface(resource);
01872     }
01873     else {
01874         nsAutoString parseType;
01875         aCondition->GetAttr(kNameSpaceID_None, nsXULAtoms::parsetype, parseType);
01876         nsresult rv = ParseLiteral(parseType, object, getter_AddRefs(onode));
01877         if (NS_FAILED(rv))
01878             return rv;
01879     }
01880 
01881     nsRDFPropertyTestNode* testnode = nsnull;
01882 
01883     if (svar && ovar) {
01884         testnode = new nsRDFPropertyTestNode(aParentNode, mConflictSet, mDB, svar, pres, ovar);
01885     }
01886     else if (svar) {
01887         testnode = new nsRDFPropertyTestNode(aParentNode, mConflictSet, mDB, svar, pres, onode);
01888     }
01889     else if (ovar) {
01890         testnode = new nsRDFPropertyTestNode(aParentNode, mConflictSet, mDB, sres, pres, ovar);
01891     }
01892     else {
01893         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
01894                ("xultemplate[%p] tautology in <triple> test", this));
01895 
01896         return NS_OK;
01897     }
01898 
01899     if (! testnode)
01900         return NS_ERROR_OUT_OF_MEMORY;
01901 
01902     mRDFTests.Add(testnode);
01903 
01904     *aResult = testnode;
01905     return NS_OK;
01906 }
01907 
01908 nsresult
01909 nsXULTemplateBuilder::CompileMemberCondition(nsTemplateRule* aRule,
01910                                              nsIContent* aCondition,
01911                                              InnerNode* aParentNode,
01912                                              TestNode** aResult)
01913 {
01914     // Compile a <member> condition, which must be of the form:
01915     //
01916     //   <member container="?var1" child="?var2" />
01917     //
01918 
01919     // container
01920     nsAutoString container;
01921     aCondition->GetAttr(kNameSpaceID_None, nsXULAtoms::container, container);
01922 
01923     if (container[0] != PRUnichar('?')) {
01924         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
01925                ("xultemplate[%p] on <member> test, expected 'container' attribute to name a variable", this));
01926 
01927         return NS_OK;
01928     }
01929 
01930     PRInt32 containervar = mRules.LookupSymbol(container.get(), PR_TRUE);
01931 
01932     // child
01933     nsAutoString child;
01934     aCondition->GetAttr(kNameSpaceID_None, nsXULAtoms::child, child);
01935 
01936     if (child[0] != PRUnichar('?')) {
01937         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
01938                ("xultemplate[%p] on <member> test, expected 'child' attribute to name a variable", this));
01939 
01940         return NS_OK;
01941     }
01942 
01943     PRInt32 childvar = mRules.LookupSymbol(child.get(), PR_TRUE);
01944 
01945     TestNode* testnode =
01946         new nsRDFConMemberTestNode(aParentNode,
01947                                    mConflictSet,
01948                                    mDB,
01949                                    mContainmentProperties,
01950                                    containervar,
01951                                    childvar);
01952 
01953     if (! testnode)
01954         return NS_ERROR_OUT_OF_MEMORY;
01955 
01956     mRDFTests.Add(testnode);
01957     
01958     *aResult = testnode;
01959     return NS_OK;
01960 }
01961 
01962 nsresult
01963 nsXULTemplateBuilder::CompileBindings(nsTemplateRule* aRule, nsIContent* aBindings)
01964 {
01965     // Add an extended rule's bindings.
01966     nsresult rv;
01967 
01968     PRUint32 count = aBindings->GetChildCount();
01969 
01970     for (PRUint32 i = 0; i < count; ++i) {
01971         nsIContent *binding = aBindings->GetChildAt(i);
01972 
01973         nsINodeInfo *ni = binding->GetNodeInfo();
01974 
01975         if (ni && ni->Equals(nsXULAtoms::binding, kNameSpaceID_XUL)) {
01976             rv = CompileBinding(aRule, binding);
01977         }
01978         else {
01979 #ifdef PR_LOGGING
01980             nsAutoString tagstr;
01981             if (ni) {
01982                 ni->GetQualifiedName(tagstr);
01983             }
01984 
01985             nsCAutoString tagstrC;
01986             tagstrC.AssignWithConversion(tagstr);
01987             PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
01988                    ("xultemplate[%p] unrecognized binding <%s>",
01989                     this, tagstrC.get()));
01990 #endif
01991 
01992             continue;
01993         }
01994 
01995         if (NS_FAILED(rv)) return rv;
01996     }
01997 
01998     return NS_OK;
01999 }
02000 
02001 
02002 nsresult
02003 nsXULTemplateBuilder::CompileBinding(nsTemplateRule* aRule,
02004                                      nsIContent* aBinding)
02005 {
02006     // Compile a <binding> "condition", which must be of the form:
02007     //
02008     //   <binding subject="?var1"
02009     //            predicate="resource"
02010     //            object="?var2" />
02011     //
02012     // XXXwaterson Some day it would be cool to allow the 'predicate'
02013     // to be bound to a variable.
02014 
02015     // subject
02016     nsAutoString subject;
02017     aBinding->GetAttr(kNameSpaceID_None, nsXULAtoms::subject, subject);
02018 
02019     if (subject.IsEmpty()) {
02020         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
02021                ("xultemplate[%p] <binding> requires `subject'", this));
02022 
02023         return NS_OK;
02024     }
02025 
02026     PRInt32 svar = 0;
02027     if (subject[0] == PRUnichar('?')) {
02028         svar = mRules.LookupSymbol(subject.get(), PR_TRUE);
02029     }
02030     else {
02031         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
02032                ("xultemplate[%p] <binding> requires `subject' to be a variable", this));
02033 
02034         return NS_OK;
02035     }
02036 
02037     // predicate
02038     nsAutoString predicate;
02039     aBinding->GetAttr(kNameSpaceID_None, nsXULAtoms::predicate, predicate);
02040     if (predicate.IsEmpty()) {
02041         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
02042                ("xultemplate[%p] <binding> requires `predicate'", this));
02043 
02044         return NS_OK;
02045     }
02046 
02047     nsCOMPtr<nsIRDFResource> pred;
02048     if (predicate[0] == PRUnichar('?')) {
02049         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
02050                ("xultemplate[%p] cannot handle variables in <binding> `predicate'", this));
02051 
02052         return NS_OK;
02053     }
02054     else {
02055         gRDFService->GetUnicodeResource(predicate, getter_AddRefs(pred));
02056     }
02057 
02058     // object
02059     nsAutoString object;
02060     aBinding->GetAttr(kNameSpaceID_None, nsXULAtoms::object, object);
02061 
02062     if (object.IsEmpty()) {
02063         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
02064                ("xultemplate[%p] <binding> requires `object'", this));
02065 
02066         return NS_OK;
02067     }
02068 
02069     PRInt32 ovar = 0;
02070     if (object[0] == PRUnichar('?')) {
02071         ovar = mRules.LookupSymbol(object.get(), PR_TRUE);
02072     }
02073     else {
02074         PR_LOG(gXULTemplateLog, PR_LOG_ALWAYS,
02075                ("xultemplate[%p] <binding> requires `object' to be a variable", this));
02076 
02077         return NS_OK;
02078     }
02079 
02080     return aRule->AddBinding(svar, pred, ovar);
02081 }
02082 
02083 nsresult
02084 nsXULTemplateBuilder::CompileSimpleRule(nsIContent* aRuleElement,
02085                                         PRInt32 aPriority,
02086                                         InnerNode* aParentNode)
02087 {
02088     // Compile a "simple" (or old-school style) <template> rule.
02089     nsresult rv;
02090 
02091     PRBool hasContainerTest = PR_FALSE;
02092 
02093     PRUint32 count = aRuleElement->GetAttrCount();
02094 
02095     // Add constraints for the LHS
02096     for (PRUint32 i = 0; i < count; ++i) {
02097         PRInt32 attrNameSpaceID;
02098         nsCOMPtr<nsIAtom> attr, prefix;
02099         rv = aRuleElement->GetAttrNameAt(i, &attrNameSpaceID,
02100                                          getter_AddRefs(attr),
02101                                          getter_AddRefs(prefix));
02102         if (NS_FAILED(rv)) return rv;
02103 
02104         // Note: some attributes must be skipped on XUL template rule subtree
02105 
02106         // never compare against rdf:property attribute
02107         if ((attr.get() == nsXULAtoms::property) && (attrNameSpaceID == kNameSpaceID_RDF))
02108             continue;
02109         // never compare against rdf:instanceOf attribute
02110         else if ((attr.get() == nsXULAtoms::instanceOf) && (attrNameSpaceID == kNameSpaceID_RDF))
02111             continue;
02112         // never compare against {}:id attribute
02113         else if ((attr.get() == nsXULAtoms::id) && (attrNameSpaceID == kNameSpaceID_None))
02114             continue;
02115         else if ((attr.get() == nsXULAtoms::parsetype) && (attrNameSpaceID == kNameSpaceID_None))
02116             continue;
02117 
02118         nsAutoString value;
02119         rv = aRuleElement->GetAttr(attrNameSpaceID, attr, value);
02120         if (NS_FAILED(rv)) return rv;
02121 
02122         TestNode* testnode = nsnull;
02123 
02124         if (CompileSimpleAttributeCondition(attrNameSpaceID, attr, value, aParentNode, &testnode)) {
02125             // handled by subclass
02126         }
02127         else if (((attrNameSpaceID == kNameSpaceID_None) && (attr.get() == nsXULAtoms::iscontainer)) ||
02128                  ((attrNameSpaceID == kNameSpaceID_None) && (attr.get() == nsXULAtoms::isempty))) {
02129             // Tests about containerhood and emptiness. These can be
02130             // globbed together, mostly. Check to see if we've already
02131             // added a container test: we only need one.
02132             if (hasContainerTest)
02133                 continue;
02134 
02135             nsRDFConInstanceTestNode::Test iscontainer =
02136                 nsRDFConInstanceTestNode::eDontCare;
02137 
02138             rv = aRuleElement->GetAttr(kNameSpaceID_None, nsXULAtoms::iscontainer, value);
02139             if (NS_FAILED(rv)) return rv;
02140 
02141             if (rv == NS_CONTENT_ATTR_HAS_VALUE) {
02142                 if (value.EqualsLiteral("true")) {
02143                     iscontainer = nsRDFConInstanceTestNode::eTrue;
02144                 }
02145                 else if (value.EqualsLiteral("false")) {
02146                     iscontainer = nsRDFConInstanceTestNode::eFalse;
02147                 }
02148             }
02149 
02150             nsRDFConInstanceTestNode::Test isempty =
02151                 nsRDFConInstanceTestNode::eDontCare;
02152 
02153             rv = aRuleElement->GetAttr(kNameSpaceID_None, nsXULAtoms::isempty, value);
02154             if (NS_FAILED(rv)) return rv;
02155 
02156             if (rv == NS_CONTENT_ATTR_HAS_VALUE) {
02157                 if (value.EqualsLiteral("true")) {
02158                     isempty = nsRDFConInstanceTestNode::eTrue;
02159                 }
02160                 else if (value.EqualsLiteral("false")) {
02161                     isempty = nsRDFConInstanceTestNode::eFalse;
02162                 }
02163             }
02164 
02165             testnode = new nsRDFConInstanceTestNode(aParentNode,
02166                                                     mConflictSet,
02167                                                     mDB,
02168                                                     mContainmentProperties,
02169                                                     mMemberVar,
02170                                                     iscontainer,
02171                                                     isempty);
02172 
02173             if (! testnode)
02174                 return NS_ERROR_OUT_OF_MEMORY;
02175 
02176             mRDFTests.Add(testnode);
02177         }
02178         else {
02179             // It's a simple RDF test
02180             nsCOMPtr<nsIRDFResource> property;
02181             rv = nsXULContentUtils::GetResource(attrNameSpaceID, attr, getter_AddRefs(property));
02182             if (NS_FAILED(rv)) return rv;
02183 
02184             // XXXwaterson this is so manky
02185             nsCOMPtr<nsIRDFNode> target;
02186             if (value.FindChar(':') != -1) { // XXXwaterson WRONG WRONG WRONG!
02187                 nsCOMPtr<nsIRDFResource> resource;
02188                 rv = gRDFService->GetUnicodeResource(value, getter_AddRefs(resource));
02189                 if (NS_FAILED(rv)) return rv;
02190 
02191                 target = do_QueryInterface(resource);
02192             }
02193             else {                
02194               nsAutoString parseType;
02195               aRuleElement->GetAttr(kNameSpaceID_None, nsXULAtoms::parsetype, parseType);
02196               rv = ParseLiteral(parseType, value, getter_AddRefs(target));
02197               if (NS_FAILED(rv))
02198                   return rv;
02199             }
02200 
02201             testnode = new nsRDFPropertyTestNode(aParentNode, mConflictSet, mDB, mMemberVar, property, target);
02202             if (! testnode)
02203                 return NS_ERROR_OUT_OF_MEMORY;
02204 
02205             mRDFTests.Add(testnode);
02206         }
02207 
02208         aParentNode->AddChild(testnode);
02209         mRules.AddNode(testnode);
02210         aParentNode = testnode;
02211     }
02212 
02213     // Create the rule.
02214     nsTemplateRule* rule = new nsTemplateRule(mDB, aRuleElement, aPriority);
02215     if (! rule)
02216         return NS_ERROR_OUT_OF_MEMORY;
02217 
02218     rule->SetContainerVariable(mContainerVar);
02219     rule->SetMemberVariable(mMemberVar);
02220 
02221     AddSimpleRuleBindings(rule, aRuleElement);
02222 
02223     // The InstantiationNode owns the rule now.
02224     nsInstantiationNode* instnode =
02225         new nsInstantiationNode(mConflictSet, rule, mDB);
02226 
02227     if (! instnode)
02228         return NS_ERROR_OUT_OF_MEMORY;
02229 
02230     aParentNode->AddChild(instnode);
02231     mRules.AddNode(instnode);
02232     
02233     return NS_OK;
02234 }
02235 
02236 PRBool
02237 nsXULTemplateBuilder::CompileSimpleAttributeCondition(PRInt32 aNameSpaceID,
02238                                                       nsIAtom* aAttribute,
02239                                                       const nsAString& aValue,
02240                                                       InnerNode* aParentNode,
02241                                                       TestNode** aResult)
02242 {
02243     // By default, not handled
02244     return PR_FALSE;
02245 }
02246 
02247 nsresult
02248 nsXULTemplateBuilder::AddSimpleRuleBindings(nsTemplateRule* aRule, nsIContent* aElement)
02249 {
02250     // Crawl the content tree of a "simple" rule, adding a variable
02251     // assignment for any attribute whose value is "rdf:".
02252 
02253     nsAutoVoidArray elements;
02254 
02255     elements.AppendElement(aElement);
02256     while (elements.Count()) {
02257         // Pop the next element off the stack
02258         PRUint32 i = (PRUint32)(elements.Count() - 1);
02259         nsIContent* element = NS_STATIC_CAST(nsIContent*, elements[i]);
02260         elements.RemoveElementAt(i);
02261 
02262         // Iterate through its attributes, looking for substitutions
02263         // that we need to add as bindings.
02264         PRUint32 count = element->GetAttrCount();
02265 
02266         for (i = 0; i < count; ++i) {
02267             PRInt32 nameSpaceID;
02268             nsCOMPtr<nsIAtom> attr, prefix;
02269 
02270             element->GetAttrNameAt(i, &nameSpaceID, getter_AddRefs(attr),
02271                                    getter_AddRefs(prefix));
02272 
02273             nsAutoString value;
02274             element->GetAttr(nameSpaceID, attr, value);
02275 
02276             // Scan the attribute for variables, adding a binding for
02277             // each one.
02278             ParseAttribute(value, AddBindingsFor, nsnull, aRule);
02279         }
02280 
02281         // Push kids onto the stack, and search them next.
02282         count = element->GetChildCount();
02283 
02284         while (count-- > 0) {
02285             elements.AppendElement(element->GetChildAt(count));
02286         }
02287     }
02288 
02289     return NS_OK;
02290 }
02291 
02292 void
02293 nsXULTemplateBuilder::AddBindingsFor(nsXULTemplateBuilder* aThis,
02294                                      const nsAString& aVariable,
02295                                      void* aClosure)
02296 {
02297     // We should *only* be recieving "rdf:"-style variables. Make
02298     // sure...
02299     if (!StringBeginsWith(aVariable, NS_LITERAL_STRING("rdf:")))
02300         return;
02301 
02302     nsTemplateRule* rule = NS_STATIC_CAST(nsTemplateRule*, aClosure);
02303 
02304     // Lookup the variable symbol
02305     PRInt32 var = aThis->mRules.LookupSymbol(PromiseFlatString(aVariable).get(), PR_TRUE);
02306 
02307     // Strip it down to the raw RDF property by clobbering the "rdf:"
02308     // prefix
02309     const nsAString& propertyStr = Substring(aVariable, PRUint32(4), aVariable.Length() - 4);
02310 
02311     nsCOMPtr<nsIRDFResource> property;
02312     gRDFService->GetUnicodeResource(propertyStr, getter_AddRefs(property));
02313 
02314     if (! rule->HasBinding(aThis->mMemberVar, property, var))
02315         // In the simple syntax, the binding is always from the
02316         // member variable, through the property, to the target.
02317         rule->AddBinding(aThis->mMemberVar, property, var);
02318 }
02319 
02320 //----------------------------------------------------------------------
02321 
02322 
02323 nsresult 
02324 nsXULTemplateBuilder::IsSystemPrincipal(nsIPrincipal *principal, PRBool *result)
02325 {
02326   if (!gSystemPrincipal)
02327     return NS_ERROR_UNEXPECTED;
02328 
02329   *result = (principal == gSystemPrincipal);
02330   return NS_OK;
02331 }
02332 
02333 PRBool
02334 nsXULTemplateBuilder::IsActivated(nsIRDFResource *aResource)
02335 {
02336     for (ActivationEntry *entry = mTop;
02337          entry != nsnull;
02338          entry = entry->mPrevious) {
02339         if (entry->mResource == aResource)
02340             return PR_TRUE;
02341     }
02342     return PR_FALSE;
02343 }