using System.Text.Json; using System.Text.RegularExpressions; using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; using Omika.App.Controls; using Omika.Core.Contracts.Documents; using Omika.Core.Services; namespace Omika.Tests.Services; /// /// Tests de COMPATIBILITÉ du corps HTML des courriels WinDev ↔ Omika : /// /// WinDev → Omika (lecture) : le CSS/<head>/<style> ne sont /// jamais rendus comme texte ; les tables (tbody), le bloc photo imbriqué et les /// tableaux de contacts sont lus dans l'ordre. /// Omika → WinDev (écriture) : le HTML persisté est conservateur /// (<table>/<tr>/<td>/<p>/<img>), la photo est empilée /// (image puis date), les contacts sont en vraies cellules, sans flex/grid. /// Roundtrip : relecture du HTML Omika sans perte structurelle. /// /// public class EmailHtmlCompatibiliteTests { private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web); private static readonly byte[] OctetsJpeg = [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46]; // ------------------------------------------------------------------ // Helpers — lecture (parser) // ------------------------------------------------------------------ private static IReadOnlyList Parser(string html) => EmailHtmlParser.Parser(html); private static IEnumerable InlinesTous(IReadOnlyList blocs) { foreach (HtmlBlock bloc in blocs) { switch (bloc) { case HtmlParagraph p: foreach (HtmlInline i in p.Inlines) yield return i; break; case HtmlTable t: foreach (HtmlRow row in t.Rows) foreach (HtmlCell cell in row.Cells) foreach (HtmlInline i in cell.Inlines) yield return i; break; } } } private static List Textes(IReadOnlyList blocs) => InlinesTous(blocs).OfType().Select(r => r.Text).ToList(); private static string ToutLeTexte(string html) => string.Join("\n", Textes(Parser(html))); // ------------------------------------------------------------------ // Helpers — écriture (DOCX → HTML) // ------------------------------------------------------------------ /// DOCX : table 1 colonne / 2 lignes — ligne 2 = 3 paragraphes (image, date, note). private static byte[] CreerDocxPhotoBloc(string titre = "Photos") { using var stream = new MemoryStream(); using (WordprocessingDocument doc = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document)) { MainDocumentPart main = doc.AddMainDocumentPart(); main.Document = new Document(); Body body = new Body(); var table = new Table(); table.Append(new TableRow(new TableCell(new Paragraph(new Run(new Text(titre)))))); var cellule = new TableCell(); cellule.Append(new Paragraph(new Run(new Text("$TABPHOTO.IMAGE$")))); cellule.Append(new Paragraph(new Run(new Text("$TABPHOTO.DATEIMAGE$")))); cellule.Append(new Paragraph(new Run(new Text("$TABPHOTO.NOTEIMAGE$")))); table.Append(new TableRow(cellule)); body.Append(table); main.Document.Append(body); main.Document.Save(); } return stream.ToArray(); } /// DOCX : table contacts 5 colonnes (en-tête + 1 ligne de données). private static byte[] CreerDocxContacts() { using var stream = new MemoryStream(); using (WordprocessingDocument doc = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document)) { MainDocumentPart main = doc.AddMainDocumentPart(); main.Document = new Document(); Body body = new Body(); var table = new Table(); table.Append(new TableRow( Cell("$TABCTSITE.NOM$"), Cell("$TABCTSITE.TELFIXE$"), Cell("$TABCTSITE.TELFAXE$"), Cell("$TABCTSITE.ROLE$"), Cell("$TABCTSITE.ETAGE$"))); table.Append(new TableRow( Cell("Dupont Jean"), Cell("04 00 00 00 00"), Cell("06 00 00 00 00"), Cell("Occupant"), Cell("4"))); body.Append(table); main.Document.Append(body); main.Document.Save(); } return stream.ToArray(); static TableCell Cell(string texte) => new(new Paragraph(new Run(new Text(texte)))); } /// DOCX : bloc photo (image/date/note empilés) + tableau contacts (5 colonnes). private static byte[] CreerDocxPhotoEtContacts() { using var stream = new MemoryStream(); using (WordprocessingDocument doc = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document)) { MainDocumentPart main = doc.AddMainDocumentPart(); main.Document = new Document(); Body body = new Body(); // Bloc photo (1 colonne / 2 lignes ; ligne 2 = 3 paragraphes empilés). var photo = new Table(); photo.Append(new TableRow(new TableCell(new Paragraph(new Run(new Text("Photos")))))); var cellulePhoto = new TableCell(); cellulePhoto.Append(new Paragraph(new Run(new Text("$TABPHOTO.IMAGE$")))); cellulePhoto.Append(new Paragraph(new Run(new Text("$TABPHOTO.DATEIMAGE$")))); cellulePhoto.Append(new Paragraph(new Run(new Text("$TABPHOTO.NOTEIMAGE$")))); photo.Append(new TableRow(cellulePhoto)); body.Append(photo); // Contacts (5 colonnes : en-têtes littéraux + 1 ligne de données déjà résolue). var contacts = new Table(); contacts.Append(new TableRow( new TableCell(new Paragraph(new Run(new Text("Nom - Prénom")))), new TableCell(new Paragraph(new Run(new Text("Tel. 1")))), new TableCell(new Paragraph(new Run(new Text("Tel. 2")))), new TableCell(new Paragraph(new Run(new Text("Rôle")))), new TableCell(new Paragraph(new Run(new Text("Étage")))))); contacts.Append(new TableRow( new TableCell(new Paragraph(new Run(new Text("Dupont Jean")))), new TableCell(new Paragraph(new Run(new Text("04 00 00 00 00")))), new TableCell(new Paragraph(new Run(new Text("06 00 00 00 00")))), new TableCell(new Paragraph(new Run(new Text("Occupant")))), new TableCell(new Paragraph(new Run(new Text("4")))))); body.Append(contacts); main.Document.Append(body); main.Document.Save(); } return stream.ToArray(); } private static RapportChampDto TableauPhotos(string json) => new() { Id = "1", Rubrique = "$TABPHOTO$", Type = "Tableau", ListeDonnesBD = json, ListeMembre = [ new RapportChampSousElementDto { Rubrique = "$TABPHOTO.IMAGE$", Type = "ChpImage", TypeMembre = "ChpImage", MembreMob = "cheminComplet" }, new RapportChampSousElementDto { Rubrique = "$TABPHOTO.DATEIMAGE$", Type = "ChpSimpleDate", TypeMembre = "ChpSimpleDate", MembreMob = "saDate" }, new RapportChampSousElementDto { Rubrique = "$TABPHOTO.NOTEIMAGE$", Type = "ChpSimpleChaine", TypeMembre = "ChpSimpleChaine", MembreMob = "saNotes" } ] }; private static string RendreCorps(IReadOnlyList champs, byte[] docx, out IReadOnlyList images) { var liste = new List(); byte[] rendu = EmailDocxRenderer.Rendre(docx, champs, liste); images = liste; return EmailImageInliner.Inliner(DocxHtmlConverter.Convertir(rendu), liste); } private static string JsonUnePhoto() => JsonSerializer.Serialize(new[] { // Les propriétés JSON suivent la convention camelCase (après normalisation // MembreMob : saDate → date, saNotes → notes). new { cheminComplet = Convert.ToBase64String(OctetsJpeg), date = "30/01/2026", notes = "Note photo" } }, Json); // ================================================================== // §13 — WINDEV → OMIKA (lecture) // ================================================================== // TEST 1 :