2017-07-24 85 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

    相關問題