POConverter.m
上传用户:center1979
上传日期:2022-07-26
资源大小:50633k
文件大小:20k
源码类别:

OpenGL

开发平台:

Visual C++

  1. /*
  2.  * POConverter.m
  3.  * 
  4.  * Created by Da Woon Jung on Wed March 09 2005.
  5.  * Copyright (c) 2005 dwj. All rights reserved.
  6.  *
  7.  * This program is free software; you can redistribute it and/or
  8.  * modify it under the terms of the GNU General Public License
  9.  * as published by the Free Software Foundation; either version 2
  10.  * of the License, or (at your option) any later version.
  11.  *
  12.  * This program is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15.  * GNU General Public License for more details.
  16.  *
  17.  * You should have received a copy of the GNU General Public License
  18.  * along with this program; if not, write to the Free Software
  19.  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  20.  */
  21. #import <Foundation/Foundation.h>
  22. #define POCONV_DEFAULT_STRINGS_FILENAME    @"po.strings"
  23. #define POCONV_DEBUG_LEVEL                 1
  24. BOOL gIgnoreFuzzy;  /*! -n cmdline arg, YES = ignore fuzzy entries */
  25. int gDebugLevel;    /*! -v cmdline arg, 0   = no console messages */
  26. NSAutoreleasePool *gPool;
  27. NSCharacterSet *gNewLineSet;
  28. NSCharacterSet *gDelimitSet;
  29. /*! Returns NO if line is a comment. range and result are modified. */
  30. static BOOL readLine(NSString *string, NSRange *range, NSString **outString)
  31. {
  32.     // Using -lineRangeForRange: because it reliably detects all sorts of line endings
  33.     NSRange lineRange = [string lineRangeForRange: *range];
  34.     NSString *lineString = (NSNotFound != lineRange.location && lineRange.length > 0) ? [string substringWithRange: lineRange] : nil;
  35.     range->location = NSMaxRange(lineRange);
  36.     range->length = 0;
  37.     *outString = lineString;
  38.     // Detect comment lines
  39.     return lineString ? ![lineString hasPrefix: @"#"] : YES;
  40. }
  41. /*! Scans data (from a po file) in ascii mode and gets the charset specification */
  42. static NSStringEncoding getCharset(NSData *data)
  43. {
  44.     NSString *string = [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
  45.     if (nil == string)
  46.         return 0;
  47.     CFStringEncoding enc = kCFStringEncodingUTF8;
  48.     NSString *line = nil;
  49.     NSString *encName = nil;
  50.     NSRange encRange;
  51.     NSRange range = NSMakeRange(0, 0);
  52.     BOOL notComment = YES;
  53.     while (YES)
  54.     {
  55.         notComment = readLine(string, &range, &line);
  56.         if (nil == line)
  57.             break;
  58.         if (notComment)
  59.         {
  60.             encRange = [line rangeOfString: @"Content-Type:"];
  61.             if (NSNotFound != encRange.location && encRange.length > 0)
  62.             {
  63.                 encRange = [line rangeOfString: @"charset="];
  64.                 if (NSNotFound != encRange.location && encRange.length > 0)
  65.                 {
  66.                     encName = [line substringWithRange: NSMakeRange(NSMaxRange(encRange), [line length] - NSMaxRange(encRange))];
  67.                     if (encName)
  68.                     {
  69.                         encRange = [encName rangeOfString:@"\n"" options:NSBackwardsSearch];
  70.                         if (NSNotFound != encRange.location && encRange.location > 0 && encRange.length > 0)
  71.                             encName = [encName substringToIndex: encRange.location];
  72.                         // Some special cases
  73.                         if (NSOrderedSame == [encName caseInsensitiveCompare:@"gbk"])
  74.                             enc = kCFStringEncodingGBK_95;
  75.                         else if (NSOrderedSame == [encName caseInsensitiveCompare:@"ISO_8859-15"])
  76.                             enc = kCFStringEncodingISOLatin9;
  77.                         else
  78.                             enc = CFStringConvertIANACharSetNameToEncoding((CFStringRef)encName);
  79.                     }
  80.                     if (gDebugLevel)
  81.                         CFShow(line);
  82.                     break;
  83.                 }
  84.             }
  85.         }
  86.     }
  87.     return (kCFStringEncodingInvalidId == enc) ? NSUTF8StringEncoding : CFStringConvertEncodingToNSStringEncoding(enc);
  88. }
  89. static NSString *extractStringFromLine(NSString *line)
  90. {
  91.     NSMutableString *result = [NSMutableString stringWithCapacity: [line length]];
  92.     NSString *aString;
  93.     NSScanner *scanner = [NSScanner scannerWithString: line];
  94.     NSRange escRange;
  95.     NSMutableString *escString = [NSMutableString string];
  96.     while (![scanner isAtEnd])
  97.     {
  98.         aString = nil;
  99.         [scanner scanUpToString:@""" intoString: nil];
  100.         if ([scanner scanString:@""" intoString: nil])
  101.         {
  102.             while (YES)
  103.             {
  104.                 // Make sure we don't skip leading spaces (but skip newlines)
  105.                 NSCharacterSet *origSkip = [scanner charactersToBeSkipped];
  106.                 [scanner setCharactersToBeSkipped: gNewLineSet];
  107.                 [scanner scanUpToCharactersFromSet: gDelimitSet intoString: &aString];
  108.                 // Remove shorcut key ampersands
  109.                 // TODO: Recognize longer "(&x)" format used in
  110.                 //       non-roman localizations
  111.                 while ([scanner scanString: @"&" intoString: nil])
  112.                 {
  113.                     if (aString)
  114.                         [escString appendString: aString];
  115.                     [scanner scanUpToCharactersFromSet: gDelimitSet intoString: &aString];
  116.                 }
  117. //                [scanner scanUpToString:@""" intoString: &aString];
  118.                 [scanner setCharactersToBeSkipped: origSkip];
  119.                 if (aString)
  120.                 {
  121.                     [escString appendString: aString];
  122.                     escRange = [aString rangeOfString:@"\" options:(NSBackwardsSearch|NSAnchoredSearch)];
  123.                     if (NSNotFound != escRange.location && escRange.length > 0)
  124.                     {
  125.                         [scanner scanString:@""" intoString: &aString];
  126.                         [escString appendString: aString];
  127.                         aString = nil;
  128.                         continue;
  129.                     }
  130.                 }
  131.                 aString = nil;
  132.                 break;
  133.             }
  134.             if ([scanner scanString:@""" intoString: nil])
  135.             {
  136.                 if (escString)
  137.                     [result appendString: escString];
  138.             }
  139.         }
  140.         else
  141.             break;
  142.     }
  143.     return result;
  144. }
  145. /*! Get all parts of a quoted string (possibly broken across multiple lines) and returns the concatenated result. */
  146. static NSString *getQuotedString(NSString *fileString, NSRange *range, NSString *line)
  147. {
  148.     NSMutableString *result = [NSMutableString stringWithString:extractStringFromLine(line)];
  149.     NSString *aString;
  150.     NSRange origRange;
  151.     // Loop until no more string parts found
  152.     BOOL notComment = YES;
  153.     while (YES)
  154.     {
  155.         origRange = *range;
  156.         notComment = readLine(fileString, range, &line);
  157.         if (nil == line || ([line length] == 0))
  158.             break;
  159.         if (!notComment || [line hasPrefix: @"msgid"] || [line hasPrefix: @"msgstr"])
  160.         {
  161.             // Back up the cursor
  162.             *range = origRange;
  163.             break;
  164.         }
  165.         aString = extractStringFromLine(line);
  166.         [result appendString: aString];
  167.     }
  168.     return ([result length] > 0) ? result : nil;
  169. }
  170. /*! Takes any comment line. If it is a flag line (starts with #, ) and contains the "fuzzy" flag, returns YES */
  171. static BOOL isFuzzy(NSString *line)
  172. {
  173.     NSRange flagRange = [line rangeOfString:@"#," options:NSAnchoredSearch];
  174.     if (NSNotFound != flagRange.location && flagRange.length > 0)
  175.     {
  176.         // Parse comma-delimited list of flags
  177.         NSString *aString = [line substringWithRange: NSMakeRange(NSMaxRange(flagRange), [line length] - NSMaxRange(flagRange))];
  178.         if (aString)
  179.         {
  180.             NSArray *flags;
  181.             NSEnumerator *flagEnum;
  182.             NSScanner *scanner;
  183.             flags = [aString componentsSeparatedByString: @","];
  184.             flagEnum = [flags objectEnumerator];
  185.             while ((aString = [flagEnum nextObject]))
  186.             {
  187.                 scanner = [NSScanner scannerWithString: aString];
  188.                 if ([scanner scanString:@"fuzzy" intoString: nil])
  189.                 {
  190.                     // Check if fuzzy is the entire word
  191.                     if ([scanner isAtEnd])
  192.                         return YES;
  193.                 }
  194.             }
  195.         }
  196.     }
  197.     return NO;
  198. }
  199. #ifdef TARGET_CELESTIA
  200. /*! Celestia: Returns YES if the line contains source file entries */
  201. static BOOL isSource(NSString *line, BOOL *containsNonKde)
  202. {
  203.     BOOL isSource = NO;
  204.     NSRange range = [line rangeOfString:@"#:" options:NSAnchoredSearch];
  205.     if (NSNotFound != range.location && range.length > 0)
  206.     {
  207.         BOOL nonKdeFound = NO;
  208.         BOOL kdeFound = NO;
  209.         isSource = YES;
  210.         NSString *aString = [line substringWithRange: NSMakeRange(NSMaxRange(range), [line length] - NSMaxRange(range))];
  211.         if (aString)
  212.         {
  213.             NSArray *sources;
  214.             NSEnumerator *sourceEnum;
  215.             sources = [aString componentsSeparatedByString: @" "];
  216.             sourceEnum = [sources objectEnumerator];
  217.             while ((aString = [sourceEnum nextObject]))
  218.             {
  219.                 if (0 == [aString length])
  220.                     continue;
  221.                 range = [aString rangeOfString: @"kde"];
  222.                 if (NSNotFound == range.location)
  223.                 {
  224.                     nonKdeFound = YES;
  225.                     break;
  226.                 }
  227.                 else if (range.length > 0)
  228.                     kdeFound = YES;
  229.             }
  230.         }
  231.         if (nonKdeFound || !kdeFound)
  232.             *containsNonKde = YES;
  233.     }
  234.     return isSource;
  235. }
  236. #endif TARGET_CELESTIA
  237. /*! Does all the work of reading, converting, and writing. Returns the number of lines successfully converted. */
  238. static long convertData(NSData *data, NSStringEncoding encoding, NSFileHandle *ofh)
  239. {
  240.     long numConverted = 0;
  241.     NSString *fileString = [[[NSString alloc] initWithData:data encoding:encoding] autorelease];
  242.     if (nil == fileString)
  243.         return 0;
  244.     BOOL notComment = YES;
  245.     NSString *line;
  246.     NSString **aString;
  247.     NSRange range = NSMakeRange(0, 0);
  248.     long fileStringLength = [fileString length];
  249.     BOOL acceptEntry = YES;
  250. #ifdef TARGET_CELESTIA
  251.     BOOL parsingSources = NO;
  252.     BOOL containsNonKde = NO;
  253. #endif
  254.     NSString *msgid = nil;
  255.     NSString *msgstr = nil;
  256.     while (range.location < fileStringLength)
  257.     {
  258.         notComment = readLine(fileString, &range, &line);
  259.         if (nil == line)
  260.             break;
  261.         if (notComment)
  262.         {
  263.             aString = nil;
  264.             if ([line hasPrefix: @"msgid"])
  265.                 aString = &msgid;
  266.             else if ([line hasPrefix: @"msgstr"])
  267.             {
  268.                 if (nil == msgid)
  269.                 {
  270.                     if (gDebugLevel>2)
  271.                         CFShow(@"Ignoring msgstr without msgid (probably the po header)");
  272.                     continue;
  273.                 }
  274.                 aString = &msgstr;
  275.             }
  276.             else
  277.             {
  278.                 // Blank line cancels fuzzy flag
  279.                 acceptEntry = YES;
  280.             }
  281. #ifdef TARGET_CELESTIA
  282. //            if (parsingSources)
  283. //                acceptEntry = containsNonKde;
  284.             acceptEntry    = YES;
  285.             parsingSources = NO;
  286.             containsNonKde = NO;
  287. #endif
  288.             if (aString)
  289.             {
  290.                 *aString = getQuotedString(fileString, &range, line);
  291.                 if (*aString == msgstr && msgid)
  292.                 {
  293.                     if (nil == *aString)
  294.                     {
  295.                         if (gDebugLevel>1)
  296.                             CFShow((CFStringRef)[NSString stringWithFormat:@"Ignoring msgid "%@" without msgstr", msgid]);
  297.                         continue;
  298.                     }
  299.                     if (acceptEntry)
  300.                     {
  301.                         NSString *ofhString = [NSString stringWithFormat:@""%@" = "%@";n", msgid, msgstr];
  302.                         if (ofhString)
  303.                         {
  304.                             NSData *ofhData;
  305.                             if (numConverted > 0)
  306.                             {
  307.                                 size_t ofhBufSize = [ofhString length]*sizeof(unichar);
  308.                                 unichar *ofhBuf = malloc(ofhBufSize);
  309.                                 [ofhString getCharacters: ofhBuf];  // gets utf-16 chars
  310.                                 ofhData = [NSData dataWithBytesNoCopy:ofhBuf length:ofhBufSize];
  311.                             }
  312.                             else
  313.                             {
  314.                                 // Before overwriting an existing file, have to zero it
  315.                                 if (ofh)
  316.                                     [ofh truncateFileAtOffset: 0];
  317.                                 // Output bom for first entry
  318.                                 ofhData = [ofhString dataUsingEncoding:NSUnicodeStringEncoding];
  319.                             }
  320.                             
  321.                             [ofh writeData:ofhData];
  322.                         }
  323.                         else if (gDebugLevel)
  324.                             CFShow(@"***Couldn't write data***");
  325.                         ++numConverted;
  326.                         if (gDebugLevel>2 && *aString)
  327.                             CFShow(*aString);
  328.                     }
  329.                     else if (gDebugLevel)
  330.                         CFShow((CFStringRef)[NSString stringWithFormat:@"Ignoring entry "%@"", *aString]);
  331.                     msgid = nil;
  332.                     msgstr = nil;
  333.                     acceptEntry = YES;
  334.                 }
  335.             }
  336.         }
  337.         else
  338.         {
  339. #ifdef TARGET_CELESTIA
  340.             // Celestia: filter out kde entries
  341.             // This looks complicated, but is needed because
  342.             // source entries (#:) may span multiple lines
  343.             if (!parsingSources)
  344.             {
  345.                 containsNonKde = NO;
  346.                 parsingSources = isSource(line, &containsNonKde);
  347.             }
  348.             else
  349.             {
  350.                 BOOL tempKdeCheck = NO;
  351.                 if (isSource(line, &tempKdeCheck))
  352.                     containsNonKde = containsNonKde || tempKdeCheck;
  353.             }
  354. #endif
  355.             // Filter out fuzzy entries if specified by the caller
  356.             if (gIgnoreFuzzy && isFuzzy(line))
  357.                 acceptEntry = NO;
  358.         }
  359.     }
  360.     return numConverted;
  361. }
  362. static void usage()
  363. {
  364.     printf("Poconverter converts a GNU gettext po file to Mac OS-nativen"
  365.            "UTF-16 Localized.strings format.n"
  366.            "n"
  367.            "Usage: Poconverter [-nv] file.po [destination filename]n"
  368.            "n"
  369.            "  -n        Ignore entries tagged with fuzzy flagn"
  370.            "  -s        Silent mode (existing file overwritten without warning)n"
  371.            "  -v        Verbose output on stderrn"
  372.            "n");
  373. }
  374. static void ctrl_c_handler(int sig)
  375. {
  376.     fprintf(stderr, "nGot Ctrl-C, cleaning up...n");
  377.     [gPool release];
  378.     exit(1);
  379. }
  380. int main(int argc, const char **argv)
  381. {
  382.     NSAutoreleasePool *gPool = [[NSAutoreleasePool alloc] init];
  383.     // Catch ctrl-c so we can release the autorelease pool
  384.     signal(SIGINT, ctrl_c_handler);
  385.     gIgnoreFuzzy = NO;
  386.     gDebugLevel = 0;
  387.     BOOL isSilent = NO;
  388.     BOOL isErr = NO;
  389.     NSProcessInfo *theProcess = [NSProcessInfo processInfo];
  390.     NSArray *args = [theProcess arguments];
  391.     NSString *arg;
  392.     NSEnumerator *argEnum = [args objectEnumerator];
  393.     [argEnum nextObject];   // advance past args[0] (the program full path)
  394.     NSString *poFilename = nil;
  395.     NSString *resultFilename = nil;
  396.     NSFileHandle *ofh;
  397.     do
  398.     {
  399.         if (argc < 2 || argc > 5)
  400.         {
  401.             usage(); break;
  402.         }
  403.         // Parse options
  404.         long j;
  405.         while ((arg = [argEnum nextObject]) && [arg hasPrefix: @"-"])
  406.         {
  407.             if ([arg length] > 1)
  408.             {
  409.                 for (j = 1; j < [arg length]; ++j)
  410.                 {
  411.                     switch ([arg characterAtIndex: j])
  412.                     {
  413.                         case 'n':
  414.                             gIgnoreFuzzy = YES; break;
  415.                         case 's':
  416.                             isSilent = YES; break;
  417.                         case 'v':
  418.                             gDebugLevel = POCONV_DEBUG_LEVEL; break;
  419.                         default:
  420.                             isErr = YES;
  421.                     }
  422.                     if (isErr)
  423.                         break;
  424.                 }
  425.             }
  426.             else
  427.             {
  428.                 isErr = YES;
  429.                 break;
  430.             }
  431.         }
  432.         if (isErr)
  433.         {
  434.             usage(); break;
  435.         }
  436.         
  437.         // Can't just have options, need filename(s) too
  438.         if (nil == arg)//i == argc)
  439.         {
  440.             usage(); break;
  441.         }
  442.         poFilename = arg;
  443.         // Check if po file exists and isn't a directory
  444.         BOOL isDirectory;
  445.         BOOL fileExists;
  446.         NSFileManager *fm = [NSFileManager defaultManager];
  447.         if (![fm fileExistsAtPath:poFilename isDirectory:&isDirectory] || isDirectory)
  448.         {
  449.             usage(); break;
  450.         }
  451.         // Destination path - can be either a filename or a directory
  452.         // If unspecified, defaults to stdout
  453.         resultFilename = [argEnum nextObject];
  454.         if (resultFilename)
  455.         {
  456.             if ([argEnum nextObject])
  457.             {
  458.                 // Too many filenames
  459.                 usage(); break;
  460.             }
  461.             // If directory, append default filename
  462.             fileExists = [fm fileExistsAtPath:resultFilename isDirectory:&isDirectory];
  463.             if (fileExists && isDirectory)
  464.             {
  465.                 resultFilename = [resultFilename stringByAppendingPathComponent:POCONV_DEFAULT_STRINGS_FILENAME];
  466.                 fileExists = [fm fileExistsAtPath: resultFilename];
  467.             }
  468.             if (!fileExists)
  469.             {
  470.                 [fm createFileAtPath:resultFilename contents:nil attributes:nil];
  471.             }
  472.             if(!isSilent && fileExists)
  473.             {
  474.                 printf("nDestination file exists, overwrite (y/n)? [y] ");
  475.                 int confirm = getchar();
  476.                 fflush(stdout);
  477.                 switch (confirm)
  478.                 {
  479.                     case 'y':
  480.                     case 'Y':
  481.                     case 'n':
  482.                         break;
  483.                     default:
  484.                         printf("Destination file not overwritten.nn");
  485.                         [gPool release];
  486.                         exit(0);
  487.                 }
  488.             }
  489.             ofh = [NSFileHandle fileHandleForWritingAtPath: resultFilename];
  490.             // Delay truncating file and wiping it out until we actually
  491.             // get something to write to
  492.         }
  493.         else
  494.         {
  495.             // Output filename not specified, use stdout
  496.             ofh = [NSFileHandle fileHandleWithStandardOutput];
  497.         }
  498.         if (nil == ofh)
  499.         {
  500.             CFShow(@"***Could not write output. Please check if directory exists, and you have permissions.***");
  501.             [gPool release];
  502.             exit(1);
  503.         }
  504.         if (gDebugLevel)
  505.             CFShow((CFStringRef)[NSString stringWithFormat:@"poFile='%@', result='%@', ignoreFuzzy=%d, debugLevel=%d, silent=%d", poFilename, (resultFilename ? resultFilename : @"stdout"), gIgnoreFuzzy, gDebugLevel, isSilent]);
  506.         NSData *data = [NSData dataWithContentsOfMappedFile: poFilename];
  507.         NSMutableCharacterSet *newLineSet = [[[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy] autorelease];
  508.         [newLineSet formIntersectionWithCharacterSet:
  509.             [[NSCharacterSet whitespaceCharacterSet] invertedSet]];
  510.         gNewLineSet = newLineSet;
  511.         gDelimitSet = [NSCharacterSet characterSetWithCharactersInString: @"&""];
  512.         // First detect the file encoding
  513.         NSStringEncoding encoding = getCharset(data);
  514.         if (gDebugLevel)
  515.             CFShow((CFStringRef)[NSString stringWithFormat:@"NSStringEncoding = %#lx", encoding]);
  516.         // Reread the file again, in the correct encoding
  517.         long numConverted = 0;
  518.         if (encoding)
  519.         {
  520.             numConverted = convertData(data, encoding, ofh);
  521.         }
  522.         if (gDebugLevel)
  523.             CFShow((CFStringRef)[NSString stringWithFormat:@"%ld entries converted.", numConverted]);
  524.     } while (NO);
  525.     [gPool release];
  526.     return 0;
  527. }