XPathGenerator.cpp (5762B)
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/. */ 6 7 #include "XPathGenerator.h" 8 9 #include "Element.h" 10 #include "nsGkAtoms.h" 11 #include "nsTArray.h" 12 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; 26 } 27 } 28 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; 38 } 39 } 40 return false; 41 } 42 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"); 52 } 53 } 54 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); 59 } 60 } 61 62 /** 63 * Put all sequences of ' in a string in between '," and ",' . And then put 64 * the result string in between concat(' and '). 65 * 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(); 71 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; 81 } 82 if (!quoteBeginPtr) { 83 result.AppendLiteral(u"\',\""); 84 quoteBeginPtr = cur; 85 } 86 } else { 87 if (!nonQuoteBeginPtr) { 88 nonQuoteBeginPtr = cur; 89 } 90 if (quoteBeginPtr) { 91 result.Append(quoteBeginPtr, cur - quoteBeginPtr); 92 result.AppendLiteral(u"\",\'"); 93 quoteBeginPtr = nullptr; 94 } 95 } 96 } 97 98 if (quoteBeginPtr) { 99 result.Append(quoteBeginPtr, cur - quoteBeginPtr); 100 result.AppendLiteral(u"\",\'"); 101 } else if (nonQuoteBeginPtr) { 102 result.Append(nonQuoteBeginPtr, cur - nonQuoteBeginPtr); 103 } 104 105 // Prepend concat(' and append '). 106 aResult.Assign(u"concat(\'"_ns + result + u"\')"_ns); 107 } 108 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); 116 } 117 } 118 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); 126 } 127 } 128 129 void XPathGenerator::Generate(const nsINode* aNode, nsAString& aResult) { 130 if (!aNode->GetParentNode()) { 131 aResult.Truncate(); 132 return; 133 } 134 135 nsAutoString nodeNamespaceURI; 136 aNode->GetNamespaceURI(nodeNamespaceURI); 137 const nsString& nodeLocalName = aNode->LocalName(); 138 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); 148 } 149 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; 159 } 160 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; 175 } 176 } 177 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); 184 } 185 if (count != 1) { 186 countPart.AssignLiteral(u"["); 187 countPart.AppendInt(count); 188 countPart.AppendLiteral(u"]"); 189 } 190 Generate(aNode->GetParentNode(), aResult); 191 aResult.Append(u"/"_ns + tag + namePart + countPart); 192 }