1/* 2 * Copyright 2008, Haiku. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Michael Pfeiffer <laplace@users.sourceforge.net> 7 */ 8 9#include "PPDParser.h" 10 11#include "AutoDelete.h" 12 13#include <stdio.h> 14#include <stdlib.h> 15 16// #define VERBOSE 1 17 18struct Keyword { 19 const char* name; 20 const char* since; 21 int major; 22 int minor; 23 bool found; 24}; 25 26static const Keyword gRequiredKeywords[] = { 27 {"DefaultImageableArea", NULL}, 28 {"DefaultPageRegion", NULL}, 29 {"DefaultPageSize", NULL}, 30 {"DefaultPaperDimension", NULL}, 31 // sometimes missing 32 // {"FileVersion", NULL}, 33 // 34 // {"FormatVersion", NULL}, 35 {"ImageableArea", NULL}, 36 // "since" is not specified in standard! 37 {"LanguageEncoding", "4.3"}, 38 {"LanguageVersion", NULL}, 39 {"Manufacturer", "4.3"}, 40 {"ModelName", NULL}, 41 {"NickName", NULL}, 42 {"PageRegion", NULL}, 43 {"PageSize", NULL}, 44 {"PaperDimension", NULL}, 45 {"PCFileName", NULL}, 46 {"PPD-Adobe", NULL}, 47 {"Product", NULL}, 48 {"PSVersion", NULL}, 49 // sometimes missing 50 // {"ShortNickName", "4.3"}, 51}; 52 53// e.g. *PPD.Adobe: "4.3" 54const char* kPPDAdobe = "PPD-Adobe"; 55 56#define NUMBER_OF_REQUIRED_KEYWORDS (int)(sizeof(gRequiredKeywords) / sizeof(struct Keyword)) 57 58class RequiredKeywords 59{ 60private: 61 Keyword fKeywords[NUMBER_OF_REQUIRED_KEYWORDS]; 62 bool fGotVersion; 63 int fMajorVersion; 64 int fMinorVersion; 65 66 void ExtractVersion(int& major, int& minor, const char* version); 67 bool IsVersionRequired(Keyword* keyword); 68 69public: 70 RequiredKeywords(); 71 bool IsRequired(Statement* statement); 72 bool IsComplete(); 73 void AppendMissing(BString* string); 74}; 75 76RequiredKeywords::RequiredKeywords() 77 : fGotVersion(false) 78{ 79 for (int i = 0; i < NUMBER_OF_REQUIRED_KEYWORDS; i ++) { 80 fKeywords[i] = gRequiredKeywords[i]; 81 fKeywords[i].found = false; 82 const char* since = fKeywords[i].since; 83 if (since != NULL) { 84 ExtractVersion(fKeywords[i].major, fKeywords[i].minor, since); 85 } 86 } 87} 88 89void RequiredKeywords::ExtractVersion(int& major, int& minor, const char* version) 90{ 91 major = atoi(version); 92 minor = 0; 93 version = strchr(version, '.'); 94 if (version != NULL) { 95 version ++; 96 minor = atoi(version); 97 } 98} 99 100bool RequiredKeywords::IsVersionRequired(Keyword* keyword) 101{ 102 if (keyword->since == NULL) return true; 103 // be conservative if version is missing 104 if (!fGotVersion) return true; 105 // keyword is not required if file version < since 106 if (fMajorVersion < keyword->major) return false; 107 if (fMajorVersion == keyword->major && 108 fMinorVersion < keyword->minor) return false; 109 return true; 110} 111 112bool RequiredKeywords::IsRequired(Statement* statement) 113{ 114 const char* keyword = statement->GetKeyword()->String(); 115 116 if (!fGotVersion && strcmp(kPPDAdobe, keyword) == 0 && 117 statement->GetValue() != NULL) { 118 Value* value = statement->GetValue(); 119 BString* string = value->GetValue(); 120 fGotVersion = true; 121 ExtractVersion(fMajorVersion, fMinorVersion, string->String()); 122 } 123 124 BString defaultKeyword; 125 if (statement->GetType() == Statement::kDefault) { 126 defaultKeyword << "Default" << keyword; 127 keyword = defaultKeyword.String(); 128 } 129 130 for (int i = 0; i < NUMBER_OF_REQUIRED_KEYWORDS; i ++) { 131 const char* name = fKeywords[i].name; 132 if (strcmp(name, keyword) == 0) { 133 fKeywords[i].found = true; 134 return true; 135 } 136 } 137 138 return false; 139} 140 141bool RequiredKeywords::IsComplete() 142{ 143 for (int i = 0; i < NUMBER_OF_REQUIRED_KEYWORDS; i ++) { 144 if (!fKeywords[i].found && IsVersionRequired(&fKeywords[i])) { 145 return false; 146 } 147 } 148 return true; 149} 150 151void RequiredKeywords::AppendMissing(BString* string) 152{ 153 for (int i = 0; i < NUMBER_OF_REQUIRED_KEYWORDS; i ++) { 154 if (!fKeywords[i].found && IsVersionRequired(&fKeywords[i])) { 155 *string << "Keyword " << fKeywords[i].name; 156 if (fKeywords[i].since != NULL) { 157 *string << fKeywords[i].major << ". " << fKeywords[i].minor 158 << " < " << 159 fMajorVersion << "." << fMinorVersion << " "; 160 } 161 *string << " is missing\n"; 162 } 163 } 164} 165 166// Constants 167 168static const char* kEndStatement = "End"; 169 170// Implementation 171 172PPDParser::PPDParser(const char* file) 173 : Parser(file) 174 , fStack(false) 175 , fRequiredKeywords(new RequiredKeywords) 176{ 177} 178 179PPDParser::~PPDParser() 180{ 181 delete fRequiredKeywords; 182} 183 184void PPDParser::Push(Statement* statement) 185{ 186 fStack.Add(statement); 187} 188 189Statement* PPDParser::Top() 190{ 191 if (fStack.Size() > 0) { 192 return fStack.StatementAt(fStack.Size()-1); 193 } 194 return NULL; 195} 196 197void PPDParser::Pop() 198{ 199 fStack.Remove(Top()); 200} 201 202void PPDParser::AddStatement(Statement* statement) 203{ 204 fRequiredKeywords->IsRequired(statement); 205 206 Statement* top = Top(); 207 if (top != NULL) { 208 top->AddChild(statement); 209 } else { 210 fPPD->Add(statement); 211 } 212} 213 214bool PPDParser::IsValidOpenStatement(GroupStatement* statement) 215{ 216 if (statement->GetGroupName() == NULL) { 217 Error("Missing group ID in open statement"); 218 return false; 219 } 220 return true; 221} 222 223bool PPDParser::IsValidCloseStatement(GroupStatement* statement) 224{ 225 if (statement->GetGroupName() == NULL) { 226 Error("Missing option in close statement"); 227 return false; 228 } 229 230 if (Top() == NULL) { 231 Error("Close statement without an open statement"); 232 return false; 233 } 234 235 GroupStatement openStatement(Top()); 236 237 // check if corresponding Open* is on top of stack 238 BString open = openStatement.GetKeyword(); 239 open.RemoveFirst("Open"); 240 BString close = statement->GetKeyword(); 241 close.RemoveFirst("Close"); 242 243 if (open != close) { 244 Error("Close statement has no corresponding open statement"); 245#ifdef VERBOSE 246 printf("********* OPEN ************\n"); 247 openStatement.GetStatement()->Print(); 248 printf("********* CLOSE ***********\n"); 249 statement->GetStatement()->Print(); 250#endif 251 return false; 252 } 253 254 BString openValue(openStatement.GetGroupName()); 255 BString closeValue(statement->GetGroupName()); 256 257 const char* whiteSpaces = " \t"; 258 openValue.RemoveSet(whiteSpaces); 259 closeValue.RemoveSet(whiteSpaces); 260 261 if (openValue != closeValue) { 262 BString message("Open name does not match close name "); 263 message << openValue << " != " << closeValue << "\n"; 264 Warning(message.String()); 265 } 266 267 return true; 268} 269 270bool PPDParser::ParseStatement(Statement* _statement) 271{ 272 AutoDelete<Statement> statement(_statement); 273 274 if (_statement->GetKeyword() == NULL) { 275 Error("Keyword missing"); 276 return false; 277 } 278 279 if (_statement->GetOption() != NULL && 280 _statement->GetOption()->GetValue() == NULL) { 281 // The parser should not provide an option without a value 282 Error("Option has no value"); 283 return false; 284 } 285 286 if (_statement->GetValue() != NULL && 287 _statement->GetValue()->GetValue() == NULL) { 288 // The parser should not provide a value without a value 289 Error("Value has no value"); 290 return false; 291 } 292 293 const char* keyword = statement.Get()->GetKeyword()->String(); 294 if (strcmp(keyword, kEndStatement) == 0) { 295 // End is ignored 296 return true; 297 } 298 299 GroupStatement group(statement.Get()); 300 if (group.IsOpenGroup()) { 301 302 if (!IsValidOpenStatement(&group)) { 303 return false; 304 } 305 // Add() has to be infront of Push()! 306 AddStatement(statement.Release()); 307 // begin of nested statement 308 Push(statement.Get()); 309 return true; 310 } 311 312 if (group.IsCloseGroup()) { 313 314 // end of nested statement 315 if (!IsValidCloseStatement(&group)) { 316 return false; 317 } 318 319 Pop(); 320 // The closing statement is not stored 321 return true; 322 } 323 324 AddStatement(statement.Release()); 325 return true; 326} 327 328bool PPDParser::ParseStatements() 329{ 330 Statement* statement; 331 while ((statement = Parser::Parse()) != NULL) { 332#ifdef VERBOSE 333 statement->Print(); fflush(stdout); 334#endif 335 if (!ParseStatement(statement)) { 336 return false; 337 } 338 339 if (!fParseAll && fRequiredKeywords->IsComplete()) { 340 break; 341 } 342 } 343 344 if (HasError()) { 345 return false; 346 } 347 348 if (Top() != NULL) { 349 BString error("Missing close statement for:\n"); 350 do { 351 error << " * " << 352 Top()->GetKeywordString() << " " << 353 Top()->GetOptionString() << "\n"; 354 Pop(); 355 } while (Top() != NULL); 356 Error(error.String()); 357 return false; 358 } 359 return true; 360} 361 362PPD* PPDParser::Parse(bool all) 363{ 364 fParseAll = all; 365 366 if (InitCheck() != B_OK) return NULL; 367 368 fPPD = new PPD(); 369 370 ParseStatements(); 371 372 if (!HasError() && !fRequiredKeywords->IsComplete()) { 373 BString string; 374 fRequiredKeywords->AppendMissing(&string); 375 Error(string.String()); 376 } 377 378 if (HasError()) { 379 delete fPPD; fPPD = NULL; 380 return NULL; 381 } 382 383 return fPPD; 384} 385 386PPD* PPDParser::ParseAll() 387{ 388 return Parse(true); 389} 390 391PPD* PPDParser::ParseHeader() 392{ 393 return Parse(false); 394} 395 396