File indexing completed on 2024-04-28 11:20:58

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2009 Alexander Rieder <alexanderrieder@gmail.com>
0004 */
0005 
0006 #include "loadedexpression.h"
0007 
0008 #include "lib/jupyterutils.h"
0009 #include "lib/imageresult.h"
0010 #include "lib/epsresult.h"
0011 #include "lib/textresult.h"
0012 #include "lib/latexresult.h"
0013 #include "lib/animationresult.h"
0014 #include "lib/latexrenderer.h"
0015 #include "lib/mimeresult.h"
0016 #include "lib/htmlresult.h"
0017 
0018 #include <QDir>
0019 #include <QStandardPaths>
0020 #include <QJsonArray>
0021 #include <QJsonObject>
0022 #include <QJsonDocument>
0023 #include <QDebug>
0024 #include <QPixmap>
0025 #include <QRegularExpression>
0026 #include <QTemporaryFile>
0027 
0028 LoadedExpression::LoadedExpression( Cantor::Session* session ) : Cantor::Expression( session, false, -1)
0029 {
0030 
0031 }
0032 
0033 void LoadedExpression::interrupt()
0034 {
0035     //Do nothing
0036 }
0037 
0038 void LoadedExpression::evaluate()
0039 {
0040     //Do nothing
0041 }
0042 
0043 void LoadedExpression::loadFromXml(const QDomElement& xml, const KZip& file)
0044 {
0045     setCommand(xml.firstChildElement(QLatin1String("Command")).text());
0046 
0047     const QDomNodeList& results = xml.elementsByTagName(QLatin1String("Result"));
0048     for (int i = 0; i < results.size(); i++)
0049     {
0050         const QDomElement& resultElement = results.at(i).toElement();
0051         const QString& type = resultElement.attribute(QLatin1String("type"));
0052         qDebug() << "type" << type;
0053         if ( type == QLatin1String("text"))
0054         {
0055             const QString& format = resultElement.attribute(QLatin1String("format"));
0056             bool isStderr = resultElement.attribute(QLatin1String("stderr")).toInt();
0057             Cantor::TextResult* result = new Cantor::TextResult(resultElement.text());
0058             if (format == QLatin1String("latex"))
0059                 result->setFormat(Cantor::TextResult::LatexFormat);
0060             result->setStdErr(isStderr);
0061             addResult(result);
0062         }
0063         else if (type == QLatin1String("mime"))
0064         {
0065             const QDomElement& resultElement = results.at(i).toElement();
0066 
0067             QJsonObject mimeBundle;
0068             const QDomNodeList& contents = resultElement.elementsByTagName(QLatin1String("Content"));
0069             for (int x = 0; x < contents.count(); x++)
0070             {
0071                 const QDomElement& content = contents.at(x).toElement();
0072 
0073                 const QString& mimeType = content.attribute(QLatin1String("key"));
0074                 QJsonDocument jsonDoc = QJsonDocument::fromJson(content.text().toUtf8());;
0075                 const QJsonValue& value = jsonDoc.object().value(QLatin1String("content"));
0076                 mimeBundle.insert(mimeType, value);
0077             }
0078 
0079             addResult(new Cantor::MimeResult(mimeBundle));
0080         }
0081         else if (type == QLatin1String("html"))
0082         {
0083             const QString& formatString = resultElement.attribute(QLatin1String("showCode"));
0084             Cantor::HtmlResult::Format format = Cantor::HtmlResult::Html;
0085             if (formatString == QLatin1String("htmlSource"))
0086                 format = Cantor::HtmlResult::HtmlSource;
0087             else if (formatString == QLatin1String("plain"))
0088                 format = Cantor::HtmlResult::PlainAlternative;
0089 
0090             const QString& plain = resultElement.firstChildElement(QLatin1String("Plain")).text();
0091             const QString& html = resultElement.firstChildElement(QLatin1String("Html")).text();
0092 
0093             std::map<QString, QJsonValue> alternatives;
0094             const QDomNodeList& alternativeElms = resultElement.elementsByTagName(QLatin1String("Alternative"));
0095             for (int x = 0; x < alternativeElms.count(); x++)
0096             {
0097                 const QDomElement& content = alternativeElms.at(x).toElement();
0098 
0099                 const QString& mimeType = content.attribute(QLatin1String("key"));
0100                 QJsonDocument jsonDoc = QJsonDocument::fromJson(content.text().toUtf8());;
0101                 const QJsonValue& value = jsonDoc.object().value(QLatin1String("root"));
0102                 alternatives[mimeType] = value;
0103             }
0104 
0105             Cantor::HtmlResult* result = new Cantor::HtmlResult(html, plain, alternatives);
0106             result->setFormat(format);
0107 
0108             addResult(result);
0109         }
0110         else if (type == QLatin1String("image") || type == QLatin1String("latex") || type == QLatin1String("animation") || type == QLatin1String("epsimage"))
0111         {
0112             const KArchiveEntry* imageEntry=file.directory()->entry(resultElement.attribute(QLatin1String("filename")));
0113             if (imageEntry&&imageEntry->isFile())
0114             {
0115                 const KArchiveFile* imageFile=static_cast<const KArchiveFile*>(imageEntry);
0116                 QString dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation);
0117                 imageFile->copyTo(dir);
0118                 QUrl imageUrl = QUrl::fromLocalFile(QDir(dir).absoluteFilePath(imageFile->name()));
0119                 if(type==QLatin1String("latex"))
0120                 {
0121                     const QByteArray& ba = QByteArray::fromBase64(resultElement.attribute(QLatin1String("image")).toLatin1());
0122                     QImage image;
0123                     image.loadFromData(ba);
0124                     addResult(new Cantor::LatexResult(resultElement.text(), imageUrl, QString(), image));
0125                 }
0126                 else if(type==QLatin1String("animation"))
0127                 {
0128                     addResult(new Cantor::AnimationResult(imageUrl));
0129                 }
0130                 else if(type==QLatin1String("epsimage"))
0131                 {
0132                     const QByteArray& ba = QByteArray::fromBase64(resultElement.attribute(QLatin1String("image")).toLatin1());
0133                     QImage image;
0134                     image.loadFromData(ba);
0135                     addResult(new Cantor::EpsResult(imageUrl, image));
0136                 }
0137                 else if(imageFile->name().endsWith(QLatin1String(".eps")))
0138                 {
0139                     const QByteArray& ba = QByteArray::fromBase64(resultElement.attribute(QLatin1String("image")).toLatin1());
0140                     QImage image;
0141                     image.loadFromData(ba);
0142                     addResult(new Cantor::EpsResult(imageUrl, image));
0143                 }
0144                 else
0145                 {
0146                     addResult(new Cantor::ImageResult(imageUrl, resultElement.text()));
0147                 }
0148             }
0149         }
0150     }
0151 
0152     const QDomElement& errElem = xml.firstChildElement(QLatin1String("Error"));
0153     if (!errElem.isNull())
0154     {
0155         setErrorMessage(errElem.text());
0156         setStatus(Error);
0157     }
0158     else
0159         setStatus(Done);
0160 }
0161 
0162 void LoadedExpression::loadFromJupyter(const QJsonObject& cell)
0163 {
0164     setCommand(Cantor::JupyterUtils::getSource(cell));
0165 
0166     const QJsonValue idObject = cell.value(QLatin1String("execution_count"));
0167     if (!idObject.isUndefined() && !idObject.isNull())
0168         setId(idObject.toInt());
0169 
0170     const QJsonArray& outputs = cell.value(QLatin1String("outputs")).toArray();
0171     for (QJsonArray::const_iterator iter = outputs.begin(); iter != outputs.end(); ++iter)
0172     {
0173         if (!Cantor::JupyterUtils::isJupyterOutput(*iter))
0174             continue;
0175 
0176         const QJsonObject& output = iter->toObject();
0177         const QString& outputType = Cantor::JupyterUtils::getOutputType(output);
0178         if (Cantor::JupyterUtils::isJupyterTextOutput(output))
0179         {
0180             const QString& text = Cantor::JupyterUtils::fromJupyterMultiline(output.value(QLatin1String("text")));
0181             bool isStderr = output.value(QLatin1String("name")).toString() == QLatin1String("stderr");
0182             Cantor::TextResult* result = new Cantor::TextResult(text);
0183             result->setStdErr(isStderr);
0184             addResult(result);
0185         }
0186         else if (Cantor::JupyterUtils::isJupyterErrorOutput(output))
0187         {
0188             const QJsonArray& tracebackLineArray = output.value(QLatin1String("traceback")).toArray();
0189             QString traceback;
0190 
0191             // Looks like the traceback in Jupyter joined with '\n', no ''
0192             // So, manually add it
0193             for (const QJsonValue& line : tracebackLineArray)
0194                 traceback += line.toString() + QLatin1Char('\n');
0195             traceback.chop(1);
0196 
0197             // IPython returns error with terminal colors, we handle it here, but should we?
0198             static const QChar ESC(0x1b);
0199             traceback.remove(QRegularExpression(QString(ESC)+QLatin1String("\\[[0-9;]*m")));
0200 
0201             setErrorMessage(traceback);
0202         }
0203         else if (Cantor::JupyterUtils::isJupyterDisplayOutput(output) || Cantor::JupyterUtils::isJupyterExecutionResult(output))
0204         {
0205             const QJsonObject& data = output.value(QLatin1String("data")).toObject();
0206 
0207             QJsonObject metadata = Cantor::JupyterUtils::getMetadata(output);
0208             const QString& text = Cantor::JupyterUtils::fromJupyterMultiline(data.value(Cantor::JupyterUtils::textMime));
0209             const QString& mainKey = Cantor::JupyterUtils::mainBundleKey(data);
0210 
0211             Cantor::Result* result = nullptr;
0212             if (mainKey == Cantor::JupyterUtils::gifMime)
0213             {
0214                 const QByteArray& bytes = QByteArray::fromBase64(data.value(mainKey).toString().toLatin1());
0215 
0216                 QTemporaryFile file;
0217                 file.setAutoRemove(false);
0218                 file.open();
0219                 file.write(bytes);
0220                 file.close();
0221 
0222                 result = new Cantor::AnimationResult(QUrl::fromLocalFile(file.fileName()), text);
0223             }
0224             else if (mainKey == Cantor::JupyterUtils::textMime)
0225             {
0226                 result = new Cantor::TextResult(text);
0227             }
0228             else if (mainKey == Cantor::JupyterUtils::htmlMime)
0229             {
0230                 const QString& html = Cantor::JupyterUtils::fromJupyterMultiline(data.value(Cantor::JupyterUtils::htmlMime));
0231                 // Some backends places gif animation in hmlt (img tag), for example, Sage
0232                 if (Cantor::JupyterUtils::isGifHtml(html))
0233                 {
0234                     result = new Cantor::AnimationResult(Cantor::JupyterUtils::loadGifHtml(html), text);
0235                 }
0236                 else
0237                 {
0238                     // Load alternative content types too
0239                     std::map<QString, QJsonValue> alternatives;
0240                     for (const QString& key : data.keys())
0241                         if (key != Cantor::JupyterUtils::htmlMime && key != Cantor::JupyterUtils::textMime)
0242                             alternatives[key] = data[key];
0243 
0244                     result = new Cantor::HtmlResult(html, text, alternatives);
0245                 }
0246             }
0247             else if (mainKey == Cantor::JupyterUtils::latexMime)
0248             {
0249                 // Some latex results contains already rendered images, so use them, if presents
0250                 const QImage& image = Cantor::JupyterUtils::loadImage(data, Cantor::JupyterUtils::pngMime);
0251 
0252                 QString latex = Cantor::JupyterUtils::fromJupyterMultiline(data.value(mainKey));
0253                 QScopedPointer<Cantor::LatexRenderer> renderer(new Cantor::LatexRenderer(this));
0254                 renderer->setLatexCode(latex);
0255                 renderer->setEquationOnly(false);
0256                 renderer->setMethod(Cantor::LatexRenderer::LatexMethod);
0257                 renderer->renderBlocking();
0258 
0259                 result = new Cantor::LatexResult(latex, QUrl::fromLocalFile(renderer->imagePath()), text, image);
0260 
0261                 // If we have failed to render LaTeX i think Cantor should show the latex code at least
0262                 if (!renderer->renderingSuccessful())
0263                     static_cast<Cantor::LatexResult*>(result)->showCode();
0264             }
0265             // So this is image
0266             else if (Cantor::JupyterUtils::imageKeys(data).contains(mainKey))
0267             {
0268                 const QImage& image = Cantor::JupyterUtils::loadImage(data, mainKey);
0269                 result = new Cantor::ImageResult(image, text);
0270                 static_cast<Cantor::ImageResult*>(result)->setOriginalFormat(mainKey);
0271 
0272                 if (mainKey == Cantor::JupyterUtils::svgMime)
0273                     static_cast<Cantor::ImageResult*>(result)->setSvgContent(Cantor::JupyterUtils::fromJupyterMultiline(data[Cantor::JupyterUtils::svgMime]));
0274 
0275                 const QJsonValue size = metadata.value(mainKey);
0276                 if (size.isObject())
0277                 {
0278                     int w = size.toObject().value(QLatin1String("width")).toInt(-1);
0279                     int h = size.toObject().value(QLatin1String("height")).toInt(-1);
0280 
0281                     if (w != -1 && h != -1)
0282                     {
0283                         static_cast<Cantor::ImageResult*>(result)->setDisplaySize(QSize(w, h));
0284                         // Remove size information, because we don't need it after setting display size
0285                         // Also, we encode image to 'image/png' on saving as .ipynb, even original image don't png
0286                         // So, without removing the size info here, after loading for example 'image/tiff' to Cantor from .ipynb and saving the worksheet
0287                         // (which means, that the image will be saved as png and not as tiff).
0288                         // We will have outdated key 'image/tiff' in metadata.
0289                         metadata.remove(mainKey);
0290                     }
0291                 }
0292 
0293             }
0294             else if (data.keys().size() == 1 && data.keys()[0] == Cantor::JupyterUtils::textMime)
0295                 result = new Cantor::TextResult(text);
0296             // Cantor don't know, how handle this, so pack into mime container result
0297             else
0298             {
0299                 qDebug() << "Found unsupported " << outputType << "result with mimes" << data.keys() << ", so add them to mime container result";
0300                 result = new Cantor::MimeResult(data);
0301             }
0302 
0303             if (result)
0304             {
0305                 result->setJupyterMetadata(metadata);
0306                 int resultIndex = output.value(QLatin1String("execution_count")).toInt(-1);
0307                 if (resultIndex != -1)
0308                     result->setExecutionIndex(resultIndex);
0309 
0310                 addResult(result);
0311             }
0312         }
0313     }
0314 
0315     if (errorMessage().isEmpty())
0316         setStatus(Done);
0317     else
0318         setStatus(Error);
0319 }