package com.megatim.manuel.core.convert;
|
|
import org.apache.poi.hwpf.HWPFDocument;
|
import org.apache.poi.hwpf.model.StyleSheet;
|
import org.apache.poi.hwpf.usermodel.*;
|
|
import java.io.FileInputStream;
|
import java.io.IOException;
|
import java.nio.file.Path;
|
import java.util.ArrayList;
|
import java.util.List;
|
|
/**
|
* Word .doc hérité (HWPF) -> HTML : gras/italique/souligné/barré, couleur, police, taille,
|
* alignement, titres (Heading/Titre 1..6), listes et tableaux.
|
*/
|
public class DocConverter extends AbstractHtmlConverter {
|
|
public DocConverter(Path outputDir) {
|
super(outputDir);
|
}
|
|
@Override
|
public ConversionResult convert(Path source, int order) throws IOException {
|
String safe = safeBase(source, order);
|
String htmlName = safe + ".html";
|
List<Heading> headings = new ArrayList<>();
|
StringBuilder body = new StringBuilder();
|
int[] hc = {0};
|
|
try (FileInputStream fis = new FileInputStream(source.toFile());
|
HWPFDocument doc = new HWPFDocument(fis)) {
|
|
Range range = doc.getRange();
|
StyleSheet styles = doc.getStyleSheet();
|
|
HeaderStories hs = new HeaderStories(doc);
|
body.append(headerFooterDiv(
|
firstNonBlank(hs.getOddHeader(), hs.getFirstHeader(), hs.getEvenHeader()), true));
|
|
boolean inList = false;
|
|
int i = 0;
|
while (i < range.numParagraphs()) {
|
Paragraph p = range.getParagraph(i);
|
|
if (p.isInTable()) {
|
if (inList) { body.append("</ul>\n"); inList = false; }
|
Table table = range.getTable(p);
|
renderTable(table, body);
|
int end = table.getEndOffset();
|
i++;
|
while (i < range.numParagraphs() && range.getParagraph(i).getStartOffset() < end) i++;
|
continue;
|
}
|
|
boolean isList = p.getIlfo() != 0;
|
if (isList && !inList) { body.append("<ul>\n"); inList = true; }
|
if (!isList && inList) { body.append("</ul>\n"); inList = false; }
|
|
renderParagraph(styles, p, body, headings, hc, safe, isList);
|
i++;
|
}
|
if (inList) body.append("</ul>\n");
|
|
body.append(headerFooterDiv(
|
firstNonBlank(hs.getOddFooter(), hs.getFirstFooter(), hs.getEvenFooter()), false));
|
}
|
|
String title = stripExt(source.getFileName().toString());
|
for (Heading h : headings) {
|
if (h.level <= 1 && !h.text.isBlank()) { title = h.text.trim(); break; }
|
}
|
|
writeHtml(htmlName, title, body.toString());
|
return new ConversionResult(htmlName, title, headings, new ArrayList<>());
|
}
|
|
private void renderParagraph(StyleSheet styles, Paragraph p, StringBuilder out,
|
List<Heading> headings, int[] hc, String safe, boolean isList) {
|
int level = headingLevel(styles, p);
|
String inner = renderRuns(p);
|
String align = alignOf(p);
|
|
if (level > 0) {
|
String anchor = safe + "_h" + (++hc[0]);
|
headings.add(new Heading(cleanText(p.text()), level, anchor));
|
out.append("<h").append(level).append(" id=\"").append(anchor).append("\"").append(align)
|
.append(">").append(inner).append("</h").append(level).append(">\n");
|
} else if (isList) {
|
out.append("<li").append(align).append(">").append(inner.isEmpty() ? " " : inner).append("</li>\n");
|
} else if (inner.trim().isEmpty()) {
|
out.append("<p class=\"sp\"> </p>\n");
|
} else {
|
out.append("<p").append(align).append(">").append(inner).append("</p>\n");
|
}
|
}
|
|
private void renderTable(Table table, StringBuilder out) {
|
out.append("<table class=\"wt\">\n");
|
for (int r = 0; r < table.numRows(); r++) {
|
TableRow row = table.getRow(r);
|
out.append("<tr>");
|
for (int c = 0; c < row.numCells(); c++) {
|
TableCell cell = row.getCell(c);
|
out.append("<td>");
|
for (int pi = 0; pi < cell.numParagraphs(); pi++) {
|
String inner = renderRuns(cell.getParagraph(pi));
|
out.append("<div>").append(inner.isEmpty() ? " " : inner).append("</div>");
|
}
|
out.append("</td>");
|
}
|
out.append("</tr>\n");
|
}
|
out.append("</table>\n");
|
}
|
|
private String renderRuns(Paragraph p) {
|
StringBuilder sb = new StringBuilder();
|
for (int i = 0; i < p.numCharacterRuns(); i++) {
|
CharacterRun r = p.getCharacterRun(i);
|
String text = cleanText(r.text());
|
if (text.isEmpty()) continue;
|
|
StringBuilder style = new StringBuilder();
|
String hex = colorHex(r);
|
if (hex != null) style.append("color:#").append(hex).append(';');
|
String font = r.getFontName();
|
if (font != null && !font.isEmpty()) style.append("font-family:'").append(font.replace("'", "")).append("';");
|
int halfPts = r.getFontSize();
|
if (halfPts > 0) style.append("font-size:").append(halfPts / 2).append("pt;");
|
|
StringBuilder open = new StringBuilder();
|
StringBuilder close = new StringBuilder();
|
if (r.isBold()) { open.append("<b>"); close.insert(0, "</b>"); }
|
if (r.isItalic()) { open.append("<i>"); close.insert(0, "</i>"); }
|
if (r.getUnderlineCode() != 0) { open.append("<u>"); close.insert(0, "</u>"); }
|
if (r.isStrikeThrough()) { open.append("<s>"); close.insert(0, "</s>"); }
|
|
String content = esc(text);
|
String span = style.length() > 0 ? "<span style=\"" + style + "\">" + content + "</span>" : content;
|
sb.append(open).append(span).append(close);
|
}
|
return sb.toString();
|
}
|
|
private int headingLevel(StyleSheet styles, Paragraph p) {
|
try {
|
int idx = p.getStyleIndex();
|
if (styles != null && idx >= 0 && idx < styles.numStyles()) {
|
return matchHeading(styles.getStyleDescription(idx).getName());
|
}
|
} catch (Exception ignore) { /* style introuvable */ }
|
return 0;
|
}
|
|
/** getIco24() renvoie une COLORREF (0x00BBGGRR). On la convertit en RRGGBB, ou null si absente/noir. */
|
private String colorHex(CharacterRun r) {
|
try {
|
int c = r.getIco24();
|
if (c < 0) return null;
|
int red = c & 0xFF;
|
int green = (c >> 8) & 0xFF;
|
int blue = (c >> 16) & 0xFF;
|
if (red == 0 && green == 0 && blue == 0) return null; // noir par défaut : on n'impose rien
|
return String.format("%02X%02X%02X", red, green, blue);
|
} catch (Exception ignore) {
|
return null;
|
}
|
}
|
|
private String alignOf(Paragraph p) {
|
switch (p.getJustification()) {
|
case 1: return " style=\"text-align:center\"";
|
case 2: return " style=\"text-align:right\"";
|
case 3: return " style=\"text-align:justify\"";
|
default: return "";
|
}
|
}
|
|
/** Bloc HTML d'en-tête/pied de page (texte brut, une ligne = un paragraphe). */
|
private String headerFooterDiv(String text, boolean isHeader) {
|
if (text == null) return "";
|
StringBuilder sb = new StringBuilder();
|
for (String line : text.split("[\\r\\n\\u000B\\u000C]+")) {
|
String c = cleanText(line);
|
if (!c.isEmpty()) sb.append("<p>").append(esc(c)).append("</p>\n");
|
}
|
if (sb.length() == 0) return "";
|
return "<div class=\"" + (isHeader ? "page-header" : "page-footer") + "\">\n" + sb + "</div>\n";
|
}
|
|
private static String firstNonBlank(String... values) {
|
for (String v : values) {
|
if (v != null && !cleanText(v).isEmpty()) return v;
|
}
|
return null;
|
}
|
|
/** Retire les marqueurs de contrôle Word (fin de cellule, champs, sauts...). */
|
private static String cleanText(String s) {
|
if (s == null) return "";
|
return s.replace('\r', ' ')
|
.replaceAll("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F]", " ")
|
.trim();
|
}
|
}
|