ShaarliResponseXPathTest.m 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. //
  2. // ShaarliResponseXsltTest.m
  3. // ShaarliOS
  4. //
  5. // Created by Marcus Rohrmoser on 02.08.15.
  6. // Copyright (c) 2015-2016 Marcus Rohrmoser http://mro.name/me. All rights reserved.
  7. //
  8. // This program is free software: you can redistribute it and/or modify
  9. // it under the terms of the GNU General Public License as published by
  10. // the Free Software Foundation, either version 3 of the License, or
  11. // (at your option) any later version.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU General Public License
  19. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. //
  21. #import <UIKit/UIKit.h>
  22. #import "XCTestCase+Tools.h"
  23. #import <libxml2/libxml/HTMLparser.h>
  24. #import <libxml2/libxml/xpath.h>
  25. static inline const char *x2c(const xmlChar *c)
  26. {
  27. return (const char *)c;
  28. }
  29. static inline const xmlChar *c2x(const char *c)
  30. {
  31. return (xmlChar *)c;
  32. }
  33. static inline const NSStringEncoding x2e(const xmlCharEncoding e)
  34. {
  35. assert(XML_CHAR_ENCODING_UTF8 == e && "odd in-memory charset");
  36. return NSUTF8StringEncoding;
  37. }
  38. /**
  39. *
  40. * @param ctxXPath see `xmlXPathNewContext`
  41. * @param xpathStr something like `string(//foo)`
  42. * @return
  43. */
  44. static NSString *stringFromXPath(const xmlXPathContextPtr ctxXPath, const char *xpathStr)
  45. {
  46. assert(ctxXPath && "must be set.");
  47. assert(NULL == ctxXPath->node && "mustn't be set.");
  48. const NSStringEncoding enc = x2e(ctxXPath->doc->charset);
  49. const xmlXPathCompExprPtr xpath = xmlXPathCtxtCompile( ctxXPath, c2x(xpathStr) ); // could be cached but I don't consider that worth while
  50. assert(XML_FROM_NONE == (xmlErrorDomain)ctxXPath->lastError.domain && "XPath eval failure.");
  51. assert(0 == ctxXPath->lastError.code && "XPath eval failure.");
  52. assert(xpath && "compile failure.");
  53. const xmlXPathObjectPtr xpo = xmlXPathCompiledEval(xpath, ctxXPath);
  54. assert(XML_FROM_NONE == (xmlErrorDomain)ctxXPath->lastError.domain && "XPath eval failure.");
  55. assert(0 == ctxXPath->lastError.code && "XPath eval failure.");
  56. assert(xpo && "XPath eval failure.");
  57. assert(XPATH_STRING == xpo->type && "XPath result type mismatch.");
  58. NSString *ret = [[NSString alloc] initWithCString:x2c(xpo->stringval) encoding:enc];
  59. xmlXPathFreeObject(xpo);
  60. xmlXPathFreeCompExpr(xpath);
  61. return ret;
  62. }
  63. /** Pull out name and value attributes as a NSDictionary.
  64. *
  65. * @param ctxXPath see `xmlXPathNewContext`
  66. * @param xpathStr something like `/html/body//form[@name='loginform']//input[(@type='text' or @type='hidden') and @name and @value]`
  67. */
  68. static NSDictionary *dictFromXPathFormInputNameValue(const xmlXPathContextPtr ctxXPath, const char *xpathStr)
  69. {
  70. assert(ctxXPath && "must be set.");
  71. assert(NULL == ctxXPath->node && "mustn't be set.");
  72. const NSStringEncoding enc = x2e(ctxXPath->doc->charset);
  73. const xmlXPathCompExprPtr xpath = xmlXPathCtxtCompile( ctxXPath, c2x(xpathStr) ); // could be cached but I don't consider that worth while
  74. assert(XML_FROM_NONE == (xmlErrorDomain)ctxXPath->lastError.domain && "XPath eval failure.");
  75. assert(0 == ctxXPath->lastError.code && "XPath eval failure.");
  76. assert(xpath && "compile failure.");
  77. const xmlXPathObjectPtr xpo = xmlXPathCompiledEval(xpath, ctxXPath);
  78. assert(XML_FROM_NONE == (xmlErrorDomain)ctxXPath->lastError.domain && "XPath eval failure.");
  79. assert(0 == ctxXPath->lastError.code && "XPath eval failure.");
  80. assert(xpo && "XPath eval failure.");
  81. assert(XPATH_NODESET == xpo->type && "XPath result type mismatch.");
  82. NSMutableDictionary *ret = [NSMutableDictionary dictionaryWithCapacity:10];
  83. const xmlNodeSetPtr ns = xpo->nodesetval;
  84. for( int i = ns->nodeNr - 1; i >= 0; i-- ) {
  85. const xmlNodePtr n = ns->nodeTab[i];
  86. assert(XML_ELEMENT_NODE == n->type && "odd type");
  87. assert(0 == strcmp( "input", x2c(n->name) ) && "odd name");
  88. // const size_t siz = sizeof(n->properties);
  89. NSString *name = nil;
  90. NSString *value = nil;
  91. for( xmlAttr *attr = n->properties; attr; attr = attr->next ) {
  92. assert(XML_TEXT_NODE == attr->children->type && "odd node type");
  93. assert(NULL == attr->children->next && "expected no siblings");
  94. // MRLogD(@"'%s'='%s'", attr->name, attr->children->content, nil);
  95. if( 0 == strcmp( "name", x2c(attr->name) ) )
  96. name = [[NSString alloc] initWithCString:x2c(attr->children->content) encoding:enc];
  97. else if( 0 == strcmp( "value", x2c(attr->name) ) )
  98. value = [[NSString alloc] initWithCString:x2c(attr->children->content) encoding:enc];
  99. }
  100. if( name && value )
  101. ret[name] = value;
  102. else if( !name )
  103. MRLogW(@"@name missing.", nil);
  104. }
  105. xmlXPathFreeObject(xpo);
  106. xmlXPathFreeCompExpr(xpath);
  107. return ret;
  108. }
  109. @interface ShaarliResponseXPathTest : XCTestCase
  110. @end
  111. @implementation ShaarliResponseXPathTest
  112. -(void)testStringFromXPath
  113. {
  114. NSData *data = [self dataWithContentsOfFixture:@"testLogin.0" withExtension:@"html"];
  115. XCTAssertEqual(2509, data.length, @"foo", nil);
  116. NSString *shaarliTitle = nil;
  117. NSString *token = nil;
  118. {
  119. const htmlParserCtxtPtr ctxHtml = htmlCreatePushParserCtxt(NULL, NULL, (const char *)[data bytes], (int)data.length, "", XML_CHAR_ENCODING_NONE);
  120. XCTAssertEqual(0, ctxHtml->errNo, @"foo", nil);
  121. XCTAssert(NULL != ctxHtml, @"foo", nil);
  122. const xmlParserErrors errorCode = htmlParseChunk(ctxHtml, "", 0, YES);
  123. XCTAssertEqual(XML_ERR_OK, errorCode, @"foo", nil);
  124. XCTAssert(NULL != ctxHtml->myDoc, @"foo", nil);
  125. {
  126. const xmlXPathContextPtr ctxXpath = xmlXPathNewContext(ctxHtml->myDoc);
  127. XCTAssert(NULL != ctxXpath, @"foo", nil);
  128. XCTAssertEqual(XML_FROM_NONE, ctxXpath->lastError.domain, @"foo", nil);
  129. XCTAssertEqual(0, ctxXpath->lastError.code, @"foo", nil);
  130. shaarliTitle = stringFromXPath(ctxXpath, "string(/html/body//*[@id='shaarli_title'])");
  131. token = stringFromXPath(ctxXpath, "string(/html/body//form[@name='loginform']//input[@name='token']/@value)");
  132. xmlXPathFreeContext(ctxXpath);
  133. }
  134. htmlFreeParserCtxt(ctxHtml);
  135. }
  136. XCTAssertEqualObjects(@"links.mro", shaarliTitle, @"foo", nil);
  137. XCTAssertEqualObjects(@"20119241badf78a3dcfa55ae58eab429a5d24bad", token, @"foo", nil);
  138. }
  139. -(void)testDictFromXPathFormInputNameValue
  140. {
  141. NSData *data = [self dataWithContentsOfFixture:@"testLogin.0" withExtension:@"html"];
  142. XCTAssertEqual(2509, data.length, @"foo", nil);
  143. NSDictionary *form = nil;
  144. {
  145. const htmlParserCtxtPtr ctxHtml = htmlCreatePushParserCtxt(NULL, NULL, (const char *)[data bytes], (int)data.length, "", XML_CHAR_ENCODING_NONE);
  146. XCTAssertEqual(0, ctxHtml->errNo, @"foo", nil);
  147. XCTAssert(NULL != ctxHtml, @"foo", nil);
  148. const xmlParserErrors errorCode = htmlParseChunk(ctxHtml, "", 0, YES);
  149. XCTAssertEqual(XML_ERR_OK, errorCode, @"foo", nil);
  150. XCTAssert(NULL != ctxHtml->myDoc, @"foo", nil);
  151. {
  152. const xmlXPathContextPtr ctxXpath = xmlXPathNewContext(ctxHtml->myDoc);
  153. XCTAssert(NULL != ctxXpath, @"foo", nil);
  154. XCTAssertEqual(XML_FROM_NONE, ctxXpath->lastError.domain, @"foo", nil);
  155. XCTAssertEqual(0, ctxXpath->lastError.code, @"foo", nil);
  156. form = dictFromXPathFormInputNameValue(ctxXpath, "/html/body//form[@name='loginform']//input[(@type='text' or @type='hidden') and @name and @value]");
  157. xmlXPathFreeContext(ctxXpath);
  158. }
  159. htmlFreeParserCtxt(ctxHtml);
  160. }
  161. XCTAssertEqual(2, form.count, @"foo", nil);
  162. XCTAssertEqualObjects(@"http://links.mro.name/", form[F_K_RETURNURL], @"foo", nil);
  163. XCTAssertEqualObjects(@"20119241badf78a3dcfa55ae58eab429a5d24bad", form[F_K_TOKEN], @"foo", nil);
  164. }
  165. @end