1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Robert Churchill <rjc@netscape.com>
24 * David Hyatt <hyatt@netscape.com>
25 * Chris Waterson <waterson@netscape.com>
26 * Pierre Phaneuf <pp@ludusdesign.com>
27 * Joe Hewitt <hewitt@netscape.com>
28 * Neil Deakin <enndeakin@sympatico.ca>
29 * Laurent Jouanneau <laurent.jouanneau@disruptive-innovations.com>
31 * Alternatively, the contents of this file may be used under the terms of
32 * either of the GNU General Public License Version 2 or later (the "GPL"),
33 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
34 * in which case the provisions of the GPL or the LGPL are applicable instead
35 * of those above. If you wish to allow use of your version of this file only
36 * under the terms of either the GPL or the LGPL, and not to allow others to
37 * use your version of this file under the terms of the MPL, indicate your
38 * decision by deleting the provisions above and replace them with the notice
39 * and other provisions required by the GPL or the LGPL. If you do not delete
40 * the provisions above, a recipient may use your version of this file under
41 * the terms of any one of the MPL, the GPL or the LGPL.
43 * ***** END LICENSE BLOCK ***** */
47 Builds content from a datasource using the XUL <template> tag.
51 . Fix ContentTagTest's location in the network construction
53 To turn on logging for this module, set:
55 NSPR_LOG_MODULES nsXULTemplateBuilder:5
61 #include "nsFixedSizeAllocator.h"
62 #include "nsIContent.h"
63 #include "nsIDOMElement.h"
64 #include "nsIDOMNode.h"
65 #include "nsIDOMDocument.h"
66 #include "nsIDOMXMLDocument.h"
67 #include "nsIPrivateDOMImplementation.h"
68 #include "nsIDOMXULElement.h"
69 #include "nsIDocument.h"
70 #include "nsBindingManager.h"
71 #include "nsIDOMNodeList.h"
72 #include "nsINameSpaceManager.h"
73 #include "nsIObserverService.h"
74 #include "nsIRDFCompositeDataSource.h"
75 #include "nsIRDFInferDataSource.h"
76 #include "nsIRDFContainerUtils.h"
77 #include "nsIXULDocument.h"
78 #include "nsIXULTemplateBuilder.h"
79 #include "nsIXULBuilderListener.h"
80 #include "nsIRDFRemoteDataSource.h"
81 #include "nsIRDFService.h"
82 #include "nsIScriptGlobalObject.h"
83 #include "nsIServiceManager.h"
84 #include "nsISimpleEnumerator.h"
85 #include "nsIMutableArray.h"
87 #include "nsIXPConnect.h"
88 #include "nsContentCID.h"
90 #include "nsXULContentUtils.h"
92 #include "nsVoidArray.h"
93 #include "nsXPIDLString.h"
94 #include "nsWhitespaceTokenizer.h"
95 #include "nsGkAtoms.h"
96 #include "nsXULElement.h"
102 #include "nsIDOMClassInfo.h"
103 #include "nsPIDOMWindow.h"
105 #include "nsNetUtil.h"
106 #include "nsXULTemplateBuilder.h"
107 #include "nsXULTemplateQueryProcessorRDF.h"
108 #include "nsXULTemplateQueryProcessorXML.h"
109 #include "nsXULTemplateQueryProcessorStorage.h"
111 //----------------------------------------------------------------------
113 static NS_DEFINE_CID(kRDFContainerUtilsCID
, NS_RDFCONTAINERUTILS_CID
);
114 static NS_DEFINE_CID(kRDFServiceCID
, NS_RDFSERVICE_CID
);
116 //----------------------------------------------------------------------
118 // nsXULTemplateBuilder
121 nsrefcnt
nsXULTemplateBuilder::gRefCnt
= 0;
122 nsIRDFService
* nsXULTemplateBuilder::gRDFService
;
123 nsIRDFContainerUtils
* nsXULTemplateBuilder::gRDFContainerUtils
;
124 nsIScriptSecurityManager
* nsXULTemplateBuilder::gScriptSecurityManager
;
125 nsIPrincipal
* nsXULTemplateBuilder::gSystemPrincipal
;
126 nsIObserverService
* nsXULTemplateBuilder::gObserverService
;
129 PRLogModuleInfo
* gXULTemplateLog
;
132 #define NS_QUERY_PROCESSOR_CONTRACTID_PREFIX "@mozilla.org/xul/xul-query-processor;1?name="
134 //----------------------------------------------------------------------
136 // nsXULTemplateBuilder methods
139 nsXULTemplateBuilder::nsXULTemplateBuilder(void)
140 : mQueriesCompiled(PR_FALSE
),
143 mObservedDocument(nsnull
)
147 static PLDHashOperator
148 DestroyMatchList(nsISupports
* aKey
, nsTemplateMatch
* aMatch
, void* aContext
)
150 nsFixedSizeAllocator
* pool
= static_cast<nsFixedSizeAllocator
*>(aContext
);
152 // delete all the matches in the list
154 nsTemplateMatch
* next
= aMatch
->mNext
;
155 nsTemplateMatch::Destroy(*pool
, aMatch
, PR_TRUE
);
159 return PL_DHASH_NEXT
;
162 nsXULTemplateBuilder::~nsXULTemplateBuilder(void)
164 if (--gRefCnt
== 0) {
165 NS_IF_RELEASE(gRDFService
);
166 NS_IF_RELEASE(gRDFContainerUtils
);
167 NS_IF_RELEASE(gSystemPrincipal
);
168 NS_IF_RELEASE(gScriptSecurityManager
);
169 NS_IF_RELEASE(gObserverService
);
177 nsXULTemplateBuilder::InitGlobals()
181 if (gRefCnt
++ == 0) {
182 // Initialize the global shared reference to the service
183 // manager and get some shared resource objects.
184 rv
= CallGetService(kRDFServiceCID
, &gRDFService
);
188 rv
= CallGetService(kRDFContainerUtilsCID
, &gRDFContainerUtils
);
192 rv
= CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID
,
193 &gScriptSecurityManager
);
197 rv
= gScriptSecurityManager
->GetSystemPrincipal(&gSystemPrincipal
);
201 rv
= CallGetService(NS_OBSERVERSERVICE_CONTRACTID
, &gObserverService
);
207 if (! gXULTemplateLog
)
208 gXULTemplateLog
= PR_NewLogModule("nsXULTemplateBuilder");
211 if (!mMatchMap
.IsInitialized() && !mMatchMap
.Init())
212 return NS_ERROR_OUT_OF_MEMORY
;
214 const size_t bucketsizes
[] = { sizeof(nsTemplateMatch
) };
215 return mPool
.Init("nsXULTemplateBuilder", bucketsizes
, 1, 256);
220 nsXULTemplateBuilder::Uninit(PRBool aIsFinal
)
222 if (mObservedDocument
&& aIsFinal
) {
223 gObserverService
->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC
);
224 mObservedDocument
->RemoveObserver(this);
225 mObservedDocument
= nsnull
;
229 mQueryProcessor
->Done();
231 for (PRInt32 q
= mQuerySets
.Length() - 1; q
>= 0; q
--) {
232 nsTemplateQuerySet
* qs
= mQuerySets
[q
];
238 mMatchMap
.EnumerateRead(DestroyMatchList
, &mPool
);
241 mRootResult
= nsnull
;
242 mRefVariable
= nsnull
;
243 mMemberVariable
= nsnull
;
245 mQueriesCompiled
= PR_FALSE
;
248 static PLDHashOperator
249 TraverseMatchList(nsISupports
* aKey
, nsTemplateMatch
* aMatch
, void* aContext
)
251 nsCycleCollectionTraversalCallback
*cb
=
252 static_cast<nsCycleCollectionTraversalCallback
*>(aContext
);
254 cb
->NoteXPCOMChild(aKey
);
255 nsTemplateMatch
* match
= aMatch
;
257 cb
->NoteXPCOMChild(match
->GetContainer());
258 cb
->NoteXPCOMChild(match
->mResult
);
259 match
= match
->mNext
;
262 return PL_DHASH_NEXT
;
265 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULTemplateBuilder
)
266 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULTemplateBuilder
)
267 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDataSource
)
268 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDB
)
269 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCompDB
)
270 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
271 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULTemplateBuilder
)
272 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDataSource
)
273 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDB
)
274 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCompDB
)
275 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRoot
)
276 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRootResult
)
277 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mListeners
)
278 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mQueryProcessor
)
279 if (tmp
->mMatchMap
.IsInitialized())
280 tmp
->mMatchMap
.EnumerateRead(TraverseMatchList
, &cb
);
282 PRUint32 i
, count
= tmp
->mQuerySets
.Length();
283 for (i
= 0; i
< count
; ++i
) {
284 nsTemplateQuerySet
*set
= tmp
->mQuerySets
[i
];
285 cb
.NoteXPCOMChild(set
->mQueryNode
);
286 cb
.NoteXPCOMChild(set
->mCompiledQuery
);
287 PRUint16 j
, rulesCount
= set
->RuleCount();
288 for (j
= 0; j
< rulesCount
; ++j
) {
289 set
->GetRuleAt(j
)->Traverse(cb
);
294 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
296 NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsXULTemplateBuilder
,
297 nsIXULTemplateBuilder
)
298 NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUOUS(nsXULTemplateBuilder
,
299 nsIXULTemplateBuilder
)
301 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULTemplateBuilder
)
302 NS_INTERFACE_MAP_ENTRY(nsIXULTemplateBuilder
)
303 NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver
)
304 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver
)
305 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
306 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIXULTemplateBuilder
)
307 NS_INTERFACE_MAP_ENTRY_DOM_CLASSINFO(XULTemplateBuilder
)
310 //----------------------------------------------------------------------
312 // nsIXULTemplateBuilder methods
316 nsXULTemplateBuilder::GetRoot(nsIDOMElement
** aResult
)
319 return CallQueryInterface(mRoot
, aResult
);
326 nsXULTemplateBuilder::GetDatasource(nsISupports
** aResult
)
329 NS_ADDREF(*aResult
= mCompDB
);
331 NS_IF_ADDREF(*aResult
= mDataSource
);
336 nsXULTemplateBuilder::SetDatasource(nsISupports
* aResult
)
338 mDataSource
= aResult
;
339 mCompDB
= do_QueryInterface(mDataSource
);
345 nsXULTemplateBuilder::GetDatabase(nsIRDFCompositeDataSource
** aResult
)
347 NS_IF_ADDREF(*aResult
= mCompDB
);
352 nsXULTemplateBuilder::GetQueryProcessor(nsIXULTemplateQueryProcessor
** aResult
)
354 NS_IF_ADDREF(*aResult
= mQueryProcessor
.get());
359 nsXULTemplateBuilder::AddRuleFilter(nsIDOMNode
* aRule
, nsIXULTemplateRuleFilter
* aFilter
)
361 if (!aRule
|| !aFilter
)
362 return NS_ERROR_NULL_POINTER
;
364 // a custom rule filter may be added, one for each rule. If a new one is
365 // added, it replaces the old one. Look for the right rule and set its
368 PRInt32 count
= mQuerySets
.Length();
369 for (PRInt32 q
= 0; q
< count
; q
++) {
370 nsTemplateQuerySet
* queryset
= mQuerySets
[q
];
372 PRInt16 rulecount
= queryset
->RuleCount();
373 for (PRInt16 r
= 0; r
< rulecount
; r
++) {
374 nsTemplateRule
* rule
= queryset
->GetRuleAt(r
);
376 nsCOMPtr
<nsIDOMNode
> rulenode
;
377 rule
->GetRuleNode(getter_AddRefs(rulenode
));
378 if (aRule
== rulenode
) {
379 rule
->SetRuleFilter(aFilter
);
389 nsXULTemplateBuilder::Rebuild()
393 for (i
= mListeners
.Count() - 1; i
>= 0; --i
) {
394 mListeners
[i
]->WillRebuild(this);
397 nsresult rv
= RebuildAll();
399 for (i
= mListeners
.Count() - 1; i
>= 0; --i
) {
400 mListeners
[i
]->DidRebuild(this);
407 nsXULTemplateBuilder::Refresh()
412 return NS_ERROR_FAILURE
;
414 nsCOMPtr
<nsISimpleEnumerator
> dslist
;
415 rv
= mCompDB
->GetDataSources(getter_AddRefs(dslist
));
416 NS_ENSURE_SUCCESS(rv
, rv
);
419 nsCOMPtr
<nsISupports
> next
;
420 nsCOMPtr
<nsIRDFRemoteDataSource
> rds
;
422 while(NS_SUCCEEDED(dslist
->HasMoreElements(&hasMore
)) && hasMore
) {
423 dslist
->GetNext(getter_AddRefs(next
));
424 if (next
&& (rds
= do_QueryInterface(next
))) {
425 rds
->Refresh(PR_FALSE
);
429 // XXXbsmedberg: it would be kinda nice to install an async nsIRDFXMLSink
430 // observer and call rebuild() once the load is complete. See bug 254600.
436 nsXULTemplateBuilder::Init(nsIContent
* aElement
)
438 NS_ENSURE_TRUE(aElement
, NS_ERROR_NULL_POINTER
);
441 nsCOMPtr
<nsIDocument
> doc
= mRoot
->GetDocument();
442 NS_ASSERTION(doc
, "element has no document");
444 return NS_ERROR_UNEXPECTED
;
447 nsresult rv
= LoadDataSources(doc
, &shouldDelay
);
449 if (NS_SUCCEEDED(rv
)) {
450 // Add ourselves as a document observer
451 doc
->AddObserver(this);
453 mObservedDocument
= doc
;
454 gObserverService
->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC
,
462 nsXULTemplateBuilder::CreateContents(nsIContent
* aElement
, PRBool aForceCreation
)
468 nsXULTemplateBuilder::HasGeneratedContent(nsIRDFResource
* aResource
,
472 *aGenerated
= PR_FALSE
;
477 nsXULTemplateBuilder::AddResult(nsIXULTemplateResult
* aResult
,
478 nsIDOMNode
* aQueryNode
)
480 NS_ENSURE_ARG_POINTER(aResult
);
481 NS_ENSURE_ARG_POINTER(aQueryNode
);
483 return UpdateResult(nsnull
, aResult
, aQueryNode
);
487 nsXULTemplateBuilder::RemoveResult(nsIXULTemplateResult
* aResult
)
489 NS_ENSURE_ARG_POINTER(aResult
);
491 return UpdateResult(aResult
, nsnull
, nsnull
);
495 nsXULTemplateBuilder::ReplaceResult(nsIXULTemplateResult
* aOldResult
,
496 nsIXULTemplateResult
* aNewResult
,
497 nsIDOMNode
* aQueryNode
)
499 NS_ENSURE_ARG_POINTER(aOldResult
);
500 NS_ENSURE_ARG_POINTER(aNewResult
);
501 NS_ENSURE_ARG_POINTER(aQueryNode
);
503 // just remove the old result and then add a new result separately
505 nsresult rv
= UpdateResult(aOldResult
, nsnull
, nsnull
);
509 return UpdateResult(nsnull
, aNewResult
, aQueryNode
);
513 nsXULTemplateBuilder::UpdateResult(nsIXULTemplateResult
* aOldResult
,
514 nsIXULTemplateResult
* aNewResult
,
515 nsIDOMNode
* aQueryNode
)
517 PR_LOG(gXULTemplateLog
, PR_LOG_ALWAYS
,
518 ("nsXULTemplateBuilder::UpdateResult %p %p %p",
519 aOldResult
, aNewResult
, aQueryNode
));
521 // get the containers where content may be inserted. If
522 // GetInsertionLocations returns false, no container has generated
523 // any content yet so new content should not be generated either. This
524 // will be false if the result applies to content that is in a closed menu
525 // or treeitem for example.
527 nsAutoPtr
<nsCOMArray
<nsIContent
> > insertionPoints
;
528 PRBool mayReplace
= GetInsertionLocations(aOldResult
? aOldResult
: aNewResult
,
529 getter_Transfers(insertionPoints
));
535 nsCOMPtr
<nsIRDFResource
> oldId
, newId
;
536 nsTemplateQuerySet
* queryset
= nsnull
;
539 rv
= GetResultResource(aOldResult
, getter_AddRefs(oldId
));
543 // Ignore re-entrant builds for content that is currently in our
545 if (IsActivated(oldId
))
550 rv
= GetResultResource(aNewResult
, getter_AddRefs(newId
));
554 // skip results that don't have ids
558 // Ignore re-entrant builds for content that is currently in our
560 if (IsActivated(newId
))
563 // look for the queryset associated with the supplied query node
564 nsCOMPtr
<nsIContent
> querycontent
= do_QueryInterface(aQueryNode
);
566 PRInt32 count
= mQuerySets
.Length();
567 for (PRInt32 q
= 0; q
< count
; q
++) {
568 nsTemplateQuerySet
* qs
= mQuerySets
[q
];
569 if (qs
->mQueryNode
== querycontent
) {
579 if (insertionPoints
) {
580 // iterate over each insertion point and add or remove the result from
582 PRUint32 count
= insertionPoints
->Count();
583 for (PRUint32 t
= 0; t
< count
; t
++) {
584 nsCOMPtr
<nsIContent
> insertionPoint
= insertionPoints
->SafeObjectAt(t
);
585 if (insertionPoint
) {
586 rv
= UpdateResultInContainer(aOldResult
, aNewResult
, queryset
,
587 oldId
, newId
, insertionPoint
);
594 // The tree builder doesn't use insertion points, so no insertion
595 // points will be set. In this case, just update the one result.
596 rv
= UpdateResultInContainer(aOldResult
, aNewResult
, queryset
,
597 oldId
, newId
, nsnull
);
604 nsXULTemplateBuilder::UpdateResultInContainer(nsIXULTemplateResult
* aOldResult
,
605 nsIXULTemplateResult
* aNewResult
,
606 nsTemplateQuerySet
* aQuerySet
,
607 nsIRDFResource
* aOldId
,
608 nsIRDFResource
* aNewId
,
609 nsIContent
* aInsertionPoint
)
611 // This method takes a result that no longer applies (aOldResult) and
612 // replaces it with a new result (aNewResult). Either may be null
613 // indicating to just remove a result or add a new one without replacing.
615 // Matches are stored in the hashtable mMatchMap, keyed by result id. If
616 // there is more than one query, or the same id is found in different
617 // containers, the values in the hashtable will be a linked list of all
618 // the matches for that id. The matches are sorted according to the
619 // queries they are associated with. Matches for earlier queries in the
620 // template take priority over matches from later queries. The priority
621 // for a match is determined from the match's QuerySetPriority method.
622 // The first query has a priority 0, and higher numbers are for later
623 // queries with successively higher priorities. Thus, a match takes
624 // precedence if it has a lower priority than another. If there is only
625 // one query or container, then the match doesn't have any linked items.
627 // Matches are nsTemplateMatch objects. They are wrappers around
628 // nsIXULTemplateResult result objects and are created with
629 // nsTemplateMatch::Create below. The aQuerySet argument specifies which
630 // query the match is associated with.
632 // When a result id exists in multiple containers, the match's mContainer
633 // field is set to the container it corresponds to. The aInsertionPoint
634 // argument specifies which container is being updated. Even though they
635 // are stored in the same linked list as other matches of the same id, the
636 // matches for different containers are treated separately. They are only
637 // stored in the same hashtable to avoid a more complex data structure, as
638 // the use of the same id in multiple containers isn't a common occurance.
640 // Only one match with a given id per container is active at a time. When
641 // a match is active, content is generated for it. When a match is
642 // inactive, content is not generated for it. A match becomes active if
643 // another match with the same id and container with a lower priority
644 // isn't already active, and the match has a rule or conditions clause
645 // which evaluates to true. The former is checked by comparing the value
646 // of the QuerySetPriority method of the match with earlier matches. The
647 // latter is checked with the DetermineMatchedRule method.
649 // Naturally, if a match with a lower priority is active, it overrides
650 // the new match, so the new match is hooked up into the match linked
651 // list as inactive, and no content is generated for it. If a match with a
652 // higher priority is active, and the new match's conditions evaluate
653 // to true, then this existing match with the higher priority needs to have
654 // its generated content removed and replaced with the new match's
655 // generated content.
657 // Similar situations apply when removing an existing match. If the match
658 // is active, the existing generated content will need to be removed, and
659 // a match of higher priority that is revealed may become active and need
660 // to have content generated.
662 // Content removal and generation is done by the ReplaceMatch method which
663 // is overridden for the content builder and tree builder to update the
664 // generated output for each type.
666 // The code below handles all of the various cases and ensures that the
667 // match lists are maintained properly.
671 nsTemplateRule
* matchedrule
= nsnull
;
673 // Indicates that the old match was active and must have its content
675 PRBool oldMatchWasActive
= PR_FALSE
;
677 // acceptedmatch will be set to a new match that has to have new content
678 // generated for it. If a new match doesn't need to have content
679 // generated, (because for example, a match with a lower priority
680 // already applies), then acceptedmatch will be null, but the match will
681 // be still hooked up into the chain, since it may become active later
682 // as other results are updated.
683 nsTemplateMatch
* acceptedmatch
= nsnull
;
685 // When aOldResult is specified, removematch will be set to the
686 // corresponding match. This match needs to be deleted as it no longer
687 // applies. However, removedmatch will be null when aOldResult is null, or
688 // when no match was found corresponding to aOldResult.
689 nsTemplateMatch
* removedmatch
= nsnull
;
691 // These will be set when aNewResult is specified indicating to add a
692 // result, but will end up replacing an existing match. The former
693 // indicates a match being replaced that was active and had content
694 // generated for it, while the latter indicates a match that wasn't active
695 // and just needs to be deleted. Both may point to different matches. For
696 // example, if the new match becomes active, replacing an inactive match,
697 // the inactive match will need to be deleted. However, if another match
698 // with a higher priority is active, the new match will override it, so
699 // content will need to be generated for the new match and removed for
700 // this existing active match.
701 nsTemplateMatch
* replacedmatch
= nsnull
, * replacedmatchtodelete
= nsnull
;
704 nsTemplateMatch
* firstmatch
;
705 if (mMatchMap
.Get(aOldId
, &firstmatch
)) {
706 nsTemplateMatch
* oldmatch
= firstmatch
;
707 nsTemplateMatch
* prevmatch
= nsnull
;
709 // look for the right match if there was more than one
710 while (oldmatch
&& (oldmatch
->mResult
!= aOldResult
)) {
711 prevmatch
= oldmatch
;
712 oldmatch
= oldmatch
->mNext
;
716 nsTemplateMatch
* findmatch
= oldmatch
->mNext
;
718 // Keep a reference so that linked list can be hooked up at
719 // the end in case an error occurs.
720 nsTemplateMatch
* nextmatch
= findmatch
;
722 if (oldmatch
->IsActive()) {
723 // Indicate that the old match was active so its content
724 // will be removed later.
725 oldMatchWasActive
= PR_TRUE
;
727 // The match being removed is the active match, so scan
728 // through the later matches to determine if one should
729 // now become the active match.
731 // only other matches with the same container should
732 // now match, leave other containers alone
733 if (findmatch
->GetContainer() == aInsertionPoint
) {
734 nsTemplateQuerySet
* qs
=
735 mQuerySets
[findmatch
->QuerySetPriority()];
737 DetermineMatchedRule(aInsertionPoint
, findmatch
->mResult
,
738 qs
, &matchedrule
, &ruleindex
);
741 rv
= findmatch
->RuleMatched(qs
,
742 matchedrule
, ruleindex
,
747 acceptedmatch
= findmatch
;
752 findmatch
= findmatch
->mNext
;
756 if (oldmatch
== firstmatch
) {
757 // the match to remove is at the beginning
758 if (oldmatch
->mNext
) {
759 if (!mMatchMap
.Put(aOldId
, oldmatch
->mNext
))
760 return NS_ERROR_OUT_OF_MEMORY
;
763 mMatchMap
.Remove(aOldId
);
768 prevmatch
->mNext
= nextmatch
;
770 removedmatch
= oldmatch
;
776 // only allow a result to be inserted into containers with a matching tag
777 nsIAtom
* tag
= aQuerySet
->GetTag();
778 if (aInsertionPoint
&& tag
&& tag
!= aInsertionPoint
->Tag())
781 PRInt32 findpriority
= aQuerySet
->Priority();
783 nsTemplateMatch
*newmatch
=
784 nsTemplateMatch::Create(mPool
, findpriority
,
785 aNewResult
, aInsertionPoint
);
787 return NS_ERROR_OUT_OF_MEMORY
;
789 nsTemplateMatch
* firstmatch
;
790 if (mMatchMap
.Get(aNewId
, &firstmatch
)) {
791 PRBool hasEarlierActiveMatch
= PR_FALSE
;
793 // Scan through the existing matches to find where the new one
794 // should be inserted. oldmatch will be set to the old match for
795 // the same query and prevmatch will be set to the match before it.
796 nsTemplateMatch
* prevmatch
= nsnull
;
797 nsTemplateMatch
* oldmatch
= firstmatch
;
799 // Break out once we've reached a query in the list with a
800 // lower priority. The new match will be inserted at this
801 // location so that the match list is sorted by priority.
802 PRInt32 priority
= oldmatch
->QuerySetPriority();
803 if (priority
> findpriority
) {
808 // look for matches that belong in the same container
809 if (oldmatch
->GetContainer() == aInsertionPoint
) {
810 if (priority
== findpriority
)
813 // If a match with a lower priority is active, the new
814 // match can't replace it.
815 if (oldmatch
->IsActive())
816 hasEarlierActiveMatch
= PR_TRUE
;
819 prevmatch
= oldmatch
;
820 oldmatch
= oldmatch
->mNext
;
823 // At this point, oldmatch will either be null, or set to a match
824 // with the same container and priority. If set, oldmatch will
825 // need to be replaced by newmatch.
828 newmatch
->mNext
= oldmatch
->mNext
;
830 newmatch
->mNext
= prevmatch
->mNext
;
832 newmatch
->mNext
= firstmatch
;
834 // hasEarlierActiveMatch will be set to true if a match with a
835 // lower priority was found. The new match won't replace it in
836 // this case. If hasEarlierActiveMatch is false, then the new match
837 // may be become active if it matches one of the rules, and will
838 // generate output. It's also possible however, that a match with
839 // the same priority already exists, which means that the new match
840 // will replace the old one. In this case, oldmatch will be set to
841 // the old match. The content for the old match must be removed and
842 // content for the new match generated in its place.
843 if (! hasEarlierActiveMatch
) {
844 // If the old match was the active match, set replacedmatch to
845 // indicate that it needs its content removed.
847 if (oldmatch
->IsActive())
848 replacedmatch
= oldmatch
;
849 replacedmatchtodelete
= oldmatch
;
852 // check if the new result matches the rules
853 rv
= DetermineMatchedRule(aInsertionPoint
, newmatch
->mResult
,
854 aQuerySet
, &matchedrule
, &ruleindex
);
856 nsTemplateMatch::Destroy(mPool
, newmatch
, PR_FALSE
);
861 rv
= newmatch
->RuleMatched(aQuerySet
,
862 matchedrule
, ruleindex
,
865 nsTemplateMatch::Destroy(mPool
, newmatch
, PR_FALSE
);
869 // acceptedmatch may have been set in the block handling
870 // aOldResult earlier. If so, we would only get here when
871 // that match has a higher priority than this new match.
872 // As only one match can have content generated for it, it
873 // is OK to set acceptedmatch here to the new match,
874 // ignoring the other one.
875 acceptedmatch
= newmatch
;
877 // Clear the matched state of the later results for the
879 nsTemplateMatch
* clearmatch
= newmatch
->mNext
;
881 if (clearmatch
->GetContainer() == aInsertionPoint
&&
882 clearmatch
->IsActive()) {
883 clearmatch
->SetInactive();
884 // Replacedmatch should be null here. If not, it
885 // means that two matches were active which isn't
887 NS_ASSERTION(!replacedmatch
,
888 "replaced match already set");
889 replacedmatch
= clearmatch
;
892 clearmatch
= clearmatch
->mNext
;
895 else if (oldmatch
&& oldmatch
->IsActive()) {
896 // The result didn't match the rules, so look for a later
897 // one. However, only do this if the old match was the
899 newmatch
= newmatch
->mNext
;
901 if (newmatch
->GetContainer() == aInsertionPoint
) {
902 rv
= DetermineMatchedRule(aInsertionPoint
, newmatch
->mResult
,
903 aQuerySet
, &matchedrule
, &ruleindex
);
905 nsTemplateMatch::Destroy(mPool
, newmatch
,
911 rv
= newmatch
->RuleMatched(aQuerySet
,
912 matchedrule
, ruleindex
,
915 nsTemplateMatch::Destroy(mPool
, newmatch
,
920 acceptedmatch
= newmatch
;
925 newmatch
= newmatch
->mNext
;
929 // put the match in the map if there isn't a previous match
931 if (!mMatchMap
.Put(aNewId
, newmatch
)) {
932 // The match may have already matched a rule above, so
933 // HasBeenRemoved should be called to indicate that it
934 // is being removed again.
935 nsTemplateMatch::Destroy(mPool
, newmatch
, PR_TRUE
);
941 // hook up the match last in case an error occurs
943 prevmatch
->mNext
= newmatch
;
946 // The id is not used in the hashtable yet so create a new match
947 // and add it to the hashtable.
948 rv
= DetermineMatchedRule(aInsertionPoint
, aNewResult
,
949 aQuerySet
, &matchedrule
, &ruleindex
);
951 nsTemplateMatch::Destroy(mPool
, newmatch
, PR_FALSE
);
956 rv
= newmatch
->RuleMatched(aQuerySet
, matchedrule
,
957 ruleindex
, aNewResult
);
959 nsTemplateMatch::Destroy(mPool
, newmatch
, PR_FALSE
);
963 acceptedmatch
= newmatch
;
966 if (!mMatchMap
.Put(aNewId
, newmatch
)) {
967 nsTemplateMatch::Destroy(mPool
, newmatch
, PR_TRUE
);
968 return NS_ERROR_OUT_OF_MEMORY
;
973 // The ReplaceMatch method is builder specific and removes the generated
974 // content for a match.
976 // Remove the content for a match that was active and needs to be replaced.
978 rv
= ReplaceMatch(replacedmatch
->mResult
, nsnull
, nsnull
,
981 // remove a match that needs to be deleted.
982 if (replacedmatchtodelete
)
983 nsTemplateMatch::Destroy(mPool
, replacedmatchtodelete
, PR_TRUE
);
985 // If the old match was active, the content for it needs to be removed.
986 // If the old match was not active, it shouldn't have had any content,
987 // so just pass null to ReplaceMatch. If acceptedmatch was set, then
988 // content needs to be generated for a new match.
989 if (oldMatchWasActive
|| acceptedmatch
)
990 rv
= ReplaceMatch(oldMatchWasActive
? aOldResult
: nsnull
,
991 acceptedmatch
, matchedrule
, aInsertionPoint
);
993 // delete the old match that was replaced
995 nsTemplateMatch::Destroy(mPool
, removedmatch
, PR_TRUE
);
1001 nsXULTemplateBuilder::ResultBindingChanged(nsIXULTemplateResult
* aResult
)
1003 // A binding update is used when only the values of the bindings have
1004 // changed, so the same rule still applies. Just synchronize the content.
1005 // The new result will have the new values.
1006 NS_ENSURE_ARG_POINTER(aResult
);
1008 return SynchronizeResult(aResult
);
1012 nsXULTemplateBuilder::GetRootResult(nsIXULTemplateResult
** aResult
)
1014 *aResult
= mRootResult
;
1015 NS_IF_ADDREF(*aResult
);
1020 nsXULTemplateBuilder::GetResultForId(const nsAString
& aId
,
1021 nsIXULTemplateResult
** aResult
)
1024 return NS_ERROR_INVALID_ARG
;
1026 nsCOMPtr
<nsIRDFResource
> resource
;
1027 gRDFService
->GetUnicodeResource(aId
, getter_AddRefs(resource
));
1031 nsTemplateMatch
* match
;
1032 if (mMatchMap
.Get(resource
, &match
)) {
1033 // find the active match
1035 if (match
->IsActive()) {
1036 *aResult
= match
->mResult
;
1037 NS_IF_ADDREF(*aResult
);
1040 match
= match
->mNext
;
1048 nsXULTemplateBuilder::GetResultForContent(nsIDOMElement
* aContent
,
1049 nsIXULTemplateResult
** aResult
)
1056 nsXULTemplateBuilder::AddListener(nsIXULBuilderListener
* aListener
)
1058 NS_ENSURE_ARG(aListener
);
1060 if (!mListeners
.AppendObject(aListener
))
1061 return NS_ERROR_OUT_OF_MEMORY
;
1067 nsXULTemplateBuilder::RemoveListener(nsIXULBuilderListener
* aListener
)
1069 NS_ENSURE_ARG(aListener
);
1071 mListeners
.RemoveObject(aListener
);
1077 nsXULTemplateBuilder::Observe(nsISupports
* aSubject
,
1079 const PRUnichar
* aData
)
1081 // Uuuuber hack to clean up circular references that the cycle collector
1082 // doesn't know about. See bug 394514.
1083 if (!strcmp(aTopic
, DOM_WINDOW_DESTROYED_TOPIC
)) {
1084 nsCOMPtr
<nsPIDOMWindow
> window
= do_QueryInterface(aSubject
);
1086 nsCOMPtr
<nsIDocument
> doc
=
1087 do_QueryInterface(window
->GetExtantDocument());
1088 if (doc
&& doc
== mObservedDocument
)
1089 NodeWillBeDestroyed(doc
);
1094 //----------------------------------------------------------------------
1096 // nsIDocumentOberver interface
1100 nsXULTemplateBuilder::AttributeChanged(nsIDocument
* aDocument
,
1101 nsIContent
* aContent
,
1102 PRInt32 aNameSpaceID
,
1103 nsIAtom
* aAttribute
,
1105 PRUint32 aStateMask
)
1107 if (aContent
== mRoot
&& aNameSpaceID
== kNameSpaceID_None
) {
1108 // Check for a change to the 'ref' attribute on an atom, in which
1109 // case we may need to nuke and rebuild the entire content model
1110 // beneath the element.
1111 if (aAttribute
== nsGkAtoms::ref
)
1114 // Check for a change to the 'datasources' attribute. If so, setup
1115 // mDB by parsing the new value and rebuild.
1116 else if (aAttribute
== nsGkAtoms::datasources
) {
1117 Uninit(PR_FALSE
); // Reset results
1120 LoadDataSources(aDocument
, &shouldDelay
);
1128 nsXULTemplateBuilder::ContentRemoved(nsIDocument
* aDocument
,
1129 nsIContent
* aContainer
,
1131 PRInt32 aIndexInContainer
)
1133 if (mRoot
&& nsContentUtils::ContentIsDescendantOf(mRoot
, aChild
)) {
1134 nsRefPtr
<nsXULTemplateBuilder
> kungFuDeathGrip(this);
1136 if (mQueryProcessor
)
1137 mQueryProcessor
->Done();
1139 // use false since content is going away anyway
1142 aDocument
->RemoveObserver(this);
1144 nsCOMPtr
<nsIXULDocument
> xuldoc
= do_QueryInterface(aDocument
);
1146 xuldoc
->SetTemplateBuilderFor(mRoot
, nsnull
);
1148 // clear the template state when removing content so that template
1149 // content will be regenerated again if the content is reinserted
1150 nsXULElement
*xulcontent
= nsXULElement::FromContent(mRoot
);
1152 xulcontent
->ClearTemplateGenerated();
1157 mDataSource
= nsnull
;
1162 nsXULTemplateBuilder::NodeWillBeDestroyed(const nsINode
* aNode
)
1164 // The call to RemoveObserver could release the last reference to
1165 // |this|, so hold another reference.
1166 nsRefPtr
<nsXULTemplateBuilder
> kungFuDeathGrip(this);
1168 // Break circular references
1169 if (mQueryProcessor
)
1170 mQueryProcessor
->Done();
1172 mDataSource
= nsnull
;
1183 //----------------------------------------------------------------------
1185 // Implementation methods
1189 nsXULTemplateBuilder::LoadDataSources(nsIDocument
* aDocument
,
1190 PRBool
* aShouldDelayBuilding
)
1192 NS_PRECONDITION(mRoot
!= nsnull
, "not initialized");
1195 PRBool isRDFQuery
= PR_FALSE
;
1197 // we'll set these again later, after we create a new composite ds
1200 mDataSource
= nsnull
;
1202 *aShouldDelayBuilding
= PR_FALSE
;
1204 nsAutoString datasources
;
1205 mRoot
->GetAttr(kNameSpaceID_None
, nsGkAtoms::datasources
, datasources
);
1207 nsAutoString querytype
;
1208 mRoot
->GetAttr(kNameSpaceID_None
, nsGkAtoms::querytype
, querytype
);
1210 // create the query processor. The querytype attribute on the root element
1211 // may be used to create one of a specific type.
1213 // XXX should non-chrome be restricted to specific names?
1214 if (querytype
.IsEmpty())
1215 querytype
.AssignLiteral("rdf");
1217 if (querytype
.EqualsLiteral("rdf")) {
1218 isRDFQuery
= PR_TRUE
;
1219 mQueryProcessor
= new nsXULTemplateQueryProcessorRDF();
1220 NS_ENSURE_TRUE(mQueryProcessor
, NS_ERROR_OUT_OF_MEMORY
);
1222 else if (querytype
.EqualsLiteral("xml")) {
1223 mQueryProcessor
= new nsXULTemplateQueryProcessorXML();
1224 NS_ENSURE_TRUE(mQueryProcessor
, NS_ERROR_OUT_OF_MEMORY
);
1226 else if (querytype
.EqualsLiteral("storage")) {
1227 mQueryProcessor
= new nsXULTemplateQueryProcessorStorage();
1228 NS_ENSURE_TRUE(mQueryProcessor
, NS_ERROR_OUT_OF_MEMORY
);
1231 nsCAutoString
cid(NS_QUERY_PROCESSOR_CONTRACTID_PREFIX
);
1232 AppendUTF16toUTF8(querytype
, cid
);
1233 mQueryProcessor
= do_CreateInstance(cid
.get(), &rv
);
1234 // XXXndeakin log an error here - bug 321169
1235 NS_ENSURE_TRUE(mQueryProcessor
, rv
);
1238 rv
= LoadDataSourceUrls(aDocument
, datasources
,
1239 isRDFQuery
, aShouldDelayBuilding
);
1240 NS_ENSURE_SUCCESS(rv
, rv
);
1242 // Now set the database on the element, so that script writers can
1244 nsCOMPtr
<nsIXULDocument
> xuldoc
= do_QueryInterface(aDocument
);
1246 xuldoc
->SetTemplateBuilderFor(mRoot
, this);
1248 if (!mRoot
->IsNodeOfType(nsINode::eXUL
)) {
1249 // Hmm. This must be an HTML element. Try to set it as a
1250 // JS property "by hand".
1251 InitHTMLTemplateRoot();
1258 nsXULTemplateBuilder::LoadDataSourceUrls(nsIDocument
* aDocument
,
1259 const nsAString
& aDataSources
,
1261 PRBool
* aShouldDelayBuilding
)
1263 // Grab the doc's principal...
1264 nsIPrincipal
*docPrincipal
= aDocument
->NodePrincipal();
1266 NS_ASSERTION(docPrincipal
== mRoot
->NodePrincipal(),
1267 "Principal mismatch? Which one to use?");
1269 PRBool isTrusted
= PR_FALSE
;
1270 nsresult rv
= IsSystemPrincipal(docPrincipal
, &isTrusted
);
1271 NS_ENSURE_SUCCESS(rv
, rv
);
1273 // Parse datasources: they are assumed to be a whitespace
1274 // separated list of URIs; e.g.,
1276 // rdf:bookmarks rdf:history http://foo.bar.com/blah.cgi?baz=9
1278 nsIURI
*docurl
= aDocument
->GetDocumentURI();
1280 nsCOMPtr
<nsIMutableArray
> uriList
= do_CreateInstance(NS_ARRAY_CONTRACTID
);
1282 return NS_ERROR_FAILURE
;
1284 nsAutoString
datasources(aDataSources
);
1287 while (first
< datasources
.Length() && nsCRT::IsAsciiSpace(datasources
.CharAt(first
)))
1290 if (first
>= datasources
.Length())
1293 PRUint32 last
= first
;
1294 while (last
< datasources
.Length() && !nsCRT::IsAsciiSpace(datasources
.CharAt(last
)))
1297 nsAutoString uriStr
;
1298 datasources
.Mid(uriStr
, first
, last
- first
);
1301 // A special 'dummy' datasource
1302 if (uriStr
.EqualsLiteral("rdf:null"))
1305 if (uriStr
.CharAt(0) == '#') {
1306 // ok, the datasource is certainly a node of the current document
1307 nsCOMPtr
<nsIDOMDocument
> domdoc
= do_QueryInterface(aDocument
);
1308 nsCOMPtr
<nsIDOMElement
> dsnode
;
1310 domdoc
->GetElementById(Substring(uriStr
, 1),
1311 getter_AddRefs(dsnode
));
1314 uriList
->AppendElement(dsnode
, PR_FALSE
);
1318 // N.B. that `failure' (e.g., because it's an unknown
1319 // protocol) leaves uriStr unaltered.
1320 NS_MakeAbsoluteURI(uriStr
, uriStr
, docurl
);
1322 nsCOMPtr
<nsIURI
> uri
;
1323 rv
= NS_NewURI(getter_AddRefs(uri
), uriStr
);
1324 if (NS_FAILED(rv
) || !uri
)
1325 continue; // Necko will barf if our URI is weird
1327 nsCOMPtr
<nsIPrincipal
> principal
;
1329 // Our document is untrusted, so check to see if we can
1330 // load the datasource that they've asked for.
1332 rv
= gScriptSecurityManager
->GetCodebasePrincipal(uri
, getter_AddRefs(principal
));
1333 NS_ASSERTION(NS_SUCCEEDED(rv
), "unable to get codebase principal");
1334 NS_ENSURE_SUCCESS(rv
, rv
);
1337 rv
= docPrincipal
->Equals(principal
, &same
);
1338 NS_ASSERTION(NS_SUCCEEDED(rv
), "unable to test same origin");
1339 NS_ENSURE_SUCCESS(rv
, rv
);
1344 // If we get here, we've run the gauntlet, and the
1345 // datasource's URI has the same origin as our
1346 // document. Let it load!
1349 uriList
->AppendElement(uri
, PR_FALSE
);
1352 nsCOMPtr
<nsIDOMNode
> rootNode
= do_QueryInterface(mRoot
);
1353 rv
= mQueryProcessor
->GetDatasource(uriList
,
1357 aShouldDelayBuilding
,
1358 getter_AddRefs(mDataSource
));
1359 NS_ENSURE_SUCCESS(rv
, rv
);
1362 if (aIsRDFQuery
&& mDataSource
) {
1363 // check if we were given an inference engine type
1364 nsCOMPtr
<nsIRDFInferDataSource
> inferDB
= do_QueryInterface(mDataSource
);
1366 nsCOMPtr
<nsIRDFDataSource
> ds
;
1367 inferDB
->GetBaseDataSource(getter_AddRefs(ds
));
1369 mCompDB
= do_QueryInterface(ds
);
1373 mCompDB
= do_QueryInterface(mDataSource
);
1375 mDB
= do_QueryInterface(mDataSource
);
1378 if (!mDB
&& isTrusted
) {
1379 gRDFService
->GetDataSource("rdf:local-store", getter_AddRefs(mDB
));
1386 nsXULTemplateBuilder::InitHTMLTemplateRoot()
1388 // Use XPConnect and the JS APIs to whack mDB and this as the
1389 // 'database' and 'builder' properties onto aElement.
1392 nsCOMPtr
<nsIDocument
> doc
= mRoot
->GetDocument();
1393 NS_ASSERTION(doc
, "no document");
1395 return NS_ERROR_UNEXPECTED
;
1397 nsIScriptGlobalObject
*global
= doc
->GetScriptGlobalObject();
1399 return NS_ERROR_UNEXPECTED
;
1401 JSObject
*scope
= global
->GetGlobalJSObject();
1403 nsIScriptContext
*context
= global
->GetContext();
1405 return NS_ERROR_UNEXPECTED
;
1407 JSContext
* jscontext
= reinterpret_cast<JSContext
*>(context
->GetNativeContext());
1408 NS_ASSERTION(context
!= nsnull
, "no jscontext");
1410 return NS_ERROR_UNEXPECTED
;
1412 JSAutoRequest
ar(jscontext
);
1414 nsIXPConnect
*xpc
= nsContentUtils::XPConnect();
1416 JSObject
* jselement
= nsnull
;
1418 nsCOMPtr
<nsIXPConnectJSObjectHolder
> wrapper
;
1419 rv
= xpc
->WrapNative(jscontext
, scope
, mRoot
, NS_GET_IID(nsIDOMElement
),
1420 getter_AddRefs(wrapper
));
1421 NS_ENSURE_SUCCESS(rv
, rv
);
1423 rv
= wrapper
->GetJSObject(&jselement
);
1424 NS_ENSURE_SUCCESS(rv
, rv
);
1428 rv
= xpc
->WrapNative(jscontext
, scope
, mDB
,
1429 NS_GET_IID(nsIRDFCompositeDataSource
),
1430 getter_AddRefs(wrapper
));
1431 NS_ENSURE_SUCCESS(rv
, rv
);
1434 rv
= wrapper
->GetJSObject(&jsobj
);
1435 NS_ENSURE_SUCCESS(rv
, rv
);
1437 jsval jsdatabase
= OBJECT_TO_JSVAL(jsobj
);
1440 ok
= JS_SetProperty(jscontext
, jselement
, "database", &jsdatabase
);
1441 NS_ASSERTION(ok
, "unable to set database property");
1443 return NS_ERROR_FAILURE
;
1448 nsCOMPtr
<nsIXPConnectJSObjectHolder
> wrapper
;
1449 rv
= xpc
->WrapNative(jscontext
, jselement
,
1450 static_cast<nsIXULTemplateBuilder
*>(this),
1451 NS_GET_IID(nsIXULTemplateBuilder
),
1452 getter_AddRefs(wrapper
));
1453 NS_ENSURE_SUCCESS(rv
, rv
);
1456 rv
= wrapper
->GetJSObject(&jsobj
);
1457 NS_ENSURE_SUCCESS(rv
, rv
);
1459 jsval jsbuilder
= OBJECT_TO_JSVAL(jsobj
);
1462 ok
= JS_SetProperty(jscontext
, jselement
, "builder", &jsbuilder
);
1464 return NS_ERROR_FAILURE
;
1471 nsXULTemplateBuilder::DetermineMatchedRule(nsIContent
*aContainer
,
1472 nsIXULTemplateResult
* aResult
,
1473 nsTemplateQuerySet
* aQuerySet
,
1474 nsTemplateRule
** aMatchedRule
,
1475 PRInt16
*aRuleIndex
)
1477 // iterate through the rules and look for one that the result matches
1478 PRInt16 count
= aQuerySet
->RuleCount();
1479 for (PRInt16 r
= 0; r
< count
; r
++) {
1480 nsTemplateRule
* rule
= aQuerySet
->GetRuleAt(r
);
1481 // If a tag was specified, it must match the tag of the container
1482 // where content is being inserted.
1483 nsIAtom
* tag
= rule
->GetTag();
1484 if ((!aContainer
|| !tag
|| tag
== aContainer
->Tag()) &&
1485 rule
->CheckMatch(aResult
)) {
1486 *aMatchedRule
= rule
;
1493 *aMatchedRule
= nsnull
;
1498 nsXULTemplateBuilder::ParseAttribute(const nsAString
& aAttributeValue
,
1499 void (*aVariableCallback
)(nsXULTemplateBuilder
*, const nsAString
&, void*),
1500 void (*aTextCallback
)(nsXULTemplateBuilder
*, const nsAString
&, void*),
1503 nsAString::const_iterator done_parsing
;
1504 aAttributeValue
.EndReading(done_parsing
);
1506 nsAString::const_iterator iter
;
1507 aAttributeValue
.BeginReading(iter
);
1509 nsAString::const_iterator
mark(iter
), backup(iter
);
1511 for (; iter
!= done_parsing
; backup
= ++iter
) {
1512 // A variable is either prefixed with '?' (in the extended
1513 // syntax) or "rdf:" (in the simple syntax).
1515 if (*iter
== PRUnichar('?') && (++iter
!= done_parsing
)) {
1518 else if ((*iter
== PRUnichar('r') && (++iter
!= done_parsing
)) &&
1519 (*iter
== PRUnichar('d') && (++iter
!= done_parsing
)) &&
1520 (*iter
== PRUnichar('f') && (++iter
!= done_parsing
)) &&
1521 (*iter
== PRUnichar(':') && (++iter
!= done_parsing
))) {
1529 // It's not a variable, or we ran off the end of the
1530 // string after the initial variable prefix. Since we may
1531 // have slurped down some characters before realizing that
1532 // fact, back up to the point where we started.
1536 else if (backup
!= mark
&& aTextCallback
) {
1537 // Okay, we've found a variable, and there's some vanilla
1538 // text that's been buffered up. Flush it.
1539 (*aTextCallback
)(this, Substring(mark
, backup
), aClosure
);
1542 if (*iter
== PRUnichar('?')) {
1543 // Well, it was not really a variable, but "??". We use one
1544 // question mark (the second one, actually) literally.
1549 // Construct a substring that is the symbol we need to look up
1550 // in the rule's symbol table. The symbol is terminated by a
1551 // space character, a caret, or the end of the string,
1552 // whichever comes first.
1553 nsAString::const_iterator
first(backup
);
1556 while (iter
!= done_parsing
) {
1558 if ((c
== PRUnichar(' ')) || (c
== PRUnichar('^')))
1564 nsAString::const_iterator
last(iter
);
1566 // Back up so we don't consume the terminating character
1567 // *unless* the terminating character was a caret: the caret
1568 // means "concatenate with no space in between".
1569 if (c
!= PRUnichar('^'))
1572 (*aVariableCallback
)(this, Substring(first
, last
), aClosure
);
1577 if (backup
!= mark
&& aTextCallback
) {
1578 // If there's any text left over, then fire the text callback
1579 (*aTextCallback
)(this, Substring(mark
, backup
), aClosure
);
1584 struct NS_STACK_CLASS SubstituteTextClosure
{
1585 SubstituteTextClosure(nsIXULTemplateResult
* aResult
, nsAString
& aString
)
1586 : result(aResult
), str(aString
) {}
1588 // some datasources are lazily initialized or modified while values are
1589 // being retrieved, causing results to be removed. Due to this, hold a
1590 // strong reference to the result.
1591 nsCOMPtr
<nsIXULTemplateResult
> result
;
1596 nsXULTemplateBuilder::SubstituteText(nsIXULTemplateResult
* aResult
,
1597 const nsAString
& aAttributeValue
,
1600 // See if it's the special value "..."
1601 if (aAttributeValue
.EqualsLiteral("...")) {
1602 aResult
->GetId(aString
);
1606 // Reasonable guess at how big it should be
1607 aString
.SetCapacity(aAttributeValue
.Length());
1609 SubstituteTextClosure
closure(aResult
, aString
);
1610 ParseAttribute(aAttributeValue
,
1611 SubstituteTextReplaceVariable
,
1612 SubstituteTextAppendText
,
1620 nsXULTemplateBuilder::SubstituteTextAppendText(nsXULTemplateBuilder
* aThis
,
1621 const nsAString
& aText
,
1624 // Append aString to the closure's result
1625 SubstituteTextClosure
* c
= static_cast<SubstituteTextClosure
*>(aClosure
);
1626 c
->str
.Append(aText
);
1630 nsXULTemplateBuilder::SubstituteTextReplaceVariable(nsXULTemplateBuilder
* aThis
,
1631 const nsAString
& aVariable
,
1634 // Substitute the value for the variable and append to the
1635 // closure's result.
1636 SubstituteTextClosure
* c
= static_cast<SubstituteTextClosure
*>(aClosure
);
1638 nsAutoString replacementText
;
1640 // The symbol "rdf:*" is special, and means "this guy's URI"
1641 if (aVariable
.EqualsLiteral("rdf:*")){
1642 c
->result
->GetId(replacementText
);
1645 // Got a variable; get the value it's assigned to
1646 nsCOMPtr
<nsIAtom
> var
= do_GetAtom(aVariable
);
1647 c
->result
->GetBindingFor(var
, replacementText
);
1650 c
->str
+= replacementText
;
1654 nsXULTemplateBuilder::IsTemplateElement(nsIContent
* aContent
)
1656 return aContent
->NodeInfo()->Equals(nsGkAtoms::_template
,
1661 nsXULTemplateBuilder::GetTemplateRoot(nsIContent
** aResult
)
1663 NS_PRECONDITION(mRoot
!= nsnull
, "not initialized");
1665 return NS_ERROR_NOT_INITIALIZED
;
1667 // First, check and see if the root has a template attribute. This
1668 // allows a template to be specified "out of line"; e.g.,
1671 // <foo template="MyTemplate">...</foo>
1672 // <template id="MyTemplate">...</template>
1675 nsAutoString templateID
;
1676 mRoot
->GetAttr(kNameSpaceID_None
, nsGkAtoms::_template
, templateID
);
1678 if (! templateID
.IsEmpty()) {
1679 nsCOMPtr
<nsIDOMDocument
> domDoc
= do_QueryInterface(mRoot
->GetDocument());
1683 nsCOMPtr
<nsIDOMElement
> domElement
;
1684 domDoc
->GetElementById(templateID
, getter_AddRefs(domElement
));
1687 return CallQueryInterface(domElement
, aResult
);
1690 #if 1 // XXX hack to workaround bug with XBL insertion/removal?
1692 // If root node has no template attribute, then look for a child
1693 // node which is a template tag
1694 PRUint32 count
= mRoot
->GetChildCount();
1696 for (PRUint32 i
= 0; i
< count
; ++i
) {
1697 nsIContent
*child
= mRoot
->GetChildAt(i
);
1699 if (IsTemplateElement(child
)) {
1700 NS_ADDREF(*aResult
= child
);
1707 // If we couldn't find a real child, look through the anonymous
1709 nsCOMPtr
<nsIDocument
> doc
= mRoot
->GetDocument();
1713 nsCOMPtr
<nsIDOMNodeList
> kids
;
1714 doc
->BindingManager()->GetXBLChildNodesFor(mRoot
, getter_AddRefs(kids
));
1718 kids
->GetLength(&length
);
1720 for (PRUint32 i
= 0; i
< length
; ++i
) {
1721 nsCOMPtr
<nsIDOMNode
> node
;
1722 kids
->Item(i
, getter_AddRefs(node
));
1726 nsCOMPtr
<nsIContent
> child
= do_QueryInterface(node
);
1728 if (IsTemplateElement(child
)) {
1729 NS_ADDREF(*aResult
= child
.get());
1740 nsXULTemplateBuilder::CompileQueries()
1742 nsCOMPtr
<nsIContent
> tmpl
;
1743 GetTemplateRoot(getter_AddRefs(tmpl
));
1748 return NS_ERROR_NOT_INITIALIZED
;
1750 // Determine if there are any special settings we need to observe
1754 mRoot
->GetAttr(kNameSpaceID_None
, nsGkAtoms::flags
, flags
);
1756 // if the dont-test-empty flag is set, containers should not be checked to
1757 // see if they are empty. If dont-recurse is set, then don't process the
1758 // template recursively and only show one level of results.
1759 nsWhitespaceTokenizer
tokenizer(flags
);
1760 while (tokenizer
.hasMoreTokens()) {
1761 const nsDependentSubstring
& token(tokenizer
.nextToken());
1762 if (token
.EqualsLiteral("dont-test-empty"))
1763 mFlags
|= eDontTestEmpty
;
1764 else if (token
.EqualsLiteral("dont-recurse"))
1765 mFlags
|= eDontRecurse
;
1768 nsCOMPtr
<nsIDOMNode
> rootnode
= do_QueryInterface(mRoot
);
1770 mQueryProcessor
->InitializeForBuilding(mDataSource
, this, rootnode
);
1774 // Set the "container" and "member" variables, if the user has specified
1775 // them. The container variable may be specified with the container
1776 // attribute on the <template> and the member variable may be specified
1777 // using the member attribute or the value of the uri attribute inside the
1778 // first action body in the template. If not specified, the container
1779 // variable defaults to '?uri' and the member variable defaults to '?' or
1780 // 'rdf:*' for simple queries.
1782 // For RDF queries, the container variable may also be set via the
1785 nsAutoString containervar
;
1786 tmpl
->GetAttr(kNameSpaceID_None
, nsGkAtoms::container
, containervar
);
1788 if (containervar
.IsEmpty())
1789 mRefVariable
= do_GetAtom("?uri");
1791 mRefVariable
= do_GetAtom(containervar
);
1793 nsAutoString membervar
;
1794 tmpl
->GetAttr(kNameSpaceID_None
, nsGkAtoms::member
, membervar
);
1796 if (membervar
.IsEmpty())
1797 mMemberVariable
= nsnull
;
1799 mMemberVariable
= do_GetAtom(membervar
);
1801 nsTemplateQuerySet
* queryset
= new nsTemplateQuerySet(0);
1803 return NS_ERROR_OUT_OF_MEMORY
;
1805 if (!mQuerySets
.AppendElement(queryset
)) {
1807 return NS_ERROR_OUT_OF_MEMORY
;
1810 PRBool canUseTemplate
= PR_FALSE
;
1811 PRInt32 priority
= 0;
1812 rv
= CompileTemplate(tmpl
, queryset
, PR_FALSE
, &priority
, &canUseTemplate
);
1814 if (NS_FAILED(rv
) || !canUseTemplate
) {
1815 for (PRInt32 q
= mQuerySets
.Length() - 1; q
>= 0; q
--) {
1816 nsTemplateQuerySet
* qs
= mQuerySets
[q
];
1822 mQueriesCompiled
= PR_TRUE
;
1828 nsXULTemplateBuilder::CompileTemplate(nsIContent
* aTemplate
,
1829 nsTemplateQuerySet
* aQuerySet
,
1832 PRBool
* aCanUseTemplate
)
1834 NS_ASSERTION(aQuerySet
, "No queryset supplied");
1836 // XXXndeakin log syntax errors
1838 nsresult rv
= NS_OK
;
1840 PRBool isQuerySetMode
= PR_FALSE
;
1841 PRBool hasQuerySet
= PR_FALSE
, hasRule
= PR_FALSE
, hasQuery
= PR_FALSE
;
1843 PRUint32 count
= aTemplate
->GetChildCount();
1845 for (PRUint32 i
= 0; i
< count
; i
++) {
1846 nsIContent
*rulenode
= aTemplate
->GetChildAt(i
);
1847 nsINodeInfo
*ni
= rulenode
->NodeInfo();
1849 // don't allow more queries than can be supported
1850 if (*aPriority
== PR_INT16_MAX
)
1851 return NS_ERROR_FAILURE
;
1853 // XXXndeakin queryset isn't a good name for this tag since it only
1854 // ever contains one query
1855 if (!aIsQuerySet
&& ni
->Equals(nsGkAtoms::queryset
, kNameSpaceID_XUL
)) {
1856 if (hasRule
|| hasQuery
)
1859 isQuerySetMode
= PR_TRUE
;
1861 // only create a queryset for those after the first since the
1862 // first one is always created by CompileQueries
1864 aQuerySet
= new nsTemplateQuerySet(++*aPriority
);
1866 return NS_ERROR_OUT_OF_MEMORY
;
1868 // once the queryset is appended to the mQuerySets list, it
1869 // will be removed by CompileQueries if an error occurs
1870 if (!mQuerySets
.AppendElement(aQuerySet
)) {
1872 return NS_ERROR_OUT_OF_MEMORY
;
1876 hasQuerySet
= PR_TRUE
;
1878 rv
= CompileTemplate(rulenode
, aQuerySet
, PR_TRUE
, aPriority
, aCanUseTemplate
);
1883 // once a queryset is used, everything must be a queryset
1887 if (ni
->Equals(nsGkAtoms::rule
, kNameSpaceID_XUL
)) {
1888 nsCOMPtr
<nsIContent
> action
;
1889 nsXULContentUtils::FindChildByTag(rulenode
,
1892 getter_AddRefs(action
));
1895 nsCOMPtr
<nsIAtom
> memberVariable
;
1896 DetermineMemberVariable(action
, getter_AddRefs(memberVariable
));
1897 if (! memberVariable
) continue;
1900 nsCOMPtr
<nsIAtom
> tag
;
1901 DetermineRDFQueryRef(aQuerySet
->mQueryNode
,
1902 getter_AddRefs(tag
));
1904 aQuerySet
->SetTag(tag
);
1906 if (! aQuerySet
->mCompiledQuery
) {
1907 nsCOMPtr
<nsIDOMNode
> query(do_QueryInterface(aQuerySet
->mQueryNode
));
1909 rv
= mQueryProcessor
->CompileQuery(this, query
,
1910 mRefVariable
, memberVariable
,
1911 getter_AddRefs(aQuerySet
->mCompiledQuery
));
1916 if (aQuerySet
->mCompiledQuery
) {
1917 rv
= CompileExtendedQuery(rulenode
, action
, memberVariable
,
1922 *aCanUseTemplate
= PR_TRUE
;
1926 // backwards-compatible RDF template syntax where there is
1927 // an <action> node but no <query> node. In this case,
1928 // use the conditions as if it was the query.
1930 nsCOMPtr
<nsIContent
> conditions
;
1931 nsXULContentUtils::FindChildByTag(rulenode
,
1933 nsGkAtoms::conditions
,
1934 getter_AddRefs(conditions
));
1937 // create a new queryset if one hasn't been created already
1939 aQuerySet
= new nsTemplateQuerySet(++*aPriority
);
1941 return NS_ERROR_OUT_OF_MEMORY
;
1943 if (!mQuerySets
.AppendElement(aQuerySet
)) {
1945 return NS_ERROR_OUT_OF_MEMORY
;
1949 nsCOMPtr
<nsIAtom
> tag
;
1950 DetermineRDFQueryRef(conditions
, getter_AddRefs(tag
));
1952 aQuerySet
->SetTag(tag
);
1954 hasQuerySet
= PR_TRUE
;
1956 nsCOMPtr
<nsIDOMNode
> conditionsnode(do_QueryInterface(conditions
));
1958 aQuerySet
->mQueryNode
= conditions
;
1959 rv
= mQueryProcessor
->CompileQuery(this, conditionsnode
,
1962 getter_AddRefs(aQuerySet
->mCompiledQuery
));
1966 if (aQuerySet
->mCompiledQuery
) {
1967 rv
= CompileExtendedQuery(rulenode
, action
, memberVariable
,
1972 *aCanUseTemplate
= PR_TRUE
;
1981 // a new queryset must always be created in this case
1983 aQuerySet
= new nsTemplateQuerySet(++*aPriority
);
1985 return NS_ERROR_OUT_OF_MEMORY
;
1987 if (!mQuerySets
.AppendElement(aQuerySet
)) {
1989 return NS_ERROR_OUT_OF_MEMORY
;
1993 hasQuerySet
= PR_TRUE
;
1995 rv
= CompileSimpleQuery(rulenode
, aQuerySet
, aCanUseTemplate
);
2002 else if (ni
->Equals(nsGkAtoms::query
, kNameSpaceID_XUL
)) {
2006 aQuerySet
->mQueryNode
= rulenode
;
2009 else if (ni
->Equals(nsGkAtoms::action
, kNameSpaceID_XUL
)) {
2010 // the query must appear before the action
2014 nsCOMPtr
<nsIAtom
> tag
;
2015 DetermineRDFQueryRef(aQuerySet
->mQueryNode
, getter_AddRefs(tag
));
2017 aQuerySet
->SetTag(tag
);
2019 nsCOMPtr
<nsIAtom
> memberVariable
;
2020 DetermineMemberVariable(rulenode
, getter_AddRefs(memberVariable
));
2021 if (! memberVariable
) continue;
2023 nsCOMPtr
<nsIDOMNode
> query(do_QueryInterface(aQuerySet
->mQueryNode
));
2025 rv
= mQueryProcessor
->CompileQuery(this, query
,
2026 mRefVariable
, memberVariable
,
2027 getter_AddRefs(aQuerySet
->mCompiledQuery
));
2029 if (aQuerySet
->mCompiledQuery
) {
2030 nsTemplateRule
* rule
= new nsTemplateRule(aTemplate
, rulenode
, aQuerySet
);
2032 return NS_ERROR_OUT_OF_MEMORY
;
2034 rv
= aQuerySet
->AddRule(rule
);
2035 if (NS_FAILED(rv
)) {
2040 rule
->SetVars(mRefVariable
, memberVariable
);
2042 *aCanUseTemplate
= PR_TRUE
;
2049 if (! hasRule
&& ! hasQuery
&& ! hasQuerySet
) {
2050 // if no rules are specified in the template, then the contents of the
2051 // <template> tag are the one-and-only template.
2052 rv
= CompileSimpleQuery(aTemplate
, aQuerySet
, aCanUseTemplate
);
2059 nsXULTemplateBuilder::CompileExtendedQuery(nsIContent
* aRuleElement
,
2060 nsIContent
* aActionElement
,
2061 nsIAtom
* aMemberVariable
,
2062 nsTemplateQuerySet
* aQuerySet
)
2064 // Compile an "extended" <template> rule. An extended rule may have
2065 // a <conditions> child, an <action> child, and a <bindings> child.
2068 nsTemplateRule
* rule
= new nsTemplateRule(aRuleElement
, aActionElement
, aQuerySet
);
2070 return NS_ERROR_OUT_OF_MEMORY
;
2072 nsCOMPtr
<nsIContent
> conditions
;
2073 nsXULContentUtils::FindChildByTag(aRuleElement
,
2075 nsGkAtoms::conditions
,
2076 getter_AddRefs(conditions
));
2078 // allow the conditions to be placed directly inside the rule
2080 conditions
= aRuleElement
;
2082 rv
= CompileConditions(rule
, conditions
);
2083 // If the rule compilation failed, then we have to bail.
2084 if (NS_FAILED(rv
)) {
2089 rv
= aQuerySet
->AddRule(rule
);
2090 if (NS_FAILED(rv
)) {
2095 rule
->SetVars(mRefVariable
, aMemberVariable
);
2097 // If we've got bindings, add 'em.
2098 nsCOMPtr
<nsIContent
> bindings
;
2099 nsXULContentUtils::FindChildByTag(aRuleElement
,
2101 nsGkAtoms::bindings
,
2102 getter_AddRefs(bindings
));
2104 // allow bindings to be placed directly inside rule
2106 bindings
= aRuleElement
;
2108 rv
= CompileBindings(rule
, bindings
);
2109 NS_ENSURE_SUCCESS(rv
, rv
);
2115 nsXULTemplateBuilder::DetermineMemberVariable(nsIContent
* aActionElement
,
2116 nsIAtom
** aMemberVariable
)
2118 // If the member variable hasn't already been specified, then
2119 // grovel over <action> to find it. We'll use the first one
2120 // that we find in a breadth-first search.
2122 if (mMemberVariable
) {
2123 *aMemberVariable
= mMemberVariable
;
2124 NS_IF_ADDREF(*aMemberVariable
);
2127 *aMemberVariable
= nsnull
;
2129 nsCOMArray
<nsIContent
> unvisited
;
2131 if (!unvisited
.AppendObject(aActionElement
))
2132 return NS_ERROR_OUT_OF_MEMORY
;
2134 while (unvisited
.Count()) {
2135 nsIContent
* next
= unvisited
[0];
2136 unvisited
.RemoveObjectAt(0);
2139 next
->GetAttr(kNameSpaceID_None
, nsGkAtoms::uri
, uri
);
2141 if (!uri
.IsEmpty() && uri
[0] == PRUnichar('?')) {
2143 *aMemberVariable
= NS_NewAtom(uri
);
2147 // otherwise, append the children to the unvisited list: this
2148 // results in a breadth-first search.
2149 PRUint32 count
= next
->GetChildCount();
2151 for (PRUint32 i
= 0; i
< count
; ++i
) {
2152 nsIContent
*child
= next
->GetChildAt(i
);
2154 if (!unvisited
.AppendObject(child
))
2155 return NS_ERROR_OUT_OF_MEMORY
;
2164 nsXULTemplateBuilder::DetermineRDFQueryRef(nsIContent
* aQueryElement
, nsIAtom
** aTag
)
2167 nsCOMPtr
<nsIContent
> content
;
2168 nsXULContentUtils::FindChildByTag(aQueryElement
,
2171 getter_AddRefs(content
));
2174 // look for older treeitem syntax as well
2175 nsXULContentUtils::FindChildByTag(aQueryElement
,
2177 nsGkAtoms::treeitem
,
2178 getter_AddRefs(content
));
2183 content
->GetAttr(kNameSpaceID_None
, nsGkAtoms::uri
, uri
);
2186 mRefVariable
= do_GetAtom(uri
);
2189 content
->GetAttr(kNameSpaceID_None
, nsGkAtoms::tag
, tag
);
2192 *aTag
= NS_NewAtom(tag
);
2197 nsXULTemplateBuilder::CompileSimpleQuery(nsIContent
* aRuleElement
,
2198 nsTemplateQuerySet
* aQuerySet
,
2199 PRBool
* aCanUseTemplate
)
2201 // compile a simple query, which is a query with no <query> or
2202 // <conditions>. This means that a default query is used.
2203 nsCOMPtr
<nsIDOMNode
> query(do_QueryInterface(aRuleElement
));
2205 nsCOMPtr
<nsIAtom
> memberVariable
;
2206 if (mMemberVariable
)
2207 memberVariable
= mMemberVariable
;
2209 memberVariable
= do_GetAtom("rdf:*");
2211 // since there is no <query> node for a simple query, the query node will
2212 // be either the <rule> node if multiple rules are used, or the <template> node.
2213 aQuerySet
->mQueryNode
= aRuleElement
;
2214 nsresult rv
= mQueryProcessor
->CompileQuery(this, query
,
2215 mRefVariable
, memberVariable
,
2216 getter_AddRefs(aQuerySet
->mCompiledQuery
));
2220 if (! aQuerySet
->mCompiledQuery
) {
2221 *aCanUseTemplate
= PR_FALSE
;
2225 nsTemplateRule
* rule
= new nsTemplateRule(aRuleElement
, aRuleElement
, aQuerySet
);
2227 return NS_ERROR_OUT_OF_MEMORY
;
2229 rv
= aQuerySet
->AddRule(rule
);
2230 if (NS_FAILED(rv
)) {
2235 rule
->SetVars(mRefVariable
, memberVariable
);
2238 aRuleElement
->GetAttr(kNameSpaceID_None
, nsGkAtoms::parent
, tag
);
2240 if (!tag
.IsEmpty()) {
2241 nsCOMPtr
<nsIAtom
> tagatom
= do_GetAtom(tag
);
2242 aQuerySet
->SetTag(tagatom
);
2245 *aCanUseTemplate
= PR_TRUE
;
2247 return AddSimpleRuleBindings(rule
, aRuleElement
);
2251 nsXULTemplateBuilder::CompileConditions(nsTemplateRule
* aRule
,
2252 nsIContent
* aCondition
)
2255 aCondition
->GetAttr(kNameSpaceID_None
, nsGkAtoms::parent
, tag
);
2257 if (!tag
.IsEmpty()) {
2258 nsCOMPtr
<nsIAtom
> tagatom
= do_GetAtom(tag
);
2259 aRule
->SetTag(tagatom
);
2262 PRUint32 count
= aCondition
->GetChildCount();
2264 nsTemplateCondition
* currentCondition
= nsnull
;
2266 for (PRUint32 i
= 0; i
< count
; i
++) {
2267 nsIContent
*node
= aCondition
->GetChildAt(i
);
2269 if (node
->NodeInfo()->Equals(nsGkAtoms::where
, kNameSpaceID_XUL
)) {
2270 nsresult rv
= CompileWhereCondition(aRule
, node
, ¤tCondition
);
2280 nsXULTemplateBuilder::CompileWhereCondition(nsTemplateRule
* aRule
,
2281 nsIContent
* aCondition
,
2282 nsTemplateCondition
** aCurrentCondition
)
2284 // Compile a <where> condition, which must be of the form:
2286 // <where subject="?var1|string" rel="relation" value="?var2|string" />
2288 // The value of rel may be:
2289 // equal - subject must be equal to object
2290 // notequal - subject must not be equal to object
2291 // less - subject must be less than object
2292 // greater - subject must be greater than object
2293 // startswith - subject must start with object
2294 // endswith - subject must end with object
2295 // contains - subject must contain object
2296 // Comparisons are done as strings unless the subject is an integer.
2299 nsAutoString subject
;
2300 aCondition
->GetAttr(kNameSpaceID_None
, nsGkAtoms::subject
, subject
);
2301 if (subject
.IsEmpty())
2304 nsCOMPtr
<nsIAtom
> svar
;
2305 if (subject
[0] == PRUnichar('?'))
2306 svar
= do_GetAtom(subject
);
2308 nsAutoString relstring
;
2309 aCondition
->GetAttr(kNameSpaceID_None
, nsGkAtoms::rel
, relstring
);
2310 if (relstring
.IsEmpty())
2315 aCondition
->GetAttr(kNameSpaceID_None
, nsGkAtoms::value
, value
);
2316 if (value
.IsEmpty())
2320 PRBool shouldMultiple
=
2321 aCondition
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::multiple
,
2322 nsGkAtoms::_true
, eCaseMatters
);
2324 nsCOMPtr
<nsIAtom
> vvar
;
2325 if (!shouldMultiple
&& (value
[0] == PRUnichar('?'))) {
2326 vvar
= do_GetAtom(value
);
2330 PRBool shouldIgnoreCase
=
2331 aCondition
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::ignorecase
,
2332 nsGkAtoms::_true
, eCaseMatters
);
2335 PRBool shouldNegate
=
2336 aCondition
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::negate
,
2337 nsGkAtoms::_true
, eCaseMatters
);
2339 nsTemplateCondition
* condition
;
2342 condition
= new nsTemplateCondition(svar
, relstring
, vvar
,
2343 shouldIgnoreCase
, shouldNegate
);
2345 else if (svar
&& !value
.IsEmpty()) {
2346 condition
= new nsTemplateCondition(svar
, relstring
, value
,
2347 shouldIgnoreCase
, shouldNegate
, shouldMultiple
);
2350 condition
= new nsTemplateCondition(subject
, relstring
, vvar
,
2351 shouldIgnoreCase
, shouldNegate
);
2354 PR_LOG(gXULTemplateLog
, PR_LOG_ALWAYS
,
2355 ("xultemplate[%p] on <where> test, expected at least one variable", this));
2360 return NS_ERROR_OUT_OF_MEMORY
;
2362 if (*aCurrentCondition
) {
2363 (*aCurrentCondition
)->SetNext(condition
);
2366 aRule
->SetCondition(condition
);
2369 *aCurrentCondition
= condition
;
2375 nsXULTemplateBuilder::CompileBindings(nsTemplateRule
* aRule
, nsIContent
* aBindings
)
2377 // Add an extended rule's bindings.
2380 PRUint32 count
= aBindings
->GetChildCount();
2382 for (PRUint32 i
= 0; i
< count
; ++i
) {
2383 nsIContent
*binding
= aBindings
->GetChildAt(i
);
2385 if (binding
->NodeInfo()->Equals(nsGkAtoms::binding
,
2386 kNameSpaceID_XUL
)) {
2387 rv
= CompileBinding(aRule
, binding
);
2391 nsAutoString tagstr
;
2392 binding
->NodeInfo()->GetQualifiedName(tagstr
);
2394 nsCAutoString tagstrC
;
2395 tagstrC
.AssignWithConversion(tagstr
);
2396 PR_LOG(gXULTemplateLog
, PR_LOG_ALWAYS
,
2397 ("xultemplate[%p] unrecognized binding <%s>",
2398 this, tagstrC
.get()));
2408 aRule
->AddBindingsToQueryProcessor(mQueryProcessor
);
2415 nsXULTemplateBuilder::CompileBinding(nsTemplateRule
* aRule
,
2416 nsIContent
* aBinding
)
2418 // Compile a <binding> "condition", which must be of the form:
2420 // <binding subject="?var1"
2421 // predicate="resource"
2422 // object="?var2" />
2424 // XXXwaterson Some day it would be cool to allow the 'predicate'
2425 // to be bound to a variable.
2428 nsAutoString subject
;
2429 aBinding
->GetAttr(kNameSpaceID_None
, nsGkAtoms::subject
, subject
);
2431 if (subject
.IsEmpty()) {
2432 PR_LOG(gXULTemplateLog
, PR_LOG_ALWAYS
,
2433 ("xultemplate[%p] <binding> requires `subject'", this));
2438 nsCOMPtr
<nsIAtom
> svar
;
2439 if (subject
[0] == PRUnichar('?')) {
2440 svar
= do_GetAtom(subject
);
2443 PR_LOG(gXULTemplateLog
, PR_LOG_ALWAYS
,
2444 ("xultemplate[%p] <binding> requires `subject' to be a variable", this));
2450 nsAutoString predicate
;
2451 aBinding
->GetAttr(kNameSpaceID_None
, nsGkAtoms::predicate
, predicate
);
2452 if (predicate
.IsEmpty()) {
2453 PR_LOG(gXULTemplateLog
, PR_LOG_ALWAYS
,
2454 ("xultemplate[%p] <binding> requires `predicate'", this));
2460 nsAutoString object
;
2461 aBinding
->GetAttr(kNameSpaceID_None
, nsGkAtoms::object
, object
);
2463 if (object
.IsEmpty()) {
2464 PR_LOG(gXULTemplateLog
, PR_LOG_ALWAYS
,
2465 ("xultemplate[%p] <binding> requires `object'", this));
2470 nsCOMPtr
<nsIAtom
> ovar
;
2471 if (object
[0] == PRUnichar('?')) {
2472 ovar
= do_GetAtom(object
);
2475 PR_LOG(gXULTemplateLog
, PR_LOG_ALWAYS
,
2476 ("xultemplate[%p] <binding> requires `object' to be a variable", this));
2481 return aRule
->AddBinding(svar
, predicate
, ovar
);
2485 nsXULTemplateBuilder::AddSimpleRuleBindings(nsTemplateRule
* aRule
,
2486 nsIContent
* aElement
)
2488 // Crawl the content tree of a "simple" rule, adding a variable
2489 // assignment for any attribute whose value is "rdf:".
2491 nsAutoVoidArray elements
;
2493 if (!elements
.AppendElement(aElement
))
2494 return NS_ERROR_OUT_OF_MEMORY
;
2496 while (elements
.Count()) {
2497 // Pop the next element off the stack
2498 PRUint32 i
= (PRUint32
)(elements
.Count() - 1);
2499 nsIContent
* element
= static_cast<nsIContent
*>(elements
[i
]);
2500 elements
.RemoveElementAt(i
);
2502 // Iterate through its attributes, looking for substitutions
2503 // that we need to add as bindings.
2504 PRUint32 count
= element
->GetAttrCount();
2506 for (i
= 0; i
< count
; ++i
) {
2507 const nsAttrName
* name
= element
->GetAttrNameAt(i
);
2509 if (!name
->Equals(nsGkAtoms::id
, kNameSpaceID_None
) &&
2510 !name
->Equals(nsGkAtoms::uri
, kNameSpaceID_None
)) {
2512 element
->GetAttr(name
->NamespaceID(), name
->LocalName(), value
);
2514 // Scan the attribute for variables, adding a binding for
2516 ParseAttribute(value
, AddBindingsFor
, nsnull
, aRule
);
2520 // Push kids onto the stack, and search them next.
2521 count
= element
->GetChildCount();
2523 while (count
-- > 0) {
2524 if (!elements
.AppendElement(element
->GetChildAt(count
)))
2525 return NS_ERROR_OUT_OF_MEMORY
;
2529 aRule
->AddBindingsToQueryProcessor(mQueryProcessor
);
2535 nsXULTemplateBuilder::AddBindingsFor(nsXULTemplateBuilder
* aThis
,
2536 const nsAString
& aVariable
,
2539 // We should *only* be recieving "rdf:"-style variables. Make
2541 if (!StringBeginsWith(aVariable
, NS_LITERAL_STRING("rdf:")))
2544 nsTemplateRule
* rule
= static_cast<nsTemplateRule
*>(aClosure
);
2546 nsCOMPtr
<nsIAtom
> var
= do_GetAtom(aVariable
);
2548 // Strip it down to the raw RDF property by clobbering the "rdf:"
2550 nsAutoString property
;
2551 property
.Assign(Substring(aVariable
, PRUint32(4), aVariable
.Length() - 4));
2553 if (! rule
->HasBinding(rule
->GetMemberVariable(), property
, var
))
2554 // In the simple syntax, the binding is always from the
2555 // member variable, through the property, to the target.
2556 rule
->AddBinding(rule
->GetMemberVariable(), property
, var
);
2561 nsXULTemplateBuilder::IsSystemPrincipal(nsIPrincipal
*principal
, PRBool
*result
)
2563 if (!gSystemPrincipal
)
2564 return NS_ERROR_UNEXPECTED
;
2566 *result
= (principal
== gSystemPrincipal
);
2571 nsXULTemplateBuilder::IsActivated(nsIRDFResource
*aResource
)
2573 for (ActivationEntry
*entry
= mTop
;
2575 entry
= entry
->mPrevious
) {
2576 if (entry
->mResource
== aResource
)
2583 nsXULTemplateBuilder::GetResultResource(nsIXULTemplateResult
* aResult
,
2584 nsIRDFResource
** aResource
)
2586 // get the resource for a result by checking its resource property. If it
2587 // is not set, check the id. This allows non-chrome implementations to
2588 // avoid having to use RDF.
2589 nsresult rv
= aResult
->GetResource(aResource
);
2595 rv
= aResult
->GetId(id
);
2599 return gRDFService
->GetUnicodeResource(id
, aResource
);