nsXULSortService.cpp 17 KB

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