Dear all, Attached please find an updated base64 embedding patch. This patch does not write original filename information to the .lyx file so the filename in .lyx file issue is resolved. The code is even simpler than before because of the removal of the link and lookup function. (Andre: neither QTextStream and QDataStream should be used, QFile.write() is enough. :-)
The new file format is: \begin_layout Standard \begin_inset Graphics embedded E1632621729.png \end_inset \begin_embedded_files \file_map \end_file_map \embed E1632621729.png iVBORw0KGgoAAAANSUhEUgAAABgAAAAYEAYAAACw5+G7AAAABmJLR0T///////8JWPfcAAAA CXBIWXMAAABIAAAASABGyWs+AAA \end_embed \end_embedded_files At this stage, the file_map section will be empty and all embedded files will be anonymous. This section is put there to avoid later lyx2lyx hassles. What do I mean by later? Technically speaking, it is perfectly OK to keep embedded files anonymous, just like how they exist in MS/Word or OOffice. However, keeping original filepaths in .lyx file leads to a few convenient features such as 'un-embed an inset' and 'extract all embedded files'. More importantly, this allows easy sharing of external files across different .lyx documents, which is very important for my work. I proposed the use of a BufferParam::'keep embedded files anonymous' flag to control the output of this section. If this flag is turned on by default, no filename information will be leaked by default. For an advanced user who happens to work with shared, out of tree files on different machines, he can turn off this flag and take things under his control. This option is very easy to implement and I personally prefer this method. If the above method is not good enough, Andre's encryption suggestion makes most sense to me, because it can apply not only to this particular problem, but also to the general problem of handling confidential information that we may encounter later. I do not have a complete working scenario right now, but it can be something like: 1. The information is encrypted with a password. 2. The recipient of the document can work with it without knowing such information, or 'unlock' such information with a password. I would propose the following key-handling procedure: 1. A user can use a global lyxrc password for all his document (and on all his working machines). If this password is set, it will be automatically used to encrypt and decrypt all documents, unless s/he intentionally sets a different password for a document, or needs a different password to decrypt a file. 2. A user can use a document-specific password in the document settings dialog, or use a password to decrypt a document in this dialog. Anyway, the filename in .lyx file issue is resolved now so the only 'problem' with my proposal is the 'individual embedding' feature, which I will address in another email. Cheers, Bo
Index: development/scons/scons_manifest.py =================================================================== --- development/scons/scons_manifest.py (revision 24712) +++ development/scons/scons_manifest.py (working copy) @@ -57,6 +57,7 @@ Dimension.h DispatchResult.h DocIterator.h + EmbeddedFiles.h Encoding.h ErrorList.h Exporter.h @@ -161,6 +162,7 @@ CutAndPaste.cpp DepTable.cpp DocIterator.cpp + EmbeddedFiles.cpp Encoding.cpp ErrorList.cpp Exporter.cpp Index: src/insets/InsetGraphicsParams.h =================================================================== --- src/insets/InsetGraphicsParams.h (revision 24712) +++ src/insets/InsetGraphicsParams.h (working copy) @@ -76,7 +76,7 @@ /// Buffer is needed to figure out if a figure is embedded. void Write(std::ostream & os, Buffer const & buf) const; /// If the token belongs to our parameters, read it. - bool Read(Lexer & lex, std::string const & token, std::string const & bufpath); + bool Read(Lexer & lex, std::string const & token, Buffer const & buf); /// convert // Only a subset of InsetGraphicsParams is needed for display purposes. // This function also interrogates lyxrc to ascertain whether Index: src/insets/InsetGraphics.cpp =================================================================== --- src/insets/InsetGraphics.cpp (revision 24712) +++ src/insets/InsetGraphics.cpp (working copy) @@ -125,7 +125,7 @@ } -void readInsetGraphics(Lexer & lex, string const & bufpath, +void readInsetGraphics(Lexer & lex, Buffer const & buf, InsetGraphicsParams & params) { bool finished = false; @@ -142,7 +142,7 @@ if (token == "\\end_inset") { finished = true; } else { - if (!params.Read(lex, token, bufpath)) + if (!params.Read(lex, token, buf)) lyxerr << "Unknown token, " << token << ", skipping." @@ -275,7 +275,7 @@ { lex.setContext("InsetGraphics::read"); //lex >> "Graphics"; - readInsetGraphics(lex, buffer().filePath(), params_); + readInsetGraphics(lex, buffer(), params_); graphic_->update(params().as_grfxParams()); } @@ -935,7 +935,7 @@ lex.setContext("InsetGraphics::string2params"); lex >> "graphics"; params = InsetGraphicsParams(); - readInsetGraphics(lex, buffer.filePath(), params); + readInsetGraphics(lex, buffer, params); } Index: src/insets/InsetGraphicsParams.cpp =================================================================== --- src/insets/InsetGraphicsParams.cpp (revision 24712) +++ src/insets/InsetGraphicsParams.cpp (working copy) @@ -13,10 +13,11 @@ #include "InsetGraphicsParams.h" +#include "Buffer.h" +#include "EmbeddedFiles.h" #include "LyX.h" // for use_gui #include "Lexer.h" #include "LyXRC.h" -#include "Buffer.h" #include "graphics/GraphicsParams.h" #include "graphics/GraphicsTypes.h" @@ -140,8 +141,14 @@ void InsetGraphicsParams::Write(ostream & os, Buffer const & buffer) const { // Do not write the default values - if (!filename.empty()) - os << "\tfilename " << filename.outputFilename(buffer.filePath()) << '\n'; + if (!filename.empty()) { + if (filename.isRelativeTo(buffer.temppath())) { + os << "\tembedded " << filename.onlyFileName() << '\n'; + // this file will be embedded in the .lyx file + buffer.embeddedFiles().registerFile(filename); + } else + os << "\tfilename " << filename.outputFilename(buffer.filePath()) << '\n'; + } if (lyxscale != 100) os << "\tlyxscale " << lyxscale << '\n'; if (display != graphics::DefaultDisplay) @@ -182,11 +189,15 @@ } -bool InsetGraphicsParams::Read(Lexer & lex, string const & token, string const & bufpath) +bool InsetGraphicsParams::Read(Lexer & lex, string const & token, + Buffer const & buf) { if (token == "filename") { lex.eatLine(); - filename.set(lex.getString(), bufpath); + filename.set(lex.getString(), buf.filePath()); + } else if (token == "embedded") { + lex.eatLine(); + filename.set(lex.getString(), buf.temppath()); } else if (token == "lyxscale") { lex.next(); lyxscale = lex.getInteger(); Index: src/EmbeddedFiles.cpp =================================================================== --- src/EmbeddedFiles.cpp (revision 0) +++ src/EmbeddedFiles.cpp (revision 0) @@ -0,0 +1,221 @@ +// -*- C++ -*- +/** + * \file EmbeddedFiles.cpp + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author Bo Peng + * + * Full author contact details are available in file CREDITS. + */ + +#include <config.h> + + +#include "EmbeddedFiles.h" + +#include "support/lassert.h" +#include "support/convert.h" +#include "support/debug.h" +#include "support/FileName.h" +#include "support/filetools.h" +#include "support/gettext.h" + + +#include <iostream> +#include <vector> +#include <map> + +using namespace std; + +namespace lyx { + +using support::FileName; +using support::DocFileName; +using support::getExtension; + +struct EmbeddedFiles::Impl +{ + Impl() : temp_path_(), file_map_(), file_list_() {} + + /// buffer temporary path + string temp_path_; + // A map between external file and embedded (randomly named) files + map<DocFileName, DocFileName> file_map_; + /// a list of files that will be written to .lyx file + mutable vector<string> file_list_; +}; + + +EmbeddedFiles::EmbeddedFiles() + : d(new Impl()) +{ +} + + +EmbeddedFiles::~EmbeddedFiles() +{ + delete d; +} + + +void EmbeddedFiles::setTempPath(std::string const & path) +{ + d->temp_path_ = path; +} + + +DocFileName EmbeddedFiles::embed(DocFileName const & file) +{ + map<DocFileName, DocFileName>::iterator it = d->file_map_.find(file); + + // NOTE: There are two possibilities, file already been inserted + // or not. This implementation choose to return the existing + // embedded file. That is to say, + // a. \c file will have a single embedded version. The content of the + // last time this file is inserted will be the content for all instances. + // b. All embedded file will have a single source. + // + // I choose this implementation because if we simply do + // d->file_map_[file] = new_temp_name + // the old embedded file will lose its source filename. This also avoid + // multiple embedded copies of the same file if a file is inserted + // several times. + FileName res; + if (it == d->file_map_.end()) { + string ext = getExtension(file.absFilename()); + while (true) { + string num = convert<string>(rand()); + res.set(d->temp_path_ + "/E" + num + "." + ext); + if (!res.exists()) + break; + } + d->file_map_[file] = res; + } else { + res = it->second; + } + + if (file.exists()) + file.copyTo(res); + + LYXERR(Debug::FILES, "Copy file " << file.absFilename() << " to " + << res.absFilename()); + + return res; +} + + +void EmbeddedFiles::clearWriteList() const +{ + d->file_list_.clear(); +} + + +void EmbeddedFiles::registerFile(DocFileName const & file) const +{ + d->file_list_.push_back(file.onlyFileName()); +} + + +bool EmbeddedFiles::read(Lexer & lex, ErrorList & errorList) +{ + int has_embed_section = false; + + while (lex.isOK()) { + string token; + lex >> token; + + if (token.empty()) + continue; + + if (token == "\\begin_embedded_files") { + has_embed_section = true; + continue; + } else if (token == "\\end_embedded_files") { + if (!has_embed_section){ + docstring const s = _("\\begin_embedded_files is missing"); + errorList.push_back(ErrorItem(_("Document embedded file error"), + s, -1, 0, 0)); + } + return !has_embed_section; + } else if (token == "\\end_document") { + lex.pushToken(token); + if (has_embed_section) { + docstring const s = _("\\end_embedded_files is missing"); + errorList.push_back(ErrorItem(_("Document embedded file error"), + s, -1, 0, 0)); + } + return has_embed_section; + } else if (token == "\\file_map") { + lex.eatLine(); + + string file_map; + string tmp; + while (true) { + lex.next(); + tmp = lex.getString(); + if (tmp == "\\end_file_map" || tmp.empty()) + break; + file_map += tmp; + } + // currently, file_map should be empty. It will be used to fill + // the file_map_ structure. + } else if (token == "\\embed") { + lex.eatLine(); + DocFileName file; + file.set(lex.getString(), d->temp_path_); + + string content; + string tmp; + while (true) { + lex.next(); + tmp = lex.getString(); + if (tmp == "\\end_embed" || tmp.empty()) + break; + content += tmp; + } + LYXERR(Debug::FILES, "Creating embedded file " << file.absFilename() + << " from content '" << content << "'" << endl); + file.decodeFrom(content); + } + } + return false; +} + + +void EmbeddedFiles::write(ostream & ofs) +{ + if (d->file_list_.empty()) + return; + + // write map between embedded and external file. + // + ofs << "\n\\file_map\n"; + // This is currently empty. An encrypted text is likely to be written here. + ofs << "\n\\end_file_map\n"; + + // sort embedded files so that change of inset order will not lead + // to massive change of .lyx file. + std::sort(d->file_list_.begin(), d->file_list_.end()); + + std::vector<string>::const_iterator it = d->file_list_.begin(); + std::vector<string>::const_iterator it_end = d->file_list_.end(); + + DocFileName file; + for (; it != it_end; ++it) { + file.set(*it, d->temp_path_); + // embedded version should always exist + LASSERT(file.exists(), /**/); + ofs << "\n\\embed " << *it << "\n"; + // encode returns a long string so I need to add a few newlines. + string content = file.encode(); + int const CHARS_PER_LINE = 72; + // This can be made safer. + for (int line = 0; line <= content.size()/CHARS_PER_LINE; ++line) + ofs << content.substr(line * CHARS_PER_LINE, CHARS_PER_LINE) << "\n"; + ofs << "\\end_embed\n"; + } +} + + +} // namespace lyx Index: src/Buffer.h =================================================================== --- src/Buffer.h (revision 24712) +++ src/Buffer.h (working copy) @@ -27,6 +27,7 @@ class BiblioInfo; class BufferParams; class DocIterator; +class EmbeddedFiles; class ErrorItem; class ErrorList; class FuncRequest; @@ -410,6 +411,8 @@ /// the document contents. TocBackend & tocBackend() const; + EmbeddedFiles & embeddedFiles() const; + /// Undo & undo(); Index: src/Text.cpp =================================================================== --- src/Text.cpp (revision 24712) +++ src/Text.cpp (working copy) @@ -1256,6 +1256,11 @@ if (token == "\\begin_body") continue; + if (token == "\\begin_embedded_files") { + lex.pushToken(token); + return false; + } + if (token == "\\end_document") return false; Index: src/frontends/qt4/GuiGraphics.cpp =================================================================== --- src/frontends/qt4/GuiGraphics.cpp (revision 24712) +++ src/frontends/qt4/GuiGraphics.cpp (working copy) @@ -16,6 +16,8 @@ #include "GuiGraphics.h" +#include "Buffer.h" +#include "EmbeddedFiles.h" #include "LengthCombo.h" #include "Length.h" #include "LyXRC.h" @@ -162,6 +164,8 @@ //graphics pane connect(filename, SIGNAL(textChanged(const QString &)), this, SLOT(change_adaptor())); + connect(embedCB, SIGNAL(toggled(bool)), + this, SLOT(change_adaptor())); connect(WidthCB, SIGNAL( clicked()), this, SLOT(change_adaptor())); connect(HeightCB, SIGNAL( clicked()), @@ -459,7 +463,11 @@ //lyxerr << bufferFilepath(); string const name = igp.filename.outputFilename(fromqstr(bufferFilepath())); - filename->setText(toqstr(name)); + + bool embedded = igp.filename.isRelativeTo(buffer().temppath()); + embedCB->setCheckState(embedded ? Qt::Checked : Qt::Unchecked); + // do not display embedded filename + filename->setText(embedded ? qt_("*****") : toqstr(name)); // set the bounding box values if (igp.bb.empty()) { @@ -598,7 +606,13 @@ { InsetGraphicsParams & igp = params_; - igp.filename.set(fromqstr(filename->text()), fromqstr(bufferFilepath())); + DocFileName file; + file.set(fromqstr(filename->text()), fromqstr(bufferFilepath())); + bool embedded = embedCB->checkState() == Qt::Checked; + if (embedded && file.exists()) + igp.filename = buffer().embeddedFiles().embed(file); + else + igp.filename = file; // the bb section igp.bb.erase(); Index: src/frontends/qt4/ui/GraphicsUi.ui =================================================================== --- src/frontends/qt4/ui/GraphicsUi.ui (revision 24712) +++ src/frontends/qt4/ui/GraphicsUi.ui (working copy) @@ -142,6 +142,13 @@ <property name="spacing" > <number>6</number> </property> + <item row="0" column="4" > + <widget class="QCheckBox" name="embedCB" > + <property name="text" > + <string>E&mbed</string> + </property> + </widget> + </item> <item row="0" column="2" > <widget class="QPushButton" name="browsePB" > <property name="toolTip" > @@ -757,6 +764,7 @@ <tabstop>tabWidget</tabstop> <tabstop>filename</tabstop> <tabstop>browsePB</tabstop> + <tabstop>embedCB</tabstop> <tabstop>scaleCB</tabstop> <tabstop>Scale</tabstop> <tabstop>WidthCB</tabstop> Index: src/Makefile.am =================================================================== --- src/Makefile.am (revision 24712) +++ src/Makefile.am (working copy) @@ -104,6 +104,7 @@ CutAndPaste.cpp \ DepTable.cpp \ DocIterator.cpp \ + EmbeddedFiles.cpp \ Encoding.cpp \ ErrorList.cpp \ Exporter.cpp \ @@ -200,6 +201,7 @@ DepTable.h \ DispatchResult.h \ DocIterator.h \ + EmbeddedFiles.h \ Encoding.h \ ErrorList.h \ Exporter.h \ Index: src/support/FileName.cpp =================================================================== --- src/support/FileName.cpp (revision 24712) +++ src/support/FileName.cpp (working copy) @@ -21,6 +21,7 @@ #include "support/Package.h" #include "support/qstring_helpers.h" +#include <QByteArray> #include <QDateTime> #include <QDir> #include <QFile> @@ -554,6 +555,48 @@ } +bool FileName::isRelativeTo(string const & path) const +{ + return prefixIs(absFilename(), path); +} + + +string const FileName::encode() const +{ + if (!isReadableFile()) { + LYXERR0("File '" << *this << "' is not redable!"); + return string(); + } + + QFile file(d->fi.absoluteFilePath()); + if (!file.open(QIODevice::ReadOnly)) { + LYXERR0("File '" << *this + << "' could not be opened in read only mode!"); + return string(); + } + + QByteArray text = file.readAll(); + file.close(); + + // NOTE: we do not care about the encoding of the file here. + return text.toBase64().data(); +} + + +void FileName::decodeFrom(string const & encoded) const +{ + QFile file(d->fi.absoluteFilePath()); + if (!file.open(QFile::WriteOnly | QFile::Truncate)) { + LYXERR0("File '" << *this + << "' could not be opened in write only mode!"); + return; + } + + QByteArray text(encoded.c_str()); + file.write(QByteArray::fromBase64(text)); +} + + docstring FileName::displayName(int threshold) const { return makeDisplayPath(absFilename(), threshold); Index: src/support/FileName.h =================================================================== --- src/support/FileName.h (revision 24712) +++ src/support/FileName.h (working copy) @@ -177,6 +177,15 @@ docstring const absoluteFilePath() const; + /// \return true if this file is under directory path + bool isRelativeTo(std::string const & path) const; + + /// encode the file to a string use base64 encoding + std::string const encode() const; + + /// decode a file from base64 encoded string + void decodeFrom(std::string const & encoded) const; + private: /// struct Private; Index: src/EmbeddedFiles.h =================================================================== --- src/EmbeddedFiles.h (revision 0) +++ src/EmbeddedFiles.h (revision 0) @@ -0,0 +1,72 @@ +// -*- C++ -*- +/** + * \file EmbeddedFiles.h + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author Bo Peng + * + * Full author contact details are available in file CREDITS. + */ + +#ifndef EMBEDDED_FILES_H +#define EMBEDDED_FILES_H + +#include "ErrorList.h" +#include "Lexer.h" + +#include "support/strfwd.h" + +namespace lyx { + +class ErrorList; + +namespace support { +class DocFileName; +class FileName; +} + + +class EmbeddedFiles { +public: + /// + EmbeddedFiles(); + ~EmbeddedFiles(); + + /// embedded files are put under the temp_path of a buffer + void setTempPath(std::string const & temp_path); + + /** Copy \c file to a random new file under the buffer temporary directory. + * + * \param file external file to be embedded + * \result an embedded file with random filename + */ + support::DocFileName embed(support::DocFileName const & file); + + /** register file as writable to .lyx file + * \param file embedded file that will be written to .lyx file + */ + void registerFile(support::DocFileName const & file) const; + + /// clear 'write to .lyx file' list, this should be done before + /// write a .lyx file. + void clearWriteList() const; + + /** read encoded embedded files and create them under the buffer tempoary directory + * \return \c false if file is not completely read + */ + bool read(Lexer & lex, ErrorList & errorList); + + /// write embedded files in base64 encoding + void write(std::ostream & ofs); + +private: + + class Impl; + Impl * const d; +}; + + +} // namespace lyx + +#endif // EMBEDDED_FILES Index: src/Buffer.cpp =================================================================== --- src/Buffer.cpp (revision 24712) +++ src/Buffer.cpp (working copy) @@ -25,6 +25,7 @@ #include "Converter.h" #include "Counters.h" #include "DocIterator.h" +#include "EmbeddedFiles.h" #include "Encoding.h" #include "ErrorList.h" #include "Exporter.h" @@ -168,6 +169,9 @@ /// mutable TocBackend toc_backend; + /// + mutable EmbeddedFiles embedded_files; + /// macro tables typedef pair<DocIterator, MacroData> ScopeMacro; typedef map<DocIterator, ScopeMacro> PositionScopeMacroMap; @@ -236,10 +240,11 @@ Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_) : parent_buffer(0), lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_), filename(file), file_fully_loaded(false), - toc_backend(&parent), macro_lock(false), timestamp_(0), - checksum_(0), wa_(0), undo_(parent) + toc_backend(&parent), embedded_files(), macro_lock(false), + timestamp_(0), checksum_(0), wa_(0), undo_(parent) { temppath = createBufferTmpDir(); + embedded_files.setTempPath(temppath.absFilename()); lyxvc.setBuffer(&parent); if (use_gui) wa_ = new frontend::WorkAreaManager; @@ -364,6 +369,12 @@ } +EmbeddedFiles & Buffer::embeddedFiles() const +{ + return d->embedded_files; +} + + Undo & Buffer::undo() { return d->undo_; @@ -561,8 +572,10 @@ } // read main text - bool const res = text().read(*this, lex, errorList, &(d->inset)); - + bool res = text().read(*this, lex, errorList, &(d->inset)); + // read embedded files + res = res || embeddedFiles().read(lex, errorList); + updateMacros(); updateMacroInstances(); return res; @@ -873,6 +886,11 @@ ofs.imbue(locale::classic()); #endif + // clear write list. Each inset, when being written to the .lyx file, + // can optionally register some files to be embedded, i.e., appended + // in base64 format to the .lyx file. + embeddedFiles().clearWriteList(); + // The top of the file should not be written by params(). // write out a comment in the top of the file @@ -903,6 +921,10 @@ text().write(*this, ofs); ofs << "\n\\end_body\n"; + ofs << "\n\\begin_embedded_files\n"; + embeddedFiles().write(ofs); + ofs << "\n\\end_embedded_files\n\n"; + // Write marker that shows file is complete ofs << "\\end_document" << endl;
embed.lyx
Description: application/lyx