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