Revision: 29194
          http://sourceforge.net/p/bibdesk/svn/29194
Author:   hofman
Date:     2025-04-25 16:30:07 +0000 (Fri, 25 Apr 2025)
Log Message:
-----------
Add search group server for SRU. This is a rerplacement protocol for z39.50 for 
libraries. Not yet tested and not yet possible to choose in UI.

Modified Paths:
--------------
    trunk/bibdesk/BDSKSearchGroup.h
    trunk/bibdesk/BDSKSearchGroup.m
    trunk/bibdesk/BDSKSearchGroupSheetController.h
    trunk/bibdesk/BDSKSearchGroupSheetController.m
    trunk/bibdesk/BDSKServerInfo.h
    trunk/bibdesk/BDSKServerInfo.m
    trunk/bibdesk/Base.lproj/BDSKSearchGroupSheet.xib
    trunk/bibdesk/Bibdesk.xcodeproj/project.pbxproj

Added Paths:
-----------
    trunk/bibdesk/BDSKSRUGroupServer.h
    trunk/bibdesk/BDSKSRUGroupServer.m

Added: trunk/bibdesk/BDSKSRUGroupServer.h
===================================================================
--- trunk/bibdesk/BDSKSRUGroupServer.h                          (rev 0)
+++ trunk/bibdesk/BDSKSRUGroupServer.h  2025-04-25 16:30:07 UTC (rev 29194)
@@ -0,0 +1,64 @@
+//
+//  BDSKSRUGroupServer.h
+//  BibDesk
+//
+//  Created by Christiaan Hofmanon 25/04/2025.
+/*
+ This software is Copyright (c) 2025
+ Christiaan Hofman. All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ 
+ - Neither the name of Christiaan Hofman nor the names of any
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Cocoa/Cocoa.h>
+#import "BDSKSearchGroup.h"
+#import "BDSKDownloader.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class BDSKServerInfo;
+
+@interface BDSKSRUGroupServer : NSObject <BDSKSearchGroupServer, 
BDSKDownloadDelegate> {
+    __weak id<BDSKSearchGroup> group;
+    BDSKServerInfo *serverInfo;
+    NSString *searchTerm;
+    BDSKDownload *download;
+    BOOL failedDownload;
+    BOOL needsReset;
+    NSInteger availableResults;
+    NSInteger fetchedResults;
+    NSInteger requestedResults;
+    NSInteger limitResults;
+    NSInteger downloadState;
+    NSString *errorMessage;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END

Added: trunk/bibdesk/BDSKSRUGroupServer.m
===================================================================
--- trunk/bibdesk/BDSKSRUGroupServer.m                          (rev 0)
+++ trunk/bibdesk/BDSKSRUGroupServer.m  2025-04-25 16:30:07 UTC (rev 29194)
@@ -0,0 +1,330 @@
+//
+//  BDSKSRUGroupServer.m
+//  BibDesk
+//
+//  Created by Christiaan Hofmanon 25/04/2025.
+/*
+ This software is Copyright (c) 2025
+ Christiaan Hofman. All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ 
+ - Neither the name of Christiaan Hofman nor the names of any
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "BDSKSRUGroupServer.h"
+#import "BDSKServerInfo.h"
+#import "BDSKStringParser.h"
+#import "BDSKMARCParser.h"
+#import "NSString_BDSKExtensions.h"
+#import "NSURL_BDSKExtensions.h"
+
+#define MAX_RESULTS 100
+#define MAX_TOTAL_RESULTS 1000
+
+enum { BDSKIdleState, BDSKSearchState, BDSKFetchState };
+
+@interface BDSKSRUGroupServer ()
+
+@property (nonatomic, copy) NSString *searchTerm;
+
+@property (nonatomic, copy) NSString *errorMessage;
+
+- (void)resetSearch;
+- (void)fetch;
+
+- (void)startDownloadFromURL:(NSURL *)theURL;
+
+@end
+
+#pragma mark -
+
+@implementation BDSKSRUGroupServer
+
+@synthesize searchTerm, errorMessage;
+
+- (instancetype)initWithGroup:(id<BDSKSearchGroup>)aGroup 
serverInfo:(BDSKServerInfo *)info {
+    self = [super init];
+    if (self) {
+        group = aGroup;
+        serverInfo = [info copy];
+        searchTerm = nil;
+        failedDownload = NO;
+        needsReset = NO;
+        availableResults = 0;
+        fetchedResults = 0;
+        limitResults = 0;
+        requestedResults = 0;
+        download = nil;
+        downloadState = BDSKIdleState;
+        errorMessage = nil;
+    }
+    return self;
+}
+
+#pragma mark BDSKSearchGroupServer protocol
+
+- (NSString *)type { return BDSKSearchGroupSRU; }
+
+- (void)reset {
+    if ([self isRetrieving])
+        [self terminate];
+    availableResults = 0;
+    fetchedResults = 0;
+}
+
+- (void)terminate {
+    [download cancel];
+    download = nil;
+    downloadState = BDSKIdleState;
+}
+
+- (void)retrieveWithSearchTerm:(NSString *)aSearchTerm {
+    if ([[[self baseComponents] URL] canConnect]) {
+        if ([[self searchTerm] isEqualToString:aSearchTerm] == NO || 
needsReset) {
+            [self setSearchTerm:aSearchTerm];
+            [self resetSearch];
+        } else if ([self isRetrieving] == NO) {
+            limitResults = MIN(availableResults, fetchedResults + 
MAX_TOTAL_RESULTS);
+            [self fetch];
+        }
+    } else {
+        failedDownload = YES;
+        [self setErrorMessage:NSLocalizedString(@"Unable to connect to 
server", @"error when pubmed connection fails")];
+    }
+}
+
+- (BDSKServerInfo *)serverInfo { return serverInfo; }
+
+- (void)setServerInfo:(BDSKServerInfo *)info {
+    if(serverInfo != info){
+        serverInfo = [info copy];
+        needsReset = YES;
+    }
+}
+
+- (NSInteger)numberOfFetchedResults { return fetchedResults; }
+
+- (NSInteger)numberOfAvailableResults { return availableResults; }
+
+- (BOOL)failedDownload { return failedDownload; }
+
+- (BOOL)isRetrieving { return BDSKIdleState != downloadState; }
+
+- (NSFormatter *)searchStringFormatter { return nil; }
+
+#pragma mark URLs
+
+static inline NSString *escapeHost(NSString *string) {
+    return [string 
stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet 
URLHostAllowedCharacterSet]];
+}
+
+static NSString *escapeDatabase(NSString *string) {
+    static NSCharacterSet *URLDatabaseOrNameAllowedCharacterSet = nil;
+    if (URLDatabaseOrNameAllowedCharacterSet == nil) {
+        NSMutableCharacterSet *tmpSet = [[NSCharacterSet 
URLPathAllowedCharacterSet] mutableCopy];
+        [tmpSet removeCharactersInString:@"/"];
+        URLDatabaseOrNameAllowedCharacterSet = [tmpSet copy];
+    }
+    return [string 
stringByAddingPercentEncodingWithAllowedCharacters:URLDatabaseOrNameAllowedCharacterSet];
+}
+
+- (NSURLComponents *)baseComponents {
+    NSString *host = [[self serverInfo] host];
+    NSInteger port = [[[self serverInfo] port] integerValue];
+    NSString *database = [[self serverInfo] database];
+    NSURLComponents *components = [[NSURLComponents alloc] init];
+    [components setScheme:@"http"];
+    [components setPercentEncodedHost:escapeHost(host)];
+    if (port > 0)
+        [components setPort:[NSNumber numberWithInteger:port]];
+    [components setPercentEncodedPath:[@"/" 
stringByAppendingString:escapeDatabase(database)]];
+    return components;
+}
+
+- (NSURL *)searchURL {
+    NSURLComponents *components = [self baseComponents];
+    NSMutableArray *query = [NSMutableArray array];
+    NSString *version = [[[self serverInfo] options] objectForKey:@"version"] 
?: @"1.1";
+    [query addObject:[@"version=" stringByAppendingString:version]];
+    [query addObject:@"operation=searchRetrieve"];
+    [query addObject:[@"query=" stringByAppendingString:[[self searchTerm] 
stringByAddingPercentEscapesForQueryTerm]]];
+    [components setPercentEncodedQuery:[query componentsJoinedByString:@"&"]];
+    return [components URL];
+}
+
+- (NSURL *)fetchURL {
+    NSURLComponents *components = [self baseComponents];
+    NSMutableArray *query = [NSMutableArray array];
+    NSString *version = [[[self serverInfo] options] objectForKey:@"version"] 
?: @"1.1";
+    [query addObject:[@"version=" stringByAppendingString:version]];
+    [query addObject:@"operation=searchRetrieve"];
+    [query addObject:[@"query=%@" stringByAppendingString:[[self searchTerm] 
stringByAddingPercentEscapesForQueryTerm]]];
+    [query addObject:[NSString stringWithFormat:@"startRecord=%ld", 1 + [self 
numberOfFetchedResults]]];
+    [query addObject:[NSString stringWithFormat:@"maximumRecords=%ld", 
MIN([self numberOfAvailableResults] - [self numberOfFetchedResults], 
MAX_RESULTS)]];
+    [components setPercentEncodedQuery:[query componentsJoinedByString:@"&"]];
+    return [components URL];
+}
+
+#pragma mark Search methods
+
+- (void)resetSearch {
+    [self reset];
+    
+    if (NO == [NSString isEmptyString:[self searchTerm]]) {
+        // get the initial XML document with our search parameters in it
+        NSURL *initialURL = [self searchURL];
+        BDSKPRECONDITION(initialURL);
+        
+        downloadState = BDSKSearchState;
+        [self startDownloadFromURL:initialURL];
+        needsReset = NO;
+    }
+}
+
+- (void)fetch {
+    if ([self numberOfAvailableResults] <= [self numberOfFetchedResults]) {
+        downloadState = BDSKIdleState;
+        [group addPublications:@[]];
+        return;
+    }
+    
+    NSRange returnRange = NSMakeRange([self numberOfFetchedResults], MIN([self 
numberOfAvailableResults] - [self numberOfFetchedResults], MAX_RESULTS));
+    
+    NSURL *theURL = [self fetchURL];
+    BDSKPOSTCONDITION(theURL);
+    
+    requestedResults = returnRange.length;
+    
+    downloadState = BDSKFetchState;
+    [self startDownloadFromURL:theURL];
+}
+
+#pragma mark Downloading
+
+- (void)download:(BDSKDownload *)aDownload didCompleteWithError:(NSError 
*)error {
+    if (error) {
+        // calling -cancel may call this delegate method
+        if ([[error domain] isEqualToString:NSURLErrorDomain] && [error code] 
== NSURLErrorCancelled)
+            return;
+        
+        downloadState = BDSKIdleState;
+        failedDownload = YES;
+        [self setErrorMessage:[error localizedDescription]];
+        
+        download = nil;
+        
+        // redraw
+        [group addPublications:nil];
+        return;
+    }
+    
+    failedDownload = NO;
+    NSError *presentableError;
+    NSData *data = [download data];
+    
+    download = nil;
+    
+    switch (downloadState) {
+        case BDSKSearchState:
+        {
+            // okay to reset state before calling -fetch
+            downloadState = BDSKIdleState;
+            
+            // parse the result opf the search
+            NSXMLDocument *document = nil;
+            if (data)
+                document = [[NSXMLDocument alloc] initWithData:data 
options:NSXMLNodeOptionsNone error:NULL];
+
+            if (nil != document) {
+                NSXMLElement *root = [document rootElement];
+                
+                // we need to extract WebEnv, Count, and QueryKey to construct 
our final URL
+                NSString *countString = [[[root 
nodesForXPath:@"/zs:searchRetrieveResponse[1]/zs:numberOfRecords[1]" 
error:NULL] lastObject] stringValue];
+                
+                availableResults = [countString integerValue];
+                limitResults = MIN(availableResults, fetchedResults + 
MAX_TOTAL_RESULTS);
+                
+                [self fetch];
+                
+            } else {
+                
+                // no document, or zero length data from the server
+                failedDownload = YES;
+                [self setErrorMessage:NSLocalizedString(@"Unable to connect to 
server", @"")];
+                
+            }
+            
+            break;
+        }
+        case BDSKFetchState:
+        {
+            
+            NSString *string = [[NSString alloc] initWithData:data 
encoding:NSUTF8StringEncoding];
+            NSArray *pubs = [BDSKMARCParser itemsFromString:string 
error:&presentableError];
+            
+            // the number of returned results is not always equal to what we 
requested
+            // fetchedResults should track the requests to get the correct 
offset
+            fetchedResults += requestedResults;
+            
+            if (nil == pubs) {
+                failedDownload = YES;
+                [self setErrorMessage:[presentableError localizedDescription]];
+                // set before addPublications:
+                downloadState = BDSKIdleState;
+                [group addPublications:nil];
+            }
+            else if (limitResults > [self numberOfFetchedResults]) {
+                [group addPublications:pubs];
+                [self fetch];
+            }
+            else {
+                // set before addPublications:
+                downloadState = BDSKIdleState;
+                [group addPublications:pubs];
+            }
+            break;
+        }
+        case BDSKIdleState:
+            break;
+        default:
+            [NSException raise:NSInternalInconsistencyException 
format:@"Unhandled case %ld", (long)downloadState];
+            break;
+    }
+}
+
+- (NSWindow *)downloadWindowForAuthenticationSheet:(BDSKDownload *)download {
+    return [group windowForSheetForObject:self];
+}
+
+- (void)startDownloadFromURL:(NSURL *)theURL {
+    NSURLRequest *request = [NSURLRequest requestWithURL:theURL];
+    [download cancel];
+    download = [[BDSKDownloader sharedDownloader] 
startDataDownloadWithRequest:request delegate:self];
+}
+
+@end

Modified: trunk/bibdesk/BDSKSearchGroup.h
===================================================================
--- trunk/bibdesk/BDSKSearchGroup.h     2025-04-24 15:59:23 UTC (rev 29193)
+++ trunk/bibdesk/BDSKSearchGroup.h     2025-04-25 16:30:07 UTC (rev 29194)
@@ -42,6 +42,7 @@
 NS_ASSUME_NONNULL_BEGIN
 
 extern NSString *BDSKSearchGroupEntrez;
+extern NSString *BDSKSearchGroupSRU;
 extern NSString *BDSKSearchGroupZoom;
 extern NSString *BDSKSearchGroupISI;
 extern NSString *BDSKSearchGroupDBLP;

Modified: trunk/bibdesk/BDSKSearchGroup.m
===================================================================
--- trunk/bibdesk/BDSKSearchGroup.m     2025-04-24 15:59:23 UTC (rev 29193)
+++ trunk/bibdesk/BDSKSearchGroup.m     2025-04-25 16:30:07 UTC (rev 29194)
@@ -45,6 +45,7 @@
 #import "BDSKServerInfo.h"
 #import "BDSKISIGroupServer.h"
 #import "BDSKDBLPGroupServer.h"
+#import "BDSKSRUGroupServer.h"
 #import "BDSKGroup+Scripting.h"
 #import "BibItem.h"
 #import "BDSKStringConstants.h"
@@ -54,6 +55,7 @@
 #import "NSFileManager_BDSKExtensions.h"
 
 NSString *BDSKSearchGroupEntrez = @"entrez";
+NSString *BDSKSearchGroupSRU = @"sru";
 NSString *BDSKSearchGroupZoom = @"zoom";
 NSString *BDSKSearchGroupISI = @"isi";
 NSString *BDSKSearchGroupDBLP = @"dblp";
@@ -386,6 +388,11 @@
                 aName = value;
             } else if ([key isEqualToString:@"database"]) {
                 aDatabase = value;
+            } else if ([key isEqualToString:@"type"]) {
+                if ([value isCaseInsensitiveEqual:BDSKSearchGroupSRU])
+                    aType = BDSKSearchGroupSRU;
+                else if ([value isCaseInsensitiveEqual:BDSKSearchGroupZoom])
+                    aType = BDSKSearchGroupZoom;
             } else {
                 if ([key isEqualToString:@"removeDiacritics"] || [key 
isEqualToString:@"lite"]) {
                     if ([value boolValue] == NO) continue;
@@ -405,6 +412,9 @@
         [dictionary setValue:aHost forKey:@"host"];
         [dictionary setValue:aPort forKey:@"port"];
         [dictionary setValue:options forKey:@"options"];
+    } else if ([aType isEqualToString:BDSKSearchGroupSRU]) {
+        [dictionary setValue:aHost forKey:@"host"];
+        [dictionary setValue:aPort forKey:@"port"];
     } else if ([aType isEqualToString:BDSKSearchGroupISI] && [options count] > 
0) {
         [dictionary setValue:options forKey:@"options"];
     }
@@ -423,6 +433,8 @@
         serverClass = [BDSKISIGroupServer class];
     else if ([aType isEqualToString:BDSKSearchGroupDBLP])
         serverClass = [BDSKDBLPGroupServer class];
+    else if ([aType isEqualToString:BDSKSearchGroupSRU])
+        serverClass = [BDSKSRUGroupServer class];
     else
         BDSKASSERT_NOT_REACHED("unknown search group type");
     return [[serverClass alloc] initWithGroup:group serverInfo:info];

Modified: trunk/bibdesk/BDSKSearchGroupSheetController.h
===================================================================
--- trunk/bibdesk/BDSKSearchGroupSheetController.h      2025-04-24 15:59:23 UTC 
(rev 29193)
+++ trunk/bibdesk/BDSKSearchGroupSheetController.h      2025-04-25 16:30:07 UTC 
(rev 29194)
@@ -112,7 +112,9 @@
 @property (nonatomic, getter=isEditable) BOOL editable;
 @property (nonatomic, readonly, getter=isZoom) BOOL zoom;
 @property (nonatomic, readonly, getter=isISI) BOOL ISI;
+@property (nonatomic, readonly, getter=isSRU) BOOL SRU;
 @property (nonatomic, readonly, getter=isZoomOrISI) BOOL zoomOrISI;
+@property (nonatomic, readonly, getter=isZoomOrSRU) BOOL zoomOrSRU;
 
 @property (nonatomic, strong) NSString *type;
 

Modified: trunk/bibdesk/BDSKSearchGroupSheetController.m
===================================================================
--- trunk/bibdesk/BDSKSearchGroupSheetController.m      2025-04-24 15:59:23 UTC 
(rev 29193)
+++ trunk/bibdesk/BDSKSearchGroupSheetController.m      2025-04-25 16:30:07 UTC 
(rev 29194)
@@ -54,13 +54,13 @@
 @implementation BDSKSearchGroupSheetController
 
 @synthesize serverPopup, nameField, addressField, portField, databaseField, 
passwordField, userField, syntaxPopup, encodingComboBox, 
removeDiacriticsButton, liteButton, editButton, addRemoveButton, serverView, 
revealButton, okButton, cancelButton, bottomConstraint, objectController, 
custom, editable;
-@dynamic zoom, ISI, zoomOrISI, typeTag, databases, serverInfo, undoManager;
+@dynamic zoom, ISI, SRU, zoomOrISI, zoomOrSRU, typeTag, databases, serverInfo, 
undoManager;
 
 + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
     NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
     if ([[NSSet setWithObjects:@"type", nil] containsObject:key])
         keyPaths = [keyPaths setByAddingObjectsFromSet:[NSSet 
setWithObjects:@"serverInfo", nil]];
-    else if ([[NSSet setWithObjects:@"typeTag", @"zoom", @"ISI", @"zoomOrISI", 
@"databases", nil] containsObject:key])
+    else if ([[NSSet setWithObjects:@"typeTag", @"zoom", @"ISI", @"SRU", 
@"zoomOrISI", @"zoomOrSRU", @"databases", nil] containsObject:key])
         keyPaths = [keyPaths setByAddingObjectsFromSet:[NSSet 
setWithObjects:@"type", nil]];
     return keyPaths;
 }
@@ -314,8 +314,12 @@
 
 - (BOOL)isISI { return [serverInfo isISI]; }
 
+- (BOOL)isSRU { return [serverInfo isSRU]; }
+
 - (BOOL)isZoomOrISI { return [serverInfo isZoom] || [serverInfo isISI]; }
 
+- (BOOL)isZoomOrSRU { return [serverInfo isZoom] || [serverInfo isSRU]; }
+
 - (BDSKServerInfo *)serverInfo { return serverInfo; }
 
 - (void)setServerInfo:(BDSKServerInfo *)info;
@@ -345,6 +349,7 @@
         case BDSKServerTypeZoom:   [self setType:BDSKSearchGroupZoom];   break;
         case BDSKServerTypeISI:    [self setType:BDSKSearchGroupISI];    break;
         case BDSKServerTypeDBLP:   [self setType:BDSKSearchGroupDBLP];   break;
+        case BDSKServerTypeSRU:   [self setType:BDSKSearchGroupSRU];   break;
         default: BDSKASSERT_NOT_REACHED("Unknown search type tag");
     }
 }
@@ -359,6 +364,7 @@
             return entrezDatabases;
         }
         case BDSKServerTypeZoom:
+        case BDSKServerTypeSRU:
             return @[];
         case BDSKServerTypeISI:
         {

Modified: trunk/bibdesk/BDSKServerInfo.h
===================================================================
--- trunk/bibdesk/BDSKServerInfo.h      2025-04-24 15:59:23 UTC (rev 29193)
+++ trunk/bibdesk/BDSKServerInfo.h      2025-04-25 16:30:07 UTC (rev 29194)
@@ -44,7 +44,8 @@
     BDSKServerTypeEntrez,
     BDSKServerTypeZoom,
     BDSKServerTypeDBLP,
-    BDSKServerTypeISI
+    BDSKServerTypeISI,
+    BDSKServerTypeSRU
 };
 
 @class BDSKReadWriteLock;
@@ -86,6 +87,7 @@
 @property (nonatomic, readonly, getter=isZoom) BOOL zoom;
 @property (nonatomic, readonly, getter=isISI) BOOL ISI;
 @property (nonatomic, readonly, getter=isDBLP) BOOL DBLP;
+@property (nonatomic, readonly, getter=isSRU) BOOL SRU;
 
 @property (nonatomic, readonly) BDSKServerType serverType;
 

Modified: trunk/bibdesk/BDSKServerInfo.m
===================================================================
--- trunk/bibdesk/BDSKServerInfo.m      2025-04-24 15:59:23 UTC (rev 29193)
+++ trunk/bibdesk/BDSKServerInfo.m      2025-04-25 16:30:07 UTC (rev 29194)
@@ -81,7 +81,7 @@
 @implementation BDSKServerInfo
 
 @synthesize type, name, database;
-@dynamic dictionaryValue, host, port, username, recordSyntax, resultEncoding, 
queryConfig, removeDiacritics, lite, options, entrez, zoom, ISI, DBLP, 
serverType, URLValue;
+@dynamic dictionaryValue, host, port, username, recordSyntax, resultEncoding, 
queryConfig, removeDiacritics, lite, options, entrez, zoom, ISI, DBLP, SRU, 
serverType, URLValue;
 
 + (BOOL)accessInstanceVariablesDirectly { return NO; }
 
@@ -88,12 +88,13 @@
 + (instancetype)defaultServerInfoWithType:(NSString *)aType;
 {
     BOOL isZoom = [aType isEqualToString:BDSKSearchGroupZoom];
+    BOOL isSRU = [aType isEqualToString:BDSKSearchGroupSRU];
     
     return [[[self class] alloc] initWithType:aType
                                          name:DEFAULT_NAME
                                      database:DEFAULT_DATABASE
-                                         host:isZoom ? DEFAULT_HOST : nil
-                                         port:isZoom ? DEFAULT_PORT : nil
+                                         host:isZoom || isSRU ? DEFAULT_HOST : 
nil
+                                         port:isZoom || isSRU ? DEFAULT_PORT : 
nil
                                       options:isZoom ? @{} : nil];
 }
 
@@ -188,9 +189,12 @@
         isEqualOrBothNil([self database], [other database]) == NO)
         isEqual = NO;
     else if ([self isZoom])
-        isEqual = isEqualOrBothNil([self host], [other host]) && 
-                  isEqualOrBothNil([self port], [(BDSKServerInfo *)other 
port]) && 
+        isEqual = isEqualOrBothNil([self host], [other host]) &&
+                  isEqualOrBothNil([self port], [(BDSKServerInfo *)other 
port]) &&
                   isEqualOrBothEmpty(options, [(BDSKServerInfo *)other 
options]);
+    else if ([self isSRU])
+        isEqual = isEqualOrBothNil([self host], [other host]) &&
+                  isEqualOrBothNil([self port], [(BDSKServerInfo *)other 
port]);
     else if ([self isISI])
         isEqual = isEqualOrBothEmpty(options, [(BDSKServerInfo *)other 
options]);
     return isEqual;
@@ -199,7 +203,7 @@
 - (NSUInteger)hash {
     NSUInteger prime = 31;
     NSUInteger hash = prime * [[self type] hash] + [[self database] hash];
-    if ([self isZoom]) {
+    if ([self isZoom] || [self isSRU]) {
         hash = prime * hash + [[self host] hash];
         hash = prime * hash + [[self port] hash];
     }
@@ -220,6 +224,9 @@
         [info setValue:[self host] forKey:HOST_KEY];
         [info setValue:[self port] forKey:PORT_KEY];
         [info setValue:[self options] forKey:OPTIONS_KEY];
+    } else if ([self isSRU]) {
+        [info setValue:[self host] forKey:HOST_KEY];
+        [info setValue:[self port] forKey:PORT_KEY];
     } else if ([self isISI] && [options count] > 0) {
         [info setValue:[self options] forKey:OPTIONS_KEY];
     }
@@ -226,9 +233,9 @@
     return info;
 }
 
-- (NSString *)host { return [self isZoom] ? host : nil; }
+- (NSString *)host { return [self isZoom] || [self isSRU] ? host : nil; }
 
-- (NSString *)port { return [self isZoom] ? port : nil; }
+- (NSString *)port { return [self isZoom] || [self isSRU] ? port : nil; }
 
 - (NSString *)username { return [options objectForKey:USERNAME_KEY]; }
 
@@ -250,6 +257,7 @@
 - (BOOL)isZoom { return [[self type] isEqualToString:BDSKSearchGroupZoom]; }
 - (BOOL)isISI { return [[self type] isEqualToString:BDSKSearchGroupISI]; }
 - (BOOL)isDBLP { return [[self type] isEqualToString:BDSKSearchGroupDBLP]; }
+- (BOOL)isSRU { return [[self type] isEqualToString:BDSKSearchGroupSRU]; }
 
 - (BDSKServerType)serverType {
     if ([self isEntrez])
@@ -260,6 +268,8 @@
         return BDSKServerTypeISI;
     if ([self isDBLP])
         return BDSKServerTypeDBLP;
+    if ([self isSRU])
+        return BDSKServerTypeSRU;
     BDSKASSERT_NOT_REACHED("Unknown search type");
     return BDSKServerTypeEntrez;
 }
@@ -337,7 +347,7 @@
     NSString *username = [self username];
     if (username)
         [components setPercentEncodedUser:escapeUser(username)];
-    if ([self isZoom]) {
+    if ([self isZoom] || [self isSRU]) {
         [components setPercentEncodedHost:escapeHost([self host])];
         [components setPort:[NSNumber numberWithInteger:[[self port] 
integerValue]]];
     } else {
@@ -357,6 +367,8 @@
             [components setPercentEncodedQuery:[query 
componentsJoinedByString:@"&"]];
     } else if ([self isISI] && [self isLite]) {
         [components setPercentEncodedQuery:@"lite=1"];
+    } else if ([self isSRU]) {
+        [components setPercentEncodedQuery:@"type=sru"];
     }
     NSURL *url = [components URL];
     return url;
@@ -396,6 +408,11 @@
                 [self setPort:DEFAULT_PORT];
             password = nil;
             options = [[NSMutableDictionary alloc] init];
+        } else if ([self isSRU]) {
+            if (host == nil)
+                [self setHost:DEFAULT_HOST];
+            if (port == nil)
+                [self setPort:DEFAULT_PORT];
         } else {
             password = nil;
             options = nil;
@@ -495,7 +512,7 @@
 
 - (BOOL)validateHost:(id *)value error:(NSError **)error {
     NSString *string = *value;
-    if ([self isZoom]) {
+    if ([self isZoom] || [self isSRU]) {
         NSRange range = [string rangeOfString:@"://"];
         if(range.location != NSNotFound){
             // ZOOM gets confused when the host has a protocol

Modified: trunk/bibdesk/Base.lproj/BDSKSearchGroupSheet.xib
===================================================================
--- trunk/bibdesk/Base.lproj/BDSKSearchGroupSheet.xib   2025-04-24 15:59:23 UTC 
(rev 29193)
+++ trunk/bibdesk/Base.lproj/BDSKSearchGroupSheet.xib   2025-04-25 16:30:07 UTC 
(rev 29194)
@@ -130,7 +130,7 @@
                                             <color key="backgroundColor" 
name="controlColor" catalog="System" colorSpace="catalog"/>
                                         </textFieldCell>
                                         <connections>
-                                            <binding destination="-2" 
name="hidden" keyPath="zoom" id="960">
+                                            <binding destination="-2" 
name="hidden" keyPath="zoomOrSRU" id="mrZ-fQ-Vvk">
                                                 <dictionary key="options">
                                                     <string 
key="NSValueTransformerName">NSNegateBoolean</string>
                                                 </dictionary>
@@ -145,7 +145,7 @@
                                             <color key="backgroundColor" 
name="controlColor" catalog="System" colorSpace="catalog"/>
                                         </textFieldCell>
                                         <connections>
-                                            <binding destination="-2" 
name="hidden" keyPath="zoom" id="958">
+                                            <binding destination="-2" 
name="hidden" keyPath="zoomOrSRU" id="fKC-PK-0y5">
                                                 <dictionary key="options">
                                                     <string 
key="NSValueTransformerName">NSNegateBoolean</string>
                                                 </dictionary>
@@ -276,6 +276,11 @@
                                         <connections>
                                             <accessibilityConnection 
property="title" destination="191" id="525"/>
                                             <binding destination="-2" 
name="enabled" keyPath="editable" id="468"/>
+                                            <binding destination="-2" 
name="hidden" keyPath="zoomOrSRU" previousBinding="979" id="zmn-bc-vkQ">
+                                                <dictionary key="options">
+                                                    <string 
key="NSValueTransformerName">NSNegateBoolean</string>
+                                                </dictionary>
+                                            </binding>
                                             <binding destination="569" 
name="value" keyPath="selection.port" id="578">
                                                 <dictionary key="options">
                                                     <integer 
key="NSConditionallySetsEditable" value="0"/>
@@ -291,11 +296,6 @@
                                                     <integer 
key="NSNullPlaceholder" value="-1"/>
                                                 </dictionary>
                                             </binding>
-                                            <binding destination="-2" 
name="hidden" keyPath="zoom" previousBinding="979" id="980">
-                                                <dictionary key="options">
-                                                    <string 
key="NSValueTransformerName">NSNegateBoolean</string>
-                                                </dictionary>
-                                            </binding>
                                             <outlet property="nextKeyView" 
destination="201" id="293"/>
                                         </connections>
                                     </textField>
@@ -376,6 +376,11 @@
                                         <connections>
                                             <accessibilityConnection 
property="title" destination="196" id="524"/>
                                             <binding destination="-2" 
name="enabled" keyPath="editable" id="480"/>
+                                            <binding destination="-2" 
name="hidden" keyPath="zoomOrSRU" previousBinding="977" id="XIN-vT-kNF">
+                                                <dictionary key="options">
+                                                    <string 
key="NSValueTransformerName">NSNegateBoolean</string>
+                                                </dictionary>
+                                            </binding>
                                             <binding destination="569" 
name="value" keyPath="selection.host" id="576">
                                                 <dictionary key="options">
                                                     <integer 
key="NSConditionallySetsEditable" value="0"/>
@@ -391,11 +396,6 @@
                                                     <integer 
key="NSNullPlaceholder" value="-1"/>
                                                 </dictionary>
                                             </binding>
-                                            <binding destination="-2" 
name="hidden" keyPath="zoom" previousBinding="977" id="978">
-                                                <dictionary key="options">
-                                                    <string 
key="NSValueTransformerName">NSNegateBoolean</string>
-                                                </dictionary>
-                                            </binding>
                                             <outlet property="nextKeyView" 
destination="198" id="212"/>
                                         </connections>
                                     </textField>

Modified: trunk/bibdesk/Bibdesk.xcodeproj/project.pbxproj
===================================================================
--- trunk/bibdesk/Bibdesk.xcodeproj/project.pbxproj     2025-04-24 15:59:23 UTC 
(rev 29193)
+++ trunk/bibdesk/Bibdesk.xcodeproj/project.pbxproj     2025-04-25 16:30:07 UTC 
(rev 29194)
@@ -469,6 +469,8 @@
                CE3B5E7D09CEDE470017D339 /* BDSKMacroResolver.m in Sources */ = 
{isa = PBXBuildFile; fileRef = CE3B5E7B09CEDE470017D339 /* BDSKMacroResolver.m 
*/; };
                CE3B682B09D1B0190017D339 /* BDSKImagePopUpButton.m in Sources 
*/ = {isa = PBXBuildFile; fileRef = CE3B682709D1B0190017D339 /* 
BDSKImagePopUpButton.m */; };
                CE3B682D09D1B0190017D339 /* BDSKImagePopUpButtonCell.m in 
Sources */ = {isa = PBXBuildFile; fileRef = CE3B682909D1B0190017D339 /* 
BDSKImagePopUpButtonCell.m */; };
+               CE3D27C82DBB85AA002EA644 /* BDSKSRUGroupServer.h in Headers */ 
= {isa = PBXBuildFile; fileRef = CE3D27C62DBB85AA002EA644 /* 
BDSKSRUGroupServer.h */; };
+               CE3D27C92DBB85AA002EA644 /* BDSKSRUGroupServer.m in Sources */ 
= {isa = PBXBuildFile; fileRef = CE3D27C72DBB85AA002EA644 /* 
BDSKSRUGroupServer.m */; };
                CE4241360D0EAE0B00F824E7 /* BDSKEditorTableView.m in Sources */ 
= {isa = PBXBuildFile; fileRef = CE4241340D0EAE0B00F824E7 /* 
BDSKEditorTableView.m */; };
                CE4241450D0EAEFE00F824E7 /* BDSKEditorTextField.m in Sources */ 
= {isa = PBXBuildFile; fileRef = CE4241430D0EAEFE00F824E7 /* 
BDSKEditorTextField.m */; };
                CE424A450D0F123500F824E7 /* BDSKCompletionServerProtocol.h in 
Headers */ = {isa = PBXBuildFile; fileRef = CE3A0BD50B1634D500233208 /* 
BDSKCompletionServerProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -1545,6 +1547,8 @@
                CE3B682709D1B0190017D339 /* BDSKImagePopUpButton.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= BDSKImagePopUpButton.m; sourceTree = "<group>"; };
                CE3B682809D1B0190017D339 /* BDSKImagePopUpButtonCell.h */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; 
path = BDSKImagePopUpButtonCell.h; sourceTree = "<group>"; };
                CE3B682909D1B0190017D339 /* BDSKImagePopUpButtonCell.m */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; path = BDSKImagePopUpButtonCell.m; sourceTree = "<group>"; };
+               CE3D27C62DBB85AA002EA644 /* BDSKSRUGroupServer.h */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.c.h; path = 
BDSKSRUGroupServer.h; sourceTree = "<group>"; };
+               CE3D27C72DBB85AA002EA644 /* BDSKSRUGroupServer.m */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = 
BDSKSRUGroupServer.m; sourceTree = "<group>"; };
                CE3D8DA0125E69BB00AE0232 /* de */ = {isa = PBXFileReference; 
fileEncoding = 10; lastKnownFileType = text.plist.strings; name = de; path = 
de.lproj/BibTeXKeys.strings; sourceTree = "<group>"; };
                CE3D8DA5125E69BB00AE0232 /* de */ = {isa = PBXFileReference; 
lastKnownFileType = text.rtf; name = de; path = de.lproj/Credits.rtf; 
sourceTree = "<group>"; };
                CE3D8DA9125E69BB00AE0232 /* de */ = {isa = PBXFileReference; 
fileEncoding = 10; lastKnownFileType = text.plist.strings; name = de; path = 
de.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -3171,6 +3175,8 @@
                                F94DB0F70B3E2FA1006F37A2 /* BDSKSearchGroup.m 
*/,
                                F92EF32109E6242100A244D0 /* BDSKSharedGroup.m 
*/,
                                CEFDBDBC0AEA86BA009EE99D /* BDSKSmartGroup.m */,
+                               CE3D27C62DBB85AA002EA644 /* 
BDSKSRUGroupServer.h */,
+                               CE3D27C72DBB85AA002EA644 /* 
BDSKSRUGroupServer.m */,
                                CEFDBDC40AEA86F0009EE99D /* BDSKStaticGroup.m 
*/,
                                F9F5ECD00AE5E7C8007EBB31 /* BDSKURLGroup.m */,
                                4575382B0B70170D00C0E49B /* BDSKWebGroup.m */,
@@ -3762,6 +3768,7 @@
                                CE2A09F9224599E100A8F31C /* 
BDSKFileContentSearchController.h in Headers */,
                                CE1C94E926A9CDFB00EF17E8 /* 
BDSKGroupTextFieldCell.h in Headers */,
                                CE2A0A46224599F600A8F31C /* 
BDSKPreferenceWindow.h in Headers */,
+                               CE3D27C82DBB85AA002EA644 /* 
BDSKSRUGroupServer.h in Headers */,
                                CE2A0A9722459A3600A8F31C /* BDSKTypeTemplate.h 
in Headers */,
                                CE2A0A3A224599F600A8F31C /* BDSKOwnerProtocol.h 
in Headers */,
                                CE2A0A4C224599F600A8F31C /* 
BDSKPubMedXMLParser.h in Headers */,
@@ -5022,6 +5029,7 @@
                                CE2F056911B517E9001B0AE0 /* 
BDSKOpenAccessoryViewController.m in Sources */,
                                CEFEF0ED11C23F1F0050350D /* 
BDSKCompletionServer.m in Sources */,
                                CEFE21321259E6CB00DAD553 /* 
BDSKSearchGroupServerManager.m in Sources */,
+                               CE3D27C92DBB85AA002EA644 /* 
BDSKSRUGroupServer.m in Sources */,
                                CE044A4512667EB500CE55C4 /* 
BDSKDownloadManager.m in Sources */,
                                CEF7F42712743BCC00B20881 /* BDSKWebView.m in 
Sources */,
                                CE03C0CB127D751D00F62F51 /* 
BDSKWebViewModalDialogController.m in Sources */,

This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.



_______________________________________________
Bibdesk-commit mailing list
Bibdesk-commit@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bibdesk-commit

Reply via email to