// nerdtext - Chris X Edwards - 2021-10-07
// A text document markup system for technical documentation.
// 
// License: GPLv3 or later. See end for complete notice.
// g++  -o nerdtext nerdtext.cxx && ./nerdtext nerdtext.cxx
#include <iostream>
#include <getopt.h>
#include <cstring>
#include <fstream>
#include <list>
#include <map>
#include <vector>
#include <algorithm>
#define FULLDOCUMENT true
using namespace std;
// To generate this (not from Vim!): hg log -l1 --template "$(date +%Y-%m-%d) - Version: {rev+1}"
const string VERSION= "2022-01-28 - Version: 133";

/* ============ Global Settings ============ */
int DEBUG_ENABLED=0; // Different levels possible with 0, the default being none.
bool EMBED=false;  // Is the output just an exceprt inserted into another fully formatted document?
bool QUIET= false;   // Supress warnings? 

/* ============ Class Declarations ============ */
class Config {                                                                 // == Defining ==
    public:
        Config(const int argc,char **argv); // Note default ctor works too; it does not parse.
        void parseopts(const int &argc,char **argv); // This function actually does the parsing.
        string optval(char c,string &val); // Use a short opt letter to return its string.
        int optval(char c,int &val); // Use a short opt letter to return its int.
        bool optval(char c,bool &val); // Use a short opt letter to return its bool.
        vector<string> arglist(); // Args not related to options (e.g. often filenames to process).
        int i= 0; string s= string(); bool b= false; // A generic types. Use with optval if no ref is passed.
        string version();
        string help();
    private: // Put all controlled variables with their defaults here.
        bool embed= false;
        bool quiet= false;
        int debug= 0;
        string backend= "HTML";
        string outfile= "-";
        string infile= "-";
        string style= "style.css";
        string syntaxcmd= "source-highlight -s cpp -f html --no-doc -o STDOUT -i sample.cxx | tail -n+5";
        vector<string> filelist; // This is the non-option argument list.
}; // End class: Config
string Config::version() {                                                     // == Describing ==
    return  "nerdtext - Chris X Edwards - "+VERSION+"\n";
} // End function: Config::help()
string Config::help() {
    string h= version();
    h+= "-h,--help              This message.\n";
    h+= "-v,--version           Output the version.\n";
    h+= "-q,--quiet             Turn off warnings.\n";
    h+= "-d[L],--debug[=L] Set  debug level, 1 when active and unspecified.\n";
    h+= "-b,--backend <back>    Backend, default is HTML.\n";
    h+= "-o,--out <file>        Output file, default is standard output.\n";
    h+= "-s,--style <file>      Name of CSS style file. Default `style.css`. Use `.` to disable.\n";
    h+= "-S,--syntax <syncmd>   Command used to process code highlighting.\n";
    return h;
} // End function: Config::help()
Config::Config(const int argc,char **argv) { // Special constructor.
    parseopts(argc,argv); // Sensibly parses the options if given the argc and argv.
} // End Config::parseopts()
void Config::parseopts(const int &argc,char **argv) {                          // == Processing ==
    while (true) {
        static struct option long_options[] = {  // The long option strings (e.g. `--help`) defined here.
            {"help",    no_argument,       NULL, 'h'}, // Bools work well in C++.
            {"version", no_argument,       NULL, 'v'}, // Output the version.
            {"quiet",   no_argument,       NULL, 'q'}, // Suppress warnings.
            {"debug",   optional_argument, NULL, 'd'}, // With -d3 or --debug=3 specify level. Or -d to activate at defatult.
            {"backend", required_argument, NULL, 'b'}, // What kind of output to generate (default HTML).
            {"embed",   no_argument,       NULL, 'e'}, // Skip document establishing stuff and go right to content.
            {"out",     required_argument, NULL, 'o'}, // Output file. - is stdout which is default.
            {"style",   required_argument, NULL, 's'}, // Style name. `.` for none.
            {"syntax",  required_argument, NULL, 'S'}, // Syntax command. `source-highlight|tail -n+5` is default.
            {0,         0,                 0,      0}  // Signal end of options with a set of zeros.
        }; // End opt struct data.
        int o= getopt_long(argc, argv, "hvqd::b:eo:s:S:", long_options, NULL); // Returns option index.
        if (o == -1) break; // Signals end of options.
        switch (o) {
            case 'q': quiet= true; QUIET= quiet; break;
            case 'd': if (optarg){ debug= atoi(optarg); } else { debug= 1;} DEBUG_ENABLED=debug; break; // Arg optional!
            case 'b': backend= optarg; break;
            case 'e': embed= true; EMBED= embed; break;
            case 'o': outfile= optarg; break;
            case 's': style= optarg; break;
            case 'S': syntaxcmd= optarg; break;
            case 'v': cout << version(); exit(EXIT_SUCCESS);
            case 'h': cout << help(); exit(EXIT_SUCCESS);
            default: cout << "Unknown option:" << o << endl; return;
        } // End switch construct.
    } // End while loop. 
    while (optind < argc) { // Load any remaining command line arguments (not options).
        string s= string(); // Create a new empty C++ string.
        for (int i=0;i<=strlen(argv[optind]);i++) s.push_back(argv[optind][i]); // Construct string.
        filelist.push_back(s); // Add string to vector of arguments.
        optind++;
    } // End while still arguments.
    return;
} // End function: Config::parseopts()
bool Config::optval(char c,bool &val) {                                        // == Accessing ==
    switch (c) {                                                               // |Can't override just on returns.
      case 'e': val= embed; break;                                             // |So a correct type ref is passed in.
      default: cout << "Bad flag." << endl; // Bad.                            // |Load it or use optional return value.
    }; // End switch.
    return val;
} // End function: Config::optval() for string
string Config::optval(char c,string &val) {
    switch (c) {
      case 'b': val= backend; break;
      case 'o': val= outfile; break;
      case 's': val= style; break;
      case 'S': val= syntaxcmd; break;
      default: cout << "Bad string option." << endl; // Bad.
    }; // End switch.
    return val;
} // End function: Config::optval() for string
int Config::optval(char c,int &val) {
    switch (c) {
      case 'd': val= debug; break;
      default: cout << "Bad numeric option." << endl; // Bad.
    }; // End switch.
    return val;
} // End function: Config::optval() for int
vector<string> Config::arglist() {
    vector<string> v; for (string s:filelist) v.push_back(s);
    return v;
} // End function: Config::arglist()

class BE {                                    // BE for Backend.
    public:
        virtual map<char,string> get_openers(){ return {{'*',"*"}, {'_',"_"}, {'`',"`"}}; }; // Must be a function to be overridden.
        virtual map<char,string> get_closers(){ return {{'*',"*"}, {'_',"_"}, {'`',"`"}}; }; // Only functions can be virtual.
        virtual pair<string,string> make_link(string link); // Formatted link opener and closer.
        virtual pair<string,string> make_heading(int level); // Formatted section heading opener and closer.
        virtual string make_img(string link,string alt);    // Formatted image syntax.
        virtual string make_br();                           // Simple line break.
        virtual string get_mdash(){ return "--"; };
        //virtual string get_multiline_separator(){ return "\n\n"; };
        virtual string make_doc_header(string title,string style);
        virtual string make_title(string title);
        virtual string make_doc_footer();
        virtual string backend_armor(string&); // Protect backend syntax coincidentally found in plain text.
        virtual string table(const char&); // All the elements that generate table components.
        virtual string listitems(const char&); // All the elements that generate list item components.
        virtual string dditems(const char&); // All the elements that generate list item components.
        virtual string literal(const char &); // The text blocks in <tt>/<code> like code.
        //make_video
    private:
}; // End class: BE

class BE_HTML: public BE {
    public:
        //string make_link(const string &,const string &); // Formatted link.
        pair<string,string> make_link(string link); // Formatted link opener and closer.
        pair<string,string> make_heading(int level); // Formatted section heading opener and closer.
        string make_img(string link,string alt);    // Formatted image syntax.
        string make_br();                           // Simple line break.
        map<char,string> get_openers(){ return {{'*',"<b>"}, {'_',"<em>"}, {'`',"<code>"}}; };
        map<char,string> get_closers(){ return {{'*',"</b>"}, {'_',"</em>"}, {'`',"</code>"}}; };
        string get_mdash(){ return "&#8201;&#8212;&#8201;"; }; // &thinsp;&mdash;&thinsp;
        //string get_multiline_separator(){ return "\n</p>\n</p>\n"; };
        string make_doc_header(string title,string style);
        string make_title(string title);
        string make_doc_footer();
        string backend_armor(string&); // Change "<" to "&gt;", etc.
        string table(const char&); // All the elements that generate table components.
        string listitems(const char&); // All the elements that generate list item components.
        string dditems(const char&); // All the elements that generate list item components.
        string literal(const char &); // The text blocks in <tt>/<code> like code.
}; // End class: BE_HTML

class BE_TeX: public BE {
    public:
        //string make_link(const string &,const string &); // Formatted link.
        pair<string,string> make_link(string link); // Formatted link opener and closer.
        string make_img(string link,string alt);    // Formatted image syntax.
        map<char,string> get_openers(){ return {{'*',"{/bf "}, {'_',"{/em "}, {'`',"{/tt "}}; };
        map<char,string> get_closers(){ return {{'*',"}"}, {'_',"}"}, {'`',"}"}}; };
        string get_mdash(){ return "---"; };
}; // End class: BE_TeX

class BE_Markdown: public BE {
    public:
        //string make_link(const string &,const string &); // Formatted link.
        pair<string,string> make_link(string link); // Formatted link opener and closer.
        string make_img(string link,string alt);    // Formatted image syntax.
        map<char,string> get_openers(){ return {{'*',"**"}, {'_',"*"}, {'`',"`"}}; };
        map<char,string> get_closers(){ return {{'*',"**"}, {'_',"_"}, {'`',"`"}}; };
        string get_mdash(){ return "-"; };
}; // End class: BE_Markdown
// Other possible backends: RTF, troff, docbook. Maybe PS/PDF? Extended from another.

class Block {
    public:
        enum Type { plain, title, heading, listing, literal, litem, quote, warning, image,
                    comment, table, passthrough, sidebar, source, dditem };
        Block();                            // Default constructor assigning default `plain` type.
        Block(enum Block::Type mt);         // Constructor supplied with a type.
        int add_line(string &l);            // Add a line to the block line list.
        int import_lines(const list<string>);     // Puts a list of strings into the lines member.
        bool set_type(enum Block::Type);    // Assign type, use `Block::plain`, `Block::literal`, etc.
        string query_type();                // Return string of type, useful for diagnostics and checks.
        bool lines_present();               // Checks if any lines have been stored in this block.
        bool output_lines_present();        // Checks if any output lines have been stored in this block.
        int lines_quantity();               // How many lines are present?
        list<string> get_lines();           // Get the input line list.
        list<string> get_output_lines();    // Get the formatted output line list.
        string get_first_line();            // Get the first line in the list.
        string get_last_line();             // Get the last line in the list.
        int length_of_first_line();         // Useful when checking for title.
        list<Block> refine();               // Further process a block into possibly more and/or more specific blocks.
        friend ostream &operator <<(ostream &,const Block); // Overload output operator.
        bool trim_blocks();                 // Remove leading and trailing blank lines.
        bool format_content(BE &);          // Format refined blocks into the desired backend.
        bool format_content_ORIG(BE &);     // DELETE THIS once the new replacement system is working.
        void unindent();                    // Fixes the block made literal by indenting.
        void wrap(BE &);                    // Add backend markers of the entire block e.g. divs, `<h1>...</h1>` etc.
        vector<pair<char,string>> original_into_replacement(string &l); // Load what will be replacements with original chars.
        int nest;                           // Nesting depth for blocks that care. (title,heading,list items)
    private:
        enum Block::Type btype= plain;      // Default is plain.
        list<string> lines;                 // The actual content within (excluding) delimiters.
        list<string> output_lines;          // Lines formatted by renderer and ready for output.
        vector<pair<char,string>> replacements; // Replacement plan with each char of input mapped to output.
}; // End class `Block`.

class BlockPara: public Block { // A paragraph type of block with multiple lines that need to be parsed.
    public:
        BlockPara();                     // Default constructor assigning default `plain` type natch.
        list<Block> refine();            // Hunting for indent literals, listitems, dditems, tables, paragraph divisions etc.
        bool check_for_listitem();       // Does this paragraph have listitem formatting?
        int remove_lf();                 // Makes the paragraph one line. Returns how many removals.
        //bool check_for_indentliteral();  // Does this paragraph indenting making it like dash delimted blocks?
        int add_paragraph(string &l);    // Add paragraph part to collection.
        list<string> paragraph_temp;     // A contianer to assemble proper paragraphs.
        string condense_paratemp_lines();// Merges accumulated text lines into one plain paragraph line.
    private:
       list<string> paragraphs;          // Distinct paragraphs, appropriate lines possibly concatenated.
       enum Block::Type btype=plain;     // Default to plain. But list items can also be put into paragraph blocks.
}; // End class: BlockPara

class M {                                     // M for Message.
    public:
        enum Type {plain,stderr,warning,deprecated,error,DEBUG}; 
        // format - e.g. "<path>: line <num>: message"
        M(const string &s);                   // Default constructor with no special mesage type.
        M(const string &s,enum M::Type mt);   // Constructor supplied with type.
    private:
        string msg_text;                      // Content of the message. Used by specialty functions.
        enum M::Type msg_type= plain;         // Default.
        void msg();                           // Normal output.
        void msg_warning();                   // e.g. "non-zero exit status" (after shell action)
        void msg_error();                     // e.g "Illegal blah syntax." "Missing blah file."
        void msg_DEBUG();                     // For temporary diagnostics - these should all be removed!
        //void msg_stderr(string t);          // e.g. "FAILED: dependency blah or better required"
        //void msg_verbose(string t,int l);   // with level e.g. "filtering: ", "evaluating: ", "reading: "
        //void msg_deprecated(string t);      // e.g. "non-zero exit status" (after shell action)
        //void msg_unsafe(string t);          // ??? I don't really know what this was about.
}; // End class: M

/* ============ Other Declarations ============ */
bool line_starts_with_X(const string &l, const string &X);
bool line_is_blank(const string &l);          // Two new lines in a row.
int line_is_indented(const string &l);        // Could be the start of indented literal block.
bool line_is_table_delim(const string &l);    // Does this paragraph have table formatting?
bool line_is_list_item_format(const string &l); // `^ *\*  *[^ ]'
bool line_is_dd_item_format(const string &l); // `^ *[^ ][^ ]*:: [^ ][^ ]*$
bool line_is_comment(const string &l);        // `^//..*$`
bool is_one_of_these(char x,string chars);    // Is character x one of the characters in string chars?
void create_img_replacements(int after, string line,vector<pair<int,int>> &no, vector<pair<char,string>> &R,BE &B); // Replace image:url[]
void create_pass_replacements(int after,string line,vector<pair<char,string>> &R,BE &B); // Replace pass:[<backend>]
void create_plus_replacements(vector<pair<char,string>> &R,BE &B); // Replace semantic + breaks.
void create_link_replacements(string line, vector<pair<char,string>> &R,BE &B); // Figure out link replacements.
void create_litem_syntax(vector<pair<char,string>> &R, BE &B); // Format the contents of a list item (bullets) block.
void create_dditem_syntax(vector<pair<char,string>> &R, BE &B); // Format the contents of a definition/description item block.
void create_table_syntax(vector<pair<char,string>> &R, BE &B); // Format the contents of a table block.
void setup_literal(vector<pair<char,string>> &R,BE &B); // Very minor decoration like <pre>..</pre> etc.
void setup_heading(vector<pair<char,string>> &R,int n,BE &B);// Section heading formatting.
void escape_backend_syntax(vector<pair<char,string>> &replacements,string &s,BE &B);
void replace_mdash(string line,vector<pair<int,int>> &no, vector<pair<char,string>> &R,BE &B); // Takes -- and makes a fancy dash.
void find_format_marks(vector<pair<char,string>> &replacements, vector<pair<int,int>>&nozones, char m, BE &B);
bool pair_are_valid_con(const string &s,const int &c_one,const int &c_two);
string testprint(string s,vector<pair<int,int>> marklist); // Just here for TESTING.
// === Character Range Strings ===
string str_from_ascii_range(int s,int e) {string o; for (char c=s;c<=e;c++) o+=c; return o;}
string uppercase() {return str_from_ascii_range(97,122);}
string lowercase() {return str_from_ascii_range(65,90);}
string numbers() {return str_from_ascii_range(48,57);}
string letters() {return uppercase()+lowercase();}



/* ============ Class Functions ============ */

/* ============ BE (Back End) Formatting ============ */

pair<string,string> BE::make_link(string link) {
    string l1= link+"[";
    string l2= "]";
    pair<string,string> out= {l1,l2}; 
    return out; 
} // End function: BE::make_link()
pair<string,string> BE_HTML::make_link(string link) {
    string l1= "<a href=\""+link+"\" target=\"_blank\">";
    string l2= "</a>";
    pair<string,string> out= {l1,l2}; 
    return out; 
} // End function: BE_HTML::make_link()
pair<string,string> BE_TeX::make_link(string link) {
    string l1= "\\href{http://"+link+"}{";
    string l2= "}";
    pair<string,string> out= {l1,l2}; 
    return out; 
} // End function: BE_TeX::make_link()
pair<string,string> BE_Markdown::make_link(string link) {
    string l1= "[";
    string l2= "]("+link+")";
    pair<string,string> out= {l1,l2}; 
    return out; 
} // End function: BE_Markdown::make_link()

pair<string,string> BE::make_heading(int level) { // Formatted section heading opener and closer.
    string o= string(level,'=')+" ";
    string c= " "+string(level,'='); // Or blank. Whatever.
    pair<string,string> out= {o,c}; 
    return out;
} // End function: BE::make_heading()

string BE::make_img(string src,string alt) {    // Formatted image syntax.
    string i= "image:"+src+"["+alt+"]";
    return i;
} // End function: BE::make_img()

string BE_HTML::make_img(string src,string alt) {    // Formatted image syntax.
    string i= "<img src=\""+src+"\" alt=\""+alt+"\" title=\""+alt+"\">";
    return i;
} // End function: BE::make_img()

string BE_TeX::make_img(string src,string alt) {    // Formatted image syntax.
    string i= "\\special{halftone "+src+"}";
    return i;
} // End function: BE::make_img()

string BE_Markdown::make_img(string src,string alt) {    // Formatted image syntax.
    string link= src; 
    string i= "!["+link+"]("+src+" \""+alt+"\")";
    return i;
} // End function: BE::make_img()

string BE::make_br() {                           // Simple line break.
    return "\n-----\n";
} // End function: BE::make_br()

string BE_HTML::make_br() {                           // Simple line break.
    return "<br>\n";
} // End function: BE::make_br()

string BE::make_doc_header(string title,string style) { return string(); }

string BE::make_title(string title) { return title + '\n' + string(title.size(),'='); }

string BE::make_doc_footer() { return ""; }

string BE_HTML::make_doc_header(string title,string style) {
    if (EMBED) return string(); // None of this stuff needed for use in other documents.
    const string DT= "<!DOCTYPE html>\n";
    const string tag_html= "<html lang=\"en\">\n";
    const string tag_head_o= "<head>\n"; // In theory all tags before a <body> is considered head. So superfluous.
    const string tag_meta1= "<meta charset=\"UTF-8\">\n";
    const string tag_meta2= "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n";
    const string tag_meta3= "<meta name=\"generator\" content=\"Nerdtext\">\n";
    string tag_style= "<link rel=\"stylesheet\" href=\""+style+"\">\n" ;
    if (style == ".") tag_style= ""; // Get rid of this line completely if the user option wants no style.
    const string tag_head_c= "</head>\n";
    //const string tag_link= "<link rel=\"stylesheet\" href=\"google?uh...no...donotthinkso\">";
    //const string tag_link= "<link rel=\"stylesheet\" href=\"google?uh...no...donotthinkso\">";
    string out= DT +tag_html +tag_head_o +tag_meta1 +tag_meta2 +tag_meta3 +tag_style; // Common title or not.
    if (title.size() > 0) { // Title supplied? Set it up.
        out+= make_title(title); // Default no title supplied.
    } else { // No title on this document.
        out+= tag_head_c + "<body>"; // Default no title.
    } // End else no title.
    out+= "<div id=\"bodycontent\">\n"; // Useful for giving the main text some style but leaving a border of body.
    return out;
} // End function: BE_HTML::make_doc_header()

string BE_HTML::make_title(string title) {
    const string tag_title= "<title>"+title+"</title>\n";
    const string tag_head_c= "</head>\n";
    const string tag_body= "<body>\n";
    string out= tag_title + tag_head_c + tag_body;
    return out;
} // End function: BE_HTML::make_title()

pair<string,string> BE_HTML::make_heading(int level) { // Formatted section heading opener and closer.
    string o= "<h"+to_string(level)+">";
    string c= "</h"+to_string(level)+">";
    pair<string,string> out= {o,c}; 
    return out;
} // End function: BE_HTML::make_heading()

string BE_HTML::make_doc_footer() {
    if (EMBED) return string(); // None of this stuff needed for use in other documents.
    const string tag_div= "</div>";
    const string tag_body= "</body>\n";
    const string tag_html= "</html>\n";
    return tag_div + tag_body + tag_html;
} // End function: BE_HTML::make_doc_footer()

string BE::backend_armor(string &s) { return s; } 

string BE_HTML::backend_armor(string &s) { /* &=&amp; >=&gt; <=&lt; '=&apos; "=&quot; */
    map<char,string> HTMLsubs= { {'&',"&amp;"}, {'>',"&gt;"}, {'<',"&lt;"}, {'"',"&quot;"} }; // THis one? {'\'',"&apos;"}  No.
    if (HTMLsubs.find(s[0]) != HTMLsubs.end()) {
        //M("In BE_HTML::backend_armor(string &s):"+string(1,s[0]),M::DEBUG);
        return HTMLsubs[s[0]];
    }
    return s;
}

string BE::table(const char &m) {
    string o;
    switch (m) {
        case 'T': { o= "table:"; break; } // Table open.
        case 't': { o= ":table"; break; } // Table close.
        case 'R': { o= "row:"; break; } // Row open.
        case 'r': { o= ":row"; break; } // Row close.
        case 'D': { o= "data:"; break; } // Cell open.
        case 'd': { o= ":data"; break; } // Cell close.
        default: o= "Table Error";
    } // End switch.
    return o;
} // End function: BE::table()

string BE_HTML::table(const char &m) {
    string o;
    switch (m) {
        case 'T': { o= "<table border=\"1\">"; break; } // Table open.   table
        case 't': { o= "</table>"; break; } // Table close. /table
        case 'R': { o= "<tr>"; break; } // Row open.     tr
        case 'r': { o= "</tr>"; break; } // Row close.   /tr
        case 'D': { o= "<td>"; break; } // Cell open.    td
        case 'd': { o= "</td>"; break; } // Cell close.  /td
        default: o= "<!-- HTML Table Error -->";
    } // End switch.
    return o;
} // End function: BE_HTML::table()

string BE::listitems(const char &m) {  // All the elements that generate list item components.
    string o;
    switch (m) {
        case 'U': { o= ""; break; } // Unordered list open.   ul
        case 'u': { o= ""; break; } // Unordered list close. /ul
        case 'L': { o= "*"; break; }  // List item open.     li
        case 'l': { o= ""; break; } // List item close.     li
        default: o= "List Item Error";
    } // End switch.
    return o;
} // End function: BE_HTML::listitems()

string BE_HTML::listitems(const char &m) {  // All the elements that generate list item components.
    string o;
    switch (m) {
        case 'U': { o= "<ul>"; break; } // Unordered list open.   ul
        case 'u': { o= "</ul>"; break; } // Unordered list close. /ul
        case 'L': { o= "<li>"; break; }  // List item open.     li
        case 'l': { o= "</li>"; break; } // List item close.     li
        default: o= "<!-- HTML List Item Error -->";
    } // End switch.
    return o;
} // End function: BE_HTML::listitems()

string BE::dditems(const char &m) {  // All the elements that generate list item components.
    string o;
    switch (m) {
        case 'L': { o= ""; break; } // Definition/description list open.   dl
        case 'l': { o= ""; break; } // Definition/description list close. /dl
        case 'T': { o= ""; break; }  // DT term open.     dt
        case 't': { o= "::"; break; } // DT term close.     /dt
        case 'D': { o= ""; break; }  // DD data open.     dd
        case 'd': { o= ""; break; } // DD data close.     /dd
        default: o= "DD Item Error";
    } // End switch.
    return o;
} // End function: BE_HTML::dditems()

string BE_HTML::dditems(const char &m) {  // All the elements that generate list item components.
    string o;
    switch (m) {
        case 'L': { o= "<dl>"; break; } // Definition/description list open.   dl
        case 'l': { o= "</dl>"; break; } // Definition/description list close. /dl
        case 'T': { o= "<dt>"; break; }  // DT term open.     dt
        case 't': { o= "</dt>"; break; } // DT term close.     /dt
        case 'D': { o= "<dd>"; break; }  // DD data open.     dd
        case 'd': { o= "</dd>"; break; } // DD data close.     /dd
        default: o= "<!-- HTML DD Item Error -->";
    } // End switch.
    return o;
} // End function: BE_HTML::dditems()

string BE::literal(const char &m) {  // Format for literal blocks.
    string o;
    switch (m) {
        case 's': { o= string(20,'-'); break; } // Start of the block.
        case 'e': { o= string(20,'-'); break; } // End of the block.
        default: o= "Literal Block Error";
    } // End switch.
    return o;
} // End function: BE::literal()

string BE_HTML::literal(const char &m) {  // Format for literal blocks.
    string o;
    switch (m) {
        case 's': { o= "<pre><code>"; break; } // Start of the block. The <code> must be outside or else it will be preformatted!
        case 'e': { o= "</code></pre>"; break; } // End of the block.
        default: o= "<!-- Literal Block Error -->";
    } // End switch.
    return o;
} // End function: BE_HTML::literal()

/* ============ M - Message Handling ============ */
// Consistent output control. Very roughly inspired by Python Asciidoc.
// Use like so:
//     M("HERE-1 "+to_string(blist.size()),M::DEBUG);
// For reference, Asciidoc output looks like this:
// asciidoctor: WARNING: nesttest2.asciidoc: line 3: section title out of sequence: expected level 1, got level 2
M::M(const string &s,enum M::Type mt) {
    msg_text= s;
    switch (mt) {
        case warning: { msg_warning() ; break; }
        case error: { msg_error() ; break; }
        case DEBUG: { msg_DEBUG() ; break; }
        default: { msg(); } // Type 'plain' too.
    } // End switch.
} // End function M::M(string s,enum M::Type mt)

M::M(const string &s) {
    M(s,M::plain);
} // End function M::M(string s)

void M::msg() {  // A plain default message.
    cout << "nerdtext: " << msg_text << endl;
} // End function M::msg_plain(string s)

void M::msg_warning() {
    if (QUIET) return;
    cerr << "nerdtext Warning: " << msg_text << endl;
} // End function M::msg_warning(string s)

void M::msg_error() {
    if (QUIET) return;
    cerr << "nerdtext ERROR: " << msg_text << endl;
} // End function M::msg_error(string s)

void M::msg_DEBUG() {
    if (DEBUG_ENABLED>0) 
    cerr << "nerdtext DEBUG: ''" << msg_text << "''"<< endl;
} // End function M::msg_error(string s)

/* ============ Block Functions ============ */
Block::Block() {
    set_type(Block::plain);
    nest= -1; // Meaning not used until it is used.
}; // End Block constructor that assigns a default plain block.

Block::Block(enum Block::Type t) {
    set_type(t);
    nest= -1; // Meaning not used until it is used.
}; // End Block constructor that takes and sets a block type.

vector<pair<char,string>> Block::original_into_replacement(string &l) {
    //vector<pair<char,string>> R; 
    if (query_type() == "comment") {
        pair<char,string> newpair= {0,""}; // Put a nothing in the replacement! One to prevent generic processing problems.
        replacements.push_back(newpair); // Make that the replacement list.
        return replacements;
    }
    for (char c:l) { // Loading each character of the current line.
        pair<char,string> newpair= {c,string(1,c)}; // Put original in corresponding place as default.
        replacements.push_back(newpair); // Add this pair of original character and replacement string to replacement vector.
    } // End for each character of the input line.
    return replacements;
} // End function: original_into_replacement()

string Block::query_type(){ // Returns printable text version of block's type.
     string printabletypes[]=  { "plain", "title", "heading", "listing", "literal", "litem", "quote",  // Keep these in order!
         "warning", "image", "comment", "table", "passthrough", "sidebar", "source", "dditem" };
    return printabletypes[btype];
}; // End function Block::query_type()

bool Block::set_type(enum Block::Type t){ // Returns true if type is new.
    if (Block::btype == t) return false; // If not new type, can exit now.
    Block::btype= t;
    return true;
} // End function Block::set_type(enum Block::Type t)

int Block::add_line(string &l) {
    Block::lines.push_back(l);
    return lines_quantity();                   // Returns the new number of lines. Void would work too.
} // End class function: Block::addline().

int Block::import_lines(const list<string> extlines) {
    M("In import_lines() adding "+to_string(extlines.size())+" strings to object's lines",M::DEBUG);
    lines= list<string>(extlines);
    return lines_quantity();
} // End function: check_for_listitem()

bool Block::lines_present() { // Checks to see if any lines have been stored in the object's line list.
    if (lines.begin() == lines.end()) return false;  // The official way to check container for zero items.
    return true;  // Must be something there.
} // End class function: Block::lines_present()

bool Block::output_lines_present() { // Checks to see if any lines have been stored in the object's line list.
    if (output_lines.begin() == output_lines.end()) return false;  // The official way to check container for zero items.
    return true;  // Must be something there.
} // End class function: Block::lines_present()

int Block::lines_quantity() { // Checks to see if any lines have been stored in the object's line list.
    if (!lines_present()) return 0;  // The official way to check container for zero items.
    return lines.size(); // Must be something there.
} // End class function: Block::lines_quantity()

string Block::get_first_line() { // Return string which is at beginning of lines list.
    if (!lines_present()) {  // If lines are _not_ present, i.e. empty line list.
        return string();         // The official way to return an empty string.
    }
    list<string>::iterator i= lines.begin();
    return *i;
} // End function get_first_line()

int Block::length_of_first_line() {
    if (lines_present()) { // Check if there are even any lines present at all.
        string fl= get_first_line();
        return fl.size();
    } else {
        return 0; // There are no lines in the lines list.
    }
} // End class function: Block::length_of_first_line()

string Block::get_last_line() { // Return string which is at end of lines list.
    if (!lines_present()) return string(); // Return an empty string if no lines present.
    string lastline= lines.back();
    return lastline;
} // End function get_last_line()

list<string> Block::get_lines() { // Supply line iterator.
    if (lines_present()) return lines;
    else return list<string>();
} // End function: Block::get_lines()

list<string> Block::get_output_lines() { // Supply line iterator.
    if (output_lines_present()) return output_lines;
    else return list<string>();
} // End function: Block::get_output_lines()

list<Block> Block::refine() { // Organizing refinement process. E.g. non-plain blocks don't need to refine.
    list<Block> newBlocks;
    if (query_type() != "plain") {                    // Non-plain blocks can be sent right back.
        newBlocks.push_back(*this);                   // Make a quick single item list for returning.
        return newBlocks;                             // Containing just the input item.
    } // End if some kind of already carefully refined non-plain type of block.
    BlockPara BP;                                    // Keeps track of lines and has all needed functions.
    BP.import_lines(get_lines());         // Give this block's lines to the new BlockPara analysis object.
    newBlocks= BP.refine(); // Special refinement of blocks maybe containing new ones this plain one had.
    return newBlocks; 
} // End function: Block::refine()

bool Block::trim_blocks() { // If correctly parsed, blank lines as first and last line are only confusing.
    bool changemade= false;
    if (!lines_present()) return false; // Not populated so no trimming.
    if (length_of_first_line() == 0) {
        M("Found a block with a superfluous blank leading first line.",M::DEBUG);
        if (lines_quantity() > 0) lines.pop_front(); // Remove the first line because it seems blank.
        changemade= true;
        if (lines_quantity() == 1) { // One blank line only in the block? Best to stop now!
            return changemade;
        } // End if blank only.
    } // End if first line is empty.
    if (get_last_line().size() == 0)  {
        M("Found a block with a superfluous blank trailing last line.",M::DEBUG);
        if (lines_quantity() > 0) lines.pop_back(); // Remove the last line because it seems blank.
        changemade= true;
    } // End if last line is blank.
    return changemade; // True if some change was made. Void would be fine too.
} // End function: Block::trim_blocks()

void Block::unindent() { // Having indented literals stay indented is deeply weird. Much more useful to undo the indent.
    if (lines.size() < 1) return; // Don't mess around with strangely empty blocks.
    int baseindent= line_is_indented(get_first_line());
    list<string> fixedlines; // A new fixed set will have to be created.
    for (string l:lines) { // Go through each line.
        int indent= line_is_indented(l); // Returns how much it is indented.
        if (indent < 1) {fixedlines.push_back(l); continue;} // These are not the indents you are looking for.
        if (indent > baseindent) indent= baseindent; // Go ahead and leave the slight _extra_ indentations past the first.
        l.erase(0,indent);  // Erase first indent characters. Trivia: `string.pop_front()` is not a thing!
        fixedlines.push_back(l);
    } // End for each of the lines in this block.
    import_lines(fixedlines);
} // End function: Block::unindent()

void Block::wrap(BE &renderer) {
    // vector<pair<char,string>> replacements; // DELETE - Just for reference.

    if (query_type() == "title") {
        return; // This is taken care of in output_document(). Leave main title alone.
    }
    pair<char,string> front, back;
    int lastreplacement= replacements.size()-1;
    replacements[0].second= "<div class=\""+query_type()+"\">\n<p>\n"+replacements[0].second;
    replacements[lastreplacement].second= "\n</p>\n</div>";
    //for (pair<char,string> p:replacements) p.second= "X"; // Clear the entire replacement.
    // Add the back end block wrap decorations. Stuff like <div>...</div>
    return;
} // End function: Block::wrap()

bool Block::format_content(BE &renderer) {
M("In start of format_content():"+get_first_line(),M::DEBUG);
    if (!lines_present()) return false; // Not populated so no formatting. Hopefully this never happens.
M("Lines are present or wouldn't get this far.",M::DEBUG);
    string cat;                         // Temporary concatenated string.
    for (string l:lines) {cat+= l; cat+= '\n';};       // Concatenation of all lines.
    original_into_replacement(cat);     // Load this comprehensive line into the replacement plan as no-op default.
M("Concatenation of all the input lines successful...",M::DEBUG);
    string qt= query_type();
    if (qt != "passthrough" && qt != "comment") escape_backend_syntax(replacements,cat,renderer); // Needs to go early to avoid confusion with BE.
    if ((qt == "literal") || (qt == "listing")) {
        setup_literal(replacements,renderer);
    } else if ((qt == "passthrough") || (qt == "comment")) {
        // Do nothing!
    } else { // This block is not literal and probably needs much translating.
M("Not a literal, listing, passthrough, or comment...:"+qt,M::DEBUG);
        vector<pair<int,int>> noinlinesets; // Locations set during inline literals to avoid replacements in.
        find_format_marks(replacements,noinlinesets,'`',renderer); // Loads the noinlinesets which protects them.
        find_format_marks(replacements,noinlinesets,'*',renderer); // Uses previously found protected zones now in...
        find_format_marks(replacements,noinlinesets,'_',renderer); // ...noinlinesets. Pairs of start/end of protected ranges.
M("Format marks successfully processed...",M::DEBUG);
        create_pass_replacements(0,cat,replacements,renderer); // Replace pass:[<backend>]
M("Finished pass replace...",M::DEBUG);
        create_img_replacements(0,cat,noinlinesets,replacements,renderer); // Must come before links to have imgs in link texts!
M("Finished img replace...",M::DEBUG);
        create_link_replacements(cat,replacements,renderer);
M("Finished link replace...",M::DEBUG);
        replace_mdash(cat,noinlinesets,replacements,renderer);
M("Finished mdash replace...",M::DEBUG);
        create_plus_replacements(replacements,renderer);
M("Finished plus replace...",M::DEBUG);
        if (qt == "title") setup_heading(replacements,nest,renderer);
M("Finished title check..."+to_string(nest),M::DEBUG);
        if (qt == "heading") setup_heading(replacements,nest,renderer);
M("Finished heading check...",M::DEBUG);
    } // End else Not literal and needs much marking up!
    if (qt == "table") create_table_syntax(replacements,renderer);
M("Finished table check...",M::DEBUG);
    if (qt == "litem") create_litem_syntax(replacements,renderer);
M("Finished litem check...",M::DEBUG);
    if (qt == "dditem") create_dditem_syntax(replacements,renderer);
M("Finished dditem check...",M::DEBUG);
    wrap(renderer); // Add the backend stuff that marks the block, e.g. <p>...</p>.
M("Finished backend document wrapping...",M::DEBUG);
    //add_
    string outputline;
    for (pair<char,string> p:replacements) {
        outputline+= p.second;
        //M("In format_content():"+outputline,M::DEBUG);
    }
    output_lines.push_front(outputline); // The "final" line in reverse has no \n so it always waits here to be added.
M("Added outputline generation...",M::DEBUG);
M("Checking contents of output_lines:",M::DEBUG);
    return true;
} // End member function: Block::format_content()

// If I had my way, this would be a Block:: member function, but that's not how this works!
ostream &operator <<(ostream &s,Block b){ // Overload output operator.
    //list<string> lines= b.get_lines();
    list<string> lines= b.get_output_lines();
    //s << "<bogus class=\"" << b.query_type() << "\">";
    //s << "Block |" << b.query_type() << "|";
    //s << lines.size() << " lines|";
    s << endl;
    //for (list<string>::iterator i=b.output_lines.begin();i!=b.output_lines.end();i++) {
    for (list<string>::iterator i=lines.begin();i!=lines.end();i++) {
        if (i->size()>0) s << *i;
    }
    //s << "</bogus>" << endl;
    return s; 
} // End operator overload for Block <<

/* ============ BlockPara Functions ============ */
BlockPara::BlockPara() {
    set_type(Block::plain);
}; // End Block constructor that assigns a default plain block.

string BlockPara::condense_paratemp_lines() {
// Converts list of strings being built up in paragraph_temp into a single long string.
// It is correct to do this only when the text is plain and not really some other kind of block.
    string outstr;
    for (list<string>::iterator i=paragraph_temp.begin();i!=paragraph_temp.end();i++) {
        outstr+= *i; // In theory, this should produce a single string with all the paragraph's lines.
        // The real question is should new lines be preserved so that they show up in the output where they are moot?
        // Undecided but it may be possible to put them in here. It seems ok to put '\n' chars in a string.
    } // End for every string in paragraph_temp.
    return outstr;
} // End function condense_paratemp_lines()

list<Block> BlockPara::refine() { // Hunting for indent literals, listitems, dditems, tables, paragraph divisions etc.
    // Reminder that BlockPara.paragraphs are plain text only with newlines removed.
    enum mode { newplainmo, plainmo, litemmo, indlitmo, tablemo, dditemmo }; // Possible modes during refinement.
    string DEBUGmode[]= { "newplainmo", "plainmo", "litemmo", "indlitmo", "tablemo", "dditemmo" }; // Possible modes during refinement.
    enum mode current_mode= newplainmo;                            // Start by assuming it really is plain.
    list<Block> newBlocks;   // What will be returned as this "plain" block broken into specialty blocks.
    list<string> rawlines= get_lines();  // Of the unrefined input block.
    rawlines.push_back(string());             // Add a blank to the very end to make sure last block is processed.
    paragraph_temp.clear();                                        // Reset WIP paragraph object, a list of line strings.  
    for (list<string>::iterator i=rawlines.begin();i!=rawlines.end();i++) { // Review all lines in this so far plain block.
M("Mode: "+DEBUGmode[current_mode] + " - " + to_string(current_mode),M::DEBUG);
if (!i->empty())M("Working on:"+string(40,' ')+*i,M::DEBUG);
        if (i->empty()) {                                          // ===== Blank Line =====
M("Working on:"+string(40,' ')+"<BLANK LINE>",M::DEBUG);
M("Found empty line.",M::DEBUG);
            switch (current_mode){
                case newplainmo: { break; } // Consecutive blank lines. A good place to ignore/delete multiples.
                case plainmo: { // Just another paragraph.
                    // paragraphs is a list<string> that contains plain line groups so far.
                    //paragraphs= paragraph_temp; //package up new paragraph
                    Block tempBlock= BlockPara();   // Object for previous plain paragraph.
                    tempBlock.import_lines(paragraph_temp); // Load it with the recently processed lines.
                    paragraphs.push_back(condense_paratemp_lines());
                    newBlocks.push_back(tempBlock);         // Add this indented literal to the new block list.
                    paragraph_temp.clear();                 // Starting the temporary paragraph accumulator afresh.
                    current_mode= newplainmo;
                    continue;
                } // End case plainmo where this is a blank following some plain text.
                case indlitmo: { // This blank ends an indented literal.
                    Block tempBlock= Block(Block::literal); // Object for previous literal block.
                    tempBlock.import_lines(paragraph_temp); // Load it with the recently processed indented literal lines.
                    tempBlock.unindent(); // Indent or literal? The need for both simultaneously is never. So the latter.
M(" Adding a new block: indented literal",M::DEBUG);
                    newBlocks.push_back(tempBlock);         // Add this indented literal to the new block list.
                    //paragraph_temp.clear();                 // Starting the temporary paragraph accumulator afresh.
                    current_mode= newplainmo;               // Assume a new plain sequence as default.
                    paragraph_temp.clear();                 // Must be time for a reset since indent literals stop at blanks.
                    continue; 
                } // End of indlitmo where an indented literal has just ended with a blank.
                case litemmo: { // Package up list item.
                    Block tempBlock= Block(Block::litem); // Object for previous list item block.
                    tempBlock.import_lines(paragraph_temp); // Load it with the recently processed lines.
                    paragraphs.push_back(condense_paratemp_lines());
                    newBlocks.push_back(tempBlock);         // Add this indented literal to the new block list.
                    paragraph_temp.clear();
                    current_mode= newplainmo;
                    continue;
                } // Finishing a list item.
                case dditemmo: { // Package up dd item.
                    Block tempBlock= Block(Block::dditem); // Object for previous dd item block.
                    tempBlock.import_lines(paragraph_temp); // Load it with the recently processed lines.
                    paragraphs.push_back(condense_paratemp_lines());
                    newBlocks.push_back(tempBlock);         // Add this indented literal to the new block list.
                    paragraph_temp.clear();
                    current_mode= newplainmo;
                    continue;
                } // Finishing a dd item.
                case tablemo: {M("There should not be blank lines in tables.",M::error); continue; } // Erroneous table problem.
                default: {M("Mode anomaly in block refine() function.",M::warning);} // Modes exhuastively checked so.. hmmm.
            } // End switch on mode after blank line.
            paragraph_temp.clear();                         // Reset WIP paragraph object.
            continue; // No need to think any more about this particular line.
        } else { // Line is *not* blank but contains something possibly useful.
            if (line_is_comment(*i)) continue;              // Ignore line completely.
            if (line_is_table_delim(*i)) {                             // ===== Table Delimiter =====
M("Found a table delimited line.",M::DEBUG);
                switch (current_mode){
                    case tablemo: { // Closing a table.
                        Block tempBlock= Block(Block::table);          // Package up a new table block.
                        tempBlock.import_lines(paragraph_temp);        // Load it with the recently processed table.
M(" Adding previous block: table",M::DEBUG);
    /*FIX:paragraphs*/  if (! paragraph_temp.empty()) newBlocks.push_back(tempBlock); // Send it to the list of new refined blocks.
                        paragraph_temp.clear();                        // Clear now stored temp lines.
                        current_mode= newplainmo;                      // Reset mode for next section.
                        break;
                    } // End case finding a table delimiter while in table mode, thereby closing the table block.
                    case newplainmo: { }   // Same as plain.
                    case plainmo: {
                        /*package up previous plain block*/;
                        BlockPara tempBlock= BlockPara(); // Package up previous plain block.
                        tempBlock.import_lines(paragraph_temp);        // Load it with the recently processed table.
M(" Adding previous block: plain",M::DEBUG);
                        if (! paragraph_temp.empty()) newBlocks.push_back(tempBlock); // Send it to the list of new refined blocks.
                        paragraph_temp.clear();                        // Clear now stored temp lines.
                        current_mode= tablemo;                         // Reset mode for table section to come.
                        break;
                    }   // This is a table opening.
                    default: {  }                 // In some other mode. Ignore and carry on.
                } // End switch on mode after table syntax line.
                continue; // No need to think any more about this particular line.
            } // End if line is a table delimiter.
            if (line_is_list_item_format(*i)) {                        // ===== List Item =====
M("Found an item formatted line.",M::DEBUG);
                switch (current_mode){
                    case plainmo: { // Some text followed _immediately_ by a list. Seems ok to me. Treat like new plain mode.
                        BlockPara tempBlock= BlockPara(); // Package up previous plain block.
                        tempBlock.import_lines(paragraph_temp);        // Load it with the recently processed block.
                        paragraphs.push_back(condense_paratemp_lines()); // This is the previous plain block wrapped up.
M(" Adding previous block: plain",M::DEBUG);
                        if (! paragraphs.empty()) newBlocks.push_back(tempBlock); // Send it to the list of new refined blocks.
                        paragraph_temp.clear();                        // Clear now stored temp lines.
                        current_mode= litemmo;
                        //tempBlock.set_type(BlockPara::litem); // Start the new 
                        break;
                    } // End case plainmo where a new list item is starting.
                    case newplainmo: {/*prev should alreadly be packaged*/; current_mode= litemmo; break; } // Starting new list item.
                    case litemmo: { // A new item in a list of them, totally normal.
                        paragraphs.push_back(condense_paratemp_lines()); // Package previous item. Mode is same.
                        break;
                    } // End case litemmo and finding another list item formatted.
                    case dditemmo: { // Package up dd item.
                        Block tempBlock= Block(Block::dditem); // Object for previous dd item block.
                        tempBlock.import_lines(paragraph_temp); // Load it with the recently processed lines.
                        paragraphs.push_back(condense_paratemp_lines());
                        newBlocks.push_back(tempBlock);         // Add this dd list to the new block list.
                        paragraph_temp.clear();
                        current_mode= litemmo;
                        continue;
                    } // Finishing a dd item.
                    case tablemo: {M("Looks like a list item in a table.",M::warning); } // Table problem but ignore.
                    //case indlitmo: { } // This is actually normal. Ignore.
                    default: {} // Ignore the indentation. Carry on.
                } // End switch on mode after list item format.
            } // End if line is formatted like a list item.
// ----------------------------------------------------------------------------
            if (line_is_dd_item_format(*i)) {                        // ===== Definition/Description Item =====
M("Found a dd item formatted line.",M::DEBUG);
                switch (current_mode){
                    case plainmo: { // Some text followed _immediately_ by a dd list. Seems ok to me. Treat like new plain mode.
                        BlockPara tempBlock= BlockPara(); // Package up previous plain block.
                        paragraphs.push_back(condense_paratemp_lines()); // This is the previous plain block wrapped up.
M(" Adding previous block: plain",M::DEBUG);
                        if (! paragraphs.empty()) newBlocks.push_back(tempBlock); // Send it to the list of new refined blocks.
                        paragraph_temp.clear();                        // Clear now stored temp lines.
                        current_mode= dditemmo;
                    } // End case plainmo where a new list item is starting.
                    case newplainmo: {/*prev should alreadly be packaged*/; current_mode= dditemmo; break; } // Starting new dd item.
                    case litemmo: { // Package up list item.
                        Block tempBlock= Block(Block::litem); // Object for previous list item block.
                        tempBlock.import_lines(paragraph_temp); // Load it with the recently processed indented literal lines.
                        paragraphs.push_back(condense_paratemp_lines());
                        newBlocks.push_back(tempBlock);         // Add this indented literal to the new block list.
                        paragraph_temp.clear();
                        current_mode= dditemmo;
                        continue;
                    } // Finishing a list item.
                    case dditemmo: { // A new item in a sequence of them, totally normal.
                        paragraphs.push_back(condense_paratemp_lines()); // Package previous item. Mode is same.
                        break;
                    } // End case dditemmo and finding another dd item formatted.
                    case tablemo: {M("Looks like a description item in a table.",M::warning); } // Table problem but ignore.
                    //case indlitmo: { } // This is actually normal. Ignore.
                    default: {} // Ignore. Carry on.
                } // End switch on mode after indented line.
            } // End if line is formatted like a dd item.
// ----------------------------------------------------------------------------
            if (line_is_indented(*i)>0) {                                // ===== Indented Literal =====
M("Found an indented line.",M::DEBUG);
                switch (current_mode){
                    case plainmo: { } // The _first_ line is not indented. This could be changed to honor this too. 
                    case newplainmo: {
                        BlockPara tempBlock= BlockPara(); // Package up previous plain block.
                        tempBlock.import_lines(paragraph_temp);        // Load it with the recently processed block.
                        paragraphs.push_back(condense_paratemp_lines());
                        if (! paragraph_temp.empty()) newBlocks.push_back(tempBlock); // Send it to the list of new refined blocks.
                        paragraph_temp.clear();                        // Clear now stored temp lines.
                        current_mode= indlitmo;
                        break;
                    } // Starting new indliteral.
                    case tablemo: {M("There should not be indented lines in tables.",M::warning); } // Table problem but ignore.
                    case indlitmo: { } // Still indented. Happens all the time.
                    case litemmo: { } // This is actually normal.
                    case dditemmo: { } // This is actually normal.
                    default: {} // Ignore the indentation. Carry on.
                } // End switch on mode after indented line.
            } // End if line starts with indent.
            if (current_mode == newplainmo) current_mode= plainmo; // New plain mode is only valid for the first line.
            paragraph_temp.push_back(string(*i)); // Add this line to growing paragraph.
        } // End else line is not blank.
    } // End for all the lines in this previously identified plain block.
M("Trying to add a new mystery block: "+query_type(),M::DEBUG);
//    newBlocks.push_back(*this);
    return newBlocks;
} // End function:BlockPara::refine()

bool BlockPara::check_for_listitem() {
    return false;
} // End function: check_for_listitem()

int BlockPara::remove_lf() {
    return 0;
} // End function: remove_lf()

/* == Utility Functions == */
// === Functions That May Be Useful
//bool line_is_attribute_format(string l); // `^\[..*\]$`
//bool line_is_tag_format(string l); // `^\[\[..*\]\]$`

bool line_starts_with_X(const string &l, const string &X) {
    if (l.size() < X.size()) return false; // The searched line must be large enough for the target term.
    for (int c=0;c<X.size();c++) {
        if (l[c] != X[c]) return false; // They all must match.
    } // End for each letter in target term.
    return true; // Survived checking every target character; must be there!
} // End function: line_starts_with_X()

bool line_is_comment(const string &l) { // `^//..*$`
    return line_starts_with_X(l,"//");
} // End function: line_is_comment()
 
int line_is_indented(const string &l) { // Returns exactly how much indentation.
    int n= 0; // Counter for how indented this line is.
    if (l.empty()) return 0; // Blank lines should not make it here but they are definitley invalid.
    for (char c:l) {
        if (c == ' ') n++; // Tally these up.
        else return n; // Only when the else is triggered by a real non-space, not when it runs out on a line of spaces.
    } // End for each character in the line.
    return 0; // Must be a line of nothing but spaces.
} // End function: line_is_indented()

bool line_is_list_item_format(const string &l) {                   // `^\([*.-]\)\1* ..*$`
    // Might need to rethink this if '** sub-item' needs to be valid.
    if (l.empty()) return false; // Blank lines should not make it here but they are definitley invalid.
    for (int c=0;c<(l.size()-2);c++) { // Look for *. -2 because after * there must be a space and other char.
        if (l[c] == '*') {
            if (l[c+1] != ' ') {return false;}  // A space must follow the list item bullet character.
            for (int c2=c+2;c2<l.size();c2++) { // Now spaces, at least one, are needed.
if (l[c2] != ' ') M("Listing is true:"+l,M::DEBUG);
                if (l[c2] != ' ') return true;  // That must be the final necessary character.
            } // End for looking for the final non space char.
        } else { // End if an item bullet character has been found.
            if (l[c] != ' ') return false; // Beginning is not '*' and, here, not ' ', then it's not a list item.
        } // End else some other character is here.
    } // End for looking for first non-space char.
    return false;
} // End function: line_is_list_item_format()

bool line_is_dd_item_format(const string &l) { // `^ *[^ ][^ ]* *::  *[^ ].*$    <dl><dt>term</dt><dd>def/desc</dd></dl>
    // Assume there's no indenting because that was picked up by the check for indented literals which should go first.
    if (l.empty()) return false; // Blank lines should not make it here but they are definitley invalid.
    int colonspos= l.find(":: "); // This string is essential so start with it.
    if (colonspos < 1) return false; // Nothing even close here. Either not found or line starts with ":: ", i.e. invalid.
    for (int n=colonspos+3;n<l.size();n++) {
        if (l[n] != ' ') return true; // Yes. Not just `term::       \n`
    } // End for looking for first non-space char.
    return false; // Should have found a dd part with non space characters by now.
} // End function: line_is_dd_item_format()

bool line_is_table_delim(const string &l) {
    if (l.size() < 3) return false; // Need start, end, and minimum one line of content.
    if (l[0] != '|') return false;    // Table delimiter starts with a pipe.
    string::const_iterator i= l.begin();    // Get an iterator ready.
    for (++i;i<l.end();i++) {         // Start at 2nd char, not beginning.
        if (*i != '=') return false;  // Second through N table delimiter characters must be "=".
    } // End for each character 
    return true;                      // Nothing has disqualified this from being a table delimiter.
} // End function: line_is_table_delim()

string text_replace(string& s, const string& bad, const string& good){
    // Simple string substitutions.
    // Replaces globally in the string, not just first.
    // Delete characters by setting good to "".
    size_t p= 0;
    while( (p= s.find(bad, p)) != string::npos) { // While bad is found.
        s.replace(p, bad.length(), good);
        p += good.length();
    }
    return s;
} // End function `text_replace`.

bool obliterate_tabs(string &l) {  // Tabs are _never_ ok. Ever. Uncool.
    // Note that this does not impact indented literal blocks since they must start with spaces anyway.
    //int beyond_len= string::npos; // Stupidly can't go directly in selection statement in C++11. -1 it is then.
    if (l.find('\t') != -1) { // A vile tab has been found!
        l= text_replace(l, "\t","    ");     // Fixing this.
        M("Tab found and replaced: "+l,M::warning);
        return 1;
    }
    return 0;
} // End function: obliterate_tabs()

bool contains_only_whitespace(const string &line){
    string line_sacrificial= line;
    if ( ! text_replace(line_sacrificial," ","").empty() ) { // More than just spaces.
        return false;
    } else return true; // Yes, only whitespace found.
} // End function: contains_only_whitespace()

bool matches_start_delimiter(const string &l, const string &d) {
    if (l.length() != d.length()) return false; // Same chars, different lengths.
    for (int i=0;i<l.length();i++) {            // Some char does not match.
        if (l[i] < d[0]) return false;
    }
    //M("Possible start delimiter!!");
    return true;                                // Must be the matching close delimiter.
} // End function: matches_start_delimiter()

bool line_is_one_repeated_char(const string &l) {
    if (l.length() < 2) return false;           // Can't be 1 or 2 chars only. Eliminates, e.g. blank lines.
    for (int i=0;i<l.length();i++) {
        if (l[i] != l[0]) return false;          // Some character is not identical.
    }
    return true;
} // End function: line_is_one_repeated_char()

int line_has_heading_format(const string &l) { // Does line start with 2 or more ==, then space, then text?
    if  (l.length() < 3) return 0;             // Too short to be minimum "== X".
    bool eqs= true;                            // Starts by looking for equals but later changes to spaces.
    int headlvl= 1;                            // Might as well keep track of level and return it.
    for (int i=1;i<l.length();i++) {           // Starting with 2 now since the first is known to be `=`.
        if (eqs) {                     // Looking for '='.
            if (l[i] == '=') {         // Found '='.
                headlvl++;             // This is what contributes to heading level.
                continue;              // On to the next character.
            } else {                   // The possible '=' not found.
                if (l[i] == ' ') {     // But found the correct next thing.
                    eqs= false;        // Change the search mode, now looking for spaces.
                } else {               // Looking for equals and found some other wrong thing e.g. '===Wrong'.
                    M("Almost a heading but missing a space: "+l,M::warning);
                    return 0;
                }                      // End else no space found.
            }                          // End else the possible '=' not found.
        } else {                       // Now on to looking for spaces.
            if (l[i] == ' ') {         // Found ' '. Extra superfluous spaces allowed. E.g. '==    OK'.
                continue;              // Spaces ignorable as long as there is at least one.
            } else {                   // Since the case of absolutely no spaces was checked, this must be it!
                M("Found a "+to_string(headlvl)+" heading: "+l,M::DEBUG); // Must be valid.
                return headlvl;        // Yes, that is good! This regexp found: '^===* *.*'
            }                          // End else did not find an expected space.
        }                              // End else looking for spaces now.
    }                                  // End for each character.
    M("Almost a Heading but no text provided: "+l,M::warning); // Something like '===   <eol>'.
    return 0;                          // So promising, but no.
} // End function: line_has_heading_format()

bool heading_level_is_valid(const int &oldlvl, const int &newlvl) {
M("oldlvl:"+to_string(oldlvl)+" newlvl:"+to_string(newlvl),M::DEBUG);
    if (newlvl > oldlvl+1) { // Error - skipping levels deeper into intermediate sub levels.
        string e= "expected level "+to_string(oldlvl+1)+" or less, got level "+to_string(newlvl);
        M("Section title out of sequence: " +e,M::warning);
        return false;
    } // End if making an excessive jump in level.
    return true;
}

bool is_one_of_these(char x,string chars) {
    for (char c:chars) if (c == x) return true;
    return false;
} // End function: is_one_of_these()

bool validzone(const int &c,vector<pair<int,int>> &nozones) { // Pair not mixed up in a `literal`.
    for (pair<int,int> p:nozones) { // Go through each backtick zone.
        if ((c>p.first)&&(c<p.second)) return false; // If in this protected range, then invalid.
    } // End for each pair of off limits mark positions.
    return true; // Not found to be in any of the protected ranges.
} // End function: validzone()

bool pair_are_valid_con(const string &s,const int &c_one,const int &c_two,vector<pair<int,int>> &nozones,const char &m) {
    // Are these valid constrained marks?
    // Instead of using the s which is kind of meaningless now, this must be transitioned to
    // using R. A specific problem is that if Backend replacement swaps out, say "`&`" with "`&amp;`" as
    // actually occured in my C notes on binary ops, then looking for the closer will be confused.
    if (c_one > 0) { // Beyond the beginning, so check for invalid previous characters.
        if (is_one_of_these(s[c_one-1],letters()+numbers()+"_"+":"+";"+"}")) return false;
    } // End if need to check the characters before marked range.
    if (c_one < s.size()-1) { // At end.
        if (is_one_of_these(s[c_two+1],letters()+numbers()+"_")) return false;
    } // End if need to check the characters after marked range.
    if (s[c_one+1] == ' ' || s[c_two-1] == ' ') return false;
    return true; // Seems legit.
} // End function: pair_are_valid_con()

int find_next_format_mark(const string &s,const int &start,char mark,vector<pair<int,int>> &nozones) {
    for (int c=start;c<s.size();c++) {
        if ((s[c] == mark) && validzone(c,nozones)) return c;
    }
    return -1;
} // End function: find_next_format_mark()

void find_format_marks(vector<pair<char,string>> &replacements, vector<pair<int,int>> &nozones,char m, BE &renderer) {
    string origstr; for (pair<char,string> p:replacements) origstr+=p.first;
    map<char,string> openers= renderer.get_openers(); map<char,string> closers= renderer.get_closers();
    int c_one= -1, c_two= 0; // Candidate opening mark and candidate closing mark.
    while (c_two < origstr.size() ) { // Do until closing position increments beyond possible length.
        if (c_one<0) c_one= find_next_format_mark(origstr,c_two,m,nozones);
        if (c_one > -1) { // After being just possibly updated.
            c_two= find_next_format_mark(origstr,c_one+1,m,nozones);
            if (c_two < 1) break;
        } else break;
// Debug format mark detection.
/*
cerr << "nerdtext DEBUG:   ";
for (char c:origstr) {if (c=='\n') cerr << '%'; else cerr << c;} cerr <<endl;
M(string(c_one,' ')+"^",M::DEBUG);
M(string(c_two,' ')+"^",M::DEBUG);
*/
        if (pair_are_valid_con(origstr,c_one,c_two,nozones,m)) {
            replacements[c_one]= {origstr[c_one],openers[m]};
            replacements[c_two]= {origstr[c_two],closers[m]};
            if (m== '`') nozones.push_back({c_one,c_two}); // These are the valid start/end positions for inline literal segments.
            c_one= find_next_format_mark(origstr,c_two+1,m,nozones); // If this two was good, then the next can't start at it. Look beyond.
        } else { // The pair were not valid.
            c_one= c_two; // Maybe the closer was really an opener.
        }
        c_two++;
    } // End while c_two is still in range.
    //return replacements.size(); // Change this to be how many replacements there were. Should be an even number!
    return;
} // End function: find_format_marks()

/* == Processing == */
int readit(istream &ins,list<string> &lines){ // Simply acquires the lines in memory from input source.
    string line;
    while (getline(ins,line)){
        lines.push_back(line);
    }
    return lines.size(); // Lines are already in referenced variable. Returns number of lines read.
} // End function: readit()

typedef struct { int position; int obracket; int cbracket; } LnPos; // Link start, open and close bracket positions.

int find_special(const string &input, const string &label, const int after,string &thetext) { // Find special things e.g. `label:[thetext]`
    int p; // Possible position.
    int L= input.size(); // Input string length - who knows how safe functions like find are. Better check.
    p= input.find(label,after); // Start by looking for the target label.
    if (p == -1) return p; // Nothing like that found; move along.
    if ((p+label.size()+3) > L) return -1; // 3 is ":[]". Not enough chars left. Not really possible at this point.
    if (input[p+label.size()] != ':') return -1; // No colon where it should be.
    int bro= p+label.size()+1; // Opening bracket position.
    if (input[bro] != '[') return -1; // No opening bracket where it should be.
    int brc= input.find("]",bro+1); // Find closing bracket.
    if (brc == -1) { // Closing ] not found.
        M("Looks like an unclosed "+label+" here: "+input,M::warning); 
        return -1; // Closing bracket not found. Might think about a warning having come this far.
    } // End if closing bracket not found.
    thetext= input.substr(bro+1,brc-bro-1); // The +1 and -1 there is to avoid grabbing the brackets. Load this answer into reference.
    return brc; // Useful for recursive searches of all instances of these.
} // End function: find_special()

vector<LnPos> find_protocol_positions(const string &s, const string &proto, const int &after, vector<pair<char,string>> &R) {
//M("In find_protocol_positions after=:"+to_string(after),M::DEBUG);
//M("In find_protocol_positions s=:"+s,M::DEBUG);
//for (char c:s) cout << c; // What's going on with the supplied string?
//for (pair<char,string> p:R) cout << p.first; // What's going on with the original?
//for (pair<char,string> p:R) cout << p.second; // What's going on with the replacement?
    int linkpos;                                          // LnPos starting position.
    int obracpos, cbracpos=0;                             // Open bracket position and closed.
    vector<LnPos> outputlist;
    string protoprefix= proto+":";                        // Protocol plus ':' to search for.
    int protosize= protoprefix.size();
    if (protoprefix.find("link")!=0) protoprefix+="//";   // Special ignoring of protocol "link".
    linkpos= s.find(protoprefix,after);                   // Search for this protocol.
    if (linkpos>=0) {                                     // Found? I.e. is it not -1?
        if (linkpos>0) {
            const string protocol_letters= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:/";
            if (protocol_letters.find(s[linkpos-1]) > -1) { // Space is best but any _non-protocol_ character will work.
            //if (s[linkpos-1] != ' ') {  // Links not at beginning of line should be preceeded by a space.
                return outputlist;
            } // End if not a space before the protocol specifier.
        } // End if link is not starting at the beginning of a line.
        obracpos= s.find("[",linkpos+protosize+1);  // Yes? Great, how about opening bracket?
        if (obracpos>-1) { // The opening was actually found.
            int a= obracpos+1; // Starting point to look for the closer is right after the opener.
            do {  // Keep finding until finding a true hit for the link, not just a nested closer.
                cbracpos= s.find("]",a); // A closing bracket found, but is it the right one?
                a++;
            } while ((R[cbracpos].second == "") && (a<R.size())); // Has this been given a replacement already? If not, done.
            //Check to see if this bracket has been modified already.
        } else cbracpos= -1; // If no opening, don't look for a closer because it could find one from different protocol.
        if (cbracpos>obracpos) { // Everything in place, in order. NOTE: Checking for weird stuff thanks to find(xx,obracpos+1)!
            if (s.find(" ",linkpos)<obracpos) { // There's a space before the opening bracket.
                //M("Space before opening bracket:"+s.substr(linkpos,(obracpos-linkpos)),M::error);
                //M("Space before opening bracket.",M::error);
                return outputlist;
            } // End if invalid space before opening bracket.
            LnPos foundlink= {linkpos,obracpos,cbracpos};
            if ((cbracpos+8) < s.size()) { // Maybe more out there; keep looking for this protocol.
            // ]link:a[]              <-- Soonest and shortest subsequent link possible.
            // 012345678              <-- Position with cbracpos=0, then 0+8=8 which is less than size of 9.
                outputlist= find_protocol_positions(s,proto,cbracpos,R); // Recursively continue search.
            } // End if may be another link yet to be discovered.
            outputlist.push_back(foundlink); // Always do this.
        } // End if open and closing brackets are in wrong order.
    } // End if there is some protocol word found.
    return outputlist;
} // End function: find_protocol_positions()

void sort_unique_linklist(vector<LnPos> &vL) {
    // Sorts multiple links that may have been picked up in the wrong order so the positions are definitely beginning to
    // end. With the new replacement vector the order isn't even that important. However, sometimes there are confusing
    // multiple detections on the same link that almost never should happen but sometimes are reasonable enough that
    // they deserve to be dealt with. For example, `https://x-ftp://yuk.bad[]` is terrible and stupid, but
    // `link:http://xed.ch[hmm]` is reasonable. Even `sftp://x[]` will get both sftp and ftp. Situations like this may -
    // not always - find two links that end in the same brackets. The goal then is to quickly eliminate link entries
    // that have matching bracket positions - both open and close - to some other earlier entry.
    // The unique() function requires a sorted list.
    if (vL.size() < 2) return; // No need to bother if there is only one or zero links found.
    std::sort(vL.begin(), vL.end(), [](LnPos a, LnPos b) { return a.position < b.position; }); // Sort. Direction irrelevant.
    vector<LnPos>::iterator unique_resize_iter; // This iterator is needed to resize after deletions from the container.
    unique_resize_iter= std::unique(vL.begin(), vL.end(),     // Since in usable forward order, remove duplicates using...
        [](LnPos a,LnPos b){ return a.cbracket == b.cbracket; });         // ...this lambda predicate function.
    vL.resize(std::distance(vL.begin(),unique_resize_iter) ); // Repair container so it properly takes effect.
    //reverse(vL.begin(),vL.end()); // Not needed now. Order doesn't really matter.
} // End function: sort_unique_linklist()

void create_link_replacements(string line, vector<pair<char,string>> &R,BE &B) { // Character positions and strings to exchange with.
    vector<LnPos> all_links_of_line; // Not limited to just one protocol - all possibile link formats collected here.
    vector<string> protocols= {"link","https","http","sftp","ftp","nntp","rsync","ssh", "imaps",
                               "imap","file","git","irc","rtsps","rtsp","snmp","svn"};
    for (string p:protocols) {
        vector<LnPos> links= find_protocol_positions(line,p,0,R);
        all_links_of_line.insert( all_links_of_line.end(), links.begin(), links.end() );
    }
    sort_unique_linklist(all_links_of_line);
    // At this point there is a vector of Links where each LnPos item contains: position,obracket,cbracket
    for (LnPos l:all_links_of_line) {                 // These should be helpfully set up in reverse order.
    //Need to replace everything between position and obracket with _null string_. It's going away.
        for (int n= l.position;n<l.obracket;n++) { // Replace with null between link start (l.position) and open bracket.
            R[n]= {R[n].first,string()}; // Blank replacement character.
        } // End for blanking range between link start and anchor text start.
        string linktext= line.substr(l.position,l.obracket-l.position);
        if (linktext.substr(0,5)=="link:") // Does link start with explicit specifier `link:xed.ch[]`?
            linktext= linktext.substr(5,string::npos); // Drop the `link:` formatting hint.
        pair<string,string> linksyntax= B.make_link(linktext); string tmpopen, tmpclose;
        tmpopen= linksyntax.first; tmpclose= linksyntax.second;
        if ((l.cbracket-l.obracket)==1) tmpopen+= linktext; // Anchor empty? E.g. `link:linktext[]`? Append link as anchor.
        R[l.obracket].second= tmpopen; // Replace R open and close bracket strings with linksytax.
        R[l.cbracket].second= tmpclose;
    } // End for each of the line's links.
    return; // Might be reasonable to supply have number of links replaced. But if it doesn't get used, no need.
} // End function: create_link_replacements()

bool in_inline_literal(int &pos,vector<pair<int,int>> &nozones) { // Checks to see if the supplied position is in a prohibited zone.
    // Currently used in create_img_replacements() but probably also valid in the same way for pass through and others.
    // I guess I'll start to find more of this stuff when I document the system itself. :-/
    for (pair<int,int> nz:nozones) {
        if ( (nz.first < pos) && (nz.second > pos)){ // Then, yes, it's in this literal protected zone.
            return true;
        } // End if in side the zone.
    } // End for each pair of no-zones.
    return false; 
} // End function: in_inline_literal()

void create_img_replacements(int after,string line,vector<pair<int,int>> &nozones,vector<pair<char,string>> &R,BE &B) { // Modify replacement map for images.
    // This probably should use `find_special()` which redundantly goes through a lot of this mess.
    if (line.size() < 8) return; // Don't bother even thinking about lines to short for any image syntax. Prevents seg faults on next line.
    if (after > line.size()-8) return; // Too close to end for image syntax. Stop any recursion now!
    int ipos= line.find("image:",after);   // Image syntax start position.
    int cbpos= line.size(); // Default to stopping any recursive searching.
    if (ipos > -1) {           // Nothing like an image present from here to end of line. Done.
        int obpos= line.find("[",ipos);  // Open bracket position.
        if (obpos > 6) {          // Doesn't have enough for `^image:X[` so not a valid image.
            cbpos= line.find("]",obpos); // Close bracket position.
            if (obpos < cbpos) {      // Probably didn't find the closer, but something is not valid.
                int spacecheck= line.find(" ",ipos); // Can't have spaces between `image:` and open bracket.
                if ((spacecheck > ipos) && (spacecheck < obpos)) { // Checking for space after image but before open bracket.
                    M("Found space in image specifier!",M::warning);
                    cbpos= ipos+1; // Reset in case this may be something like `image: image:./realsrc.jpg[okreally]`
                } else if ((in_inline_literal(obpos,nozones)) || (in_inline_literal(cbpos,nozones))) { // Image in inline literal, protected. 
                    // Obviously there could be issues with ''image:url `blah` []''... hopefully invalid at an earlier check.
                    M("Found a complete image specifier in a literal which is interesting.",M::DEBUG);
                    cbpos= ipos+1; // Reset in case this may be something like `image: image:./realsrc.jpg[okreally]`
                } else { // No weird spaces. Looks good.
                    string linktext= line.substr(ipos+6,obpos-(ipos+6));
                    string alttext= linktext; // Not sure this is a sane default. Maybe better to have none.
                    if ((cbpos-obpos)>1) alttext= line.substr(obpos+1,cbpos-obpos-1); // If there really is a true alt text, use it.
                    string  imagesyntax= B.make_img(linktext,alttext);
                    R[ipos].second= imagesyntax; // Replace R at image start location with whole thing.
                    for (int p= ipos+1;p<=cbpos;p++) { // Must clear original text defaults out of the replacement string.
                        R[p].second= ""; // Also important later as sign cbracket closes image and not a possible link!
                    } // End for all characters of the image specifier to be blanked out.
                } // End for else everything looks like a correct image syntax.
            } // End if some weird order problem with brackets.
        } // End if the open bracket position is too close to start of "image:".
    } // End if -1 found for position.
    if (cbpos < (line.size()-8)) create_img_replacements(cbpos,line,nozones,R,B); // Could be more of these so recursively keep checking until done.
    return;
} // End function: create_img_replacements()

void create_pass_replacements(int after,string line,vector<pair<char,string>> &R,BE &B) {
    string thetext;
    int cbpos= find_special(line,"pass",after,thetext);
M(thetext,M::DEBUG);
    if (cbpos<0) return;
    int thestart= cbpos-thetext.size()-6;
    R[thestart].second= thetext; // The text specified just gets passed along to the start position. The rest, all dropped.
    thestart++; // The first location takes the text. _After_ this will be blanked out.
    for (int n=thestart;n<=cbpos;n++) { // All the characters that need to be blanked out.
        R[n].second= ""; // Blank most of this out.
    } // End for all the characters in the pass:[thetext] replacement operation.
    if (cbpos < (line.size()-8)) create_pass_replacements(cbpos,line,R,B); // Could be more of these so recursively keep checking until done.
} // End function: create_pass_replacements()

void create_plus_replacements(vector<pair<char,string>> &R,BE &B) { // Modify replacement map for images.
    int N= R.size(); // Characters in the original line.
    for (int n=0;n<N;n++) { // Go through each character in the original.
        bool f_ok= false, b_ok= false;
        if (R[n].first == '+') {
            if ( (n == 0) || (R[n-1].first == ' ') || (R[n-1].first == '\n') ) f_ok= true; // This is at the beginning or previous is a space.
            if (((n+1)==N) || (R[n+1].first == '\n')) b_ok= true; // This is at the end or next is a newline.
            if (f_ok && b_ok) R[n].second= B.make_br(); // Replace this plus with break. Need to abstract to B!
        } // End if a '+' has been found.
    } // End for every character in the input line.
} // End function: create_plus_replacements()

void create_litem_syntax(vector<pair<char,string>> &R, BE &B) {
    int Rlen= R.size();
    string opener= B.listitems('U'); // Prescreened as definitely a list so prepare the opener.
    bool newliok= true; // If a '* ' shows up is it valid to be the bullet of a list item?
    bool initemnow= false; // Helps track if just starting out or one of many (that must be closed).
    for (int n=0;n<Rlen-2;n++) {
        switch(R[n].first) {
          case '*': {
            if (newliok && (R[n+1].first==' ')) { // A star is found and must be considered a legit bullet.
M("Found a litem star...",M::DEBUG);
                R[n].second= opener + B.listitems('L'); // Replace the * with syntax. Goes first becuase always happens.
                opener= string(); // Clear the opener since it's job is done.
                R[n+1].second= string(); // Replace the space after the * with nothing. Nothing should be there.
                if (initemnow) {
                    R[n].second= B.listitems('l') + R[n].second;
                } // Prepend closing previous if necessary.
            initemnow= true; 
            newliok= false; // There won't be another one until a NL is found.
            } // End if a new list item is valid.
            break;
          } // End case - found *
          case ' ': break; // Ignore because leading spaces are valid and might be ok. So doesn't affect anything.
          case '\n': {newliok= true; break;} // After this new line, a star as non-space character is a bullet.
          default:  newliok= false; // Some nonspace thing that wasn't a * was found meaning this is not going to be an item.
        }
    } // End for each character in contents replacement set.
    R[Rlen-2].second= R[Rlen-2].second+ B.listitems('l') + B.listitems('u'); // </td> </tr> </table>
} // End function: create_litem_syntax()

void create_dditem_syntax(vector<pair<char,string>> &R, BE &B) { // Format the contents of a definition/description item block.
    int L= R.size(); // Length of input string.
    int dtstart= 0,colpos= -1, ddend= -1; // Stars point to these 3 positions: `*->blahterm *->:: blah desc*->\n`
    for (int n=0;n<L;n++) {
        switch (R[n].first) {
          case ':':  {
              if (n>L-3) break; // This is not the colon that matters - too close to the EOL.
              if ((R[n+1].first == ':') && (R[n+2].first == ' ') ) { // Two colons in a row, then a space! Correct syntax for a dd!
                  if (dtstart < colpos < ddend) { // Commit the previous one, if there was one. This is a new one in progress.
                      R[dtstart].second= B.dditems('T')+R[dtstart].second;
                      R[colpos].second= B.dditems('t')+B.dditems('D'); R[colpos+1].second= ""; R[colpos+2].second= "";
                      R[ddend].second= B.dditems('d')+R[ddend].second;
                      dtstart= ddend + 1; // New start is just after old end. Check this didn't just run off.
                      ddend= -1; // Reset to unknown.
                  } // End if second colon and a space found.
                  colpos= n; // This needs to be updated at some point no matter what.
                  n+= 2; // Skip looking at the next one - it's a colon too. And the next, a space.
              } // End if correct dt::dd separator found.
              break;
          } // End case : found.
          case '\n': { // End of the line - possibly meaning the end of the DD.
              ddend= n; // This will be the end of the dd unless another whole line comes along.
              break;
          } // End case newline found.
        } // End switch to handle various important characters.
    } // End for every item in the replacement set.
    if (dtstart < colpos < ddend) { // Commit the previous one, if there was one. This is a new one in progress.
        R[dtstart].second= B.dditems('T')+R[dtstart].second;
        R[colpos].second= B.dditems('t')+B.dditems('D'); R[colpos+1].second= ""; R[colpos+2].second= "";
        R[ddend-1].second= R[ddend-1].second+B.dditems('d');
    }
    R[0].second= B.dditems('L')+R[0].second; // Needs to be down here to make sure it isn't pushed after the <dt>.
    R[L-2].second= R[L-2].second + B.dditems('l'); // -2 because count went past by 1 and the real -1 is the ending NL.
    return;
} // End function: create_dditem_syntax()

void create_table_syntax(vector<pair<char,string>> &R, BE &B) {
    int Rlen= R.size();
    char sep= ',';
    for (int n=0;n<Rlen;n++) {
        if (n == 0) { // The first character of the data.
            if (R[n].first == '|') { // Is the first character a bar?
                sep= '|'; // Reset the separator to look for.
                R[0].second= B.table('T') + B.table('R') + B.table('D'); // Replace bar with table opening stuff.
            } else { // End the first character is not a bar, might be important.
                R[0].second= B.table('T') + B.table('R') + B.table('D') + R[0].second; // Prepend all the table opening stuff.
            } // End first character is not a bar so keep it.
        } else {                                                           // Now beyond the first character.
            if (R[n].first == sep) {                                       // A separator!
                if ((n>0) && (R[n-1].first=='\n')) { // Is this a bar at the line beginning?
                    R[n].second= string(); // Ignore it.
                } else { // This is a separator some ways in?
                    R[n].second= B.table('d'); // Replace separator with the data closer.
                    if ((n<Rlen-1) && (R[n+1].first != '\n')) { // Not at end and next char is not newline.
                        R[n].second+=B.table('D'); // There must be more then, so open a new one!
                    } // End if end or dangling pointless separator at end of line (which is valid but ignored).
                } // End this is a separator in the middle of the line somewhere, not at start.
            } else if (R[n].first == '\n') { // This is a new line
                R[n].second= B.table('d') + B.table('r') + R[n].second;    // </td></tr>
                if (n<Rlen-1) { // Still not the end.
                    R[n].second= R[n].second + B.table('R') + B.table('D'); // <tr><td> There will be another newline.
                } // End if this must have been the final newline.
            } // End if end of a line has been reached.
        } // End if this is not the first character.
    } // End for each character in content's replacement set.
    R[Rlen-2].second= R[Rlen-2].second+ B.table('d') + B.table('r') + B.table('t'); // </td> </tr> </table>
} // End function: create_table_syntax()

void setup_literal(vector<pair<char,string>> &R,BE &B){
    R[0].second= B.literal('s') + R[0].second; // Probably could have added it from the default second too.
    R[R.size()-2].second+= B.literal('e'); // N.b. _append_. -2 because 0 is off the edge and -1 is the NL which is fine.
    return;
} // End function: setup_literal()

void setup_heading(vector<pair<char,string>> &R,int level,BE &B) { // Section heading formatting.
// The first non-space character after the first equals.
    string headlvl= to_string(level+1); // Level 1 ("== This") is "<h2>this</h2>".
    pair<string,string> h= B.make_heading(level);
    //if (level == 1) { // Some special attention for the title which doesn't have ==='s to ditch.
    if (R[0].first == '=') { // Some special attention for the title which doesn't have ==='s to ditch.
        R[0].second=  h.first; //"<h"+headlvl+">" leave out the =
    } else { // Seems like a main heading with underline style, so can't lose its first char.
        R[0].second=  h.first+R[0].second; //"<h"+headlvl+">" + first replacement text;
    }
    R[R.size()-2].second+= h.second; // "</h"+headlvl+">"; // -2 because 0 is off the edge and -1 is the NL which is fine.
    //if (level == 1) { // Some special attention for the title which doesn't have ==='s to ditch.
        //R[0].second+= string(1,R[0].first); // Add that first letter back to the replacement list please!
        //return; 
    //}
    int n;
    bool eqhere= false;
    for (n=1;n<R.size();n++) { // Replaced beginning and end but now need to quash superfluous "==   ".
        if (R[n].first == '=') { R[n].second= string(); eqhere= true; continue; }
        break;
    } // End for all of the rest of the characters looking for asciidoc syntax to blank out.
    if (eqhere) {
        for (int m=n;m<R.size();m++) { // Just did all the "="; now looking for any " " before real heading.
            if (R[m].first == ' ') { R[m].second= string(); continue; }
            break;
        } // End for all of the rest of the characters looking for asciidoc syntax to clear out with blanks.
    } // End if this follows some detected equals.
    return;
} // End function: setup_heading()

void escape_backend_syntax(vector<pair<char,string>> &R,string &s,BE &B) {
    for (int n=0;n<s.size()-1;n++) { // For each character up until 1 from last.
    //for (pair<char,string> p:R) { // Go through each character in the supplied string.
        string thechar= string(1,R[n].first);
        string r= B.backend_armor(thechar);
        if (r!=thechar) { // It has changed, so update. Might be just as fast to update all of them.
            M("Fixing a backend syntax confuser: "+thechar+" = "+r+" ",M::DEBUG);
            R[n].second= r;
        }
    } // End check out every original character.
    return;
} // End function: escape_backend_syntax()

void replace_mdash(string line,vector<pair<int,int>> &nozones, vector<pair<char,string>> &R,BE &B) { // Character positions and strings to exchange with.
    for (int n=0;n<line.size()-1;n++) { // For each character up until 1 from last.
        if ((line[n] == '-') && (line[n+1] == '-')) { // Two dashes found.
            if (!in_inline_literal(n,nozones)) { // Image in inline literal, protected. If one is good/bad, the other must be good/bad.
                if ((n == 0) || line[n-1] != '-') { // Beginning is ok.
                    if ((n == line.size()-2) || line[n+2] != '-') { // End is ok.
                        M("Found double dash.",M::DEBUG);
                        R[n].second= B.get_mdash(); // Load the first dash's output with whatever is needed.
                        R[n+1].second= string(); // Empty. The full replacement goes in the first slot.
                    } // End of end check.
                } // End beginning ok.
            } // End if in valid unprotected region, i.e. not in an inline literal.

        } // End two dashes found.
    } // End scan through all chars.
} // End function: replace_mdash()

bool main_title_detect(const int line1len, list<string> &lines,int n, string &line){
    // This could probably be abstracted to check underline styles of subheadings too
    // but for some weird reason I just use it for main titles personally, so that's what we get.
    // This one is questionalbe; the other underline syntax styles are absurd.
    if ( n > 2 ) return false;      // n is current line number. Only 2nd line valid (first line is n=1).
    if ( line1len != line.size() ) return false; // First and this line must match in length.
    if ( ! line_is_one_repeated_char(line) ) return false; // All chars must be '=' (already established) and are not.
    return true; // Is line 2, length of line 1, homogenous. '=' already established. Definitely main title!
} // End function: main_title_detect()

list<Block> process_lines(list<string> &lines){ // Returning a list of rough parsed Blocks. Obivous delimited blocks organized.
    list<Block> blist;
    Block cb;                                   // Current Block.
    string line;
    string start_delimiter;
    bool in_delimited_block= false;
    int current_heading_level= 1;               // One is nothing or just title. '= Level 1', '== Level 2', etc.
    int linum= 0; // Where's Pythons enum() when you need it? This is ridiculous. Too bad std::distance() is flaky.
    M("Lines read: "+to_string(lines.size()),M::DEBUG);
    for (list<string>::iterator i=lines.begin();i!=lines.end();i++) {
        linum++;
        //M("Line number:"+to_string(linum),M::DEBUG);
        line= *i;
        obliterate_tabs(line);
        //if (contains_only_whitespace(line)){ } // Paragraph blanks are ok. Address this after forming blocks.
        if (in_delimited_block) { // Continuation of an established delimited block.
            if (matches_start_delimiter(line,start_delimiter)) { // Found a closing delimiter?
                in_delimited_block= false; // Stop being in a delimited block now.
                blist.push_back(cb);       // Add this newly discovered block to the block list.
                cb= Block();               // Reset the current block to a new default plain one.
            } else { // In a delimited block and not currently closing it. So actual block content.
                cb.add_line(line);
            } // End else in but not closing a delimited block.
        } // End if in_delimited_block
        else { // Not in a delimited block at this point.
// -----------------------------------------------------
            if (line[0] == '=') { // Starts with =. Seems like this line could involve title syntax.
                int possible_heading_level;
                if ( main_title_detect(cb.length_of_first_line(),lines,linum,line) ) { // More complicated underline main title found.
                    M("Main title specified with underline.",M::DEBUG);
                    cb.set_type(Block::title);
                    cb.nest= 1;                  // This is main title level.
                    possible_heading_level= 1;   // Underline style doesn't pick this up so set it now.
                    blist.push_back(cb);         // This must be a title block and it is now done. Reset.
                    cb= Block();                 // Reset the current block to a new default plain one.
                    current_heading_level= 1;
                } else  { // Not a main title with underline style.
                    possible_heading_level= line_has_heading_format(line); // This here allows a check for near heading that isn't confused for main title.
                    if (possible_heading_level == 1) { // Then main title using single = syntax.
                        M("Main title specified with one equals",M::DEBUG);
                        cb.set_type(Block::title);
                        string stripped= line.substr(2,string::npos);           // Add the title text.
                        cb.add_line(stripped);           // Add the title text.
                        cb.nest= 1;                      // This is main title level.
                        blist.push_back(cb);             // This must be a title block and it is now done. Reset.
                        cb= Block();                     // Reset the current block to a new default plain one.
                        current_heading_level= possible_heading_level;
                        continue;
                    } else { // Does not seem to be a _main_ title. Perhaps a sub heading title. 
                        M("Checking for subheading syntax.",M::DEBUG);
                        if (possible_heading_level) { // Check for normal sub-heading syntax. 0 is not heading.
                            if (heading_level_is_valid(current_heading_level,possible_heading_level)) {
                                M("Subheading syntax found. Level: "+to_string(possible_heading_level),M::DEBUG);
                                if (cb.lines_present()) {    // Clean up previous. Only add and reset if contains something.
                                    blist.push_back(cb);     // Add the non-delimited previous block to list.
                                    cb= Block();             // Reset the current block to a new default one.
                                } // End if lines exist to make adding the block worthwhile.
                                cb.set_type(Block::heading); // Prepare this as a heading block.
                                cb.add_line(line);           // Add the title text. TODO: Need to strip the ====.
                                cb.nest= possible_heading_level; // Important for formatting headings.
                                blist.push_back(cb);         // One line only. Done with this block. Add it.
                                cb= Block();                 // Reset the current block to a new default plain one.
                                current_heading_level= possible_heading_level;
                                continue;
                            } // End if heading level makes sense.
                        } // End if line has heading format.
                        //M("Not main title despite confusing similarities.",M::DEBUG);
                    } // End else a genuine main title.
                } // End else not main title with underline.
            } // End if beginning of line starts with a "=" indicating some kind of title.
// -----------------------------------------------------
            if (line_is_one_repeated_char(line)) { // Remember that -~^+= are title underlines! OMIT! Or prepass to convert?
                M("Possible delimited block: "+line,M::DEBUG);
                start_delimiter= line; // Tentative. But will never be read if a delimited block is not found.
                in_delimited_block= true; // This is likely true but if none of the hints pan out, it gets put back.
                Block::Type newtype; // Need to hold off on actual new typee assignment until old block is correctly dispatched.
                switch (line[0]) {
                    case '.': { newtype= Block::literal; break; } // Same as indent. Prepass to convert indents?
                    case '-': { newtype= Block::listing; break; }
                    case '/': { newtype= Block::comment; break; }
                    case '*': { newtype= Block::sidebar; break; }
                    case '_': { newtype= Block::quote; break; }
                    case '+': { newtype= Block::passthrough; break; }
                    default: { in_delimited_block= false; }      // Real normal stuff after all!
                }
                if (in_delimited_block) {      // Must be a newly arrived delimited block.
                    M("Type:" + (cb.query_type()),M::DEBUG);
                    blist.push_back(cb);       // Add the now ended plain block to the block list.
                    cb= Block();               // Reset the plain block with a new soon-to-be delimited one.
                    cb.set_type(newtype); // Only now it is ok to overwrite with the new type for the new block.
                }
            } else { // Line is normal non-delimiter and not in a delimited block, so plain.
                cb.add_line(line);
            } // End if line is single repeated line, i.e. start of a new probable delimiter.
        } // End else not in a delimited block.
    } // End for iterating over each item in the supplied line list.
    if (cb.query_type() == "plain" && cb.lines_present()) {
        blist.push_back(cb); // Finish any implied plain blocks at the end.
    } // End if non-empty plain block.
M("First block:"+ blist.begin()->query_type(),M::DEBUG);
    return blist;
} // End function `process_lines`.

list<Block> process_document(list<string> &rawlines,BE &renderer) { // At some point send back to main for printing.
    // Process the raw lines into blocks.
    list<Block> RoughBlocks= process_lines(rawlines); // Unrefined blocks easily separated by delimiters.
    list<Block> Blocks;                               // All finished blocks with refinement.
    M("RoughBlocks found after processing: "+to_string(RoughBlocks.size()),M::DEBUG);
    for (list<Block>::iterator i=RoughBlocks.begin();i!=RoughBlocks.end();i++) { // Iterate through each Block.
        i->trim_blocks();
        if (i->query_type() == "plain") { // Plain blocks need to be refined for more subtle formatting.
            list<Block> refined_plain_blocks= i->refine();
M("Paragraph block refine() successful...",M::DEBUG);
            for (Block B:refined_plain_blocks) Blocks.push_back(B);
M("Paragraph block push refined blocks to Blocks successful...",M::DEBUG);
        } else { // Rough block is not plain and needs no refinement.
            Blocks.push_back(*i); // Simply add the rough block directly to final Block list.
        } // End non-plain block that's ready to go.
    } // End for iterate over Blocks for refinement.
M("About to format each block in Blocks which has size of:"+to_string(Blocks.size()),M::DEBUG);
    for (list<Block>::iterator i=Blocks.begin();i!=Blocks.end();i++) { // Iterate through each Block.
M("Formatting this block:"+i->get_first_line(),M::DEBUG);
M("process_document Blocks.query_type:" + (i->query_type()),M::DEBUG);
        i->format_content(renderer);
    } // End for iterate over Blocks for output formatting.
M("Content processed...",M::DEBUG);
    return Blocks;
} // End function: process_document()

void output_document(list<Block> Blocks,BE &renderer,string style,ostream &os) {
    M( string(20,'=')+ "Conversion Complete" +string(20,'=') ,M::DEBUG);
    if (FULLDOCUMENT) { // Full documents need headers. Omit if just an included excerpt.
        if (Blocks.begin()->query_type() == "title") {// Does a title need to figure into the header?
            // HERE
            os << renderer.make_doc_header(Blocks.begin()->get_first_line(),style);
        } else { // No title specified.
            // HERE
            os << renderer.make_doc_header(string(),style); // Do a plain header.
        } // End else no title.
    } // End check for excpert or full document - header.
    for (list<Block>::iterator i=Blocks.begin();i!=Blocks.end();i++) os << *i; // Output all blocks.
    if (FULLDOCUMENT) { // Full documents need footers. Omit if just an included excerpt.
        // HERE
        os << renderer.make_doc_footer(); // Do a normal footer.
    } // End check for excpert or full document - footer.
    return;
} // End function: output_document()

/* == Main == */
int main(int argc,char **argv){
    Config C= Config(argc,argv);  // Parse command line options.
    string style; C.optval('s',style);
    string outfile; C.optval('o',outfile);
    list<string> rawlines;
    BE_HTML renderer; // Backend object.
    //BE_TeX renderer; // Backend object.
    //BE renderer; // Backend object.
    if (C.arglist().size() == 0) { // No files specified - look for standard input.
		readit(cin,rawlines); // No arguments? Use standard input then.
    } else { // go through each file specified in arguments.
		for (string s:C.arglist()) { // Go through all files (presumably!) listed.
            if ((s[0] == '-') && (s[1] == '\0')) { // The standard input dash?
                M("Found a stdin dash:"+s,M::DEBUG);
                readit(cin,rawlines); // No arguments? Use standard input then.
            } else { // A real file name provided.
                M("Found a user specified file name:"+s,M::DEBUG);
                ifstream ifile(s);
                if (ifile.is_open()) { // Double check that it opened ok.
                    readit(ifile,rawlines);
                    ifile.close();
                } // End if file opened cleanly.
                else M("Unable to open file for reading!!",M::error);
            } // Not the stdin dash, a real filename provided.
        } // End for every item in the argument list.
    } // End else there are some files specified as arguments.
    if ((outfile[0] == '-') && (outfile[1] == '\0')) { // The standard output dash?
        output_document(process_document(rawlines,renderer),renderer,style,cout);
    } else { // Seems to be more than just the standard output dash.
        ofstream f(outfile);
        if (f.is_open()) {
            output_document(process_document(rawlines,renderer),renderer,style,f);
            f.close();
        } else {
            M("Problem writing to:"+outfile,M::warning);
            exit(1);
        }
    } // End real output file name provided.
    return 0; // 0 is successful. Something is would not be.
} // End function `main`.

/* -------------------------------------------------------------------------
Copyright 2021 Chris X Edwards

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 3 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

The license should be available at https://www.gnu.org/licenses/gpl-3.0.txt. If
for some reason it is not, a copy is distributed with this project at./LICENSE.
   ------------------------------------------------------------------------- */
