mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-25 17:51:01 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@13525 72102866-910b-0410-8b05-ffd578937521
553 lines
14 KiB
Objective-C
553 lines
14 KiB
Objective-C
/* StringsFile
|
|
|
|
Copyright (C) 2002 Free Software Foundation, Inc.
|
|
|
|
Written by: Alexander Malmberg <alexander@malmberg.org>
|
|
Created: 2002
|
|
|
|
This file is part of the GNUstep Project
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
You should have received a copy of the GNU General Public
|
|
License along with this program; see the file COPYING.LIB.
|
|
If not, write to the Free Software Foundation,
|
|
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include <Foundation/NSObject.h>
|
|
#include <Foundation/NSString.h>
|
|
#include <Foundation/NSArray.h>
|
|
#include <Foundation/NSFileManager.h>
|
|
#include <Foundation/NSDate.h>
|
|
|
|
#include "StringsFile.h"
|
|
|
|
#include "StringsEntry.h"
|
|
#include "SourceEntry.h"
|
|
|
|
#include "make_strings.h"
|
|
|
|
|
|
static NSString *parse_string(NSString **ptr)
|
|
{
|
|
NSString *str=*ptr;
|
|
NSString *ret;
|
|
int i,c;
|
|
unichar ch;
|
|
|
|
c=[str length];
|
|
for (i=0;i<c;i++)
|
|
{
|
|
ch=[str characterAtIndex: i];
|
|
if (ch=='\\')
|
|
{
|
|
if (i==c)
|
|
{
|
|
fprintf(stderr,"parse error, \\ without second character\n");
|
|
exit(1);
|
|
}
|
|
i++;
|
|
}
|
|
if (ch=='\"')
|
|
break;
|
|
}
|
|
if (i==c)
|
|
{
|
|
fprintf(stderr,"parse error, unterminated string\n");
|
|
exit(1);
|
|
}
|
|
ret=[str substringToIndex: i];
|
|
str=[str substringFromIndex: i+1];
|
|
*ptr=str;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
#define DUMMY @"<dummy>"
|
|
#define DUMMY2 @"<dummy2>"
|
|
|
|
@implementation StringsFile
|
|
|
|
- init
|
|
{
|
|
self=[super init];
|
|
strings=[[NSMutableArray alloc] init];
|
|
return self;
|
|
}
|
|
|
|
-(void) dealloc
|
|
{
|
|
DESTROY(global_comment);
|
|
DESTROY(strings);
|
|
[super dealloc];
|
|
}
|
|
|
|
- initWithFile: (NSString *)filename
|
|
{
|
|
NSString *str;
|
|
|
|
self=[self init];
|
|
|
|
str=[NSString stringWithContentsOfFile: filename];
|
|
if (!str) return self;
|
|
|
|
{
|
|
StringsEntry *se=nil;
|
|
NSMutableArray *update_list=[[NSMutableArray alloc] init];
|
|
NSArray *lines;
|
|
NSString *l;
|
|
int i,c,pos;
|
|
NSMutableDictionary *dummy_entries=[[NSMutableDictionary alloc] init];
|
|
|
|
NSMutableString *user_comment=[[NSMutableString alloc] init];
|
|
|
|
NSString *key,*trans;
|
|
|
|
/* this is a bit yucky, but it works */
|
|
lines=[str componentsSeparatedByString: @"/*"];
|
|
c=[lines count];
|
|
for (i=0;i<c;i++)
|
|
{
|
|
l=[lines objectAtIndex: i];
|
|
/* First entry has everything before the first comment and needs
|
|
to be handled specially. */
|
|
if (i)
|
|
{
|
|
/* Parse special comments. */
|
|
if ([l hasPrefix: @"**"])
|
|
{ /* It's one of our banners, just ignore it. If it's the first
|
|
one we put all comments so far in the global user comment. */
|
|
if (user_comment)
|
|
{
|
|
if (![user_comment isEqual: @""])
|
|
global_comment=[user_comment copy];
|
|
DESTROY(user_comment);
|
|
}
|
|
}
|
|
else if ([l hasPrefix: @" File: "])
|
|
{
|
|
se=[[StringsEntry alloc] init];
|
|
[se addFlag: FLAG_UNMATCHED];
|
|
[update_list addObject: se];
|
|
[se release];
|
|
|
|
l=[l substringFromIndex: 7];
|
|
pos=[l rangeOfString: @":"].location;
|
|
[se setFile: [l substringToIndex: pos]];
|
|
l=[l substringFromIndex: pos+1];
|
|
[se setLine: [l intValue]];
|
|
}
|
|
else if ([l hasPrefix: @" Flag: untranslated */"])
|
|
[se addFlag: FLAG_UNTRANSLATED];
|
|
else if ([l hasPrefix: @" Flag: unmatched */"])
|
|
[se addFlag: FLAG_UNMATCHED]; /* this is essentially a noop */
|
|
else if ([l hasPrefix: @" Comment: "])
|
|
{
|
|
l=[l substringFromIndex: 10];
|
|
pos=[l rangeOfString: @" */"].location;
|
|
[se setComment: [l substringToIndex: pos]];
|
|
}
|
|
else
|
|
{
|
|
pos=[l rangeOfString: @"*/"].location;
|
|
if ([user_comment length])
|
|
[user_comment appendString: @"\n"];
|
|
[user_comment appendString: [l substringToIndex: pos]];
|
|
}
|
|
|
|
pos=[l rangeOfString: @"*/"].location;
|
|
if (pos==NSNotFound) continue;
|
|
l=[l substringFromIndex: pos+2];
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
pos=[l rangeOfString: @"\""].location;
|
|
if (pos==NSNotFound) break;
|
|
|
|
l=[l substringFromIndex: pos+1];
|
|
key=parse_string(&l);
|
|
|
|
pos=[l rangeOfString: @"="].location;
|
|
if (pos==NSNotFound)
|
|
{
|
|
fprintf(stderr,"parse error in '%s', expecting '='\n",[filename cString]);
|
|
exit(1);
|
|
}
|
|
l=[l substringFromIndex: pos+1];
|
|
pos=[l rangeOfString: @"\""].location;
|
|
if (pos==NSNotFound)
|
|
{
|
|
fprintf(stderr,"parse error in '%s', expecting second string\n",[filename cString]);
|
|
exit(1);
|
|
}
|
|
l=[l substringFromIndex: pos+1];
|
|
trans=parse_string(&l);
|
|
|
|
pos=[l rangeOfString: @";"].location;
|
|
if (pos==NSNotFound)
|
|
{
|
|
fprintf(stderr,"parse error in '%s', expecting ';'\n",[filename cString]);
|
|
exit(1);
|
|
}
|
|
l=[l substringFromIndex: pos+1];
|
|
|
|
if (![update_list count])
|
|
{ /* we're probably parsing a file not created by us */
|
|
if (![dummy_entries objectForKey: key])
|
|
{
|
|
se=[[StringsEntry alloc] init];
|
|
[se setFile: DUMMY];
|
|
[se setFlags: FLAG_UNMATCHED];
|
|
[update_list addObject: se];
|
|
[se release];
|
|
[dummy_entries setObject: se forKey: key];
|
|
}
|
|
}
|
|
|
|
[update_list makeObjectsPerformSelector: @selector(setKey:) withObject: key];
|
|
[update_list makeObjectsPerformSelector: @selector(setTranslated:) withObject: trans];
|
|
/* {
|
|
int i,c=[update_list count];
|
|
for (i=0;i<c;i++)
|
|
{
|
|
printf("%4i : %@\n",i,[update_list objectAtIndex: i]);
|
|
}
|
|
}*/
|
|
|
|
[strings addObjectsFromArray: update_list];
|
|
|
|
[update_list removeAllObjects];
|
|
se=nil;
|
|
}
|
|
}
|
|
|
|
DESTROY(user_comment);
|
|
DESTROY(dummy_entries);
|
|
DESTROY(update_list);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
|
|
-(void) _writeTo: (NSMutableString *)str entryHead: (StringsEntry *)se
|
|
{
|
|
if ([se file])
|
|
{
|
|
[str appendString: @"/* File: "];
|
|
[str appendString: [se file]];
|
|
[str appendString: [NSString stringWithFormat: @":%i */\n",[se line]]];
|
|
}
|
|
if ([se comment])
|
|
{
|
|
[str appendString: @"/* Comment: "];
|
|
[str appendString: [se comment]];
|
|
[str appendString: @" */\n"];
|
|
}
|
|
}
|
|
|
|
-(void) _writeTo: (NSMutableString *)str entryFlags: (StringsEntry *)se
|
|
{
|
|
int flags=[se flags];
|
|
if (!flags) return;
|
|
if (flags&FLAG_UNMATCHED)
|
|
[str appendString: @"/* Flag: unmatched */\n"];
|
|
else
|
|
if (flags&FLAG_UNTRANSLATED)
|
|
[str appendString: @"/* Flag: untranslated */\n"];
|
|
else
|
|
{
|
|
fprintf(stderr,"unknown flag %08x\n",flags);
|
|
}
|
|
}
|
|
|
|
-(void) _writeTo: (NSMutableString *)str entryKey: (StringsEntry *)se
|
|
{
|
|
[str appendString: @"\""];
|
|
[str appendString: [se key]];
|
|
|
|
if ([[se key] length]+[[se translated] length]<70)
|
|
[str appendString: @"\" = \""];
|
|
else
|
|
[str appendString: @"\"\n= \""];
|
|
|
|
[str appendString: [se translated]];
|
|
[str appendString: @"\";\n"];
|
|
}
|
|
|
|
|
|
-(void) _writeTo: (NSMutableString *)str manyEntries: (NSMutableArray *)list
|
|
{
|
|
int i,c;
|
|
StringsEntry *tr,*cur;
|
|
|
|
[list sortUsingSelector: @selector(compareFileLine:)];
|
|
c=[list count];
|
|
if (!c) return;
|
|
cur=tr=nil;
|
|
for (i=0;i<c;i++)
|
|
{
|
|
cur=[list objectAtIndex: i];
|
|
[self _writeTo: str entryHead: cur];
|
|
if ([cur flags])
|
|
[self _writeTo: str entryFlags: cur];
|
|
|
|
if (!([cur flags]&FLAG_UNTRANSLATED))
|
|
tr=cur;
|
|
}
|
|
if (tr)
|
|
[self _writeTo: str entryKey: tr];
|
|
else
|
|
[self _writeTo: str entryKey: cur];
|
|
}
|
|
|
|
|
|
-(BOOL) writeToFile: (NSString *)filename
|
|
{
|
|
int i,c;
|
|
BOOL result;
|
|
NSMutableString *str=[[NSMutableString alloc] initWithCapacity: 32*1024];
|
|
StringsEntry *se;
|
|
|
|
NSMutableArray *strings_left=[strings mutableCopy];
|
|
NSMutableArray *str_list=[[NSMutableArray alloc] init];
|
|
NSMutableArray *dup_list=[[NSMutableArray alloc] init];
|
|
NSMutableArray *un_list=[[NSMutableArray alloc] init];
|
|
|
|
StringsEntry *cur,*c2;
|
|
|
|
int single_file,wrote_banner,unflags;
|
|
int un_count=0;
|
|
|
|
if (global_comment && ![global_comment isEqual: @""])
|
|
{
|
|
[str appendString: @"/*"];
|
|
[str appendString: global_comment];
|
|
[str appendString: @"*/\n\n"];
|
|
}
|
|
|
|
[str appendString:
|
|
[NSString stringWithFormat:
|
|
@"/***\n"
|
|
@"%@\n"
|
|
@"updated by make_strings %@\n"
|
|
@"add comments above this one\n"
|
|
@"***/\n",
|
|
filename,[NSDate dateWithTimeIntervalSinceNow: 0]]];
|
|
|
|
wrote_banner=0;
|
|
|
|
/* First, output all keys that appear in multiple places (unless all
|
|
appearances are in one file and none are marked unmatched or untranslated).
|
|
Collect unmatched or untranslated single entries in un_list and matched
|
|
translated (single/multiple in one file) entries in str_list. */
|
|
while ([strings_left count])
|
|
{
|
|
cur=[strings_left objectAtIndex: 0];
|
|
if (([cur flags]&(FLAG_UNMATCHED|FLAG_UNTRANSLATED))==
|
|
(FLAG_UNMATCHED|FLAG_UNTRANSLATED)
|
|
|| (aggressive_import && [[cur file] isEqual: DUMMY2]))
|
|
{ /* ignore strings that are unmatched _and_ untranslated */
|
|
[strings_left removeObjectAtIndex: 0];
|
|
continue;
|
|
}
|
|
[dup_list addObject: cur];
|
|
[strings_left removeObjectAtIndex: 0];
|
|
|
|
single_file=1;
|
|
unflags=[cur flags];
|
|
for (i=0;i<[strings_left count];i++)
|
|
{
|
|
c2=[strings_left objectAtIndex: i];
|
|
|
|
if (([c2 flags]&(FLAG_UNMATCHED|FLAG_UNTRANSLATED))==
|
|
(FLAG_UNMATCHED|FLAG_UNTRANSLATED)
|
|
|| (aggressive_import && [[cur file] isEqual: DUMMY2]))
|
|
{ /* ignore strings that are unmatched _and_ untranslated */
|
|
[strings_left removeObjectAtIndex: i];
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
if ([[cur key] isEqual: [c2 key]])
|
|
{
|
|
unflags|=[c2 flags];
|
|
[dup_list addObject: c2];
|
|
[strings_left removeObjectAtIndex: i];
|
|
if (single_file)
|
|
if (![[cur file] isEqual: [c2 file]])
|
|
single_file=0;
|
|
i--;
|
|
}
|
|
}
|
|
if (single_file && !unflags)
|
|
{
|
|
[str_list addObjectsFromArray: dup_list];
|
|
[dup_list removeAllObjects];
|
|
continue;
|
|
}
|
|
if ([dup_list count]==1)
|
|
{
|
|
[un_list addObjectsFromArray: dup_list];
|
|
[dup_list removeAllObjects];
|
|
continue;
|
|
}
|
|
|
|
if (unflags)
|
|
un_count+=[dup_list count];
|
|
|
|
if (!wrote_banner)
|
|
{
|
|
[str appendString: @"\n\n/*** Keys found in multiple places ***/\n"];
|
|
wrote_banner=1;
|
|
}
|
|
|
|
[str appendString: @"\n"];
|
|
[self _writeTo: str manyEntries: dup_list];
|
|
[dup_list removeAllObjects];
|
|
}
|
|
|
|
DESTROY(strings_left);
|
|
|
|
/* Now output all single unmatched or untranslated entries. Order by line
|
|
and file so key changes and movements are easy to spot and fix. */
|
|
if ([un_list count])
|
|
{
|
|
[str appendString: @"\n\n/*** Unmatched/untranslated keys ***/\n"];
|
|
[un_list sortUsingSelector: @selector(compareFileLine:)];
|
|
c=[un_list count];
|
|
un_count+=c;
|
|
for (i=0;i<c;i++)
|
|
{
|
|
se=[un_list objectAtIndex: i];
|
|
[str appendString: @"\n"];
|
|
[self _writeTo: str entryHead: se];
|
|
[self _writeTo: str entryFlags: se];
|
|
[self _writeTo: str entryKey: se];
|
|
}
|
|
}
|
|
|
|
/* Finally, output all matched and translated entries ordered by file. The
|
|
translator should never have to touch these strings (unless there are typos
|
|
or something). */
|
|
if ([str_list count])
|
|
{
|
|
NSString *last_filename=nil;
|
|
|
|
[str_list sortUsingSelector: @selector(compareFileKeyComment:)];
|
|
c=[str_list count];
|
|
for (i=0;i<c;i++)
|
|
{
|
|
se=[str_list objectAtIndex: i];
|
|
if (!last_filename || ![last_filename isEqual: [se file]])
|
|
{
|
|
last_filename=[se file];
|
|
[str appendString:
|
|
[NSString stringWithFormat: @"\n\n/*** Strings from %@ ***/\n",
|
|
last_filename]];
|
|
}
|
|
[self _writeTo: str entryHead: se];
|
|
if (i==c-1 || ![[se key] isEqual: [[str_list objectAtIndex: i+1] key]])
|
|
[self _writeTo: str entryKey: se];
|
|
}
|
|
}
|
|
DESTROY(str_list);
|
|
DESTROY(dup_list);
|
|
|
|
{
|
|
NSString *backupname=[filename stringByAppendingString: @"~"];
|
|
[[NSFileManager defaultManager] removeFileAtPath: backupname handler: nil];
|
|
[[NSFileManager defaultManager] movePath: filename toPath: backupname handler: nil];
|
|
result=[str writeToFile: filename atomically: YES];
|
|
|
|
if (!result)
|
|
fprintf(stderr,"Error saving '%s'!\n",[filename cString]);
|
|
}
|
|
|
|
DESTROY(str);
|
|
DESTROY(un_list);
|
|
|
|
if (un_count)
|
|
fprintf(stderr,"'%s': %i untranslated or unmatched messages\n",[filename cString],un_count);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
-(void) addSourceEntry: (SourceEntry *)e
|
|
{
|
|
/* First try to find a match among our unmatched strings. We consider
|
|
two entries to match if they have the same key, file and comment. This
|
|
could be extended, but the risk of errors increases. */
|
|
int i,c;
|
|
StringsEntry *se;
|
|
|
|
c=[strings count];
|
|
|
|
/* Look for exact matches. If we find an exact match (same file, key, and
|
|
comment) we mark the StringsEntry matched and don't add the SourceEntry.
|
|
*/
|
|
for (i=0;i<c;i++)
|
|
{
|
|
se=[strings objectAtIndex: i];
|
|
if (!([se flags]&FLAG_UNMATCHED))
|
|
continue;
|
|
|
|
if (![[se key] isEqual: [e key]])
|
|
continue;
|
|
|
|
if (([se flags]&FLAG_UNMATCHED) && [[se file] isEqual: [e file]])
|
|
{
|
|
if ((![se comment] && ![e comment]) ||
|
|
([[se comment] isEqual: [e comment]]))
|
|
{
|
|
[se setFlags: [se flags]&~FLAG_UNMATCHED];
|
|
[se setLine: [e line]];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aggressive_match)
|
|
{
|
|
/* If aggressive match is enabled, we try to find an existing and
|
|
translated StringsEntry. If we find we add a new StringsEntry from
|
|
the SourceEntry with the same translation and marked as translated.
|
|
*/
|
|
for (i=0;i<c;i++)
|
|
{
|
|
se=[strings objectAtIndex: i];
|
|
|
|
if ([se flags]&FLAG_UNTRANSLATED)
|
|
continue;
|
|
|
|
if (![[se key] isEqual: [e key]])
|
|
continue;
|
|
|
|
{
|
|
StringsEntry *se2=[StringsEntry stringsEntryFromSourceEntry: e];
|
|
[se2 setFlags: 0];
|
|
[se2 setTranslated: [se translated]];
|
|
[strings addObject: se2];
|
|
|
|
/* If aggressive_import is enabled and the one we matched is a
|
|
dummy we change the name to DUMMY2 so we can ignore it later. */
|
|
if (aggressive_import && [[se file] isEqual: DUMMY])
|
|
[se setFile: DUMMY2];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* No match, add a new, untranslated StringsEntry. */
|
|
[strings addObject: [StringsEntry stringsEntryFromSourceEntry: e]];
|
|
}
|
|
|
|
@end
|
|
|