質問
上記質問に引き続き、任意の文字列値を持つテキストノードを得る関数を考えます。
HTML
1<ul id="sample"> 2 <li>test</li> 3 <li>"hoge"</li> 4 <li>'foo'</li> 5 <li>"piyo'</li> 6</ul> 7<script> 8'use strict'; 9function getFirstTextNode (contextNode, string) { 10 return document.evaluate('descendant::text()[.="' + String(string).replace(/"/g, '""') + '"]', contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; 11} 12 13var ul = document.getElementById('sample'); 14console.log(getFirstTextNode(ul, 'test')); // "test" 15console.log(getFirstTextNode(ul, '"hoge"')); // SyntaxError: Failed to execute 'evaluate' on 'Document': The string 'descendant::text()[.=""hoge""]' is not a valid XPath expression. 16</script>
XPath では文字列リテラルはダブルクォート("
)もしくはシングルクォート('
)で括りますが、ダブルクォートで括るとダブルクォートを含める事が出来ず、シングルクォートで括るとシングルクォートを含める事が出来ません。
エスケープ方法について調査したところ、XPath 2.0 では ""
でダブルクォートをエスケープできる事がわかりました。
ところが、Google Chrome 49.0.2623.112 m, Firefox 45.0.1 では期待に反して SyntaxError
になってしまいます。
JavaScript
1'use strict'; 2function getFirstTextNode (contextNode, string) { 3 return document.evaluate('descendant::text()[.="' + String(string).replace(/"/g, '""') + '"]', contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; 4} 5 6var ul = document.getElementById('sample'); 7console.log(getFirstTextNode(ul, '"hoge"')); // SyntaxError: Failed to execute 'evaluate' on 'Document': The string 'descendant::text()[.="""hoge"""]' is not a valid XPath expression.
XPath でダブルクォート("
)をエスケープするにはどんな方法が考えられるでしょうか。
(解決案) concat() で連結する
CertaiN さんに XPath 式の concat()
関数で連結する方法を教えて頂きました。
HTML
1<ul id="sample"> 2 <li>test</li> 3 <li>"</li> 4 <li>"""</li> 5 <li>"""x"""</li> 6 <li>"hoge"</li> 7 <li>'foo'</li> 8 <li>"piyo'</li> 9</ul> 10 11<script> 12'use strict'; 13var toXPathStringLiteral = (function () { 14 function replacefn (match, p1) { 15 16 if (p1) { 17 return ',\u0027' + p1 + '\u0027'; 18 } 19 20 return ',"' + match + '"'; 21 } 22 23 return function toXPathStringLiteral (string) { 24 string = String(string); 25 26 if (/^"+$/g.test(string)) { 27 return '\u0027' + string + '\u0027'; 28 } 29 30 switch (string.indexOf('"')) { 31 case -1: 32 return '"' + string + '"'; 33 case 0: 34 return 'concat(' + string.replace(/("+)|[^"]+/g, replacefn).slice(1) + ')'; 35 default: 36 return 'concat(' + string.replace(/("+)|[^"]+/g, replacefn) + ')'; 37 } 38 }; 39}()); 40 41 42function getFirstTextNode (contextNode, string) { 43 return document.evaluate('descendant::text()[.=' + toXPathStringLiteral(string) + ']', contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; 44} 45 46var ul = document.getElementById('sample'); 47 48console.log(toXPathStringLiteral('test')); // "test" 49console.log(toXPathStringLiteral('"')); // '"' 50console.log(toXPathStringLiteral('"""')); // '"""' 51console.log(toXPathStringLiteral('"""x"""')); // concat('"""',"x",'"""') 52console.log(toXPathStringLiteral('"hoge"')); // concat('"',"hoge",'"') 53console.log(toXPathStringLiteral('\u0027foo\u0027')); // "'foo'" 54console.log(toXPathStringLiteral('"piyo\u0027')); // concat('"',"piyo'") 55 56console.log(getFirstTextNode(ul, 'test')); // test 57console.log(getFirstTextNode(ul, '"')); // " 58console.log(getFirstTextNode(ul, '"""')); // """ 59console.log(getFirstTextNode(ul, '"""x"""')); // """x""" 60console.log(getFirstTextNode(ul, '"hoge"')); // "hoge" 61console.log(getFirstTextNode(ul, '\u0027foo\u0027')); // 'foo' 62console.log(getFirstTextNode(ul, '"piyo\u0027')); // "piyo' 63</script>
更新履歴
- 2016/04/10 10:01
getFirstTextNode
におけるevaluate
の第2引数がul
になっていたのでcontextNode
に修正 - 2016/04/10 12:05
concat()
で連結するコードを追記 (CertaiN さん発案) - 2016/04/11 09:50
concat()
で連結するコードで"""
を指定するとconcat()
がエラーを返すバグ修正

回答2件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/04/10 08:06
2016/04/11 00:58