/**
NSPrinter
Class representing a printer's capabilities.
Copyright (C) 1996, 1997, 2004 Free Software Foundation, Inc.
Authors: Simon Frankau
Date: June 1997
Modified for Printing Backend Support
Author: Chad Hardin
Date: July 2004
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 Lesser 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "config.h"
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import "AppKit/AppKitExceptions.h"
#import "AppKit/NSGraphics.h"
#import "AppKit/NSPrinter.h"
#import "GNUstepGUI/GSPrinting.h"
//
// Class variables:
//
//
// 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;
//Class variable to cache NSPrinters, without this they
//are created (and PPDs are parsed) ALL the time
static NSMutableDictionary* printerCache;
//
// Private methods used for PPD Parsing
//
@interface NSPrinter (PPDParsingPrivate)
-(void) loadPPDAtPath: (NSString*) PPDstring
symbolValues: (NSMutableDictionary*) ppdSymbolValues
inclusionSet: (NSMutableSet*) includeSet;
-(void) addPPDKeyword: (NSString*) mainKeyword
withScanner: (NSScanner*) PPDdata
withPPDPath: (NSString*) ppdPath;
-(void) addPPDUIConstraint: (NSScanner*) constraint
withPPDPath: (NSString*) ppdPath;
-(void) addPPDOrderDependency: (NSScanner*) dependency
withPPDPath: (NSString*) ppdPath;
-(id) addString: (NSString*) string
forKey: (NSString*) key
inTable: (NSString*) table;
-(void) addValue: (NSString*) value
andValueTranslation: (NSString*) valueTranslation
andOptionTranslation: (NSString*) optionTranslation
forKey: (NSString*) key;
-(NSString*) interpretQuotedValue: (NSString*) qString;
-(int) gethex: (unichar) character;
@end
@implementation NSPrinter
//
// Class methods
//
+(void) initialize
{
if (self == [NSPrinter class])
{
// Initial version
[self setVersion:1];
}
printerCache = RETAIN([NSMutableDictionary dictionary]);
}
/** Load the appropriate bundle for the Printer
(eg: GSLPRPrinter, GSCUPSPrinter).
*/
+(id) allocWithZone: (NSZone*) zone
{
Class principalClass;
principalClass = [[GSPrinting printingBundle] principalClass];
if (principalClass == nil)
return nil;
return [[principalClass printerClass] allocWithZone: zone];
}
//
// Finding an NSPrinter
//
+(NSPrinter*) printerWithName: (NSString*) name
{
NSEnumerator *keyEnum;
NSString *key;
NSPrinter *printer;
//First, the cache has to be managed.
//Take into account any deleted printers.
keyEnum = [[printerCache allKeys] objectEnumerator];
while ((key = [keyEnum nextObject]))
{
NSEnumerator *namesEnum;
NSString *validName;
BOOL stillValid = NO;
namesEnum = [[self printerNames] objectEnumerator];
while ((validName = [namesEnum nextObject]))
{
if ([validName isEqualToString: key])
{
stillValid = YES;
break;
}
}
if (stillValid == NO)
{
[printerCache removeObjectForKey: key];
}
}
printer = [printerCache objectForKey: name];
if (printer)
{
return printer;
}
else
{
Class principalClass;
principalClass = [[GSPrinting printingBundle] principalClass];
if (principalClass == nil)
return nil;
printer = [[principalClass printerClass] printerWithName: name];
if (printer)
{
[printerCache setObject: printer
forKey: name];
}
return printer;
}
}
//
// This now different than the OpenStep spec and instead
// follows the more useful implementation Apple choosed. In
// OpenStep, this method would read a PPD and return a NSPrinter
// based upon values from that PPD, regardless if that printer
// was actually avaiable for use or not. On the contrary, Apple's
// implementation looks
// at all avaiable printers and returns one that has the same
// type. The reason for this is because they use CUPS. CUPS
// does not work by maintaining a repository of PPDs. Instead,
// the CUPS server trasnmits PPDs as they are needed, and only
// for actual real printers. Since we cannot know how the backend
// bundles will be handling their PPDs, or if they will even be using
// PPDs for that matter, (a Win32 printing backend, for example),
// I've choosen to go with Apple's implementation. Really, I see
// little use in creating a NSPrinter for a printer that is not
// available for use in the first place, I am open for commments
// on this, of course.
+(NSPrinter*) printerWithType: (NSString*) type
{
NSEnumerator *printerNamesEnum;
NSString *printerName;
printerNamesEnum = [[self printerNames] objectEnumerator];
while ((printerName = [printerNamesEnum nextObject]))
{
NSPrinter *printer;
printer = [self printerWithName: printerName];
if ([[printer type] isEqualToString: type])
{
return printer;
}
}
return nil;
}
+(NSArray*) printerNames
{
Class principalClass;
principalClass = [[GSPrinting printingBundle] principalClass];
if (principalClass == nil)
return nil;
return [[principalClass printerClass] printerNames];
}
// See note at +(NSPrinter*) printerWithType:(NSString*) type
+(NSArray*) printerTypes
{
NSMutableSet *printerTypes;
NSEnumerator *printerNamesEnum;
NSString *printerName;
NSPrinter *printer;
printerTypes = [NSMutableSet setWithCapacity:1];
printerNamesEnum = [[self printerNames] objectEnumerator];
while ((printerName = [printerNamesEnum nextObject]))
{
printer = [self printerWithName: printerName];
[printerTypes addObject: [printer type]];
}
return [printerTypes allObjects];
}
//
// Instance methods
//
//
// Printer Attributes
//
-(NSString*) host
{
return _printerHost;
}
-(NSString*) name
{
return _printerName;
}
-(NSString*) note
{
return _printerNote;
}
-(NSString*) type
{
return _printerType;
}
//
// Retrieving Specific Information
//
-(BOOL) acceptsBinary
{
// FIXME: I'm not sure if acceptsBinary is the same as BCP protocol?
NSString *result;
NSScanner *protocols;
result = [self stringForKey: @"Protocols"
inTable: @"PPD"];
if (!result)
return NO;
protocols = [NSScanner scannerWithString: result];
while (![protocols isAtEnd])
{
[protocols scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
intoString: &result];
if ([result isEqual:@"BCP"])
return YES;
}
return NO;
}
-(NSRect) imageRectForPaper: (NSString*) paperName
{
NSString *key;
key = [NSString stringWithFormat: @"ImageableArea/%@", paperName];
return [self rectForKey: key
inTable: @"PPD"];
}
-(NSSize) pageSizeForPaper: (NSString*) paperName
{
NSString *key;
key = [NSString stringWithFormat: @"PaperDimension/%@", paperName];
return [self sizeForKey: key
inTable: @"PPD"];
}
-(BOOL) isColor
{
return [self booleanForKey: @"ColorDevice"
inTable: @"PPD"];
}
-(BOOL) isFontAvailable: (NSString*) fontName
{
NSString *key;
key = [NSString stringWithFormat: @"Font/%@", fontName];
return [self isKey: key
inTable: @"PPD"];
}
-(int) languageLevel
{
return [self intForKey: @"LanguageLevel"
inTable: @"PPD"];
}
-(BOOL) isOutputStackInReverseOrder
{
// FIXME: Is this what is needed? I'm not sure how this is worked out.
NSString *result;
result = [self stringForKey: @"DefaultOutputOrder"
inTable: @"PPD"];
if (!result)
return NO;
if ([result caseInsensitiveCompare: @"REVERSE"] == NSOrderedSame)
return YES;
else
return NO;
}
//
// Querying the NSPrinter Tables
//
-(BOOL) booleanForKey: (NSString*) key
inTable: (NSString*) table
{
NSString *result;
result = [self stringForKey: key
inTable: table];
if (!result) //raise exception?
return NO;
if ([result caseInsensitiveCompare: @"TRUE"] == NSOrderedSame)
return YES;
else
return NO;
}
-(NSDictionary*) deviceDescription
{
NSMutableDictionary *result;
result = [NSMutableDictionary dictionary];
if ([self isKey: @"DefaultResolution"
inTable:@"PPD"])
{
int dpi = [self intForKey: @"DefaultResolution"
inTable: @"PPD"];
[result setObject: [NSNumber numberWithInt: dpi]
forKey: NSDeviceResolution];
}
if ([self isKey: @"ColorDevice"
inTable: @"PPD"])
{
BOOL color = [self booleanForKey: @"ColorDevice"
inTable: @"PPD"];
// FIXME: Should NSDeviceWhiteColorSpace be NSDeviceBlackColorSpace?
// FIXME #2: Are they calibrated?
// Basically I'm not sure which color spaces should be used...
if (color == YES)
{
[result setObject: NSDeviceCMYKColorSpace
forKey: NSDeviceColorSpaceName];
}
else
{
[result setObject: NSDeviceWhiteColorSpace
forKey: NSDeviceColorSpaceName];
}
}
if ([self isKey: @"DefaultBitsPerPixel"
inTable: @"PPD"])
{
int bits = [self intForKey: @"DefaultBitsPerPixel"
inTable: @"PPD"];
[result setObject: [NSNumber numberWithInt: bits]
forKey: NSDeviceBitsPerSample];
}
if ([self isKey: @"DefaultPageSize"
inTable: @"PPD"])
{
NSString* defaultPageSize = [self stringForKey: @"DefaultPageSize"
inTable: @"PPD"];
if (defaultPageSize)
{
NSSize paperSize = [self pageSizeForPaper: defaultPageSize];
[result setObject: [NSValue valueWithSize:paperSize]
forKey: NSDeviceSize];
}
}
[result setObject: [NSNumber numberWithBool:NO]
forKey: NSDeviceIsScreen];
[result setObject: [NSNumber numberWithBool:YES]
forKey: NSDeviceIsPrinter];
NSDebugMLLog(@"GSPrinting", @"Device Description: %@", [result description]);
return result;
}
-(float) floatForKey: (NSString*) key
inTable: (NSString*) table
{
NSString *result;
result = [self stringForKey: key
inTable: table];
if (!result) //raise exception?
return 0.0;
return [result floatValue];
}
-(int) intForKey: (NSString*) key
inTable: (NSString*) table
{
NSString *result;
result = [self stringForKey: key
inTable: table];
if (!result) //raise exception?
return 0;
return [result intValue];
}
-(NSRect) rectForKey: (NSString*) key
inTable: (NSString*) table
{
NSString *result;
NSScanner *bits;
double x1, y1, x2, y2;
result = [self stringForKey: key
inTable: table];
if (!result) //raise exception?
return NSZeroRect;
bits = [NSScanner scannerWithString: result];
if ([bits scanDouble: &x1] &&
[bits scanDouble: &y1] &&
[bits scanDouble: &x2] &&
[bits scanDouble: &y2])
{
return NSMakeRect(x1, y1, x2-x1, y2-y1);
}
return NSZeroRect;
}
-(NSSize) sizeForKey: (NSString*) key
inTable: (NSString*) table
{
NSString *result;
NSScanner *bits;
double x, y;
result = [self stringForKey: key
inTable: table];
if (!result) //raise exception?
return NSZeroSize;
bits = [NSScanner scannerWithString: result];
if ([bits scanDouble: &x] &&
[bits scanDouble: &y])
{
return NSMakeSize(x,y);
}
return NSZeroSize;
}
-(NSString*) stringForKey: (NSString*) key
inTable: (NSString*) table
{
NSArray *results;
results = [self stringListForKey: key
inTable: table];
if (results == nil)
return nil;
return [results objectAtIndex: 0];
}
-(NSArray*) stringListForKey: (NSString*) key
inTable: (NSString*) table
{
NSDictionary *tableObj;
NSMutableArray *result;
tableObj = [_tables objectForKey: table ];
if (tableObj == nil) //raise exception?
{
return nil;
}
result = [tableObj objectForKey: key];
if ([[result objectAtIndex:0] isEqual:@""])
{
NSMutableArray *origResult = result;
result = [NSMutableArray array];
[result addObjectsFromArray: origResult];
[result removeObjectAtIndex: 0];
}
return result;
}
-(NSPrinterTableStatus) statusForTable: (NSString*) table
{
NSDictionary *tableObj;
// Select correct table
tableObj = [_tables objectForKey: table];
if (tableObj == nil)
return NSPrinterTableNotFound;
else if (![tableObj isKindOfClass: [NSDictionary class]])
return NSPrinterTableError;
else
return NSPrinterTableOK;
}
-(BOOL) isKey: (NSString*) key
inTable: (NSString*) table
{
NSMutableDictionary *tableObj;
// Select correct table
tableObj = [_tables objectForKey: table];
if (tableObj == nil) //raise exception?
{
return NO;
}
// And check it
if ([tableObj objectForKey: key] == nil)
return NO;
else
return YES;
}
//
// NSCoding protocol
//
- (void) encodeWithCoder: (NSCoder*)aCoder
{
if ([aCoder allowsKeyedCoding])
{
// TODO: Determine keys for NSPrinter.
}
else
{
[aCoder encodeObject: _printerHost];
[aCoder encodeObject: _printerName];
[aCoder encodeObject: _printerNote];
[aCoder encodeObject: _printerType];
[aCoder encodeObject: _tables];
}
}
- (id) initWithCoder: (NSCoder*)aDecoder
{
if ([aDecoder allowsKeyedCoding])
{
// TODO: Determine keys for NSPrinter.
}
else
{
_printerHost = [aDecoder decodeObject];
_printerName = [aDecoder decodeObject];
_printerNote = [aDecoder decodeObject];
_printerType = [aDecoder decodeObject];
_tables = [aDecoder decodeObject];
}
return self;
}
@end
///
///Private implementation of routines that will be usefull
///for the printing backend bundles that subclass us.
///
@implementation NSPrinter (Private)
//
// Initialisation method used by backend bundles
//
-(id) initWithName: (NSString*) name
withType: (NSString*) type
withHost: (NSString*) host
withNote: (NSString*) note
{
self = [super init];
// Initialise instance variables
ASSIGN(_printerName, name);
ASSIGN(_printerType, type);
ASSIGN(_printerHost, host);
ASSIGN(_printerNote, note);
_tables = RETAIN([NSMutableDictionary dictionary]);
return self;
}
//
// Deallocation of instance variables
//
-(void) dealloc
{
RELEASE(_printerHost);
RELEASE(_printerName);
RELEASE(_printerNote);
RELEASE(_printerType);
RELEASE(_tables);
[super dealloc];
}
@end
@implementation NSPrinter (PPDParsing)
-(BOOL) parsePPDAtPath: (NSString*) ppdPath
{
NSAutoreleasePool* subpool;
NSMutableDictionary* ppdSymbolValues;
NSEnumerator* objEnum;
NSMutableArray* valArray;
//make sure the class variables for scanning are created
if (!newlineSet)
{
newlineSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r"];
RETAIN(newlineSet);
}
if (!keyEndSet)
{
keyEndSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r\t: "];
RETAIN(keyEndSet);
}
if (!optKeyEndSet)
{
optKeyEndSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r:/"];
RETAIN(optKeyEndSet);
}
if (!valueEndSet)
{
valueEndSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r/"];
RETAIN(valueEndSet);
}
[_tables setObject: [NSMutableDictionary dictionary]
forKey: @"PPD"];
[_tables setObject: [NSMutableDictionary dictionary]
forKey: @"PPDOptionTranslation"];
[_tables setObject: [NSMutableDictionary dictionary]
forKey: @"PPDArgumentTranslation"];
[_tables setObject: [NSMutableDictionary dictionary]
forKey: @"PPDOrderDependency"];
[_tables setObject: [NSMutableDictionary dictionary]
forKey: @"PPDUIConstraints"];
// Create a temporary autorelease pool, as many temporary objects are used
subpool = [[NSAutoreleasePool alloc] init];
// NB: There are some structure keywords (such as OpenUI/CloseUI) that may
// be repeated, but as yet are not used. Since they are structure keywords,
// they'll probably need special processing anyway, and so aren't
// added to this list.
// Create dictionary for temporary storage of symbol values
ppdSymbolValues = [NSMutableDictionary dictionary];
//The inclusion set keeps track of what PPD files have been *Include(d).
//If one comes up twice recursion has occurred and we stop it.
// And scan the PPD itself
[self loadPPDAtPath: ppdPath
symbolValues: ppdSymbolValues
inclusionSet: [NSMutableSet setWithCapacity:10]];
// Search the PPD dictionary for symbolvalues and substitute them.
objEnum = [[_tables objectForKey: @"PPD"] objectEnumerator];
while ((valArray = [objEnum nextObject]))
{
NSString *oldValue;
NSString *newValue;
int i, max;
max = [valArray count];
for (i=0 ; i < max ; i++)
{
oldValue = [valArray objectAtIndex: i];
if ([oldValue isKindOfClass: [NSString class]]
&& ![oldValue isEqual: @""]
&& [[oldValue substringToIndex: 1] isEqual: @"^"])
{
newValue = [ppdSymbolValues
objectForKey: [oldValue substringFromIndex: 1]];
if (!newValue)
{
[NSException raise: NSPPDParseException
format: @"Unknown symbol value, ^%@ in PPD file %@.ppd",
oldValue, ppdPath];
}
[valArray replaceObjectAtIndex: i
withObject: newValue];
}
}
}
// Make sure all the required keys are present
//Too many PPDs don't pass the test....
/*
objEnum = [[NSArray arrayWithObjects: @"NickName",
@"ModelName",
@"PCFileName",
@"Product",
@"PSVersion",
@"FileVersion",
@"FormatVersion",
@"LanguageEncoding",
@"LanguageVersion",
@"PageSize",
@"PageRegion",
@"ImageableArea",
@"PaperDimension",
@"PPD-Adobe",
nil] objectEnumerator];
while ((checkVal = [objEnum nextObject]))
{
if (![self isKey: checkVal
inTable: @"PPD"])
{
[NSException raise:NSPPDParseException
format:@"Required keyword *%@ not found in PPD file %@.ppd",
checkVal, PPDPath];
}
}
*/
// Release the local autoreleasePool
[subpool drain];
//Sometimes it's good to see the tables...
/*
NSDebugMLLog(@"GSPrinting", @"\n\nPPD: %@\n\n",
[[_tables objectForKey: @"PPD"] description]);
NSDebugMLLog(@"GSPrinting", @"\n\nPPDOptionTranslation: %@\n\n",
[[_tables objectForKey: @"PPDOptionTranslation"] description]);
NSDebugMLLog(@"GSPrinting", @"\n\nPPDArgumentTranslation: %@\n\n",
[[_tables objectForKey: @"PPDArgumentTranslation"] description]);
NSDebugMLLog(@"GSPrinting", @"\n\nPPDOrderDependency: %@\n\n",
[[_tables objectForKey: @"PPDOrderDependency"] description]);
NSDebugMLLog(@"GSPrinting", @"\n\nPPDUIConstraints: %@\n\n",
[[_tables objectForKey: @"PPDUIConstraints"] description]);
*/
return YES;
}
@end
@implementation NSPrinter (PPDParsingPrivate)
-(void) loadPPDAtPath: (NSString*) ppdPath
symbolValues: (NSMutableDictionary*) ppdSymbolValues
inclusionSet: (NSMutableSet*) inclusionSet
{
NSString* ppdString;
NSScanner* ppdData;
NSString* keyword;
//See if this ppd has been processed before
if ([inclusionSet member: ppdPath])
{
//this ppd has been done already!
[NSException raise: NSPPDIncludeStackOverflowException
format: @"Recursive *Includes! PPD *Include stack: %@",
[[inclusionSet allObjects] description] ];
}
[inclusionSet addObject: ppdPath];
ppdString = [NSString stringWithContentsOfFile: ppdPath];
if (nil == ppdString)
{
// The file isn't readable
[NSException raise: NSPPDParseException
format: @"PPD file '%@' isn't readable", ppdPath];
}
// Set up the scanner - Appending a newline means that it should be
// able to process the last line correctly
ppdData = [NSScanner scannerWithString:
[ppdString stringByAppendingString: @"\n"]];
[ppdData setCharactersToBeSkipped: [NSCharacterSet whitespaceCharacterSet]];
// Main processing starts here...
while (YES) //Only check for the end after accounting for whitespace
{
// Get to the start of a new keyword, skipping blank lines
[ppdData scanCharactersFromSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]
intoString: NULL];
//this could be the end...
if ([ppdData isAtEnd])
break;
// All new entries should starts '*'
if (![ppdData scanString: @"*"
intoString: NULL])
{
[NSException raise: NSPPDParseException
format: @"Line not starting with * in PPD file %@",
ppdPath];
}
// Skip lines starting '*%', '*End', '*SymbolLength', or '*SymbolEnd'
if ([ppdData scanString: @"%"
intoString: NULL]
|| [ppdData scanString: @"End" //if we get this there is problem, yes?
intoString: NULL]
|| [ppdData scanString: @"SymbolLength"
intoString: NULL]
|| [ppdData scanString: @"SymbolEnd" //if we get this there is problem, yes?
intoString: NULL])
{
[ppdData scanUpToCharactersFromSet: newlineSet
intoString: NULL];
continue;
}
// Read main keyword, up to a colon, space or newline
[ppdData scanUpToCharactersFromSet: keyEndSet
intoString: &keyword];
// Loop if there is no value section, these keywords are ignored
if ([ppdData scanCharactersFromSet: newlineSet
intoString: NULL])
{
continue;
}
// Add the line to the relevant table
if ([keyword isEqual: @"OrderDependency"])
{
[self addPPDOrderDependency: ppdData
withPPDPath: ppdPath];
}
else if ([keyword isEqual: @"UIConstraints"])
{
[self addPPDUIConstraint: ppdData
withPPDPath: ppdPath];
}
else if ([keyword isEqual: @"Include"])
{
NSFileManager *fileManager;
NSString *fileName = nil;
NSString *path = nil;
fileManager = [NSFileManager defaultManager];
[ppdData scanString: @":"
intoString: NULL];
// Find the filename between two "s"
[ppdData scanString: @"\"" /*"*/
intoString: NULL];
[ppdData scanUpToString: @"\"" /*"*/
intoString: &fileName];
[ppdData scanString: @"\"" /*"*/
intoString: NULL];
//the fileName could be an absolute path or just a filename.
if ([fileManager fileExistsAtPath: fileName])
{
//it was absolute, we are done
path = fileName;
}
//it was not absolute. Check to see if it exists in the
//directory of this ppd
else if ([fileManager fileExistsAtPath:
[[ppdPath stringByDeletingLastPathComponent]
stringByAppendingPathComponent: fileName] ])
{
path = [[ppdPath stringByDeletingLastPathComponent]
stringByAppendingPathComponent: fileName];
}
else //could not find the *Include fileName
{
[NSException raise: NSPPDIncludeNotFoundException
format: @"Could not find *Included PPD file %@", path];
}
[self loadPPDAtPath: path
symbolValues: ppdSymbolValues
inclusionSet: inclusionSet];
}
else if ([keyword isEqual: @"SymbolValue"])
{
NSString *symbolName;
NSString *symbolVal;
if (![ppdData scanString: @"^"
intoString: NULL])
{
[NSException raise: NSPPDParseException
format:@"Badly formatted *SymbolValue in PPD file %@",
ppdPath];
}
[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
withPPDPath: ppdPath];
}
// Skip any other data that don't conform with the specification.
[ppdData scanUpToCharactersFromSet: newlineSet
intoString: NULL];
}
}
-(void) addPPDKeyword: (NSString*) mainKeyword
withScanner: (NSScanner*) ppdData
withPPDPath: (NSString*) ppdPath
{
NSArray *repKeys;
NSString* optionKeyword = nil;
NSString* optionTranslation = nil;
NSString* value = nil;
NSString* valueTranslation = nil;
// Array of Repeated Keywords (Appendix B of the PostScript Printer
// Description File Format Specification).
repKeys = [NSArray arrayWithObjects:@"Emulators",
@"Extensions",
@"FaxSupport",
//@"Include", (handled separately)
@"Message",
@"PrinterError",
@"Product",
@"Protocols",
@"PSVersion",
@"Source",
@"Status",
//@"UIConstraints", (handled separately)
// Even though this is not mentioned in the list of repeated keywords,
// it's often repeated anyway, so I'm putting it here.
@"InkName",
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 %@",
ppdPath];
}
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];
[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 = [self interpretQuotedValue: value];
}
}
else
{
// Otherwise, scan up to the end of line or '/'
[ppdData scanUpToCharactersFromSet: valueEndSet
intoString: &value];
}
if (!value)
{
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
optionTranslation = [self interpretQuotedValue: optionTranslation];
valueTranslation = [self 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;
mainAndOptionKeyword=[mainKeyword stringByAppendingFormat: @"/%@",
optionKeyword];
if ([self isKey: mainAndOptionKeyword
inTable: @"PPD"])
{
return;
}
[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 (![[_tables objectForKey: @"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 addValue: value
andValueTranslation: valueTranslation
andOptionTranslation: optionTranslation
forKey: mainKeyword];
}
}
-(void) addPPDUIConstraint: (NSScanner*) constraint
withPPDPath: (NSString*) ppdPath
{
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 PPD File %@",
ppdPath];
}
// 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: @"/%@", optionKey1];
[self addString: mainKey2
forKey: mainKey1
inTable: @"PPDUIConstraints"];
[self addString: optionKey2
forKey: mainKey1
inTable: @"PPDUIConstraints"];
}
-(void) addPPDOrderDependency: (NSScanner*) dependency
withPPDPath: (NSString*) ppdPath
{
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 %@",
ppdPath];
}
[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: @"/%@", optionKeyword];
[self addString: realValue
forKey: keyword
inTable: @"PPDOrderDependency"];
[self addString: section
forKey: keyword
inTable: @"PPDOrderDependency"];
}
//
// Adds the various values to the relevant tables, for the given key
//
-(void) 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"];
}
}
//
// Adds the string to the array of strings.
// Or creates the array if it does not exist and adds the string
//
-(id) addString: (NSString*) string
forKey: (NSString*) key
inTable: (NSString*) table
{
NSMutableDictionary *tableObj;
NSMutableArray *array;
tableObj = [_tables objectForKey: table];
if (tableObj == nil)
NSDebugMLLog(@"GSPrinting", @"Could not find table %@!", table);
array = (NSMutableArray*)[tableObj objectForKey:key];
if (array == nil) //it does not exist, create it
{
array = [NSMutableArray array];
[tableObj setObject: array
forKey: key];
}
[array addObject: string];
return self;
}
// Function to convert hexadecimal substrings
-(NSString*) interpretQuotedValue: (NSString*) qString
{
NSScanner *scanner;
NSCharacterSet *emptySet;
NSString *value = nil;
NSString *part;
int stringLength;
int location;
NSRange range;
if (!qString)
{
return nil;
}
// 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];
// "<<" is a valid part of a PS string
if ([scanner scanString: @"<"
intoString: NULL])
{
value = [value stringByAppendingString: @"<<"];
}
else
{
[scanner scanCharactersFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]
intoString: NULL];
while (![scanner scanString: @">"
intoString: NULL])
{
location = [scanner scanLocation];
if (location+2 > stringLength)
{
[NSException raise: NSPPDParseException
format: @"Badly formatted hexadecimal substring '%@' in \
PPD printer file.", qString];
// NOT REACHED
}
value = [value stringByAppendingFormat: @"%c",
16 * [self gethex: [qString characterAtIndex: location]]
+ [self gethex: [qString characterAtIndex: location+1]]];
[scanner setScanLocation: location+2];
[scanner scanCharactersFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]
intoString: NULL];
}
}
if ([scanner scanUpToString:@"<" intoString:&part])
{
value = [value stringByAppendingString: part];
}
}
return value;
}
// Convert a character to a value between 0 and 15
-(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 character '%d' in PPD printer file.",
character];
return 0; /* Quiet compiler warnings */
}
@end