--- name: docx description: "Criacao, edicao e analise de documentos Word (.docx). Usar quando o utilizador mencionar \"Word\", \".docx\", ou pedir documentos profissionais com formatacao (indices, cabecalhos, numeracao de paginas, letterheads). Inclui extracao de conteudo, insercao/substituicao de imagens, find-and-replace, tracked changes, comentarios, e conversao de conteudo para Word. Aplica-se a relatorios, memorandos, cartas, templates e propostas em formato .docx. NAO usar para PDFs, folhas de calculo, Google Docs, ou tarefas de codigo nao relacionadas." --- # Criacao, edicao e analise de documentos DOCX ## Visao geral Um ficheiro .docx e um arquivo ZIP contendo ficheiros XML. ## Referencia rapida | Tarefa | Abordagem | |--------|-----------| | Ler/analisar conteudo | `pandoc` ou descompactar para XML bruto | | Criar novo documento | Usar `docx-js` - ver seccao Criar Novos Documentos | | Editar documento existente | Descompactar -> editar XML -> recompactar - ver seccao Editar Documentos Existentes | ### Converter .doc para .docx Ficheiros legacy `.doc` devem ser convertidos antes de editar: ```bash python scripts/office/soffice.py --headless --convert-to docx document.doc ``` ### Ler conteudo ```bash # Extracao de texto com tracked changes pandoc --track-changes=all document.docx -o output.md # Acesso ao XML bruto python scripts/office/unpack.py document.docx unpacked/ ``` ### Converter para imagens ```bash python scripts/office/soffice.py --headless --convert-to pdf document.docx pdftoppm -jpeg -r 150 document.pdf page ``` ### Aceitar tracked changes Para produzir um documento limpo com todas as alteracoes aceites (requer LibreOffice): ```bash python scripts/accept_changes.py input.docx output.docx ``` --- ## Criar novos documentos Gerar ficheiros .docx com JavaScript, depois validar. Instalacao: `npm install -g docx` ### Setup ```javascript const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun, Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink, TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType, VerticalAlign, PageNumber, PageBreak } = require('docx'); const doc = new Document({ sections: [{ children: [/* conteudo */] }] }); Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer)); ``` ### Validacao Apos criar o ficheiro, validar. Se a validacao falhar, descompactar, corrigir o XML e recompactar. ```bash python scripts/office/validate.py doc.docx ``` ### Tamanho da pagina ```javascript // CRITICO: docx-js assume A4 por defeito // Definir sempre o tamanho da pagina explicitamente para resultados consistentes // Para documentos PT/EU, A4 e o padrao correcto sections: [{ properties: { page: { size: { width: 11906, // A4: 210mm em DXA height: 16838 // A4: 297mm em DXA }, margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 polegada de margens } }, children: [/* conteudo */] }] ``` **Tamanhos de pagina comuns (unidades DXA, 1440 DXA = 1 polegada):** | Papel | Largura | Altura | Largura conteudo (margens 1") | |-------|---------|--------|-------------------------------| | A4 (padrao PT/EU) | 11.906 | 16.838 | 9.026 | | US Letter | 12.240 | 15.840 | 9.360 | **Orientacao paisagem:** docx-js troca largura/altura internamente, por isso passar dimensoes retrato e deixar a biblioteca tratar: ```javascript size: { width: 11906, // Passar lado CURTO como width height: 16838, // Passar lado LONGO como height orientation: PageOrientation.LANDSCAPE // docx-js troca no XML }, // Largura de conteudo = 16838 - margem esquerda - margem direita (usa o lado longo) ``` ### Estilos (Override Built-in Headings) Usar Arial como fonte por defeito (suporte universal). Manter titulos a preto para legibilidade. ```javascript const doc = new Document({ styles: { default: { document: { run: { font: "Arial", size: 24 } } }, // 12pt por defeito paragraphStyles: [ // IMPORTANTE: Usar IDs exactos para override dos estilos built-in { id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true, run: { size: 32, bold: true, font: "Arial" }, paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } }, // outlineLevel necessario para TOC { id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true, run: { size: 28, bold: true, font: "Arial" }, paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } }, ] }, sections: [{ children: [ new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("Titulo")] }), ] }] }); ``` ### Listas (NUNCA usar bullets unicode) ```javascript // ERRADO - nunca inserir caracteres de bullet manualmente new Paragraph({ children: [new TextRun("* Item")] }) // MAU new Paragraph({ children: [new TextRun("\u2022 Item")] }) // MAU // CORRECTO - usar config numbering com LevelFormat.BULLET const doc = new Document({ numbering: { config: [ { reference: "bullets", levels: [{ level: 0, format: LevelFormat.BULLET, text: "\u2022", alignment: AlignmentType.LEFT, style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }, { reference: "numbers", levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT, style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }, ] }, sections: [{ children: [ new Paragraph({ numbering: { reference: "bullets", level: 0 }, children: [new TextRun("Item com bullet")] }), new Paragraph({ numbering: { reference: "numbers", level: 0 }, children: [new TextRun("Item numerado")] }), ] }] }); // ATENCAO: Cada reference cria numeracao INDEPENDENTE // Mesma reference = continua (1,2,3 depois 4,5,6) // Reference diferente = reinicia (1,2,3 depois 1,2,3) ``` ### Tabelas **CRITICO: Tabelas precisam de larguras duplas** - definir tanto `columnWidths` na tabela COMO `width` em cada celula. Sem ambos, tabelas renderizam incorrectamente em algumas plataformas. ```javascript // CRITICO: Definir sempre largura da tabela para renderizacao consistente // CRITICO: Usar ShadingType.CLEAR (nao SOLID) para evitar fundos pretos const border = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" }; const borders = { top: border, bottom: border, left: border, right: border }; new Table({ width: { size: 9026, type: WidthType.DXA }, // Sempre usar DXA (percentagens quebram no Google Docs) columnWidths: [4513, 4513], // Deve somar a largura da tabela (DXA: 1440 = 1 polegada) rows: [ new TableRow({ children: [ new TableCell({ borders, width: { size: 4513, type: WidthType.DXA }, // Tambem definir em cada celula shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, // CLEAR e nao SOLID margins: { top: 80, bottom: 80, left: 120, right: 120 }, // Padding da celula (interno, nao adicionado a largura) children: [new Paragraph({ children: [new TextRun("Celula")] })] }) ] }) ] }) ``` **Calculo de largura da tabela:** Usar sempre `WidthType.DXA` — `WidthType.PERCENTAGE` quebra no Google Docs. ```javascript // Largura da tabela = soma de columnWidths = largura do conteudo // A4 com margens de 1": 11906 - 2880 = 9026 DXA width: { size: 9026, type: WidthType.DXA }, columnWidths: [7000, 2026] // Deve somar a largura da tabela ``` **Regras de largura:** - **Usar sempre `WidthType.DXA`** — nunca `WidthType.PERCENTAGE` (incompativel com Google Docs) - Largura da tabela deve igualar a soma de `columnWidths` - `width` da celula deve corresponder ao respectivo `columnWidth` - `margins` da celula sao padding interno - reduzem a area de conteudo, nao adicionam a largura - Para tabelas full-width: usar largura de conteudo (largura pagina menos margens esquerda e direita) ### Imagens ```javascript // CRITICO: parametro type e OBRIGATORIO new Paragraph({ children: [new ImageRun({ type: "png", // Obrigatorio: png, jpg, jpeg, gif, bmp, svg data: fs.readFileSync("image.png"), transformation: { width: 200, height: 150 }, altText: { title: "Titulo", description: "Desc", name: "Nome" } // Os tres sao obrigatorios })] }) ``` ### Quebras de pagina ```javascript // CRITICO: PageBreak deve estar dentro de um Paragraph new Paragraph({ children: [new PageBreak()] }) // Ou usar pageBreakBefore new Paragraph({ pageBreakBefore: true, children: [new TextRun("Nova pagina")] }) ``` ### Indice (Table of Contents) ```javascript // CRITICO: Headings devem usar HeadingLevel APENAS - sem estilos custom new TableOfContents("Indice", { hyperlink: true, headingStyleRange: "1-3" }) ``` ### Cabecalhos/Rodapes ```javascript sections: [{ properties: { page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } } // 1440 = 1 polegada }, headers: { default: new Header({ children: [new Paragraph({ children: [new TextRun("Cabecalho")] })] }) }, footers: { default: new Footer({ children: [new Paragraph({ children: [new TextRun("Pagina "), new TextRun({ children: [PageNumber.CURRENT] })] })] }) }, children: [/* conteudo */] }] ``` ### Regras criticas para docx-js - **Definir tamanho de pagina explicitamente** - docx-js assume A4; para documentos PT/EU, A4 (11906 x 16838 DXA) e o padrao correcto - **Paisagem: passar dimensoes retrato** - docx-js troca largura/altura internamente; passar lado curto como `width`, lado longo como `height`, e definir `orientation: PageOrientation.LANDSCAPE` - **Nunca usar `\n`** - usar elementos Paragraph separados - **Nunca usar bullets unicode** - usar `LevelFormat.BULLET` com config numbering - **PageBreak deve estar em Paragraph** - standalone cria XML invalido - **ImageRun requer `type`** - especificar sempre png/jpg/etc - **Definir sempre `width` da tabela com DXA** - nunca usar `WidthType.PERCENTAGE` (quebra no Google Docs) - **Tabelas precisam de larguras duplas** - array `columnWidths` E `width` da celula, ambos devem corresponder - **Largura tabela = soma de columnWidths** - para DXA, garantir que somam exactamente - **Adicionar sempre margins nas celulas** - usar `margins: { top: 80, bottom: 80, left: 120, right: 120 }` para padding legivel - **Usar `ShadingType.CLEAR`** - nunca SOLID para shading de tabelas - **TOC requer HeadingLevel apenas** - sem estilos custom nos paragrafos de heading - **Override built-in styles** - usar IDs exactos: "Heading1", "Heading2", etc. - **Incluir `outlineLevel`** - necessario para TOC (0 para H1, 1 para H2, etc.) --- ## Editar documentos existentes **Seguir os 3 passos por ordem.** ### Passo 1: Descompactar ```bash python scripts/office/unpack.py document.docx unpacked/ ``` Extrai XML, pretty-prints, funde runs adjacentes e converte smart quotes para entidades XML (`“` etc.) para sobreviverem a edicao. Usar `--merge-runs false` para saltar a fusao de runs. ### Passo 2: Editar XML Editar ficheiros em `unpacked/word/`. Ver Referencia XML abaixo para padroes. **Usar "Claude" como autor** para tracked changes e comentarios, salvo se o utilizador pedir outro nome. **Usar a ferramenta Edit directamente para substituicao de strings. Nao escrever scripts Python.** Scripts introduzem complexidade desnecessaria. A ferramenta Edit mostra exactamente o que esta a ser substituido. **CRITICO: Usar smart quotes para novo conteudo.** Ao adicionar texto com apostrofos ou aspas, usar entidades XML para produzir smart quotes: ```xml Here’s a quote: “Hello” ``` | Entidade | Caractere | |----------|-----------| | `‘` | ' (aspas simples esquerda) | | `’` | ' (aspas simples direita / apostrofo) | | `“` | " (aspas duplas esquerda) | | `”` | " (aspas duplas direita) | **Adicionar comentarios:** Usar `comment.py` para tratar boilerplate em multiplos ficheiros XML (texto deve ser XML pre-escaped): ```bash python scripts/comment.py unpacked/ 0 "Texto do comentario com & e ’" python scripts/comment.py unpacked/ 1 "Texto de resposta" --parent 0 # resposta ao comentario 0 python scripts/comment.py unpacked/ 0 "Texto" --author "Autor Custom" # nome de autor custom ``` Depois adicionar marcadores ao document.xml (ver Comentarios na Referencia XML). ### Passo 3: Recompactar ```bash python scripts/office/pack.py unpacked/ output.docx --original document.docx ``` Valida com auto-reparacao, condensa XML e cria DOCX. Usar `--validate false` para saltar. **Auto-reparacao corrige:** - `durableId` >= 0x7FFFFFFF (regenera ID valido) - `xml:space="preserve"` em falta em `` com whitespace **Auto-reparacao nao corrige:** - XML malformado, nesting de elementos invalido, relacoes em falta, violacoes de schema ### Erros comuns - **Substituir elementos `` inteiros**: Ao adicionar tracked changes, substituir o bloco `...` completo com `......` como siblings. Nao injectar tags de tracked change dentro de um run. - **Preservar formatacao ``**: Copiar o bloco `` do run original para os runs de tracked change para manter bold, tamanho de fonte, etc. --- ## Referencia XML ### Conformidade com schema - **Ordem de elementos em ``**: ``, ``, ``, ``, ``, `` por ultimo - **Whitespace**: Adicionar `xml:space="preserve"` a `` com espacos no inicio/fim - **RSIDs**: Devem ser hex de 8 digitos (ex: `00AB1234`) ### Tracked Changes **Insercao:** ```xml texto inserido ``` **Eliminacao:** ```xml texto eliminado ``` **Dentro de ``**: Usar `` em vez de ``, e `` em vez de ``. **Edicoes minimas** - marcar apenas o que muda: ```xml O prazo e de 30 60 dias. ``` **Eliminar paragrafos/itens de lista inteiros** - ao remover TODO o conteudo de um paragrafo, marcar tambem o paragraph mark como eliminado para fundir com o proximo paragrafo. Adicionar `` dentro de ``: ```xml ... Conteudo inteiro do paragrafo a ser eliminado... ``` Sem o `` em ``, aceitar alteracoes deixa um paragrafo/item de lista vazio. **Rejeitar insercao de outro autor** - aninhar eliminacao dentro da insercao: ```xml texto inserido por ela ``` **Restaurar eliminacao de outro autor** - adicionar insercao depois (nao modificar a eliminacao): ```xml texto eliminado texto eliminado ``` ### Comentarios Apos executar `comment.py` (ver Passo 2), adicionar marcadores ao document.xml. Para respostas, usar flag `--parent` e aninhar marcadores dentro dos do pai. **CRITICO: `` e `` sao siblings de ``, nunca dentro de ``.** ```xml eliminado mais texto texto ``` ### Imagens 1. Adicionar ficheiro de imagem a `word/media/` 2. Adicionar relacao a `word/_rels/document.xml.rels`: ```xml ``` 3. Adicionar content type a `[Content_Types].xml`: ```xml ``` 4. Referenciar no document.xml: ```xml ``` --- ## Dependencias - **pandoc**: Extracao de texto - **docx**: `npm install -g docx` (novos documentos) - **LibreOffice**: Conversao PDF (auto-configurado para ambientes sandbox via `scripts/office/soffice.py`) - **Poppler**: `pdftoppm` para imagens --- ## Integracao Descomplicar ### MCPs disponiveis | MCP | Uso | |-----|-----| | `mcp__filesystem__*` | Ler/escrever ficheiros .docx locais, listar directorios | | `mcp__google-workspace__*` | Upload para Google Drive, conversao para Google Docs | | `mcp__ssh-unified__*` | Operacoes em ficheiros .docx em servidores remotos | ### Workflow tipico 1. **Criar documento** com docx-js conforme instrucoes acima 2. **Validar** com `scripts/office/validate.py` 3. **Guardar** via `mcp__filesystem__write_file` ou directamente no path do projecto 4. **Upload** (opcional) via `mcp__google-workspace__drive_upload_file` para partilha com cliente ### Convencoes Descomplicar - **Formato de data** em documentos: DD-MM-YYYY (ex: 06-03-2026) - **Formato monetario**: 1.234,56 EUR (separador milhar ponto, decimal virgula) - **Fonte padrao**: Arial 12pt - **Tamanho pagina padrao**: A4 (11906 x 16838 DXA) - **Autor para tracked changes**: "Claude" (salvo indicacao contraria) - **Idioma do documento**: PT-PT salvo indicacao contraria --- **Versao**: 1.0.0 | **Autor**: Descomplicar®