2017-07-24 48 views
5

Przechowuję pliki Word (.docx) przy użyciu GridFS na serwerze. Chciałbym móc scalić dokumenty w jeden plik Word przy użyciu pakietu NPM docx-builder.Jak wykonać operacje obsługi plików po stronie serwera w Meteor?

Oto jak mam przesłać pliki:

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; 
    } 
}) 

Jak mogę to zabrać po stronie serwera? Mogę wykonać tylko tę stronę serwera, ponieważ pakiet, którego używam, wymaga pakietu fs, którego nie można wykonać po stronie klienta.

Oto jak próbuję pracować nad tym w tej chwili. Od klienta, dzwonię w następujący sposób (który jest zadeklarowany jako 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(); 
    }); 
} 

Jednak kiedy jestem pobierając dokument końcowy z kolekcji ProgramFiles po stronie klienta, dokument jest pusty dokument programu Word.

Co tu jest nie tak?


mam włączone @ odpowiedź FrederickStark jest do mojego kodu. Właśnie utknąłem na tej części teraz.


Oto kolejna próba:

'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

Co pan próbował do tej pory? Powinieneś po prostu przesyłać zapytania do plików z db i przekazywać je do docx-builder –

+1

@FrederickStark Dodałem moje prace. –

Odpowiedz

3

Patrząc na docs dla docx-builder, że obsługuje tylko odczyt plików docx z systemu plików. Problem z wywołaniem document.url() polega na tym, że daje on adres URL, do którego można uzyskać dostęp przez http, a nie ścieżkę do systemu plików.

Aby móc korzystać z GridFS, najpierw musisz zapisać pliki w folderze tymczasowym, zanim docx-builder będzie je mógł odczytać.

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); 
    }); 

}); 

Podejrzewam, można to zrobić za pomocą fs.writeFileSync(filePath, document.data) synchronicznie, ale nie był pewien, więc nie korzystać z niego w przykładzie.

Alternatywnie można szukać pakietu docx, który obsługuje odczyt ze strumienia lub bufora, a następnie nie będą potrzebne pliki tymczasowe.

+0

To jest niesamowite. To naprawdę jest niezmiernie pomocne. Tylko pytanie. Kiedy już się scalę i używam 'docx.save()' do zapisu do innego pliku w katalogu '/ tmp /', jak mogę udostępnić ten plik klientowi do pobrania? Domyślam się, co próbuję powiedzieć, w jaki sposób odsłonić ostateczny plik, który tworzy "docx.save()". Dziękuję Ci. –

+1

Chciałbym dodać go z powrotem do kolekcji plików z GridFS, a następnie uzyskać adres URL do pobrania z 'document.url()' –

+1

Ponadto, zapomniałem wspomnieć, że można wyczyścić kod, zastępując 'fooColleciton.find (fooId) .fetch(). pop() 'z' fooCollection.findOne (fooId) '. 'findOne' natychmiast zwróci dokument z tym identyfikatorem, zamiast pobierać tablicę, a następnie wyskakuje tylko jeden element. –

3

Mogę wykonać tylko tę stronę serwera, ponieważ pakiet, którego używam, wymaga pakietu fs, którego nie można wykonać po stronie klienta.

O ile prawdą jest, że biblioteka docx-builder że używasz obecnie zależy fs (stąd w środowisku Node), możliwe jest, aby używać bezpośrednio jej zależność docxtemplater i używać go tylko na klienta (przeglądarki) stronie.

Działa bardzo podobnie do docx-builder, zaczynając od "standardowego" pliku szablonu i można scalić do niego lokalne pliki docx, a następnie wygenerować wynikowy plik docx i zapisać go lokalnie lub wysłać na serwer.

Zasadnicze kroki w celu osiągnięcia tego, co próbujesz zrobić (tj.scalanie plików docx), ale na Klienta strona byłoby:

  1. Pobieranie plików po stronie klienta jeśli one już istnieją w sieci, lub pozwolić użytkownikowi wybrać pliki z lokalnego systemu plików poprzez typ pliku wejściowego i HTML5 File API:
<input type="file" id="fileInput" /> 
  1. odczytanie zawartości pliku i wyodrębniania jej korpus, jak to zrobiono w 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); 
} 
  1. Gdy wszystkie pliki być scalone są przetwarzane, obciążenie rozrusznika szablonu plik:
// '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); 
} 
  1. Dołącz do zawartości i zdefiniuj klucz danych z formatowaniem, aby wstawić go do pliku szablonu, a następnie wyrenderować dokument:
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); 
} 
  1. Albo skłonić "zapisz jako" dialogowym lub wysłać plik (blob) na serwer.
function useResult(doc) { 
    var out = doc.getZip().generate({ 
    type: 'blob', 
    mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' 
    }); 
    saveAs(out, 'output.docx'); 
} 

demo online: https://ghybs.github.io/docx-builder-demo/ (brak serwera przetwarzanie strona w ogóle, czystej logiki po stronie klienta)

kod

Źródło: https://github.com/ghybs/docx-builder-demo

Biblioteki:

+0

Czy mógłbyś trochę wyjaśnić, w jaki sposób zrobiłbyś część 2 z plikami przechowywanymi w 'CollectionFS'? Gdzie powinienem przekazać w 'document.url()', aby załadować pliki (dokumenty), które otrzymuję z mojej kolekcji? –

+0

Do 'JSZipUtils.getBinaryContent (document.url(), callbackForInsertedDoc) (użyj oczywiście innego wywołania zwrotnego niż z szablonu) – ghybs

+0

Zobacz krok 2 w zaktualizowanej odpowiedzi. – ghybs