michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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: * This Original Code has been modified by IBM Corporation. michael@0: * Modifications made by IBM described herein are michael@0: * Copyright (c) International Business Machines michael@0: * Corporation, 2000 michael@0: * michael@0: * Modifications to Mozilla code or documentation michael@0: * identified per MPL Section 3.3 michael@0: * michael@0: * Date Modified by Description of modification michael@0: * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink michael@0: * use in OS2 michael@0: */ michael@0: michael@0: /* michael@0: This file provides the implementation for the sort service manager. michael@0: */ michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsXULContentUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsQuickSort.h" michael@0: #include "nsWhitespaceTokenizer.h" michael@0: #include "nsXULSortService.h" michael@0: #include "nsIDOMXULElement.h" michael@0: #include "nsIXULTemplateBuilder.h" michael@0: #include "nsTemplateMatch.h" michael@0: #include "nsICollation.h" michael@0: #include "nsUnicharUtils.h" michael@0: michael@0: NS_IMPL_ISUPPORTS(XULSortServiceImpl, nsIXULSortService) michael@0: michael@0: void michael@0: XULSortServiceImpl::SetSortHints(nsIContent *aNode, nsSortState* aSortState) michael@0: { michael@0: // set sort and sortDirection attributes when is sort is done michael@0: aNode->SetAttr(kNameSpaceID_None, nsGkAtoms::sort, michael@0: aSortState->sort, true); michael@0: michael@0: nsAutoString direction; michael@0: if (aSortState->direction == nsSortState_descending) michael@0: direction.AssignLiteral("descending"); michael@0: else if (aSortState->direction == nsSortState_ascending) michael@0: direction.AssignLiteral("ascending"); michael@0: aNode->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, michael@0: direction, true); michael@0: michael@0: // for trees, also set the sort info on the currently sorted column michael@0: if (aNode->NodeInfo()->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) { michael@0: if (aSortState->sortKeys.Count() >= 1) { michael@0: nsAutoString sortkey; michael@0: aSortState->sortKeys[0]->ToString(sortkey); michael@0: SetSortColumnHints(aNode, sortkey, direction); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: XULSortServiceImpl::SetSortColumnHints(nsIContent *content, michael@0: const nsAString &sortResource, michael@0: const nsAString &sortDirection) michael@0: { michael@0: // set sort info on current column. This ensures that the michael@0: // column header sort indicator is updated properly. michael@0: for (nsIContent* child = content->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: if (child->IsXUL()) { michael@0: nsIAtom *tag = child->Tag(); michael@0: michael@0: if (tag == nsGkAtoms::treecols) { michael@0: SetSortColumnHints(child, sortResource, sortDirection); michael@0: } else if (tag == nsGkAtoms::treecol) { michael@0: nsAutoString value; michael@0: child->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, value); michael@0: // also check the resource attribute for older code michael@0: if (value.IsEmpty()) michael@0: child->GetAttr(kNameSpaceID_None, nsGkAtoms::resource, value); michael@0: if (value == sortResource) { michael@0: child->SetAttr(kNameSpaceID_None, nsGkAtoms::sortActive, michael@0: NS_LITERAL_STRING("true"), true); michael@0: child->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, michael@0: sortDirection, true); michael@0: // Note: don't break out of loop; want to set/unset michael@0: // attribs on ALL sort columns michael@0: } else if (!value.IsEmpty()) { michael@0: child->UnsetAttr(kNameSpaceID_None, nsGkAtoms::sortActive, michael@0: true); michael@0: child->UnsetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, michael@0: true); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: XULSortServiceImpl::GetItemsToSort(nsIContent *aContainer, michael@0: nsSortState* aSortState, michael@0: nsTArray& aSortItems) michael@0: { michael@0: // if there is a template attached to the sort node, use the builder to get michael@0: // the items to be sorted michael@0: nsCOMPtr element = do_QueryInterface(aContainer); michael@0: if (element) { michael@0: nsCOMPtr builder; michael@0: element->GetBuilder(getter_AddRefs(builder)); michael@0: michael@0: if (builder) { michael@0: nsresult rv = builder->GetQueryProcessor(getter_AddRefs(aSortState->processor)); michael@0: if (NS_FAILED(rv) || !aSortState->processor) michael@0: return rv; michael@0: michael@0: return GetTemplateItemsToSort(aContainer, builder, aSortState, aSortItems); michael@0: } michael@0: } michael@0: michael@0: // if there is no template builder, just get the children. For trees, michael@0: // get the treechildren element as use that as the parent michael@0: nsCOMPtr treechildren; michael@0: if (aContainer->NodeInfo()->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) { michael@0: nsXULContentUtils::FindChildByTag(aContainer, michael@0: kNameSpaceID_XUL, michael@0: nsGkAtoms::treechildren, michael@0: getter_AddRefs(treechildren)); michael@0: if (!treechildren) michael@0: return NS_OK; michael@0: michael@0: aContainer = treechildren; michael@0: } michael@0: michael@0: for (nsIContent* child = aContainer->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: contentSortInfo* cinfo = aSortItems.AppendElement(); michael@0: if (!cinfo) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: cinfo->content = child; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: XULSortServiceImpl::GetTemplateItemsToSort(nsIContent* aContainer, michael@0: nsIXULTemplateBuilder* aBuilder, michael@0: nsSortState* aSortState, michael@0: nsTArray& aSortItems) michael@0: { michael@0: for (nsIContent* child = aContainer->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: michael@0: nsCOMPtr childnode = do_QueryInterface(child); michael@0: michael@0: nsCOMPtr result; michael@0: nsresult rv = aBuilder->GetResultForContent(childnode, getter_AddRefs(result)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (result) { michael@0: contentSortInfo* cinfo = aSortItems.AppendElement(); michael@0: if (!cinfo) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: cinfo->content = child; michael@0: cinfo->result = result; michael@0: } michael@0: else if (aContainer->Tag() != nsGkAtoms::_template) { michael@0: rv = GetTemplateItemsToSort(child, aBuilder, aSortState, aSortItems); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: int michael@0: testSortCallback(const void *data1, const void *data2, void *privateData) michael@0: { michael@0: /// Note: testSortCallback is a small C callback stub for NS_QuickSort michael@0: contentSortInfo *left = (contentSortInfo *)data1; michael@0: contentSortInfo *right = (contentSortInfo *)data2; michael@0: nsSortState* sortState = (nsSortState *)privateData; michael@0: michael@0: int32_t sortOrder = 0; michael@0: michael@0: if (sortState->direction == nsSortState_natural && sortState->processor) { michael@0: // sort in natural order michael@0: sortState->processor->CompareResults(left->result, right->result, michael@0: nullptr, sortState->sortHints, &sortOrder); michael@0: } michael@0: else { michael@0: int32_t length = sortState->sortKeys.Count(); michael@0: for (int32_t t = 0; t < length; t++) { michael@0: // for templates, use the query processor to do sorting michael@0: if (sortState->processor) { michael@0: sortState->processor->CompareResults(left->result, right->result, michael@0: sortState->sortKeys[t], michael@0: sortState->sortHints, &sortOrder); michael@0: if (sortOrder) michael@0: break; michael@0: } michael@0: else { michael@0: // no template, so just compare attributes. Ignore namespaces for now. michael@0: nsAutoString leftstr, rightstr; michael@0: left->content->GetAttr(kNameSpaceID_None, sortState->sortKeys[t], leftstr); michael@0: right->content->GetAttr(kNameSpaceID_None, sortState->sortKeys[t], rightstr); michael@0: michael@0: sortOrder = XULSortServiceImpl::CompareValues(leftstr, rightstr, sortState->sortHints); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (sortState->direction == nsSortState_descending) michael@0: sortOrder = -sortOrder; michael@0: michael@0: return sortOrder; michael@0: } michael@0: michael@0: nsresult michael@0: XULSortServiceImpl::SortContainer(nsIContent *aContainer, nsSortState* aSortState) michael@0: { michael@0: nsTArray items; michael@0: nsresult rv = GetItemsToSort(aContainer, aSortState, items); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t numResults = items.Length(); michael@0: if (!numResults) michael@0: return NS_OK; michael@0: michael@0: uint32_t i; michael@0: michael@0: // inbetweenSeparatorSort sorts the items between separators independently michael@0: if (aSortState->inbetweenSeparatorSort) { michael@0: uint32_t startIndex = 0; michael@0: for (i = 0; i < numResults; i++) { michael@0: if (i > startIndex + 1) { michael@0: nsAutoString type; michael@0: items[i].result->GetType(type); michael@0: if (type.EqualsLiteral("separator")) { michael@0: if (aSortState->invertSort) michael@0: InvertSortInfo(items, startIndex, i - startIndex); michael@0: else michael@0: NS_QuickSort((void *)(items.Elements() + startIndex), i - startIndex, michael@0: sizeof(contentSortInfo), testSortCallback, (void*)aSortState); michael@0: michael@0: startIndex = i + 1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (i > startIndex + 1) { michael@0: if (aSortState->invertSort) michael@0: InvertSortInfo(items, startIndex, i - startIndex); michael@0: else michael@0: NS_QuickSort((void *)(items.Elements() + startIndex), i - startIndex, michael@0: sizeof(contentSortInfo), testSortCallback, (void*)aSortState); michael@0: } michael@0: } else { michael@0: // if the items are just being inverted, that is, just switching between michael@0: // ascending and descending, just reverse the list. michael@0: if (aSortState->invertSort) michael@0: InvertSortInfo(items, 0, numResults); michael@0: else michael@0: NS_QuickSort((void *)items.Elements(), numResults, michael@0: sizeof(contentSortInfo), testSortCallback, (void*)aSortState); michael@0: } michael@0: michael@0: // first remove the items from the old positions michael@0: for (i = 0; i < numResults; i++) { michael@0: nsIContent* child = items[i].content; michael@0: nsIContent* parent = child->GetParent(); michael@0: michael@0: if (parent) { michael@0: // remember the parent so that it can be reinserted back michael@0: // into the same parent. This is necessary as multiple rules michael@0: // may generate results which get placed in different locations. michael@0: items[i].parent = parent; michael@0: int32_t index = parent->IndexOf(child); michael@0: parent->RemoveChildAt(index, true); michael@0: } michael@0: } michael@0: michael@0: // now add the items back in sorted order michael@0: for (i = 0; i < numResults; i++) michael@0: { michael@0: nsIContent* child = items[i].content; michael@0: nsIContent* parent = items[i].parent; michael@0: if (parent) { michael@0: parent->AppendChildTo(child, true); michael@0: michael@0: // if it's a container in a tree or menu, find its children, michael@0: // and sort those also michael@0: if (!child->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, michael@0: nsGkAtoms::_true, eCaseMatters)) michael@0: continue; michael@0: michael@0: for (nsIContent* grandchild = child->GetFirstChild(); michael@0: grandchild; michael@0: grandchild = grandchild->GetNextSibling()) { michael@0: nsINodeInfo *ni = grandchild->NodeInfo(); michael@0: nsIAtom *localName = ni->NameAtom(); michael@0: if (ni->NamespaceID() == kNameSpaceID_XUL && michael@0: (localName == nsGkAtoms::treechildren || michael@0: localName == nsGkAtoms::menupopup)) { michael@0: SortContainer(grandchild, aSortState); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: XULSortServiceImpl::InvertSortInfo(nsTArray& aData, michael@0: int32_t aStart, int32_t aNumItems) michael@0: { michael@0: if (aNumItems > 1) { michael@0: // reverse the items in the array starting from aStart michael@0: int32_t upPoint = (aNumItems + 1) / 2 + aStart; michael@0: int32_t downPoint = (aNumItems - 2) / 2 + aStart; michael@0: int32_t half = aNumItems / 2; michael@0: while (half-- > 0) { michael@0: aData[downPoint--].swap(aData[upPoint++]); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: XULSortServiceImpl::InitializeSortState(nsIContent* aRootElement, michael@0: nsIContent* aContainer, michael@0: const nsAString& aSortKey, michael@0: const nsAString& aSortHints, michael@0: nsSortState* aSortState) michael@0: { michael@0: // used as an optimization for the content builder michael@0: if (aContainer != aSortState->lastContainer.get()) { michael@0: aSortState->lastContainer = aContainer; michael@0: aSortState->lastWasFirst = false; michael@0: aSortState->lastWasLast = false; michael@0: } michael@0: michael@0: // The attributes allowed are either: michael@0: // sort="key1 key2 ..." michael@0: // or sortResource="key1" sortResource2="key2" michael@0: // The latter is for backwards compatibility, and is equivalent to concatenating michael@0: // both values in the sort attribute michael@0: nsAutoString sort(aSortKey); michael@0: aSortState->sortKeys.Clear(); michael@0: if (sort.IsEmpty()) { michael@0: nsAutoString sortResource, sortResource2; michael@0: aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortResource, sortResource); michael@0: if (!sortResource.IsEmpty()) { michael@0: nsCOMPtr sortkeyatom = do_GetAtom(sortResource); michael@0: aSortState->sortKeys.AppendObject(sortkeyatom); michael@0: sort.Append(sortResource); michael@0: michael@0: aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortResource2, sortResource2); michael@0: if (!sortResource2.IsEmpty()) { michael@0: nsCOMPtr sortkeyatom2 = do_GetAtom(sortResource2); michael@0: aSortState->sortKeys.AppendObject(sortkeyatom2); michael@0: sort.AppendLiteral(" "); michael@0: sort.Append(sortResource2); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: nsWhitespaceTokenizer tokenizer(sort); michael@0: while (tokenizer.hasMoreTokens()) { michael@0: nsCOMPtr keyatom = do_GetAtom(tokenizer.nextToken()); michael@0: NS_ENSURE_TRUE(keyatom, NS_ERROR_OUT_OF_MEMORY); michael@0: aSortState->sortKeys.AppendObject(keyatom); michael@0: } michael@0: } michael@0: michael@0: aSortState->sort.Assign(sort); michael@0: aSortState->direction = nsSortState_natural; michael@0: michael@0: bool noNaturalState = false; michael@0: nsWhitespaceTokenizer tokenizer(aSortHints); michael@0: while (tokenizer.hasMoreTokens()) { michael@0: const nsDependentSubstring& token(tokenizer.nextToken()); michael@0: if (token.EqualsLiteral("comparecase")) michael@0: aSortState->sortHints |= nsIXULSortService::SORT_COMPARECASE; michael@0: else if (token.EqualsLiteral("integer")) michael@0: aSortState->sortHints |= nsIXULSortService::SORT_INTEGER; michael@0: else if (token.EqualsLiteral("descending")) michael@0: aSortState->direction = nsSortState_descending; michael@0: else if (token.EqualsLiteral("ascending")) michael@0: aSortState->direction = nsSortState_ascending; michael@0: else if (token.EqualsLiteral("twostate")) michael@0: noNaturalState = true; michael@0: } michael@0: michael@0: // if the twostate flag was set, the natural order is skipped and only michael@0: // ascending and descending are allowed michael@0: if (aSortState->direction == nsSortState_natural && noNaturalState) { michael@0: aSortState->direction = nsSortState_ascending; michael@0: } michael@0: michael@0: // set up sort order info michael@0: aSortState->invertSort = false; michael@0: michael@0: nsAutoString existingsort; michael@0: aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, existingsort); michael@0: nsAutoString existingsortDirection; michael@0: aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, existingsortDirection); michael@0: michael@0: // if just switching direction, set the invertSort flag michael@0: if (sort.Equals(existingsort)) { michael@0: if (aSortState->direction == nsSortState_descending) { michael@0: if (existingsortDirection.EqualsLiteral("ascending")) michael@0: aSortState->invertSort = true; michael@0: } michael@0: else if (aSortState->direction == nsSortState_ascending && michael@0: existingsortDirection.EqualsLiteral("descending")) { michael@0: aSortState->invertSort = true; michael@0: } michael@0: } michael@0: michael@0: // sort items between separators independently michael@0: aSortState->inbetweenSeparatorSort = michael@0: aRootElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::sortSeparators, michael@0: nsGkAtoms::_true, eCaseMatters); michael@0: michael@0: // sort static content (non template generated nodes) after generated content michael@0: aSortState->sortStaticsLast = aRootElement->AttrValueIs(kNameSpaceID_None, michael@0: nsGkAtoms::sortStaticsLast, michael@0: nsGkAtoms::_true, eCaseMatters); michael@0: michael@0: aSortState->initialized = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t michael@0: XULSortServiceImpl::CompareValues(const nsAString& aLeft, michael@0: const nsAString& aRight, michael@0: uint32_t aSortHints) michael@0: { michael@0: if (aSortHints & SORT_INTEGER) { michael@0: nsresult err; michael@0: int32_t leftint = PromiseFlatString(aLeft).ToInteger(&err); michael@0: if (NS_SUCCEEDED(err)) { michael@0: int32_t rightint = PromiseFlatString(aRight).ToInteger(&err); michael@0: if (NS_SUCCEEDED(err)) { michael@0: return leftint - rightint; michael@0: } michael@0: } michael@0: // if they aren't integers, just fall through and compare strings michael@0: } michael@0: michael@0: if (aSortHints & SORT_COMPARECASE) { michael@0: return ::Compare(aLeft, aRight); michael@0: } michael@0: michael@0: nsICollation* collation = nsXULContentUtils::GetCollation(); michael@0: if (collation) { michael@0: int32_t result; michael@0: collation->CompareString(nsICollation::kCollationCaseInSensitive, michael@0: aLeft, aRight, &result); michael@0: return result; michael@0: } michael@0: michael@0: return ::Compare(aLeft, aRight, nsCaseInsensitiveStringComparator()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: XULSortServiceImpl::Sort(nsIDOMNode* aNode, michael@0: const nsAString& aSortKey, michael@0: const nsAString& aSortHints) michael@0: { michael@0: // get root content node michael@0: nsCOMPtr sortNode = do_QueryInterface(aNode); michael@0: if (!sortNode) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsSortState sortState; michael@0: nsresult rv = InitializeSortState(sortNode, sortNode, michael@0: aSortKey, aSortHints, &sortState); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // store sort info in attributes on content michael@0: SetSortHints(sortNode, &sortState); michael@0: rv = SortContainer(sortNode, &sortState); michael@0: michael@0: sortState.processor = nullptr; // don't hang on to this reference michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: NS_NewXULSortService(nsIXULSortService** sortService) michael@0: { michael@0: *sortService = new XULSortServiceImpl(); michael@0: if (!*sortService) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(*sortService); michael@0: return NS_OK; michael@0: }