Logo Search packages:      
Sourcecode: qbrew version File versions

export.cpp

/***************************************************************************
  export.cpp
  -------------------
  Export methods for the Recipe class
  -------------------
  Copyright (c) 2005-2007 David Johnson
  Please see the header file for copyright and license information.
 ***************************************************************************/

#include <QApplication>
#include <QDomDocument>
#include <QFile>
#include <QLocale>
#include <QMessageBox>
#include <QPrinter>
#include <QTextStream>

#include "resource.h"
#include "data.h"
#include "recipe.h"

using namespace Resource;

QString escape(const QString &s);
QString underline(const QString &line);

//////////////////////////////////////////////////////////////////////////////
// escape()
// --------
// Escape special xml/html characters

QString escape(const QString &s)
{
    QString x = s;
    x.replace("&", "&amp;");
    x.replace("<", "&lt;");
    x.replace(">", "&gt;");
    x.replace("'", "&apos;");
    x.replace("\"", "&quot;");
    return x;
}

QString underline(const QString &line)
{
    QString text;
    text = line + '\n' + text.fill('-', line.length()) + '\n';
    return text;
}

//////////////////////////////////////////////////////////////////////////////
// exportHTML()
// ------------
// Export recipe as html

bool Recipe::exportHtml(const QString &filename)
{
    if (!filename.isEmpty()) {
        QFile datafile(filename);
        if (!datafile.open(QFile::WriteOnly | QFile::Text)) {
            // error opening file
            qWarning("Error: Cannot open file %s", qPrintable(filename));
            QMessageBox::warning(0, TITLE,
                                 QString("Cannot write file %1:\n%2")
                                 .arg(filename)
                                 .arg(datafile.errorString()));
            datafile.close();
            return false;
        }

        QTextStream data(&datafile);
        QApplication::setOverrideCursor(Qt::WaitCursor);

        data << recipeHTML();

        QApplication::restoreOverrideCursor();
        datafile.close();
        return true;
    }  
    return false;
}

//////////////////////////////////////////////////////////////////////////////
// recipeHTML()
// ------------
// Get the html of the recipe for printing and exporting

const QString Recipe::recipeHTML()
{
    const QString header = "<big><strong>%1</strong></big>\n";
    const QString table = "<table summary=\"%1\" border=0 cellpadding=0 cellspacing=%2>\n";
    const QString th = "<td><strong>%1</strong></td>\n";
    
    // heading
    QString html = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
    html += "<html>\n<head>\n";
    html += "<meta name=\"generator\" content=\"" + TITLE + " " + VERSION + " \">\n";
    html += "<title>" + escape(title_) + "</title>\n";
    html += "<meta name=\"author\" content=\"" + escape(brewer_) + "\">\n";
    html += "</head>\n\n";

    html += "<body>\n";

    html += table.arg("header").arg("5 bgcolor=\"#CCCCCC\" width=\"100%\"");
    html += "<tr><td>\n" + header.arg(escape(title_)) + "</td></tr>\n";
    html += "</table>\n<br>\n\n";

    // recipe table
    html += table.arg("recipe").arg(0);
    html += "<tbody>\n<tr>\n";
    html += th.arg("Recipe");
    html += "<td width=\"15\"></td>\n";
    html += "<td>" + escape(title_) + "</td>\n";
    html += "<td width=\"25\"></td>\n";

    html += th.arg("Style");
    html += "<td width=\"15\"></td>\n";
    html += "<td>" + escape(style_.name()) + "</td>\n";
    html += "</tr>\n<tr>\n";

    html += th.arg("Brewer");
    html += "<td></td>\n";
    html += "<td>" + escape(brewer_) + "</td>\n";
    html += "<td></td>\n";

    html += th.arg("Batch");
    html += "<td></td>\n";
    html += "<td>" + size_.toString(2) + "</td>\n";

    if (mashed()) html += "</tr>\n<tr>\n" + th.arg("Mashed");
    html += "</tr>\n</tbody>\n</table>\n<br>\n\n";

    // characteristics table
    html += header.arg("Recipe Characteristics");
    html += table.arg("characteristics").arg(0);
    html += "<tbody>\n<tr>\n";

    html += th.arg("Recipe Gravity");
    html += "<td width=\"15\"></td>\n";
    html += "<td>" + QString::number(og_, 'f', 3) + " OG</td>\n";
    html += "<td width=\"25\"></td>\n";

    html += th.arg("Estimated FG");
    html += "<td width=\"15\"></td>\n";
    html += "<td>" + QString::number(FGEstimate(), 'f', 3) + " FG</td>\n";
    html += "</tr>\n<tr>\n";

    html += th.arg("Recipe Bitterness");
    html += "<td></td>\n";
    html += "<td>" + QString::number(ibu_, 'f', 0) + " IBU</td>\n";
    html += "<td></td>\n";

    html += th.arg("Alcohol by Volume");
    html += "<td></td>\n";
    html += "<td>" + QString::number(ABV() * 100.0, 'f', 1) + "%</td>\n";
    html += "</tr>\n<tr>\n";

    html += th.arg("Recipe Color");
    html += "<td></td>\n";
    html += "<td>" + QString::number(srm_, 'f', 0) + DEGREE + " SRM</td>\n";
    html += "<td></td>\n";

    html += th.arg("Alcohol by Weight");
    html += "<td></td>\n";
    html += "<td>" + QString::number(ABW() * 100.0, 'f', 1) + "%</td>\n";
    html += "</tr>\n</tbody>\n</table>\n<br>\n\n";

    // ingredients table
    html += header.arg("Ingredients");
    html += table.arg("ingredients").arg(0);
    html += "<tbody>\n";

    // grains
    html += "<tr>\n" + th.arg("Quantity");
    html += "<td width=\"15\"></td>\n";
    html += th.arg("Grain");
    html += "<td width=\"15\"></td>\n";
    html += th.arg("Use");
    html += "</tr>\n\n";

    // presort
    QMultiMap<QString, Grain> gmap;
    foreach (Grain grain, grains_) gmap.insert(grain.name(), grain);

    foreach (Grain grain, gmap.values()) {
        html += "<tr>\n<td>" + grain.weight().toString(2) + "</td>\n";
        html += "<td></td>\n";
        html += "<td>" + escape(grain.name()) + "</td>\n";
        html += "<td></td>\n";
        html += "<td colspan=3>" + grain.useString() + "</td>\n</tr>\n\n";
    }

    // hops
    html += "<tr>\n" + th.arg("Quantity");
    html += "<td width=\"15\"></td>\n";
    html += th.arg("Hop");
    html += "<td width=\"15\"></td>\n";
    html += th.arg("Form");
    html += "<td width=\"15\"></td>\n";
    html += th.arg("Time");
    html += "</tr>\n\n";

    // presort
    QMultiMap<QString, Hop> hmap;
    foreach (Hop hop, hops_) hmap.insert(hop.name(), hop);

    foreach (Hop hop, hmap.values()) {
        html += "<tr>\n<td>" + hop.weight().toString(2) + "</td>\n";
        html += "<td></td>\n";
        html += "<td>" + escape(hop.name()) + "</td>\n";
        html += "<td></td>\n";
        html += "<td>" + hop.form() + "</td>\n";
        html += "<td></td>\n";
        html += "<td>" + QString::number(hop.time()) + " minutes</td>\n</tr>\n\n";
    }

    // misc ingredients
    html += "<tr>\n" + th.arg("Quantity");
    html += "<td width=\"15\"></td>\n";
    html += th.arg("Misc");
    html += "<td width=\"15\"></td>\n";
    html += th.arg("Notes");
    html += "</tr>\n\n";

    // presort
    QMultiMap<QString, Misc> mmap;
    foreach (Misc misc, miscs_) mmap.insert(misc.name(), misc);

    foreach (Misc misc, mmap) {
        html += "<tr>\n<td>" + misc.quantity().toString(2) + "</td>\n";
        html += "<td></td>\n";
        html += "<td>" + escape(misc.name()) + "</td>\n";
        html += "<td></td>\n";
        html += "<td colspan=3>" + escape(misc.notes()) + "</td>\n</tr>\n\n";
    }

    html += "</tbody>\n</table>\n<br>\n\n";

    // notes
    // TODO: using replace() might be dangerous if we ever use richtext in notes
    html += header.arg("Recipe Notes") + "\n";
    html += "<p>" + escape(recipenotes_).replace('\n', "<br>\n") + "\n</p>\n<br>\n";

    html += header.arg("Batch Notes") + "\n";
    html += "<p>" + escape(batchnotes_).replace('\n', "<br>\n") + "\n</p>\n<br>\n";

    html += "</body>\n</html>\n";

    return html;
}

//////////////////////////////////////////////////////////////////////////////
// exportText()
// ------------
// Export recipe as text

bool Recipe::exportText(const QString &filename)
{
    if (!filename.isEmpty()) {
        QFile datafile(filename);
        if (!datafile.open(QFile::WriteOnly | QFile::Text)) {
        // error opening file
            qWarning("Error: Cannot open file %s", qPrintable(filename));
            QMessageBox::warning(0, TITLE,
                                 QString("Cannot write file %1:\n%2")
                                 .arg(filename)
                                 .arg(datafile.errorString()));
            datafile.close();
            return false;
        }

        QTextStream data(&datafile);
        QApplication::setOverrideCursor(Qt::WaitCursor);

        data << recipeText();

        QApplication::restoreOverrideCursor();
        datafile.close();
        return true;
    }  
    return false;
}

//////////////////////////////////////////////////////////////////////////////
// recipeText()
// ------------
// Get the ascii text of the recipe for exporting

const QString Recipe::recipeText()
{
    // title stuff
    QString text = underline(title());
    text += "Brewer: " + brewer_ + '\n';
    text += "Style: " +  style_.name() + '\n';
    text += "Batch: " + size_.toString(2);
    if (mashed()) text += ", Mashed";
    text += "\n\n";

    // style stuff
    text += underline("Characteristics");
    text += "Recipe Gravity: " +
        QString::number(og_, 'f', 3) + " OG\n";
    text += "Recipe Bitterness: " +
        QString::number(ibu_, 'f', 0) + " IBU\n";
    text += "Recipe Color: " +
        QString::number(srm_, 'f', 0) + DEGREE + " SRM\n";
    text += "Estimated FG: " +
        QString::number(FGEstimate(), 'f', 3) + '\n';
    text += "Alcohol by Volume: " +
        QString::number(ABV() * 100.0, 'f', 1) + "%\n";
    text += "Alcohol by Weight: " +
        QString::number(ABW() * 100.0, 'f', 1) + "%\n\n";

    // ingredients
    text += underline("Ingredients");

    // grains
    // using a map will sort the grains
    QMultiMap<QString, Grain> gmap;
    foreach (Grain grain, grains_) gmap.insert(grain.name(), grain);

    foreach (Grain grain, gmap.values()) {
        text += grain.name().leftJustified(30, ' ');
        text += grain.weight().toString(2) + ", ";
        text += grain.useString() + '\n';
    }
    text += '\n';

    // hops
    // using a map will sort the hops
    QMultiMap<QString, Hop> hmap;
    foreach (Hop hop, hops_) hmap.insert(hop.name(), hop);

    foreach (Hop hop, hmap.values()) {
        text += hop.name().leftJustified(30, ' ');
        text += hop.weight().toString(2) + ", ";
        text += hop.form() + ", ";
        text += QString::number(hop.time()) + " minutes\n";
    }
    text += '\n';

    // misc ingredients
    // using a map will sort the ingredients
    QMultiMap<QString, Misc> mmap;
    foreach (Misc misc, miscs_) mmap.insert(misc.name(), misc);

    foreach (Misc misc, mmap.values()) {
        text += misc.name().leftJustified(30, ' ');
        text += misc.quantity().toString(2) + ", ";
        text += misc.notes() + '\n';
    }
    text += '\n';

    // notes
    text += underline("Notes");

    // TODO: wrap long notes
    text += "Recipe Notes:\n" + recipenotes_ + "\n\n";
    text += "Batch Notes:\n" + batchnotes_ + "\n\n";

    return text;
}

//////////////////////////////////////////////////////////////////////////////
// exportBeerXML()
// ---------------
// Export recipe to BeerXML format

bool Recipe::exportBeerXML(const QString &filename)
{
    if (filename.isEmpty()) return false;

    QFile datafile(filename);
    if (!datafile.open(QFile::WriteOnly | QFile::Text)) {
        // error opening file
        qWarning("Error: Cannot open file %s", qPrintable(filename));
        QMessageBox::warning(0, TITLE,
                             QString("Cannot write file %1:\n%2")
                             .arg(filename)
                             .arg(datafile.errorString()));
        datafile.close();
        return false;
    }

    QTextStream data(&datafile);
    QApplication::setOverrideCursor(Qt::WaitCursor);

    QDomDocument doc; // BeerXML 1 doesn't have a doctype
    doc.appendChild(doc.createProcessingInstruction("xml", "version=\"1.0\""));

    // create the root element
    QDomElement root = doc.createElement(tagRECIPES);
    doc.appendChild(root);

    QDomComment comment =
        doc.createComment(QString("BeerXML generated by %1 %2")
                          .arg(PACKAGE).arg(VERSION));
    doc.appendChild(comment);

    // start record
    QDomElement record = doc.createElement(tagRECIPE);
    root.appendChild(record);
    QDomElement element = doc.createElement(tagVERSION);
    element.appendChild(doc.createTextNode(beerXMLVersion));
    record.appendChild(element);

    // title
    element = doc.createElement(tagNAME);
    element.appendChild(doc.createTextNode(title_));
    record.appendChild(element);

    // type
    element = doc.createElement(tagTYPE);
    switch (recipeType()) {
      case Recipe::TypeExtract:
          element.appendChild(doc.createTextNode(TYPE_EXTRACT));
          break;
      case Recipe::TypePartial:
          element.appendChild(doc.createTextNode(TYPE_PARTIAL));
          break;
      case Recipe::TypeAllGrain:
          element.appendChild(doc.createTextNode(TYPE_ALLGRAIN));
          break;
    }
    record.appendChild(element);

    // brewer
    element = doc.createElement(tagBREWER);
    element.appendChild(doc.createTextNode(brewer_));
    record.appendChild(element);

    // style record
    QDomElement subrecord = doc.createElement(tagSTYLE);
    // version
    element = doc.createElement(tagVERSION);
    element.appendChild(doc.createTextNode(beerXMLVersion));
    subrecord.appendChild(element);
    // name
    element = doc.createElement(tagNAME);
    element.appendChild(doc.createTextNode(style_.name()));
    subrecord.appendChild(element);
    // TODO: unfinished...
    // category
    // category number
    // style letter
    // style guide
    // type
    // og
    element = doc.createElement(tagOGMIN);
    element.appendChild(doc.createTextNode
                        (QString::number(style_.OGLow(),'f',4)));
    subrecord.appendChild(element);
    element = doc.createElement(tagOGMAX);
    element.appendChild(doc.createTextNode
                        (QString::number(style_.OGHi(),'f',4)));
    subrecord.appendChild(element);
    // fg
    element = doc.createElement(tagFGMIN);
    element.appendChild(doc.createTextNode
                        (QString::number(style_.FGLow(),'f',4)));
    subrecord.appendChild(element);
    element = doc.createElement(tagFGMAX);
    element.appendChild(doc.createTextNode
                        (QString::number(style_.FGHi(),'f',4)));
    subrecord.appendChild(element);
    // ibu
    element = doc.createElement(tagIBUMIN);
    element.appendChild(doc.createTextNode
                        (QString::number(style_.IBULow(),'f',2)));
    subrecord.appendChild(element);
    element = doc.createElement(tagIBUMAX);
    element.appendChild(doc.createTextNode
                        (QString::number(style_.IBUHi(),'f',2)));
    subrecord.appendChild(element);
    // color
    element = doc.createElement(tagCOLORMIN);
    element.appendChild(doc.createTextNode
                        (QString::number(style_.SRMLow(),'f',2)));
    subrecord.appendChild(element);
    element = doc.createElement(tagCOLORMAX);
    element.appendChild(doc.createTextNode
                        (QString::number(style_.SRMHi(),'f',2)));
    subrecord.appendChild(element);
    // add style record
    record.appendChild(subrecord);

    // batch size
    element = doc.createElement(tagBATCHSIZE);
    double size = size_.amount(Volume::liter);
    element.appendChild(doc.createTextNode(QString::number(size,'f',8)));
    record.appendChild(element);

    if (mashed()) {
        // efficiency
        element = doc.createElement(tagEFFICIENCY);
        element.appendChild(doc.createTextNode
            (QString::number(Data::instance()->efficiency()*100.0,'f',1)));
        record.appendChild(element);
    }

    // fermentables list
    element = doc.createElement(tagFERMENTABLES);
    QDomElement subelement;
    // iterate through _grains list
    foreach (Grain grain, grains_) {
        // fermentable
        subrecord = doc.createElement(tagFERMENTABLE);
        // version
        subelement = doc.createElement(tagVERSION);
        subelement.appendChild(doc.createTextNode(beerXMLVersion));
        subrecord.appendChild(subelement);
        // name
        subelement = doc.createElement(tagNAME);
        subelement.appendChild(doc.createTextNode(grain.name()));
        subrecord.appendChild(subelement);
        // type // TODO: ???
        // amount
        subelement = doc.createElement(tagAMOUNT);
        size = grain.weight().amount(Weight::kilogram);
        subelement.appendChild(doc.createTextNode(QString::number(size,'f',8)));
        subrecord.appendChild(subelement);
        // color
        subelement = doc.createElement(tagCOLOR);
        subelement.appendChild(doc.createTextNode
                               (QString::number(grain.color(),'f',2)));
        subrecord.appendChild(subelement);
        // yield
        subelement = doc.createElement(tagYIELD);
        double yield = extractToYield(grain.extract()) * 100.0;
        subelement.appendChild(doc.createTextNode(QString::number(yield,'f',2)));
        subrecord.appendChild(subelement);
        // use??? // TODO: beerxml doesn't have this
        element.appendChild(subrecord);
    }
    record.appendChild(element);

    // hop list
    element = doc.createElement(tagHOPS);
    QString buf;
    // iterate through hops list
    foreach (Hop hop, hops_) {
        // hop
        subrecord = doc.createElement(tagHOP);
        // version
        subelement = doc.createElement(tagVERSION);
        subelement.appendChild(doc.createTextNode(beerXMLVersion));
        subrecord.appendChild(subelement);
        // name
        subelement = doc.createElement(tagNAME);
        subelement.appendChild(doc.createTextNode(hop.name()));
        subrecord.appendChild(subelement);
        // amount
        subelement = doc.createElement(tagAMOUNT);
        size = hop.weight().amount(Weight::kilogram);
        subelement.appendChild(doc.createTextNode(QString::number(size,'f',8)));
        subrecord.appendChild(subelement);
        // alpha
        subelement = doc.createElement(tagALPHA);
        subelement.appendChild(doc.createTextNode
                               (QString::number(hop.alpha(),'f',2)));
        subrecord.appendChild(subelement);
        // use // TODO: note that I'm using "Boil" for all hops...
        subelement = doc.createElement(tagUSE);
        subelement.appendChild(doc.createTextNode("Boil"));
        subrecord.appendChild(subelement);
        // time
        subelement = doc.createElement(tagTIME);
        subelement.appendChild(doc.createTextNode
                               (QString::number(hop.time())));
        subrecord.appendChild(subelement);
        // form
        subelement = doc.createElement(tagFORM);
        buf = "Leaf";;
        if (hop.form() == HOP_PELLET) buf = "Pellet";
        if (hop.form() == HOP_PLUG) buf = "Plug";
        subelement.appendChild(doc.createTextNode(buf));
        subrecord.appendChild(subelement);
        element.appendChild(subrecord);
    }
    record.appendChild(element);
 
    // yeasts
    element = doc.createElement(tagYEASTS);
    // iterate through list looking for yeasts
    // TODO: need to separate yeasts from other miscs in database
    foreach (Misc misc, miscs_) {
        if (misc.name().contains("yeast", Qt::CaseInsensitive)) {
            // yeast
            subrecord = doc.createElement(tagYEAST);
            // version
            subelement = doc.createElement(tagVERSION);
            subelement.appendChild(doc.createTextNode(beerXMLVersion));
            subrecord.appendChild(subelement);
            // name
            subelement = doc.createElement(tagNAME);
            subelement.appendChild(doc.createTextNode(misc.name()));
            subrecord.appendChild(subelement);
            // amount
            subelement = doc.createElement(tagAMOUNT);
            subelement.appendChild(doc.createTextNode("0.00")); // TODO: fixup for miscs
            subrecord.appendChild(subelement);
            // notes
            subelement = doc.createElement(tagNOTES);
            subelement.appendChild(doc.createTextNode(misc.notes()));
            subrecord.appendChild(subelement);            
            // type ???
            // form ???
            element.appendChild(subrecord);
        }
    }
    record.appendChild(element);

    // miscs
    element = doc.createElement(tagMISCS);
    // iterate through list looking for non-yeast miscs
    foreach (Misc misc, miscs_) {
        if (!misc.name().contains("yeast", Qt::CaseInsensitive)) {
            // misc
            subrecord = doc.createElement(tagMISC);
            // version
            subelement = doc.createElement(tagVERSION);
            subelement.appendChild(doc.createTextNode(beerXMLVersion));
            subrecord.appendChild(subelement);
            // name
            subelement = doc.createElement(tagNAME);
            subelement.appendChild(doc.createTextNode(misc.name()));
            subrecord.appendChild(subelement);
            // amount
            subelement = doc.createElement(tagAMOUNT);
            subelement.appendChild(doc.createTextNode("0.00")); // TODO: fixup for miscs
            subrecord.appendChild(subelement);
            // notes
            subelement = doc.createElement(tagNOTES);
            subelement.appendChild(doc.createTextNode(misc.notes()));
            subrecord.appendChild(subelement);            
            element.appendChild(subrecord);
            // type ???
            // use ???
            // time ???
        }
    }
    record.appendChild(element);

    // waters
    element = doc.createElement(tagWATERS);
    // NOTE: not currently supporting water
    record.appendChild(element);

    // mash
    // NOTE: not currently supporting mash

    // notes
    if ((recipenotes_.length() + batchnotes_.length()) > 0) {
        element = doc.createElement(tagNOTES);
        buf = recipenotes_;
        if (batchnotes_.length() > 0) {
            if (buf.length() > 0) buf += "\n\n";
            buf += batchnotes_;
        }
        element.appendChild(doc.createTextNode(buf));
        record.appendChild(element);
    }

    // save it
    doc.save(data, 2);

    QApplication::restoreOverrideCursor();
    datafile.close();
    return true;
}

//////////////////////////////////////////////////////////////////////////////
// exportPdf()
// ------------
// Export recipe as pdf

bool Recipe::exportPdf(const QString &filename)
{
    if (!filename.isEmpty()) {
        QPrinter printer(QPrinter::HighResolution);
        printer.setColorMode(QPrinter::GrayScale);
        printer.setOutputFormat(QPrinter::PdfFormat);
        printer.setOutputFileName(filename);
        printer.setCreator(TITLE + ' ' + VERSION);
        printer.setDocName(title_);

        // for convenience, use US_Letter for C/US/Canada
        switch (QLocale::system().country()) {
          case QLocale::AnyCountry:
          case QLocale::Canada:
          case QLocale::UnitedStates:
          case QLocale::UnitedStatesMinorOutlyingIslands:
              printer.setPageSize(QPrinter::Letter); break;
          default:
              printer.setPageSize(QPrinter::A4); break;
        }

        printRecipe(&printer);

        return true;
    }
    return false;
}

Generated by  Doxygen 1.6.0   Back to index