Don't like ads? PRO users don't see any ads ;-)
Guest

xml.d edit 3

By: Shahid4 on May 1st, 2012  |  syntax: Diff  |  size: 18.18 KB  |  hits: 23  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. --- a/xml.d     2012-04-30 22:58:38.496710573 +0100
  2. +++ b/xml.d     2012-05-01 17:22:43.410371452 +0100
  3. @@ -842,14 +842,14 @@
  4.                 debug(xpath)logline("Got xpath "~to!string(xpath)~" in node "~to!string(getName)~"\n");
  5.                 R truncxpath;
  6.                 auto nextnode = getNextNode(xpath,truncxpath);
  7. -               R attrmatch;
  8. +               R predmatch;
  9.                 // XXX need to be able to split the attribute match off even when it doesn't have [] around it
  10.                 ptrdiff_t offset = nextnode.length - find(nextnode, cast(R)"[").length;
  11.                 if (offset != nextnode.length) {
  12.                         // rip out attribute string
  13. -                       attrmatch = nextnode[offset..nextnode.length];
  14. +                       predmatch = nextnode[offset..nextnode.length];
  15.                         nextnode = nextnode[0..offset];
  16. -                       debug(xpath)logline("Found attribute chunk: "~to!string(attrmatch)~"\n");
  17. +                       debug(xpath)logline("Found predicate chunk: "~to!string(predmatch)~"\n");
  18.                 }
  19.                 debug(xpath)logline("Looking for "~to!string(nextnode)~"\n");
  20.                 XmlNode[]retarr;
  21. @@ -858,7 +858,13 @@
  22.                         // we were searching for nodes, and this is one
  23.                         debug(xpath)logline("Found a node we want! name is: "~to!string(getName)~"\n");
  24.                         retarr ~= this;
  25. -               } else foreach(child;getChildren) if (!child.isCData && !child.isXmlComment && !child.isXmlPI && child.matchXPathPredicate(attrmatch,caseSensitive)) {
  26. +               } else if (nextnode.front == '@') {
  27. +                       if( matchXPathPredicate(nextnode, caseSensitive)) {
  28. +                               auto attr = getWSToken(nextnode);
  29. +                               attr.popFront();
  30. +                               retarr ~= new CData!R(getAttribute(attr));
  31. +                       }
  32. +               } else foreach(child;getChildren) if (!child.isCData && !child.isXmlComment && !child.isXmlPI && child.matchXPathPredicate(predmatch,caseSensitive)) {
  33.                         if (!nextnode.length || (caseSensitive && child.getName == nextnode) || (!caseSensitive && !icmp(child.getName(), nextnode))) {
  34.                                 // child that matches the search string, pass on the truncated string
  35.                                 debug(xpath)logline("Sending "~to!string(truncxpath)~" to "~to!string(child.getName)~"\n");
  36. @@ -875,152 +881,139 @@
  37.                 return retarr;
  38.         }
  39.  
  40. -       private bool matchXPathPredicate(R attrstr,bool caseSen) {
  41. -               debug(xpath)logline("matching attribute string "~to!string(attrstr)~"\n");
  42. +       private bool matchXPathPredicate(R predstr,bool caseSen) {
  43. +               debug(xpath)logline("matching predicate string "~to!string(predstr)~"\n");
  44.                 // strip off the encasing [] if it exists
  45. -               if (!attrstr.length) {
  46. +               if (!predstr.length) {
  47.                         return true;
  48.                 }
  49. -               if (attrstr.front == '[' && attrstr.back == ']') {
  50. -                       attrstr.popFront();
  51. -                       attrstr.popBack();
  52. -               } else if (attrstr.front == '[' || attrstr.back == ']') {
  53. +               if (predstr.front == '[' && predstr.back == ']') {
  54. +                       predstr.popFront();
  55. +                       predstr.popBack();
  56. +               } else if (predstr.front == '[' || predstr.back == ']') {
  57.                         // this seems to be malformed
  58. -                       throw new XPathError("got malformed attribute match "~to!string(attrstr)~"\n");
  59. +                       throw new XPathError("got malformed predicate match "~to!string(predstr)~"\n");
  60.                 }
  61.                 // rip apart the xpath predicate assuming it's node and attribute matches
  62. -               R[]attrlist;
  63. +               R[]predlist;
  64.                 // basically, we're splitting on " and " and " or ", but while respecting []
  65.                 int bcount = 0, i = 0;
  66.                 ptrdiff_t lslice = 0;
  67. -               R tmpattrstr = attrstr;
  68. -               //foreach (i,c;attrstr) {
  69. -               while(!tmpattrstr.empty()) {
  70. -                       auto c = tmpattrstr.front;
  71. -                       if (c == '[') {
  72. +               //foreach (i,c;predstr) {
  73. +               R tmpstr = predstr.save;
  74. +               for( dchar quote = '\0'; !tmpstr.empty(); ) {
  75. +                       auto c = tmpstr.front;
  76. +                       if( quote != '\0' ) {
  77. +                               if( quote == c ) {
  78. +                                       quote = '\0';
  79. +                               }
  80. +                       } else if (c == '\'' || c == '"' ) {
  81. +                               quote = c;
  82. +                       } else if (c == '[') {
  83.                                 bcount++;
  84.                         } else if (c == ']') {
  85.                                 bcount--;
  86.                         } else if (bcount == 0 && c == ' ') {
  87.                                 if (i != lslice) {
  88. -                                       attrlist ~= attrstr[lslice..i];
  89. +                                       predlist ~= predstr[lslice..i];
  90.                                 }
  91.                                 lslice = i+1;
  92.                         }
  93.                         i++;
  94. -                       tmpattrstr.popFront();
  95. +                       tmpstr.popFront();
  96.                 }
  97.                 // tack the last one on
  98. -               attrlist ~= attrstr[lslice..attrstr.length];
  99. +               predlist ~= predstr[lslice..predstr.length];
  100.                 // length must be odd, otherwise the string is jank
  101. -               if (!(attrlist.length%2)) throw new XPathError("Encountered a janky predicate: "~to!string(attrstr));
  102. +               if (!(predlist.length%2)) throw new XPathError("Encountered a janky predicate: "~to!string(predstr));
  103.                 // verify that odd numbers are "and" or "or"
  104. -               foreach (j,attr;attrlist) if (j%2 && attr != cast(R)"and" && attr != cast(R)"or") {
  105. +               foreach (j,attr;predlist) if (j%2 && attr != cast(R)"and" && attr != cast(R)"or") {
  106.                         throw new XPathError("Encountered consecutive terms not separated by \"and\" or \"or\" starting at: "~to!string(attr));
  107.                 } else if (!(j%2) && (attr == cast(R)"and" || attr == cast(R)"or")) {
  108. -                       throw new XPathError("Encountered consecutive joining terms (\"and\" or \"or\") in: "~to!string(attrstr));
  109. +                       throw new XPathError("Encountered consecutive joining terms (\"and\" or \"or\") in: "~to!string(predstr));
  110.                 }
  111.                 bool[]res;
  112. -               res.length = attrlist.length;
  113. +               res.length = predlist.length;
  114.                 int numOrdTerms = 0;
  115. -               debug(xpath)foreach (attr;attrlist) {
  116. -                       logline("Term: "~to!string(attr)~"\n");
  117. +               debug(xpath)foreach (pred;predlist) {
  118. +                       logline("Term: "~to!string(pred)~"\n");
  119.                 }
  120. -               foreach (j,attr;attrlist) if (!(j%2)) {
  121. -                       debug(xpath)logline("matching on "~to!string(attr)~"\n");
  122. +               foreach (j,pred;predlist) if (!(j%2)) {
  123. +                       debug(xpath)logline("matching on "~to!string(pred)~"\n");
  124. +                       bool isattr   = false;          // is elem1 @attribute
  125. +                       bool verbatim = false;          // is elem2 quoted string
  126. +                       R elem1;                        // Left of comparator
  127. +                       R comparator;                   // null, ">","<","=", ">=","<=","!="
  128. +                       R elem2;                        // right of comparator
  129. +
  130. +                       if (pred.front == '@') {
  131. +                               isattr = true;
  132. +                               pred.popFront();
  133. +                       }
  134. +                       // TODO XXX check elem1/elem2 is an XPath func()
  135. +                       elem1 = getWSToken(pred);
  136. +                       pred = stripLeft(pred);
  137. +                       // if there is still data in pred, it's time to look for a comparison operator
  138. +                       if (pred.length) {
  139. +                               // figure out what comparison needs to be done
  140. +                               auto secelem = pred.save;
  141. +                               if (!secelem.empty()) secelem.popFront();
  142. +                               if (!secelem.empty() && (pred.front == '<' || pred.front == '>' || pred.front == '!') && secelem.front == '=') {
  143. +                                       comparator = pred[0..2];
  144. +                                       popN(pred, 2);
  145. +                                       pred = stripLeft(pred);
  146. +                               } else if (pred.front == '<' || pred.front == '>' || pred.front == '=') {
  147. +                                       comparator = pred[0..1];
  148. +                                       pred.popFront();
  149. +                                       pred = stripLeft(pred);
  150. +                               } else {
  151. +                                       throw new XPathError("Could not determine comparator at: "~to!string(pred));
  152. +                               }
  153. +                               secelem = pred.save;
  154. +                               if (!secelem.empty()) secelem.popFront();
  155. +                               if (secelem.empty() && !isNumeric(to!string([pred.front]))) {
  156. +                                       throw new XPathError("Badly formed XPath query: Non-numeric comparands must be quoted ("~to!string(pred)~")");
  157. +                               }
  158. +                               // strip off quotes if necessary
  159. +                               if (pred.back == '"' && pred.front == '"') {
  160. +                                       pred.popFront();
  161. +                                       pred.popBack();
  162. +                                       verbatim = true;
  163. +                               } else if (pred.back == '"' || pred.front == '"') {
  164. +                                       throw new XPathError("Badly formed XPath query: Missing quote ("~to!string(pred)~")");
  165. +                               }
  166. +                       }
  167. +                       elem2 = pred.save;
  168.                         // check to see if we're doing an attribute match
  169.                         // there should be NO zero-length strings this far in
  170. -                       if (attr.front == '@') {
  171. -                               attr.popFront();
  172. -                               R comparator;
  173. -                               auto attrname = getWSToken(attr);
  174. -                               bool verbatim = false;
  175. -                               attr = stripLeft(attr);
  176. -                               // if there is still data in attr, it's time to look for a comparison operator
  177. -                               if (attr.length) {
  178. -                                       // figure out what comparison needs to be done
  179. -                                       auto secelem = attr.save;
  180. -                                       if (!secelem.empty()) secelem.popFront();
  181. -                                       if (!secelem.empty() && (attr.front == '<' || attr.front == '>' || attr.front == '!') && secelem.front == '=') {
  182. -                                               comparator = attr[0..2];
  183. -                                               popN(attr, 2);
  184. -                                               attr = stripLeft(attr);
  185. -                                       } else if (attr.front == '<' || attr.front == '>' || attr.front == '=') {
  186. -                                               comparator = attr[0..1];
  187. -                                               attr.popFront();
  188. -                                               attr = stripLeft(attr);
  189. -                                       } else {
  190. -                                               throw new XPathError("Could not determine comparator at: "~to!string(attr));
  191. -                                       }
  192. -                                       secelem = attr.save;
  193. -                                       if (!secelem.empty()) secelem.popFront();
  194. -                                       if (secelem.empty() && !isNumeric(to!string([attr.front]))) {
  195. -                                               throw new XPathError("Badly formed XPath query: Non-numeric comparands must be quoted ("~to!string(attr)~")");
  196. -                                       }
  197. -                                       // strip off quotes if necessary
  198. -                                       if (attr.back == '"' && attr.front == '"') {
  199. -                                               attr.popFront();
  200. -                                               attr.popBack();
  201. -                                               verbatim = true;
  202. -                                       } else if (attr.back == '"' || attr.front == '"') {
  203. -                                               throw new XPathError("Badly formed XPath query: Missing quote ("~to!string(attr)~")");
  204. -                                       }
  205. -                               }
  206. -                               // make sure that if we pulled a comparator, there's something to compare on the other side
  207. -                               if (comparator.length && !attr.length) throw new XPathError("Got a comparator without anything to compare");
  208. -                               if (!hasAttribute(attrname)) {
  209. -                                       debug(xpath)logline("could not find "~to!string(attrname)~"\n");
  210. +                       if ( isattr ) {
  211. +                               if (!hasAttribute(elem1)) {
  212. +                                       debug(xpath)logline("could not find attr "~to!string(elem1)~"\n");
  213.                                         res[j] = false;
  214.                                         continue;
  215.                                 }
  216. -                               if (comparator.length) {
  217. -                                       bool lres,neg = false,i1num = isNumeric(to!string(getAttribute(attrname))),i2num = isNumeric(to!string(attr));
  218. -                                       double i1,i2;
  219. -                                       // currently ignoring verbatim in this section of code
  220. -                                       // we can't compare non-numerics without quotes
  221. -                                       if (!verbatim && (!i1num || !i2num)) {
  222. -                                               //res[j] = false;
  223. -                                               //continue;
  224. -                                               throw new XPathError("Badly formed XPath query: Non-numeric comparands must be quoted ("~to!string(attr)~")");
  225. -                                       }
  226. -                                       // get numeric equivalents
  227. -                                       if (i1num) i1 = to!double(to!string(getAttribute(attrname)));
  228. -                                       if (i2num) i2 = to!double(to!string(attr));
  229. -                                       if (comparator.front == '<') {
  230. -                                               if (i1num && i2num) {
  231. -                                                       lres = (i1 < i2);
  232. -                                               } else {
  233. -                                                       lres = (getAttribute(attrname) < attr);
  234. -                                               }
  235. -                                       } else if (comparator.front == '>') {
  236. -                                               if (i1num && i2num) {
  237. -                                                       lres = (i1 > i2);
  238. -                                               } else {
  239. -                                                       lres = (getAttribute(attrname) > attr);
  240. -                                               }
  241. -                                       } else if (comparator.front == '!') neg = true;
  242. -                                       // check to see if equality is also called for
  243. -                                       if (comparator.back == '=') {
  244. -                                               if (verbatim) {
  245. -                                                       if ((getAttribute(attrname) != attr && caseSen) || (icmp(getAttribute(attrname), attr) != 0 && !caseSen)) {
  246. -                                                               debug(xpath)logline("search value "~to!string(attr)~" did not match attribute value "~to!string(getAttribute(attrname))~"\n");
  247. -                                                               lres = false;
  248. -                                                       } else {
  249. -                                                               lres = true;
  250. -                                                       }
  251. -                                               } else {
  252. -                                                       lres |= (i1 == i2);
  253. -                                               }
  254. -                                               if (neg) lres = !lres;
  255. -                                       }
  256. -                                       res[j] = lres;
  257. +                               if( comparator.length == 0 ) {
  258. +                                       // Just check for existance
  259. +                                       res[j] = true;
  260.                                         continue;
  261.                                 }
  262. -                               res[j] = true;
  263. -                               continue;
  264. +                               res[j] = compareXPathPredicate( elem1, comparator, elem2, getAttribute(elem1), caseSen );
  265.                         }
  266. +                       else if (true) {
  267. +                               // assume elem1 is a tag
  268. +                               //if (!comparator.length || !elem2.length) throw new XPathError("Is this really an Error?");
  269. +                               foreach(child;getChildren) {
  270. +                                       if( child.isCData || child.isXmlComment || child.isXmlPI || child.getName != elem1 )
  271. +                                               continue;
  272. +                              
  273. +                                       if( compareXPathPredicate( elem1, comparator, elem2, child.getCData, caseSen )) {
  274. +                                               res[j] = true;
  275. +                                               break;
  276. +                                       }
  277. +                               }
  278. +                       }                              
  279.                         // XXX take care of other types of matches other than attribute matches
  280. -               } else if (attr == cast(R)"or") {
  281. +               } else if (pred == cast(R)"or") {
  282.                         numOrdTerms++;
  283.                 }
  284.                 // collect "and" terms into "or" groups
  285. @@ -1029,9 +1022,9 @@
  286.                 ordTerms[0] = res[0];
  287.                 debug(xpath)logline("res[0]="~to!(string)(res[0])~"\n");
  288.                 numOrdTerms = 0; // we're using this as current position, now
  289. -               foreach (j,attr;attrlist) if (j%2) {
  290. +               foreach (j,attr;predlist) if (j%2) {
  291.                         if (attr == cast(R)"and") {
  292. -                               debug(xpath)logline("combining anded terms on ord term "~to!(string)(numOrdTerms)~" and i="~to!(string)(j)~" with res.length="~to!(string)(res.length)~" and attrlist.length="~to!(string)(attrlist.length)~"\n");
  293. +                               debug(xpath)logline("combining anded terms on ord term "~to!(string)(numOrdTerms)~" and i="~to!(string)(j)~" with res.length="~to!(string)(res.length)~" and predlist.length="~to!(string)(predlist.length)~"\n");
  294.                                 ordTerms[numOrdTerms] &= res[j+1];
  295.                                 debug(xpath)logline("res["~to!(string)(j+1)~"]="~to!(string)(res[j+1])~"\n");
  296.                         } else if (attr == cast(R)"or") {
  297. @@ -1047,7 +1040,59 @@
  298.                 debug(xpath)logline("Ended up with "~to!(string)(ret)~"\n");
  299.                 return ret;
  300.         }
  301. -      
  302. +
  303. +       private bool compareXPathPredicate( R elem1, R comparator, R elem2, R elem1value, bool caseSen )
  304. +       {
  305. +               // make sure that if we pulled a comparator, there's something to compare on the other side
  306. +               if (comparator.length && !elem2.length) throw new XPathError("Got a comparator without anything to compare");
  307. +               if (comparator.length) {
  308. +                       bool lres,i1num = isNumeric(to!string(elem1value)),i2num = isNumeric(to!string(elem2));
  309. +                       if (comparator.front == '<' || comparator.front == '>') {
  310. +                               // Must be numeric
  311. +                               if( !i2num )
  312. +                                       throw new XPathError("Badly formed XPath query: comparator '"~to!string(comparator)~"' requires a numeric operand Not ("~to!string(elem2)~")");
  313. +                               if( !i1num ) {
  314. +                                       return false;
  315. +                               }
  316. +                      
  317. +                               // get numeric equivalents
  318. +                               double i1 = to!double(to!string(elem1value));
  319. +                               double i2 = to!double(to!string(elem2));
  320. +
  321. +                               if (comparator.front == '<') {
  322. +                                       lres = i1 < i2;
  323. +                               } else /*if (comparator.front == '>')*/ {
  324. +                                       lres = i1 > i2;
  325. +                               }
  326. +                               // check to see if equality is also called for
  327. +                               if (comparator.back == '=') {
  328. +                                       lres |= (i1 == i2);
  329. +                               }
  330. +                       } else {
  331. +                               bool neg = false;
  332. +                               if (comparator.front == '!') neg = true;
  333. +
  334. +                               if( !i1num || !i2num ) {
  335. +                                       if ((elem1value != elem2 && caseSen) || (icmp(elem1value, elem2) != 0 && !caseSen)) {
  336. +                                               debug(xpath)logline("search value "~to!string(elem2)~" did not match attribute value "~to!string(elem1value)~"\n");
  337. +                                               lres = false;
  338. +                                       } else {
  339. +                                               lres = true;
  340. +                                       }
  341. +                               } else {
  342. +                                       // get numeric equivalents
  343. +                                       double i1 = to!double(to!string(elem1value));
  344. +                                       double i2 = to!double(to!string(elem2));
  345. +                                       lres = (i1 == i2);
  346. +                               }
  347. +                               if (neg) lres = !lres;
  348. +                       }
  349. +                       return lres;
  350. +               }
  351. +               return false;
  352. +       }
  353. +
  354. +
  355.         private bool isDeepPath(R xpath) {
  356.                 // check to see if we're currently searching a deep path
  357.                 auto secelem = xpath.save;
  358. @@ -1440,6 +1485,7 @@
  359.    *--------------------------
  360.    */
  361.  public int prealloc = 50;
  362. +///ditto
  363.  class XmlDocument(R=string) : XmlNode!R {
  364.         // this should inherit the reset and toXml that we want
  365.         protected XmlNode!R[]xmlNodes;
  366. @@ -1628,6 +1674,21 @@
  367.         string xmlstring = "<message responseID=\"1234abcd\" text=\"weather 12345\" type=\"message\" order=\"5\"><flags>triggered</flags><flags>targeted</flags></message>";
  368.         runTests(xmlstring);
  369.         runTests(cast(custring)xmlstring);
  370. +
  371. +       string xmlstring2 =
  372. +       `<table class="table1">
  373. +       <tr>         <th>URL </th><td><a href="path1/path2">Link 1.1</a></td></tr>
  374. +       <tr ab="two"><th>Head</th><td>Text 1.2</td></tr>
  375. +       <tr ab="4">  <th>Head</th><td>Text 1.3</td></tr>
  376. +       </table>
  377. +       <table class="table2">
  378. +       <tr>         <th>URL </th><td><a href="path1/path2">Link 2.1</a></td></tr>
  379. +       <tr ab="six"><th>Head</th><td>Text 2.2</td></tr>
  380. +       <tr ab="9">  <th>Head</th><td>Text 2.3</td></tr>
  381. +       </table>`;
  382. +
  383. +       runTests2(xmlstring2);
  384. +       runTests2(cast(custring)xmlstring2);
  385.  }
  386.  
  387.  version(XML_main) {
  388. @@ -1675,12 +1736,55 @@
  389.                 assert(searchlist.length == 2 && searchlist[0].getName == cast(R)"flags");
  390.                 searchlist = xml.parseXPath(cast(R)"/message[@order=5]/flags");
  391.                 assert(searchlist.length == 2 && searchlist[0].getName == cast(R)"flags");
  392. -      
  393. +
  394. +               logline("kxml.xml XPath ??? tests\n");
  395. +               searchlist = xml.parseXPath(cast(R)`//@text`);
  396. +               assert(searchlist.length == 1 && searchlist[0].getCData == cast(R)"weather 12345");
  397. +               searchlist = xml.parseXPath(cast(R)`/message[flags="triggered" and flags="targeted"]/@order`);
  398. +               assert(searchlist.length == 1 && searchlist[0].getCData == cast(R)"5");
  399. +               searchlist = xml.parseXPath(cast(R)`/message[@order<6 and flags="triggered"]/flags`);
  400. +               assert(searchlist.length == 2 && searchlist[0].getName == cast(R)"flags");
  401. +               searchlist = xml.parseXPath(cast(R)`/message[@order<6 and flags="fail"]/flags`);
  402. +               assert(searchlist.length == 0);
  403. +
  404. +
  405.                 /*logline("kxml.xml XPath subnode match test\n");
  406.                 searchlist = xml.parseXPath("/message[flags@tweak]");
  407.                 assert(searchlist.length == 2 && searchlist[0].getName == "flags");*/
  408.         }
  409.  
  410. +       void runTests2(R)(R xmlstring) {
  411. +               logline("Running More tests\n");
  412. +               auto xml = readDocument(xmlstring);
  413. +
  414. +               logline("kxml.xml XPath no-match tests\n");
  415. +               auto
  416. +               searchlist = xml.parseXPath(cast(R)`//ab`);
  417. +               assert(searchlist.length == 0);
  418. +               searchlist = xml.parseXPath(cast(R)`//ab=9`);           // Should this throw?
  419. +               assert(searchlist.length == 0);
  420. +               searchlist = xml.parseXPath(cast(R)`//td="Text2.2"`);   // Should this throw?
  421. +               assert(searchlist.length == 0);
  422. +               searchlist = xml.parseXPath(cast(R)`//tr[ab<=7]/td`);
  423. +               assert(searchlist.length == 0);
  424. +
  425. +               logline("kxml.xml XPath attr tests\n");
  426. +               searchlist = xml.parseXPath(cast(R)`//@ab`);
  427. +               assert(searchlist.length == 4);
  428. +               searchlist = xml.parseXPath(cast(R)`//@ab<=7`);
  429. +               assert(searchlist.length == 1);
  430. +               searchlist = xml.parseXPath(cast(R)`//table[@class!="table2"]//@ab`);
  431. +               assert(searchlist.length == 2);
  432. +//             searchlist = xml.parseXPath(cast(R)`//@class!="table2"//@ab`);  // Should this work?
  433. +//             assert(searchlist.length == 2);
  434. +
  435. +               logline("kxml.xml XPath predicate tests\n");
  436. +               searchlist = xml.parseXPath(cast(R)`//tr[@ab<=7]/td`);
  437. +               assert(searchlist.length == 1 && searchlist[0].getCData == cast(R)"Text 1.3");
  438. +               searchlist = xml.parseXPath(cast(R)`//tr[@ab>=9 and th="Head"]/td`);
  439. +               assert(searchlist.length == 1 && searchlist[0].getCData == cast(R)"Text 2.3");
  440. +       }
  441. +
  442.         struct custring {
  443.                 string data;
  444.                 void opAssign(custring assgn) {data = assgn.data;}