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