package com.outjected.email.impl.util;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.NodeTraversor;
import org.jsoup.select.NodeVisitor;

public class HtmlToPlainText {

    public static String convert(String html) {
        Document doc = Jsoup.parse(html);
        HtmlToPlainText formatter = new HtmlToPlainText();
        return formatter.getPlainText(doc);
    }

    /**
     * Format an Element to plain-text
     *
     * @param element the root element to format
     * @return formatted text
     */
    private String getPlainText(Element element) {
        FormattingVisitor formatter = new FormattingVisitor();
        NodeTraversor.traverse(formatter, element); // walk the DOM, and call .head() and .tail() for each node
        return formatter.toString();
    }

    // the formatting rules, implemented in a breadth-first DOM traverse
    private static class FormattingVisitor implements NodeVisitor {
        private static final int maxWidth = 80;
        private int width = 0;
        private StringBuilder accum = new StringBuilder(); // holds the accumulated text

        // hit when the node is first seen
        @Override public void head(Node node, int depth) {
            String name = node.nodeName();
            if (node instanceof TextNode) {
                if (node.parent().nodeName().equals("title")) {
                    //skip title;
                    return;
                }
                append(((TextNode) node).text()); // TextNodes carry all user-readable text in the DOM.
            }
            else if (name.equals("li")) {
                append("\n * ");
            }
            else if (name.equals("dt")) {
                append("  ");
            }
            else if (Strings.in(name, "p", "h1", "h2", "h3", "h4", "h5", "tr")) {
                append("\n");
            }
        }

        // hit when all of the node's children (if any) have been visited
        @Override public void tail(Node node, int depth) {
            String name = node.nodeName();
            if (Strings.in(name, "br", "dd", "dt", "p", "h1", "h2", "h3", "h4", "h5", "div")) {
                append("\n");
            }
            else if (name.equals("a")) {
                append(String.format(" (%s)", node.absUrl("href")));
            }
        }

        // appends text to the string builder with a simple word wrap method
        private void append(String text) {
            if (accum.length() == 0 && Strings.isNullOrBlank(text)) {
                return;
            }

            if (text.startsWith("\n")) {
                width = 0; // reset counter if starts with a newline. only from formats above, not in natural text
            }
            if (text.equals(" ") && (accum.length() == 0 || Strings.in(accum.substring(accum.length() - 1), " ", "\n"))) {
                return; // don't accumulate long runs of empty spaces
            }

            if (text.length() + width > maxWidth) { // won't fit, needs to wrap
                String[] words = text.split("\\s+", -1);
                for (int i = 0; i < words.length; i++) {
                    String word = words[i];
                    boolean last = i == words.length - 1;
                    if (!last) // insert a space if not the last word
                    {
                        word = word + " ";
                    }
                    if (word.length() + width > maxWidth) { // wrap and reset counter
                        accum.append("\n").append(word);
                        width = word.length();
                    }
                    else {
                        accum.append(word);
                        width += word.length();
                    }
                }
            }
            else { // fits as is, without need to wrap text
                accum.append(text);
                width += text.length();
            }
        }

        @Override public String toString() {
            return accum.toString();
        }
    }
}

@@ -1093,9 +1093,10 @@
 h = 
-8
+20
 0;%0A 
@@ -1540,32 +1540,257 @@
               %7D%0A
+                if (node.parentNode().nodeName().equals(%22p%22)) %7B%0A                    append(((TextNode) node).getWholeText()); // TextNodes carry all user-readable text in the DOM.%0A                %7D%0A                else %7B%0A    
                 
@@ -1869,32 +1869,50 @@
 ext in the DOM.%0A
+                %7D%0A
             %7D%0A  
@@ -2101,32 +2101,24 @@
    else if (
-Strings.
 in(name, %22p%22
@@ -2405,24 +2405,16 @@
     if (
-Strings.
 in(name,
@@ -2815,24 +2815,16 @@
 == 0 && 
-Strings.
 isNullOr
@@ -3111,16 +3111,8 @@
  %7C%7C 
-Strings.
 in(a
@@ -4324,24 +4324,24 @@
 toString();%0A
-
         %7D%0A  
@@ -4343,11 +4343,371 @@
  %7D%0A    %7D
+%0A%0A    private static boolean in(final String needle, final String... haystack) %7B%0A        for (String s : haystack) %7B%0A            if (s.equals(needle)) %7B%0A                return true;%0A            %7D%0A        %7D%0A        return false;%0A    %7D%0A%0A    private static boolean isNullOrBlank(String string) %7B%0A        return string == null %7C%7C string.trim().length() == 0;%0A    %7D
 %0A%7D%0A

improve html to plaintext conversion