I'm trying to repair some mis-numbered movie subtitle files (each sub is separated by a blank line). The following code scans up to the faulty subtitle index number in a test file. If I just 'printf' the faulty old indices and replacement new indices, everything appears just as expected.
//######################################################################
-(IBAction)scanToSubIndex:(id)sender
{
NSMutableString* tempString = [[NSMutableString alloc] initWithString:[theTextView string]];
int textLen = (int)[tempString length];
NSScanner *theScanner = [NSScanner scannerWithString:tempString];
while ([theScanner isAtEnd] == NO)
{
[theScanner scanUpToString:@"\r\n\r\n" intoString:NULL];
[theScanner scanString:@"\r\n\r\n" intoString:NULL];
if([theScanner scanLocation] >= textLen)
break;
else
{ // remove OLD subtitle index...
NSString *oldNumStr;
[theScanner scanUpToString:@"\r\n" intoString:&oldNumStr];
printf("old number:%s\n", [oldNumStr UTF8String]);
NSRange range = [tempString rangeOfString:oldNumStr];
[tempString deleteCharactersInRange:range];
// ...and insert SEQUENTIAL index
NSString *newNumStr = [self changeSubIndex];
printf("new number:%s\n\n", [newNumStr UTF8String]);
[tempString insertString:newNumStr atIndex:range.location];
}
}
printf("\ntempString\n\n:%s\n", [tempString UTF8String]);
}
//######################################################################
-(NSString*)changeSubIndex
{
static int newIndex = 1;
// convert int to string and return...
NSString *numString = [NSString stringWithFormat:@"%d", newIndex];
++newIndex;
return numString;
}
When I attempt to write the new indices to the mute string however, I end up with disordered results like this:
sub 1
sub 2
sub 3
sub 1
sub 5
sub 6
sub 7
sub 5
sub 9
sub 7
sub 8
An interesting observation (and possible clue?) is that when I reach subtitle number 1000, every number gets written to the mutable string in sequential order as required. I've been struggling with this for a couple of weeks now, and I can't find any other similar questions on SO. Any help much appreciated :-)
NSMutableString
is a subclass of NSString
. In other words, you can pass NSMutableString
at places where the NSString
is expected. But it doesn't mean you're allowed to modify it.
scannerWithString:
expects NSString
. Translated to human language - I expect a string and I also do expect that the string is read-only (wont be modified).
In other words - your code is considered to be a programmer error - you give something to the NSScanner
, NSScanner
expects immutable string and you're modifying it.
We don't know what the NSScanner
class is doing under the hood. There can be buffering or any other kind of optimization.
Even if you will be lucky with the mentioned scanLocation
fix (in the comments), you shouldn't rely on it, because the under the hood implementation can change with any new release.
Don't do this. Not just here, but everywhere where you see immutable data type.
(There're situations where you can do it, but then you should really know what the under the hood implementation is doing, be certain that it wont be modified, etc. But generally speaking, it's not a good idea unless you know what you're doing.)
This sample code is based on the following assumptions:
@"\r\n"
)@import Foundation;
NS_ASSUME_NONNULL_BEGIN
@interface SubRipText : NSObject
+ (NSString *)fixSubtitleIndexes:(NSString *)string;
@end
NS_ASSUME_NONNULL_END
@implementation SubRipText
+ (NSString *)fixSubtitleIndexes:(NSString *)string {
NSMutableString *result = [@"" mutableCopy];
__block BOOL nextLineIsIndex = YES;
__block NSUInteger index = 1;
[string enumerateLinesUsingBlock:^(NSString * _Nonnull line, BOOL * _Nonnull stop) {
if (nextLineIsIndex) {
[result appendFormat:@"%lu\r\n", (unsigned long)index];
index++;
nextLineIsIndex = NO;
return;
}
[result appendFormat:@"%@\r\n", line];
nextLineIsIndex = line.length == 0;
}];
return result;
}
@end
Usage:
NSString *test = @"29\r\n"
"00:00:00,498 --> 00:00:02,827\r\n"
"Hallo\r\n"
"\r\n"
"4023\r\n"
"00:00:02,827 --> 00:00:06,383\r\n"
"This is two lines,\r\n"
"subtitles rocks!\r\n"
"\r\n"
"1234\r\n"
"00:00:06,383 --> 00:00:09,427\r\n"
"Maybe not,\r\n"
"just learn English :)\r\n";
NSString *result = [SubRipText fixSubtitleIndexes:test];
NSLog(@"%@", result);
Output:
1
00:00:00,498 --> 00:00:02,827
Hallo
2
00:00:02,827 --> 00:00:06,383
This is two lines,
subtitles rocks!
3
00:00:06,383 --> 00:00:09,427
Maybe not,
just learn English :)
There're other ways how to achieve this, but you should think about readability, speed of writing, speed of running, ... Depends on your usage - how many of them are you going to fix, etc.