Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / XPathGenerator.cpp
blobee9134833c7c8338743c05627439d0a93636e434
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "XPathGenerator.h"
9 #include "nsGkAtoms.h"
10 #include "Element.h"
11 #include "nsTArray.h"
13 /**
14 * Check whether a character is a non-word character. A non-word character is a
15 * character that isn't in ('a'..'z') or in ('A'..'Z') or a number or an
16 * underscore.
17 * */
18 bool IsNonWordCharacter(const char16_t& aChar) {
19 if (((char16_t('A') <= aChar) && (aChar <= char16_t('Z'))) ||
20 ((char16_t('a') <= aChar) && (aChar <= char16_t('z'))) ||
21 ((char16_t('0') <= aChar) && (aChar <= char16_t('9'))) ||
22 (aChar == char16_t('_'))) {
23 return false;
24 } else {
25 return true;
29 /**
30 * Check whether a string contains a non-word character.
31 * */
32 bool ContainNonWordCharacter(const nsAString& aStr) {
33 const char16_t* cur = aStr.BeginReading();
34 const char16_t* end = aStr.EndReading();
35 for (; cur < end; ++cur) {
36 if (IsNonWordCharacter(*cur)) {
37 return true;
40 return false;
43 /**
44 * Get the prefix according to the given namespace and assign the result to
45 * aResult.
46 * */
47 void GetPrefix(const nsINode* aNode, nsAString& aResult) {
48 if (aNode->IsXULElement()) {
49 aResult.AssignLiteral(u"xul");
50 } else if (aNode->IsHTMLElement()) {
51 aResult.AssignLiteral(u"xhtml");
55 void GetNameAttribute(const nsINode* aNode, nsAString& aResult) {
56 if (aNode->HasName()) {
57 const mozilla::dom::Element* elem = aNode->AsElement();
58 elem->GetAttr(nsGkAtoms::name, aResult);
62 /**
63 * Put all sequences of ' in a string in between '," and ",' . And then put
64 * the result string in between concat(' and ').
66 * For example, a string 'a'' will return result concat('',"'",'a',"''",'')
67 * */
68 void GenerateConcatExpression(const nsAString& aStr, nsAString& aResult) {
69 const char16_t* cur = aStr.BeginReading();
70 const char16_t* end = aStr.EndReading();
72 // Put all sequences of ' in between '," and ",'
73 nsAutoString result;
74 const char16_t* nonQuoteBeginPtr = nullptr;
75 const char16_t* quoteBeginPtr = nullptr;
76 for (; cur < end; ++cur) {
77 if (char16_t('\'') == *cur) {
78 if (nonQuoteBeginPtr) {
79 result.Append(nonQuoteBeginPtr, cur - nonQuoteBeginPtr);
80 nonQuoteBeginPtr = nullptr;
82 if (!quoteBeginPtr) {
83 result.AppendLiteral(u"\',\"");
84 quoteBeginPtr = cur;
86 } else {
87 if (!nonQuoteBeginPtr) {
88 nonQuoteBeginPtr = cur;
90 if (quoteBeginPtr) {
91 result.Append(quoteBeginPtr, cur - quoteBeginPtr);
92 result.AppendLiteral(u"\",\'");
93 quoteBeginPtr = nullptr;
98 if (quoteBeginPtr) {
99 result.Append(quoteBeginPtr, cur - quoteBeginPtr);
100 result.AppendLiteral(u"\",\'");
101 } else if (nonQuoteBeginPtr) {
102 result.Append(nonQuoteBeginPtr, cur - nonQuoteBeginPtr);
105 // Prepend concat(' and append ').
106 aResult.Assign(u"concat(\'"_ns + result + u"\')"_ns);
109 void XPathGenerator::QuoteArgument(const nsAString& aArg, nsAString& aResult) {
110 if (!aArg.Contains('\'')) {
111 aResult.Assign(u"\'"_ns + aArg + u"\'"_ns);
112 } else if (!aArg.Contains('\"')) {
113 aResult.Assign(u"\""_ns + aArg + u"\""_ns);
114 } else {
115 GenerateConcatExpression(aArg, aResult);
119 void XPathGenerator::EscapeName(const nsAString& aName, nsAString& aResult) {
120 if (ContainNonWordCharacter(aName)) {
121 nsAutoString quotedArg;
122 QuoteArgument(aName, quotedArg);
123 aResult.Assign(u"*[local-name()="_ns + quotedArg + u"]"_ns);
124 } else {
125 aResult.Assign(aName);
129 void XPathGenerator::Generate(const nsINode* aNode, nsAString& aResult) {
130 if (!aNode->GetParentNode()) {
131 aResult.Truncate();
132 return;
135 nsAutoString nodeNamespaceURI;
136 aNode->GetNamespaceURI(nodeNamespaceURI);
137 const nsString& nodeLocalName = aNode->LocalName();
139 nsAutoString prefix;
140 nsAutoString tag;
141 nsAutoString nodeEscapeName;
142 GetPrefix(aNode, prefix);
143 EscapeName(nodeLocalName, nodeEscapeName);
144 if (prefix.IsEmpty()) {
145 tag.Assign(nodeEscapeName);
146 } else {
147 tag.Assign(prefix + u":"_ns + nodeEscapeName);
150 if (aNode->HasID()) {
151 // this must be an element
152 const mozilla::dom::Element* elem = aNode->AsElement();
153 nsAutoString elemId;
154 nsAutoString quotedArgument;
155 elem->GetId(elemId);
156 QuoteArgument(elemId, quotedArgument);
157 aResult.Assign(u"//"_ns + tag + u"[@id="_ns + quotedArgument + u"]"_ns);
158 return;
161 int32_t count = 1;
162 nsAutoString nodeNameAttribute;
163 GetNameAttribute(aNode, nodeNameAttribute);
164 for (const mozilla::dom::Element* e = aNode->GetPreviousElementSibling(); e;
165 e = e->GetPreviousElementSibling()) {
166 nsAutoString elementNamespaceURI;
167 e->GetNamespaceURI(elementNamespaceURI);
168 nsAutoString elementNameAttribute;
169 GetNameAttribute(e, elementNameAttribute);
170 if (e->LocalName().Equals(nodeLocalName) &&
171 elementNamespaceURI.Equals(nodeNamespaceURI) &&
172 (nodeNameAttribute.IsEmpty() ||
173 elementNameAttribute.Equals(nodeNameAttribute))) {
174 ++count;
178 nsAutoString namePart;
179 nsAutoString countPart;
180 if (!nodeNameAttribute.IsEmpty()) {
181 nsAutoString quotedArgument;
182 QuoteArgument(nodeNameAttribute, quotedArgument);
183 namePart.Assign(u"[@name="_ns + quotedArgument + u"]"_ns);
185 if (count != 1) {
186 countPart.AssignLiteral(u"[");
187 countPart.AppendInt(count);
188 countPart.AppendLiteral(u"]");
190 Generate(aNode->GetParentNode(), aResult);
191 aResult.Append(u"/"_ns + tag + namePart + countPart);