1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
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/
9 : *
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
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
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.
21 : *
22 : * Contributor(s):
23 : * Scott Putterman <putterman@netscape.com>
24 : * Pierre Phaneuf <pp@ludusdesign.com>
25 : * Chase Tingley <tingley@sundell.net>
26 : * Neil Deakin <enndeakin@sympatico.ca>
27 : *
28 : * Alternatively, the contents of this file may be used under the terms of
29 : * either of the GNU General Public License Version 2 or later (the "GPL"),
30 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 : * in which case the provisions of the GPL or the LGPL are applicable instead
32 : * of those above. If you wish to allow use of your version of this file only
33 : * under the terms of either the GPL or the LGPL, and not to allow others to
34 : * use your version of this file under the terms of the MPL, indicate your
35 : * decision by deleting the provisions above and replace them with the notice
36 : * and other provisions required by the GPL or the LGPL. If you do not delete
37 : * the provisions above, a recipient may use your version of this file under
38 : * the terms of any one of the MPL, the GPL or the LGPL.
39 : *
40 : * ***** END LICENSE BLOCK *****
41 : *
42 : * This Original Code has been modified by IBM Corporation.
43 : * Modifications made by IBM described herein are
44 : * Copyright (c) International Business Machines
45 : * Corporation, 2000
46 : *
47 : * Modifications to Mozilla code or documentation
48 : * identified per MPL Section 3.3
49 : *
50 : * Date Modified by Description of modification
51 : * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
52 : * use in OS2
53 : */
54 :
55 : /*
56 : This file provides the implementation for the sort service manager.
57 : */
58 :
59 : #include "nsCOMPtr.h"
60 : #include "nsIContent.h"
61 : #include "nsIDOMElement.h"
62 : #include "nsIDOMNode.h"
63 : #include "nsIDocument.h"
64 : #include "nsINameSpaceManager.h"
65 : #include "nsIServiceManager.h"
66 : #include "nsGkAtoms.h"
67 : #include "nsXULContentUtils.h"
68 : #include "nsString.h"
69 : #include "nsQuickSort.h"
70 : #include "nsWhitespaceTokenizer.h"
71 : #include "nsXULSortService.h"
72 : #include "nsIDOMXULElement.h"
73 : #include "nsIXULTemplateBuilder.h"
74 : #include "nsTemplateMatch.h"
75 : #include "nsICollation.h"
76 : #include "nsUnicharUtils.h"
77 :
78 0 : NS_IMPL_ISUPPORTS1(XULSortServiceImpl, nsIXULSortService)
79 :
80 : void
81 0 : XULSortServiceImpl::SetSortHints(nsIContent *aNode, nsSortState* aSortState)
82 : {
83 : // set sort and sortDirection attributes when is sort is done
84 : aNode->SetAttr(kNameSpaceID_None, nsGkAtoms::sort,
85 0 : aSortState->sort, true);
86 :
87 0 : nsAutoString direction;
88 0 : if (aSortState->direction == nsSortState_descending)
89 0 : direction.AssignLiteral("descending");
90 0 : else if (aSortState->direction == nsSortState_ascending)
91 0 : direction.AssignLiteral("ascending");
92 : aNode->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
93 0 : direction, true);
94 :
95 : // for trees, also set the sort info on the currently sorted column
96 0 : if (aNode->NodeInfo()->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) {
97 0 : if (aSortState->sortKeys.Count() >= 1) {
98 0 : nsAutoString sortkey;
99 0 : aSortState->sortKeys[0]->ToString(sortkey);
100 0 : SetSortColumnHints(aNode, sortkey, direction);
101 : }
102 : }
103 0 : }
104 :
105 : void
106 0 : XULSortServiceImpl::SetSortColumnHints(nsIContent *content,
107 : const nsAString &sortResource,
108 : const nsAString &sortDirection)
109 : {
110 : // set sort info on current column. This ensures that the
111 : // column header sort indicator is updated properly.
112 0 : for (nsIContent* child = content->GetFirstChild();
113 : child;
114 0 : child = child->GetNextSibling()) {
115 0 : if (child->IsXUL()) {
116 0 : nsIAtom *tag = child->Tag();
117 :
118 0 : if (tag == nsGkAtoms::treecols) {
119 0 : SetSortColumnHints(child, sortResource, sortDirection);
120 0 : } else if (tag == nsGkAtoms::treecol) {
121 0 : nsAutoString value;
122 0 : child->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, value);
123 : // also check the resource attribute for older code
124 0 : if (value.IsEmpty())
125 0 : child->GetAttr(kNameSpaceID_None, nsGkAtoms::resource, value);
126 0 : if (value == sortResource) {
127 : child->SetAttr(kNameSpaceID_None, nsGkAtoms::sortActive,
128 0 : NS_LITERAL_STRING("true"), true);
129 : child->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
130 0 : sortDirection, true);
131 : // Note: don't break out of loop; want to set/unset
132 : // attribs on ALL sort columns
133 0 : } else if (!value.IsEmpty()) {
134 : child->UnsetAttr(kNameSpaceID_None, nsGkAtoms::sortActive,
135 0 : true);
136 : child->UnsetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
137 0 : true);
138 : }
139 : }
140 : }
141 : }
142 0 : }
143 :
144 : nsresult
145 0 : XULSortServiceImpl::GetItemsToSort(nsIContent *aContainer,
146 : nsSortState* aSortState,
147 : nsTArray<contentSortInfo>& aSortItems)
148 : {
149 : // if there is a template attached to the sort node, use the builder to get
150 : // the items to be sorted
151 0 : nsCOMPtr<nsIDOMXULElement> element = do_QueryInterface(aContainer);
152 0 : if (element) {
153 0 : nsCOMPtr<nsIXULTemplateBuilder> builder;
154 0 : element->GetBuilder(getter_AddRefs(builder));
155 :
156 0 : if (builder) {
157 0 : nsresult rv = builder->GetQueryProcessor(getter_AddRefs(aSortState->processor));
158 0 : if (NS_FAILED(rv) || !aSortState->processor)
159 0 : return rv;
160 :
161 0 : return GetTemplateItemsToSort(aContainer, builder, aSortState, aSortItems);
162 : }
163 : }
164 :
165 : // if there is no template builder, just get the children. For trees,
166 : // get the treechildren element as use that as the parent
167 0 : nsCOMPtr<nsIContent> treechildren;
168 0 : if (aContainer->NodeInfo()->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) {
169 : nsXULContentUtils::FindChildByTag(aContainer,
170 : kNameSpaceID_XUL,
171 : nsGkAtoms::treechildren,
172 0 : getter_AddRefs(treechildren));
173 0 : if (!treechildren)
174 0 : return NS_OK;
175 :
176 0 : aContainer = treechildren;
177 : }
178 :
179 0 : for (nsIContent* child = aContainer->GetFirstChild();
180 : child;
181 0 : child = child->GetNextSibling()) {
182 0 : contentSortInfo* cinfo = aSortItems.AppendElement();
183 0 : if (!cinfo)
184 0 : return NS_ERROR_OUT_OF_MEMORY;
185 :
186 0 : cinfo->content = child;
187 : }
188 :
189 0 : return NS_OK;
190 : }
191 :
192 :
193 : nsresult
194 0 : XULSortServiceImpl::GetTemplateItemsToSort(nsIContent* aContainer,
195 : nsIXULTemplateBuilder* aBuilder,
196 : nsSortState* aSortState,
197 : nsTArray<contentSortInfo>& aSortItems)
198 : {
199 0 : for (nsIContent* child = aContainer->GetFirstChild();
200 : child;
201 0 : child = child->GetNextSibling()) {
202 :
203 0 : nsCOMPtr<nsIDOMElement> childnode = do_QueryInterface(child);
204 :
205 0 : nsCOMPtr<nsIXULTemplateResult> result;
206 0 : nsresult rv = aBuilder->GetResultForContent(childnode, getter_AddRefs(result));
207 0 : NS_ENSURE_SUCCESS(rv, rv);
208 :
209 0 : if (result) {
210 0 : contentSortInfo* cinfo = aSortItems.AppendElement();
211 0 : if (!cinfo)
212 0 : return NS_ERROR_OUT_OF_MEMORY;
213 :
214 0 : cinfo->content = child;
215 0 : cinfo->result = result;
216 : }
217 0 : else if (aContainer->Tag() != nsGkAtoms::_template) {
218 0 : rv = GetTemplateItemsToSort(child, aBuilder, aSortState, aSortItems);
219 0 : NS_ENSURE_SUCCESS(rv, rv);
220 : }
221 : }
222 :
223 0 : return NS_OK;
224 : }
225 :
226 : int
227 0 : testSortCallback(const void *data1, const void *data2, void *privateData)
228 : {
229 : /// Note: testSortCallback is a small C callback stub for NS_QuickSort
230 0 : contentSortInfo *left = (contentSortInfo *)data1;
231 0 : contentSortInfo *right = (contentSortInfo *)data2;
232 0 : nsSortState* sortState = (nsSortState *)privateData;
233 :
234 0 : PRInt32 sortOrder = 0;
235 :
236 0 : if (sortState->direction == nsSortState_natural && sortState->processor) {
237 : // sort in natural order
238 0 : sortState->processor->CompareResults(left->result, right->result,
239 0 : nsnull, sortState->sortHints, &sortOrder);
240 : }
241 : else {
242 0 : PRInt32 length = sortState->sortKeys.Count();
243 0 : for (PRInt32 t = 0; t < length; t++) {
244 : // for templates, use the query processor to do sorting
245 0 : if (sortState->processor) {
246 0 : sortState->processor->CompareResults(left->result, right->result,
247 : sortState->sortKeys[t],
248 0 : sortState->sortHints, &sortOrder);
249 0 : if (sortOrder)
250 0 : break;
251 : }
252 : else {
253 : // no template, so just compare attributes. Ignore namespaces for now.
254 0 : nsAutoString leftstr, rightstr;
255 0 : left->content->GetAttr(kNameSpaceID_None, sortState->sortKeys[t], leftstr);
256 0 : right->content->GetAttr(kNameSpaceID_None, sortState->sortKeys[t], rightstr);
257 :
258 0 : sortOrder = XULSortServiceImpl::CompareValues(leftstr, rightstr, sortState->sortHints);
259 : }
260 : }
261 : }
262 :
263 0 : if (sortState->direction == nsSortState_descending)
264 0 : sortOrder = -sortOrder;
265 :
266 0 : return sortOrder;
267 : }
268 :
269 : nsresult
270 0 : XULSortServiceImpl::SortContainer(nsIContent *aContainer, nsSortState* aSortState)
271 : {
272 0 : nsTArray<contentSortInfo> items;
273 0 : nsresult rv = GetItemsToSort(aContainer, aSortState, items);
274 0 : NS_ENSURE_SUCCESS(rv, rv);
275 :
276 0 : PRUint32 numResults = items.Length();
277 0 : if (!numResults)
278 0 : return NS_OK;
279 :
280 : PRUint32 i;
281 :
282 : // inbetweenSeparatorSort sorts the items between separators independently
283 0 : if (aSortState->inbetweenSeparatorSort) {
284 0 : PRUint32 startIndex = 0;
285 0 : for (i = 0; i < numResults; i++) {
286 0 : if (i > startIndex + 1) {
287 0 : nsAutoString type;
288 0 : items[i].result->GetType(type);
289 0 : if (type.EqualsLiteral("separator")) {
290 0 : if (aSortState->invertSort)
291 0 : InvertSortInfo(items, startIndex, i - startIndex);
292 : else
293 0 : NS_QuickSort((void *)(items.Elements() + startIndex), i - startIndex,
294 0 : sizeof(contentSortInfo), testSortCallback, (void*)aSortState);
295 :
296 0 : startIndex = i + 1;
297 : }
298 : }
299 : }
300 :
301 0 : if (i > startIndex + 1) {
302 0 : if (aSortState->invertSort)
303 0 : InvertSortInfo(items, startIndex, i - startIndex);
304 : else
305 0 : NS_QuickSort((void *)(items.Elements() + startIndex), i - startIndex,
306 0 : sizeof(contentSortInfo), testSortCallback, (void*)aSortState);
307 : }
308 : } else {
309 : // if the items are just being inverted, that is, just switching between
310 : // ascending and descending, just reverse the list.
311 0 : if (aSortState->invertSort)
312 0 : InvertSortInfo(items, 0, numResults);
313 : else
314 0 : NS_QuickSort((void *)items.Elements(), numResults,
315 0 : sizeof(contentSortInfo), testSortCallback, (void*)aSortState);
316 : }
317 :
318 : // first remove the items from the old positions
319 0 : for (i = 0; i < numResults; i++) {
320 0 : nsIContent* child = items[i].content;
321 0 : nsIContent* parent = child->GetParent();
322 :
323 0 : if (parent) {
324 : // remember the parent so that it can be reinserted back
325 : // into the same parent. This is necessary as multiple rules
326 : // may generate results which get placed in different locations.
327 0 : items[i].parent = parent;
328 0 : PRInt32 index = parent->IndexOf(child);
329 0 : parent->RemoveChildAt(index, true);
330 : }
331 : }
332 :
333 : // now add the items back in sorted order
334 0 : for (i = 0; i < numResults; i++)
335 : {
336 0 : nsIContent* child = items[i].content;
337 0 : nsIContent* parent = items[i].parent;
338 0 : if (parent) {
339 0 : parent->AppendChildTo(child, true);
340 :
341 : // if it's a container in a tree or menu, find its children,
342 : // and sort those also
343 0 : if (!child->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container,
344 0 : nsGkAtoms::_true, eCaseMatters))
345 0 : continue;
346 :
347 0 : for (nsIContent* grandchild = child->GetFirstChild();
348 : grandchild;
349 0 : grandchild = grandchild->GetNextSibling()) {
350 0 : nsINodeInfo *ni = grandchild->NodeInfo();
351 0 : nsIAtom *localName = ni->NameAtom();
352 0 : if (ni->NamespaceID() == kNameSpaceID_XUL &&
353 : (localName == nsGkAtoms::treechildren ||
354 : localName == nsGkAtoms::menupopup)) {
355 0 : SortContainer(grandchild, aSortState);
356 : }
357 : }
358 : }
359 : }
360 :
361 0 : return NS_OK;
362 : }
363 :
364 : nsresult
365 0 : XULSortServiceImpl::InvertSortInfo(nsTArray<contentSortInfo>& aData,
366 : PRInt32 aStart, PRInt32 aNumItems)
367 : {
368 0 : if (aNumItems > 1) {
369 : // reverse the items in the array starting from aStart
370 0 : PRInt32 upPoint = (aNumItems + 1) / 2 + aStart;
371 0 : PRInt32 downPoint = (aNumItems - 2) / 2 + aStart;
372 0 : PRInt32 half = aNumItems / 2;
373 0 : while (half-- > 0) {
374 0 : aData[downPoint--].swap(aData[upPoint++]);
375 : }
376 : }
377 0 : return NS_OK;
378 : }
379 :
380 : nsresult
381 0 : XULSortServiceImpl::InitializeSortState(nsIContent* aRootElement,
382 : nsIContent* aContainer,
383 : const nsAString& aSortKey,
384 : const nsAString& aSortHints,
385 : nsSortState* aSortState)
386 : {
387 : // used as an optimization for the content builder
388 0 : if (aContainer != aSortState->lastContainer.get()) {
389 0 : aSortState->lastContainer = aContainer;
390 0 : aSortState->lastWasFirst = false;
391 0 : aSortState->lastWasLast = false;
392 : }
393 :
394 : // The attributes allowed are either:
395 : // sort="key1 key2 ..."
396 : // or sortResource="key1" sortResource2="key2"
397 : // The latter is for backwards compatibility, and is equivalent to concatenating
398 : // both values in the sort attribute
399 0 : nsAutoString sort(aSortKey);
400 0 : aSortState->sortKeys.Clear();
401 0 : if (sort.IsEmpty()) {
402 0 : nsAutoString sortResource, sortResource2;
403 0 : aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortResource, sortResource);
404 0 : if (!sortResource.IsEmpty()) {
405 0 : nsCOMPtr<nsIAtom> sortkeyatom = do_GetAtom(sortResource);
406 0 : aSortState->sortKeys.AppendObject(sortkeyatom);
407 0 : sort.Append(sortResource);
408 :
409 0 : aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortResource2, sortResource2);
410 0 : if (!sortResource2.IsEmpty()) {
411 0 : nsCOMPtr<nsIAtom> sortkeyatom2 = do_GetAtom(sortResource2);
412 0 : aSortState->sortKeys.AppendObject(sortkeyatom2);
413 0 : sort.AppendLiteral(" ");
414 0 : sort.Append(sortResource2);
415 : }
416 : }
417 : }
418 : else {
419 0 : nsWhitespaceTokenizer tokenizer(sort);
420 0 : while (tokenizer.hasMoreTokens()) {
421 0 : nsCOMPtr<nsIAtom> keyatom = do_GetAtom(tokenizer.nextToken());
422 0 : NS_ENSURE_TRUE(keyatom, NS_ERROR_OUT_OF_MEMORY);
423 0 : aSortState->sortKeys.AppendObject(keyatom);
424 : }
425 : }
426 :
427 0 : aSortState->sort.Assign(sort);
428 0 : aSortState->direction = nsSortState_natural;
429 :
430 0 : bool noNaturalState = false;
431 0 : nsWhitespaceTokenizer tokenizer(aSortHints);
432 0 : while (tokenizer.hasMoreTokens()) {
433 0 : const nsDependentSubstring& token(tokenizer.nextToken());
434 0 : if (token.EqualsLiteral("comparecase"))
435 0 : aSortState->sortHints |= nsIXULSortService::SORT_COMPARECASE;
436 0 : else if (token.EqualsLiteral("integer"))
437 0 : aSortState->sortHints |= nsIXULSortService::SORT_INTEGER;
438 0 : else if (token.EqualsLiteral("descending"))
439 0 : aSortState->direction = nsSortState_descending;
440 0 : else if (token.EqualsLiteral("ascending"))
441 0 : aSortState->direction = nsSortState_ascending;
442 0 : else if (token.EqualsLiteral("twostate"))
443 0 : noNaturalState = true;
444 : }
445 :
446 : // if the twostate flag was set, the natural order is skipped and only
447 : // ascending and descending are allowed
448 0 : if (aSortState->direction == nsSortState_natural && noNaturalState) {
449 0 : aSortState->direction = nsSortState_ascending;
450 : }
451 :
452 : // set up sort order info
453 0 : aSortState->invertSort = false;
454 :
455 0 : nsAutoString existingsort;
456 0 : aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, existingsort);
457 0 : nsAutoString existingsortDirection;
458 0 : aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, existingsortDirection);
459 :
460 : // if just switching direction, set the invertSort flag
461 0 : if (sort.Equals(existingsort)) {
462 0 : if (aSortState->direction == nsSortState_descending) {
463 0 : if (existingsortDirection.EqualsLiteral("ascending"))
464 0 : aSortState->invertSort = true;
465 : }
466 0 : else if (aSortState->direction == nsSortState_ascending &&
467 0 : existingsortDirection.EqualsLiteral("descending")) {
468 0 : aSortState->invertSort = true;
469 : }
470 : }
471 :
472 : // sort items between separators independently
473 : aSortState->inbetweenSeparatorSort =
474 : aRootElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::sortSeparators,
475 0 : nsGkAtoms::_true, eCaseMatters);
476 :
477 : // sort static content (non template generated nodes) after generated content
478 : aSortState->sortStaticsLast = aRootElement->AttrValueIs(kNameSpaceID_None,
479 : nsGkAtoms::sortStaticsLast,
480 0 : nsGkAtoms::_true, eCaseMatters);
481 :
482 0 : aSortState->initialized = true;
483 :
484 0 : return NS_OK;
485 : }
486 :
487 : PRInt32
488 0 : XULSortServiceImpl::CompareValues(const nsAString& aLeft,
489 : const nsAString& aRight,
490 : PRUint32 aSortHints)
491 : {
492 0 : if (aSortHints & SORT_INTEGER) {
493 : PRInt32 err;
494 0 : PRInt32 leftint = PromiseFlatString(aLeft).ToInteger(&err);
495 0 : if (NS_SUCCEEDED(err)) {
496 0 : PRInt32 rightint = PromiseFlatString(aRight).ToInteger(&err);
497 0 : if (NS_SUCCEEDED(err)) {
498 0 : return leftint - rightint;
499 : }
500 : }
501 : // if they aren't integers, just fall through and compare strings
502 : }
503 :
504 0 : if (aSortHints & SORT_COMPARECASE) {
505 0 : return ::Compare(aLeft, aRight);
506 : }
507 :
508 0 : nsICollation* collation = nsXULContentUtils::GetCollation();
509 0 : if (collation) {
510 : PRInt32 result;
511 : collation->CompareString(nsICollation::kCollationCaseInSensitive,
512 0 : aLeft, aRight, &result);
513 0 : return result;
514 : }
515 :
516 0 : return ::Compare(aLeft, aRight, nsCaseInsensitiveStringComparator());
517 : }
518 :
519 : NS_IMETHODIMP
520 0 : XULSortServiceImpl::Sort(nsIDOMNode* aNode,
521 : const nsAString& aSortKey,
522 : const nsAString& aSortHints)
523 : {
524 : // get root content node
525 0 : nsCOMPtr<nsIContent> sortNode = do_QueryInterface(aNode);
526 0 : if (!sortNode)
527 0 : return NS_ERROR_FAILURE;
528 :
529 0 : nsSortState sortState;
530 : nsresult rv = InitializeSortState(sortNode, sortNode,
531 0 : aSortKey, aSortHints, &sortState);
532 0 : NS_ENSURE_SUCCESS(rv, rv);
533 :
534 : // store sort info in attributes on content
535 0 : SetSortHints(sortNode, &sortState);
536 0 : rv = SortContainer(sortNode, &sortState);
537 :
538 0 : sortState.processor = nsnull; // don't hang on to this reference
539 0 : return rv;
540 : }
541 :
542 : nsresult
543 0 : NS_NewXULSortService(nsIXULSortService** sortService)
544 : {
545 0 : *sortService = new XULSortServiceImpl();
546 0 : if (!*sortService)
547 0 : return NS_ERROR_OUT_OF_MEMORY;
548 :
549 0 : NS_ADDREF(*sortService);
550 0 : return NS_OK;
551 : }
|