Mesclando DOCX, XLSX e PDF em um único binder — demonstração

O que me consumia nas sextas-feiras

Todas as tardes de sexta-feira, por cerca de um ano, eu tinha o mesmo pequeno ritual. Um contrato chegava como três arquivos — o acordo principal em Word, um anexo de preços em Excel e a folha de termos de um parceiro como PDF — e eu precisava entregá‑los como um PDF limpo. Nada de difícil. Abrir o Word, exportar para PDF. Abrir o Excel, exportar para PDF. Abrir algum aplicativo gratuito de mesclagem de PDF, arrastar os três arquivos, conferir a ordem, salvar.

Levava talvez oito minutos. Multiplique isso por quinze contratos por semana e você perde duas horas só movendo o mouse. Pior, a cada poucas semanas alguém enviava um binder com o anexo na primeira página porque os nomes dos arquivos eram ordenados alfabeticamente no aplicativo de mesclagem.

Se isso soa familiar, o resto deste post é a tarde em que finalmente substituí o ritual por código.

O custo real não é o tempo — é aquele contrato em cada cinquenta onde as páginas ficam na ordem errada e ninguém percebe até o cliente assinar a versão errada.

O que eu realmente queria

Não um “pipeline de documentos sofisticado”. Apenas três coisas:

  1. Passar a um método uma lista de arquivos (qualquer combinação de DOCX, XLSX, PDF) e receber um PDF de volta.
  2. Apontar a mesma lógica para uma pasta e deixar que ela descubra a lista de arquivos sozinha.
  3. Extrair um intervalo de páginas do binder final sem refazer toda a mesclagem.

É isso. Se a biblioteca não conseguir fazer essas três coisas de forma limpa, não quero saber.

Configuração

  • .NET 6.0 ou superior
  • GroupDocs.Merger for .NET 24.10+ (obtenha uma licença temporária para não enviar a marca d’água de avaliação)
  • Uma pasta com a mistura de documentos que você normalmente mesclaria manualmente
dotnet add package GroupDocs.Merger

É só isso para dependências. Sem conversor externo, sem instalação headless do Office, sem biblioteca de manipulação de PDF adicional.

Etapa 1 — Deixe uma pasta ser a entrada

Sempre começo aqui porque é o ponto de entrada realista. Na prática, algo mais (um manipulador de upload, um job de ingestão de e‑mail, um dump noturno da contabilidade) coloca um monte de arquivos em um diretório, e meu código tem que lidar com o que encontrar.

// Pick up every supported file in the drop folder; the PDF wins
// the tie-break for position 0 so the merger keeps the output
// as a PDF regardless of how files are named.
string[] extensions = { ".pdf", ".docx", ".xlsx" };
var files = Directory.EnumerateFiles(folderPath)
    .Where(f => extensions.Contains(Path.GetExtension(f).ToLowerInvariant()))
    .OrderBy(f => Path.GetExtension(f).ToLowerInvariant() == ".pdf" ? 0 : 1)
    .ThenBy(f => f)
    .ToArray();

if (files.Length == 0)
    throw new InvalidOperationException(
        $"No supported documents found in '{folderPath}'.");

O truque do OrderBy é a parte interessante. O GroupDocs.Merger escolhe seu formato de saída a partir do primeiro arquivo que você abre — se eu passar um DOCX como documento principal, recebo um DOCX de saída. Como meu pipeline sempre quer um PDF, garanto que qualquer PDF existente na pasta receba a posição 0.

Duas coisas que valem a pena mencionar:

  • ToLowerInvariant() porque um parceiro pode, um dia, enviar REPORT.PDF e seu filtro que só aceita minúsculas descartará silenciosamente.
  • O ThenBy(f) está lá apenas para tornar a saída determinística. Sem ele, duas execuções na mesma pasta podem diferir dependendo do humor do sistema de arquivos.

Etapa 2 — A própria mesclagem

Depois de ter uma lista ordenada de caminhos, a mesclagem é mais curta que a descrição da mesclagem.

Console.WriteLine($"Primary source: {sourcePaths[0]}");
using var merger = new Merger(sourcePaths[0]);

var joinOptions = new JoinOptions();
for (int i = 1; i < sourcePaths.Length; i++)
{
    Console.WriteLine($"Joining: {sourcePaths[i]}");
    merger.Join(sourcePaths[i], joinOptions);
}

merger.Save(outputPath);
Console.WriteLine($"Unified PDF binder saved to: {Path.GetFullPath(outputPath)}");

Algumas observações de quem já usou isso em produção:

  • O using importa. Merger mantém handles de arquivo nas fontes; se você esquecer de descartá‑lo, o worker da pasta de drop acabará falhando ao tentar excluir seus próprios inputs.
  • JoinOptions está vazio aqui porque os padrões são o que eu quero 95 % das vezes. Quando precisar, é lá que vivem intervalos de páginas, rotação e posições de inserção.
  • Quando o Excel entra no binder, o layout de planilha‑para‑página é decidido pela área de impressão da planilha fonte. Se seu XLSX acabar em 38 páginas e você queria três, a correção está na planilha, não em JoinOptions.

Um teste de sanidade que sempre adiciono logo após o Save:

using var verify = new Merger(outputPath);
Console.WriteLine($"Result pages: {verify.GetDocumentInfo().PageCount}");

Dois segundos de código que pegaram mais bugs de “anexo silenciosamente descartado” do que qualquer teste que eu já escrevi.

Etapa 3 — Extrair um trecho depois

O pedido que recebo toda vez: “Você pode me enviar só a página de capa?” ou “O cliente só quer as assinaturas.” Reconstruir todo o binder só para entregar duas páginas é bobagem — a extração faz isso diretamente.

using var merger = new Merger(binderPath);
merger.ExtractPages(new ExtractOptions(pages));
merger.Save(outputPath);
Console.WriteLine($"Extracted pages [{string.Join(",", pages)}] to " +
    Path.GetFullPath(outputPath));

pages é um int[] de números de página baseados em 1 que você quer manter. Tudo o mais é descartado. É rápido porque o resultado já é um PDF — sem ida e volta de conversão.

Antes vs. depois, honestamente

Como eu fazia antes Com Merger.Join
Tempo por contrato 5–10 minutos de cliques menos de 30 segundos de ponta a ponta
Falha típica Páginas na ordem errada, ninguém percebe Qualquer ordem que a lista de arquivos indicar, de forma repetível
Escalando para 100/dia Não escala — você contrata uma pessoa Um worker, entediado na maior parte do tempo
Código que mantenho Uma página no Confluence intitulada “Binder Process v4” Uma classe, ~70 linhas
Saída Três PDFs e uma oração Um binder, com contagem de páginas que você pode registrar

A linha que mais me importa é a de “falha”. Mesclar manualmente falha silenciosamente; código que registra a contagem de páginas falha ruidosamente.

Uma história real de uma equipe tiny legal‑tech

Uma startup de duas pessoas com a qual trabalhei tinha uma assistente jurídica cujo dia começava com a montagem de contratos. Acordo em Word, precificação em Excel, adendo em PDF, tudo juntado em um app, enviado ao DocuSign. Cerca de oito minutos por pacote, o que em 30 pacotes por dia era basicamente toda a manhã dela.

Eles inseriram o método de varredura de pasta no serviço backend que já monitorava o e‑mail de entrada. Vinte segundos por pacote, mais uma linha de log com a contagem de páginas. A assistente passou a revisar contratos em vez de montá‑los. Ninguém enviou mais um binder fora de ordem — não porque a biblioteca seja mágica, mas porque a lista de arquivos está explícita no código e pode ser comparada.

string folder = @"C:\IncomingContracts";
string output = @"C:\Processed\ContractPackage.pdf";

var files = CreatePdfBinderFromFolder(folder, output);
Console.WriteLine($"Package created: {files}");

É a integração completa. Tudo que estava a montante (o listener de e‑mail, o caminho de armazenamento) já existia.

Coisas que não precisei hoje, mas que usarei amanhã

A mesma biblioteca faz um monte de coisas que não cobri porque o artigo ficaria muito longo. Aproximadamente na ordem em que as usei:

  • Watermarks on the output para carimbos “DRAFT” em cópias pré‑assinatura.
  • Page rotation para digitalizações que chegam de lado.
  • Custom page ordering quando a ordem de origem não é a ordem de entrega.
  • PDF encryption para tudo que vai para um contraparte externa.

Tudo isso está por trás da mesma API Merger. A docs tem a lista completa — eu só quis apontar que “merge” é o starter barato e o resto está disponível quando precisar.

O que eu diria ao meu eu do passado

Se você está prestes a escrever seu próprio passo DOCX‑to‑PDF porque “é só um método”, pare. A conversão é a parte que apodrece silenciosamente — novos recursos do Office, tratamento de imagens escaneadas, fontes embutidas, etc. Deixe outra coisa cuidar dessa superfície e gaste sua tarde de sexta‑feira em algo que não seja ordenação de nomes de arquivos.

Para onde ir a seguir: