1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsTemplateRule.h"
7 #include "nsTemplateMatch.h"
8 #include "nsXULContentUtils.h"
9 #include "nsUnicharUtils.h"
10 #include "nsReadableUtils.h"
11 #include "nsICollation.h"
12 
nsTemplateCondition(nsIAtom * aSourceVariable,const nsAString & aRelation,nsIAtom * aTargetVariable,bool aIgnoreCase,bool aNegate)13 nsTemplateCondition::nsTemplateCondition(nsIAtom* aSourceVariable,
14                                          const nsAString& aRelation,
15                                          nsIAtom* aTargetVariable,
16                                          bool aIgnoreCase,
17                                          bool aNegate)
18     : mSourceVariable(aSourceVariable),
19       mTargetVariable(aTargetVariable),
20       mIgnoreCase(aIgnoreCase),
21       mNegate(aNegate),
22       mNext(nullptr)
23 {
24     SetRelation(aRelation);
25 
26     MOZ_COUNT_CTOR(nsTemplateCondition);
27 }
28 
nsTemplateCondition(nsIAtom * aSourceVariable,const nsAString & aRelation,const nsAString & aTargets,bool aIgnoreCase,bool aNegate,bool aIsMultiple)29 nsTemplateCondition::nsTemplateCondition(nsIAtom* aSourceVariable,
30                                          const nsAString& aRelation,
31                                          const nsAString& aTargets,
32                                          bool aIgnoreCase,
33                                          bool aNegate,
34                                          bool aIsMultiple)
35     : mSourceVariable(aSourceVariable),
36       mIgnoreCase(aIgnoreCase),
37       mNegate(aNegate),
38       mNext(nullptr)
39 {
40     SetRelation(aRelation);
41 
42     if (aIsMultiple) {
43         int32_t start = 0, end = 0;
44         while ((end = aTargets.FindChar(',',start)) >= 0) {
45             if (end > start) {
46                 mTargetList.AppendElement(Substring(aTargets, start, end - start));
47             }
48             start = end + 1;
49         }
50         if (start < int32_t(aTargets.Length())) {
51             mTargetList.AppendElement(Substring(aTargets, start));
52         }
53     }
54     else {
55         mTargetList.AppendElement(aTargets);
56     }
57 
58     MOZ_COUNT_CTOR(nsTemplateCondition);
59 }
60 
nsTemplateCondition(const nsAString & aSource,const nsAString & aRelation,nsIAtom * aTargetVariable,bool aIgnoreCase,bool aNegate)61 nsTemplateCondition::nsTemplateCondition(const nsAString& aSource,
62                                          const nsAString& aRelation,
63                                          nsIAtom* aTargetVariable,
64                                          bool aIgnoreCase,
65                                          bool aNegate)
66     : mSource(aSource),
67       mTargetVariable(aTargetVariable),
68       mIgnoreCase(aIgnoreCase),
69       mNegate(aNegate),
70       mNext(nullptr)
71 {
72     SetRelation(aRelation);
73 
74     MOZ_COUNT_CTOR(nsTemplateCondition);
75 }
76 
77 void
SetRelation(const nsAString & aRelation)78 nsTemplateCondition::SetRelation(const nsAString& aRelation)
79 {
80     if (aRelation.EqualsLiteral("equals") || aRelation.IsEmpty())
81         mRelation = eEquals;
82     else if (aRelation.EqualsLiteral("less"))
83         mRelation = eLess;
84     else if (aRelation.EqualsLiteral("greater"))
85         mRelation = eGreater;
86     else if (aRelation.EqualsLiteral("before"))
87         mRelation = eBefore;
88     else if (aRelation.EqualsLiteral("after"))
89         mRelation = eAfter;
90     else if (aRelation.EqualsLiteral("startswith"))
91         mRelation = eStartswith;
92     else if (aRelation.EqualsLiteral("endswith"))
93         mRelation = eEndswith;
94     else if (aRelation.EqualsLiteral("contains"))
95         mRelation = eContains;
96     else
97         mRelation = eUnknown;
98 }
99 
100 bool
CheckMatch(nsIXULTemplateResult * aResult)101 nsTemplateCondition::CheckMatch(nsIXULTemplateResult* aResult)
102 {
103     bool match = false;
104 
105     nsAutoString leftString;
106     if (mSourceVariable)
107       aResult->GetBindingFor(mSourceVariable, leftString);
108     else
109       leftString.Assign(mSource);
110 
111     if (mTargetVariable) {
112         nsAutoString rightString;
113         aResult->GetBindingFor(mTargetVariable, rightString);
114 
115         match = CheckMatchStrings(leftString, rightString);
116     }
117     else {
118         // iterate over the strings in the target and determine
119         // whether there is a match.
120         uint32_t length = mTargetList.Length();
121         for (uint32_t t = 0; t < length; t++) {
122             match = CheckMatchStrings(leftString, mTargetList[t]);
123 
124             // stop once a match is found. In negate mode, stop once a
125             // target does not match.
126             if (match != mNegate) break;
127         }
128     }
129 
130     return match;
131 }
132 
133 
134 bool
CheckMatchStrings(const nsAString & aLeftString,const nsAString & aRightString)135 nsTemplateCondition::CheckMatchStrings(const nsAString& aLeftString,
136                                        const nsAString& aRightString)
137 {
138     bool match = false;
139 
140     if (aRightString.IsEmpty()) {
141         if ((mRelation == eEquals) && aLeftString.IsEmpty())
142             match = true;
143     }
144     else {
145         switch (mRelation) {
146             case eEquals:
147                 if (mIgnoreCase)
148                     match = aLeftString.Equals(aRightString,
149                                                nsCaseInsensitiveStringComparator());
150                 else
151                     match = aLeftString.Equals(aRightString);
152                 break;
153 
154             case eLess:
155             case eGreater:
156             {
157                 // non-numbers always compare false
158                 nsresult err;
159                 int32_t leftint = PromiseFlatString(aLeftString).ToInteger(&err);
160                 if (NS_SUCCEEDED(err)) {
161                     int32_t rightint = PromiseFlatString(aRightString).ToInteger(&err);
162                     if (NS_SUCCEEDED(err)) {
163                         match = (mRelation == eLess) ? (leftint < rightint) :
164                                                        (leftint > rightint);
165                     }
166                 }
167 
168                 break;
169             }
170 
171             case eBefore:
172             {
173                 nsICollation* collation = nsXULContentUtils::GetCollation();
174                 if (collation) {
175                     int32_t sortOrder;
176                     collation->CompareString((mIgnoreCase ?
177                                               static_cast<int32_t>(nsICollation::kCollationCaseInSensitive) :
178                                               static_cast<int32_t>(nsICollation::kCollationCaseSensitive)),
179                                               aLeftString,
180                                               aRightString,
181                                               &sortOrder);
182                     match = (sortOrder < 0);
183                 }
184                 else if (mIgnoreCase) {
185                     match = (Compare(aLeftString, aRightString,
186                                      nsCaseInsensitiveStringComparator()) < 0);
187                 }
188                 else {
189                     match = (Compare(aLeftString, aRightString) < 0);
190                 }
191                 break;
192             }
193 
194             case eAfter:
195             {
196                 nsICollation* collation = nsXULContentUtils::GetCollation();
197                 if (collation) {
198                     int32_t sortOrder;
199                     collation->CompareString((mIgnoreCase ?
200                                               static_cast<int32_t>(nsICollation::kCollationCaseInSensitive) :
201                                               static_cast<int32_t>(nsICollation::kCollationCaseSensitive)),
202                                               aLeftString,
203                                               aRightString,
204                                               &sortOrder);
205                     match = (sortOrder > 0);
206                 }
207                 else if (mIgnoreCase) {
208                     match = (Compare(aLeftString, aRightString,
209                                      nsCaseInsensitiveStringComparator()) > 0);
210                 }
211                 else {
212                     match = (Compare(aLeftString, aRightString) > 0);
213                 }
214                 break;
215             }
216 
217             case eStartswith:
218                 if (mIgnoreCase)
219                     match = (StringBeginsWith(aLeftString, aRightString,
220                                               nsCaseInsensitiveStringComparator()));
221                 else
222                     match = (StringBeginsWith(aLeftString, aRightString));
223                 break;
224 
225             case eEndswith:
226                 if (mIgnoreCase)
227                     match = (StringEndsWith(aLeftString, aRightString,
228                                             nsCaseInsensitiveStringComparator()));
229                 else
230                     match = (StringEndsWith(aLeftString, aRightString));
231                 break;
232 
233             case eContains:
234             {
235                 nsAString::const_iterator start, end;
236                 aLeftString.BeginReading(start);
237                 aLeftString.EndReading(end);
238                 if (mIgnoreCase)
239                     match = CaseInsensitiveFindInReadable(aRightString, start, end);
240                 else
241                     match = FindInReadable(aRightString, start, end);
242                 break;
243             }
244 
245             default:
246                 break;
247         }
248     }
249 
250     if (mNegate) match = !match;
251 
252     return match;
253 }
254 
nsTemplateRule(nsIContent * aRuleNode,nsIContent * aAction,nsTemplateQuerySet * aQuerySet)255 nsTemplateRule::nsTemplateRule(nsIContent* aRuleNode,
256                                nsIContent* aAction,
257                                nsTemplateQuerySet* aQuerySet)
258         : mQuerySet(aQuerySet),
259           mAction(aAction),
260           mBindings(nullptr),
261           mConditions(nullptr)
262 {
263     MOZ_COUNT_CTOR(nsTemplateRule);
264     mRuleNode = do_QueryInterface(aRuleNode);
265 }
266 
nsTemplateRule(const nsTemplateRule & aOtherRule)267 nsTemplateRule::nsTemplateRule(const nsTemplateRule& aOtherRule)
268         : mQuerySet(aOtherRule.mQuerySet),
269           mRuleNode(aOtherRule.mRuleNode),
270           mAction(aOtherRule.mAction),
271           mBindings(nullptr),
272           mConditions(nullptr)
273 {
274     MOZ_COUNT_CTOR(nsTemplateRule);
275 }
276 
~nsTemplateRule()277 nsTemplateRule::~nsTemplateRule()
278 {
279     MOZ_COUNT_DTOR(nsTemplateRule);
280 
281     while (mBindings) {
282         Binding* doomed = mBindings;
283         mBindings = mBindings->mNext;
284         delete doomed;
285     }
286 
287     while (mConditions) {
288         nsTemplateCondition* cdel = mConditions;
289         mConditions = mConditions->GetNext();
290         delete cdel;
291     }
292 }
293 
294 nsresult
GetRuleNode(nsIDOMNode ** aRuleNode) const295 nsTemplateRule::GetRuleNode(nsIDOMNode** aRuleNode) const
296 {
297     *aRuleNode = mRuleNode;
298     NS_IF_ADDREF(*aRuleNode);
299     return NS_OK;
300 }
301 
SetCondition(nsTemplateCondition * aCondition)302 void nsTemplateRule::SetCondition(nsTemplateCondition* aCondition)
303 {
304     while (mConditions) {
305         nsTemplateCondition* cdel = mConditions;
306         mConditions = mConditions->GetNext();
307         delete cdel;
308     }
309 
310     mConditions = aCondition;
311 }
312 
313 bool
CheckMatch(nsIXULTemplateResult * aResult) const314 nsTemplateRule::CheckMatch(nsIXULTemplateResult* aResult) const
315 {
316     // check the conditions in the rule first
317     nsTemplateCondition* condition = mConditions;
318     while (condition) {
319         if (!condition->CheckMatch(aResult))
320             return false;
321 
322         condition = condition->GetNext();
323     }
324 
325     if (mRuleFilter) {
326         // if a rule filter was set, check it for a match. If an error occurs,
327         // assume that the match was acceptable
328         bool match;
329         nsresult rv = mRuleFilter->Match(aResult, mRuleNode, &match);
330         return NS_FAILED(rv) || match;
331     }
332 
333     return true;
334 }
335 
336 bool
HasBinding(nsIAtom * aSourceVariable,nsAString & aExpr,nsIAtom * aTargetVariable) const337 nsTemplateRule::HasBinding(nsIAtom* aSourceVariable,
338                            nsAString& aExpr,
339                            nsIAtom* aTargetVariable) const
340 {
341     for (Binding* binding = mBindings; binding != nullptr; binding = binding->mNext) {
342         if ((binding->mSourceVariable == aSourceVariable) &&
343             (binding->mExpr.Equals(aExpr)) &&
344             (binding->mTargetVariable == aTargetVariable))
345             return true;
346     }
347 
348     return false;
349 }
350 
351 nsresult
AddBinding(nsIAtom * aSourceVariable,nsAString & aExpr,nsIAtom * aTargetVariable)352 nsTemplateRule::AddBinding(nsIAtom* aSourceVariable,
353                            nsAString& aExpr,
354                            nsIAtom* aTargetVariable)
355 {
356     NS_PRECONDITION(aSourceVariable != 0, "no source variable!");
357     if (! aSourceVariable)
358         return NS_ERROR_INVALID_ARG;
359 
360     NS_PRECONDITION(aTargetVariable != 0, "no target variable!");
361     if (! aTargetVariable)
362         return NS_ERROR_INVALID_ARG;
363 
364     NS_ASSERTION(! HasBinding(aSourceVariable, aExpr, aTargetVariable),
365                  "binding added twice");
366 
367     Binding* newbinding = new Binding;
368     if (! newbinding)
369         return NS_ERROR_OUT_OF_MEMORY;
370 
371     newbinding->mSourceVariable = aSourceVariable;
372     newbinding->mTargetVariable = aTargetVariable;
373     newbinding->mParent         = nullptr;
374 
375     newbinding->mExpr.Assign(aExpr);
376 
377     Binding* binding = mBindings;
378     Binding** link = &mBindings;
379 
380     // Insert it at the end, unless we detect that an existing
381     // binding's source is dependent on the newbinding's target.
382     //
383     // XXXwaterson this isn't enough to make sure that we get all of
384     // the dependencies worked out right, but it'll do for now. For
385     // example, if you have (ab, bc, cd), and insert them in the order
386     // (cd, ab, bc), you'll get (bc, cd, ab). The good news is, if the
387     // person uses a natural ordering when writing the XUL, it'll all
388     // work out ok.
389     while (binding) {
390         if (binding->mSourceVariable == newbinding->mTargetVariable) {
391             binding->mParent = newbinding;
392             break;
393         }
394         else if (binding->mTargetVariable == newbinding->mSourceVariable) {
395             newbinding->mParent = binding;
396         }
397 
398         link = &binding->mNext;
399         binding = binding->mNext;
400     }
401 
402     // Insert the newbinding
403     *link = newbinding;
404     newbinding->mNext = binding;
405     return NS_OK;
406 }
407 
408 nsresult
AddBindingsToQueryProcessor(nsIXULTemplateQueryProcessor * aProcessor)409 nsTemplateRule::AddBindingsToQueryProcessor(nsIXULTemplateQueryProcessor* aProcessor)
410 {
411     Binding* binding = mBindings;
412 
413     while (binding) {
414         nsresult rv = aProcessor->AddBinding(mRuleNode, binding->mTargetVariable,
415                                              binding->mSourceVariable, binding->mExpr);
416         if (NS_FAILED(rv)) return rv;
417 
418         binding = binding->mNext;
419     }
420 
421     return NS_OK;
422 }
423