michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsTemplateRule.h" michael@0: #include "nsTemplateMatch.h" michael@0: #include "nsXULContentUtils.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsICollation.h" michael@0: michael@0: nsTemplateCondition::nsTemplateCondition(nsIAtom* aSourceVariable, michael@0: const nsAString& aRelation, michael@0: nsIAtom* aTargetVariable, michael@0: bool aIgnoreCase, michael@0: bool aNegate) michael@0: : mSourceVariable(aSourceVariable), michael@0: mTargetVariable(aTargetVariable), michael@0: mIgnoreCase(aIgnoreCase), michael@0: mNegate(aNegate), michael@0: mNext(nullptr) michael@0: { michael@0: SetRelation(aRelation); michael@0: michael@0: MOZ_COUNT_CTOR(nsTemplateCondition); michael@0: } michael@0: michael@0: nsTemplateCondition::nsTemplateCondition(nsIAtom* aSourceVariable, michael@0: const nsAString& aRelation, michael@0: const nsAString& aTargets, michael@0: bool aIgnoreCase, michael@0: bool aNegate, michael@0: bool aIsMultiple) michael@0: : mSourceVariable(aSourceVariable), michael@0: mIgnoreCase(aIgnoreCase), michael@0: mNegate(aNegate), michael@0: mNext(nullptr) michael@0: { michael@0: SetRelation(aRelation); michael@0: michael@0: if (aIsMultiple) { michael@0: int32_t start = 0, end = 0; michael@0: while ((end = aTargets.FindChar(',',start)) >= 0) { michael@0: if (end > start) { michael@0: mTargetList.AppendElement(Substring(aTargets, start, end - start)); michael@0: } michael@0: start = end + 1; michael@0: } michael@0: if (start < int32_t(aTargets.Length())) { michael@0: mTargetList.AppendElement(Substring(aTargets, start)); michael@0: } michael@0: } michael@0: else { michael@0: mTargetList.AppendElement(aTargets); michael@0: } michael@0: michael@0: MOZ_COUNT_CTOR(nsTemplateCondition); michael@0: } michael@0: michael@0: nsTemplateCondition::nsTemplateCondition(const nsAString& aSource, michael@0: const nsAString& aRelation, michael@0: nsIAtom* aTargetVariable, michael@0: bool aIgnoreCase, michael@0: bool aNegate) michael@0: : mSource(aSource), michael@0: mTargetVariable(aTargetVariable), michael@0: mIgnoreCase(aIgnoreCase), michael@0: mNegate(aNegate), michael@0: mNext(nullptr) michael@0: { michael@0: SetRelation(aRelation); michael@0: michael@0: MOZ_COUNT_CTOR(nsTemplateCondition); michael@0: } michael@0: michael@0: void michael@0: nsTemplateCondition::SetRelation(const nsAString& aRelation) michael@0: { michael@0: if (aRelation.EqualsLiteral("equals") || aRelation.IsEmpty()) michael@0: mRelation = eEquals; michael@0: else if (aRelation.EqualsLiteral("less")) michael@0: mRelation = eLess; michael@0: else if (aRelation.EqualsLiteral("greater")) michael@0: mRelation = eGreater; michael@0: else if (aRelation.EqualsLiteral("before")) michael@0: mRelation = eBefore; michael@0: else if (aRelation.EqualsLiteral("after")) michael@0: mRelation = eAfter; michael@0: else if (aRelation.EqualsLiteral("startswith")) michael@0: mRelation = eStartswith; michael@0: else if (aRelation.EqualsLiteral("endswith")) michael@0: mRelation = eEndswith; michael@0: else if (aRelation.EqualsLiteral("contains")) michael@0: mRelation = eContains; michael@0: else michael@0: mRelation = eUnknown; michael@0: } michael@0: michael@0: bool michael@0: nsTemplateCondition::CheckMatch(nsIXULTemplateResult* aResult) michael@0: { michael@0: bool match = false; michael@0: michael@0: nsAutoString leftString; michael@0: if (mSourceVariable) michael@0: aResult->GetBindingFor(mSourceVariable, leftString); michael@0: else michael@0: leftString.Assign(mSource); michael@0: michael@0: if (mTargetVariable) { michael@0: nsAutoString rightString; michael@0: aResult->GetBindingFor(mTargetVariable, rightString); michael@0: michael@0: match = CheckMatchStrings(leftString, rightString); michael@0: } michael@0: else { michael@0: // iterate over the strings in the target and determine michael@0: // whether there is a match. michael@0: uint32_t length = mTargetList.Length(); michael@0: for (uint32_t t = 0; t < length; t++) { michael@0: match = CheckMatchStrings(leftString, mTargetList[t]); michael@0: michael@0: // stop once a match is found. In negate mode, stop once a michael@0: // target does not match. michael@0: if (match != mNegate) break; michael@0: } michael@0: } michael@0: michael@0: return match; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsTemplateCondition::CheckMatchStrings(const nsAString& aLeftString, michael@0: const nsAString& aRightString) michael@0: { michael@0: bool match = false; michael@0: michael@0: if (aRightString.IsEmpty()) { michael@0: if ((mRelation == eEquals) && aLeftString.IsEmpty()) michael@0: match = true; michael@0: } michael@0: else { michael@0: switch (mRelation) { michael@0: case eEquals: michael@0: if (mIgnoreCase) michael@0: match = aLeftString.Equals(aRightString, michael@0: nsCaseInsensitiveStringComparator()); michael@0: else michael@0: match = aLeftString.Equals(aRightString); michael@0: break; michael@0: michael@0: case eLess: michael@0: case eGreater: michael@0: { michael@0: // non-numbers always compare false michael@0: nsresult err; michael@0: int32_t leftint = PromiseFlatString(aLeftString).ToInteger(&err); michael@0: if (NS_SUCCEEDED(err)) { michael@0: int32_t rightint = PromiseFlatString(aRightString).ToInteger(&err); michael@0: if (NS_SUCCEEDED(err)) { michael@0: match = (mRelation == eLess) ? (leftint < rightint) : michael@0: (leftint > rightint); michael@0: } michael@0: } michael@0: michael@0: break; michael@0: } michael@0: michael@0: case eBefore: michael@0: { michael@0: nsICollation* collation = nsXULContentUtils::GetCollation(); michael@0: if (collation) { michael@0: int32_t sortOrder; michael@0: collation->CompareString((mIgnoreCase ? michael@0: static_cast(nsICollation::kCollationCaseInSensitive) : michael@0: static_cast(nsICollation::kCollationCaseSensitive)), michael@0: aLeftString, michael@0: aRightString, michael@0: &sortOrder); michael@0: match = (sortOrder < 0); michael@0: } michael@0: else if (mIgnoreCase) { michael@0: match = (Compare(aLeftString, aRightString, michael@0: nsCaseInsensitiveStringComparator()) < 0); michael@0: } michael@0: else { michael@0: match = (Compare(aLeftString, aRightString) < 0); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case eAfter: michael@0: { michael@0: nsICollation* collation = nsXULContentUtils::GetCollation(); michael@0: if (collation) { michael@0: int32_t sortOrder; michael@0: collation->CompareString((mIgnoreCase ? michael@0: static_cast(nsICollation::kCollationCaseInSensitive) : michael@0: static_cast(nsICollation::kCollationCaseSensitive)), michael@0: aLeftString, michael@0: aRightString, michael@0: &sortOrder); michael@0: match = (sortOrder > 0); michael@0: } michael@0: else if (mIgnoreCase) { michael@0: match = (Compare(aLeftString, aRightString, michael@0: nsCaseInsensitiveStringComparator()) > 0); michael@0: } michael@0: else { michael@0: match = (Compare(aLeftString, aRightString) > 0); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case eStartswith: michael@0: if (mIgnoreCase) michael@0: match = (StringBeginsWith(aLeftString, aRightString, michael@0: nsCaseInsensitiveStringComparator())); michael@0: else michael@0: match = (StringBeginsWith(aLeftString, aRightString)); michael@0: break; michael@0: michael@0: case eEndswith: michael@0: if (mIgnoreCase) michael@0: match = (StringEndsWith(aLeftString, aRightString, michael@0: nsCaseInsensitiveStringComparator())); michael@0: else michael@0: match = (StringEndsWith(aLeftString, aRightString)); michael@0: break; michael@0: michael@0: case eContains: michael@0: { michael@0: nsAString::const_iterator start, end; michael@0: aLeftString.BeginReading(start); michael@0: aLeftString.EndReading(end); michael@0: if (mIgnoreCase) michael@0: match = CaseInsensitiveFindInReadable(aRightString, start, end); michael@0: else michael@0: match = FindInReadable(aRightString, start, end); michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (mNegate) match = !match; michael@0: michael@0: return match; michael@0: } michael@0: michael@0: nsTemplateRule::nsTemplateRule(nsIContent* aRuleNode, michael@0: nsIContent* aAction, michael@0: nsTemplateQuerySet* aQuerySet) michael@0: : mQuerySet(aQuerySet), michael@0: mAction(aAction), michael@0: mBindings(nullptr), michael@0: mConditions(nullptr) michael@0: { michael@0: MOZ_COUNT_CTOR(nsTemplateRule); michael@0: mRuleNode = do_QueryInterface(aRuleNode); michael@0: } michael@0: michael@0: nsTemplateRule::nsTemplateRule(const nsTemplateRule& aOtherRule) michael@0: : mQuerySet(aOtherRule.mQuerySet), michael@0: mRuleNode(aOtherRule.mRuleNode), michael@0: mAction(aOtherRule.mAction), michael@0: mBindings(nullptr), michael@0: mConditions(nullptr) michael@0: { michael@0: MOZ_COUNT_CTOR(nsTemplateRule); michael@0: } michael@0: michael@0: nsTemplateRule::~nsTemplateRule() michael@0: { michael@0: MOZ_COUNT_DTOR(nsTemplateRule); michael@0: michael@0: while (mBindings) { michael@0: Binding* doomed = mBindings; michael@0: mBindings = mBindings->mNext; michael@0: delete doomed; michael@0: } michael@0: michael@0: while (mConditions) { michael@0: nsTemplateCondition* cdel = mConditions; michael@0: mConditions = mConditions->GetNext(); michael@0: delete cdel; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTemplateRule::GetRuleNode(nsIDOMNode** aRuleNode) const michael@0: { michael@0: *aRuleNode = mRuleNode; michael@0: NS_IF_ADDREF(*aRuleNode); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsTemplateRule::SetCondition(nsTemplateCondition* aCondition) michael@0: { michael@0: while (mConditions) { michael@0: nsTemplateCondition* cdel = mConditions; michael@0: mConditions = mConditions->GetNext(); michael@0: delete cdel; michael@0: } michael@0: michael@0: mConditions = aCondition; michael@0: } michael@0: michael@0: bool michael@0: nsTemplateRule::CheckMatch(nsIXULTemplateResult* aResult) const michael@0: { michael@0: // check the conditions in the rule first michael@0: nsTemplateCondition* condition = mConditions; michael@0: while (condition) { michael@0: if (!condition->CheckMatch(aResult)) michael@0: return false; michael@0: michael@0: condition = condition->GetNext(); michael@0: } michael@0: michael@0: if (mRuleFilter) { michael@0: // if a rule filter was set, check it for a match. If an error occurs, michael@0: // assume that the match was acceptable michael@0: bool match; michael@0: nsresult rv = mRuleFilter->Match(aResult, mRuleNode, &match); michael@0: return NS_FAILED(rv) || match; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsTemplateRule::HasBinding(nsIAtom* aSourceVariable, michael@0: nsAString& aExpr, michael@0: nsIAtom* aTargetVariable) const michael@0: { michael@0: for (Binding* binding = mBindings; binding != nullptr; binding = binding->mNext) { michael@0: if ((binding->mSourceVariable == aSourceVariable) && michael@0: (binding->mExpr.Equals(aExpr)) && michael@0: (binding->mTargetVariable == aTargetVariable)) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: nsTemplateRule::AddBinding(nsIAtom* aSourceVariable, michael@0: nsAString& aExpr, michael@0: nsIAtom* aTargetVariable) michael@0: { michael@0: NS_PRECONDITION(aSourceVariable != 0, "no source variable!"); michael@0: if (! aSourceVariable) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: NS_PRECONDITION(aTargetVariable != 0, "no target variable!"); michael@0: if (! aTargetVariable) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: NS_ASSERTION(! HasBinding(aSourceVariable, aExpr, aTargetVariable), michael@0: "binding added twice"); michael@0: michael@0: Binding* newbinding = new Binding; michael@0: if (! newbinding) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: newbinding->mSourceVariable = aSourceVariable; michael@0: newbinding->mTargetVariable = aTargetVariable; michael@0: newbinding->mParent = nullptr; michael@0: michael@0: newbinding->mExpr.Assign(aExpr); michael@0: michael@0: Binding* binding = mBindings; michael@0: Binding** link = &mBindings; michael@0: michael@0: // Insert it at the end, unless we detect that an existing michael@0: // binding's source is dependent on the newbinding's target. michael@0: // michael@0: // XXXwaterson this isn't enough to make sure that we get all of michael@0: // the dependencies worked out right, but it'll do for now. For michael@0: // example, if you have (ab, bc, cd), and insert them in the order michael@0: // (cd, ab, bc), you'll get (bc, cd, ab). The good news is, if the michael@0: // person uses a natural ordering when writing the XUL, it'll all michael@0: // work out ok. michael@0: while (binding) { michael@0: if (binding->mSourceVariable == newbinding->mTargetVariable) { michael@0: binding->mParent = newbinding; michael@0: break; michael@0: } michael@0: else if (binding->mTargetVariable == newbinding->mSourceVariable) { michael@0: newbinding->mParent = binding; michael@0: } michael@0: michael@0: link = &binding->mNext; michael@0: binding = binding->mNext; michael@0: } michael@0: michael@0: // Insert the newbinding michael@0: *link = newbinding; michael@0: newbinding->mNext = binding; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTemplateRule::AddBindingsToQueryProcessor(nsIXULTemplateQueryProcessor* aProcessor) michael@0: { michael@0: Binding* binding = mBindings; michael@0: michael@0: while (binding) { michael@0: nsresult rv = aProcessor->AddBinding(mRuleNode, binding->mTargetVariable, michael@0: binding->mSourceVariable, binding->mExpr); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: binding = binding->mNext; michael@0: } michael@0: michael@0: return NS_OK; michael@0: }