From e67703933ce90609f82db55f64c51698677d4231 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Wed, 18 Mar 2026 09:28:23 -0400 Subject: [PATCH 01/10] Add WordPressShared dependency to WordPressKitObjC --- Modules/Package.swift | 2 + .../AccountServiceRemoteREST.m | 6 +- .../WordPressKitObjC/BlogServiceRemoteREST.m | 6 +- .../BlogServiceRemoteXMLRPC.m | 6 +- .../CommentServiceRemoteREST.m | 6 +- .../CommentServiceRemoteXMLRPC.m | 4 +- .../WordPressKitObjC/DisplayableImageHelper.m | 281 ------------------ .../WordPressKitObjC/MediaServiceRemoteREST.m | 4 +- .../MediaServiceRemoteXMLRPC.m | 4 +- .../WordPressKitObjC/MenusServiceRemote.m | 8 +- .../WordPressKitObjC/PostServiceRemoteREST.m | 15 +- .../PostServiceRemoteXMLRPC.m | 17 +- .../ReaderPostServiceRemote.m | 4 +- .../ReaderTopicServiceRemote.m | 6 +- .../WordPressKitObjC/RemoteReaderPost.m | 6 +- .../TaxonomyServiceRemoteREST.m | 7 +- .../TaxonomyServiceRemoteXMLRPC.m | 6 +- .../WordPressKitObjC/WPMapFilterReduce.m | 28 -- .../include/DisplayableImageHelper.h | 38 --- .../include/WPMapFilterReduce.h | 22 -- 20 files changed, 54 insertions(+), 422 deletions(-) delete mode 100644 Modules/Sources/WordPressKitObjC/DisplayableImageHelper.m delete mode 100644 Modules/Sources/WordPressKitObjC/WPMapFilterReduce.m delete mode 100644 Modules/Sources/WordPressKitObjC/include/DisplayableImageHelper.h delete mode 100644 Modules/Sources/WordPressKitObjC/include/WPMapFilterReduce.h diff --git a/Modules/Package.swift b/Modules/Package.swift index 636097166d26..e9a13001b458 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -24,6 +24,7 @@ let package = Package( .library(name: "WordPressReader", targets: ["WordPressReader"]), .library(name: "WordPressCore", targets: ["WordPressCore"]), .library(name: "WordPressCoreProtocols", targets: ["WordPressCoreProtocols"]), + .library(name: "WordPressKit", targets: ["WordPressKit"]), ], dependencies: [ .package(url: "https://github.com/airbnb/lottie-ios", from: "4.4.0"), @@ -213,6 +214,7 @@ let package = Package( dependencies: [ "NSObject-SafeExpectations", "wpxmlrpc", + "WordPressShared", "WordPressKitModels", "WordPressKitObjCUtils", ], diff --git a/Modules/Sources/WordPressKitObjC/AccountServiceRemoteREST.m b/Modules/Sources/WordPressKitObjC/AccountServiceRemoteREST.m index 984df146bfc8..15c72d08a4f8 100644 --- a/Modules/Sources/WordPressKitObjC/AccountServiceRemoteREST.m +++ b/Modules/Sources/WordPressKitObjC/AccountServiceRemoteREST.m @@ -1,6 +1,6 @@ #import "AccountServiceRemoteREST.h" -#import "WPMapFilterReduce.h" +@import WordPressShared; @import WordPressKitModels; @import NSObject_SafeExpectations; @@ -420,9 +420,9 @@ - (RemoteUser *)remoteUserFromDictionary:(NSDictionary *)dictionary - (NSArray *)remoteBlogsFromJSONArray:(NSArray *)jsonBlogs { NSArray *blogs = jsonBlogs; - return [[blogs wpkit_map:^id(NSDictionary *jsonBlog) { + return [[blogs wp_map:^id(NSDictionary *jsonBlog) { return [[RemoteBlog alloc] initWithJSONDictionary:jsonBlog]; - }] wpkit_filter:^BOOL(RemoteBlog *blog) { + }] wp_filter:^BOOL(RemoteBlog *blog) { // Exclude deleted sites from query result, since the app does not handle deleted sites properly. // I tried to use query arguments `site_visibility=visible` and `site_activity=active`, but neither excludes // deleted sites. diff --git a/Modules/Sources/WordPressKitObjC/BlogServiceRemoteREST.m b/Modules/Sources/WordPressKitObjC/BlogServiceRemoteREST.m index 6670ec55862b..153a3b32ae12 100644 --- a/Modules/Sources/WordPressKitObjC/BlogServiceRemoteREST.m +++ b/Modules/Sources/WordPressKitObjC/BlogServiceRemoteREST.m @@ -2,9 +2,9 @@ #import "BlogServiceRemoteREST.h" #import "NSMutableDictionary+Helpers.h" #import "RemotePostType.h" -#import "WPMapFilterReduce.h" #import "WPKitLogging.h" +@import WordPressShared; @import WordPressKitModels; @import NSObject_SafeExpectations; @@ -145,7 +145,7 @@ - (void)syncPostTypesWithSuccess:(PostTypesHandler)success success:^(NSDictionary *responseObject, NSHTTPURLResponse *httpResponse) { NSAssert([responseObject isKindOfClass:[NSDictionary class]], @"Response should be a dictionary."); - NSArray *postTypes = [[responseObject arrayForKey:RemotePostTypesKey] wpkit_map:^id(NSDictionary *json) { + NSArray *postTypes = [[responseObject arrayForKey:RemotePostTypesKey] wp_map:^id(NSDictionary *json) { return [self remotePostTypeWithDictionary:json]; }]; if (!postTypes.count) { @@ -340,7 +340,7 @@ - (NSString *)pathForSettings - (NSArray *)usersFromJSONArray:(NSArray *)jsonUsers { - return [jsonUsers wpkit_map:^RemoteUser *(NSDictionary *jsonUser) { + return [jsonUsers wp_map:^RemoteUser *(NSDictionary *jsonUser) { return [self userFromJSONDictionary:jsonUser]; }]; } diff --git a/Modules/Sources/WordPressKitObjC/BlogServiceRemoteXMLRPC.m b/Modules/Sources/WordPressKitObjC/BlogServiceRemoteXMLRPC.m index 674d86cad0fd..2a734624885e 100644 --- a/Modules/Sources/WordPressKitObjC/BlogServiceRemoteXMLRPC.m +++ b/Modules/Sources/WordPressKitObjC/BlogServiceRemoteXMLRPC.m @@ -1,9 +1,9 @@ #import "BlogServiceRemoteXMLRPC.h" #import "NSMutableDictionary+Helpers.h" #import "RemotePostType.h" -#import "WPMapFilterReduce.h" #import "WPKitLogging.h" +@import WordPressShared; @import WordPressKitModels; @import NSObject_SafeExpectations; @@ -49,7 +49,7 @@ - (void)getAllAuthorsWithRemoteUsers:(NSMutableArray *)remoteUsers [self.api callMethod:@"wp.getUsers" parameters:parameters success:^(id responseObject, NSHTTPURLResponse *response) { - NSArray *responseUsers = [[responseObject allObjects] wpkit_map:^id(NSDictionary *xmlrpcUser) { + NSArray *responseUsers = [[responseObject allObjects] wp_map:^id(NSDictionary *xmlrpcUser) { return [self remoteUserFromXMLRPCDictionary:xmlrpcUser]; }]; @@ -82,7 +82,7 @@ - (void)syncPostTypesWithSuccess:(PostTypesHandler)success failure:(void (^)(NSE success:^(id responseObject, NSHTTPURLResponse *response) { NSAssert([responseObject isKindOfClass:[NSDictionary class]], @"Response should be a dictionary."); - NSArray *postTypes = [[responseObject allObjects] wpkit_map:^id(NSDictionary *json) { + NSArray *postTypes = [[responseObject allObjects] wp_map:^id(NSDictionary *json) { return [self remotePostTypeFromXMLRPCDictionary:json]; }]; if (!postTypes.count) { diff --git a/Modules/Sources/WordPressKitObjC/CommentServiceRemoteREST.m b/Modules/Sources/WordPressKitObjC/CommentServiceRemoteREST.m index 556eb7c8c71f..89db026672f3 100644 --- a/Modules/Sources/WordPressKitObjC/CommentServiceRemoteREST.m +++ b/Modules/Sources/WordPressKitObjC/CommentServiceRemoteREST.m @@ -1,7 +1,7 @@ #import "CommentServiceRemoteREST.h" #import "RemoteComment.h" -#import "WPMapFilterReduce.h" +@import WordPressShared; @import WordPressKitModels; @import NSObject_SafeExpectations; @@ -464,7 +464,7 @@ - (void)getLikesForCommentID:(NSNumber *)commentID - (NSArray *)remoteCommentsFromJSONArray:(NSArray *)jsonComments { - return [jsonComments wpkit_map:^id(NSDictionary *jsonComment) { + return [jsonComments wp_map:^id(NSDictionary *jsonComment) { return [self remoteCommentFromJSONDictionary:jsonComment]; }]; } @@ -530,7 +530,7 @@ - (NSString *)remoteStatusWithStatus:(NSString *)status commentID:(NSNumber *)commentID siteID:(NSNumber *)siteID { - return [jsonUsers wpkit_map:^id(NSDictionary *jsonUser) { + return [jsonUsers wp_map:^id(NSDictionary *jsonUser) { return [[RemoteLikeUser alloc] initWithDictionary:jsonUser commentID:commentID siteID:siteID]; }]; } diff --git a/Modules/Sources/WordPressKitObjC/CommentServiceRemoteXMLRPC.m b/Modules/Sources/WordPressKitObjC/CommentServiceRemoteXMLRPC.m index 5a7fd9091132..0a57e3659e4f 100644 --- a/Modules/Sources/WordPressKitObjC/CommentServiceRemoteXMLRPC.m +++ b/Modules/Sources/WordPressKitObjC/CommentServiceRemoteXMLRPC.m @@ -1,7 +1,7 @@ #import "CommentServiceRemoteXMLRPC.h" #import "RemoteComment.h" -#import "WPMapFilterReduce.h" +@import WordPressShared; @import wpxmlrpc; @import NSObject_SafeExpectations; @@ -203,7 +203,7 @@ - (void)trashComment:(RemoteComment *)comment - (NSArray *)remoteCommentsFromXMLRPCArray:(NSArray *)xmlrpcArray { - return [xmlrpcArray wpkit_map:^id(NSDictionary *xmlrpcComment) { + return [xmlrpcArray wp_map:^id(NSDictionary *xmlrpcComment) { return [self remoteCommentFromXMLRPCDictionary:xmlrpcComment]; }]; } diff --git a/Modules/Sources/WordPressKitObjC/DisplayableImageHelper.m b/Modules/Sources/WordPressKitObjC/DisplayableImageHelper.m deleted file mode 100644 index d8d360c177f8..000000000000 --- a/Modules/Sources/WordPressKitObjC/DisplayableImageHelper.m +++ /dev/null @@ -1,281 +0,0 @@ -#import "DisplayableImageHelper.h" -#import "NSString+Helpers.h" - -static const NSInteger FeaturedImageMinimumWidth = 150; - -static NSString * const AttachmentsDictionaryKeyWidth = @"width"; -static NSString * const AttachmentsDictionaryKeyURL = @"URL"; -static NSString * const AttachmentsDictionaryKeyMimeType = @"mime_type"; - -@implementation WPKitDisplayableImageHelper - -+ (NSInteger)widthOfAttachment:(NSDictionary *)attachment { - NSInteger result = 0; - id obj = [attachment objectForKey:AttachmentsDictionaryKeyWidth]; - if ([obj isKindOfClass:NSNumber.class]) { - NSNumber *number = (NSNumber *)obj; - result = [number integerValue]; - } else if ([obj isKindOfClass:NSString.class]) { - NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; - numberFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; - NSNumber *number= [numberFormatter numberFromString:(NSString *)obj]; - result = [number integerValue]; - } - return result; -} - -+ (NSString *)searchPostAttachmentsForImageToDisplay:(NSDictionary *)attachmentsDict existingInContent:(NSString *)content -{ - NSArray *attachments = [attachmentsDict allValues]; - if ([attachments count] == 0) { - return nil; - } - - NSString *imageToDisplay; - - attachments = [self filteredAttachmentsArray:attachments]; - - for (NSDictionary *attachment in attachments) { - NSInteger width = [self widthOfAttachment:attachment]; - if (width < FeaturedImageMinimumWidth) { - // The remaining images are too small so just stop now. - break; - } - id obj = attachment[AttachmentsDictionaryKeyURL]; - if ([obj isKindOfClass:NSString.class]) { - NSString *maybeImage = (NSString *)obj; - if ([content containsString:maybeImage]) { - imageToDisplay = maybeImage; - break; - } - } - } - - return imageToDisplay; -} - -+ (NSArray *)filteredAttachmentsArray:(NSArray *)attachments -{ - NSString *key = AttachmentsDictionaryKeyMimeType; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K BEGINSWITH %@", key, @"image"]; - attachments = [attachments filteredArrayUsingPredicate:predicate]; - attachments = [self sortAttachmentsArray:attachments]; - return attachments; -} - -+ (NSArray *)sortAttachmentsArray:(NSArray *)attachments -{ - return [attachments sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *attachmentA, NSDictionary *attachmentB) { - NSInteger widthA = [self widthOfAttachment:attachmentA]; - NSInteger widthB = [self widthOfAttachment:attachmentB]; - - if (widthA < widthB) { - return NSOrderedDescending; - } else if (widthA > widthB) { - return NSOrderedAscending; - } else { - return NSOrderedSame; - } - }]; -} - -+ (NSString *)searchPostContentForImageToDisplay:(NSString *)content -{ - NSString *imageSrc = @""; - // If there is no image tag in the content, just bail. - if (!content || [content rangeOfString:@""; - regex = [NSRegularExpression regularExpressionWithPattern:imgPattern options:NSRegularExpressionCaseInsensitive error:&error]; - }); - - // Find all the image tags in the content passed. - NSArray *matches = [regex matchesInString:content options:0 range:NSMakeRange(0, [content length])]; - - for (NSTextCheckingResult *match in matches) { - NSString *tag = [content substringWithRange:match.range]; - NSString *src = [self extractSrcFromImgTag:tag]; - - // Ignore WordPress emoji images - if ([src rangeOfString:@"/images/core/emoji/"].location != NSNotFound || - [src rangeOfString:@"/wp-includes/images/smilies/"].location != NSNotFound || - [src rangeOfString:@"/wp-content/mu-plugins/wpcom-smileys/"].location != NSNotFound) { - continue; - } - - // Ignore .svg images since we can't display them in a UIImageView - if ([src rangeOfString:@".svg"].location != NSNotFound) { - continue; - } - - // Check the tag for a good width - NSInteger width = MAX([self widthFromElementAttribute:tag], [self widthFromQueryString:src]); - if (width > FeaturedImageMinimumWidth) { - imageSrc = src; - break; - } - } - if (imageSrc.length == 0) { - imageSrc = [self searchContentBySizeClassForImageToFeature:content]; - } - - return imageSrc; -} - -+ (NSSet *)searchPostContentForAttachmentIdsInGalleries:(NSString *)content -{ - NSMutableSet *resultSet = [NSMutableSet set]; - // If there is no gallery shortcode in the content, just bail. - if (!content || [content rangeOfString:@"[gallery "].location == NSNotFound) { - return resultSet; - } - - // Get all the things - static NSRegularExpression *regexGallery; - static dispatch_once_t onceTokenRegexGallery; - dispatch_once(&onceTokenRegexGallery, ^{ - NSError *error; - NSString *galleryPattern = @"\\[gallery[^]]+ids=\"([0-9,]*)\"[^]]*\\]"; - regexGallery = [NSRegularExpression regularExpressionWithPattern:galleryPattern options:NSRegularExpressionCaseInsensitive error:&error]; - }); - - // Find all the gallery shortcodes in the content passed. - NSArray *matches = [regexGallery matchesInString:content options:0 range:NSMakeRange(0, [content length])]; - - for (NSTextCheckingResult *match in matches) { - if (match.numberOfRanges < 2) { - continue; - } - NSString *tag = [content substringWithRange:[match rangeAtIndex:1]]; - NSSet *tagIds = [self idsFromGallery:tag]; - [resultSet unionSet:tagIds]; - } - return resultSet; -} - -/** - Extract the path to an image from an image tag. - - @param tag An image tag. - @return The value of the src param. - */ -+ (NSString *)extractSrcFromImgTag:(NSString *)tag -{ - static NSRegularExpression *regex; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSError *error; - NSString *srcPattern = @"src\\s*=\\s*(?:'|\")(.*?)(?:'|\")"; - regex = [NSRegularExpression regularExpressionWithPattern:srcPattern options:NSRegularExpressionCaseInsensitive error:&error]; - }); - - NSRange srcRng = [regex rangeOfFirstMatchInString:tag options:0 range:NSMakeRange(0, [tag length])]; - NSString *src = [tag substringWithRange:srcRng]; - NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString:@"\"'="]; - NSRange quoteRng = [src rangeOfCharacterFromSet:charSet]; - src = [src substringFromIndex:quoteRng.location]; - src = [src stringByTrimmingCharactersInSet:charSet]; - return src; -} - -/** - Search the passed string for an image that is a good candidate to feature. - @param content The content string to search. - @return The url path for the image or an empty string. - */ -+ (NSString *)searchContentBySizeClassForImageToFeature:(NSString *)content -{ - NSString *str = @""; - // If there is no image tag in the content, just bail. - if (!content || [content rangeOfString:@" *)jsonMenus { - return [jsonMenus wpkit_map:^id(NSDictionary *dictionary) { + return [jsonMenus wp_map:^id(NSDictionary *dictionary) { return [self menuFromJSONDictionary:dictionary]; }]; } @@ -204,7 +204,7 @@ - (nullable NSArray *)remoteMenusFromJSONArray:(nullable NSArray - (nullable NSArray *)menuItemsFromJSONDictionaries:(nullable NSArray *)dictionaries parent:(nullable RemoteMenuItem *)parent { NSParameterAssert([dictionaries isKindOfClass:[NSArray class]]); - return [dictionaries wpkit_map:^id(NSDictionary *dictionary) { + return [dictionaries wp_map:^id(NSDictionary *dictionary) { RemoteMenuItem *item = [self menuItemFromJSONDictionary:dictionary]; item.parentItem = parent; @@ -215,7 +215,7 @@ - (nullable NSArray *)menuItemsFromJSONDictionaries:(nullable NSArray *)jsonLocations { - return [jsonLocations wpkit_map:^id(NSDictionary *dictionary) { + return [jsonLocations wp_map:^id(NSDictionary *dictionary) { return [self menuLocationFromJSONDictionary:dictionary]; }]; } diff --git a/Modules/Sources/WordPressKitObjC/PostServiceRemoteREST.m b/Modules/Sources/WordPressKitObjC/PostServiceRemoteREST.m index 77cab9349f3d..66e6783315bb 100644 --- a/Modules/Sources/WordPressKitObjC/PostServiceRemoteREST.m +++ b/Modules/Sources/WordPressKitObjC/PostServiceRemoteREST.m @@ -3,10 +3,9 @@ #import "RemotePostCategory.h" #import "RemotePostTerm.h" #import "FilePart.h" -#import "WPMapFilterReduce.h" -#import "DisplayableImageHelper.h" #import "NSString+Helpers.h" +@import WordPressShared; @import WordPressKitModels; @import NSObject_SafeExpectations; @@ -385,7 +384,7 @@ - (NSDictionary *)dictionaryWithRemoteOptions:(id )opt #pragma mark - Private methods - (NSArray *)remotePostsFromJSONArray:(NSArray *)jsonPosts { - return [jsonPosts wpkit_map:^id(NSDictionary *jsonPost) { + return [jsonPosts wp_map:^id(NSDictionary *jsonPost) { return [self remotePostFromJSONDictionary:jsonPost]; }]; } @@ -474,9 +473,9 @@ + (RemotePost *)remotePostFromJSONDictionary:(NSDictionary *)jsonPost { post.pathForDisplayImage = post.postThumbnailPath; } else { // parse contents for a suitable image - post.pathForDisplayImage = [WPKitDisplayableImageHelper searchPostContentForImageToDisplay:post.content]; + post.pathForDisplayImage = [DisplayableImageHelper searchPostContentForImageToDisplay:post.content]; if ([post.pathForDisplayImage length] == 0) { - post.pathForDisplayImage = [WPKitDisplayableImageHelper searchPostAttachmentsForImageToDisplay:[jsonPost dictionaryForKey:@"attachments"] existingInContent:post.content]; + post.pathForDisplayImage = [DisplayableImageHelper searchPostAttachmentsForImageToDisplay:[jsonPost dictionaryForKey:@"attachments"] existingInContent:post.content]; } } @@ -564,7 +563,7 @@ - (NSDictionary *)parametersWithRemotePost:(RemotePost *)post } - (NSArray *)metadataForPost:(RemotePost *)post { - return [post.metadata wpkit_map:^id(NSDictionary *meta) { + return [post.metadata wp_map:^id(NSDictionary *meta) { NSNumber *metaID = [meta objectForKey:@"id"]; NSString *metaValue = [meta objectForKey:@"value"]; NSString *metaKey = [meta objectForKey:@"key"]; @@ -585,7 +584,7 @@ - (NSArray *)metadataForPost:(RemotePost *)post { } + (NSArray *)remoteCategoriesFromJSONArray:(NSArray *)jsonCategories { - return [jsonCategories wpkit_map:^id(NSDictionary *jsonCategory) { + return [jsonCategories wp_map:^id(NSDictionary *jsonCategory) { return [self remoteCategoryFromJSONDictionary:jsonCategory]; }]; } @@ -635,7 +634,7 @@ + (NSArray *)tagNamesFromJSONDictionary:(NSDictionary *)jsonTags { postID:(NSNumber *)postID siteID:(NSNumber *)siteID { - return [jsonUsers wpkit_map:^id(NSDictionary *jsonUser) { + return [jsonUsers wp_map:^id(NSDictionary *jsonUser) { return [[RemoteLikeUser alloc] initWithDictionary:jsonUser postID:postID siteID:siteID]; }]; } diff --git a/Modules/Sources/WordPressKitObjC/PostServiceRemoteXMLRPC.m b/Modules/Sources/WordPressKitObjC/PostServiceRemoteXMLRPC.m index a2850cb963dd..95ee0cba1017 100644 --- a/Modules/Sources/WordPressKitObjC/PostServiceRemoteXMLRPC.m +++ b/Modules/Sources/WordPressKitObjC/PostServiceRemoteXMLRPC.m @@ -4,9 +4,8 @@ #import "RemotePostTerm.h" #import "NSMutableDictionary+Helpers.h" #import "NSString+Helpers.h" -#import "WPMapFilterReduce.h" -#import "DisplayableImageHelper.h" +@import WordPressShared; @import WordPressKitModels; @import NSObject_SafeExpectations; @@ -289,7 +288,7 @@ - (NSDictionary *)dictionaryWithRemoteOptions:(id )opt #pragma mark - Private methods - (NSArray *)remotePostsFromXMLRPCArray:(NSArray *)xmlrpcArray { - return [xmlrpcArray wpkit_map:^id(NSDictionary *xmlrpcPost) { + return [xmlrpcArray wp_map:^id(NSDictionary *xmlrpcPost) { return [self remotePostFromXMLRPCDictionary:xmlrpcPost]; }]; } @@ -343,7 +342,7 @@ + (RemotePost *)remotePostFromXMLRPCDictionary:(NSDictionary *)xmlrpcDictionary post.pathForDisplayImage = post.postThumbnailPath; } else { // parse content for a suitable image. - post.pathForDisplayImage = [WPKitDisplayableImageHelper searchPostContentForImageToDisplay:post.content]; + post.pathForDisplayImage = [DisplayableImageHelper searchPostContentForImageToDisplay:post.content]; } return post; @@ -365,18 +364,18 @@ + (NSArray *)tagsFromXMLRPCTermsArray:(NSArray *)terms { } + (NSArray *)remoteCategoriesFromXMLRPCTermsArray:(NSArray *)terms { - return [[terms wpkit_filter:^BOOL(NSDictionary *category) { + return [[terms wp_filter:^BOOL(NSDictionary *category) { return [[category stringForKey:@"taxonomy"] isEqualToString:@"category"]; - }] wpkit_map:^id(NSDictionary *category) { + }] wp_map:^id(NSDictionary *category) { return [self remoteCategoryFromXMLRPCDictionary:category]; }]; } + (NSArray *)otherTermsFromXMLRPCTermsArray:(NSArray *)terms { - return [[terms wpkit_filter:^BOOL(NSDictionary *category) { + return [[terms wp_filter:^BOOL(NSDictionary *category) { return ![[category stringForKey:@"taxonomy"] isEqualToString:@"category"] && ![[category stringForKey:@"taxonomy"] isEqualToString:@"post_tag"]; - }] wpkit_map:^id(NSDictionary *term) { + }] wp_map:^id(NSDictionary *term) { return [[RemotePostTerm alloc] initWithXMLRPCResponse:term]; }]; } @@ -426,7 +425,7 @@ - (NSDictionary *)parametersWithRemotePost:(RemotePost *)post postParams[@"date_created_gmt"] = [NSDate date]; } if (post.categories) { - NSArray *categoryNames = [post.categories wpkit_map:^id(RemotePostCategory *category) { + NSArray *categoryNames = [post.categories wp_map:^id(RemotePostCategory *category) { return category.name; }]; diff --git a/Modules/Sources/WordPressKitObjC/ReaderPostServiceRemote.m b/Modules/Sources/WordPressKitObjC/ReaderPostServiceRemote.m index f481b06918b6..97b102a2a5fe 100644 --- a/Modules/Sources/WordPressKitObjC/ReaderPostServiceRemote.m +++ b/Modules/Sources/WordPressKitObjC/ReaderPostServiceRemote.m @@ -4,9 +4,9 @@ #import "ReaderTopicServiceRemote.h" #import "WPKitDateUtils.h" #import "NSString+Helpers.h" -#import "WPMapFilterReduce.h" #import "WordPressComRestApiErrorDomain.h" +@import WordPressShared; @import NSObject_SafeExpectations; NSString * const PostRESTKeyPosts = @"posts"; @@ -188,7 +188,7 @@ - (void)fetchPostsFromEndpoint:(NSURL *)endpoint __block CGFloat offset = [[params numberForKey:ParamKeyOffset] floatValue]; NSString *algorithm = [responseObject stringForKey:ParamsKeyAlgorithm]; NSArray *jsonPosts = [responseObject arrayForKey:PostRESTKeyPosts]; - NSArray *posts = [jsonPosts wpkit_map:^id(NSDictionary *jsonPost) { + NSArray *posts = [jsonPosts wp_map:^id(NSDictionary *jsonPost) { if (rankByOffset) { RemoteReaderPost *post = [self formatPostDictionary:jsonPost offset:offset]; offset++; diff --git a/Modules/Sources/WordPressKitObjC/ReaderTopicServiceRemote.m b/Modules/Sources/WordPressKitObjC/ReaderTopicServiceRemote.m index a350db72eb03..e8b0a0289d94 100644 --- a/Modules/Sources/WordPressKitObjC/ReaderTopicServiceRemote.m +++ b/Modules/Sources/WordPressKitObjC/ReaderTopicServiceRemote.m @@ -1,6 +1,6 @@ #import "ReaderTopicServiceRemote.h" -#import "WPMapFilterReduce.h" +@import WordPressShared; @import WordPressKitModels; @import NSObject_SafeExpectations; @@ -301,9 +301,9 @@ - (NSString *)slugForTopicName:(NSString *)topicName - (NSArray *)normalizeMenuTopicsList:(NSArray *)rawTopics subscribed:(BOOL)subscribed recommended:(BOOL)recommended { - return [[rawTopics wpkit_filter:^BOOL(id obj) { + return [[rawTopics wp_filter:^BOOL(id obj) { return [obj isKindOfClass:[NSDictionary class]]; - }] wpkit_map:^id(NSDictionary *topic) { + }] wp_map:^id(NSDictionary *topic) { return [self normalizeMenuTopicDictionary:topic subscribed:subscribed recommended:recommended]; }]; } diff --git a/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m b/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m index d786366e53ab..af2acc0c6856 100644 --- a/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m +++ b/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m @@ -4,9 +4,9 @@ #import "NSString+Helpers.h" #import "NSString+XMLExtensions.h" #import "WPKitDateUtils.h" -#import "WPMapFilterReduce.h" #import "DisplayableImageHelper.h" +@import WordPressShared; @import WordPressKitModels; @import NSObject_SafeExpectations; @@ -534,7 +534,7 @@ - (NSString *)featuredMediaImageFromPostDictionary:(NSDictionary *)dict { - (NSString *)suitableImageFromPostContent:(NSDictionary *)dict { NSString *content = [dict stringForKey:PostRESTKeyContent]; - NSString *imageToDisplay = [WPKitDisplayableImageHelper searchPostContentForImageToDisplay:content]; + NSString *imageToDisplay = [DisplayableImageHelper searchPostContentForImageToDisplay:content]; return [self stringOrEmptyString:imageToDisplay]; } @@ -660,7 +660,7 @@ - (BOOL)siteIsPrivateFromPostDictionary:(NSDictionary *)dict - (NSArray *)slugsFromDiscoverPostTaxonomies:(NSArray *)discoverPostTaxonomies { - return [discoverPostTaxonomies wpkit_map:^id(NSDictionary *dict) { + return [discoverPostTaxonomies wp_map:^id(NSDictionary *dict) { return [dict stringForKey:PostRESTKeySlug]; }]; } diff --git a/Modules/Sources/WordPressKitObjC/TaxonomyServiceRemoteREST.m b/Modules/Sources/WordPressKitObjC/TaxonomyServiceRemoteREST.m index f3cb3b33df4f..cda476907b3a 100644 --- a/Modules/Sources/WordPressKitObjC/TaxonomyServiceRemoteREST.m +++ b/Modules/Sources/WordPressKitObjC/TaxonomyServiceRemoteREST.m @@ -2,8 +2,9 @@ #import "RemotePostTag.h" #import "RemoteTaxonomyPaging.h" #import "RemotePostCategory.h" -#import "WPMapFilterReduce.h" #import "WPKitLogging.h" + +@import WordPressShared; @import NSObject_SafeExpectations; NS_ASSUME_NONNULL_BEGIN @@ -283,7 +284,7 @@ - (void)updateTaxonomyWithType:(NSString *)typeIdentifier - (NSArray *)remoteCategoriesWithJSONArray:(NSArray *)jsonArray { - return [jsonArray wpkit_map:^id(NSDictionary *jsonCategory) { + return [jsonArray wp_map:^id(NSDictionary *jsonCategory) { return [self remoteCategoryWithJSONDictionary:jsonCategory]; }]; } @@ -299,7 +300,7 @@ - (RemotePostCategory *)remoteCategoryWithJSONDictionary:(NSDictionary *)jsonCat - (NSArray *)remoteTagsWithJSONArray:(NSArray *)jsonArray { - return [jsonArray wpkit_map:^id(NSDictionary *jsonTag) { + return [jsonArray wp_map:^id(NSDictionary *jsonTag) { return [self remoteTagWithJSONDictionary:jsonTag]; }]; } diff --git a/Modules/Sources/WordPressKitObjC/TaxonomyServiceRemoteXMLRPC.m b/Modules/Sources/WordPressKitObjC/TaxonomyServiceRemoteXMLRPC.m index ed1c62d0e1fa..ccaddd98d18f 100644 --- a/Modules/Sources/WordPressKitObjC/TaxonomyServiceRemoteXMLRPC.m +++ b/Modules/Sources/WordPressKitObjC/TaxonomyServiceRemoteXMLRPC.m @@ -3,9 +3,9 @@ #import "RemotePostTag.h" #import "RemoteTaxonomyPaging.h" #import "NSString+Helpers.h" -#import "WPMapFilterReduce.h" #import "WPKitLogging.h" +@import WordPressShared; @import NSObject_SafeExpectations; NS_ASSUME_NONNULL_BEGIN @@ -316,7 +316,7 @@ - (void)editTaxonomyWithType:(NSString *)typeIdentifier - (NSArray *)remoteCategoriesFromXMLRPCArray:(NSArray *)xmlrpcArray { - return [xmlrpcArray wpkit_map:^id(NSDictionary *xmlrpcCategory) { + return [xmlrpcArray wp_map:^id(NSDictionary *xmlrpcCategory) { return [self remoteCategoryFromXMLRPCDictionary:xmlrpcCategory]; }]; } @@ -332,7 +332,7 @@ - (RemotePostCategory *)remoteCategoryFromXMLRPCDictionary:(NSDictionary *)xmlrp - (NSArray *)remoteTagsFromXMLRPCArray:(NSArray *)xmlrpcArray { - return [xmlrpcArray wpkit_map:^id(NSDictionary *xmlrpcTag) { + return [xmlrpcArray wp_map:^id(NSDictionary *xmlrpcTag) { return [self remoteTagFromXMLRPCDictionary:xmlrpcTag]; }]; } diff --git a/Modules/Sources/WordPressKitObjC/WPMapFilterReduce.m b/Modules/Sources/WordPressKitObjC/WPMapFilterReduce.m deleted file mode 100644 index 1e8b768391d7..000000000000 --- a/Modules/Sources/WordPressKitObjC/WPMapFilterReduce.m +++ /dev/null @@ -1,28 +0,0 @@ -#import "WPMapFilterReduce.h" - -@implementation NSArray (WPKitMapFilterReduce) - -- (NSArray *)wpkit_map:(WPKitMapBlock)mapBlock -{ - NSMutableArray *results = [NSMutableArray arrayWithCapacity:self.count]; - for (id obj in self) { - id objectToAdd = mapBlock(obj); - if (objectToAdd) { - [results addObject:objectToAdd]; - } - } - return [NSArray arrayWithArray:results]; -} - -- (NSArray *)wpkit_filter:(WPKitFilterBlock)filterBlock -{ - NSMutableArray *results = [NSMutableArray arrayWithCapacity:self.count]; - for (id obj in self) { - if (filterBlock(obj)) { - [results addObject:obj]; - } - } - return [NSArray arrayWithArray:results]; -} - -@end diff --git a/Modules/Sources/WordPressKitObjC/include/DisplayableImageHelper.h b/Modules/Sources/WordPressKitObjC/include/DisplayableImageHelper.h deleted file mode 100644 index 06e2a7e1c487..000000000000 --- a/Modules/Sources/WordPressKitObjC/include/DisplayableImageHelper.h +++ /dev/null @@ -1,38 +0,0 @@ -#import - -/** - Helper for searching a post's content or attachments for an image suitable for - using as the displayed image in the post list. - */ -@interface WPKitDisplayableImageHelper : NSObject - -/** - Get the url path of the image to display for a post. - - @param attachmentsDict A dictionary representing a posts attachments from the REST API. - @param content The post content. The attachment url must exist in the content. - @return The url path for the featured image or nil - */ -+ (NSString *)searchPostAttachmentsForImageToDisplay:(NSDictionary *)attachmentsDict existingInContent:(NSString *)content; - -/** - Search the passed string for an image that is a good candidate to feature. - - @details Loops over all img tags in the passed html content, extracts the URL from the - src attribute and checks for an acceptable width. The image URL with the best - width is returned. - @param content The content string to search. - @return The URL path for the image or an empty string. - */ -+ (NSString *)searchPostContentForImageToDisplay:(NSString *)content; - -/** - Find attachments ids in post content - - @param content The content string to search - - @return A set with all the attachment id that where found in galleries - */ -+ (NSSet *)searchPostContentForAttachmentIdsInGalleries:(NSString *)content; - -@end diff --git a/Modules/Sources/WordPressKitObjC/include/WPMapFilterReduce.h b/Modules/Sources/WordPressKitObjC/include/WPMapFilterReduce.h deleted file mode 100644 index c7968cf91365..000000000000 --- a/Modules/Sources/WordPressKitObjC/include/WPMapFilterReduce.h +++ /dev/null @@ -1,22 +0,0 @@ -#import - -typedef id (^WPKitMapBlock)(id obj); -typedef BOOL (^WPKitFilterBlock)(id obj); - -@interface NSArray (WPKitMapFilterReduce) - -/** - Transforms values in an array - - The resulting array will include the results of calling mapBlock for each of - the receiver array objects. If mapBlock returns nil that value will be missing - from the resulting array. - */ -- (NSArray *)wpkit_map:(WPKitMapBlock)mapBlock; - -/** - Filters an array to only include values that satisfy the filter block - */ -- (NSArray *)wpkit_filter:(WPKitFilterBlock)filterBlock; - -@end From a5fbaf385d5a4175125a38805d454a587320c810 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Wed, 18 Mar 2026 09:30:38 -0400 Subject: [PATCH 02/10] Remove WPKit extension usages --- Modules/Sources/WordPressKit/AccountSettingsRemote.swift | 5 +++-- Modules/Sources/WordPressKit/PluginDirectoryEntry.swift | 5 +++-- Modules/Sources/WordPressKit/StatsLastPostInsight.swift | 3 ++- Modules/Sources/WordPressKitObjC/RemoteReaderPost.m | 6 +++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Modules/Sources/WordPressKit/AccountSettingsRemote.swift b/Modules/Sources/WordPressKit/AccountSettingsRemote.swift index 37dafc1508d0..475254d9360d 100644 --- a/Modules/Sources/WordPressKit/AccountSettingsRemote.swift +++ b/Modules/Sources/WordPressKit/AccountSettingsRemote.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared import WordPressKitObjC public class AccountSettingsRemote: ServiceRemoteWordPressComREST { @@ -178,12 +179,12 @@ public class AccountSettingsRemote: ServiceRemoteWordPressComREST { throw ResponseError.decodingFailure } - let aboutMeText = aboutMe.wpkit_stringByDecodingXMLCharacters() + let aboutMeText = aboutMe.stringByDecodingXMLCharacters() return AccountSettings(firstName: firstName, lastName: lastName, displayName: displayName, - aboutMe: aboutMeText!, + aboutMe: aboutMeText, username: username, usernameCanBeChanged: usernameCanBeChanged, email: email, diff --git a/Modules/Sources/WordPressKit/PluginDirectoryEntry.swift b/Modules/Sources/WordPressKit/PluginDirectoryEntry.swift index 0fbbb7941242..26e62724f377 100644 --- a/Modules/Sources/WordPressKit/PluginDirectoryEntry.swift +++ b/Modules/Sources/WordPressKit/PluginDirectoryEntry.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared public struct PluginDirectoryEntry { public let name: String @@ -77,7 +78,7 @@ extension PluginDirectoryEntry: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let decodedName = try container.decode(String.self, forKey: .name) - name = decodedName.wpkit_stringByDecodingXMLCharacters() + name = decodedName.stringByDecodingXMLCharacters() slug = try container.decode(String.self, forKey: .slug) version = try? container.decode(String.self, forKey: .version) lastUpdated = try? container.decode(Date.self, forKey: .lastUpdated) @@ -115,7 +116,7 @@ extension PluginDirectoryEntry: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(name.wpkit_stringByEncodingXMLCharacters(), forKey: .name) + try container.encode(name.stringByEncodingXMLCharacters(), forKey: .name) try container.encode(slug, forKey: .slug) try container.encodeIfPresent(version, forKey: .version) try container.encodeIfPresent(lastUpdated, forKey: .lastUpdated) diff --git a/Modules/Sources/WordPressKit/StatsLastPostInsight.swift b/Modules/Sources/WordPressKit/StatsLastPostInsight.swift index 9319c951df01..efb17afcbe18 100644 --- a/Modules/Sources/WordPressKit/StatsLastPostInsight.swift +++ b/Modules/Sources/WordPressKit/StatsLastPostInsight.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared public struct StatsLastPostInsight: Equatable, Decodable { public let title: String @@ -81,7 +82,7 @@ extension StatsLastPostInsight { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - title = try container.decode(String.self, forKey: .title).trimmingCharacters(in: .whitespaces).wpkit_stringByDecodingXMLCharacters() + title = try container.decode(String.self, forKey: .title).trimmingCharacters(in: .whitespaces).stringByDecodingXMLCharacters() url = try container.decode(URL.self, forKey: .url) let dateString = try container.decode(String.self, forKey: .publishedDate) guard let date = StatsLastPostInsight.dateFormatter.date(from: dateString) else { diff --git a/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m b/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m index af2acc0c6856..16af2f008988 100644 --- a/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m +++ b/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m @@ -101,7 +101,7 @@ - (instancetype)initWithDictionary:(NSDictionary *)dict; self.authorID = [authorDict numberForKey:PostRESTKeyID]; self.author = [self stringOrEmptyString:[authorDict stringForKey:PostRESTKeyNiceName]]; // typically the author's screen name self.authorAvatarURL = [self stringOrEmptyString:[authorDict stringForKey:PostRESTKeyAvatarURL]]; - self.authorDisplayName = [[self stringOrEmptyString:[authorDict stringForKey:PostRESTKeyName]] wpkit_stringByDecodingXMLCharacters]; // Typically the author's given name + self.authorDisplayName = [[self stringOrEmptyString:[authorDict stringForKey:PostRESTKeyName]] stringByDecodingXMLCharacters]; // Typically the author's given name self.authorEmail = [self authorEmailFromAuthorDictionary:authorDict]; self.authorURL = [self stringOrEmptyString:[authorDict stringForKey:PostRESTKeyURL]]; self.siteIconURL = [self stringOrEmptyString:[dict stringForKeyPath:@"site_icon.img"]]; @@ -283,8 +283,8 @@ - (NSDictionary *)primaryAndSecondaryTagsFromPostDictionary:(NSDictionary *)dict primaryTagSlug = editorialSlug; } - primaryTag = [primaryTag wpkit_stringByDecodingXMLCharacters]; - secondaryTag = [secondaryTag wpkit_stringByDecodingXMLCharacters]; + primaryTag = [primaryTag stringByDecodingXMLCharacters]; + secondaryTag = [secondaryTag stringByDecodingXMLCharacters]; return @{ TagKeyPrimary:primaryTag, From 8b6c402d7126b70918a0e656d0f9207762bbcbd2 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Wed, 18 Mar 2026 09:31:39 -0400 Subject: [PATCH 03/10] Remove remaining wpkit_decode usages --- Modules/Package.swift | 1 + Modules/Sources/WordPressKitModels/NSString+Summary.swift | 3 ++- .../Sources/WordPressKitModels/RemoteBlogOptionsHelper.swift | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Modules/Package.swift b/Modules/Package.swift index e9a13001b458..7c4c8b54c067 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -207,6 +207,7 @@ let package = Package( dependencies: [ "NSObject-SafeExpectations", "WordPressKitObjCUtils", + "WordPressShared", ] ), .target( diff --git a/Modules/Sources/WordPressKitModels/NSString+Summary.swift b/Modules/Sources/WordPressKitModels/NSString+Summary.swift index dea8017ee3cb..a7b5c90b513a 100644 --- a/Modules/Sources/WordPressKitModels/NSString+Summary.swift +++ b/Modules/Sources/WordPressKitModels/NSString+Summary.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared import WordPressKitObjCUtils /// This is an extension to NSString that provides logic to summarize HTML content, @@ -29,7 +30,7 @@ private extension String { let characterSet = NSCharacterSet.whitespacesAndNewlines return self.wpkit_stringByStrippingHTML() - .wpkit_stringByDecodingXMLCharacters() + .stringByDecodingXMLCharacters() .trimmingCharacters(in: characterSet) } diff --git a/Modules/Sources/WordPressKitModels/RemoteBlogOptionsHelper.swift b/Modules/Sources/WordPressKitModels/RemoteBlogOptionsHelper.swift index 9861639e1fc9..2726d529dff3 100644 --- a/Modules/Sources/WordPressKitModels/RemoteBlogOptionsHelper.swift +++ b/Modules/Sources/WordPressKitModels/RemoteBlogOptionsHelper.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared @objcMembers public class RemoteBlogOptionsHelper: NSObject { @@ -73,8 +74,8 @@ import Foundation public class func remoteBlogSettings(fromXMLRPCDictionaryOptions options: NSDictionary) -> RemoteBlogSettings { let remoteSettings = RemoteBlogSettings() - remoteSettings.name = options.string(forKeyPath: "blog_title.value")?.wpkit_stringByDecodingXMLCharacters() - remoteSettings.tagline = options.string(forKeyPath: "blog_tagline.value")?.wpkit_stringByDecodingXMLCharacters() + remoteSettings.name = options.string(forKeyPath: "blog_title.value")?.stringByDecodingXMLCharacters() + remoteSettings.tagline = options.string(forKeyPath: "blog_tagline.value")?.stringByDecodingXMLCharacters() if options["blog_public"] != nil { remoteSettings.privacy = options.number(forKeyPath: "blog_public.value") } From ed175e8f938c04270f765b494f95ab0c3c61624a Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Wed, 18 Mar 2026 09:32:03 -0400 Subject: [PATCH 04/10] Remove NSString+XMLExtensions from WordPressKitObjcUtils --- .../Sources/WordPressKit/String+Helpers.swift | 8 - .../WordPressKitObjCUtils/NSString+Helpers.m | 1 - .../NSString+XMLExtensions.m | 400 ------------------ .../include/NSString+XMLExtensions.h | 10 - 4 files changed, 419 deletions(-) delete mode 100644 Modules/Sources/WordPressKitObjCUtils/NSString+XMLExtensions.m delete mode 100644 Modules/Sources/WordPressKitObjCUtils/include/NSString+XMLExtensions.h diff --git a/Modules/Sources/WordPressKit/String+Helpers.swift b/Modules/Sources/WordPressKit/String+Helpers.swift index cc73aa2f4a64..3793598d02ee 100644 --- a/Modules/Sources/WordPressKit/String+Helpers.swift +++ b/Modules/Sources/WordPressKit/String+Helpers.swift @@ -1,14 +1,6 @@ import Foundation extension String { - func stringByDecodingXMLCharacters() -> String { - return NSString.wpkit_decodeXMLCharacters(in: self) - } - - func stringByEncodingXMLCharacters() -> String { - return NSString.wpkit_encodeXMLCharacters(in: self) - } - func trim() -> String { return trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) } diff --git a/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m b/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m index 43930bd7e92e..c9a2fd5d1ee3 100644 --- a/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m +++ b/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m @@ -1,6 +1,5 @@ #import "NSString+Helpers.h" #import -#import "NSString+XMLExtensions.h" static NSString *const Ellipsis = @"\u2026"; diff --git a/Modules/Sources/WordPressKitObjCUtils/NSString+XMLExtensions.m b/Modules/Sources/WordPressKitObjCUtils/NSString+XMLExtensions.m deleted file mode 100644 index 19ee5fb74f21..000000000000 --- a/Modules/Sources/WordPressKitObjCUtils/NSString+XMLExtensions.m +++ /dev/null @@ -1,400 +0,0 @@ -// Adapted from MWFeedParser -// https://github.com/mwaterfall/MWFeedParser Copyright (c) 2010 Michael Waterfall - -#import "NSString+XMLExtensions.h" - - -typedef struct { - __unsafe_unretained NSString *escapeSequence; - unichar uchar; -} HTMLEscapeMap; - - -// Taken from http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_Special_characters -// Ordered by uchar lowest to highest for bsearching -static HTMLEscapeMap gAsciiHTMLEscapeMap[] = { - // A.2.2. Special characters - { @""", 34 }, - { @"&", 38 }, - { @"'", 39 }, - { @"<", 60 }, - { @">", 62 }, - - // A.2.1. Latin-1 characters - { @" ", 160 }, - { @"¡", 161 }, - { @"¢", 162 }, - { @"£", 163 }, - { @"¤", 164 }, - { @"¥", 165 }, - { @"¦", 166 }, - { @"§", 167 }, - { @"¨", 168 }, - { @"©", 169 }, - { @"ª", 170 }, - { @"«", 171 }, - { @"¬", 172 }, - { @"­", 173 }, - { @"®", 174 }, - { @"¯", 175 }, - { @"°", 176 }, - { @"±", 177 }, - { @"²", 178 }, - { @"³", 179 }, - { @"´", 180 }, - { @"µ", 181 }, - { @"¶", 182 }, - { @"·", 183 }, - { @"¸", 184 }, - { @"¹", 185 }, - { @"º", 186 }, - { @"»", 187 }, - { @"¼", 188 }, - { @"½", 189 }, - { @"¾", 190 }, - { @"¿", 191 }, - { @"À", 192 }, - { @"Á", 193 }, - { @"Â", 194 }, - { @"Ã", 195 }, - { @"Ä", 196 }, - { @"Å", 197 }, - { @"Æ", 198 }, - { @"Ç", 199 }, - { @"È", 200 }, - { @"É", 201 }, - { @"Ê", 202 }, - { @"Ë", 203 }, - { @"Ì", 204 }, - { @"Í", 205 }, - { @"Î", 206 }, - { @"Ï", 207 }, - { @"Ð", 208 }, - { @"Ñ", 209 }, - { @"Ò", 210 }, - { @"Ó", 211 }, - { @"Ô", 212 }, - { @"Õ", 213 }, - { @"Ö", 214 }, - { @"×", 215 }, - { @"Ø", 216 }, - { @"Ù", 217 }, - { @"Ú", 218 }, - { @"Û", 219 }, - { @"Ü", 220 }, - { @"Ý", 221 }, - { @"Þ", 222 }, - { @"ß", 223 }, - { @"à", 224 }, - { @"á", 225 }, - { @"â", 226 }, - { @"ã", 227 }, - { @"ä", 228 }, - { @"å", 229 }, - { @"æ", 230 }, - { @"ç", 231 }, - { @"è", 232 }, - { @"é", 233 }, - { @"ê", 234 }, - { @"ë", 235 }, - { @"ì", 236 }, - { @"í", 237 }, - { @"î", 238 }, - { @"ï", 239 }, - { @"ð", 240 }, - { @"ñ", 241 }, - { @"ò", 242 }, - { @"ó", 243 }, - { @"ô", 244 }, - { @"õ", 245 }, - { @"ö", 246 }, - { @"÷", 247 }, - { @"ø", 248 }, - { @"ù", 249 }, - { @"ú", 250 }, - { @"û", 251 }, - { @"ü", 252 }, - { @"ý", 253 }, - { @"þ", 254 }, - { @"ÿ", 255 }, - - // A.2.2. Special characters cont'd - { @"Œ", 338 }, - { @"œ", 339 }, - { @"Š", 352 }, - { @"š", 353 }, - { @"Ÿ", 376 }, - - // A.2.3. Symbols - { @"ƒ", 402 }, - - // A.2.2. Special characters cont'd - { @"ˆ", 710 }, - { @"˜", 732 }, - - // A.2.3. Symbols cont'd - { @"Α", 913 }, - { @"Β", 914 }, - { @"Γ", 915 }, - { @"Δ", 916 }, - { @"Ε", 917 }, - { @"Ζ", 918 }, - { @"Η", 919 }, - { @"Θ", 920 }, - { @"Ι", 921 }, - { @"Κ", 922 }, - { @"Λ", 923 }, - { @"Μ", 924 }, - { @"Ν", 925 }, - { @"Ξ", 926 }, - { @"Ο", 927 }, - { @"Π", 928 }, - { @"Ρ", 929 }, - { @"Σ", 931 }, - { @"Τ", 932 }, - { @"Υ", 933 }, - { @"Φ", 934 }, - { @"Χ", 935 }, - { @"Ψ", 936 }, - { @"Ω", 937 }, - { @"α", 945 }, - { @"β", 946 }, - { @"γ", 947 }, - { @"δ", 948 }, - { @"ε", 949 }, - { @"ζ", 950 }, - { @"η", 951 }, - { @"θ", 952 }, - { @"ι", 953 }, - { @"κ", 954 }, - { @"λ", 955 }, - { @"μ", 956 }, - { @"ν", 957 }, - { @"ξ", 958 }, - { @"ο", 959 }, - { @"π", 960 }, - { @"ρ", 961 }, - { @"ς", 962 }, - { @"σ", 963 }, - { @"τ", 964 }, - { @"υ", 965 }, - { @"φ", 966 }, - { @"χ", 967 }, - { @"ψ", 968 }, - { @"ω", 969 }, - { @"ϑ", 977 }, - { @"ϒ", 978 }, - { @"ϖ", 982 }, - - // A.2.2. Special characters cont'd - { @" ", 8194 }, - { @" ", 8195 }, - { @" ", 8201 }, - { @"‌", 8204 }, - { @"‍", 8205 }, - { @"‎", 8206 }, - { @"‏", 8207 }, - { @"–", 8211 }, - { @"—", 8212 }, - { @"‘", 8216 }, - { @"’", 8217 }, - { @"‚", 8218 }, - { @"“", 8220 }, - { @"”", 8221 }, - { @"„", 8222 }, - { @"†", 8224 }, - { @"‡", 8225 }, - // A.2.3. Symbols cont'd - { @"•", 8226 }, - { @"…", 8230 }, - - // A.2.2. Special characters cont'd - { @"‰", 8240 }, - - // A.2.3. Symbols cont'd - { @"′", 8242 }, - { @"″", 8243 }, - - // A.2.2. Special characters cont'd - { @"‹", 8249 }, - { @"›", 8250 }, - - // A.2.3. Symbols cont'd - { @"‾", 8254 }, - { @"⁄", 8260 }, - - // A.2.2. Special characters cont'd - { @"€", 8364 }, - - // A.2.3. Symbols cont'd - { @"ℑ", 8465 }, - { @"℘", 8472 }, - { @"ℜ", 8476 }, - { @"™", 8482 }, - { @"ℵ", 8501 }, - { @"←", 8592 }, - { @"↑", 8593 }, - { @"→", 8594 }, - { @"↓", 8595 }, - { @"↔", 8596 }, - { @"↵", 8629 }, - { @"⇐", 8656 }, - { @"⇑", 8657 }, - { @"⇒", 8658 }, - { @"⇓", 8659 }, - { @"⇔", 8660 }, - { @"∀", 8704 }, - { @"∂", 8706 }, - { @"∃", 8707 }, - { @"∅", 8709 }, - { @"∇", 8711 }, - { @"∈", 8712 }, - { @"∉", 8713 }, - { @"∋", 8715 }, - { @"∏", 8719 }, - { @"∑", 8721 }, - { @"−", 8722 }, - { @"∗", 8727 }, - { @"√", 8730 }, - { @"∝", 8733 }, - { @"∞", 8734 }, - { @"∠", 8736 }, - { @"∧", 8743 }, - { @"∨", 8744 }, - { @"∩", 8745 }, - { @"∪", 8746 }, - { @"∫", 8747 }, - { @"∴", 8756 }, - { @"∼", 8764 }, - { @"≅", 8773 }, - { @"≈", 8776 }, - { @"≠", 8800 }, - { @"≡", 8801 }, - { @"≤", 8804 }, - { @"≥", 8805 }, - { @"⊂", 8834 }, - { @"⊃", 8835 }, - { @"⊄", 8836 }, - { @"⊆", 8838 }, - { @"⊇", 8839 }, - { @"⊕", 8853 }, - { @"⊗", 8855 }, - { @"⊥", 8869 }, - { @"⋅", 8901 }, - { @"⌈", 8968 }, - { @"⌉", 8969 }, - { @"⌊", 8970 }, - { @"⌋", 8971 }, - { @"⟨", 9001 }, - { @"⟩", 9002 }, - { @"◊", 9674 }, - { @"♠", 9824 }, - { @"♣", 9827 }, - { @"♥", 9829 }, - { @"♦", 9830 } -}; - - -@implementation NSString (WPKitXMLExtensions) - -+ (NSString *)wpkit_encodeXMLCharactersIn : (NSString *)source { - if (![source isKindOfClass:[NSString class]] || !source) - return @""; - - NSString *result = [NSString stringWithString:source]; - - // NOTE: we use unicode entities instead of & > < since some weird hosts (powweb, fatcow, and cousins) - // have a weird PHP/libxml2 combination that ignores regular entities - if ([result rangeOfString:@"&"].location != NSNotFound) - result = [[result componentsSeparatedByString:@"&"] componentsJoinedByString:@"&"]; - - if ([result rangeOfString:@"<"].location != NSNotFound) - result = [[result componentsSeparatedByString:@"<"] componentsJoinedByString:@"<"]; - - if ([result rangeOfString:@">"].location != NSNotFound) - result = [[result componentsSeparatedByString:@">"] componentsJoinedByString:@">"]; - - return result; -} - - -+ (NSString *) wpkit_decodeXMLCharactersIn:(NSString *)original { - if (![original isKindOfClass:[NSString class]] || !original) - return @""; - - NSString *source = [NSString stringWithString:original]; - - NSRange range = NSMakeRange(0, [source length]); - NSRange subrange = [source rangeOfString:@"&" options:NSBackwardsSearch range:range]; - - // if no ampersands, we've got a quick way out - if (subrange.length == 0) return source; - NSMutableString *finalString = [NSMutableString stringWithString:source]; - do { - NSRange semiColonRange = NSMakeRange(subrange.location, NSMaxRange(range) - subrange.location); - semiColonRange = [source rangeOfString:@";" options:0 range:semiColonRange]; - range = NSMakeRange(0, subrange.location); - // if we don't find a semicolon in the range, we don't have a sequence - if (semiColonRange.location == NSNotFound) { - continue; - } - NSRange escapeRange = NSMakeRange(subrange.location, semiColonRange.location - subrange.location + 1); - NSString *escapeString = [source substringWithRange:escapeRange]; - NSUInteger length = [escapeString length]; - // a squence must be longer than 3 (<) and less than 11 (ϑ) - if (length > 3 && length < 11) { - if ([escapeString characterAtIndex:1] == '#') { - unichar char2 = [escapeString characterAtIndex:2]; - if (char2 == 'x' || char2 == 'X') { - // Hex escape squences £ - NSString *hexSequence = [escapeString substringWithRange:NSMakeRange(3, length - 4)]; - NSScanner *scanner = [NSScanner scannerWithString:hexSequence]; - unsigned value; - if ([scanner scanHexInt:&value] && - value < USHRT_MAX && - value > 0 - && [scanner scanLocation] == length - 4) { - unichar uchar = value; - NSString *charString = [NSString stringWithCharacters:&uchar length:1]; - [finalString replaceCharactersInRange:escapeRange withString:charString]; - } - - } else { - // Decimal Sequences { - NSString *numberSequence = [escapeString substringWithRange:NSMakeRange(2, length - 3)]; - NSScanner *scanner = [NSScanner scannerWithString:numberSequence]; - int value; - if ([scanner scanInt:&value] && - value < USHRT_MAX && - value > 0 - && [scanner scanLocation] == length - 3) { - unichar uchar = value; - NSString *charString = [NSString stringWithCharacters:&uchar length:1]; - [finalString replaceCharactersInRange:escapeRange withString:charString]; - } - } - } else { - // "standard" sequences - for (unsigned i = 0; i < sizeof(gAsciiHTMLEscapeMap) / sizeof(HTMLEscapeMap); ++i) { - if ([escapeString isEqualToString:gAsciiHTMLEscapeMap[i].escapeSequence]) { - [finalString replaceCharactersInRange:escapeRange withString:[NSString stringWithCharacters:&gAsciiHTMLEscapeMap[i].uchar length:1]]; - break; - } - } - } - } - - } while ((subrange = [source rangeOfString:@"&" options:NSBackwardsSearch range:range]).length != 0); - - return finalString; -} - -- (NSString *)wpkit_stringByDecodingXMLCharacters { - return [NSString wpkit_decodeXMLCharactersIn:self]; -} -- (NSString *)wpkit_stringByEncodingXMLCharacters { - return [NSString wpkit_encodeXMLCharactersIn:self]; -} - - -@end diff --git a/Modules/Sources/WordPressKitObjCUtils/include/NSString+XMLExtensions.h b/Modules/Sources/WordPressKitObjCUtils/include/NSString+XMLExtensions.h deleted file mode 100644 index f2de4533e67e..000000000000 --- a/Modules/Sources/WordPressKitObjCUtils/include/NSString+XMLExtensions.h +++ /dev/null @@ -1,10 +0,0 @@ -#import - -@interface NSString (WPKitXMLExtensions) - -+ (NSString *)wpkit_encodeXMLCharactersIn : (NSString *)source; -+ (NSString *)wpkit_decodeXMLCharactersIn : (NSString *)source; -- (NSString *)wpkit_stringByDecodingXMLCharacters; -- (NSString *)wpkit_stringByEncodingXMLCharacters; - -@end From 4331fb1212f06c61d9f7f68a7d7341524a3d721f Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Wed, 18 Mar 2026 09:33:55 -0400 Subject: [PATCH 05/10] Remove wpkit_stringByStrippingHTML --- Modules/Sources/WordPressKitModels/NSString+Summary.swift | 2 +- Modules/Sources/WordPressKitObjC/WordPressComServiceRemote.m | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Modules/Sources/WordPressKitModels/NSString+Summary.swift b/Modules/Sources/WordPressKitModels/NSString+Summary.swift index a7b5c90b513a..918c8483536c 100644 --- a/Modules/Sources/WordPressKitModels/NSString+Summary.swift +++ b/Modules/Sources/WordPressKitModels/NSString+Summary.swift @@ -29,7 +29,7 @@ private extension String { func makePlainText() -> String { let characterSet = NSCharacterSet.whitespacesAndNewlines - return self.wpkit_stringByStrippingHTML() + return self.stringByStrippingHTML() .stringByDecodingXMLCharacters() .trimmingCharacters(in: characterSet) } diff --git a/Modules/Sources/WordPressKitObjC/WordPressComServiceRemote.m b/Modules/Sources/WordPressKitObjC/WordPressComServiceRemote.m index a7a63fdd5247..7b88ad8b64ca 100644 --- a/Modules/Sources/WordPressKitObjC/WordPressComServiceRemote.m +++ b/Modules/Sources/WordPressKitObjC/WordPressComServiceRemote.m @@ -2,6 +2,7 @@ #import "NSString+Helpers.h" #import "WordPressComRestApiErrorDomain.h" +@import WordPressShared; @import NSObject_SafeExpectations; @implementation WordPressComServiceRemote @@ -238,7 +239,7 @@ - (NSError *)errorWithLocalizedMessage:(NSError *)error { - (NSString *)errorMessageForError:(NSError *)error { NSString *errorCode = [error.userInfo stringForKey:self.wordPressComRESTAPI.errorCodeKey]; - NSString *errorMessage = [[error.userInfo stringForKey:NSLocalizedDescriptionKey] wpkit_stringByStrippingHTML]; + NSString *errorMessage = [[error.userInfo stringForKey:NSLocalizedDescriptionKey] stringByStrippingHTML]; if ([errorCode isEqualToString:@"username_only_lowercase_letters_and_numbers"]) { return NSLocalizedString(@"Sorry, usernames can only contain lowercase letters (a-z) and numbers.", nil); From bf73434e7ab3cf8ff2f5ec878bec673568de54a1 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Wed, 18 Mar 2026 09:36:31 -0400 Subject: [PATCH 06/10] Remove wpkit_stringByEllipsizingWithMaxLength --- .../WordPressKitModels/NSString+Summary.swift | 4 +- .../WordPressKitObjC/RemoteReaderPost.m | 2 +- .../WordPressKitObjCUtils/NSString+Helpers.m | 75 ------------------- .../include/NSString+Helpers.h | 1 - .../Utility/NSString+Helpers.m | 46 ++++++++++++ .../include/NSString+Helpers.h | 1 + 6 files changed, 50 insertions(+), 79 deletions(-) diff --git a/Modules/Sources/WordPressKitModels/NSString+Summary.swift b/Modules/Sources/WordPressKitModels/NSString+Summary.swift index 918c8483536c..a9dae04a4933 100644 --- a/Modules/Sources/WordPressKitModels/NSString+Summary.swift +++ b/Modules/Sources/WordPressKitModels/NSString+Summary.swift @@ -21,7 +21,7 @@ extension NSString { .strippingShortcodes() .makePlainText() .trimmingCharacters(in: characterSet) - .wpkit_stringByEllipsizing(withMaxLength: NSString.PostDerivedSummaryLength, preserveWords: true) + .wp_stringByEllipsizing(withMaxLength: NSString.PostDerivedSummaryLength, preserveWords: true) } } @@ -29,7 +29,7 @@ private extension String { func makePlainText() -> String { let characterSet = NSCharacterSet.whitespacesAndNewlines - return self.stringByStrippingHTML() + return self.strippingHTML() .stringByDecodingXMLCharacters() .trimmingCharacters(in: characterSet) } diff --git a/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m b/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m index 16af2f008988..0e04d1d51895 100644 --- a/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m +++ b/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m @@ -720,7 +720,7 @@ - (NSString *)makePlainText:(NSString *)string */ - (NSString *)titleFromSummary:(NSString *)summary { - return [summary wpkit_stringByEllipsizingWithMaxLength:ReaderPostTitleLength preserveWords:YES]; + return [summary wp_stringByEllipsizingWithMaxLength:ReaderPostTitleLength preserveWords:YES]; } diff --git a/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m b/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m index c9a2fd5d1ee3..673ad53fe470 100644 --- a/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m +++ b/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m @@ -97,81 +97,6 @@ - (NSString *)wpkit_stringByStrippingHTML return [self stringByReplacingOccurrencesOfString:@"<[^>]+>" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, self.length)]; } -// A method to truncate a string at a predetermined length and append ellipsis to the end - -- (NSString *)wpkit_stringByEllipsizingWithMaxLength:(NSInteger)lengthlimit preserveWords:(BOOL)preserveWords -{ - NSInteger currentLength = [self length]; - NSString *result = @""; - NSString *temp = @""; - - if (currentLength <= lengthlimit) { //If the string is already within limits - return self; - } else if (lengthlimit > 0) { //If the string is longer than the limit, and the limit is larger than 0. - - NSInteger newLimitWithoutEllipsis = lengthlimit - [Ellipsis length]; - - if (preserveWords) { - - NSArray *wordsSeperated = [self tokenize]; - - if ([wordsSeperated count] == 1) { // If this is a long word then we disregard preserveWords property. - return [NSString stringWithFormat:@"%@%@", [self substringToIndex:newLimitWithoutEllipsis], Ellipsis]; - } - - for (NSString *word in wordsSeperated) { - - if ([temp isEqualToString:@""]) { - temp = word; - } else { - temp = [NSString stringWithFormat:@"%@%@", temp, word]; - } - - if ([temp length] <= newLimitWithoutEllipsis) { - result = [temp copy]; - } else { - return [NSString stringWithFormat:@"%@%@",result,Ellipsis]; - } - } - } else { - return [NSString stringWithFormat:@"%@%@", [self substringToIndex:newLimitWithoutEllipsis], Ellipsis]; - } - - } else { //if the limit is 0. - return @""; - } - - return self; -} - -- (NSArray *)tokenize -{ - CFLocaleRef locale = CFLocaleCopyCurrent(); - CFRange stringRange = CFRangeMake(0, [self length]); - - CFStringTokenizerRef tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault, - (CFStringRef)self, - stringRange, - kCFStringTokenizerUnitWordBoundary, - locale); - - CFStringTokenizerTokenType tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer); - - NSMutableArray *tokens = [NSMutableArray new]; - - while (tokenType != kCFStringTokenizerTokenNone) { - stringRange = CFStringTokenizerGetCurrentTokenRange(tokenizer); - NSString *token = [self substringWithRange:NSMakeRange(stringRange.location, stringRange.length)]; - [tokens addObject:token]; - tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer); - } - - CFRelease(locale); - CFRelease(tokenizer); - - return tokens; -} - - (bool)wpkit_isEmpty { return self.length == 0; } diff --git a/Modules/Sources/WordPressKitObjCUtils/include/NSString+Helpers.h b/Modules/Sources/WordPressKitObjCUtils/include/NSString+Helpers.h index a6f28ced6b56..c0c2d200ce5c 100644 --- a/Modules/Sources/WordPressKitObjCUtils/include/NSString+Helpers.h +++ b/Modules/Sources/WordPressKitObjCUtils/include/NSString+Helpers.h @@ -4,7 +4,6 @@ - (NSString *)wpkit_stringByUrlEncoding; - (NSString *)wpkit_stringByStrippingHTML; -- (NSString *)wpkit_stringByEllipsizingWithMaxLength:(NSInteger)lengthlimit preserveWords:(BOOL)preserveWords; - (bool)wpkit_isEmpty; @end diff --git a/Modules/Sources/WordPressSharedObjC/Utility/NSString+Helpers.m b/Modules/Sources/WordPressSharedObjC/Utility/NSString+Helpers.m index 454135640a35..3abd17a54684 100644 --- a/Modules/Sources/WordPressSharedObjC/Utility/NSString+Helpers.m +++ b/Modules/Sources/WordPressSharedObjC/Utility/NSString+Helpers.m @@ -267,5 +267,51 @@ - (NSString *)stringByNormalizingWhitespace return [self stringByReplacingOccurrencesOfString:@"\\s{2,}" withString:@" " options:NSRegularExpressionSearch range:NSMakeRange(0, self.length)]; } +// A method to truncate a string at a predetermined length and append ellipsis to the end + +- (NSString *)wp_stringByEllipsizingWithMaxLength:(NSInteger)lengthlimit preserveWords:(BOOL)preserveWords +{ + NSInteger currentLength = [self length]; + NSString *result = @""; + NSString *temp = @""; + + if (currentLength <= lengthlimit) { //If the string is already within limits + return self; + } else if (lengthlimit > 0) { //If the string is longer than the limit, and the limit is larger than 0. + + NSInteger newLimitWithoutEllipsis = lengthlimit - [Ellipsis length]; + + if (preserveWords) { + + NSArray *wordsSeperated = [self tokenize]; + + if ([wordsSeperated count] == 1) { // If this is a long word then we disregard preserveWords property. + return [NSString stringWithFormat:@"%@%@", [self substringToIndex:newLimitWithoutEllipsis], Ellipsis]; + } + + for (NSString *word in wordsSeperated) { + + if ([temp isEqualToString:@""]) { + temp = word; + } else { + temp = [NSString stringWithFormat:@"%@%@", temp, word]; + } + + if ([temp length] <= newLimitWithoutEllipsis) { + result = [temp copy]; + } else { + return [NSString stringWithFormat:@"%@%@",result,Ellipsis]; + } + } + } else { + return [NSString stringWithFormat:@"%@%@", [self substringToIndex:newLimitWithoutEllipsis], Ellipsis]; + } + + } else { //if the limit is 0. + return @""; + } + + return self; +} @end diff --git a/Modules/Sources/WordPressSharedObjC/include/NSString+Helpers.h b/Modules/Sources/WordPressSharedObjC/include/NSString+Helpers.h index 98545b2fac5a..4f5939379701 100644 --- a/Modules/Sources/WordPressSharedObjC/include/NSString+Helpers.h +++ b/Modules/Sources/WordPressSharedObjC/include/NSString+Helpers.h @@ -5,6 +5,7 @@ - (NSMutableDictionary *)dictionaryFromQueryString; - (NSString *)stringByReplacingHTMLEmoticonsWithEmoji; - (NSString *)stringByStrippingHTML; +- (NSString *)wp_stringByEllipsizingWithMaxLength:(NSInteger)lengthlimit preserveWords:(BOOL)preserveWords; - (BOOL)isWordPressComPath; /** From f857bd5de554fa34665921d876a6809d431a27cf Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Wed, 18 Mar 2026 09:39:26 -0400 Subject: [PATCH 07/10] Remove wpkit_stringByStrippingHTML --- .../WordPressKit/PeopleServiceRemote.swift | 3 ++- .../WordPressKitObjC/ReaderPostServiceRemote.m | 2 +- .../WordPressKitObjCUtils/NSString+Helpers.m | 17 ----------------- .../include/NSString+Helpers.h | 2 -- .../Utility/NSString+Helpers.m | 9 +++++++++ .../include/NSString+Helpers.h | 1 + 6 files changed, 13 insertions(+), 21 deletions(-) diff --git a/Modules/Sources/WordPressKit/PeopleServiceRemote.swift b/Modules/Sources/WordPressKit/PeopleServiceRemote.swift index ef179b950fde..132e863ae6b0 100644 --- a/Modules/Sources/WordPressKit/PeopleServiceRemote.swift +++ b/Modules/Sources/WordPressKit/PeopleServiceRemote.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressShared import WordPressKitObjC /// Encapsulates all of the People Management WordPress.com Methods @@ -558,7 +559,7 @@ private extension PeopleServiceRemote { let firstName = user["first_name"] as? String let lastName = user["last_name"] as? String let avatarURL = (user["avatar_URL"] as? NSString) - .flatMap { URL(string: $0.wpkit_stringByUrlEncoding())} + .flatMap { URL(string: $0.wp_stringByUrlEncoding())} let linkedUserID = user["linked_user_ID"] as? Int ?? ID let isSuperAdmin = user["is_super_admin"] as? Bool ?? false diff --git a/Modules/Sources/WordPressKitObjC/ReaderPostServiceRemote.m b/Modules/Sources/WordPressKitObjC/ReaderPostServiceRemote.m index 97b102a2a5fe..ecc8d9cd26e7 100644 --- a/Modules/Sources/WordPressKitObjC/ReaderPostServiceRemote.m +++ b/Modules/Sources/WordPressKitObjC/ReaderPostServiceRemote.m @@ -142,7 +142,7 @@ - (NSString *)endpointUrlForSearchPhrase:(NSString *)phrase { NSAssert([phrase length] > 0, @"A search phrase is required."); - NSString *endpoint = [NSString stringWithFormat:@"read/search?q=%@", [phrase wpkit_stringByUrlEncoding]]; + NSString *endpoint = [NSString stringWithFormat:@"read/search?q=%@", [phrase wp_stringByUrlEncoding]]; NSString *absolutePath = [self pathForEndpoint:endpoint withVersion:WordPressComRESTAPIVersion_1_2]; NSURL *url = [NSURL URLWithString:absolutePath relativeToURL:self.wordPressComRESTAPI.baseURL]; return [url absoluteString]; diff --git a/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m b/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m index 673ad53fe470..d71423f8c79b 100644 --- a/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m +++ b/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m @@ -80,23 +80,6 @@ + (NSString *)unicodeCharacterFromHexString:(NSString *)hexString return [[NSString alloc] initWithBytes:&hex length:4 encoding:NSUTF32LittleEndianStringEncoding]; } -// Taken from AFNetworking's AFPercentEscapedQueryStringPairMemberFromStringWithEncoding -- (NSString *)wpkit_stringByUrlEncoding -{ - NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; - NSString *charactersToLeaveUnescaped = @"[]."; - [allowedCharacterSet addCharactersInString:charactersToLeaveUnescaped]; - return [self stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; -} - -/* - * Uses a RegEx to strip all HTML tags from a string and unencode entites - */ -- (NSString *)wpkit_stringByStrippingHTML -{ - return [self stringByReplacingOccurrencesOfString:@"<[^>]+>" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, self.length)]; -} - - (bool)wpkit_isEmpty { return self.length == 0; } diff --git a/Modules/Sources/WordPressKitObjCUtils/include/NSString+Helpers.h b/Modules/Sources/WordPressKitObjCUtils/include/NSString+Helpers.h index c0c2d200ce5c..345eb34aa7e0 100644 --- a/Modules/Sources/WordPressKitObjCUtils/include/NSString+Helpers.h +++ b/Modules/Sources/WordPressKitObjCUtils/include/NSString+Helpers.h @@ -2,8 +2,6 @@ @interface NSString (WPKitHelpers) -- (NSString *)wpkit_stringByUrlEncoding; -- (NSString *)wpkit_stringByStrippingHTML; - (bool)wpkit_isEmpty; @end diff --git a/Modules/Sources/WordPressSharedObjC/Utility/NSString+Helpers.m b/Modules/Sources/WordPressSharedObjC/Utility/NSString+Helpers.m index 3abd17a54684..345cc214f5d9 100644 --- a/Modules/Sources/WordPressSharedObjC/Utility/NSString+Helpers.m +++ b/Modules/Sources/WordPressSharedObjC/Utility/NSString+Helpers.m @@ -225,6 +225,15 @@ - (NSArray *)tokenize return tokens; } +// Taken from AFNetworking's AFPercentEscapedQueryStringPairMemberFromStringWithEncoding +- (NSString *)wp_stringByUrlEncoding +{ + NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; + NSString *charactersToLeaveUnescaped = @"[]."; + [allowedCharacterSet addCharactersInString:charactersToLeaveUnescaped]; + return [self stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; +} + - (BOOL)isWordPressComPath { NSString *const dotcomDomain = @"wordpress.com"; diff --git a/Modules/Sources/WordPressSharedObjC/include/NSString+Helpers.h b/Modules/Sources/WordPressSharedObjC/include/NSString+Helpers.h index 4f5939379701..81990957309c 100644 --- a/Modules/Sources/WordPressSharedObjC/include/NSString+Helpers.h +++ b/Modules/Sources/WordPressSharedObjC/include/NSString+Helpers.h @@ -5,6 +5,7 @@ - (NSMutableDictionary *)dictionaryFromQueryString; - (NSString *)stringByReplacingHTMLEmoticonsWithEmoji; - (NSString *)stringByStrippingHTML; +- (NSString *)wp_stringByUrlEncoding; - (NSString *)wp_stringByEllipsizingWithMaxLength:(NSInteger)lengthlimit preserveWords:(BOOL)preserveWords; - (BOOL)isWordPressComPath; From f85575ec4667d48164b01d20c720f06eed38e20a Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Wed, 18 Mar 2026 09:40:23 -0400 Subject: [PATCH 08/10] Remove wpkit_isEmpty --- .../NSString+WPKitNumericValueHack.m | 18 +++ .../WordPressKitObjC/PostServiceRemoteREST.m | 2 +- .../PostServiceRemoteXMLRPC.m | 4 +- .../WordPressKitObjC/RemoteReaderPost.m | 4 +- .../TaxonomyServiceRemoteXMLRPC.m | 2 +- .../include/NSString+WPKitNumericValueHack.h} | 6 - .../WordPressKitObjCUtils/NSString+Helpers.m | 104 ------------------ 7 files changed, 23 insertions(+), 117 deletions(-) create mode 100644 Modules/Sources/WordPressKitObjC/NSString+WPKitNumericValueHack.m rename Modules/Sources/{WordPressKitObjCUtils/include/NSString+Helpers.h => WordPressKitObjC/include/NSString+WPKitNumericValueHack.h} (74%) delete mode 100644 Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m diff --git a/Modules/Sources/WordPressKitObjC/NSString+WPKitNumericValueHack.m b/Modules/Sources/WordPressKitObjC/NSString+WPKitNumericValueHack.m new file mode 100644 index 000000000000..5fe8234a57bc --- /dev/null +++ b/Modules/Sources/WordPressKitObjC/NSString+WPKitNumericValueHack.m @@ -0,0 +1,18 @@ +#import "NSString+WPKitNumericValueHack.h" + +@implementation NSString (WPKitNumericValueHack) + +- (NSNumber *)wpkit_numericValue { + return [NSNumber numberWithUnsignedLongLong:[self longLongValue]]; +} + +@end + +@implementation NSObject (WPKitNumericValueHack) +- (NSNumber *)wpkit_numericValue { + if ([self isKindOfClass:[NSNumber class]]) { + return (NSNumber *)self; + } + return nil; +} +@end diff --git a/Modules/Sources/WordPressKitObjC/PostServiceRemoteREST.m b/Modules/Sources/WordPressKitObjC/PostServiceRemoteREST.m index 66e6783315bb..fbc02baeba2c 100644 --- a/Modules/Sources/WordPressKitObjC/PostServiceRemoteREST.m +++ b/Modules/Sources/WordPressKitObjC/PostServiceRemoteREST.m @@ -417,7 +417,7 @@ + (RemotePost *)remotePostFromJSONDictionary:(NSDictionary *)jsonPost { post.permalinkTemplateURL = [jsonPost stringForKeyPath:@"other_URLs.permalink_URL"]; post.status = jsonPost[@"status"]; post.password = jsonPost[@"password"]; - if ([post.password wpkit_isEmpty]) { + if (post.password.length == 0) { post.password = nil; } post.parentID = [jsonPost numberForKeyPath:@"parent.ID"]; diff --git a/Modules/Sources/WordPressKitObjC/PostServiceRemoteXMLRPC.m b/Modules/Sources/WordPressKitObjC/PostServiceRemoteXMLRPC.m index 95ee0cba1017..c33a20896e8f 100644 --- a/Modules/Sources/WordPressKitObjC/PostServiceRemoteXMLRPC.m +++ b/Modules/Sources/WordPressKitObjC/PostServiceRemoteXMLRPC.m @@ -3,7 +3,7 @@ #import "RemotePostCategory.h" #import "RemotePostTerm.h" #import "NSMutableDictionary+Helpers.h" -#import "NSString+Helpers.h" +#import "NSString+WPKitNumericValueHack.h" @import WordPressShared; @import WordPressKitModels; @@ -313,7 +313,7 @@ + (RemotePost *)remotePostFromXMLRPCDictionary:(NSDictionary *)xmlrpcDictionary post.authorID = [xmlrpcDictionary numberForKey:@"post_author"]; post.status = [self statusForPostStatus:xmlrpcDictionary[@"post_status"] andDate:post.date]; post.password = xmlrpcDictionary[@"post_password"]; - if ([post.password wpkit_isEmpty]) { + if (post.password.length == 0) { post.password = nil; } post.parentID = [xmlrpcDictionary numberForKey:@"post_parent"]; diff --git a/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m b/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m index 0e04d1d51895..2f7b0557d1c6 100644 --- a/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m +++ b/Modules/Sources/WordPressKitObjC/RemoteReaderPost.m @@ -1,10 +1,8 @@ #import "RemoteReaderPost.h" #import "RemoteSourcePostAttribution.h" #import "RemoteReaderCrossPostMeta.h" -#import "NSString+Helpers.h" -#import "NSString+XMLExtensions.h" +#import "NSString+WPKitNumericValueHack.h" #import "WPKitDateUtils.h" -#import "DisplayableImageHelper.h" @import WordPressShared; @import WordPressKitModels; diff --git a/Modules/Sources/WordPressKitObjC/TaxonomyServiceRemoteXMLRPC.m b/Modules/Sources/WordPressKitObjC/TaxonomyServiceRemoteXMLRPC.m index ccaddd98d18f..7c1c87f2f90f 100644 --- a/Modules/Sources/WordPressKitObjC/TaxonomyServiceRemoteXMLRPC.m +++ b/Modules/Sources/WordPressKitObjC/TaxonomyServiceRemoteXMLRPC.m @@ -2,7 +2,7 @@ #import "RemotePostCategory.h" #import "RemotePostTag.h" #import "RemoteTaxonomyPaging.h" -#import "NSString+Helpers.h" +#import "NSString+WPKitNumericValueHack.h" #import "WPKitLogging.h" @import WordPressShared; diff --git a/Modules/Sources/WordPressKitObjCUtils/include/NSString+Helpers.h b/Modules/Sources/WordPressKitObjC/include/NSString+WPKitNumericValueHack.h similarity index 74% rename from Modules/Sources/WordPressKitObjCUtils/include/NSString+Helpers.h rename to Modules/Sources/WordPressKitObjC/include/NSString+WPKitNumericValueHack.h index 345eb34aa7e0..23188f89eed8 100644 --- a/Modules/Sources/WordPressKitObjCUtils/include/NSString+Helpers.h +++ b/Modules/Sources/WordPressKitObjC/include/NSString+WPKitNumericValueHack.h @@ -1,11 +1,5 @@ #import -@interface NSString (WPKitHelpers) - -- (bool)wpkit_isEmpty; - -@end - @interface NSString (WPKitNumericValueHack) - (NSNumber *)wpkit_numericValue; @end diff --git a/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m b/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m deleted file mode 100644 index d71423f8c79b..000000000000 --- a/Modules/Sources/WordPressKitObjCUtils/NSString+Helpers.m +++ /dev/null @@ -1,104 +0,0 @@ -#import "NSString+Helpers.h" -#import - -static NSString *const Ellipsis = @"\u2026"; - -@implementation NSString (WPKitHelpers) - -#pragma mark Helpers - -/** - Parses an WordPress core emoji IMG tag and returns the corresponding emoji character. - */ -+ (NSString *)emojiFromCoreEmojiImageTag:(NSString *)tag -{ - if ([tag rangeOfString:@" 0) { - NSTextCheckingResult *match = [matches firstObject]; - if (match.numberOfRanges == 2) { - NSRange range = [match rangeAtIndex:1]; - return [tag substringWithRange:range]; - } - } - - matches = [filenameRegex matchesInString:tag options:0 range:sourceRange]; - if ([matches count] > 0) { - NSTextCheckingResult *match = [matches firstObject]; - if (match.numberOfRanges == 2) { - NSRange range = [match rangeAtIndex:1]; - NSString *filename = [tag substringWithRange:range]; - return [self emojiCharacterFromCoreEmojiFilename:filename]; - } - } - - return nil; -} - -/** - Processes the filename of an core emoji image from `s.w.org/images/core/emoji` - and returns the unicode character for the emoji. - Filenames can be formatted as a single hex value, or for emoji comprised of - Unicode pairs, as two hex values separated by a dash. - */ -+ (NSString *)emojiCharacterFromCoreEmojiFilename:(NSString *)filename -{ - NSArray *components = [filename componentsSeparatedByString:@"-"]; - NSMutableArray *marr = [NSMutableArray array]; - for (NSString *string in components) { - NSString *unicodeChar = [NSString unicodeCharacterFromHexString:string]; - if (unicodeChar) { - [marr addObject:unicodeChar]; - } - } - - return [marr componentsJoinedByString:@""]; -} - -+ (NSString *)unicodeCharacterFromHexString:(NSString *)hexString -{ - NSScanner *scanner = [NSScanner scannerWithString:hexString]; - unsigned long long hex = 0; - BOOL success = [scanner scanHexLongLong:&hex]; - if (!success) { - return nil; - } - return [[NSString alloc] initWithBytes:&hex length:4 encoding:NSUTF32LittleEndianStringEncoding]; -} - -- (bool)wpkit_isEmpty { - return self.length == 0; -} - -@end - -@implementation NSString (WPKitNumericValueHack) - -- (NSNumber *)wpkit_numericValue { - return [NSNumber numberWithUnsignedLongLong:[self longLongValue]]; -} - -@end - -@implementation NSObject (WPKitNumericValueHack) -- (NSNumber *)wpkit_numericValue { - if ([self isKindOfClass:[NSNumber class]]) { - return (NSNumber *)self; - } - return nil; -} -@end From 2a8bd6f57965916cb35bec5f8ea2117b03d76efd Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Wed, 18 Mar 2026 09:44:34 -0400 Subject: [PATCH 09/10] Remove WordPressKitObjCUtils --- Modules/Package.swift | 11 +---------- Modules/Sources/WordPressKit/Exports.swift | 1 - .../Sources/WordPressKitModels/NSString+Summary.swift | 1 - .../NSString+MD5.m | 1 - .../include/NSString+MD5.h | 0 5 files changed, 1 insertion(+), 13 deletions(-) rename Modules/Sources/{WordPressKitObjCUtils => WordPressKitObjC}/NSString+MD5.m (99%) rename Modules/Sources/{WordPressKitObjCUtils => WordPressKitObjC}/include/NSString+MD5.h (100%) diff --git a/Modules/Package.swift b/Modules/Package.swift index 7c4c8b54c067..b3ebfdbc9d37 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -196,17 +196,10 @@ let package = Package( resources: [.process("Resources")], swiftSettings: [.swiftLanguageMode(.v5)] ), - .target( - name: "WordPressKitObjCUtils", - cSettings: [ - .define("NS_BLOCK_ASSERTIONS", to: "1", .when(configuration: .release)) - ] - ), .target( name: "WordPressKitModels", dependencies: [ "NSObject-SafeExpectations", - "WordPressKitObjCUtils", "WordPressShared", ] ), @@ -216,8 +209,7 @@ let package = Package( "NSObject-SafeExpectations", "wpxmlrpc", "WordPressShared", - "WordPressKitModels", - "WordPressKitObjCUtils", + "WordPressKitModels" ], publicHeadersPath: "include", cSettings: [ @@ -229,7 +221,6 @@ let package = Package( dependencies: [ "WordPressKitObjC", "WordPressKitModels", - "WordPressKitObjCUtils", "NSObject-SafeExpectations", "WordPressShared", "wpxmlrpc", diff --git a/Modules/Sources/WordPressKit/Exports.swift b/Modules/Sources/WordPressKit/Exports.swift index 96fc9d929098..3471acab3818 100644 --- a/Modules/Sources/WordPressKit/Exports.swift +++ b/Modules/Sources/WordPressKit/Exports.swift @@ -1,6 +1,5 @@ @_exported import WordPressKitModels @_exported import WordPressKitObjC -@_exported import WordPressKitObjCUtils extension ServiceRemoteWordPressComREST { public var wordPressComRestApi: WordPressComRestApi { diff --git a/Modules/Sources/WordPressKitModels/NSString+Summary.swift b/Modules/Sources/WordPressKitModels/NSString+Summary.swift index a9dae04a4933..2a2696f56e2f 100644 --- a/Modules/Sources/WordPressKitModels/NSString+Summary.swift +++ b/Modules/Sources/WordPressKitModels/NSString+Summary.swift @@ -1,6 +1,5 @@ import Foundation import WordPressShared -import WordPressKitObjCUtils /// This is an extension to NSString that provides logic to summarize HTML content, /// and convert HTML into plain text. diff --git a/Modules/Sources/WordPressKitObjCUtils/NSString+MD5.m b/Modules/Sources/WordPressKitObjC/NSString+MD5.m similarity index 99% rename from Modules/Sources/WordPressKitObjCUtils/NSString+MD5.m rename to Modules/Sources/WordPressKitObjC/NSString+MD5.m index 23d5519d5c24..74eb558d39db 100644 --- a/Modules/Sources/WordPressKitObjCUtils/NSString+MD5.m +++ b/Modules/Sources/WordPressKitObjC/NSString+MD5.m @@ -1,7 +1,6 @@ #import "NSString+MD5.h" #import - @implementation NSString (MD5) - (NSString *)md5 diff --git a/Modules/Sources/WordPressKitObjCUtils/include/NSString+MD5.h b/Modules/Sources/WordPressKitObjC/include/NSString+MD5.h similarity index 100% rename from Modules/Sources/WordPressKitObjCUtils/include/NSString+MD5.h rename to Modules/Sources/WordPressKitObjC/include/NSString+MD5.h From e7babf923bc58ba12c4d5246eea2265dbf400968 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Wed, 18 Mar 2026 09:57:54 -0400 Subject: [PATCH 10/10] Remove Swift flags --- config/Common.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/Common.xcconfig b/config/Common.xcconfig index 7bc6bd1c6fe6..c568d7bb2f76 100644 --- a/config/Common.xcconfig +++ b/config/Common.xcconfig @@ -11,4 +11,4 @@ GCC_TREAT_WARNINGS_AS_ERRORS = NO // See https://forums.swift.org/t/swift-cant-see-objc-methods-that-include-package-symbols/65732/9 // Without this flag, Objective-C APIs that contains Swift types in WordPressKit will not be available // to Swift code in the app target. -OTHER_SWIFT_FLAGS = $(inherited) -Xcc "-fmodule-map-file=${OBJROOT}/GeneratedModuleMaps-${PLATFORM_NAME}/WordPressKit.modulemap" -Xcc "-fmodule-map-file=${OBJROOT}/GeneratedModuleMaps-${PLATFORM_NAME}/WordPressKitModels.modulemap" -Xcc "-fmodule-map-file=${OBJROOT}/GeneratedModuleMaps-${PLATFORM_NAME}/WordPressKitObjC.modulemap" -Xcc "-fmodule-map-file=${OBJROOT}/GeneratedModuleMaps-${PLATFORM_NAME}/WordPressKitObjCUtils.modulemap" +OTHER_SWIFT_FLAGS = $(inherited) -Xcc "-fmodule-map-file=${OBJROOT}/GeneratedModuleMaps-${PLATFORM_NAME}/WordPressKit.modulemap" -Xcc "-fmodule-map-file=${OBJROOT}/GeneratedModuleMaps-${PLATFORM_NAME}/WordPressKitModels.modulemap" -Xcc "-fmodule-map-file=${OBJROOT}/GeneratedModuleMaps-${PLATFORM_NAME}/WordPressKitObjC.modulemap"