Ignore:
Timestamp:
11/24/07 09:10:41 (16 years ago)
Author:
Nicholas Riley
Message:

Pester.xcodeproj: Add Perl embedding bits; remove SoundFileManager.h.

DynaLoader.a: i386/ppc version from Tiger; Leopard's version causes
Tiger to crash.

Info-Pester.plist: Updated copyright date.

Read Me.rtfd: Remove .typeAttributes.dict, no longer used; update a
bit.

PSTimeDateEditor.m: Switch to NJRDateFormatters again.

NJRDateFormatter.[hm]: Removed old-style date formatter workarounds;
added code for using Date::Manip and trying multiple ICU-based date
formatters.

English.lproj/InfoPlist.strings: Updated copyright date.

English.lproj/MainMenu.nib: Modified date completion menu for items
Date::Manip can parse.

Manip.pm: Date::Manip 5.47, converted to UTF-8.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Cocoa/Pester/Source/NJRDateFormatter.m

    r49 r360  
    99#import "NJRDateFormatter.h"
    1010
    11 NSUserDefaults *locale;
    12 
    13 @implementation NJRDateFormatter
    14 
    15 + (void)initialize;
    16 {
    17     locale = [[NSUserDefaults standardUserDefaults] retain];
    18 }
    19 
    20 + (NSString *)format:(NSString *)format withoutComponent:(unichar)component;
    21 {
    22     NSScanner *scanner = [NSScanner scannerWithString: format];
    23     int formatLength = [format length];
    24     NSRange range;
    25     [scanner setCharactersToBeSkipped: [NSCharacterSet characterSetWithCharactersInString: @""]];
    26     // NSLog(@"format:withoutComponent: trying to excise %c from %@", component, format);
    27     while ([scanner scanUpToString: @"%" intoString: nil] || ![scanner isAtEnd]) {
    28         range.location = [scanner scanLocation];
    29         // NSLog(@"location: %d/%d, remaining: %@%@", range.location, formatLength, [format substringFromIndex: range.location], [scanner isAtEnd] ? @", isAtEnd" : @"");
    30         // XXX works fine without keeping track of length in 10.1.5; in 10.2, [scanner scanUptoString:intoString:] still returns YES even when scanner is at end and thereÕs nothing left to scan, and if you start accessing the string past the end... *boom*
    31         if (range.location >= formatLength) break;
    32         [scanner scanUpToCharactersFromSet: [NSCharacterSet letterCharacterSet] intoString: nil];
    33         if ([format characterAtIndex: [scanner scanLocation]] == component) {
    34             if ([scanner scanUpToString: @"%" intoString: nil] && ![scanner isAtEnd]) {
    35                 NSMutableString *mutableFormat = [format mutableCopy];
    36                 if (range.location != 0 && [[NSCharacterSet punctuationCharacterSet] characterIsMember: [format characterAtIndex: range.location - 1]]) {
    37                     range.location--; // "%I:%M:%S%p" -> "%I:%M%p"
    38                 }
    39                 range.length = [scanner scanLocation] - range.location;
    40                 [mutableFormat deleteCharactersInRange: range];
    41                 format = [mutableFormat copy];
    42                 [mutableFormat release];
    43                 return [format autorelease];
    44             } else {
    45                 range = [format rangeOfCharacterFromSet: [NSCharacterSet letterCharacterSet] options: NSBackwardsSearch range: NSMakeRange(0, range.location)];
    46                 return [format substringToIndex: NSMaxRange(range)];
    47             }
    48         }
    49     }
    50     return format;
    51 }
    52 
    53 + (NSString *)localizedDateFormatIncludingWeekday:(BOOL)weekday;
    54 {
    55     NSString *format = [locale stringForKey: NSDateFormatString];
    56     if (weekday) return format;
    57     return [self format: format withoutComponent: (unichar)'A'];
    58 }
    59 
    60 + (NSString *)localizedShortDateFormatIncludingWeekday:(BOOL)weekday;
    61 {
    62     NSString *format = [locale stringForKey: NSShortDateFormatString];
    63     if (weekday) return format;
    64     return [self format: format withoutComponent: (unichar)'A'];
    65 }
    66 
    67 NSString *stringByInsertingStringAtLocation(NSString *string, NSString *insert, int location) {
    68     return [NSString stringWithFormat: @"%@%@%@", [string substringToIndex: location], insert,
    69         [string substringFromIndex: location]];
    70 }
    71 
    72 + (NSString *)localizedTimeFormatIncludingSeconds:(BOOL)seconds;
    73 {
    74     NSString *format = [locale stringForKey: NSTimeFormatString];
    75     NSArray *ampm = [locale arrayForKey: NSAMPMDesignation];
    76     NSString *am = [ampm objectAtIndex: 0], *pm = [ampm objectAtIndex: 1];
    77     // work around bug with inconsistent AM/PM and time format
    78     if ([am isEqualToString: @""] && [pm isEqualToString: @""])
    79         format = [self format: format withoutComponent: 'p'];
    80     else {
    81         NSRange ampmComponentRange = [format rangeOfString: @"%p"];
    82         NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet];
    83         BOOL needSpaceInFormatString = ![whitespace characterIsMember: [am characterAtIndex: 0]];
    84         if (ampmComponentRange.location == NSNotFound) // "%1I:%M:%S" -> "%1I:%M:%S%p", "%1I:%M:%S %p"
    85             format = [format stringByAppendingString: (needSpaceInFormatString ? @" %p" : @"%p")];
    86         else {
    87             NSRange whitespaceRange = [format rangeOfCharacterFromSet: whitespace options: NSBackwardsSearch range: NSMakeRange(0, ampmComponentRange.location)];
    88             if (whitespaceRange.location == NSNotFound) {
    89                 if (needSpaceInFormatString) // "%1I:%M:%S%p" -> "%1I:%M:%S %p"
    90                     format = stringByInsertingStringAtLocation(format, @" ", ampmComponentRange.location);
    91                 // else "%1I:%M:%S%p" -> no change
    92             } else {
    93                 if (NSMaxRange(whitespaceRange) == ampmComponentRange.location) {
    94                     if (!needSpaceInFormatString) // "%1I:%M:%S %p" -> "%1I:%M:%S%p"
    95                         format = [[format substringToIndex: whitespaceRange.location] stringByAppendingString: [format substringFromIndex: ampmComponentRange.location]];
    96                     // else "%1I:%M:%S %p" -> no change
    97                 } else {
    98                     if (needSpaceInFormatString)
    99                         format = stringByInsertingStringAtLocation(format, @" ", ampmComponentRange.location);
    100                     // else "%1I %M:%S%p" -> no change
    101                 }
    102             }
    103         }
    104     }
    105     if (seconds) return format;
    106     return [self format: format withoutComponent: 'S'];
     11// generated by perl -MExtUtils::Embed -e xsinit -- -o perlxsi.c
     12#include <EXTERN.h>
     13#include <perl.h>
     14
     15EXTERN_C void xs_init (pTHX);
     16
     17EXTERN_C void boot_DynaLoader (pTHX_ CV* cv);
     18
     19EXTERN_C void
     20xs_init(pTHX)
     21{
     22    char *file = __FILE__;
     23    dXSUB_SYS;
     24   
     25    /* DynaLoader is a special case */
     26    newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
     27}
     28// end generated code
     29
     30static PerlInterpreter *my_perl;
     31static NSDateFormatter *dateManipFormatter;
     32
     33static NSDate *parse_natural_language_date(NSString *input) {
     34    if (my_perl == NULL) return nil;
     35   
     36    if ([input rangeOfString: @"|"].length > 0) {
     37        NSMutableString *sanitized = [[input mutableCopy] autorelease];
     38        [sanitized replaceOccurrencesOfString: @"|" withString: @""
     39                                      options: NSLiteralSearch
     40                                        range: NSMakeRange(0, [sanitized length])];
     41        input = sanitized;
     42    }
     43   
     44    NSString *temp = [[NSString alloc] initWithFormat: @"UnixDate(q|%@|, '%%q')", input];
     45    NSLog(@"%@", temp);
     46    SV *d = eval_pv([temp UTF8String], TRUE);
     47    [temp release];
     48    if (d == NULL) return nil;
     49   
     50    STRLEN s_len;
     51    char *s = SvPV(d, s_len);
     52    if (s == NULL || s_len == 0) return nil;
     53   
     54    NSDate *date = [dateManipFormatter dateFromString: [NSString stringWithUTF8String: s]];
     55    NSLog(@"%@", date);
     56   
     57    return date;
     58}
     59
     60static void init_perl(void) {
     61    const char *argv[] = {"", "-CSD", "-I", "", "-MDate::Manip", "-e", "0"};
     62    argv[3] = [[[NSBundle mainBundle] resourcePath] fileSystemRepresentation];
     63    PERL_SYS_INIT(0, NULL);
     64    my_perl = perl_alloc();
     65    if (my_perl == NULL) return;
     66   
     67    perl_construct(my_perl);
     68    if (perl_parse(my_perl, xs_init, 7, (char **)argv, NULL) != 0) goto fail;
     69   
     70    PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
     71    if (perl_run(my_perl) != 0) goto fail;
     72   
     73    // XXX detect localization changes
     74    eval_pv("Date_Init(\"Language=English\", \"DateFormat=non-US\", \"Internal=1\"", TRUE);
     75
     76    if (parse_natural_language_date(@"tomorrow") == nil) goto fail;
     77   
     78    return;
     79   
     80fail:
     81    perl_destruct(my_perl);
     82    perl_free(my_perl);
     83    PERL_SYS_TERM();
     84    my_perl = NULL;
    10785}
    10886
    10987// workaround for bug in Jaguar (and earlier?) NSCalendarDate dateWithNaturalLanguageString:
    110 NSString * stringByRemovingSurroundingWhitespace(NSString *string) {
     88NSString *stringByRemovingSurroundingWhitespace(NSString *string) {
    11189    static NSCharacterSet *nonWhitespace = nil;
    11290    NSRange firstValidCharacter, lastValidCharacter;
    113 
     91   
    11492    if (!nonWhitespace) {
    11593        nonWhitespace = [[[NSCharacterSet characterSetWithCharactersInString:
    116             @" \t\r\n"] invertedSet] retain];
    117     }
    118 
     94                           @" \t\r\n"] invertedSet] retain];
     95    }
     96   
    11997    firstValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace];
    12098    if (firstValidCharacter.length == 0)
    12199        return @"";
    122100    lastValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace options: NSBackwardsSearch];
    123 
     101   
    124102    if (firstValidCharacter.location == 0 && lastValidCharacter.location == [string length] - 1)
    125103        return string;
     
    128106}
    129107
    130 - (id)initWithDateFormat:(NSString *)format allowNaturalLanguage:(BOOL)flag;
    131 {
    132     if ( (self = [super initWithDateFormat: format allowNaturalLanguage: flag]) != nil) {
    133         NSRange ampmRange = [format rangeOfString: @"%p"];
    134         NSArray *ampm = [locale arrayForKey: NSAMPMDesignation];
    135         NSString *am = [ampm objectAtIndex: 0], *pm = [ampm objectAtIndex: 1];
    136         if (flag && ampmRange.location != NSNotFound &&
    137             [[locale stringForKey: NSTimeFormatString] rangeOfString: @"%p"].location == NSNotFound && ![am isEqualToString: pm]) {
    138             // workaround for bug in NSCalendarDate dateWithNaturalLanguageString: discarding AM/PM value when AM/PM designations have spaces in them (of which the use thereof is a workaround for NSDateFormatter discarding the AM/PM value)
    139             NSMutableString *paddedFormat = [format mutableCopy];
    140             [paddedFormat replaceCharactersInRange: ampmRange withString: @" %p"];
    141             alteredLocale = [[locale dictionaryRepresentation] mutableCopy];
    142             [(NSMutableDictionary *)alteredLocale setObject: paddedFormat forKey: NSTimeFormatString];
    143             [(NSMutableDictionary *)alteredLocale setObject:
    144                 [NSArray arrayWithObjects: stringByRemovingSurroundingWhitespace(am),
    145                     stringByRemovingSurroundingWhitespace(pm), nil]
    146                                                      forKey: NSAMPMDesignation];
    147             [paddedFormat release];
    148         } else {
    149             alteredLocale = [(NSDictionary *)locale retain];
    150         }
    151     }
    152     return self;
     108static const NSDateFormatterStyle formatterStyles[] = {
     109    NSDateFormatterShortStyle,
     110    NSDateFormatterMediumStyle,
     111    NSDateFormatterLongStyle,
     112    NSDateFormatterFullStyle,
     113    NSDateFormatterNoStyle
     114};
     115
     116@implementation NJRDateFormatter
     117
     118#pragma mark initialize-release
     119
     120+ (void)initialize;
     121{
     122    dateManipFormatter = [[NSDateFormatter alloc] init];
     123    [dateManipFormatter setDateFormat: @"yyyyMMddHHmmss"]; // Date::Manip's "%q"
     124    init_perl();
     125}
     126
     127+ (NJRDateFormatter *)dateFormatter;
     128{
     129    NJRDateFormatter *formatter = [[self alloc] init];
     130    NSMutableArray *tryFormatters = [[NSMutableArray alloc] init];
     131   
     132    for (NSDateFormatterStyle *s = formatterStyles ; *s < NSDateFormatterNoStyle ; *s++) {
     133        NSDateFormatter *tryFormatter = [[NSDateFormatter alloc] init];
     134        [tryFormatter setLenient: YES];
     135        [tryFormatter setTimeStyle: NSDateFormatterNoStyle];
     136        [tryFormatter setDateStyle: *s];
     137        [tryFormatters addObject: tryFormatter];
     138        [tryFormatter release];
     139    }
     140    // XXX do this in init
     141    formatter->tryFormatters = tryFormatters;
     142
     143    return [formatter autorelease];
     144}
     145
     146+ (NJRDateFormatter *)timeFormatter;
     147{
     148    NJRDateFormatter *formatter = [[self alloc] init];
     149    NSMutableArray *tryFormatters = [[NSMutableArray alloc] init];
     150   
     151    for (NSDateFormatterStyle *s = formatterStyles ; *s < NSDateFormatterNoStyle ; *s++) {
     152        NSDateFormatter *tryFormatter = [[NSDateFormatter alloc] init];
     153        [tryFormatter setLenient: YES];
     154        [tryFormatter setTimeStyle: *s];
     155        [tryFormatter setDateStyle: NSDateFormatterNoStyle];
     156        [tryFormatters addObject: tryFormatter];
     157        [tryFormatter release];
     158    }
     159    formatter->tryFormatters = tryFormatters;
     160   
     161    return [formatter autorelease];
    153162}
    154163
    155164- (void)dealloc;
    156165{
    157     [alteredLocale release];
     166    [tryFormatters release]; tryFormatters = nil;
    158167    [super dealloc];
    159168}
    160169
     170#pragma mark primitive formatter
     171
     172- (NSString *)stringForObjectValue:(id)obj;
     173{
     174    return [super stringForObjectValue: obj];
     175}
     176
     177- (NSAttributedString *)attributedStringForObjectValue:(id)obj withDefaultAttributes:(NSDictionary *)attrs;
     178{
     179    return [super attributedStringForObjectValue: obj
     180                           withDefaultAttributes: attrs];
     181}
     182
    161183- (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error
    162184{
    163     NSCalendarDate *date;
    164     if (![self allowsNaturalLanguage])
    165         return [super getObjectValue: anObject forString: string errorDescription: error];
    166     if (string == nil) return nil;
    167     NS_DURING // dateWithNaturalLanguageString: can throw an exception
    168         date = [NSCalendarDate dateWithNaturalLanguageString: stringByRemovingSurroundingWhitespace(string) locale: alteredLocale];
    169         // NSLog(@"%@: natural language date is %@", string, date);
    170     NS_HANDLER
    171         if (error != nil) *error = [localException reason];
    172         NS_VALUERETURN(NO, BOOL);
    173     NS_ENDHANDLER
    174     // [super getObjectValue: anObject forString: string errorDescription: error];
    175     // NSLog(@"%@: formatter date is %@", string, anObject == nil ? @"(null)" : *anObject);
    176     if (date == nil) return [super getObjectValue: anObject forString: string errorDescription: error];
     185    if ([super getObjectValue: anObject forString: string errorDescription: error])
     186        return YES;
     187       
     188    NSDate *date;
     189    NSEnumerator *e = [tryFormatters objectEnumerator];
     190    NSDateFormatter *tryFormatter;
     191   
     192    // XXX untested; does this work?
     193    while ( (tryFormatter = [e nextObject]) != nil) {
     194        date = [tryFormatter dateFromString: string];
     195        if (date != nil) goto success;
     196    }
     197   
     198    date = parse_natural_language_date(string);
     199    if (date != nil) goto success;
     200   
     201    return NO;
     202
     203success:
    177204    *anObject = date;
     205    if (error != NULL) *error = nil;
    178206    return YES;
    179207}
    180208
     209#pragma mark miscellaneous
     210
     211+ (BOOL)naturalLanguageParsingAvailable;
     212{
     213    return (my_perl != NULL);
     214}
    181215@end
Note: See TracChangeset for help on using the changeset viewer.