2017-07-24 88 views
5

我在服务器上使用GridFS存储Word(.docx)文件。我希望能够通过使用docx-builder NPM软件包将文档合并到一个Word文件中。如何在Meteor中执行服务器端文件处理操作?

这里是如何,我上传文件:

Meteor.methods({ 
    uploadFiles: function (files) { 
     check(files, [Object]); 

     if (files.length < 1) 
     throw new Meteor.Error("invalid-files", "No files were uploaded"); 

     var documentPaths = []; 

     _.each(files, function (file) { 
     ActivityFiles.insert(file, function (error, fileObj) { 
      if (error) { 
      console.log("Could not upload file"); 
      } else { 
      documentPaths.push("/cfs/files/activities/" + fileObj._id); 
      } 
     }); 
     }); 

     return documentPaths; 
    } 
}) 

我如何去在服务器端做这个?我只能做这个服务器端,因为我使用的包需要fs包,不能执行客户端。

下面是我目前正在尝试的工作。从客户端,我打电话给下面的方法(这被声明为Meteor.method):

print: function(programId) { 
    // Get the program by ID. 
    var program = Programs.findOne(programId); 
    // Create a new document. 
    var docx = new docxbuilder.Document(); 
    // Go through all activities in the program. 
    program.activityIds.forEach(function(activityId) { 
    // Create a temporary server side folder to store activity files. 
    const tempDir = fs.mkdtempSync('/tmp/'); 
    // Get the activity by ID. 
    var activity = Activities.findOne(activityId); 
    // Get the document by ID. 
    var document = ActivityFiles.findOne(activity.documents.pop()._id); 
    // Declare file path to where file will be read. 
    const filePath = tempDir + sep + document.name(); 
    // Create stream to write to path. 
    const fileStream = fs.createWriteStream(filePath); 
    // Read from document, write to file. 
    document.createReadStream().pipe(fileStream); 
    // Insert into final document when finished writinf to file. 
    fileStream.on('finish',() => { 
     docx.insertDocxSync(filePath); 
     // Delete file when operation is completed. 
     fs.unlinkSync(filePath); 
    }); 
    }); 
    // Save the merged document. 
    docx.save('/tmp' + sep + 'output.docx', function (error) { 
    if (error) { 
     console.log(error); 
    } 
    // Insert into Collection so client can access merged document. 
    Fiber = Npm.require('fibers'); 
    Fiber(function() { 
     ProgramFiles.insert('/tmp' + sep + 'output.docx'); 
    }).run(); 
    }); 
} 

然而,当我从ProgramFiles收集在客户端下载的最后文件,该文件是一个空的Word文档。

这里怎么回事?


我已将@ FrederickStark的答案纳入我的代码。现在就停留在这部分。


这里的另一种尝试:

'click .merge-icon': (e) => { 
    var programId = Router.current().url.split('/').pop(); 
    var programObj = Programs.findOne(programId); 
    var insertedDocuments = []; 
    programObj.activityIds.forEach(function(activityId) { 
     var activityObj = Activities.findOne(activityId); 
     var documentObj = ActivityFiles.findOne(activityObj.documents.pop()._id); 
     JSZipUtils.getBinaryContent(documentObj.url(), callback); 
     function callback(error, content) { 
     var zip = new JSZip(content); 
     var doc = new Docxtemplater().loadZip(zip); 
     var xml = zip.files[doc.fileTypeConfig.textPath].asText(); 
     xml = xml.substring(xml.indexOf("<w:body>") + 8); 
     xml = xml.substring(0, xml.indexOf("</w:body>")); 
     xml = xml.substring(0, xml.indexOf("<w:sectPr")); 
     insertedDocuments.push(xml); 
     } 
    }); 
    JSZipUtils.getBinaryContent('/assets/template.docx', callback); 
    function callback(error, content) { 
     var zip = new JSZip(content); 
     var doc = new Docxtemplater().loadZip(zip); 
     console.log(doc); 
     setData(doc); 
    } 


    function setData(doc) { 
     doc.setData({ 
     // Insert blank line between contents. 
     inserted_docs_formatted: insertedDocuments.join('<w:br/><w:br/>') 
     // The template file must use a `{@inserted_docs_formatted}` placeholder 
     // that will be replaced by the above value. 
     }); 

     doc.render(); 

     useResult(doc); 
    } 

    function useResult(doc) { 
     var out = doc.getZip().generate({ 
     type: 'blob', 
     mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' 
     }); 
     saveAs(out, 'output.docx'); 
    } 
+1

你尝试过什么到目前为止对话框(可选)?你应该能够从数据库查询文件并将它们传递到docx-builder –

+1

@FrederickStark我已经添加了我的工作。 –

回答

3

望着文档为docx-builder,它仅支持从文件系统中读取DOCX文件。调用document.url()的问题是,它为您提供了一个可以通过http访问的url,而不是文件系统上的路径。

所以要使用GridFS,您首先需要将文件写入临时文件夹,然后docx-builder才能读取它们。

import fs from 'fs'; 
import { sep } from 'path'; 
const tempDir = fs.mkdtempSync('/tmp/' + sep); 

program.activityIds.forEach(function(activityId) { 
    var activity = Activities.findOne(activityId); 
    console.log(activity); 
    var document = ActivityFiles.findOne(activity.documents.pop()._id); 
    documents.push(document); 

    // Build a file path in the temp folder 
    const filePath = tempDir + sep + document.name(); 

    // Write the document to the file system using streams 
    const fileStream = fs.createWriteStream(filePath); 
    document.createReadStream().pipe(fileStream); 

    // When the stream has finished writing the file, add it to your docx 
    fileStream.on('finish',() => { 
    console.log(filePath); 
    docx.insertDocxSync(filePath); 
    // Delete the file after you're done 
    fs.unlinkSync(filePath); 
    }); 

}); 

我怀疑你可以做到这一点同步使用fs.writeFileSync(filePath, document.data),但不知道,所以没有在本例中使用它。

或者,您可以查找可以支持从流或缓冲区中读取的docx包,然后您将不需要临时文件。

+0

这真棒。它非常有帮助。就一个问题。一旦我完成合并,并使用'docx.save()'写入'/ tmp /'目录中的另一个文件,我该如何让该文件可供客户端下载?我想我想说的是,我如何公开最后一个'docx.save()'创建的文件。谢谢。 –

+1

我会把它加回到GridFS的Files集合中,然后用'document.url()' –

+1

得到可下载的URL。另外,忘记提及你可以通过替换'fooColleciton.find(fooId) .fetch()。pop()'带'fooCollection.findOne(fooId)'。 'findOne'将立即返回带有该ID的文档,而不必获取数组,然后弹出唯一的项目 –

3

我只能做这个服务器端,因为我使用的包需要fs包,不能执行客户端。

虽然它是真实的docx-builder库,您当前使用取决于fs(因此在节点上的环境),也可以直接使用它的依赖docxtemplater,并使用它仅在客户端(浏览器)的一面。

非常类似于docx-builder的作品,您从一个“标准”模板文件开始,您可以将本地docx文件合并到其中,然后生成生成的docx文件并将其保存到本地或将其发送到您的服务器。

实现你想要做的事情的基本步骤(即:合并的docx文件),但在客户端将是:

  1. 检索在客户端文件,如果它们在网络中已经存在,或者让用户选择通过文件类型输入和HTML5 File API从本地文件系统的文件:
<input type="file" id="fileInput" /> 
  • 读取文件内容和提取其体,如在docx-builder完成:
  • var insertedDocsFormatted = []; 
    
    // If the file is locally provided through File type input: 
    document.getElementById('fileInput').addEventListener('change', function (evt) { 
        var file = evt.currentTarget.files[0], 
         fr = new FileReader(); 
    
        fr.onload = function() { 
        insertDocContent(fr.result); 
        }; 
        fr.readAsArrayBuffer(file); 
    }); 
    
    // Or if the file already exists on a server: 
    JSZipUtils.getBinaryContent(url, function (err, content) { 
        insertDocContent(content); 
    }); 
    
    function insertDocContent(content) { 
        var zip = new JSZip(content), 
         doc = new Docxtemplater().loadZip(zip); 
    
        var xml = zip.files[doc.fileTypeConfig.textPath].asText(); 
    
        // Inspired from https://github.com/raulbojalil/docx-builder 
        xml = xml.substring(xml.indexOf("<w:body>") + 8); 
        xml = xml.substring(0, xml.indexOf("</w:body>")); 
        xml = xml.substring(0, xml.indexOf("<w:sectPr")); 
    
        // Keep for later use. 
        insertedDocsFormatted.push(xml); 
    } 
    
  • 一旦所有被合并的处理的文件中,加载起动机模板文件
  • // 'template.docx' is a static file on your server (e.g. in `public/` folder) 
    // But it could even be replaced by a user locally provided file, 
    // as done in step 2 above. 
    JSZipUtils.getBinaryContent('template.docx', callback); 
    
    function callback(err, content) { 
        var zip = new JSZip(content); 
        var doc = new Docxtemplater().loadZip(zip); 
    
        setData(doc); 
    } 
    
  • 加入内容并定义一个格式化的数据键,以便将其插入到模板文件中,然后渲染文档:
  • function setData(doc) { 
        doc.setData({ 
        // Insert blank line between contents. 
        inserted_docs_formatted: insertedDocsFormatted.join('<w:br/><w:br/>') 
        // The template file must use a `{@inserted_docs_formatted}` placeholder 
        // that will be replaced by the above value. 
        }); 
    
        doc.render(); 
    
        useResult(doc); 
    } 
    
  • 要么提示 “另存为”对话框或发送文件(BLOB)到服务器。
  • function useResult(doc) { 
        var out = doc.getZip().generate({ 
        type: 'blob', 
        mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' 
        }); 
        saveAs(out, 'output.docx'); 
    } 
    

    在线演示:https://ghybs.github.io/docx-builder-demo/(无服务器侧处理可言,纯客户端逻辑)

    源代码:https://github.com/ghybs/docx-builder-demo

    库:

    +0

    您能否详细介绍一下如何使用存储在'CollectionFS'中的文件做第2部分?我应该在哪里传入'document.url()'来加载我从我的集合中获得的文件(文档)? –

    +0

    到'JSZipUtils.getBinaryContent(document.url(),callbackForInsertedDoc)'(显然,使用与模板不同的回调) – ghybs

    +0

    请参阅已更新答案中的第2步。 – ghybs

    相关问题