/* NSPrinter.m Class representing a printer's or printer model's capabilities. Copyright (C) 1996, 1997 Free Software Foundation, Inc. Authors: Simon Frankau Date: June 1997 - January 1998 This file is part of the GNUstep GUI Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. If you are interested in a warranty or support for this source code, contact Scott Christley for more information. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* NB: * There are a few FIXMEs in the functionality left. * Parsing of the PPDs is somewhat suboptimal. * (I think it's best to leave optimisation until more of GNUstep is done). * The *OpenUI, *CloseUI, *OpenGroup and *CloseGroup are not processed. * (This is not required in the OpenStep standard, but could be useful). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Define size used for the name and type maps - just use a small table #define NAMEMAPSIZE 0 #define TYPEMAPSIZE 0 // The maximum level of nesting of *Include directives #define MAX_PPD_INCLUDES 4 // A macro to skip whitespace over lines #define skipSpace(x) [x scanCharactersFromSet:\ [NSCharacterSet whitespaceAndNewlineCharacterSet]\ intoString:NULL] static NSString *NSPrinter_PATH = @"PrinterTypes"; static NSString *NSPrinter_INDEXFILE = @"Printers"; // // Class variables: // // Maps holding NSPrinters with the types of printers, and the real printers static NSMapTable *typeMap = NULL; static NSMapTable *nameMap = NULL; // Dictionary of real printers, from which NSPrinters can be made static NSDictionary *nameDict = nil; // An array to cache the available printer types static NSArray *printerTypesAvailable = nil; // // Class variables used during scanning: // // Character sets used in scanning. static NSCharacterSet *newlineSet = nil; static NSCharacterSet *keyEndSet = nil; static NSCharacterSet *optKeyEndSet = nil; static NSCharacterSet *valueEndSet = nil; // Array of Repeated Keywords (Appendix B of the PostScript Printer // Description File Format Specification). static NSArray *repKeys = nil; // Array to collect the values of symbol values in. static NSMutableDictionary *PPDSymbolValues; // File name of the file being processed static NSString *PPDFileName; #ifndef LIB_FOUNDATION_LIBRARY static void __NSRetainNothing(void *table, const void *anObject) { } static void __NSReleaseNothing(void *table, void *anObject) { } static NSString* __NSDescribeObjects(void *table, const void *anObject) { return [(NSObject*)anObject description]; } static const NSMapTableValueCallBacks NSNonRetainedObjectMapValueCallBacks = { (void (*)(NSMapTable *, const void *))__NSRetainNothing, (void (*)(NSMapTable *, void *))__NSReleaseNothing, (NSString *(*)(NSMapTable *, const void *))__NSDescribeObjects }; #endif /* LIB_FOUNDATION_LIBRARY */ // Convert a character to a value between 0 and 15 static int gethex(unichar character) { switch (character) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'A': return 10; case 'B': return 11; case 'C': return 12; case 'D': return 13; case 'E': return 14; case 'F': return 15; case 'a': return 10; case 'b': return 11; case 'c': return 12; case 'd': return 13; case 'e': return 14; case 'f': return 15; } [NSException raise:NSPPDParseException format:@"Badly formatted hexadeximal substring in PPD printer file."]; // NOT REACHED return 0; /* Quiet compiler warnings */ } // Function to convert hexadecimal substrings static NSString *interpretQuotedValue(NSString *qString) { NSScanner *scanner; NSCharacterSet *emptySet; NSString *value = nil; NSString *part; int stringLength; int location; NSRange range; // Don't bother unless there's something to convert range = [qString rangeOfString:@"<"]; if(!range.length) return qString; scanner = [NSScanner scannerWithString:qString]; emptySet = [NSCharacterSet characterSetWithCharactersInString:@""]; [scanner setCharactersToBeSkipped:emptySet]; if(![scanner scanUpToString:@"<" intoString:&value]) value = [NSString string]; stringLength = [qString length]; while (![scanner isAtEnd]) { [scanner scanString:@"<" intoString:NULL]; skipSpace(scanner); while (![scanner scanString:@">" intoString:NULL]) { location = [scanner scanLocation]; if (location+2 > stringLength) { [NSException raise:NSPPDParseException format:@"Badly formatted hexadecimal substring in PPD printer file."]; // NOT REACHED } value = [value stringByAppendingFormat:@"%c", 16 * gethex([qString characterAtIndex:location]) + gethex([qString characterAtIndex:location+1])]; [scanner setScanLocation:location+2]; skipSpace(scanner); } if([scanner scanUpToString:@"<" intoString:&part]) { value = [value stringByAppendingString:part]; } } return value; } static NSString *getFile(NSString *name, NSString *type) { return [NSBundle pathForGNUstepResource: name ofType: type inDirectory: NSPrinter_PATH]; } @interface NSPrinter (private) + allocMaps; - initWithPPD:(NSString *)PPDstring withName:(NSString *)name withType:(NSString *)type withHost:(NSString *)host withNote:(NSString *)note fromFile:(NSString *)file isReal:(BOOL)real; - loadPPD:(NSString *)PPDstring inclusionNum:(int)includeNum; - addPPDKeyword:(NSString *)mainKeyword withScanner:(NSScanner *)PPDdata; - addPPDUIConstraint:(NSScanner *)constraint; - addPPDOrderDependency:(NSScanner *)dependency; - addValue:(NSString *)value andValueTranslation:(NSString *)valueTranslation andOptionTranslation:(NSString *)optionTranslation forKey:(NSString *)key; - addString:(NSString *)string forKey:(NSString *)key inTable:(NSMutableDictionary *)table; @end @implementation NSPrinter // // Class methods // + (void)initialize { if (self == [NSPrinter class]) { // Initial version [self setVersion:1]; } } // // Finding an NSPrinter // + (NSPrinter *)printerWithName:(NSString *)name { NSString *path; NSArray *printerInfo; NSPrinter *printer; /* Contents of printerInfo array: * [0]: NSString of the printer's type * [1]: NSString of the printer's host * [2]: NSString of the printer's note */ // Make sure the printer names dictionary etc. exists if (!nameMap) [self allocMaps]; printer = NSMapGet(nameMap, name); // If the NSPrinter object for the printer already exists, return it if (printer) return printer; // Otherwise, try to find the information in the nameDict printerInfo = [nameDict objectForKey:name]; // Make sure you can find the printer name in the dictionary if (!printerInfo) { [NSException raise:NSGenericException format:@"Could not find printer named %s", [name cString]]; // NOT REACHED } // Create it path = getFile([printerInfo objectAtIndex:0], @"ppd"); // If not found if (path == nil || [path length] == 0) { [NSException raise:NSGenericException format:@"Could not find PPD file %s.ppd", [[printerInfo objectAtIndex:0] cString]]; // NOT REACHED } printer = [[[self alloc] initWithPPD:[NSString stringWithContentsOfFile:path] withName:name withType:[printerInfo objectAtIndex:0] withHost:[printerInfo objectAtIndex:1] withNote:[printerInfo objectAtIndex:2] fromFile:[printerInfo objectAtIndex:0] isReal:YES] autorelease]; // Once created, put it in the map table NSMapInsert(nameMap, name, printer); return printer; } + (NSPrinter *)printerWithType:(NSString *)type { NSString *path; NSPrinter *printer = nil; // Make sure the printer types dictionary exists if (!typeMap) [self allocMaps]; else printer = NSMapGet(typeMap, type); // If the NSPrinter is already created, use it if (printer) return printer; path = getFile(type, @"ppd"); // If not found if (path == nil || [path length] == 0) { [NSException raise:NSGenericException format:@"Could not find PPD file %s.ppd", [type cString]]; // NOT REACHED } printer = [[[self alloc] initWithPPD:[NSString stringWithContentsOfFile:path] withName:type withType:type withHost:@"" withNote:@"" fromFile:path isReal:NO] autorelease]; // Once created, put it in the hash table NSMapInsert(typeMap, type, printer); return printer; } + (NSArray *)printerNames { if(!nameDict) [NSPrinter allocMaps]; return [nameDict allKeys]; } + (NSArray *)printerTypes { NSBundle *lbdle; NSArray *lpaths; NSMutableArray *printers; NSString *path; NSDictionary *env; NSAutoreleasePool *subpool; // There's a lot of temp strings used... int i, max; if (printerTypesAvailable) return printerTypesAvailable; printers = [[NSMutableArray array] retain]; subpool = [[NSAutoreleasePool alloc] init]; env = [[NSProcessInfo processInfo] environment]; lbdle = [NSBundle bundleWithPath: [env objectForKey: @"GNUSTEP_USER_ROOT"]]; lpaths = [lbdle pathsForResourcesOfType:@"ppd" inDirectory:NSPrinter_PATH]; max = [lpaths count]; for(i=0 ; i MAX_PPD_INCLUDES) { [NSException raise:NSPPDIncludeStackOverflowException format:@"Too many *Includes in PPD"]; // NOT REACHED } [self loadPPD:[NSString stringWithContentsOfFile:path] inclusionNum:includeNum]; } else if ([keyword isEqual:@"SymbolValue"]) { NSString *symbolName; NSString *symbolVal; if (![PPDdata scanString:@"^" intoString:NULL]) { [NSException raise:NSPPDParseException format:@"Badly formatted *SymbolValue in PPD file %s.ppd", [PPDFileName cString]]; // NOT REACHED } [PPDdata scanUpToString:@":" intoString:&symbolName]; [PPDdata scanString:@":" intoString:NULL]; [PPDdata scanString:@"\"" intoString:NULL]; [PPDdata scanUpToString:@"\"" intoString:&symbolVal]; if (!symbolVal) symbolVal = @""; [PPDdata scanString:@"\"" intoString:NULL]; [PPDSymbolValues setObject:symbolVal forKey:symbolName]; } else [self addPPDKeyword:keyword withScanner:PPDdata]; } return self; } - addPPDKeyword:(NSString *)mainKeyword withScanner:(NSScanner *)PPDdata { NSString *optionKeyword = nil; NSString *optionTranslation = nil; NSString *value = nil; NSString *valueTranslation = nil; // Scan off any optionKeyword [PPDdata scanUpToCharactersFromSet:optKeyEndSet intoString:&optionKeyword]; if ([PPDdata scanCharactersFromSet:newlineSet intoString:NULL]) { [NSException raise:NSPPDParseException format:@"Keyword has optional keyword but no value in PPD file %s.ppd", [PPDFileName cString]]; // NOT REACHED } if ([PPDdata scanString:@"/" intoString:NULL]) { // Option keyword translation exists - scan it [PPDdata scanUpToString:@":" intoString:&optionTranslation]; } [PPDdata scanString:@":" intoString:NULL]; // Read the value part // Values starting with a " are read until the second ", ignoring \n etc. if ([PPDdata scanString:@"\"" intoString:NULL]) { [PPDdata scanUpToString:@"\"" intoString:&value]; if (!value) value = @""; [PPDdata scanString:@"\"" intoString:NULL]; // It is a QuotedValue if it's in quotes, and there is no option // key, or the main key is a *JCL keyword if (!optionKeyword || [[mainKeyword substringToIndex:3] isEqualToString:@"JCL"]) value = interpretQuotedValue(value); } else { // Otherwise, scan up to the end of line or '/' [PPDdata scanUpToCharactersFromSet:valueEndSet intoString:&value]; } // If there is a value translation, scan it if ([PPDdata scanString:@"/" intoString:NULL]) { [PPDdata scanUpToCharactersFromSet:newlineSet intoString:&valueTranslation]; } // The translations also have to have any hex substrings interpreted if (optionTranslation) optionTranslation = interpretQuotedValue(optionTranslation); if (valueTranslation) valueTranslation = interpretQuotedValue(valueTranslation); // The keyword (or keyword/option pair, if there's a option), should only // only have one value, unless it's one of the optionless keywords which // allow multiple instances. // If a keyword is read twice, 'first instance is correct', according to // the standard. // Finally, add the strings to the tables if (optionKeyword) { NSString *mainAndOptionKeyword=[mainKeyword stringByAppendingFormat:@"/%s", [optionKeyword cString]]; if ([self isKey:mainAndOptionKeyword inTable:@"PPD"]) return self; [self addValue:value andValueTranslation:valueTranslation andOptionTranslation:optionTranslation forKey:mainAndOptionKeyword]; // Deal with the oddities of stringForKey:inTable: // If this method is used to find a keyword with options, using // just the keyword it should return an empty string // stringListForKey:inTable:, however, should return the list of // option keywords. // This is done by making the first item in the array an empty // string, which will be skipped by stringListForKey:, if necessary if (![PPD objectForKey:mainKeyword]) { [self addString:@"" forKey:mainKeyword inTable:PPD]; [self addString:@"" forKey:mainKeyword inTable:PPDOptionTranslation]; [self addString:@"" forKey:mainKeyword inTable:PPDArgumentTranslation]; } [self addValue:optionKeyword andValueTranslation:optionKeyword andOptionTranslation:optionKeyword forKey:mainKeyword]; } else { if ([self isKey:mainKeyword inTable:@"PPD"] && ![repKeys containsObject:mainKeyword]) return self; [self addValue:value andValueTranslation:valueTranslation andOptionTranslation:optionTranslation forKey:mainKeyword]; } return self; } - addPPDUIConstraint:(NSScanner *)constraint { NSString *mainKey1 = nil; NSString *optionKey1 = nil; NSString *mainKey2 = nil; NSString *optionKey2 = nil; // UIConstraint should have no option keyword if (![constraint scanString:@":" intoString:NULL]) { [NSException raise:NSPPDParseException format:@"UIConstraints has option keyword in PPDFileName %s.ppd", [PPDFileName cString]]; // NOT REACHED } // Skip the '*' [constraint scanString:@"*" intoString:NULL]; // Scan the bits. Stuff not starting with * must be an optionKeyword [constraint scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&mainKey1]; if (![constraint scanString:@"*" intoString:NULL]) { [constraint scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&optionKey1]; [constraint scanString:@"*" intoString:NULL]; } [constraint scanUpToCharactersFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:&mainKey2]; if (![constraint scanCharactersFromSet:newlineSet intoString:NULL]) { [constraint scanUpToCharactersFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:&optionKey2]; } else { optionKey2 = @""; } // Add to table if (optionKey1) mainKey1 = [mainKey1 stringByAppendingFormat:@"/%s",[optionKey1 cString]]; [self addString:mainKey2 forKey:mainKey1 inTable:PPDUIConstraints]; [self addString:optionKey2 forKey:mainKey1 inTable:PPDUIConstraints]; return self; } - addPPDOrderDependency:(NSScanner *)dependency { NSString *realValue = nil; NSString *section = nil; NSString *keyword = nil; NSString *optionKeyword = nil; // Order dependency should have no option keyword if (![dependency scanString:@":" intoString:NULL]) { [NSException raise:NSPPDParseException format:@"OrderDependency has option keyword in PPD file %s.ppd", [PPDFileName cString]]; // NOT REACHED } [dependency scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&realValue]; [dependency scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:§ion]; [dependency scanString:@"*" intoString:NULL]; [dependency scanUpToCharactersFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:&keyword]; if (![dependency scanCharactersFromSet:newlineSet intoString:NULL]) { // Optional keyword exists [dependency scanUpToCharactersFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:&optionKeyword]; } // Go to next line of PPD file [dependency scanCharactersFromSet:newlineSet intoString:NULL]; // Add to table if (optionKeyword) keyword = [keyword stringByAppendingFormat:@"/%s", [optionKeyword cString]]; [self addString:realValue forKey:keyword inTable:PPDOrderDependency]; [self addString:section forKey:keyword inTable:PPDOrderDependency]; return self; } // // Adds the various values to the relevant tables, for the given key // - addValue:(NSString *)value andValueTranslation:(NSString *)valueTranslation andOptionTranslation:(NSString *)optionTranslation forKey:(NSString *)key { [self addString:value forKey:key inTable:PPD]; if (valueTranslation) [self addString:valueTranslation forKey:key inTable:PPDArgumentTranslation]; if (optionTranslation) [self addString:optionTranslation forKey:key inTable:PPDOptionTranslation]; return self; } // // Adds the string to the array of strings // - addString:(NSString *)string forKey:(NSString *)key inTable:(NSMutableDictionary *)table { NSMutableArray *array; array = (NSMutableArray *)[table objectForKey:key]; if (array) // Add string to existing array [array addObject:string]; else // Create the array if it does not exist [table setObject:[NSMutableArray arrayWithObject:string] forKey:key]; return self; } @end