5 // Copyright (c) 2005 A. Karl Keller (http://karlk.net)
7 // This code is open-source, free software, made available without warranty under
8 // the terms of the GNU General Public License, either version 2 or later (see
9 // http://www.gnu.org/licenses/gpl.html or included copy); as such, it may be
10 // redistributed and/or modified in accordance with that document.
14 #import "RTKRevision.h"
15 #import "RTKMutableArrayCategory.h"
16 #import "RTKArrayCategory.h"
17 #import "RTKStringCategory.h"
18 #import "RTKMutableAttributedStringCategory.h"
20 #import "Chomp/Chomp.h"
22 @implementation RTKVerse
26 if(self = [super init]) {
27 revisions = [[NSMutableArray alloc] init];
28 [self setReference:@""];
29 [self setPreUSFMType:@""];
30 [self setType:@"\\v"];
32 [revisions addObject:[[RTKRevision alloc] init]];
33 currentRevisionIndex = 0;
35 [self setDictionary:[NSDictionary dictionary]];
40 - (id)initWithDictionary:(NSDictionary *)dict
42 if(self = [self init]) {
43 [self setRevisions: (NSMutableArray *)
44 [[RTKRevision collectSelf] revisionWithDictionary:
45 [[dict objectForKey:@"revisions"] each]]];
47 [self setReference:[dict objectForKey:@"reference"]];
49 [self setType:[dict objectForKey:@"usfmType"]];
51 [self setType:[dict objectForKey:@"type"]];
53 [self setPreUSFMType:[dict objectForKey:@"type"]];
55 [self setType:@"\\v"];
57 [self setCurrentRevisionIndex:[[dict objectForKey:@"currentRevisionIndex"] intValue]];
58 [self setLocked:[[dict objectForKey:@"locked"] intValue]];
60 [self setDictionary:dict];
65 + (RTKVerse *)verseWithDictionary:dict
67 return [[[RTKVerse alloc] initWithDictionary:dict] autorelease];
82 RTKVerse * copy = [[[RTKVerse alloc] init] autorelease];
84 // Should be safe, since NSString is immutable.
85 [copy setReference:reference];
86 [copy setCurrentRevisionIndex:currentRevisionIndex];
87 [copy setRevisions:[revisions deepCopy]];
88 [copy setLocked:locked];
91 [copy setDictionary:dictionary];
97 - (NSMutableDictionary *)dictionaryRepresentation
99 NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithDictionary:dictionary];
101 NSArray * revisionsArray = (NSArray *) [[revisions collect] dictionaryRepresentation];
103 [dict setObject:revisionsArray
104 forKey:@"revisions"];
106 [dict setObject:reference
107 forKey:@"reference"];
109 [dict setObject:preUSFMType
115 [dict setObject:[NSNumber numberWithInt:currentRevisionIndex]
116 forKey:@"currentRevisionIndex"];
118 [dict setObject:[NSNumber numberWithInt:locked] forKey:@"locked"];
123 - (id)initWithString:(NSString *)string
125 if(self = [self init]) {
126 [revisions removeAllObjects];
127 [revisions addObject:[RTKRevision revisionWithString:string]];
128 [self setCurrentRevisionIndex:0];
133 + (RTKVerse *)verseWithString:string
135 return [[[RTKVerse alloc] initWithString:string] autorelease];
138 - (id)initWithSFMString:(NSString *)string
139 andStateDict:(NSMutableDictionary *)dict
141 if(self = [self init]) {
142 NSMutableArray * strings = [[string componentsSeparatedByString:@" "] mutableCopy];
143 int stringCount = [strings count];
145 // Discard leading 0x20 space characters.
146 while(stringCount > 0 && [[strings objectAtIndex:0] length] == 0) {
147 [strings removeObjectAtIndex:0];
151 NSString * formatMarker = (stringCount > 0 ? [strings objectAtIndex:0] : @"");
152 NSString * formatMarkerData = (stringCount > 1 ? [strings objectAtIndex:1] : @"");
154 if([formatMarker isEqualToString:@"\\id"]) {
155 [dict setObject:formatMarkerData forKey:@"book"];
156 [dict setObject:@"1" forKey:@"chapter"];
157 [strings removeObjectsInRange:NSMakeRange(0,2)];
158 } else if([formatMarker isEqualToString:@"\\c"]) {
159 [dict setObject:formatMarkerData forKey:@"chapter"];
160 [dict setObject:@"1" forKey:@"verse"];
161 [strings removeObjectsInRange:NSMakeRange(0,2)];
162 } else if([formatMarker isEqualToString:@"\\v"]) {
163 [dict setObject:formatMarkerData forKey:@"verse"];
164 [strings removeObjectsInRange:NSMakeRange(0,2)];
165 } else if([formatMarker isEqualToString:@"\\mt"]) {
166 if([[dict objectForKey:@"preceding format marker"] isEqualToString:@"\\v"])
167 [dict setObject:[NSString stringWithFormat:@"%i",
168 [[[[dict objectForKey:@"verse"] componentsSeparatedByString:@"-"] lastObject] intValue] + 1] forKey:@"verse"];
169 if([formatMarkerData intValue] > 0) { // A numbered major title size.
170 formatMarker = [NSString stringWithFormat:@"%@ %@", formatMarker, formatMarkerData];
171 [strings removeObjectAtIndex:0];
173 [strings removeObjectAtIndex:0];
175 if([[dict objectForKey:@"preceding format marker"] isEqualToString:@"\\v"])
176 [dict setObject:[NSString stringWithFormat:@"%i",
177 [[[[dict objectForKey:@"verse"] componentsSeparatedByString:@"-"] lastObject] intValue] + 1] forKey:@"verse"];
178 [strings removeObjectAtIndex:0];
181 [self setType:formatMarker];
182 [self setReference:[NSString stringWithFormat:@"%@ %@:%@",
183 [dict objectForKey:@"book"],
184 [dict objectForKey:@"chapter"],
185 [dict objectForKey:@"verse"]]];
186 string = [strings componentsJoinedByString:@" "];
188 [revisions removeAllObjects];
189 [revisions addObject:[RTKRevision revisionWithSFMString:string]];
190 [self setCurrentRevisionIndex:0];
192 [dict setObject:formatMarker forKey:@"preceding format marker"];
197 + (RTKVerse *)verseWithSFMString:string
200 return [[[RTKVerse alloc] initWithSFMString:string
201 andStateDict:dict] autorelease];
204 - (void)updateTypeFieldToUSFM
206 [self setPreUSFMType:type];
208 if([type isEqualToString:@"Text"]) {
210 } else if([type isEqualToString:@"Paragraph Break"]) {
212 } else if([type isEqualToString:@"Section Title"]) {
214 } else if([type isEqualToString:@"Major Title"]) {
216 } else if([type isEqualToString:@"Chapter Number"]) {
221 - (void)appendLine:(NSString *)line
223 [(RTKRevision *)[self currentRevision] appendLine:line];
228 NSMutableString * string = [NSMutableString new];
230 RTKRevision * revision = [self currentRevision];
232 NSString * outputReference = nil;
233 if([type isEqualToString:@"\\v"]) {
234 outputReference = [NSString stringWithFormat:@"(%@) ", [reference verse]];
235 } else if([type isEqualToString:@"\\c"]) {
236 outputReference = [NSString stringWithFormat:@"(%@) ", reference];;
239 [string appendString:
240 [revision stringWithVerseNumber:outputReference]];
245 - (NSString *)sfmString
247 RTKRevision * revision = [self currentRevision];
249 NSString * marker = [[type componentsSeparatedByString:@" "] objectAtIndex:0];
251 if ([marker isEqualToString:@"\\v"]) {
252 return [NSString stringWithFormat:@"%@ %@ %@", type, [reference verse], [revision roman]];
253 } else if ([marker isEqualToString:@"\\c"]) {
254 return [NSString stringWithFormat:@"%@ %@", type, [reference chapter]];
255 } else if ([marker isEqualToString:@"\\mt"]) {
256 return [NSString stringWithFormat:@"%@ %@", type, [revision roman]];
257 } else if ([marker isEqualToString:@"\\id"]) {
258 return [NSString stringWithFormat:@"%@ %@ %@", type, [reference book], [revision roman]];
260 return [NSString stringWithFormat:@"%@ %@", type, [revision roman]];
263 // TODO: Check for retain problems. Should be autoreleasing more stuff here.
264 - (NSMutableAttributedString *)mutableAttributedString:(BOOL)romanString
266 RTKRevision * revision = [self currentRevision];
267 NSMutableArray * strings = [[[NSMutableArray alloc] init] autorelease];
270 NSMutableAttributedString *newLineString = [[[NSMutableAttributedString alloc] initWithString:@"\n"] autorelease];
271 [newLineString normalFontSize];
273 NSMutableAttributedString *spaceString = [[[NSMutableAttributedString alloc] initWithString:@" "] autorelease];
274 [spaceString normalFontSize];
277 if([type isEqualToString:@"\\v"]) {
278 string = [[NSMutableAttributedString alloc] initWithString:[reference verse]];
280 [string addAttribute:@"RTKVerseComponent" value:@"Verse Reference"];
282 [string superscript];
283 [string smallFontSize];
285 [strings addObject:string];
286 [strings addObject:spaceString];
288 string = [revision mutableAttributedString:romanString];
289 [string addAttribute:@"RTKVerseComponent" value:@"Verse Text"];
290 [string normalFontSize];
292 [strings addObject:string];
293 [strings addObject:spaceString];
295 } else if([type isEqualToString:@"\\p"]) {
296 [strings addObject:newLineString]; [strings addObject:newLineString];
297 } else if([type isEqualToString:@"\\s1"]) {
298 string = [revision mutableAttributedString:romanString];
299 [string addAttribute:@"RTKVerseComponent" value:@"Verse Text"];
300 [string largeFontSize];
301 [strings addObject:newLineString]; [strings addObject:newLineString];
302 [strings addObject:string];
303 [strings addObject:newLineString]; [strings addObject:newLineString];
304 } else if([type isEqualToString:@"\\mt1"]) {
305 string = [revision mutableAttributedString:romanString];
306 [string addAttribute:@"RTKVerseComponent" value:@"Verse Text"];
307 [string largeFontSize];
308 [strings addObject:newLineString];
309 [strings addObject:string];
310 [strings addObject:newLineString];
311 } else if([type isEqualToString:@"\\c"]) {
312 string = [[NSMutableAttributedString alloc] initWithString:[reference chapter]];
313 [string addAttribute:@"RTKVerseComponent" value:@"Chapter Reference"];
315 [string largeFontSize];
316 [strings addObject:newLineString];
317 [strings addObject:string];
318 [strings addObject:newLineString];
320 NSMutableAttributedString * outputString = [strings mutableAttributedStringFromComponents];
321 [outputString addAttribute:@"RTKVerse" value:self];
325 - (BOOL)updateWithAttributedString:(NSAttributedString *)string
326 atIndex:(NSUInteger)index
328 NSRange componentRange;
329 NSString *component = [string attribute:@"RTKVerseComponent"
331 longestEffectiveRange:&componentRange
332 inRange:NSMakeRange(0, [string length])];
334 if(!component) // If it didn't find the component, check one character back.
335 component = [string attribute:@"RTKVerseComponent"
337 longestEffectiveRange:&componentRange
338 inRange:NSMakeRange(0, [string length])];
339 NSAttributedString *componentString = [string attributedSubstringFromRange:componentRange];
341 if([component isEqualToString:@"Verse Text"]) {
342 [[self currentRevision] setRoman:[componentString string]];
344 NSLog(@"%@", [componentString string]);
346 return YES; // Verse accepted change.
348 return NO; // Verse rejected change.
352 - (void)setDictionary:(NSDictionary *)theDictionary
354 [theDictionary retain];
355 [dictionary release];
356 dictionary = theDictionary;
359 - (NSDictionary *)dictionary
364 - (void)setReference:(NSString *)theReference
366 [theReference retain];
368 reference = theReference;
370 [[NSNotificationCenter defaultCenter] postNotificationName:@"RTKVerseSetReference" object:self userInfo:nil];
373 - (NSString *)reference
378 - (void)setPreUSFMType:(NSString *)theType
381 [preUSFMType release];
382 preUSFMType = theType;
384 [[NSNotificationCenter defaultCenter] postNotificationName:@"RTKVerseSetPreUSFMType" object:self userInfo:nil];
387 - (NSString *)preUSFMType
392 - (void)setType:(NSString *)theType
398 [[NSNotificationCenter defaultCenter] postNotificationName:@"RTKVerseSetType" object:self userInfo:nil];
406 - (void)setLocked:(BOOL)state
410 [[NSNotificationCenter defaultCenter] postNotificationName:@"RTKVerseSetLocked" object:self userInfo:nil];
418 #pragma mark - revision management
420 - (void)setRevisions:(NSMutableArray *)theRevisions
422 [theRevisions retain];
424 revisions = theRevisions;
426 [[NSNotificationCenter defaultCenter] postNotificationName:@"RTKVerseSetRevisions" object:self userInfo:nil];
429 - (NSMutableArray *)revisions
436 return [revisions count];
439 - (void)setCurrentRevisionIndex:(int)index
441 currentRevisionIndex = index;
443 [[NSNotificationCenter defaultCenter] postNotificationName:@"RTKVerseSetCurrentRevisionIndex" object:self userInfo:nil];
446 - (int)currentRevisionIndex
448 return currentRevisionIndex;
451 // Just a convenience method.
452 - (RTKRevision *)currentRevision
454 if([revisions count])
455 return [revisions objectAtIndex:currentRevisionIndex];
460 // Another convenience method
463 NSEnumerator * e = [revisions objectEnumerator];
464 RTKRevision * revision;
465 while(revision = [e nextObject]) {
466 if(![revision blank])
469 if(![type isEqualToString:@"\\v"])
476 - (BOOL)matchesString:(NSString *)string
478 if([reference containsCaseInsensitiveSubstring:string])
480 if([type containsCaseInsensitiveSubstring:string])
483 NSEnumerator * e = [revisions objectEnumerator];
484 RTKRevision * revision;
485 while(revision = [e nextObject]) {
486 if([revision matchesString:string])