Browse Source

Finish Share Extension programming

HonorLee 8 years ago
parent
commit
14e7203ee9

+ 1 - 1
ShareSupport/HTMLNode.h

@@ -7,7 +7,7 @@
 //
 
 #import <Foundation/Foundation.h>
-#import "libxml2/HTMLparser.h"
+#import <libxml/HTMLparser.h>
 #import "HTMLParser.h"
 
 @class HTMLParser;

+ 2 - 1
ShareSupport/HTMLParser.h

@@ -7,8 +7,9 @@
 //
 
 #import <Foundation/Foundation.h>
-#import "libxml/HTMLParser.h"
+#import <libxml/HTMLparser.h>
 #import "HTMLNode.h"
+
 @class HTMLNode;
 
 @interface HTMLParser : NSObject 

+ 15 - 0
ShareSupport/Info.plist

@@ -2,6 +2,21 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
+	<key>NSAppTransportSecurity</key>
+	<dict>
+		<key>NSAllowsArbitraryLoads</key>
+		<true/>
+		<key>NSExceptionDomains</key>
+		<dict>
+			<key>baidu.com</key>
+			<dict>
+				<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
+				<true/>
+				<key>NSIncludesSubdomains</key>
+				<true/>
+			</dict>
+		</dict>
+	</dict>
 	<key>CFBundleDevelopmentRegion</key>
 	<string>en</string>
 	<key>CFBundleDisplayName</key>

+ 96 - 43
ShareSupport/ShareViewController.m

@@ -8,8 +8,8 @@
 
 #import "ShareViewController.h"
 #import <MobileCoreServices/MobileCoreServices.h>
-#import <libxml/HTMLparser.h>
-
+#import "HTMLParser.h"
+#import "TFHpple.h"
 
 @interface ShareViewController ()
 @property NSUserDefaults *userDefaults;
@@ -18,42 +18,14 @@
 @property UILabel *AnalyseViewLabel;
 @property NSString *CurrentStatus;
 @property UIActivityIndicatorView *indicator;
+@property UILabel *StatusLabel;
+@property UIButton *DoneBtn;
+@property NSString *RealVideoID;
+
 @end
 
 @implementation ShareViewController
-- (BOOL)isContentValid {
-    // Do validation of contentText and/or NSExtensionContext attachments here
-    return YES;
-}
 
-- (void)didSelectPost {
-    // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
-    _userDefaults = [[NSUserDefaults alloc]initWithSuiteName:@"group.honorlee.TDGroup"];
-    [_userDefaults synchronize];
-    NSMutableArray *tmpData = [NSMutableArray arrayWithArray:(NSMutableArray *)[_userDefaults objectForKey:@"tasks"]];
-    NSLog(@"%lu",(unsigned long)[tmpData count]);
-//    NSArray *inputItems = self.extensionContext.inputItems;
-//    NSLog(@"%lu",(unsigned long)[inputItems count]);
-    NSExtensionItem *content = self.extensionContext.inputItems[0];
-    NSItemProvider *itemProvider = content.attachments.firstObject;
-    if([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeURL]){
-        [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeURL options:nil completionHandler:^(NSURL *url,NSError *err) {
-            NSString *sharedURL = url.absoluteString;
-            if([sharedURL containsString:@"tumblr.com"]){
-                [tmpData addObject:sharedURL];
-                [_userDefaults setObject:tmpData forKey:@"tasks"];
-                [_userDefaults synchronize];
-            }else{
-                NSLog(@"Not tumblr");
-            }
-        }];
-    }
-    
-    
-//    NSString *type = (NSString *)
-    // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
-    
-}
 -(void)viewDidLoad{
     UIView *backgroundView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
     [backgroundView setBackgroundColor:[UIColor blackColor]];
@@ -70,17 +42,14 @@
             _SharedURL = url.absoluteString;
             dispatch_async(dispatch_get_main_queue(), ^{
                  [_FistViewURLText setText:_SharedURL];
+                NSLog(@"%@",_SharedURL);
             });
             if(![_SharedURL containsString:@"tumblr.com"]){
-                //[_FirstNextBtn setEnabled:NO];
+                [_FirstNextBtn setEnabled:NO];
             }
         }];
     }
 }
-- (NSArray *)configurationItems {
-    // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
-    return @[];
-}
 
 // Cancel
 - (IBAction)Cancel:(id)sender {
@@ -92,16 +61,100 @@
     [_AnalyseView setBackgroundColor:[UIColor whiteColor]];
     [_AnalyseView.layer setCornerRadius:5];
     [_AnalyseView.layer setMasksToBounds:YES];
-    _indicator = [[UIActivityIndicatorView alloc]initWithFrame:CGRectMake(0, 0, 80, 80)];
+    //Indicator
+    _indicator = [[UIActivityIndicatorView alloc]initWithFrame:CGRectMake(110, 0, 80, 80)];
     [self.view addSubview:_AnalyseView];
-    [_AnalyseView addSubview:_indicator];
     [_indicator startAnimating];
-    [UIView transitionFromView:_FirstView toView:_AnalyseView duration:0.5 options:UIViewAnimationOptionTransitionCrossDissolve completion:^(BOOL finished) {
+    [_indicator setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];
+    [_indicator setHidesWhenStopped:YES];
+    [_AnalyseView addSubview:_indicator];
+    [UIView transitionFromView:_FirstView toView:_AnalyseView duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve completion:^(BOOL finished) {
         [_FirstView removeFromSuperview];
+        dispatch_async(dispatch_get_main_queue(), ^{
+        });
+        [self startAnalyse];
+        
     }];
 }
 #pragma AnalyseURL
--(void)ChangeStatus{
+-(void)startAnalyse{
+    //StatusLabel
+    _StatusLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 20, 300, 60)];
+    [_StatusLabel setFont:[UIFont systemFontOfSize:15.0f]];
+    [_StatusLabel setTextColor:[UIColor grayColor]];
+    [_StatusLabel setTextAlignment:NSTextAlignmentCenter];
+    [_StatusLabel setNumberOfLines:0];
+    [_StatusLabel setHidden:YES];
+    [_AnalyseView addSubview:_StatusLabel];
+    
+    //DONE button
+    _DoneBtn = [[UIButton alloc]initWithFrame:CGRectMake(-1,100,302, 41)];
+    [_DoneBtn setHidden:YES];
+    [_DoneBtn setTitleColor:[UIColor colorWithRed:0 green:191/255.0 blue:1 alpha:1] forState:UIControlStateNormal];
+    [_DoneBtn setTitle:@"DONE" forState:UIControlStateNormal];
+    [_DoneBtn.layer setBorderWidth:1.0f];
+    [_DoneBtn.layer setBorderColor:[UIColor lightGrayColor].CGColor];
+    [_DoneBtn addTarget:self action:@selector(onPress:) forControlEvents:UIControlEventTouchDown];
+    [_AnalyseView addSubview:_DoneBtn];
+    
+    NSError *error = nil;
+    NSData *htmlData = [NSData dataWithContentsOfURL:[NSURL URLWithString:_SharedURL]];
+    TFHpple *doc = [[TFHpple alloc]initWithHTMLData:htmlData];
+    NSArray *eles = [doc searchWithXPathQuery:@"//iframe[@class='embed_iframe tumblr_video_iframe']"];
+    if([eles count]>0){
+        TFHppleElement *ele = [eles objectAtIndex:0];
+        NSString *secURL = [ele objectForKey:@"src"];
+        htmlData = [NSData dataWithContentsOfURL:[NSURL URLWithString:secURL]];
+        doc = [[TFHpple alloc]initWithHTMLData:htmlData];
+        eles = [doc searchWithXPathQuery:@"//source[@type='video/mp4']"];
+        if([eles count]>0){
+            ele = [eles objectAtIndex:0];
+            secURL = [ele objectForKey:@"src"];
+            NSLog(@"%@",secURL);
+    
+            NSRegularExpression *videoRegex = [NSRegularExpression regularExpressionWithPattern:@"tumblr_[a-zA-Z0-9]+" options:NSRegularExpressionCaseInsensitive error:&error] ;
+            NSTextCheckingResult *r = [videoRegex firstMatchInString:secURL options:NSMatchingReportCompletion range:NSMakeRange(0, secURL.length)];
     
+            _RealVideoID = [secURL substringWithRange:r.range];
+            [self success];
+        }else{
+            [self onError];
+        }
+    }else{
+        [self onError];
+    }
+//    NSLog(@"%@",[bodyNode rawContents]);
+
+}
+-(void)onPress:(id)sender{
+    [_DoneBtn setBackgroundColor:[UIColor colorWithRed:0 green:191/255.0 blue:1 alpha:1]];
+    [_DoneBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
+}
+-(void)success{
+    [_StatusLabel setHidden:NO];
+    [_StatusLabel setText:@"Success!\nOpen TumblrDownloader for next move"];
+    [_indicator stopAnimating];
+    [_DoneBtn addTarget:self action:@selector(saveToTasks:)forControlEvents:UIControlEventTouchUpInside];
+    [_DoneBtn setHidden:NO];
+}
+-(void)onError{
+    [_StatusLabel setHidden:NO];
+    [_StatusLabel setText:@"Error!\nUnavailable Tumblr Video post share"];
+    [_indicator stopAnimating];
+    [_DoneBtn addTarget:self action:@selector(finish:)forControlEvents:UIControlEventTouchUpInside];
+    [_DoneBtn setHidden:NO];
+}
+-(void)finish:(id)sender{
+    [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
+}
+-(void)saveToTasks:(id)sender{
+    _userDefaults = [[NSUserDefaults alloc]initWithSuiteName:@"group.honorlee.TDGroup"];
+    [_userDefaults synchronize];
+    NSMutableArray *tmpData = [NSMutableArray arrayWithArray:(NSMutableArray *)[_userDefaults objectForKey:@"tasks"]];
+    [tmpData addObject:_RealVideoID];
+    [_userDefaults setObject:tmpData forKey:@"tasks"];
+    [_userDefaults synchronize];
+
+    [self finish:nil];
 }
 @end

+ 57 - 0
ShareSupport/hpple/TFHpple.h

@@ -0,0 +1,57 @@
+//
+//  TFHpple.h
+//  Hpple
+//
+//  Created by Geoffrey Grosenbach on 1/31/09.
+//
+//  Copyright (c) 2009 Topfunky Corporation, http://topfunky.com
+//
+//  MIT LICENSE
+//
+//  Permission is hereby granted, free of charge, to any person obtaining
+//  a copy of this software and associated documentation files (the
+//  "Software"), to deal in the Software without restriction, including
+//  without limitation the rights to use, copy, modify, merge, publish,
+//  distribute, sublicense, and/or sell copies of the Software, and to
+//  permit persons to whom the Software is furnished to do so, subject to
+//  the following conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+//  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+//  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+//  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+//  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+#import <Foundation/Foundation.h>
+
+#import "TFHppleElement.h"
+
+@interface TFHpple : NSObject 
+
+- (id) initWithData:(NSData *)theData encoding:(NSString *)encoding isXML:(BOOL)isDataXML;
+- (id) initWithData:(NSData *)theData isXML:(BOOL)isDataXML;
+- (id) initWithXMLData:(NSData *)theData encoding:(NSString *)encoding;
+- (id) initWithXMLData:(NSData *)theData;
+- (id) initWithHTMLData:(NSData *)theData encoding:(NSString *)encoding;
+- (id) initWithHTMLData:(NSData *)theData;
+
++ (TFHpple *) hppleWithData:(NSData *)theData encoding:(NSString *)encoding isXML:(BOOL)isDataXML;
++ (TFHpple *) hppleWithData:(NSData *)theData isXML:(BOOL)isDataXML;
++ (TFHpple *) hppleWithXMLData:(NSData *)theData encoding:(NSString *)encoding;
++ (TFHpple *) hppleWithXMLData:(NSData *)theData;
++ (TFHpple *) hppleWithHTMLData:(NSData *)theData encoding:(NSString *)encoding;
++ (TFHpple *) hppleWithHTMLData:(NSData *)theData;
+
+- (NSArray *) searchWithXPathQuery:(NSString *)xPathOrCSS;
+- (TFHppleElement *) peekAtSearchWithXPathQuery:(NSString *)xPathOrCSS;
+
+@property (nonatomic, readonly) NSData * data;
+@property (nonatomic, readonly) NSString * encoding;
+
+@end

+ 141 - 0
ShareSupport/hpple/TFHpple.m

@@ -0,0 +1,141 @@
+//
+//  TFHpple.m
+//  Hpple
+//
+//  Created by Geoffrey Grosenbach on 1/31/09.
+//
+//  Copyright (c) 2009 Topfunky Corporation, http://topfunky.com
+//
+//  MIT LICENSE
+//
+//  Permission is hereby granted, free of charge, to any person obtaining
+//  a copy of this software and associated documentation files (the
+//  "Software"), to deal in the Software without restriction, including
+//  without limitation the rights to use, copy, modify, merge, publish,
+//  distribute, sublicense, and/or sell copies of the Software, and to
+//  permit persons to whom the Software is furnished to do so, subject to
+//  the following conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+//  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+//  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+//  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+//  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#import "TFHpple.h"
+#import "XPathQuery.h"
+
+@interface TFHpple ()
+{
+    NSData * data;
+    NSString * encoding;
+    BOOL isXML;
+}
+
+@end
+
+
+@implementation TFHpple
+
+@synthesize data;
+@synthesize encoding;
+
+
+- (id) initWithData:(NSData *)theData encoding:(NSString *)theEncoding isXML:(BOOL)isDataXML
+{
+  if (!(self = [super init])) {
+    return nil;
+  }
+
+  data = theData;
+  encoding = theEncoding;
+  isXML = isDataXML;
+
+  return self;
+}
+
+- (id) initWithData:(NSData *)theData isXML:(BOOL)isDataXML
+{
+    return [self initWithData:theData encoding:nil isXML:isDataXML];
+}
+
+- (id) initWithXMLData:(NSData *)theData encoding:(NSString *)theEncoding
+{
+  return [self initWithData:theData encoding:theEncoding isXML:YES];
+}
+
+- (id) initWithXMLData:(NSData *)theData
+{
+  return [self initWithData:theData encoding:nil isXML:YES];
+}
+
+- (id) initWithHTMLData:(NSData *)theData encoding:(NSString *)theEncoding
+{
+    return [self initWithData:theData encoding:theEncoding isXML:NO];
+}
+
+- (id) initWithHTMLData:(NSData *)theData
+{
+  return [self initWithData:theData encoding:nil isXML:NO];
+}
+
++ (TFHpple *) hppleWithData:(NSData *)theData encoding:(NSString *)theEncoding isXML:(BOOL)isDataXML {
+  return [[[self class] alloc] initWithData:theData encoding:theEncoding isXML:isDataXML];
+}
+
++ (TFHpple *) hppleWithData:(NSData *)theData isXML:(BOOL)isDataXML {
+  return [[self class] hppleWithData:theData encoding:nil isXML:isDataXML];
+}
+
++ (TFHpple *) hppleWithHTMLData:(NSData *)theData encoding:(NSString *)theEncoding {
+  return [[self class] hppleWithData:theData encoding:theEncoding isXML:NO];
+}
+
++ (TFHpple *) hppleWithHTMLData:(NSData *)theData {
+  return [[self class] hppleWithData:theData encoding:nil isXML:NO];
+}
+
++ (TFHpple *) hppleWithXMLData:(NSData *)theData encoding:(NSString *)theEncoding {
+  return [[self class] hppleWithData:theData encoding:theEncoding isXML:YES];
+}
+
++ (TFHpple *) hppleWithXMLData:(NSData *)theData {
+  return [[self class] hppleWithData:theData encoding:nil isXML:YES];
+}
+
+#pragma mark -
+
+// Returns all elements at xPath.
+- (NSArray *) searchWithXPathQuery:(NSString *)xPathOrCSS
+{
+  NSArray * detailNodes = nil;
+  if (isXML) {
+    detailNodes = PerformXMLXPathQueryWithEncoding(data, xPathOrCSS, encoding);
+  } else {
+    detailNodes = PerformHTMLXPathQueryWithEncoding(data, xPathOrCSS, encoding);
+  }
+
+  NSMutableArray * hppleElements = [NSMutableArray array];
+  for (id node in detailNodes) {
+    [hppleElements addObject:[TFHppleElement hppleElementWithNode:node isXML:isXML withEncoding:encoding]];
+  }
+  return hppleElements;
+}
+
+// Returns first element at xPath
+- (TFHppleElement *) peekAtSearchWithXPathQuery:(NSString *)xPathOrCSS
+{
+  NSArray * elements = [self searchWithXPathQuery:xPathOrCSS];
+  if ([elements count] >= 1) {
+    return [elements objectAtIndex:0];
+  }
+
+  return nil;
+}
+
+@end

+ 106 - 0
ShareSupport/hpple/TFHppleElement.h

@@ -0,0 +1,106 @@
+//
+//  TFHppleElement.h
+//  Hpple
+//
+//  Created by Geoffrey Grosenbach on 1/31/09.
+//
+//  Copyright (c) 2009 Topfunky Corporation, http://topfunky.com
+//
+//  MIT LICENSE
+//
+//  Permission is hereby granted, free of charge, to any person obtaining
+//  a copy of this software and associated documentation files (the
+//  "Software"), to deal in the Software without restriction, including
+//  without limitation the rights to use, copy, modify, merge, publish,
+//  distribute, sublicense, and/or sell copies of the Software, and to
+//  permit persons to whom the Software is furnished to do so, subject to
+//  the following conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+//  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+//  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+//  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+//  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+
+@interface TFHppleElement : NSObject
+
+- (id) initWithNode:(NSDictionary *) theNode isXML:(BOOL)isDataXML withEncoding:(NSString *)theEncoding;
+
++ (TFHppleElement *) hppleElementWithNode:(NSDictionary *) theNode isXML:(BOOL)isDataXML withEncoding:(NSString *)theEncoding;
+
+@property (nonatomic, copy, readonly) NSString *raw;
+// Returns this tag's innerHTML content.
+@property (nonatomic, copy, readonly) NSString *content;
+
+// Returns the name of the current tag, such as "h3".
+@property (nonatomic, copy, readonly) NSString *tagName;
+
+// Returns tag attributes with name as key and content as value.
+//   href  = 'http://peepcode.com'
+//   class = 'highlight'
+@property (nonatomic, strong, readonly) NSDictionary *attributes;
+
+// Returns the children of a given node
+@property (nonatomic, strong, readonly) NSArray *children;
+
+// Returns the first child of a given node
+@property (nonatomic, strong, readonly) TFHppleElement *firstChild;
+
+// the parent of a node
+@property (nonatomic, unsafe_unretained, readonly) TFHppleElement *parent;
+
+// Returns YES if the node has any child
+// This is more efficient than using the children property since no NSArray is constructed
+- (BOOL)hasChildren;
+
+// Returns YES if this is a text node
+- (BOOL)isTextNode;
+
+// Provides easy access to the content of a specific attribute, 
+// such as 'href' or 'class'.
+- (NSString *) objectForKey:(NSString *) theKey;
+
+// Returns the children whose tag name equals the given string
+// (comparison is performed with NSString's isEqualToString)
+// Returns an empty array if no matching child is found
+- (NSArray *) childrenWithTagName:(NSString *)tagName;
+
+// Returns the first child node whose tag name equals the given string
+// (comparison is performed with NSString's isEqualToString)
+// Returns nil if no matching child is found
+- (TFHppleElement *) firstChildWithTagName:(NSString *)tagName;
+
+// Returns the children whose class equals the given string
+// (comparison is performed with NSString's isEqualToString)
+// Returns an empty array if no matching child is found
+- (NSArray *) childrenWithClassName:(NSString *)className;
+
+// Returns the first child whose class requals the given string
+// (comparison is performed with NSString's isEqualToString)
+// Returns nil if no matching child is found
+- (TFHppleElement *) firstChildWithClassName:(NSString*)className;
+
+// Returns the first text node from this element's children
+// Returns nil if there is no text node among the children
+- (TFHppleElement *) firstTextChild;
+
+// Returns the string contained by the first text node from this element's children
+// Convenience method which can be used instead of firstTextChild.content
+- (NSString *) text;
+
+// Returns elements searched with xpath
+- (NSArray *) searchWithXPathQuery:(NSString *)xPathOrCSS;
+
+// Custom keyed subscripting
+- (id)objectForKeyedSubscript:(id)key;
+
+
+@end

+ 243 - 0
ShareSupport/hpple/TFHppleElement.m

@@ -0,0 +1,243 @@
+//
+//  TFHppleElement.m
+//  Hpple
+//
+//  Created by Geoffrey Grosenbach on 1/31/09.
+//
+//  Copyright (c) 2009 Topfunky Corporation, http://topfunky.com
+//
+//  MIT LICENSE
+//
+//  Permission is hereby granted, free of charge, to any person obtaining
+//  a copy of this software and associated documentation files (the
+//  "Software"), to deal in the Software without restriction, including
+//  without limitation the rights to use, copy, modify, merge, publish,
+//  distribute, sublicense, and/or sell copies of the Software, and to
+//  permit persons to whom the Software is furnished to do so, subject to
+//  the following conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+//  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+//  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+//  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+//  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+#import "TFHppleElement.h"
+#import "XPathQuery.h"
+
+static NSString * const TFHppleNodeContentKey           = @"nodeContent";
+static NSString * const TFHppleNodeNameKey              = @"nodeName";
+static NSString * const TFHppleNodeChildrenKey          = @"nodeChildArray";
+static NSString * const TFHppleNodeAttributeArrayKey    = @"nodeAttributeArray";
+static NSString * const TFHppleNodeAttributeNameKey     = @"attributeName";
+
+static NSString * const TFHppleTextNodeName            = @"text";
+
+@interface TFHppleElement ()
+{    
+    NSDictionary * node;
+    BOOL isXML;
+    NSString *encoding;
+    __unsafe_unretained TFHppleElement *parent;
+}
+
+@property (nonatomic, unsafe_unretained, readwrite) TFHppleElement *parent;
+
+@end
+
+@implementation TFHppleElement
+@synthesize parent;
+
+
+- (id) initWithNode:(NSDictionary *) theNode isXML:(BOOL)isDataXML withEncoding:(NSString *)theEncoding
+{
+  if (!(self = [super init]))
+    return nil;
+
+    isXML = isDataXML;
+    node = theNode;
+    encoding = theEncoding;
+
+  return self;
+}
+
++ (TFHppleElement *) hppleElementWithNode:(NSDictionary *) theNode isXML:(BOOL)isDataXML withEncoding:(NSString *)theEncoding
+{
+  return [[[self class] alloc] initWithNode:theNode isXML:isDataXML withEncoding:theEncoding];
+}
+
+#pragma mark -
+
+- (NSString *)raw
+{
+    return [node objectForKey:@"raw"];
+}
+
+- (NSString *) content
+{
+  return [node objectForKey:TFHppleNodeContentKey];
+}
+
+
+- (NSString *) tagName
+{
+  return [node objectForKey:TFHppleNodeNameKey];
+}
+
+- (NSArray *) children
+{
+  NSMutableArray *children = [NSMutableArray array];
+  for (NSDictionary *child in [node objectForKey:TFHppleNodeChildrenKey]) {
+      TFHppleElement *element = [TFHppleElement hppleElementWithNode:child isXML:isXML withEncoding:encoding];
+      element.parent = self;
+      [children addObject:element];
+  }
+  return children;
+}
+
+- (TFHppleElement *) firstChild
+{
+  NSArray * children = self.children;
+  if (children.count)
+    return [children objectAtIndex:0];
+  return nil;
+}
+
+
+- (NSDictionary *) attributes
+{
+  NSMutableDictionary * translatedAttributes = [NSMutableDictionary dictionary];
+  for (NSDictionary * attributeDict in [node objectForKey:TFHppleNodeAttributeArrayKey]) {
+      if ([attributeDict objectForKey:TFHppleNodeContentKey] && [attributeDict objectForKey:TFHppleNodeAttributeNameKey]) {
+          [translatedAttributes setObject:[attributeDict objectForKey:TFHppleNodeContentKey]
+                                   forKey:[attributeDict objectForKey:TFHppleNodeAttributeNameKey]];
+      }
+  }
+  return translatedAttributes;
+}
+
+- (NSString *) objectForKey:(NSString *) theKey
+{
+  return [[self attributes] objectForKey:theKey];
+}
+
+- (id) description
+{
+  return [node description];
+}
+
+- (BOOL)hasChildren
+{
+    if ([node objectForKey:TFHppleNodeChildrenKey])
+        return YES;
+    else
+        return NO;
+}
+
+- (BOOL)isTextNode
+{
+    // we must distinguish between real text nodes and standard nodes with tha name "text" (<text>)
+    // real text nodes must have content
+    if ([self.tagName isEqualToString:TFHppleTextNodeName] && (self.content))
+        return YES;
+    else
+        return NO;
+}
+
+- (NSArray*) childrenWithTagName:(NSString*)tagName
+{
+    NSMutableArray* matches = [NSMutableArray array];
+    
+    for (TFHppleElement* child in self.children)
+    {
+        if ([child.tagName isEqualToString:tagName])
+            [matches addObject:child];
+    }
+    
+    return matches;
+}
+
+- (TFHppleElement *) firstChildWithTagName:(NSString*)tagName
+{
+    for (TFHppleElement* child in self.children)
+    {
+        if ([child.tagName isEqualToString:tagName])
+            return child;
+    }
+    
+    return nil;
+}
+
+- (NSArray*) childrenWithClassName:(NSString*)className
+{
+    NSMutableArray* matches = [NSMutableArray array];
+    
+    for (TFHppleElement* child in self.children)
+    {
+        if ([[child objectForKey:@"class"] isEqualToString:className])
+            [matches addObject:child];
+    }
+    
+    return matches;
+}
+
+- (TFHppleElement *) firstChildWithClassName:(NSString*)className
+{
+    for (TFHppleElement* child in self.children)
+    {
+        if ([[child objectForKey:@"class"] isEqualToString:className])
+            return child;
+    }
+    
+    return nil;
+}
+
+- (TFHppleElement *) firstTextChild
+{
+    for (TFHppleElement* child in self.children)
+    {
+        if ([child isTextNode])
+            return child;
+    }
+    
+    return [self firstChildWithTagName:TFHppleTextNodeName];
+}
+
+- (NSString *) text
+{
+    return self.firstTextChild.content;
+}
+
+// Returns all elements at xPath.
+- (NSArray *) searchWithXPathQuery:(NSString *)xPathOrCSS
+{
+    
+    NSData *data = [self.raw dataUsingEncoding:NSUTF8StringEncoding];
+
+    NSArray * detailNodes = nil;
+    if (isXML) {
+        detailNodes = PerformXMLXPathQueryWithEncoding(data, xPathOrCSS, encoding);
+    } else {
+        detailNodes = PerformHTMLXPathQueryWithEncoding(data, xPathOrCSS, encoding);
+    }
+    
+    NSMutableArray * hppleElements = [NSMutableArray array];
+    for (id newNode in detailNodes) {
+        [hppleElements addObject:[TFHppleElement hppleElementWithNode:newNode isXML:isXML withEncoding:encoding]];
+    }
+    return hppleElements;
+}
+
+// Custom keyed subscripting
+- (id)objectForKeyedSubscript:(id)key
+{
+    return [self objectForKey:key];
+}
+
+@end

+ 14 - 0
ShareSupport/hpple/XPathQuery.h

@@ -0,0 +1,14 @@
+//
+//  XPathQuery.h
+//  FuelFinder
+//
+//  Created by Matt Gallagher on 4/08/08.
+//  Copyright 2008 __MyCompanyName__. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NSArray *PerformHTMLXPathQuery(NSData *document, NSString *query);
+NSArray *PerformHTMLXPathQueryWithEncoding(NSData *document, NSString *query,NSString *encoding);
+NSArray *PerformXMLXPathQuery(NSData *document, NSString *query);
+NSArray *PerformXMLXPathQueryWithEncoding(NSData *document, NSString *query,NSString *encoding);

+ 196 - 0
ShareSupport/hpple/XPathQuery.m

@@ -0,0 +1,196 @@
+//
+//  XPathQuery.m
+//  FuelFinder
+//
+//  Created by Matt Gallagher on 4/08/08.
+//  Copyright 2008 __MyCompanyName__. All rights reserved.
+//
+
+#import "XPathQuery.h"
+
+#import <libxml/tree.h>
+#import <libxml/parser.h>
+#import <libxml/HTMLparser.h>
+#import <libxml/xpath.h>
+#import <libxml/xpathInternals.h>
+
+NSDictionary *DictionaryForNode(xmlNodePtr currentNode, NSMutableDictionary *parentResult,BOOL parentContent);
+NSArray *PerformXPathQuery(xmlDocPtr doc, NSString *query);
+
+NSDictionary *DictionaryForNode(xmlNodePtr currentNode, NSMutableDictionary *parentResult,BOOL parentContent)
+{
+    NSMutableDictionary *resultForNode = [NSMutableDictionary dictionary];
+    if (currentNode->name) {
+        NSString *currentNodeContent = [NSString stringWithCString:(const char *)currentNode->name
+                                                          encoding:NSUTF8StringEncoding];
+        resultForNode[@"nodeName"] = currentNodeContent;
+    }
+
+    xmlChar *nodeContent = xmlNodeGetContent(currentNode);
+    if (nodeContent != NULL) {
+        NSString *currentNodeContent = [NSString stringWithCString:(const char *)nodeContent
+                                                          encoding:NSUTF8StringEncoding];
+        if ([resultForNode[@"nodeName"] isEqual:@"text"] && parentResult) {
+            if (parentContent) {
+                NSCharacterSet *charactersToTrim = [NSCharacterSet whitespaceAndNewlineCharacterSet];
+                parentResult[@"nodeContent"] = [currentNodeContent stringByTrimmingCharactersInSet:charactersToTrim];
+                return nil;
+            }
+            if (currentNodeContent != nil) {
+                resultForNode[@"nodeContent"] = currentNodeContent;
+            }
+            return resultForNode;
+        } else {
+            resultForNode[@"nodeContent"] = currentNodeContent;
+        }
+        xmlFree(nodeContent);
+    }
+
+    xmlAttr *attribute = currentNode->properties;
+    if (attribute) {
+        NSMutableArray *attributeArray = [NSMutableArray array];
+        while (attribute) {
+            NSMutableDictionary *attributeDictionary = [NSMutableDictionary dictionary];
+            NSString *attributeName = [NSString stringWithCString:(const char *)attribute->name
+                                                       encoding:NSUTF8StringEncoding];
+            if (attributeName) {
+                attributeDictionary[@"attributeName"] = attributeName;
+            }
+          
+            if (attribute->children) {
+                NSDictionary *childDictionary = DictionaryForNode(attribute->children, attributeDictionary, true);
+                if (childDictionary) {
+                    attributeDictionary[@"attributeContent"] = childDictionary;
+                }
+            }
+
+            if ([attributeDictionary count] > 0) {
+                [attributeArray addObject:attributeDictionary];
+            }
+            attribute = attribute->next;
+        }
+
+        if ([attributeArray count] > 0) {
+            resultForNode[@"nodeAttributeArray"] = attributeArray;
+        }
+    }
+
+    xmlNodePtr childNode = currentNode->children;
+    if (childNode) {
+        NSMutableArray *childContentArray = [NSMutableArray array];
+        while (childNode) {
+            NSDictionary *childDictionary = DictionaryForNode(childNode, resultForNode,false);
+            if (childDictionary) {
+                [childContentArray addObject:childDictionary];
+            }
+            childNode = childNode->next;
+        }
+        if ([childContentArray count] > 0) {
+            resultForNode[@"nodeChildArray"] = childContentArray;
+        }
+    }
+
+    xmlBufferPtr buffer = xmlBufferCreate();
+    xmlNodeDump(buffer, currentNode->doc, currentNode, 0, 0);
+    NSString *rawContent = [NSString stringWithCString:(const char *)buffer->content encoding:NSUTF8StringEncoding];
+    if (rawContent != nil) {
+        resultForNode[@"raw"] = rawContent;
+    }
+    xmlBufferFree(buffer);
+    return resultForNode;
+}
+
+NSArray *PerformXPathQuery(xmlDocPtr doc, NSString *query)
+{
+    xmlXPathContextPtr xpathCtx;
+    xmlXPathObjectPtr xpathObj;
+
+    /* Make sure that passed query is non-nil and is NSString object */
+    if (query == nil || ![query isKindOfClass:[NSString class]]) {
+        return nil;
+    }
+  
+    /* Create xpath evaluation context */
+    xpathCtx = xmlXPathNewContext(doc);
+    if(xpathCtx == NULL) {
+        NSLog(@"Unable to create XPath context.");
+        return nil;
+    }
+
+    /* Evaluate xpath expression */
+    xpathObj = xmlXPathEvalExpression((xmlChar *)[query cStringUsingEncoding:NSUTF8StringEncoding], xpathCtx);
+    if(xpathObj == NULL) {
+        NSLog(@"Unable to evaluate XPath.");
+        xmlXPathFreeContext(xpathCtx);
+        return nil;
+    }
+
+    xmlNodeSetPtr nodes = xpathObj->nodesetval;
+    if (!nodes) {
+        NSLog(@"Nodes was nil.");
+        xmlXPathFreeObject(xpathObj);
+        xmlXPathFreeContext(xpathCtx);
+        return nil;
+    }
+
+    NSMutableArray *resultNodes = [NSMutableArray array];
+    for (NSInteger i = 0; i < nodes->nodeNr; i++) {
+        NSDictionary *nodeDictionary = DictionaryForNode(nodes->nodeTab[i], nil,false);
+        if (nodeDictionary) {
+            [resultNodes addObject:nodeDictionary];
+        }
+    }
+
+    /* Cleanup */
+    xmlXPathFreeObject(xpathObj);
+    xmlXPathFreeContext(xpathCtx);
+
+    return resultNodes;
+}
+
+NSArray *PerformHTMLXPathQuery(NSData *document, NSString *query) {
+    return PerformHTMLXPathQueryWithEncoding(document, query, nil);
+}
+
+NSArray *PerformHTMLXPathQueryWithEncoding(NSData *document, NSString *query,NSString *encoding)
+{
+    xmlDocPtr doc;
+
+    /* Load XML document */
+    const char *encoded = encoding ? [encoding cStringUsingEncoding:NSUTF8StringEncoding] : NULL;
+
+    doc = htmlReadMemory([document bytes], (int)[document length], "", encoded, HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR);
+    if (doc == NULL) {
+        NSLog(@"Unable to parse.");
+        return nil;
+    }
+    
+    NSArray *result = PerformXPathQuery(doc, query);
+    xmlFreeDoc(doc);
+    
+    return result;
+}
+
+NSArray *PerformXMLXPathQuery(NSData *document, NSString *query) {
+    return PerformXMLXPathQueryWithEncoding(document, query, nil);
+}
+
+NSArray *PerformXMLXPathQueryWithEncoding(NSData *document, NSString *query,NSString *encoding)
+{
+    xmlDocPtr doc;
+    
+    /* Load XML document */
+    const char *encoded = encoding ? [encoding cStringUsingEncoding:NSUTF8StringEncoding] : NULL;
+
+    doc = xmlReadMemory([document bytes], (int)[document length], "", encoded, XML_PARSE_RECOVER);
+    
+    if (doc == NULL) {
+        NSLog(@"Unable to parse.");
+        return nil;
+    }
+    
+    NSArray *result = PerformXPathQuery(doc, query);
+    xmlFreeDoc(doc);
+    
+    return result;
+}

+ 30 - 4
TumblrDownloader.xcodeproj/project.pbxproj

@@ -9,7 +9,6 @@
 /* Begin PBXBuildFile section */
 		D30DC0C01C4F68C300545EF1 /* TumblrDownloader.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = D386B1301C469FDB00D7E0C5 /* TumblrDownloader.entitlements */; };
 		D30DC0C11C4F68C700545EF1 /* ShareSupport.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = D386B1311C46A0F900D7E0C5 /* ShareSupport.entitlements */; };
-		D30DC0CC1C4FA2E200545EF1 /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D30DC0CA1C4FA28200545EF1 /* libxml2.tbd */; };
 		D3166D7C1C48F3F50003B6B8 /* Downloader.m in Sources */ = {isa = PBXBuildFile; fileRef = D3166D7B1C48F3F50003B6B8 /* Downloader.m */; };
 		D3166D811C48F9C20003B6B8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D3166D831C48F9C20003B6B8 /* Localizable.strings */; };
 		D36ACDC71C44D07E00B2C7CF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D36ACDC61C44D07E00B2C7CF /* main.m */; };
@@ -38,6 +37,10 @@
 		D386B1761C4BB08900D7E0C5 /* TasksViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D386B1751C4BB08900D7E0C5 /* TasksViewController.m */; };
 		D386B1791C4BD42500D7E0C5 /* DownloadQueenViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D386B1781C4BD42500D7E0C5 /* DownloadQueenViewController.m */; };
 		D386B17E1C4BEE8C00D7E0C5 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D386B17D1C4BEE8C00D7E0C5 /* MobileCoreServices.framework */; };
+		D3FA7C121C4FCF1600BC7FE6 /* libxml2.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D3FA7C111C4FCF1600BC7FE6 /* libxml2.2.dylib */; };
+		D3FA7C221C4FD3A300BC7FE6 /* TFHpple.m in Sources */ = {isa = PBXBuildFile; fileRef = D3FA7C1D1C4FD3A300BC7FE6 /* TFHpple.m */; };
+		D3FA7C231C4FD3A300BC7FE6 /* TFHppleElement.m in Sources */ = {isa = PBXBuildFile; fileRef = D3FA7C1F1C4FD3A300BC7FE6 /* TFHppleElement.m */; };
+		D3FA7C241C4FD3A300BC7FE6 /* XPathQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = D3FA7C211C4FD3A300BC7FE6 /* XPathQuery.m */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -65,7 +68,6 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-		D30DC0CA1C4FA28200545EF1 /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = usr/lib/libxml2.tbd; sourceTree = SDKROOT; };
 		D3166D7A1C48F3F50003B6B8 /* Downloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Downloader.h; sourceTree = "<group>"; };
 		D3166D7B1C48F3F50003B6B8 /* Downloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Downloader.m; sourceTree = "<group>"; };
 		D3166D821C48F9C20003B6B8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -110,6 +112,13 @@
 		D386B1771C4BD42500D7E0C5 /* DownloadQueenViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DownloadQueenViewController.h; sourceTree = "<group>"; };
 		D386B1781C4BD42500D7E0C5 /* DownloadQueenViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DownloadQueenViewController.m; sourceTree = "<group>"; };
 		D386B17D1C4BEE8C00D7E0C5 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
+		D3FA7C111C4FCF1600BC7FE6 /* libxml2.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.2.dylib; path = ../../../../../usr/lib/libxml2.2.dylib; sourceTree = "<group>"; };
+		D3FA7C1C1C4FD3A300BC7FE6 /* TFHpple.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TFHpple.h; path = hpple/TFHpple.h; sourceTree = "<group>"; };
+		D3FA7C1D1C4FD3A300BC7FE6 /* TFHpple.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TFHpple.m; path = hpple/TFHpple.m; sourceTree = "<group>"; };
+		D3FA7C1E1C4FD3A300BC7FE6 /* TFHppleElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TFHppleElement.h; path = hpple/TFHppleElement.h; sourceTree = "<group>"; };
+		D3FA7C1F1C4FD3A300BC7FE6 /* TFHppleElement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TFHppleElement.m; path = hpple/TFHppleElement.m; sourceTree = "<group>"; };
+		D3FA7C201C4FD3A300BC7FE6 /* XPathQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XPathQuery.h; path = hpple/XPathQuery.h; sourceTree = "<group>"; };
+		D3FA7C211C4FD3A300BC7FE6 /* XPathQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XPathQuery.m; path = hpple/XPathQuery.m; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -124,7 +133,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				D30DC0CC1C4FA2E200545EF1 /* libxml2.tbd in Frameworks */,
+				D3FA7C121C4FCF1600BC7FE6 /* libxml2.2.dylib in Frameworks */,
 				D386B17E1C4BEE8C00D7E0C5 /* MobileCoreServices.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -135,7 +144,7 @@
 		D36ACDB91C44D07E00B2C7CF = {
 			isa = PBXGroup;
 			children = (
-				D30DC0CA1C4FA28200545EF1 /* libxml2.tbd */,
+				D3FA7C111C4FCF1600BC7FE6 /* libxml2.2.dylib */,
 				D386B17D1C4BEE8C00D7E0C5 /* MobileCoreServices.framework */,
 				D36ACDC41C44D07E00B2C7CF /* TumblrDownloader */,
 				D386B1211C46991400D7E0C5 /* ShareSupport */,
@@ -196,6 +205,7 @@
 		D386B1211C46991400D7E0C5 /* ShareSupport */ = {
 			isa = PBXGroup;
 			children = (
+				D3FA7C1B1C4FD39400BC7FE6 /* hpple */,
 				D386B1311C46A0F900D7E0C5 /* ShareSupport.entitlements */,
 				D386B1221C46991400D7E0C5 /* ShareViewController.h */,
 				D386B1231C46991400D7E0C5 /* ShareViewController.m */,
@@ -222,6 +232,19 @@
 			name = Assets;
 			sourceTree = "<group>";
 		};
+		D3FA7C1B1C4FD39400BC7FE6 /* hpple */ = {
+			isa = PBXGroup;
+			children = (
+				D3FA7C1C1C4FD3A300BC7FE6 /* TFHpple.h */,
+				D3FA7C1D1C4FD3A300BC7FE6 /* TFHpple.m */,
+				D3FA7C1E1C4FD3A300BC7FE6 /* TFHppleElement.h */,
+				D3FA7C1F1C4FD3A300BC7FE6 /* TFHppleElement.m */,
+				D3FA7C201C4FD3A300BC7FE6 /* XPathQuery.h */,
+				D3FA7C211C4FD3A300BC7FE6 /* XPathQuery.m */,
+			);
+			name = hpple;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -373,6 +396,9 @@
 			buildActionMask = 2147483647;
 			files = (
 				D386B1241C46991400D7E0C5 /* ShareViewController.m in Sources */,
+				D3FA7C221C4FD3A300BC7FE6 /* TFHpple.m in Sources */,
+				D3FA7C241C4FD3A300BC7FE6 /* XPathQuery.m in Sources */,
+				D3FA7C231C4FD3A300BC7FE6 /* TFHppleElement.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

BIN
TumblrDownloader.xcodeproj/project.xcworkspace/xcuserdata/HonorLee.xcuserdatad/UserInterfaceState.xcuserstate


+ 1 - 1
TumblrDownloader/zh-Hans.lproj/Localizable.strings

@@ -8,7 +8,7 @@
 "OpenTumblrBtnText" = "打开Tumblr";
 "OpenTumblrSuccess" = "正在打开Tumblr";
 "OpenTumblrError"   = "打开Tumblr失败,请确定已安装Tumblr";
-"HelpText" = "1. 打开 Tumblr 应用\n\n2. 找到包含了希望搜集的内容的帖子,并点击分享按钮\n\n3. 在弹出的分享界面上选择TumblrDownloader以获取当前帖子的URL地址\n\n4. 打开 TumblrDownloader 并选择\"任务\"分类\n\n5. 在这里你可以开始或删除已搜集的Tumblr下载任务";
+"HelpText" = "1. 打开 Tumblr 应用\n\n2. 找到包含希望下载视频的帖子,并点击分享按钮\n\n3. 在弹出的分享界面上选择TumblrDownloader以获取当前视频的唯一ID\n\n4. 打开 TumblrDownloader 并选择\"待下载\"分类\n\n5. 在这里你可以开始或删除已搜集的Tumblr下载任务";
 
 "PullToRefresh"     = "下拉刷新...";
 "PullToRefreshing"  = "刷新中...";