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 }