root/src/tests/add-ons/print/ppd/parser/PPDParser.cpp
/*
 * Copyright 2008, Haiku.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *              Michael Pfeiffer <laplace@users.sourceforge.net>
 */

#include "PPDParser.h"

#include "AutoDelete.h"

#include <stdio.h>
#include <stdlib.h>

// #define VERBOSE 1 

struct Keyword {
        const char* name;
        const char* since;
        int major;
        int minor;
        bool found;
};

static const Keyword gRequiredKeywords[] = {
        {"DefaultImageableArea", NULL},
        {"DefaultPageRegion", NULL},
        {"DefaultPageSize", NULL},
        {"DefaultPaperDimension", NULL},
        // sometimes missing
        // {"FileVersion", NULL},
        // 
        // {"FormatVersion", NULL},
        {"ImageableArea", NULL},
        // "since" is not specified in standard!
        {"LanguageEncoding", "4.3"},
        {"LanguageVersion", NULL},
        {"Manufacturer", "4.3"},
        {"ModelName", NULL},
        {"NickName", NULL},
        {"PageRegion", NULL},
        {"PageSize", NULL},
        {"PaperDimension", NULL},
        {"PCFileName", NULL},
        {"PPD-Adobe", NULL},
        {"Product", NULL},
        {"PSVersion", NULL},
        // sometimes missing
        // {"ShortNickName", "4.3"},
};

// e.g. *PPD.Adobe: "4.3"
const char* kPPDAdobe = "PPD-Adobe";

#define NUMBER_OF_REQUIRED_KEYWORDS (int)(sizeof(gRequiredKeywords) / sizeof(struct Keyword))

class RequiredKeywords
{
private:
        Keyword fKeywords[NUMBER_OF_REQUIRED_KEYWORDS];
        bool    fGotVersion;
        int     fMajorVersion;
        int     fMinorVersion;
        
        void ExtractVersion(int& major, int& minor, const char* version);
        bool IsVersionRequired(Keyword* keyword);

public:
        RequiredKeywords();
        bool IsRequired(Statement* statement);
        bool IsComplete();
        void AppendMissing(BString* string);
};

RequiredKeywords::RequiredKeywords()
        : fGotVersion(false)
{
        for (int i = 0; i < NUMBER_OF_REQUIRED_KEYWORDS; i ++) {
                fKeywords[i] = gRequiredKeywords[i];
                fKeywords[i].found = false;
                const char* since = fKeywords[i].since;
                if (since != NULL) {
                        ExtractVersion(fKeywords[i].major, fKeywords[i].minor, since);
                }
        }
}

void RequiredKeywords::ExtractVersion(int& major, int& minor, const char* version)
{
        major = atoi(version);
        minor = 0;
        version = strchr(version, '.');
        if (version != NULL) {
                version ++;
                minor = atoi(version);
        }
}

bool RequiredKeywords::IsVersionRequired(Keyword* keyword)
{
        if (keyword->since == NULL) return true;
        // be conservative if version is missing
        if (!fGotVersion) return true;
        // keyword is not required if file version < since
        if (fMajorVersion < keyword->major) return false;
        if (fMajorVersion == keyword->major &&
                fMinorVersion < keyword->minor) return false;
        return true;
}

bool RequiredKeywords::IsRequired(Statement* statement)
{
        const char* keyword = statement->GetKeyword()->String();

        if (!fGotVersion && strcmp(kPPDAdobe, keyword) == 0 && 
                statement->GetValue() != NULL) {
                Value* value = statement->GetValue();
                BString* string = value->GetValue();
                fGotVersion = true;
                ExtractVersion(fMajorVersion, fMinorVersion, string->String());
        }

        BString defaultKeyword;
        if (statement->GetType() == Statement::kDefault) {
                defaultKeyword << "Default" << keyword;
                keyword = defaultKeyword.String();
        }
        
        for (int i = 0; i < NUMBER_OF_REQUIRED_KEYWORDS; i ++) {
                const char* name = fKeywords[i].name;
                if (strcmp(name, keyword) == 0) {
                        fKeywords[i].found = true;
                        return true;
                }
        }
        
        return false;
}

bool RequiredKeywords::IsComplete()
{
        for (int i = 0; i < NUMBER_OF_REQUIRED_KEYWORDS; i ++) {
                if (!fKeywords[i].found && IsVersionRequired(&fKeywords[i])) {
                        return false;
                }
        }
        return true;
}

void RequiredKeywords::AppendMissing(BString* string)
{
        for (int i = 0; i < NUMBER_OF_REQUIRED_KEYWORDS; i ++) {
                if (!fKeywords[i].found && IsVersionRequired(&fKeywords[i])) {
                        *string << "Keyword " << fKeywords[i].name;
                        if (fKeywords[i].since != NULL) {
                                *string << fKeywords[i].major << ". " << fKeywords[i].minor 
                                << " < " <<
                                fMajorVersion << "." << fMinorVersion << " ";
                        }
                        *string << " is missing\n";
                }
        }       
}

// Constants

static const char* kEndStatement = "End";

// Implementation

PPDParser::PPDParser(const char* file)
        : Parser(file)
        , fStack(false)
        , fRequiredKeywords(new RequiredKeywords)
{
}

PPDParser::~PPDParser()
{
        delete fRequiredKeywords;
}

void PPDParser::Push(Statement* statement)
{
        fStack.Add(statement);
}

Statement* PPDParser::Top()
{
        if (fStack.Size() > 0) {
                return fStack.StatementAt(fStack.Size()-1);
        }
        return NULL;
}

void PPDParser::Pop()
{
        fStack.Remove(Top());
}

void PPDParser::AddStatement(Statement* statement)
{
        fRequiredKeywords->IsRequired(statement);
        
        Statement* top = Top();
        if (top != NULL) {
                top->AddChild(statement);
        } else {
                fPPD->Add(statement);
        }
}

bool PPDParser::IsValidOpenStatement(GroupStatement* statement)
{
        if (statement->GetGroupName() == NULL) {
                Error("Missing group ID in open statement");
                return false;
        }
        return true;
}

bool PPDParser::IsValidCloseStatement(GroupStatement* statement)
{
        if (statement->GetGroupName() == NULL) {
                Error("Missing option in close statement");
                return false;
        }

        if (Top() == NULL) {
                Error("Close statement without an open statement");
                return false;
        }               
        
        GroupStatement openStatement(Top());
                
        // check if corresponding Open* is on top of stack
        BString open = openStatement.GetKeyword();
        open.RemoveFirst("Open");
        BString close = statement->GetKeyword();
        close.RemoveFirst("Close");

        if (open != close) {
                Error("Close statement has no corresponding open statement");
#ifdef VERBOSE
                printf("********* OPEN ************\n");
                openStatement.GetStatement()->Print();
                printf("********* CLOSE ***********\n");
                statement->GetStatement()->Print();
#endif
                return false;
        }

        BString openValue(openStatement.GetGroupName());
        BString closeValue(statement->GetGroupName());

        const char* whiteSpaces = " \t";
        openValue.RemoveSet(whiteSpaces);
        closeValue.RemoveSet(whiteSpaces);

        if (openValue != closeValue) {
                BString message("Open name does not match close name ");
                message << openValue << " != " << closeValue << "\n";
                Warning(message.String());
        }

        return true;
}               

bool PPDParser::ParseStatement(Statement* _statement)
{
        AutoDelete<Statement> statement(_statement);
        
        if (_statement->GetKeyword() == NULL) {
                Error("Keyword missing");
                return false;
        }
        
        if (_statement->GetOption() != NULL && 
                _statement->GetOption()->GetValue() == NULL) {
                // The parser should not provide an option without a value
                Error("Option has no value");
                return false;
        }
        
        if (_statement->GetValue() != NULL && 
                _statement->GetValue()->GetValue() == NULL) {
                // The parser should not provide a value without a value
                Error("Value has no value");
                return false;
        }
        
        const char* keyword = statement.Get()->GetKeyword()->String();
        if (strcmp(keyword, kEndStatement) == 0) {
                // End is ignored
                return true;
        }
        
        GroupStatement group(statement.Get());
        if (group.IsOpenGroup()) {
                
                if (!IsValidOpenStatement(&group)) {
                        return false;
                }
                // Add() has to be infront of Push()!
                AddStatement(statement.Release());
                // begin of nested statement
                Push(statement.Get());
                return true;
        }

        if (group.IsCloseGroup()) {

                // end of nested statement
                if (!IsValidCloseStatement(&group)) {
                        return false;
                }
        
                Pop();
                // The closing statement is not stored
                return true;
        }
        
        AddStatement(statement.Release());
        return true;
}

bool PPDParser::ParseStatements()
{
        Statement* statement;
        while ((statement = Parser::Parse()) != NULL) {
#ifdef VERBOSE
                statement->Print(); fflush(stdout);
#endif
                if (!ParseStatement(statement)) {
                        return false;
                }
                
                if (!fParseAll && fRequiredKeywords->IsComplete()) {
                        break;
                }
        }
        
        if (HasError()) {
                return false;
        }
        
        if (Top() != NULL) {
                BString error("Missing close statement for:\n");
                do {
                        error << " * " << 
                                Top()->GetKeywordString() << " " << 
                                Top()->GetOptionString() << "\n";
                        Pop();
                } while (Top() != NULL);
                Error(error.String());
                return false;
        }
        return true;
}

PPD* PPDParser::Parse(bool all)
{
        fParseAll = all;
        
        if (InitCheck() != B_OK) return NULL;
        
        fPPD = new PPD();

        ParseStatements();
        
        if (!HasError() && !fRequiredKeywords->IsComplete()) {
                BString string;
                fRequiredKeywords->AppendMissing(&string);
                Error(string.String());
        }
                
        if (HasError()) {
                delete fPPD; fPPD = NULL;
                return NULL;
        }
        
        return fPPD;
}

PPD* PPDParser::ParseAll()
{
        return Parse(true);
}

PPD* PPDParser::ParseHeader()
{
        return Parse(false);
}