/*
    This file is part of Msc-generator.
    Copyright (C) 2008-2024 Zoltan Turanyi
    Distributed under GNU Affero General Public License.

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

    Msc-generator 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.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Msc-generator.  If not, see <http://www.gnu.org/licenses/>.
*/
/** @file gvstyle.cpp Graph specific style and context definitions Also DOT attribute descriptions.
* @ingroup libgvgen_files */


#include <cstring>
#include "parser_tools.h" //for remove_head_tail_whitespace()
#include "gvstyle.h"
#include "graphchart.h"

using namespace graph;

/** Return a textual representation of a graph edge type.
 * This is the symbol used to actually define the edge in the input file.*/
std::string_view GraphEdgeType::AsText() const
{
    switch (type) {
    default: _ASSERT(0); FALLTHROUGH;
    case GraphEdgeType::SOLID:
        switch (dir) {
        case GraphEdgeType::NO_ARROW: return "--";
        default: _ASSERT(0); FALLTHROUGH;
        case GraphEdgeType::FWD:      return "->";
        case GraphEdgeType::BACK:     return "<-";
        case GraphEdgeType::BIDIR:    return "<->";
        }
    case GraphEdgeType::DOUBLE:
        switch (dir) {
        case GraphEdgeType::NO_ARROW: return "==";
        default: _ASSERT(0); FALLTHROUGH;
        case GraphEdgeType::FWD:      return "=>";
        case GraphEdgeType::BACK:     return "<=";
        case GraphEdgeType::BIDIR:    return "<=>";
        }
    case GraphEdgeType::DOTTED:
        switch (dir) {
        case GraphEdgeType::NO_ARROW: return "..";
        default: _ASSERT(0); FALLTHROUGH;
        case GraphEdgeType::FWD:      return ">";
        case GraphEdgeType::BACK:     return "<";
        case GraphEdgeType::BIDIR:    return "<>";
        }
    case GraphEdgeType::DASHED:
        switch (dir) {
        case GraphEdgeType::NO_ARROW: return "++";
        default: _ASSERT(0); FALLTHROUGH;
        case GraphEdgeType::FWD:      return ">>";
        case GraphEdgeType::BACK:     return "<<";
        case GraphEdgeType::BIDIR:    return "<<>>";
        }
    case GraphEdgeType::SOLID2:
        switch (dir) {
        case GraphEdgeType::NO_ARROW: return "---";
        default: _ASSERT(0); FALLTHROUGH;
        case GraphEdgeType::FWD:      return "->>";
        case GraphEdgeType::BACK:     return "<<-";
        case GraphEdgeType::BIDIR:    return "<<->>";
        }
    case GraphEdgeType::DOUBLE2:
        switch (dir) {
        case GraphEdgeType::NO_ARROW: return "===";
        default: _ASSERT(0); FALLTHROUGH;
        case GraphEdgeType::FWD:      return "==>";
        case GraphEdgeType::BACK:     return "<==";
        case GraphEdgeType::BIDIR:    return "<==>";
        }
    }

    switch (dir) {
    case GraphEdgeType::NO_ARROW: return "--";
    default: _ASSERT(0); FALLTHROUGH;
    case GraphEdgeType::FWD:      return "->";
    case GraphEdgeType::BACK:     return "<-";
    case GraphEdgeType::BIDIR:    return "<->";
    }
}


/** Generates an Attribute object from this GraphAttribute. */
Attribute GraphAttribute::GenerateAttribute(std::string_view name, bool attempt_number) const
{
    FileLineColRange a(linenum_attr, linenum_attr);
    FileLineColRange v(linenum_value, linenum_value);
    //if only digits, signs and dot, attempt to return a number FIXME
    if (attempt_number &&
             value.end()==std::find_if(value.begin(), value.end(),
                 [](char ch) {return nullptr==strchr("0123456789.+-", ch); }))
        return Attribute(name, value.c_str(), a, v);
    return Attribute(name, value.c_str(), a, v);
}

/** Parse a comma separated list of style values and apply it to us.*/
void ParsedStyle::Parse(std::string_view ss)
{
    static const std::array<std::string_view, 5> line_styles =
        {"dashed", "dotted", "solid", "double", "triple"};
    static const std::array<std::string_view, 3> fill_styles =
        {"wedged", "striped", "filled"};
    //pick only one (last) of the above two list
    //and all of the others
    while (ss.size()) {
        const size_t pos = ss.find(',');
        std::string_view s;
        if (pos != ss.npos) {
            s = ss.substr(0, pos);
            ss.remove_prefix(pos + 1);
        } else
            s.swap(ss);
        remove_head_tail_whitespace(s);
        bool minus = false;
        if (s[0]=='!') {
            line.clear();
            fill.clear();
            others.clear();
            s.remove_prefix(1);
        } else if (s[0]=='-') {
            minus = true;
            s.remove_prefix(1);
        }
        remove_head_tail_whitespace(s); //remove space between '!'/'-' and value
        if (std::ranges::find(line_styles, s)!=line_styles.end()) {
            if (minus) {
                if (line==s) line.clear();
            } else
                line = s;
        } else if (std::ranges::find(fill_styles, s)!=fill_styles.end()) {
            if (minus) {
                if (fill==s) fill.clear();
            } else
                fill = s;
        } else if (!minus)
            others.insert(std::string(s));
        else if (auto i = others.find(s); i!=others.end())
            others.erase(i);
    }
}

/** Convert us back to a comma separated list of values understood by graphviz */
ParsedStyle::operator string() const
{
    std::string result;
    if (line.length())
        result = line + ",";
    if (fill.length())
        result += fill + ",";
    for (auto &o:others)
        result += o +",";
    if (result.length())
        result.erase(--result.end()); //cut away last colon
    return result;
}


/** Potential characters to denote applicability of graph language
 * attributes to graphs/nodes/edges/subgraphs. */
static const char attr_types_code[] = "GCSNE";

const DotAttributeDescription * GraphStyle::FindThisAttribute(std::string_view a)
{
    for (auto aa = DotAttributes; aa->name; aa++)
        if (a==aa->name)
            return aa;
    return nullptr;
}

Style &GraphStyle::operator += (const Style &toadd)
{
    const GraphStyle* p = dynamic_cast<const GraphStyle *>(&toadd);
    if (p)
        for (auto &a : p->attributes)
            attributes[a.first]=a.second;
    return *this;
}

/** Try to find attributes in an error message provided by graphviz.
 * This is not rocket science, we just try to find phrases of some
 * graphviz errors. Then we try to see if we have one such attribute set,
 * and return it - just to provide a line number to the user (which
 * graphviz dont do). */
const GraphAttribute * GraphStyle::FindInErrorText(const std::string & text) const
{
    //see if common text values found
    static const char translations[][30] = {
        "Arrow type", "arrowType",
        "color", "color/colorList",
        ""
    };
    std::list<const DotAttributeDescription *> found;
    for (auto t = translations; **t; t+=2)
        if (text.find(*t) != std::string::npos)
            for (auto aa = DotAttributes; aa->name; aa++)
                if (strncmp(aa->name, t[1], 30)==0 || strncmp(aa->type, t[1], 30)==0)
                    found.push_back(aa);
    //Now check our attributes:
    for (auto &a : attributes) {
        if (text.find(a.second.value) == std::string::npos) continue;
        else if (text.find(a.first) == std::string::npos
                 && (!a.second.descr || text.find(a.second.descr->type) == std::string::npos)) {
            //see if our attribute is one we found in one of the translations
            if (std::find(found.begin(), found.end(), a.second.descr)==found.end())
                continue;
        }
        return &a.second;
    }
    return nullptr;
}

bool GraphStyle::DoesThisAttributeApply(const DotAttributeDescription *aa, GraphStyle::EGraphElementType type,
                                        std::string_view layout)
{
    if (aa == nullptr) return false;
    if (type == ANY) return true;
    if (strchr(aa->used_by, attr_types_code[type]) == nullptr) return false;
    //OK, see if applicable for this layout
    if (layout.length()==0) return true;
    const auto layouts = GetLayoutMethods();
    //see if any layout name can be found in aa->notes;
    for (auto &l : layouts)
        if (strstr(aa->notes, l.c_str())) {
            //Yes, at least one layout name is in notes
            //see if we have our layout
            const bool is_there = strstr(aa->notes, layout.data())!=nullptr;
            //see if we have 'not'
            if (strstr(aa->notes, "not"))
                return !is_there;
            //see if we have 'not'
            if (strstr(aa->notes, "only"))
                return is_there;
            _ASSERT(0);
            return true;
        }
    return true; //no layout in aa->notes, all layouts applicable.
}

/** Add an attribute to a style. Provide errors if needed and return true
 * if the attribute name was recognized as one that applies to us.*/
bool GraphStyle::AddAttribute(const Attribute & a, Chart *chart)
{
    _ASSERT(a.type!=EAttrType::STYLE);
    string l;
    //get the current layout if our chart is a GraphChart
    //(ignore layout specifics if not)
    GraphChart *ch = dynamic_cast<GraphChart *>(chart);
    if (ch && ch->Graphs.size()) {
        auto i = ch->GetCurrentGraph().style.read().attributes.find("layout");
        if (i!=ch->GetCurrentGraph().style.read().attributes.end())
            l = i->second.value;
    }
    const bool colon_label = a.name == "::";
    string attr_name = colon_label ? "label" : a.name;
    auto aa = FindThisAttribute(attr_name);
    if (!DoesThisAttributeApply(aa, element_type, l)) return false;
    //now check a few values
    std::string attr_value = a.value;
    if (strncmp(aa->type, "bool", 5)==0) {
        bool res = true;
        if (CheckBoolValue(a.value.c_str(), res))
            attr_value = res ? "1" : "0";
        else if (IsDotAttributeAnExtension(aa)) {
            if (chart)
                BooleanValueError(a, chart->Error);
            return true; //we recognized attr as applicable
        }
    }
    if (attr_name == "style" && attributes[attr_name].value.length()) {
        //merge style values
        ParsedStyle ps;
        ps.Parse(attributes[attr_name].value);
        ps.Parse(attr_value);
        attr_value = ps;
    }
    attributes[attr_name].value = attr_value;
    attributes[attr_name].linenum_attr = a.linenum_attr.start;
    attributes[attr_name].linenum_value = a.linenum_value.start;
    attributes[attr_name].descr = aa;
    //for labels we store the original "decorated" value in "original_label"
    //and store a version without formatting escapes in "label"
    if (colon_label) {
        //When user specifies a colon label, we store the value both in
        //attributes "label" and "original_value", this is how we know
        //in PostParseProcess() that this was a colon label.
        attributes["original_label"].value = attr_value;
        attributes["original_label"].linenum_attr = a.linenum_attr.start;
        attributes["original_label"].linenum_value = a.linenum_value.start;
        attributes["original_label"].descr = nullptr;
    } else if (a.name=="label") {
        //When user specifies "label" attribute *after* a colon label,
        //we overwrite the pervious one, so we need to remove any
        //"original_label" so that we know it was a label attr and not a colon label.
        attributes.erase("original_label");
    }
    return true;
}

/** Adds the attribute names applicable to a given type object for a given layout to csh.*/
void GraphStyle::DotAttributeNames(GraphStyle::EGraphElementType type, std::string_view layout, Csh &csh)
{
    for (auto aa = DotAttributes; aa->name; aa++)
        if (GraphStyle::DoesThisAttributeApply(aa, type, layout))
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + aa->name, DotAttributeDescriptionText(aa),
                           EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
}

std::string graph::DotAttributeDescriptionText(const DotAttributeDescription *aa)
{
    std::string ret;
    if (aa==nullptr) return ret;
    if (aa->description)
        ret = aa->description;
    if (aa->type[0] || aa->def[0] || aa->minimum[0] || aa->notes[0]) {
        ret += " (";
        bool bb = false;
        if (aa->type[0]) {
            ret << "of type '" << aa->type << "'";
            bb = true;
        }
        if (aa->def[0]) {
            if (bb) ret << ", ";
            ret << "default is " << aa->def;
            bb = true;
        }
        if (aa->minimum[0]) {
            if (bb) ret << ", ";
            ret << "minimum is " << aa->minimum;
            bb = true;
        }
        if (aa->notes[0]) {
            if (bb) ret << ", ";
            ret << aa->notes;
        }
        ret += ")";
    }
    return ret;
}
/** Draw a node for csh hint.*/
void DrawNode(Canvas *canvas, const std::string &attrs)
{
    if (!canvas || canvas->cannot_draw()) return;
    std::string text = "graph { bgcolor = none a [label = \"\", ";
    text << attrs << ", fixedsize=true, width=0.2, height=0.15] }";
    GraphChart gc(nullptr, nullptr, nullptr);
    gc.Chart::ParseText(text, "");
    gc.CompleteParse();
    auto cr = canvas->GetContext();
    cairo_save(cr);
    cairo_translate(cr, 2, 2);
    cairo_scale(cr, (HINT_GRAPHIC_SIZE_X-3)/gc.AllCovers.GetBoundingBox().x.Spans(),
                    (HINT_GRAPHIC_SIZE_Y-3)/gc.AllCovers.GetBoundingBox().y.Spans());
    cairo_translate(cr, -gc.AllCovers.GetBoundingBox().x.from, -gc.AllCovers.GetBoundingBox().y.from);
    gc.DrawComplete(*canvas, false, 0);
    cairo_restore(cr);
}


/** Draw an edge for csh hint.*/
void DrawEdge(Canvas *canvas, const std::string &attrs, bool half)
{
    if (!canvas || canvas->cannot_draw()) return;
    std::string text = "digraph { bgcolor = none rankdir=LR a [label = \"\", style=invis] b [label = \"\", style=invis] a--b [";
    text << attrs << "] }";
    GraphChart gc(nullptr, nullptr, nullptr);
    gc.Chart::ParseText(text, "");
    gc.CompleteParse();
    _ASSERT(gc.GetCurrentGraph().edges.size());
    if (gc.GetCurrentGraph().edges.size()==0) return;
    Block b = gc.GetCurrentGraph().edges.back()->GetAreaToDraw().GetBoundingBox();
    if (half)
        b.x.from = b.x.MidPoint();
    const double ratio = std::min((HINT_GRAPHIC_SIZE_X-3)/b.x.Spans(), (HINT_GRAPHIC_SIZE_Y-3)/b.y.Spans());
    auto cr = canvas->GetContext();
    cairo_save(cr);
    cairo_translate(cr, HINT_GRAPHIC_SIZE_X/2, HINT_GRAPHIC_SIZE_Y/2);
    cairo_scale(cr, ratio, ratio);
    cairo_translate(cr, -b.x.MidPoint(), -b.y.MidPoint());
    gc.DrawComplete(*canvas, false, 0);
    cairo_restore(cr);
}

/** Callback for drawing a symbol before attribute names in the hints popup list box.
* @ingroup hintpopup_callbacks*/
bool CshHintGraphicCallbackForEdgeStyle(Canvas *canvas, CshHintGraphicParam p, CshHintStore &)
{
    if (!canvas) return false;
    const char *c = reinterpret_cast<const char*>(p);
    DrawEdge(canvas, std::string("fillcolor=black, dir=forward, style=")+c, false);
    return true;
}

/** Callback for drawing a symbol before attribute names in the hints popup list box.
* @ingroup hintpopup_callbacks*/
bool CshHintGraphicCallbackForArrowType(Canvas *canvas, CshHintGraphicParam p, CshHintStore &)
{
    if (!canvas) return false;
    const char *c = reinterpret_cast<const char*>(p);
    DrawEdge(canvas, std::string("fillcolor=black, dir=forward, arrowhead=")+c, true);
    return true;
}

/** Callback for drawing a symbol before attribute names in the hints popup list box.
* @ingroup hintpopup_callbacks*/
bool CshHintGraphicCallbackForDirType(Canvas *canvas, CshHintGraphicParam p, CshHintStore &)
{
    if (!canvas) return false;
    const char *c = reinterpret_cast<const char*>(p);
    DrawEdge(canvas, std::string("fillcolor=black, dir=")+c, false);
    return true;
}


/** Callback for drawing a symbol before attribute names in the hints popup list box.
* @ingroup hintpopup_callbacks*/
bool CshHintGraphicCallbackForNodeClusterStyle(Canvas *canvas, CshHintGraphicParam p, CshHintStore &)
{
    if (!canvas) return false;
    const char *c = reinterpret_cast<const char*>(p);
    std::string plus = "shape = box";
    if (strncmp(c, "striped", 10)==0)
        plus += ", fillcolor=\"white:gray:black\"";
    else if (strncmp(c, "wedged", 10)==0)
        plus = "fillcolor=\"white:gray:black\", shape=ellipse";
    else if (strncmp(c, "radial", 10)==0)
        plus = "fillcolor=\"white:black\", shape=ellipse, style=filled";
    DrawNode(canvas, plus + std::string(", style=")+c);
    return true;
}



/** Callback for drawing a symbol before attribute names in the hints popup list box.
* @ingroup hintpopup_callbacks*/
bool CshHintGraphicCallbackForNodeShapes(Canvas *canvas, CshHintGraphicParam p, CshHintStore &)
{
    if (!canvas) return false;
    const char *c = reinterpret_cast<const char*>(p);
    DrawNode(canvas, std::string("shape=")+c);
    return true;
}



/** Adds the attribute values for one attribute applicable to a given type object for a given layout to csh.*/
bool GraphStyle::DotAttributeValues(EGraphElementType type, std::string_view attr, std::string_view layout, Csh &csh)
{
    auto aa = FindThisAttribute(attr);
    if (!DoesThisAttributeApply(aa, type, layout)) return false;
    if (strncmp(aa->type, "double", 7)==0 || strncmp(aa->type, "int", 7)==0) {
        static std::string n; //we need this, since description is passed around as a char*. However, in one list popup we will have only one occurrence of this.
        n.clear(); //clear stuff from a previous call to DotAttributeValues
        if (aa->minimum[0])
            n << "<number [" << aa->minimum << "..]>";
        else
            n = "<number>";
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + n, strncmp(aa->type, "double", 7)==0 ? "A floating point number." : "An integer number.",
            EHintType::ATTR_VALUE, false));
        if (aa->def[0])
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + aa->def, "The default value.",
                EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->type, "escString", 10)==0 || strncmp(aa->type, "lblString", 20)==0) {
        std::string n;
        if (strncmp(aa->type, "lblString", 20)==0)
            n = "You can use a HTML label or a text with ";
        else
            n = "You can use ";
        n += "the following escape characters. ";
        switch (type) {
        default:
            _ASSERT(0);
            FALLTHROUGH;
        case ANY: n += "For node attributes, the substring \"\\N\" "
            "is replaced by the name of the node, and the substring \"\\G\" by the name of the graph. For graph or cluster attributes, "
            "the substring \"\\G\" is replaced by the name of the graph or cluster. For edge attributes, the substring \"\\E\" is "
            "replaced by the name of the edge, the substring \"\\G\" is replaced by the name of the graph or cluster, and "
            "the substrings \"\\T\" and \"\\H\" by the names of the tail and head nodes, respectively. The name of an edge is "
            "the string formed from the name of the tail node, the appropriate edge operator ('--' or '->') and the name of the "
            "head node. "; break;
        case NODE:
            n += "The substring \"\\N\" is replaced by the name of the node, and the substring \"\\G\" by the name of the graph.";
            break;
        case GRAPH:
        case CLUSTER:
            n += "The substring \"\\G\" is replaced by the name of the graph or cluster. ";
            break;
        case EDGE:
            n += "The substring \"\\E\" is "
                "replaced by the name of the edge, the substring \"\\G\" is replaced by the name of the graph or cluster, and "
                "the substrings \"\\T\" and \"\\H\" by the names of the tail and head nodes, respectively. The name of an edge is "
                "the string formed from the name of the tail node, the appropriate edge operator ('--' or '->') and the name of the "
                "head node. ";
            break;
        }
        n += "The substring \"\\L\" is replaced by the object's label attribute. ";
        if (strncmp(aa->name, "label", 10) || strncmp(aa->name, "taillabel", 10) ||strncmp(aa->name, "headlabel", 10))
            n += "In addition, the escape sequences \"\\n\", \"\\l\" and \"\\r\" "
            "divide the label into lines, centered, left-justified, and right-justified, respectively. ";
        n += "Obviously, one can use \"\\\\\" to get a single backslash. A backslash appearing before any character not listed above is ignored.";

        std::string a;
        if (strstr(aa->name, "URL") || strstr(aa->name, "ref"))
            a = "<URL>";
        else if (strstr(aa->name, "tooltip"))
            a = "<tooltip text>";
        else if (strstr(aa->name, "target"))
            a = "<target text>";
        else //"id" attribute
            a = "<ID text>";
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + a, std::move(n), EHintType::ATTR_VALUE, false));
    } if (strncmp(aa->type, "arrowType", 10)==0) {
        static const char arrowtypes[][15] = {
             "box", "crow", "circle", "diamond", "dot", "inv", "none", "normal", "tee", "vee",
             "onormal", "lnormal", "rnormal", "olnormal", "ornormal", ""
        };
        for (auto at = arrowtypes; at[0][0]; at++)
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + *at, nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForArrowType, CshHintGraphicParam(*at)));
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "[o][l|r]<arrowtype>",
            "You can create your own arrow types from the basic set. The 'o' modifier creates an hollow shape, while the 'l' and 'r' modifiers "
            "clip the arrow shape to one of its halves. You can repeat this up to four times. E.g., 'olveelboxboxobox'.",
            EHintType::ATTR_VALUE, false));
    } else if (strncmp(aa->type, "color", 10)==0 || strncmp(aa->type, "color/colorList", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<\"#red,green,blue\">",
            "You can specify the three components of an RGB color using two digit hexadecimal numbers, e.g. '#ff0000' is red.",
            EHintType::ATTR_VALUE, false));
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<\"#red,green,blue,alpha\">",
            "You can specify the three components of an RGB color plus an alpha value using two digit hexadecimal numbers, "
            "e.g. '#ff000080' is semi-transparent red.",
            EHintType::ATTR_VALUE, false));
        CshHint hint("", "Apply this color.", EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForColors, 0);
        for (auto i = csh.Contexts.back().Colors.begin(); i!=csh.Contexts.back().Colors.end(); i++) {
            hint.decorated = csh.HintPrefix(COLOR_ATTRVALUE) + i->first;
            hint.param = i->second.ConvertToUnsigned();
            csh.AddToHints(hint);
        }
        if (strncmp(aa->type, "color/colorList", 20)==0) {
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<colorList>",
                "You can also specify a list of colors and optional weights, separated by colons (':'). The weights must add up to at "
                "most one, and the remaining weight is distributed evenly between the colors without weight. The weights shall be "
                "separated from the color by a semicoln (';'). The weights determine "
                "how much of the line or area is colored by one color. E.g., 'red:green' will color half red, half green, but "
                "'red;0.33:green' results in one third red, two thirds green coloring.",
                EHintType::ATTR_VALUE, false));
        }
    } else if (strncmp(aa->type, "clusterMode", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "local", nullptr, EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "global", nullptr, EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "none", nullptr, EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->type, "bool", 20)==0) {
        csh.AddYesNoToHints();
    } else if (strncmp(aa->type, "dirType", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "forward", nullptr, EHintType::ATTR_VALUE, true,
            CshHintGraphicCallbackForDirType, CshHintGraphicParam("forward")));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "back", nullptr, EHintType::ATTR_VALUE, true,
            CshHintGraphicCallbackForDirType, CshHintGraphicParam("back")));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "none", nullptr, EHintType::ATTR_VALUE, true,
            CshHintGraphicCallbackForDirType, CshHintGraphicParam("none")));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "both", nullptr, EHintType::ATTR_VALUE, true,
            CshHintGraphicCallbackForDirType, CshHintGraphicParam("both")));
    } else if (strncmp(aa->name, "diredgeconstraints", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "no", nullptr, EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, 0));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "yes",
            "Constraints are generated for each edge in the largest(heuristic) directed acyclic subgraph such "
            "that the edge must point downwards.", EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, 1));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "hier",
            "Generates level constraints similar to those used with 'mode=hier'.", EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->type, "addDouble/addPoint", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<\"[+]number\">",
            "Margin used in both x and y directions. If it begins with a plus sign '+', an additive margin is specified.",
            EHintType::ATTR_VALUE, false));
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<\"[+]x,y\">",
            "Margin used in x and y directions, respectively. If it begins with a plus sign '+', an additive margin is specified.",
            EHintType::ATTR_VALUE, false));
    } else if (strncmp(aa->name, "fixedsize", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "no",
            "The 'height' and 'withd' attributes serve as minimum sizes - the node can be larger if needed.",
            EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, 0));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "yes",
            "The 'height' and 'withd' attributes determine the exact size of the node - no smaller, no larger. "
            "A warning is given if the label does not fit.",
            EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, 1));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "shape",
            "The 'height' and 'withd' attributes determine the exact size of the node - no smaller, no larger. "
            "The label can be much larger.",
            EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->type, "portPos", 20)==0) {
        static const char compass_points[][3] = {
            "n", "ne", "e", "se", "s", "sw", "w", "nw", "c", ""
        };
        for (auto at = compass_points; at[0][0]; at++)
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + *at,
                "Modifier indicating what compass direction on a node an edge should be aimed.",
                EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "_",
            "Modifier indicating what compass direction on a node an edge should be aimed. "
            "Specifies that an appropriate side of the port adjacent to the exterior of the node should be used, if such exists. Otherwise, the center is used.",
            EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<port name>[:direction]",
            "Modifier indicating where on a node an edge should be aimed. The node must either have record shape with one of its "
            "fields having the given portname, or have an HTML-like label, one of whose components has a PORT attribute set to portname. "
            "You can optionally attach a direction, specifying the angle, too.",
            EHintType::ATTR_VALUE, false));
    } else if (strncmp(aa->name, "labeljust", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "r", "Right justified", EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForTextIdent, MSC_IDENT_RIGHT));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "l", "Left justified", EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForTextIdent, MSC_IDENT_LEFT));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "c", "Center justified", EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForTextIdent, MSC_IDENT_CENTER));
    } else if (strncmp(aa->name, "labelloc", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "t", "Top placement", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "b", "Bottom placement", EHintType::ATTR_VALUE, true));
        if (type==NODE)
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "c", "Center placement", EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->name, "layout", 20)==0) {
        const auto layouts = GetLayoutMethods();
        for (auto &l : layouts)
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + l, nullptr, EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->name, "ltail", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<cluster_name>",
            "The name of the cluster to clip the tail of an edge to.",
            EHintType::ATTR_VALUE, false));
    } else if (strncmp(aa->type, "double/point", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<\"number\">",
            "Value used in both x and y directions.",
            EHintType::ATTR_VALUE, false));
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<\"x,y[!]\">",
            "Value used in x and y directions, respectively. The optional '!' indicates the node position should not change.",
            EHintType::ATTR_VALUE, false));
    } else if (strncmp(aa->name, "mode", 20)==0) {
        if (layout.length() && layout=="neato") {
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "major",
                "Use stress majorization for optimizing the layout.", EHintType::ATTR_VALUE, true));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "KK",
                "Use version of the gradient descent method for optimizing the layout. "
                "The only advantage compared to 'major' is that it is sometimes appreciably faster for small (number of nodes < 100) graphs. "
                "A significant disadvantage is that it may cycle.", EHintType::ATTR_VALUE, true));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "hier",
                "Adds a top-down directionality similar to the layout used in dot (experimental).", EHintType::ATTR_VALUE, true));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "ipsep",
                "Allows the graph to specify minimum vertical and horizontal distances between nodes. (See the sep attribute.) (experimental)", EHintType::ATTR_VALUE, true));
        } else if (layout.length() && layout=="sfdp") {
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "spring",
                "Corresponds to using a spring-electrical model.", EHintType::ATTR_VALUE, true));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "maxent",
                "Similar to 'spring', but also takes into account edge lengths specified by the 'len' attribute.", EHintType::ATTR_VALUE, true));
        }
    } else if (strncmp(aa->name, "model", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "shortpath",
            "Use stress majorization for optimizing the layout.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "circuit", nullptr, EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "subset", nullptr, EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "mds", nullptr, EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->name, "normalize", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "no", "Don't normalize", EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, 0));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "yes", "Normalize, the angle of the first edge is 0.", EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, 1));
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<degrees>", "Normalize, the angle of the first edge is set to this value.", EHintType::ATTR_VALUE, false, CshHintGraphicCallbackForYesNo, 1));
    } else if (strncmp(aa->name, "ordering", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "out", "Makes the outedges of a node, that is, edges with the node as its tail node, appear "
            "left-to-right in the same order in which they are defined in the input.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "yes", "Makes the inedges of a "
            "node appear left-to-right in the same order in which they are defined in the input.", EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->name, "outputorder", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "breadthfirst",
            "Simplest, but when the graph layout does not avoid edge-node overlap, this mode will sometimes have edges drawn over nodes and "
            "sometimes on top of nodes.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "nodesfirst",
            "All nodes are drawn first, followed by the edges.This guarantees an edge-node overlap will not be mistaken for an edge ending "
            "at a node.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "edgesfirst",
            "Draws edges before nodes. Usually for aesthetic reasons, it may be desirable that all edges appear beneath nodes, even if the "
            "resulting drawing is ambiguous.", EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->name, "pack", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "no", "The entire graph is laid out together. ", EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, 0));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "yes", "Each connected component of the graph "
            "is laid out separately, and then the graphs are packed together. Default margin of 8 points is used.",
            EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, 1));
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<positive integer>",
            "Each connected component of the graph "
            "is laid out separately, and then the graphs are packed together, value is used as margin in points around each part.",
            EHintType::ATTR_VALUE, false, CshHintGraphicCallbackForYesNo, 1));

    } else if (strncmp(aa->name, "packmode", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "node",
            "Causes packing at the node and edge level, with no overlapping of these objects. This produces a layout with the least area, "
            "but it also allows interleaving, where a node of one component may lie between two nodes in another component.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "clust",
            "Guarantees that top-level clusters are kept intact. What effect a value has also depends on the layout algorithm. For example, neato does "
            "not support clusters, so this value will have the same effect as the default 'node' value.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "graph",
            "Does a packing using the bounding box of the component. Thus, there will be a rectangular region around a component "
            "free of elements of any other component.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "array[_flag][number]",
            "indicates that the components should be packed at the graph level into an array of graphs. By default, the components are in "
            "row-major order, with the number of columns roughly the square root of the number of components. If the optional flags contains 'c', "
            "then column-major order is used. Finally, if the optional integer suffix is used, this specifies the number of columns for row-major "
            "or the number of rows for column-major. Thus, the mode 'array_c4' indicates array packing, with 4 rows, starting in the upper left "
            "and going down the first column, then down the second column, etc., until all components are used.\n"
            "If a graph is smaller than the array cell it occupies, it is centered by default.The optional flags may contain 't', 'b', 'l', or 'r', "
            "indicating that the graphs should be aligned along the top, bottom, left or right, respectively.\n"
            "If the optional flags contains 'u', this causes the insertion order of elements in the array to be determined by user-supplied values. "
            "Each component can specify its sort value by a non-negative integer using the sortv attribute. Components are inserted in order, "
            "starting with the one with the smallest sort value.If no sort value is specified, zero is used.", EHintType::ATTR_VALUE, false));
    } else if (strncmp(aa->name, "pos", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "x,y(!)", "x and y coordinates. '!' indicates the node should not move.", EHintType::ATTR_VALUE, false));
    } else if (strncmp(aa->name, "quadtree", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "normal", nullptr, EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "fast", "Gives about a 2-4 times overall speedup compared with 'normal', though layout quality can suffer a little.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "none", nullptr, EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->name, "rank", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "same", "All nodes are placed on the same rank.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "min", "All nodes are placed on the minimum rank.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "source", "All nodes are placed on the minimum rank, and the only nodes on the minimum rank "
            "belong to some subgraph whose rank attribute is 'source' or 'min'.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "max", "All nodes are placed on the maximum rank.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "sink", "all nodes are placed on the maximum rank, and the only nodes on the maximum rank "
            "belong to some subgraph whose rank attribute is 'sink' or 'max'.", EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->name, "ranksep", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "TB", "Top to bottom.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "BT", "Bottom to top.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "LR", "Left to right.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "RL", "Right to left.", EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->name, "ratio", 20)==0) {
        if (layout.length() && layout == "dot")
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number>", "Gives the desired rank separation, in inches. This is the minimum "
                "vertical distance between the bottom of the nodes "
                "in one rank and the tops of nodes in the next. If the value contains 'equally', the centers of all ranks are spaced equally apart. "
                "Note that both settings are possible, e.g., 'ranksep = \"1.2 equally\"'.", EHintType::ATTR_VALUE, false));
        else if (layout.length() && layout == "twopi") {
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number>", "Specifies the radial separation of concentric circles. ", EHintType::ATTR_VALUE, false));
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number series>", "The first double specifies the radius of the inner "
                "circle; the second double specifies the increase in radius from the first "
                "circle to the second; etc. If there are more circles than numbers, the last number is used as the increment for the remainder.",
                EHintType::ATTR_VALUE, false));
        }
    } else if (strncmp(aa->name, "root", 20)==0) {
        if (type==NODE || type==ANY) {
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "no", "The node shall not be used as a center node.", EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, 0));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "yes", "The node shall be used as a center node.", EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, 1));
        }
        if (type==GRAPH || type==ANY) {
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<node name>", "This node shall be used as a center node.", EHintType::ATTR_VALUE, false));
        }
    } else if (strncmp(aa->name, "samehead", 20)==0 || strncmp(aa->name, "sametail", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<string identifier>", nullptr, EHintType::ATTR_VALUE, false));
    } else if (strncmp(aa->name, "shape", 20)==0) {
        static const char shapes[][20] = {
            "polygon", "ellipse", "oval", "circle", "point", "egg", "triangle", "none", "plaintext", "diamond", "trapezium", "parallelogram",
            "house", "pentagon", "hexagon", "septagon", "octagon", "note", "tab", "folder", "box3d", "component", "rect", "rectangle", "square",
            "doublecircle", "doubleoctagon", "tripleoctagon", "invtriangle", "invtrapezium", "invhouse", "underline", "Mdiamond", "Msquare", "Mcircle",
            /* biological circuit shapes, as specified by SBOLv*/
            /** gene expression symbols **/
            "promoter", "cds", "terminator", "utr", "insulator", "ribosite", "rnastab", "proteasesite", "proteinstab",
            /** dna construction symbols **/
            "primersite", "restrictionsite", "fivepoverhang", "threepoverhang", "noverhang", "assembly", "signature", "rpromoter",
            "larrow", "rarrow", "lpromoter",
            /*  *** shapes other than polygons  *** */
            "record", "Mrecord", "epsf", "star", ""};
        for (auto at = shapes; at[0][0]; at++)
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + *at, nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForNodeShapes, (CshHintGraphicParam)*at));
    } else if (strncmp(aa->type, "smoothType", 20)==0) {
        static const char sm[][20] = {"none", "avg_dist", "graph_dist", "power_dist", "rng", "spring", "triangle", ""};
        for (auto at = sm; at[0][0]; at++)
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + *at, nullptr, EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->name, "splines", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "no", "Edges are drawn as line segments.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "yes", "Edges are drawn as splines routed around nodes.", EHintType::ATTR_VALUE, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "none", "No edges are drawn at all.", EHintType::ATTR_VALUE, true));
        if (layout.length() && layout=="fdp")
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "compound", "The edges are drawn to avoid clusters as well as nodes.", EHintType::ATTR_VALUE, true));
    } else if (strncmp(aa->name, "start", 20)==0) {
        if (layout.length() && (layout=="fdp" || layout=="neato")) {
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "regular", "The nodes are placed regularly about a circle. "
                "You can append a number as seed for the random number generator. If seed is a positive number, this is used as the seed. "
                "If it is anything else, the current time, and possibly the process id, is used to pick a seed, "
                "thereby making the choice more random. In this case, the seed value is stored in the graph.", EHintType::ATTR_VALUE, true));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "self", "Abbreviated version of neato is run to obtain the initial layout. "
                "You can append a number as seed for the random number generator. If seed is a positive number, this is used as the seed. "
                "If it is anything else, the current time, and possibly the process id, is used to pick a seed, "
                "thereby making the choice more random. In this case, the seed value is stored in the graph.", EHintType::ATTR_VALUE, true));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "random", "The nodes are placed randomly in a unit square. "
                "You can append a number as seed for the random number generator. If seed is a positive number, this is used as the seed. "
                "If it is anything else, the current time, and possibly the process id, is used to pick a seed, "
                "thereby making the choice more random. In this case, the seed value is stored in the graph.", EHintType::ATTR_VALUE, true));
        }
    } else if (strncmp(aa->name, "style", 20)==0) {
        if (type==EDGE) {
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "dashed", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForEdgeStyle, CshHintGraphicParam("dashed")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "dotted", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForEdgeStyle, CshHintGraphicParam("dotted")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "solid", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForEdgeStyle, CshHintGraphicParam("solid")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "invis", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForEdgeStyle, CshHintGraphicParam("invis")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "bold", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForEdgeStyle, CshHintGraphicParam("bold")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "tapered", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForEdgeStyle, CshHintGraphicParam("tapered")));
            /* Some ChartGen extensions for */
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "double", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForEdgeStyle, CshHintGraphicParam("double")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "triple", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForEdgeStyle, CshHintGraphicParam("triple")));
        }
        if (type==NODE) {
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "wedged",
                "Fill like a piechart. Use the fillcolor attribute with several colors, like: "
                "'fillcolor=\"white:gray:black\"', with the colors drawn counter-clockwise starting at angle 0. "
                "Only takes effect with elliptically-shaped nodes.", EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("wedged")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "diagonals", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("diagonals")));
        }
        if (type==NODE || type == CLUSTER) {
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "dashed", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("dashed")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "dotted", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("dotted")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "solid", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("solid")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "invis", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("invis")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "bold", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("bold")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "filled", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("filled")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "striped",
                "Fill with vertical colored bars. Use the fillcolor attribute with several colors, like: "
                "'fillcolor=\"white:gray:black\"'. Only takes effect with rectangular shapes.",
                EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("striped")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "rounded", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("rounded")));
            /* Some ChartGen extensions for */
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "double", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("double")));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "triple", nullptr, EHintType::ATTR_VALUE, true,
                CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("triple")));
        }
        if (type==NODE || type == CLUSTER || type==GRAPH)
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + "radial",
                "Indicates a radial-style gradient fill if applicable",
                EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForNodeClusterStyle, CshHintGraphicParam("radial")));
    } else if (strncmp(aa->type, "string", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<string>", nullptr, EHintType::ATTR_VALUE, false));
    } else if (strncmp(aa->type, "labelformat", 20)==0) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"\"format string\"",
            "Specify a format string using text escapes, like '\\b'. Do not use plain text (non-formatting characters or escapes).",
            EHintType::ATTR_VALUE, false));
    } else
        return false;
    return true;
}


//Info taken from http://www.graphviz.org/content/attrs
const DotAttributeDescription graph::DotAttributes[] =  {
{"Damping", "G", "double", "0.99", "0.0", "neato only",
    "Factor damping force motions. On each iteration, a nodes movement is limited to this factor of its potential motion. "
    "By being less than 1.0, the system tends to ``cool'', thereby preventing cycling."},
{"K", "GC", "double", "0.3", "0", "sfdp, fdp only",
    "Spring constant used in virtual physical model. It roughly corresponds to an ideal edge length (in inches), in that "
    "increasing K tends to increase the distance between nodes. Note that the edge attribute 'len' can be used to override this value for adjacent nodes."},
{"URL", "ENGC", "escString", "<none>", "", "svg, postscript, map only",
    "Hyperlinks incorporated into device-dependent output attached to Nodes, Edges, Graphs or Cluster subgraphs. Used with ISMAP output."},
//{"_background", "G", "string", "<none>", "", "", ""}, Not supported by our rendering engine
{"area", "NC", "double", "1.0", "0", "patchwork only",
    "Indicates the preferred area for a node or empty cluster when laid out by patchwork."},
{"arrowhead", "E", "arrowType", "normal", "", "",
    "Style of arrowhead on the head node of an edge. This will only appear for directed graphs (if the dir attribute is 'forward' or 'both'."},
{"arrowsize", "E", "double", "1.0", "0.0", "",
    "Multiplicative scale factor for arrowheads."},
{"arrowtail", "E", "arrowType", "normal", "", "",
    "Style of arrowhead on the tail node of an edge. This will only appear for directed graphs (if the dir attribute is 'back' or 'both'."},
//{"bb", "G", "rect", "", "", "write only", ""},
{"bgcolor", "GC", "color/colorList", "<none>", "", "",
    "When attached to the root graph, this color is used as the background for entire canvas. When a cluster attribute, it is used as the initial background for the cluster. "
    "If a cluster has a filled 'style', the cluster's 'fillcolor' will overlay the background color.\n"
    "If the value is of 'colorList' type, a gradient fill is used. By default, this is a linear fill; setting 'style = radial' will cause a radial fill. "
    "At present, only two colors are used. If the second color(after a colon) is missing, the default color is used for it. "
    "See also the gradientangle attribute for setting the gradient angle.\n"
    "The canvas is filled with white by default (plain design), you can set 'bgcolor=transparent' to have transparent background."},
//{"center", "G", "bool", "false", "", "", ""}, Makes no sense
//{"charset", "G", "string", "UTF-8", "", "", ""}, We handle our own
{"clusterrank", "G", "clusterMode", "local", "", "dot only",
    "Mode used for handling clusters. If clusterrank is 'local', a subgraph whose name begins with \"cluster\" is given special treatment. "
    "The subgraph is laid out separately, and then integrated as a unit into its parent graph, with a bounding rectangle drawn about it. "
    "If the cluster has a label parameter, this label is displayed within the rectangle. Note also that there can be clusters within clusters. "
    "At present, the modes 'global' and 'none' appear to be identical, both turning off the special cluster processing."},
{"color", "ENC", "color/colorList", "black", "", "",
    "Basic drawing color for graphics, not text. For the latter, use the 'fontcolor' attribute.\n"
    "For edges, the value can either be a single color or a colorList. In the latter case, if colorList has no fractions, the edge is drawn "
    "using parallel splines or lines, one for each color in the list, in the order given. The head arrow, if any, is drawn using the first "
    "color in the list, and the tail arrow, if any, the second color. This supports the common case of drawing opposing edges, but using "
    "parallel splines instead of separately routed multiedges. If any fraction is used, the colors are drawn in series, with each color being "
    "given roughly its specified fraction of the edge."},
{"colorscheme", "ENCG", "string", "", "", "",
    "This attribute specifies a color scheme namespace. If defined, it specifies the context for interpreting color names. In particular, if "
    "a color value has form \"xxx\" or \"//xxx\", then the color 'xxx' will be evaluated according to the current color scheme. If no color "
    "scheme is set, the standard X11 naming is used. For example, if 'colorscheme=bugn9', then 'color=7' is interpreted as \"/bugn9/7\"."},
{"comment", "ENG", "string", "", "", "",
    "Comments are inserted into output."},
{"compound", "G", "bool", "false", "", "dot only",
    "If true, allow edges between clusters."},
{"concentrate", "G", "bool", "false", "", "",
    "If true, use edge concentrators. This merges multiedges into a single edge and causes partially parallel edges to share part of their "
    "paths. The latter feature is not yet available outside of dot."},
{"constraint", "E", "bool", "true", "", "dot only",
    "If false, the edge is not used in ranking the nodes."},
{"decorate", "E", "bool", "false", "", "",
    "If true, attach edge label to edge by a 2-segment polyline, underlining the label, then going to the closest point of spline."},
{"defaultdist", "G", "double", "1+(avg. len)*sqrt(|V|)", "epsilon", "neato only",
    "This specifies the distance between nodes in separate connected components. If set too small, connected components may overlap. "
    "Only applicable if 'pack=false'."},
{"dim", "G", "int", "2", "2", "sfdp, fdp, neato only",
    "Set the number of dimensions used for the layout. The maximum value allowed is 10."},
{"dimen", "G", "int", "2", "2", "sfdp, fdp, neato only",
    "Set the number of dimensions used for rendering. The maximum value allowed is 10. If both dimen and dim are set, the latter specifies "
    "the dimension used for layout, and the former for rendering. If only dimen is set, this is used for both layout and rendering dimensions.\n"
    "Note that, at present, all aspects of rendering are 2D.This includes the shape and size of nodes, overlap removal, and edge routing. "
    "Thus, for dimen > 2, the only valid information is the pos attribute of the nodes. "
    "All other coordinates will be 2D and, at best, will reflect a projection of a higher-dimensional point onto the plane."},
{"dir", "E", "dirType", "forward(directed)/none(undirected)", "", "",
    "Set edge type for drawing arrowheads. This indicates which ends of the edge should be decorated with an arrowhead. The actual style of the "
    "arrowhead can be specified using the 'arrowhead' and arrowtail attributes."},
{"diredgeconstraints", "G", "string/bool", "false", "", "neato only",
    "Only valid when 'mode=ipsep'. If true, constraints are generated for each edge in the largest (heuristic) directed acyclic subgraph such "
    "that the edge must point downwards. If 'hier', generates level constraints similar to those used with 'mode=hier'. The main difference is "
    "that, in the latter case, only these constraints are involved, so a faster solver can be used."},
{"distortion", "N", "double", "0.0", "-100.0", "",
    "Distortion factor for shape=polygon. Positive values cause top part to be larger than bottom; negative values do the opposite."},
//{"dpi", "G", "double", "96.0/0.0", "", "svg, bitmap output only", ""},
{"edgeURL", "E", "escString", "", "", "svg, map only",
    "If 'edgeURL' is defined, this is the link used for the non-label parts of an edge. This value overrides any URL defined for the edge. "
    "Also, this value is used near the head or tail node unless overridden by a 'headURL' or 'tailURL' value, respectively."},
{"edgehref", "E", "escString", "", "", "svg, map only",
    "Synonym for 'edgeURL'"},
{"edgetarget", "E", "escString", "<none>", "", "svg, map only",
    "If the edge has a 'URL' or 'edgeURL' attribute, this attribute determines which window of the browser is used for the URL attached to "
    "the non-label part of the edge. Setting it to \"_graphviz\" will open a new window if it doesn't already exist, or reuse it if it does. "
    "If undefined, the value of the 'target' is used."},
{"edgetooltip", "E", "escString", "", "", "svg, cmap only",
    "Tooltip annotation attached to the non-label part of an edge. This is used only if the edge has a URL or edgeURL attribute."},
{"epsilon", "G", "double", ".0001 * # nodes(mode == KK)/.0001(mode == major)", "", "neato only",
    "Terminating condition. If the length squared of all energy gradients are < epsilon, the algorithm stops."},
{"esep", "G", "addDouble/addPoint", "+3", "", "not dot",
    "Margin used around polygons for purposes of spline edge routing. The interpretation is the same as given for sep. This should "
    "normally be strictly less than sep."},
{"fillcolor", "NEC", "color/colorList", "lightgrey(nodes)/black(clusters)", "", "",
    "Color used to fill the background of a node or cluster assuming 'style=filled' or a filled arrowhead. If 'fillcolor' is not defined, "
    "'color' is used. (For clusters, if 'color' is not defined, 'bgcolor' is used.) If this is not defined, the default is used, except "
    "for 'shape=point' or when the output format is MIF, which use black by default.\n"
    "If the value is a colorList, a gradient fill is used. By default, this is a linear fill; setting 'style = radial' will cause a radial fill. "
    "At present, only two colors are used.If the second color(after a colon) is missing, the default color is used for it. See also the "
    "'gradientangle' attribute for setting the gradient angle. Note that a cluster inherits the root graph's attributes if defined. "
    "Thus, if the root graph has defined a 'fillcolor', this will override a 'color' or 'bgcolor' attribute set for the cluster."},
{"fixedsize", "N", "bool/string", "false", "", "",
    "If false, the size of a node is determined by smallest width and height needed to contain its label and image, if any, with a margin "
    "specified by the 'margin' attribute. The width and height must also be at least as large as the sizes specified by the 'width' and 'height' "
    "attributes, which specify the minimum values for these parameters.\n"
    "If true, the node size is specified by the values of the 'width' and 'height' attributes only and is not expanded to contain the text label. "
    "There will be a warning if the label (with margin) cannot fit within these limits. If the fixedsize attribute is set to \"shape\", the 'width'"
    "and 'height' attributes also determine the size of the node shape, but the label can be much larger. Both the label and shape sizes are "
    "used when avoiding node overlap, but all edges to the node ignore the label and only contact the node shape. "
    "No warning is given if the label is too large."},
{"fontcolor", "ENGC", "color", "black", "", "",
    "Color used for text."},
{"fontname", "ENGC", "string", "Times-Roman", "", "",
    "Font used for text. This very much depends on the output format and, for non-bitmap output such as PostScript or SVG, the availability "
    "of the font when the graph is displayed or printed. As such, it is best to rely on font faces that are generally available, such as Times-Roman."},
//{"fontnames", "G", "string", "", "", "svg only", ""},
//{"fontpath", "G", "string", "system-dependent", "", "", ""},
{"fontsize", "ENGC", "double", "14.0", "1.0", "",
    "Font size, in points, used for text."},
{"forcelabels", "G", "bool", "true", "", "",
    "If true, all 'xlabel' attributes are placed, even if there is some overlap with nodes or other labels."},
{"gradientangle", "NCG", "int", "", "", "",
    "If a gradient fill is being used, this determines the angle of the fill. For linear fills, the colors transform along a line specified "
    "by the angle and the center of the object. For radial fills, a value of zero causes the colors to transform radially from the center; "
    "for non-zero values, the colors transform from a point near the object's periphery as specified by the value."},
{"group", "N", "string", "", "", "dot only",
    "If the end points of an edge belong to the same group, i.e., have the same group attribute, parameters are set to avoid crossings and "
    "keep the edges straight."},
{"headURL", "E", "escString", "", "", "svg, map only",
    "If 'headURL' is defined, it is output as part of the head label of the edge. Also, this value is used near the head node, overriding any 'URL' value. "},
//{"head_lp", "E", "point", "", "", "write only", ""},
{"headclip", "E", "bool", "true", "", "",
    "If true, the head of an edge is clipped to the boundary of the head node; otherwise, the end of the edge goes to the center of the node, or "
    "the center of a port, if applicable."},
{"headhref", "E", "escString", "", "", "svg, map only",
    "Synonym for 'headURL'."},
{"headlabel", "E", "lblString", "", "", "",
    "Text label to be placed near head of edge."},
{"headport", "E", "portPos", "center", "", "",
    "Indicates where on the head node to attach the head of the edge. In the default case, the edge is aimed towards the center of the node, "
    "and then clipped at the node boundary."},
{"headtarget", "E", "escString", "<none>", "", "svg, map only",
    "If the edge has a headURL, this attribute determines which window of the browser is used for the URL. Setting it to \"_graphviz\" "
    "will open a new window if it doesn't already exist, or reuse it if it does. If undefined, the value of the 'target' is used."},
{"headtooltip", "E", "escString", "", "", "svg, cmap only",
    "Tooltip annotation attached to the head of an edge. This is used only if the edge has a 'headURL' attribute."},
{"height", "N", "double", "0.5", "0.02", "",
    "Height of node, in inches. This is taken as the initial, minimum height of the node. If 'fixedsize' is true, this will be the final height "
    "of the node. Otherwise, if the node label requires more height to fit, the node's height will be increased to contain the label. "
    "Note also that, if the output format is 'dot', the value given to height will be the final value. "
    "If the node shape is regular, the width and height are made identical. In this case, if either the width or the height is set explicitly, "
    "that value is used. In this case, if both the 'width' and the 'height' are set explicitly, the maximum of the two values is used. "
    "If neither is set explicitly, the minimum of the two default values is used."},
{"href", "GCNE", "escString", "", "", "svg, postscript, map only",
    "Synonym for 'URL'."},
{"id", "GCNE", "escString", "", "", "svg, postscript, map only",
    "Allows the graph author to provide an id for graph objects which is to be included in the output. Normal \"\\N\", \"\\E\", \"\\G\" "
    "substitutions are applied. If provided, it is the responsibility of the provider to keep its values sufficiently unique for its "
    "intended downstream use. Note, in particular, that \"\\E\" does not provide a unique id for multi-edges. If no id attribute is "
    "provided, then a unique internal id is used. However, this value is unpredictable by the graph writer. An externally provided id "
    "is not used internally. If the graph provides an id attribute, this will be used as a prefix for internally generated attributes. "
    "By making these distinct, the user can include multiple image maps in the same document."},
//{"image", "N", "string", "", "", "", ""},
//{"imagepath", "G", "string", "", "", "", ""},
//{"imagescale", "N", "bool/string", "false", "", "", ""},
{"inputscale", "G", "double", "<none>", "", "fdp, neato only",
    "For layout algorithms that support initial input positions (specified by the pos attribute), this attribute can be used to appropriately "
    "scale the values. By default, fdp and neato interpret the x and y values of pos as being in inches. "
    "If not set, no scaling is done and the units on input are treated as inches. A value of 0 is equivalent to inputscale = 72."},
{"label", "ENGC", "lblString", "'\\N' (nodes)/<empty> (otherwise)", "", "",
    "Text label attached to objects. If a node's shape is record, then the label can have a special format which describes the record layout. "
    "Note that a node's default label is \"\\N\", so the node's name or ID becomes its label. Technically, a node's name can be an HTML "
    "string but this will not mean that the node's label will be interpreted as an HTML-like label. This is because the node's actual "
    "label is an ordinary string, which will be replaced by the raw bytes stored in the node's name.To get an HTML-like label, the label "
    "attribute value itself must be an HTML string."},
{"labelURL", "E", "escString", "", "", "svg, map only",
    "If 'labelURL' is defined, this is the link used for the label of an edge. This value overrides any 'URL' defined for the edge."},
{"label_scheme", "G", "int", "0", "0", "sfdp only",
    "The value indicates whether to treat a node whose name has the form \"|edgelabel|*\" as a special node representing an edge "
    "label. The default (0) produces no effect. If the attribute is set to 1, sfdp uses a penalty-based method to make that kind "
    "of node close to the center of its neighbor. With a value of 2, sfdp uses a penalty-based method to make that kind of node "
    "close to the old center of its neighbor. Finally, a value of 3 invokes a two-step process of overlap removal and straightening."},
{"labelangle", "E", "double", "-25.0", "-180.0", "",
    "This, along with 'labeldistance', determine where the headlabel (taillabel) are placed with respect to the head (tail) in polar "
    "coordinates. The origin in the coordinate system is the point where the edge touches the node. The ray of 0 degrees goes from "
    "the origin back along the edge, parallel to the edge at the origin. The angle, in degrees, specifies the rotation from the 0 "
    "degree ray, with positive angles moving counterclockwise and negative angles moving clockwise."},
{"labeldistance", "E", "double", "1.0", "0.0", "",
    "Multiplicative scaling factor adjusting the distance that the headlabel(taillabel) is from the head(tail) node. The default distance "
    "is 10 points."},
{"labelfloat", "E", "bool", "false", "", "",
    "If true, allows edge labels to be less constrained in position. In particular, it may appear on top of other edges."},
{"labelfontcolor", "E", "color", "black", "", "",
    "Color used for headlabel and taillabel. If not set, defaults to edge's fontcolor."},
{"labelfontname", "E", "string", "Times-Roman", "", "",
    "Font used for headlabel and taillabel. If not set, defaults to edge's fontname."},
{"labelfontsize", "E", "double", "14.0", "1.0", "",
    "Font size, in points, used for headlabel and taillabel. If not set, defaults to edge's fontsize."},
{"labelhref", "E", "escString", "", "", "svg, map only",
    "Synonym for 'labelURL'."},
{"labeljust", "GC", "string", "'c'", "", "",
    "Justification for cluster labels. Note that a subgraph inherits attributes from its parent. Thus, if the root graph sets labeljust to "
    "\"l\", the subgraph inherits this value."},
{"labelloc", "NGC", "string", "'t'(clusters)/'b'(root graphs)/'c'(nodes)", "", "",
    "Vertical placement of labels.\n"
    "By default, root graph labels go on the bottom and cluster labels go on the top. Note that a subgraph inherits attributes "
    "from its parent. Thus, if the root graph sets labelloc to \"b\", the subgraph inherits this value.\n"
    "For nodes, this attribute is used only when the height of the node is larger than the height of its label. "
    "In the default case, the label is vertically centered."},
{"labeltarget", "E", "escString", "<none>", "", "svg, map only",
    "If the edge has a 'URL' or 'labelURL' attribute, this attribute determines which window of the browser is used for the URL "
    "attached to the label. Setting it to \"_graphviz\" will open a new window if it doesn't already exist, or reuse it if it does. "
    "If undefined, the value of the 'target' is used."},
{"labeltooltip", "E", "escString", "", "", "svg, cmap only",
    "Tooltip annotation attached to label of an edge. This is used only if the edge has a 'URL' or 'labelURL' attribute."},
{"landscape", "G", "bool", "false", "", "",
    "If true, the graph is rendered in landscape mode. Synonymous with 'rotate=90' or 'orientation=landscape'."},
//{"layer", "ENC", "layerRange", "", "", "", ""},
//{"layerlistsep", "G", "string", "","", "", ""},
//{"layers", "G", "layerList", "", "", "", ""},
//{"layerselect", "G", "layerRange", "", "", "", ""},
//{"layersep", "G", "string", "':\\t'", "", "", ""},
{"layout", "G", "string", "", "", "",
    "Specifies the name of the layout algorithm to use, such as \"dot\" or \"neato\". "
    "Contrary to DOT custom, command-line attributes or selection from the ribbon overrules this setting."},
{"len", "E", "double", "1.0(neato)/0.3(fdp)", "", "fdp, neato only",
    "Preferred edge length, in inches."},
{"levels", "G", "int", "MAXINT", "0.0", "sfdp only",
    "Number of levels allowed in the multilevel scheme."},
{"levelsgap", "G", "double", "0.0", "", "neato only",
    "Specifies strictness of level constraints in neato when 'mode=ipsep'or 'hier'. Larger positive values mean stricter "
    "constraints, which demand more separation between levels. On the other hand, negative values will relax the constraints "
    "by allowing some overlap between the levels."},
{"lhead", "E", "double", "", "", "dot only",
    "Height of graph or cluster label, in inches."},
//{"lheight", "GC", "double", "", "", "write only", ""},
//{"lp", "EGC", "point", "", "", "write only", ""},
{"ltail", "E", "string", "", "", "dot only",
    "Logical tail of an edge. When compound is true, if ltail is defined and is the name of a cluster containing the real tail, "
    "the edge is clipped to the boundary of the cluster. "},
//{"lwidth", "GC", "double", "", "", "write only", ""},
{"margin", "NCG", "double/point", "<device-dependent>", "", "",
    "For graphs, this sets x and y margins of canvas, in inches. "
    "Note that the margin is not part of the drawing but just empty space left around the drawing. It basically corresponds to a translation "
    "of drawing, as would be necessary to center a drawing on a page. Nothing is actually drawn in the margin. To actually extend the "
    "background of a drawing, see the 'pad' attribute.\n"
    "For clusters, this specifies the space between the nodes in the cluster and the cluster bounding box. By default, this is 8 points.\n"
    "For nodes, this attribute specifies space left around the node's label. By default, the value is 0.11,0.055."},
{"maxiter", "G", "int", "100 * # nodes(mode == KK)/200(mode == major)/600(fdp)", "", "fdp, neato only",
    "Sets the number of iterations used."},
{"mclimit", "G", "double", "1.0", "", "dot only",
    "Multiplicative scale factor used to alter the MinQuit (default = 8) and MaxIter (default = 24) parameters used during crossing "
    "minimization. These correspond to the number of tries without improvement before quitting and the maximum number of iterations in each pass."},
{"mindist", "G", "double", "1.0", "0.0", "circo only",
    "Specifies the minimum separation between all nodes."},
{"minlen", "E", "int", "1", "0", "dot only",
    "Minimum edge length (rank difference between head and tail)."},
{"mode", "G", "string", "major", "", "neato, sfdp only",
    "Technique for optimizing the layout."},
{"model", "G", "string", "shortpath", "", "neato only",
    "This value specifies how the distance matrix is computed for the input graph. The distance matrix specifies the ideal distance between "
    "every pair of nodes. neato attempts to find a layout which best achieves these distances. By default, it uses the length of the shortest "
    "path, where the length of each edge is given by its len attribute. If model is 'circuit', neato uses the circuit resistance model to "
    "compute the distances. This tends to emphasize clusters. If model is 'subset', neato uses the subset model. This sets the edge length "
    "to be the number of nodes that are neighbors of exactly one of the end points, and then calculates the shortest paths. This helps to "
    "separate nodes with high degree.\n"
    "For more control of distances, one can use 'mds'. In this case, the len of an edge is used as the ideal distance between its vertices. "
    "A shortest path calculation is only used for pairs of nodes not connected by an edge. Thus, by supplying a complete graph, the input "
    "can specify all of the relevant distances."},
//{"mosek", "G", "bool", "false", "", "neato only", ""},
{"newrank", "G", "bool", "false", "", "dot only",
    "Setting this attribute enables the new ranking algorithm in dot (available from v2.30). "
    "In general it is a good idea to turn it on, if you fiddle with ranks."},
{"nodesep", "G", "double", "0.25", "0.02", "",
    "In dot, this specifies the minimum space between two adjacent nodes in the same rank, in inches.\n"
    "For other layouts, this affects the spacing between loops on a single node, or multiedges between a pair of nodes."},
{"nojustify", "GCNE", "bool", "false", "", "",
    "By default, the justification of multi-line labels is done within the largest context that makes sense. Thus, in the label of a "
    "polygonal node, a left-justified line will align with the left side of the node (shifted by the prescribed margin). In record nodes, "
    "left-justified line will line up with the left side of the enclosing column of fields. If nojustify is 'true', multi-line labels will "
    "be justified in the context of itself. For example, if the attribute is set, the first label line is long, and the second is shorter "
    "and left-justified, the second will align with the left-most character in the first line, regardless of how large the node might be."},
{"normalize", "G", "double/bool", "false", "", "not dot",
    "If set, normalize coordinates of final layout so that the first point is at the origin, and then rotate the layout so that the angle "
    "of the first edge is specified by the value of normalize in degrees. If normalize is not a number, it is evaluated as a bool, with "
    "true corresponding to 0 degrees. NOTE: Since the attribute is evaluated first as a number, 0 and 1 cannot be used for false and true."},
{"notranslate", "G", "bool", "false", "", "neato only",
    "By default, the final layout is translated so that the upper-left corner of the bounding box is at the origin. This can be annoying "
    "if some nodes are pinned. To avoid this translation, set notranslate to true."},
{"nslimit", "G", "double", "", "", "dot only",
    "Used to set number of iterations in network simplex applications. nslimit is used in computing node x coordinates, nslimit1 for "
    "ranking nodes. If defined, # iterations = nslimit(1) * # nodes; otherwise, # iterations = MAXINT."},
{"nslimit1", "G", "double", "", "", "dot only",
    "Used to set number of iterations in network simplex applications. nslimit is used in computing node x coordinates, nslimit1 for "
    "ranking nodes. If defined, # iterations = nslimit(1) * # nodes; otherwise, # iterations = MAXINT."},
{"ordering", "GN", "string", "", "", "dot only",
   "If the value of the attribute is 'out', then the outedges of a node, that is, edges with the node as its tail node, must appear "
   "left-to-right in the same order in which they are defined in the input. If the value of the attribute is 'in', then the inedges of a "
    "node must appear left-to-right in the same order in which they are defined in the input. If defined as a graph or subgraph attribute, "
    "the value is applied to all nodes in the graph or subgraph. Note that the graph attribute takes precedence over the node attribute."},
{"orientation", "N", "double", "0.0", "360.0", "",
    "Angle, in degrees, used to rotate polygon node shapes. For any number of polygon sides, 0 degrees rotation results in a flat base."},
//{"orientation", "G", "string", "", "", "", ""},
{"outputorder", "G", "outputMode", "breadthfirst", "", "",
    "Specify the order in which nodes and edges are drawn."},
{"overlap", "G", "string/bool", "true", "", "not dot",
    "Determines if and how node overlaps should be removed. Nodes are first enlarged using the 'sep' attribute. If 'true', overlaps are "
    "retained. If the value is 'scale', overlaps are removed by uniformly scaling in x and y. If the value converts to 'false', and it is "
    "available, Prism, a proximity graph-based algorithm, is used to remove node overlaps. This can also be invoked explicitly with "
    "'overlap = prism'. This technique starts with a small scaling up, controlled by the overlap_scaling attribute, which can remove "
    "a significant portion of the overlap. The prism option also accepts an optional non-negative integer suffix. This can be used "
    "to control the number of attempts made at overlap removal. By default, 'overlap=prism' is equivalent to 'overlap=prism1000'. "
    "Setting overlap'prism0' causes only the scaling phase to be run.\n"
    "If Prism is not available 'overlap=false' uses a Voronoi-based technique. This can always be invoked explicitly with 'overlap=voronoi'\n"
    "If the value is 'scalexy', x and y are separately scaled to remove overlaps.\n"
    "If the value is 'compress', the layout will be scaled down as much as possible without introducing any overlaps, obviously assuming "
    "there are none to begin with."},
{"overlap_scaling", "G", "double", "-4", "-1.0e10", "prism only",
    "When 'overlap=prism', the layout is scaled by this factor, thereby removing a fair amount of node overlap, and making node overlap removal "
    "faster and better able to retain the graph's shape. If overlap_scaling is negative, the layout is scaled by -1*overlap_scaling times "
    "the average label size.If overlap_scaling is positive, the layout is scaled by overlap_scaling.If overlap_scaling is zero, no scaling is done."},
{"overlap_shrink", "G", "bool", "true", "", "prism only",
    "If true, the overlap removal algorithm will perform a compression pass to reduce the size of the layout."},
{"pack", "G", "bool/int", "false", "", "",
    "This is true if the value of pack is 'true' (case-insensitive) or a non-negative integer. If true, each connected component of the graph "
    "is laid out separately, and then the graphs are packed together. If pack has an integral value, this is used as the size, in points, of "
    "a margin around each part; otherwise, a default margin of 8 is used. If pack is interpreted as false, the entire graph is laid out together. "
    "The granularity and method of packing is influenced by the packmode attribute. "
    "For layouts which always do packing, such a twopi, the pack attribute is just used to set the margin."},
{"packmode", "G", "packMode", "node", "", "",
    "This indicates how connected components should be packed (cf. packMode). Note that defining packmode will automatically turn on packing as "
    "though one had set pack=true."},
{"pad", "G", "double/point", "0.0555 (4 points)", "", "",
    "The pad attribute specifies how much, in inches, to extend the drawing area around the minimal area needed to draw the graph. If the pad is "
    "a single double, both the x and y pad values are set equal to the given value. This area is part of the drawing and will be filled with the "
    "background color, if appropriate. Normally, a small pad is used for aesthetic reasons, especially when a background color is used, to avoid "
        "having nodes and edges abutting the boundary of the drawn region."},
//{"page", "G", "double/point", "", "", "", ""},
//{"pagedir", "G", "pagedir", "BL", "", "", ""},
{"pencolor", "C", "color", "black", "", "",
    "Color used to draw the bounding box around a cluster. If 'pencolor' is not defined, 'color' is used. If this is not defined, 'bgcolor' "
    "is used. If this is not defined, the default is used. Note that a cluster inherits the root graph's attributes if defined. Thus, "
    "if the root graph has defined a 'pencolor', this will override a 'color' or 'bgcolor' attribute set for the cluster."},
{"penwidth", "CNE", "double", "1.0", "0.0", "",
    "Specifies the width of the pen, in points, used to draw lines and curves, including the boundaries of edges and clusters. The value "
    "is inherited by subclusters. It has no effect on text."},
{"peripheries", "NC", "int", "shape default(nodes)/1(clusters)", "0", "",
    "Set number of peripheries used in polygonal shapes and cluster boundaries. Note that user-defined shapes are treated as a form of box "
    "shape, so the default peripheries value is 1 and the user-defined shape will be drawn in a bounding rectangle. Setting 'peripheries=0' "
    "will turn this off. Also, 1 is the maximum peripheries value for clusters."},
{"pin", "N", "bool", "false", "", "fdp, neato only",
    "If true and the node has a pos attribute on input, neato or fdp prevents the node from moving from the input position. "
    "This property can also be specified in the pos attribute itself (cf. the point type)."},
{"pos", "EN", "point/splineType", "", "", "",
    "Position of node, or spline control points. For nodes, the position indicates the center of the node. "
    "In neato and fdp, pos can be used to set the initial position of a node. By default, the coordinates are assumed to be in inches."},
{"quadtree", "G", "quadType/bool", "normal", "", "sfdp only",
    "Quadtree scheme to use. A 'true' bool value corresponds to 'normal'; a 'false' bool value corresponds to 'none'. As a slight "
    "exception to the normal interpretation of bool, a value of '2' corresponds to 'fast'"},
{"quantum", "G", "double", "0.0", "0.0", "",
    "If quantum > 0.0, node label dimensions will be rounded to integral multiples of the quantum."},
{"rank", "S", "rankType", "", "", "dot only",
    "Rank constraints on the nodes in a subgraph. If 'rank=same', all nodes are placed on the same rank. If 'rank=min', all nodes are "
    "placed on the minimum rank. If 'rank=source', all nodes are placed on the minimum rank, and the only nodes on the minimum rank "
    "belong to some subgraph whose rank attribute is 'source' or 'min. Analogous criteria hold for 'rank=max'and 'rank=sink'. "
    "(Note: the minimum rank is topmost or leftmost, and the maximum rank is bottommost or rightmost.)"},
{"rankdir", "G", "rankdir", "TB", "", "dot only",
    "Sets direction of graph layout. For example, if 'rankdir=LR', and barring cycles, an edge T -> H; will go from left to right. "
    "By default, graphs are laid out from top to bottom. "
    "This attribute also has a side-effect in determining how record nodes are interpreted. See record shapes."},
{"ranksep", "G", "double/doubleList", "0.5(dot)/1.0(twopi)", "0.02", "twopi, dot only",
    "In dot, this gives the desired rank separation, in inches. This is the minimum vertical distance between the bottom of the nodes "
    "in one rank and the tops of nodes in the next. If the value contains 'equally', the centers of all ranks are spaced equally apart. "
    "Note that both settings are possible, e.g., 'ranksep = \"1.2 equally\"'.\n"
    "In twopi, this attribute specifies the radial separation of concentric circles. For twopi, ranksep can also be a list of doubles. "
    "The first double specifies the radius of the inner circle; the second double specifies the increase in radius from the first "
    "circle to the second; etc.If there are more circles than numbers, the last number is used as the increment for the remainder."},
{"ratio", "G", "double", "", "", "",
    "Sets the aspect ratio (drawing height/drawing width) for the drawing. Note that this is adjusted before the size attribute constraints "
    "are enforced. In addition, the calculations usually ignore the node sizes, so the final drawing size may only approximate what is desired. "
    "If 'ratio' is numeric, it is taken as the desired aspect ratio. Then, if the actual aspect ratio is less than the desired ratio, the "
    "drawing height is scaled up to achieve the desired ratio; if the actual ratio is greater than that desired ratio, the drawing width is scaled up."},
//{"rects", "N", "rect", "", "", "write only", ""},
{"regular", "N", "bool", "false", "", "",
    "If true, force polygon to be regular, i.e., the vertices of the polygon will lie on a circle whose center is the center of the node."},
{"remincross", "G", "bool", "true", "", "dot only",
    "If true and there are multiple clusters, run crossing minimization a second time."},
{"repulsiveforce", "G", "double", "1.0", "0.0", "sfdp only",
    "The power of the repulsive force used in an extended Fruchterman-Reingold force directed model. Values larger than 1 tend to reduce the "
    "warping effect at the expense of less clustering."},
//{"resolution", "G", "double", "96.0/0.0", "", "svg, bitmap output only", ""},
{"root", "GN", "string/bool", "<none>(graphs)/false(nodes)", "", "circo, twopi only",
    "This specifies nodes to be used as the center of the layout and the root of the generated spanning tree. As a graph attribute, this gives "
    "the name of the node. As a node attribute, it specifies that the node should be used as a central node. In twopi, this will actually be the "
    "central node. In circo, the block containing the node will be central in the drawing of its connected component. If not defined, twopi "
    "will pick a most central node, and circo will pick a random node.\n"
    "If the root attribute is defined as the empty string, twopi will reset it to name of the node picked as the root node.\n"
    "For twopi, it is possible to have multiple roots, presumably one for each component.If more than one node in a component is marked as the root, "
    "twopi will pick one."},
{"rotate", "G", "int", "0", "", "",
    "If 90, set drawing orientation to landscape."},
{"rotation", "G", "double", "0", "", "sfdp only",
    "Causes the final layout to be rotated counter-clockwise by the specified number of degrees."},
{"samehead", "E", "string", "", "", "dot only",
    "Edges with the same head and the same samehead value are aimed at the same point on the head. This has no effect on loops. Each node can "
    "have at most 5 unique samehead values."},
{"sametail", "E", "string", "", "", "dot only",
    "Edges with the same tail and the same sametail value are aimed at the same point on the tail. This has no effect on loops. Each node can "
    "have at most 5 unique sametail values."},
//{"samplepoints", "N", "int", "8(output)/20(overlap and image maps)", "", "", ""}, only for DOT and XDOT output
{"scale", "G", "double/point", "", "", "not dot",
    "If set, after the initial layout, the layout is scaled by the given factors. If only a single number is given, this is used for both factors."},
{"searchsize", "G", "int", "30", "", "dot only",
    "During network simplex, maximum number of edges with negative cut values to search when looking for one with minimum cut value."},
{"sep", "G", "addDouble/addPoint", "+4", "", "not dot",
    "Specifies margin to leave around nodes when removing node overlap. This guarantees a minimal non-zero distance between nodes. "
    "If the attribute begins with a plus sign '+', an additive margin is specified. That is, \"+w,h\" causes the node's bounding box "
    "to be increased by w points on the left and right sides, and by h points on the top and bottom. Without a plus sign, the node is "
    "scaled by 1 + w in the x coordinate and 1 + h in the y coordinate.\n"
    "If only a single number is given, this is used for both dimensions.\n"
    "If unset but 'esep' is defined, the 'sep' values will be set to the 'esep' values divided by 0.8. If 'esep' is also unset, the default value is used."},
{"shape", "N", "shape", "ellipse", "", "",
    "Set the shape of a node."},
//{"shapefile", "N", "string", "", "", "", ""}, deprecated
//{"showboxes", "ENG", "int", "0", "0", "dot only", ""}, debug only
{"sides", "N", "int", "4", "0", "",
    "Number of sides if shape=polygon."},
{"size", "G", "double/point", "", "", "",
    "Maximum width and height of drawing, in inches. If only a single number is given, this is used for both the width and the height. "
    "If defined and the drawing is larger than the given size, the drawing is uniformly scaled down so that it fits within the given size. "
    "If size ends in an exclamation point(!), then it is taken to be the desired size. In this case, if both dimensions of the drawing are "
    "less than size, the drawing is scaled up uniformly until at least one dimension equals its dimension in size.\n"
    "Note that there is some interaction between the size and ratio attributes."},
{"skew", "N", "double", "0.0", "-100.0", "",
    "Skew factor for shape=polygon. Positive values skew top of polygon to right; negative to left."},
{"smoothing", "G", "smoothType", "'none'", "", "sfdp only",
    "Specifies a post-processing step used to smooth out an uneven distribution of nodes."},
{"sortv", "GCN", "int", "0", "0", "",
    "If packmode indicates an array packing, this attribute specifies an insertion order among the components, with smaller values inserted first."},
{"splines", "G", "bool/string", "", "", "",
    "Controls how, and if, edges are represented. If 'true', edges are drawn as splines routed around nodes; if 'false', edges are drawn as line "
    "segments. If set to 'none' or '\"\"', no edges are drawn at all.\n"
    "By default, the attribute is unset. How this is interpreted depends on the layout. For dot, the default is to draw edges as splines. For all "
    "other layouts, the default is to draw edges as line segments. Note that for these latter layouts, if 'splines=true', this requires "
    "non-overlapping nodes (cf. overlap). If fdp is used for layout and 'splines=compound', then the edges are drawn to avoid clusters as well as nodes."},
{"start", "G", "startType", "", "", "fdp, neato only",
    "Parameter used to determine the initial layout of nodes. If unset, the nodes are randomly placed in a unit square with the same seed is "
    "always used for the random number generator, so the initial placement is repeatable."},
{"style", "ENCG", "style", "", "", "",
    "Set style information for components of the graph. For cluster subgraphs, if 'style=filled', the cluster box's background is filled. "
    "If the default style attribute has been set for a component, an individual component can use 'style=\"\"' to revert to the normal default."},
//{"stylesheet", "G", "string", "", "", "svg only", ""}, SGV specific rendering
{"tailURL", "E", "escString", "", "", "svg, map only",
    "If 'tailURL' is defined, it is output as part of the tail label of the edge. Also, this value is used near the tail node, overriding any 'URL' value."},
//{"tail_lp", "E", "point", "", "", "write only", ""},
{"tailclip", "E", "bool", "true", "", "",
   "If true, the tail of an edge is clipped to the boundary of the tail node; otherwise, the end of the edge goes to the center of the node, or "
   "the center of a port, if applicable."},
{"tailhref", "E", "escString", "", "", "svg, map only",
    "Synonym for 'tailURL'."},
{"taillabel", "E", "lblString", "", "", "",
    "Text label to be placed near tail of edge."},
{"tailport", "E", "portPos", "center", "", "",
    "Indicates where on the tail node to attach the tail of the edge."},
{"tailtarget", "E", "escString", "<none>", "", "svg, map only",
    "If the edge has a 'tailURL', this attribute determines which window of the browser is used for the URL. Setting it to \"_graphviz\" will open "
    "a new window if it doesn't already exist, or reuse it if it does. If undefined, the value of the 'target' is used."},
{"tailtooltip", "E", "escString", "", "", "svg, cmap only",
    "Tooltip annotation attached to the tail of an edge. This is used only if the edge has a 'tailURL' attribute."},
{"target", "ENGC", "escString/string", "<none>", "", "svg, map only",
    "If the object has a URL, this attribute determines which window of the browser is used for the URL. See W3C documentation."},
{"tooltip", "NEC", "escString", "", "", "svg, cmap only",
    "Tooltip annotation attached to the node or edge. If unset, Graphviz will use the object's label if defined. Note that if "
    "the label is a record specification or an HTML-like label, the resulting tooltip may be unhelpful. In this case, if tooltips will be "
    "generated, the user should set a tooltip attribute explicitly."},
//{"truecolor", "G", "bool", "", "", "bitmap output only", ""},
//{"vertices", "N", "pointList", "", "", "write only", ""},
//{"viewport", "G", "viewPort", "", "", "",
//    "Clipping window on final drawing. Note that this attribute supersedes any size attribute. The width and height of the viewport specify "
//    "precisely the final size of the output."},
{"voro_margin", "G", "double", "0.05", "0.0", "not dot",
    "Factor to scale up drawing to allow margin for expansion in Voronoi technique. dim' = (1+2*margin)*dim."},
{"weight", "E", "double", "1", "0(dot,twopi)/1(neato,fdp)", "",
    "Weight of edge. In dot, the heavier the weight, the shorter, straighter and more vertical the edge is. N.B. Weights in dot must be "
    "integers. For twopi, a weight of 0 indicates the edge should not be used in constructing a spanning tree from the root. "
    "For other layouts, a larger weight encourages the layout to make the edge length closer to that specified by the len attribute."},
{"width", "N", "double", "0.75", "0.01", "",
    "Width of node, in inches. This is taken as the initial, minimum width of the node. If fixedsize is true, this will be the final "
    "width of the node. Otherwise, if the node label requires more width to fit, the node's width will be increased to contain the label. "
    "Note also that, if the output format is dot, the value given to width will be the final value. "
    "If the node shape is regular, the width and height are made identical. In this case, if either the width or the height is set "
    "explicitly, that value is used.In this case, if both the width or the height are set explicitly, the maximum of the two values is used. "
    "If neither is set explicitly, the minimum of the two default values is used."},
//{"xdotversion", "G", "string", "", "", "xdot only", ""}, only on xdot
{"xlabel", "EN", "lblString", "", "", "",
    "External label for a node or edge. For nodes, the label will be placed outside of the node but near it. For edges, the label will be "
    "placed near the center of the edge. This can be useful in dot to avoid the occasional problem when the use of edge labels distorts the layout. "
    "For other layouts, the xlabel attribute can be viewed as a synonym for the label attribute.\n"
    "These labels are added after all nodes and edges have been placed.The labels will be placed so that they do not overlap any node or label. "
    "This means it may not be possible to place all of them. To force placing all of them, use the forcelabels attribute."},
//{"xlp", "NE", "point", "", "", "write only", ""},
{"z", "N", "double", "0.0", "-MAXFLOAT/-1000", "", ""},

///Here comes the chartgen extensions

{"label_format", "ENGC", "labelformat", "", "", "",
    "Specify initial formatting for the labels. Use the string formatting escapes applicable "
    "to signalling charts."},
{"link_format", "ENGC", "labelformat", "", "", "",
    "Specify formatting for URLs in labels. Use the string formatting escapes applicable "
    "to signalling charts."},
{"shadow_offset", "NC", "double", "0 (no shadow)", "0..100", "",
    "Specifies the offset shadow in pixels, how 'deep' the shadow appears below the object. "
    "Setting to zero disables the shadow."},
{"shadow_blur", "NC", "double", "0 (no shadow)", "0..100", "",
    "Specifies the thickness of the blur at the edge of the shadow in pixels. "
    "Setting to zero results in crisp edged shadows."},
{"shadow_color", "NC", "color", "black", "", "",
    "Specifies the thickness of the blur at the edge of the shadow in pixels."},
{"collapse_strict", "G", "bool", "true", "", "",
    "If true, than multiple edges between collapsed subgraphs are replaced by a single edge. "
    "If the graph is strict, this attribute is ignored (and assumed true)."},


{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}};

/** Checks if 'value' represents a boolean. If so, places it into 'result' and returns true.*/
bool GraphStyle::CheckBoolValue(const char * value, bool & result)
{
    if (CaseInsensitiveEqual(value, "true") || CaseInsensitiveEqual(value, "yes") ||
        CaseInsensitiveEqual(value, "1")) {
        result = true;
        return true;
    }
    if (CaseInsensitiveEqual(value, "false") || CaseInsensitiveEqual(value, "no") ||
        CaseInsensitiveEqual(value, "0")) {
        result = false;
        return true;
    }
    return false;
}

/** Emit an error message demanding a boolean value. */
void GraphStyle::BooleanValueError(const Attribute & a, MscError & error)
{
    error.Error(a, true, "This attribute expects 'true', 'yes', '1', 'false', 'no', or '0' as value. Ignoring it.");
}

bool graph::IsDotAttributeAnExtension(const DotAttributeDescription *a)
{
    static const DotAttributeDescription *first = nullptr;
    if (first==nullptr)
        //search for the first extension attribute only once.
        for (auto p = DotAttributes; p->name; p++)
            if (strncmp(p->name, "label_format", 15)==0) {
                first = p;
                break;
            }
    return a && a>=first; //checking a!=null is to avoid comparing nullptr to something valid
}



void GraphContext::Empty()
{
    ContextBase<GraphStyle>::Empty();
    pedantic.reset();
    styles.clear();
    styles["graph"] = GraphStyle(EStyleType::DEFAULT, GraphStyle::GRAPH);
    styles["cluster"] = GraphStyle(EStyleType::DEF_ADD, GraphStyle::CLUSTER);
    styles["cluster_collapsed"] = GraphStyle(EStyleType::DEF_ADD, GraphStyle::CLUSTER);
    styles["node"] = GraphStyle(EStyleType::DEFAULT, GraphStyle::NODE);
    styles["edge"] = GraphStyle(EStyleType::DEFAULT, GraphStyle::EDGE);
    styles["edge_collapsed"] = GraphStyle(EStyleType::DEFAULT, GraphStyle::EDGE);

    styles["->"] = GraphStyle(EStyleType::DEF_ADD, GraphStyle::EDGE);
    styles.emplace("=>", styles["->"]);
    styles.emplace(">>", styles["->"]);
    styles.emplace(">", styles["->"]);
    styles.emplace("->>", styles["->"]);
    styles.emplace("==>", styles["->"]);
    styles.emplace("<->", styles["->"]);
    styles.emplace("<=>", styles["->"]);
    styles.emplace("<<>>", styles["->"]);
    styles.emplace("<>", styles["->"]);
    styles.emplace("<<->>", styles["->"]);
    styles.emplace("<==>", styles["->"]);
    styles.emplace("<-", styles["->"]);
    styles.emplace("<=", styles["->"]);
    styles.emplace("<<", styles["->"]);
    styles.emplace("<", styles["->"]);
    styles.emplace("<<-", styles["->"]);
    styles.emplace("<==", styles["->"]);
    styles.emplace("--", styles["->"]);
    styles.emplace("==", styles["->"]);
    styles.emplace("++", styles["->"]);
    styles.emplace("..", styles["->"]);
    styles.emplace("---", styles["->"]);
    styles.emplace("===", styles["->"]);
}

void GraphContext::Plain()
{
    ContextBase<GraphStyle>::Plain();
    pedantic = false;
    styles["graph"].write().attributes["pad"].value = "0.1"; //default pad of 0.1 inch (7.2 pixels)
    styles["cluster"].write().attributes["pad"].value = "0";
    styles["cluster_collapsed"].write().attributes["shape"].value = "box";
    styles["cluster_collapsed"].write().attributes["style"].value = "double";
    styles["edge_collapsed"].write().attributes["style"].value = "double";
    styles["node"].write().attributes["label"].value = "\\N"; //To make the default label be the name of the node

    styles["->"].write().attributes["dir"].value = "forward";
    styles["->"].write().attributes["style"].value = "solid";
    styles["<-"].write().attributes["dir"].value = "back";
    styles["<-"].write().attributes["style"].value = "solid";
    styles["<->"].write().attributes["dir"].value = "both";
    styles["<->"].write().attributes["style"].value = "solid";
    styles["--"].write().attributes["dir"].value = "none";
    styles["--"].write().attributes["style"].value = "solid";

    styles["=>"].write().attributes["dir"].value = "forward";
    styles["=>"].write().attributes["style"].value = "double";
    styles["<="].write().attributes["dir"].value = "back";
    styles["<="].write().attributes["style"].value = "double";
    styles["<=>"].write().attributes["dir"].value = "both";
    styles["<=>"].write().attributes["style"].value = "double";
    styles["=="].write().attributes["dir"].value = "none";
    styles["=="].write().attributes["style"].value = "double";

    styles[">>"].write().attributes["dir"].value = "forward";
    styles[">>"].write().attributes["style"].value = "dashed";
    styles["<<"].write().attributes["dir"].value = "back";
    styles["<<"].write().attributes["style"].value = "dashed";
    styles["<<>>"].write().attributes["dir"].value = "both";
    styles["<<>>"].write().attributes["style"].value = "dashed";
    styles["++"].write().attributes["dir"].value = "none";
    styles["++"].write().attributes["style"].value = "dashed";

    styles[">"].write().attributes["dir"].value = "forward";
    styles[">"].write().attributes["style"].value = "dotted";
    styles["<"].write().attributes["dir"].value = "back";
    styles["<"].write().attributes["style"].value = "dotted";
    styles["<>"].write().attributes["dir"].value = "both";
    styles["<>"].write().attributes["style"].value = "dotted";
    styles[".."].write().attributes["dir"].value = "none";
    styles[".."].write().attributes["style"].value = "dotted";

    styles["->>"].write().attributes["dir"].value = "forward";
    styles["->>"].write().attributes["style"].value = "solid";
    styles["->>"].write().attributes["arrowhead"].value = "normalnormal";
    styles["->>"].write().attributes["arrowtail"].value = "normalnormal";
    styles["<<-"].write().attributes["dir"].value = "back";
    styles["<<-"].write().attributes["style"].value = "solid";
    styles["<<-"].write().attributes["arrowhead"].value = "normalnormal";
    styles["<<-"].write().attributes["arrowtail"].value = "normalnormal";
    styles["<<->>"].write().attributes["dir"].value = "both";
    styles["<<->>"].write().attributes["style"].value = "solid";
    styles["<<->>"].write().attributes["arrowhead"].value = "normalnormal";
    styles["<<->>"].write().attributes["arrowtail"].value = "normalnormal";
    styles["---"].write().attributes["dir"].value = "none";
    styles["---"].write().attributes["style"].value = "solid";
    styles["---"].write().attributes["arrowhead"].value = "normalnormal";
    styles["---"].write().attributes["arrowtail"].value = "normalnormal";

    styles["==>"].write().attributes["dir"].value = "forward";
    styles["==>"].write().attributes["style"].value = "double";
    styles["<=="].write().attributes["dir"].value = "back";
    styles["<=="].write().attributes["style"].value = "double";
    styles["<==>"].write().attributes["dir"].value = "both";
    styles["<==>"].write().attributes["style"].value = "double";
    styles["==="].write().attributes["dir"].value = "none";
    styles["==="].write().attributes["style"].value = "double";
}

void GraphContext::SetDefaultFont(const std::string& face, Chart *chart, const FileLineColRange& l) {
    GraphStyle style(EStyleType::STYLE);
    style.AddAttribute(Attribute("fontname", face, l, l, true), chart);
    styles["graph"].write() += style;
    styles["node"].write() += style;
    styles["edge"].write() += style;
}

void GraphContext::ApplyContextContent(const GraphContext &o)
{
    ContextBase::ApplyContextContent(o);
    if (o.IsFull()) {
        pedantic = o.pedantic;
    } else {
        if (o.pedantic) pedantic = o.pedantic;
    }
}

void GraphContext::ApplyContextContent(GraphContext &&o)
{
    if (o.IsFull()) {
        pedantic = o.pedantic;
    } else {
        if (o.pedantic) pedantic = o.pedantic;
    }
    ContextBase::ApplyContextContent(std::move(o));
}

