graphviz_rust/
printer.rs

1//! Serialize a [Graph] into a string according to the [`graphviz` DOT language].
2//!
3//! # Example:
4//! ```rust
5//!     use dot_generator::*;
6//!     use dot_structures::*;
7//!     use graphviz_rust::printer::{PrinterContext,DotPrinter};
8//!         let mut ctx = PrinterContext::default();
9//!         let s = subgraph!("id"; node!("abc"), edge!(node_id!("a") => node_id!("b")));
10//!         assert_eq!(s.print(&mut ctx), "subgraph id {\n  abc\n  a -- b\n}".to_string());
11//! ```
12//!
13//! [`graphviz` DOT language]: https://graphviz.org/doc/info/lang.html
14use std::collections::HashMap;
15
16use dot_structures::{
17    Attribute, Edge, EdgeTy, Graph, GraphAttributes, Id, Node, NodeId, Port, Stmt, Subgraph, Vertex,
18};
19
20/// A function which can be passed to the [PrinterContext] to provide custom printing for attribute values.
21///
22/// # Example:
23/// ```rust
24/// use dot_generator::*;
25/// use dot_structures::*;
26/// use graphviz_rust::printer::{AttributeValuePrinter, DotPrinter, PrinterContext};
27/// fn attr_formatter_test() {
28///     let mut ctx = PrinterContext::default();
29///     let formatter: Box<AttributeValuePrinter> =
30///         Box::new(|value, _line_sep, _indent, _indent_step| format!(r#""**{}**""#, value.trim_matches('"')));
31///     let g = graph!(di id!("attr_formatting");
32///          node!("abc";attr!("custom",esc "Custom Text")),
33///          edge!(node_id!("a") => node_id!("b"))
34///     );
35///     assert_eq!(
36///         "digraph attr_formatting {\n  abc[custom=\"**Custom Text**\"]\n  a -> b\n}",
37///         g.print(
38///             ctx.with_node_mult_attr_s_l()
39///                 .with_attr_value_printer(id!("custom"), formatter)
40///         )
41///     );
42/// }
43/// ```
44pub type AttributeValuePrinter = dyn Fn(&str, &str, &str, usize) -> String;
45
46/// Context allows to customize the output of the file.
47///
48/// # Example:
49/// ```rust
50///     use self::graphviz_rust::printer::PrinterContext;
51///
52///     let mut ctx = PrinterContext::default();
53///     ctx.always_inline();
54///     ctx.with_indent_step(4);
55/// ```
56pub struct PrinterContext {
57    /// internal flag which is decoupled from the graph
58    is_digraph: bool,
59    /// a flag adds a semicolon at the end of the line
60    semi: bool,
61    /// a flag, print multiple node attributes on seperate lines
62    mult_node_attr_on_s_l: bool,
63    /// include a comma after node attributes on seperate lines
64    mult_node_attr_on_s_l_comma: bool,
65    /// an initial indent. 0 by default
66    indent: usize,
67    /// a step of the indent. 2 by default
68    indent_step: usize,
69    /// a line separator. can be empty
70    l_s: String,
71    /// a len of the text to keep on one line
72    inline_size: usize,
73    l_s_i: String,
74    l_s_m: String,
75    /// a map of attribute id to AttributeValuePrinters
76    attr_value_printers: HashMap<Id, Box<AttributeValuePrinter>>,
77}
78
79impl PrinterContext {
80    /// Print everything on one line.
81    pub fn always_inline(&mut self) -> &mut PrinterContext {
82        self.l_s_m = self.l_s_i.clone();
83        self.l_s = self.l_s_i.clone();
84        self
85    }
86    /// Add a semicolon at the end of every line.
87    pub fn with_semi(&mut self) -> &mut PrinterContext {
88        self.semi = true;
89        self
90    }
91    /// Print multiple attributes on seperate lines
92    pub fn with_node_mult_attr_s_l(&mut self) -> &mut PrinterContext {
93        self.mult_node_attr_on_s_l = true;
94        self
95    }
96    /// Don't include a comma when printing attributes on seperate lines
97    pub fn with_no_node_mult_attr_s_l_comma(&mut self) -> &mut PrinterContext {
98        self.mult_node_attr_on_s_l_comma = false;
99        self
100    }
101    /// Set a step of the indent.
102    pub fn with_indent_step(&mut self, step: usize) -> &mut PrinterContext {
103        self.indent_step = step;
104        self
105    }
106    /// Set a specific line separator.
107    pub fn with_line_sep(&mut self, sep: String) -> &mut PrinterContext {
108        self.l_s = sep.clone();
109        self.l_s_m = sep;
110        self
111    }
112    /// Set the max line length.
113    ///
114    /// The default value is 90.
115    pub fn with_inline_size(&mut self, inline_s: usize) -> &mut PrinterContext {
116        self.inline_size = inline_s;
117        self
118    }
119    /// Add an attribute printer for a specific attribute id.
120    pub fn with_attr_value_printer(
121        &mut self,
122        attr_id: Id,
123        fmt: Box<AttributeValuePrinter>,
124    ) -> &mut PrinterContext {
125        self.attr_value_printers.insert(attr_id, fmt);
126        self
127    }
128
129    pub fn new(semi: bool, indent_step: usize, line_s: String, inline_size: usize) -> Self {
130        PrinterContext {
131            is_digraph: false,
132            semi,
133            mult_node_attr_on_s_l: false,
134            mult_node_attr_on_s_l_comma: true,
135            indent: 0,
136            indent_step,
137            inline_size,
138            l_s: line_s.clone(),
139            l_s_i: line_s,
140            l_s_m: "".to_string(),
141            attr_value_printers: HashMap::new(),
142        }
143    }
144}
145
146impl PrinterContext {
147    fn indent(&self) -> String {
148        if self.is_inline_on() {
149            "".to_string()
150        } else {
151            " ".repeat(self.indent)
152        }
153    }
154    fn indent_grow(&mut self) {
155        if !self.is_inline_on() {
156            self.indent += self.indent_step
157        }
158    }
159    fn indent_shrink(&mut self) {
160        if !self.is_inline_on() {
161            self.indent -= self.indent_step
162        }
163    }
164
165    fn is_inline_on(&self) -> bool {
166        self.l_s == self.l_s_i
167    }
168    fn inline_mode(&mut self) {
169        self.l_s = self.l_s_i.clone()
170    }
171    fn multiline_mode(&mut self) {
172        self.l_s = self.l_s_m.clone()
173    }
174}
175
176impl Default for PrinterContext {
177    fn default() -> Self {
178        PrinterContext {
179            is_digraph: false,
180            mult_node_attr_on_s_l: false,
181            mult_node_attr_on_s_l_comma: true,
182            semi: false,
183            indent: 0,
184            indent_step: 2,
185            l_s: "\n".to_string(),
186            inline_size: 90,
187            l_s_i: "".to_string(),
188            l_s_m: "\n".to_string(),
189            attr_value_printers: HashMap::new(),
190        }
191    }
192}
193
194/// The trait for serailizing a [Graph] into the `graphviz` DOT language:
195///
196/// # Example:
197///  ```rust
198///         use dot_generator::*;
199///         use dot_structures::*;
200///         use self::graphviz_rust::printer::PrinterContext;
201///         use self::graphviz_rust::printer::DotPrinter;
202///
203///         let mut ctx =PrinterContext::default();
204///         ctx.always_inline();
205///         ctx.with_indent_step(4);
206///         let graph = graph!(strict di id!("t"));
207///
208///         let string = graph.print(&mut ctx);
209/// ```
210pub trait DotPrinter {
211    fn print(&self, ctx: &mut PrinterContext) -> String;
212}
213
214impl DotPrinter for Id {
215    fn print(&self, _ctx: &mut PrinterContext) -> String {
216        match self {
217            Id::Html(v) | Id::Escaped(v) | Id::Plain(v) => v.clone(),
218            Id::Anonymous(_) => "".to_string(),
219        }
220    }
221}
222
223impl DotPrinter for Port {
224    fn print(&self, ctx: &mut PrinterContext) -> String {
225        match self {
226            Port(Some(id), Some(d)) => format!(":{}:{}", id.print(ctx), d),
227            Port(None, Some(d)) => format!(":{}", d),
228            Port(Some(id), None) => format!(":{}", id.print(ctx)),
229            _ => unreachable!(""),
230        }
231    }
232}
233
234impl DotPrinter for NodeId {
235    fn print(&self, ctx: &mut PrinterContext) -> String {
236        match self {
237            NodeId(id, None) => id.print(ctx),
238            NodeId(id, Some(port)) => [id.print(ctx), port.print(ctx)].join(""),
239        }
240    }
241}
242
243impl DotPrinter for Attribute {
244    fn print(&self, ctx: &mut PrinterContext) -> String {
245        match self {
246            Attribute(l, r) => {
247                let l_val = l.print(ctx);
248                let r_val = r.print(ctx);
249                if let Some(formatter) = ctx.attr_value_printers.get(l) {
250                    format!(
251                        "{}={}",
252                        l_val,
253                        formatter(&r_val, &ctx.l_s, &ctx.indent(), ctx.indent_step)
254                    )
255                } else {
256                    format!("{}={}", l_val, r_val)
257                }
258            }
259        }
260    }
261}
262
263impl DotPrinter for Vec<Attribute> {
264    fn print(&self, ctx: &mut PrinterContext) -> String {
265        let attrs: Vec<String> = self.iter().map(|e| e.print(ctx)).collect();
266        if attrs.is_empty() {
267            "".to_string()
268        } else if attrs.len() > 1 && ctx.mult_node_attr_on_s_l {
269            let indent = ctx.indent();
270            ctx.indent_grow();
271            let r = format!(
272                "[{}{}{}{}{}]",
273                ctx.l_s,
274                ctx.indent(),
275                attrs.join(&format!(
276                    "{}{}{}",
277                    {
278                        if ctx.is_inline_on() || ctx.mult_node_attr_on_s_l_comma {
279                            ","
280                        } else {
281                            ""
282                        }
283                    },
284                    ctx.l_s,
285                    ctx.indent()
286                )),
287                ctx.l_s,
288                indent,
289            );
290            ctx.indent_shrink();
291            r
292        } else {
293            format!("[{}]", attrs.join(","))
294        }
295    }
296}
297
298impl DotPrinter for GraphAttributes {
299    fn print(&self, ctx: &mut PrinterContext) -> String {
300        match self {
301            GraphAttributes::Graph(attrs) => format!("graph{}", attrs.print(ctx)),
302            GraphAttributes::Node(attrs) => format!("node{}", attrs.print(ctx)),
303            GraphAttributes::Edge(attrs) => format!("edge{}", attrs.print(ctx)),
304        }
305    }
306}
307
308impl DotPrinter for Node {
309    fn print(&self, ctx: &mut PrinterContext) -> String {
310        format!("{}{}", self.id.print(ctx), self.attributes.print(ctx))
311    }
312}
313
314impl DotPrinter for Vertex {
315    fn print(&self, ctx: &mut PrinterContext) -> String {
316        match self {
317            Vertex::N(el) => el.print(ctx),
318            Vertex::S(el) => el.print(ctx),
319        }
320    }
321}
322
323impl DotPrinter for Subgraph {
324    fn print(&self, ctx: &mut PrinterContext) -> String {
325        let indent = ctx.indent();
326        ctx.indent_grow();
327        let header = format!("subgraph {} {{{}", self.id.print(ctx), ctx.l_s);
328        let r = format!("{}{}{}{}}}", header, self.stmts.print(ctx), ctx.l_s, indent);
329        ctx.indent_shrink();
330        r
331    }
332}
333
334impl DotPrinter for Graph {
335    fn print(&self, ctx: &mut PrinterContext) -> String {
336        ctx.indent_grow();
337
338        match self {
339            Graph::Graph { id, strict, stmts } if *strict => {
340                ctx.is_digraph = false;
341                let body = stmts.print(ctx);
342                format!(
343                    "strict graph {} {{{}{}{}}}",
344                    id.print(ctx),
345                    ctx.l_s,
346                    body,
347                    ctx.l_s
348                )
349            }
350            Graph::Graph {
351                id,
352                strict: _,
353                stmts,
354            } => {
355                ctx.is_digraph = false;
356                let body = stmts.print(ctx);
357                format!("graph {} {{{}{}{}}}", id.print(ctx), ctx.l_s, body, ctx.l_s)
358            }
359            Graph::DiGraph { id, strict, stmts } if *strict => {
360                ctx.is_digraph = true;
361                let body = stmts.print(ctx);
362                format!(
363                    "strict digraph {} {{{}{}{}}}",
364                    id.print(ctx),
365                    ctx.l_s,
366                    body,
367                    ctx.l_s
368                )
369            }
370            Graph::DiGraph {
371                id,
372                strict: _,
373                stmts,
374            } => {
375                ctx.is_digraph = true;
376                let body = stmts.print(ctx);
377                format!(
378                    "digraph {} {{{}{}{}}}",
379                    id.print(ctx),
380                    ctx.l_s,
381                    body,
382                    ctx.l_s
383                )
384            }
385        }
386    }
387}
388
389impl DotPrinter for Vec<Stmt> {
390    fn print(&self, ctx: &mut PrinterContext) -> String {
391        let attrs: Vec<String> = self.iter().map(|e| e.print(ctx)).collect();
392        attrs.join(ctx.l_s.as_str())
393    }
394}
395
396impl DotPrinter for Stmt {
397    fn print(&self, ctx: &mut PrinterContext) -> String {
398        let end = if ctx.semi { ";" } else { "" };
399        let indent = ctx.indent();
400        match self {
401            Stmt::Node(e) => format!("{}{}{}", indent, e.print(ctx), end),
402            Stmt::Subgraph(e) => format!("{}{}{}", indent, e.print(ctx), end),
403            Stmt::Attribute(e) => format!("{}{}{}", indent, e.print(ctx), end),
404            Stmt::GAttribute(e) => format!("{}{}{}", indent, e.print(ctx), end),
405            Stmt::Edge(e) => format!("{}{}{}", indent, e.print(ctx), end),
406        }
407    }
408}
409
410fn print_edge(edge: &Edge, ctx: &mut PrinterContext) -> String {
411    let bond = if ctx.is_digraph { "->" } else { "--" };
412    match edge {
413        Edge {
414            ty: EdgeTy::Pair(l, r),
415            attributes,
416        } => {
417            if attributes.is_empty() {
418                format!("{} {} {}", l.print(ctx), bond, r.print(ctx))
419            } else {
420                format!(
421                    "{} {} {} {}",
422                    l.print(ctx),
423                    bond,
424                    r.print(ctx),
425                    attributes.print(ctx)
426                )
427            }
428        }
429        Edge {
430            ty: EdgeTy::Chain(vs),
431            attributes,
432        } => {
433            let mut iter = vs.iter();
434            let h = iter.next().unwrap().print(ctx);
435            let mut chain = h;
436            for el in iter {
437                chain = format!("{} {} {}", chain, bond, el.print(ctx))
438            }
439            format!("{}{}", chain, attributes.print(ctx))
440        }
441    }
442}
443
444impl DotPrinter for Edge {
445    fn print(&self, ctx: &mut PrinterContext) -> String {
446        let mut edge_str = print_edge(self, ctx);
447        if edge_str.len() <= ctx.inline_size && !ctx.is_inline_on() {
448            ctx.inline_mode();
449            edge_str = print_edge(self, ctx);
450            ctx.multiline_mode();
451        }
452
453        edge_str
454    }
455}
456
457#[cfg(test)]
458mod tests {
459    use dot_generator::{attr, edge, graph, id, node, node_id, port, stmt, subgraph};
460    use dot_structures::*;
461
462    use crate::printer::{DotPrinter, PrinterContext};
463
464    #[test]
465    fn edge_test() {
466        let mut ctx = PrinterContext::default();
467        let edge = edge!(node_id!("abc") => node_id!("bce") => node_id!("cde"); attr!("a",2));
468        assert_eq!(edge.print(&mut ctx), "abc -- bce -- cde[a=2]");
469        ctx.is_digraph = true;
470        assert_eq!(edge.print(&mut ctx), "abc -> bce -> cde[a=2]");
471    }
472
473    #[test]
474    fn node_id_test() {
475        let node_id = NodeId(id!("abc"), Some(port!(id!("abc"), "n")));
476        let mut ctx = PrinterContext::default();
477        assert_eq!(node_id.print(&mut ctx), "abc:abc:n".to_string());
478    }
479
480    #[test]
481    fn node_test() {
482        let mut ctx = PrinterContext::default();
483        assert_eq!(
484            node!("abc";attr!("a",2)).print(&mut ctx),
485            "abc[a=2]".to_string()
486        );
487    }
488
489    #[test]
490    fn attr_test() {
491        let mut ctx = PrinterContext::default();
492        let attr = attr!("a", 2);
493        assert_eq!(attr.print(&mut ctx), "a=2".to_string());
494    }
495
496    #[test]
497    fn graph_attr_test() {
498        let mut ctx = PrinterContext::default();
499        let n_attr = GraphAttributes::Node(vec![attr!("a", 2), attr!("b", 3)]);
500        assert_eq!(n_attr.print(&mut ctx), "node[a=2,b=3]".to_string());
501    }
502
503    #[test]
504    fn subgraph_test() {
505        let mut ctx = PrinterContext::default();
506        let s = subgraph!("id"; node!("abc"), edge!(node_id!("a") => node_id!("b")));
507        println!("{}", s.print(&mut ctx));
508        assert_eq!(
509            s.print(&mut ctx),
510            "subgraph id {\n  abc\n  a -- b\n}".to_string()
511        );
512    }
513
514    #[test]
515    fn graph_test() {
516        let mut ctx = PrinterContext::default();
517        ctx.always_inline();
518        let g = graph!(strict di id!("t");
519          node!("aa";attr!("color","green")),
520          subgraph!("v";
521            node!("aa"; attr!("shape","square")),
522            subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))),
523            node!("aaa";attr!("color","red")),
524            edge!(node_id!("aaa") => node_id!("bbb"))
525            ),
526          edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))),
527          edge!(node_id!("aa") => node_id!("aaa") => node_id!("v"))
528        );
529        assert_eq!(
530            r#"strict digraph t {aa[color=green]subgraph v {aa[shape=square]subgraph vv {a2 -> b2}aaa[color=red]aaa -> bbb}aa -> be -> subgraph v {d -> aaa}aa -> aaa -> v}"#,
531            g.print(&mut ctx)
532        );
533    }
534
535    #[test]
536    fn semi_graph_test() {
537        let mut ctx = PrinterContext::default();
538        let g = graph!(strict di id!("t");
539          node!("aa";attr!("color","green")),
540          subgraph!("v";
541            node!("aa"; attr!("shape","square")),
542            subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))),
543            node!("aaa";attr!("color","red")),
544            edge!(node_id!("aaa") => node_id!("bbb"))
545            ),
546          edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))),
547          edge!(node_id!("aa") => node_id!("aaa") => node_id!("v"))
548        );
549        assert_eq!(
550            "strict digraph t {\n  aa[color=green];\n  subgraph v {\n    aa[shape=square];\n    subgraph vv {\n      a2 -> b2;\n    };\n    aaa[color=red];\n    aaa -> bbb;\n  };\n  aa -> be -> subgraph v {d -> aaa;};\n  aa -> aaa -> v;\n}",
551            g.print(ctx.with_semi())
552        );
553    }
554
555    #[test]
556    fn indent_step_graph_test() {
557        let mut ctx = PrinterContext::default();
558        let g = graph!(strict di id!("t");
559          node!("aa";attr!("color","green")),
560          subgraph!("v";
561            node!("aa"; attr!("shape","square")),
562            subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))),
563            node!("aaa";attr!("color","red")),
564            edge!(node_id!("aaa") => node_id!("bbb"))
565            ),
566          edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))),
567          edge!(node_id!("aa") => node_id!("aaa") => node_id!("v"))
568        );
569        assert_eq!(
570            "strict digraph t {\n    aa[color=green]\n    subgraph v {\n        aa[shape=square]\n        subgraph vv {\n            a2 -> b2\n        }\n        aaa[color=red]\n        aaa -> bbb\n    }\n    aa -> be -> subgraph v {d -> aaa}\n    aa -> aaa -> v\n}",
571            g.print(ctx.with_indent_step(4))
572        );
573    }
574
575    #[test]
576    fn mult_attr_l_s_graph_test() {
577        let mut ctx = PrinterContext::default();
578        let g = graph!(di id!("multi");
579          node!("a";attr!("shape","square")),
580          node!("aa";attr!("color","blue"),attr!("shape","Mrecord")),
581          subgraph!("v";
582            node!("aaa"; attr!("shape","square")),
583            node!("aaaa";attr!("color","red"),attr!("shape","Mrecord")),
584            edge!(node_id!("aaa") => node_id!("aaaa");attr!("label","FALSE"))
585          ),
586          edge!(node_id!("a") => node_id!("aa");attr!("label","TRUE"), attr!("color","green"))
587        );
588        assert_eq!(
589            "digraph multi {\n  a[shape=square]\n  aa[\n    color=blue,\n    shape=Mrecord\n  ]\n  subgraph v {\n    aaa[shape=square]\n    aaaa[\n      color=red,\n      shape=Mrecord\n    ]\n    aaa -> aaaa [label=FALSE]\n  }\n  a -> aa [label=TRUE,color=green]\n}",
590            g.print(ctx.with_node_mult_attr_s_l())
591        );
592    }
593
594    #[test]
595    fn mult_attr_l_s_graph_test_no_comma() {
596        let mut ctx = PrinterContext::default();
597        let g = graph!(di id!("multi");
598          node!("a";attr!("shape","square")),
599          node!("aa";attr!("color","blue"),attr!("shape","Mrecord")),
600          subgraph!("v";
601            node!("aaa"; attr!("shape","square")),
602            node!("aaaa";attr!("color","red"),attr!("shape","Mrecord")),
603            edge!(node_id!("aaa") => node_id!("aaaa");attr!("label","FALSE"))
604          ),
605          edge!(node_id!("a") => node_id!("aa");attr!("label","TRUE"), attr!("color","green"))
606        );
607        assert_eq!(
608            "digraph multi {\n  a[shape=square]\n  aa[\n    color=blue\n    shape=Mrecord\n  ]\n  subgraph v {\n    aaa[shape=square]\n    aaaa[\n      color=red\n      shape=Mrecord\n    ]\n    aaa -> aaaa [label=FALSE]\n  }\n  a -> aa [label=TRUE,color=green]\n}",
609            g.print(ctx.with_node_mult_attr_s_l().with_no_node_mult_attr_s_l_comma())
610        );
611    }
612
613    #[test]
614    fn mult_attr_l_s_graph_test_no_comma_no_inline() {
615        let mut ctx = PrinterContext::default();
616        let g = graph!(di id!("multi");
617          node!("a";attr!("shape","square")),
618          node!("aa";attr!("color","blue"),attr!("shape","Mrecord")),
619          subgraph!("v";
620            node!("aaa"; attr!("shape","square")),
621            node!("aaaa";attr!("color","red"),attr!("shape","Mrecord")),
622            edge!(node_id!("aaa") => node_id!("aaaa");attr!("label","FALSE"))
623          ),
624          edge!(node_id!("a") => node_id!("aa");attr!("label","TRUE"), attr!("color","green"))
625        );
626        assert_eq!(
627            "digraph multi {\n  a[shape=square]\n  aa[\n    color=blue\n    shape=Mrecord\n  ]\n  subgraph v {\n    aaa[shape=square]\n    aaaa[\n      color=red\n      shape=Mrecord\n    ]\n    aaa -> aaaa [label=FALSE]\n  }\n  a -> aa [\n    label=TRUE\n    color=green\n  ]\n}",
628            g.print(ctx.with_node_mult_attr_s_l().with_no_node_mult_attr_s_l_comma().with_inline_size(0))
629        );
630    }
631
632    #[test]
633    fn attr_formatter_graph_test() {
634        let mut ctx = PrinterContext::default();
635        let g = graph!(di id!("multi");
636          node!("a";attr!("shape","square")),
637          node!("aa";attr!("color","blue"),attr!("custom", esc "Custom Text"),attr!("shape","Mrecord")),
638          subgraph!("v";
639            node!("aaa"; attr!("shape","square")),
640            node!("aaaa";attr!("color","red"),attr!("custom", esc "Custom Text2")),
641            edge!(node_id!("aaa") => node_id!("aaaa");attr!("label","FALSE"))
642          ),
643          edge!(node_id!("a") => node_id!("aa");attr!("label","TRUE"), attr!("color","green"))
644        );
645        assert_eq!(
646            "digraph multi {\n  a[shape=square]\n  aa[\n    color=blue,\n    custom=\"**Custom Text**\",\n    shape=Mrecord\n  ]\n  subgraph v {\n    aaa[shape=square]\n    aaaa[\n      color=red,\n      custom=\"**Custom Text2**\"\n    ]\n    aaa -> aaaa [label=FALSE]\n  }\n  a -> aa [label=TRUE,color=green]\n}",
647            g.print(ctx.with_node_mult_attr_s_l().with_attr_value_printer(id!("custom"), Box::new(|value, _l_s, _indent, _i_s| {
648                format!(r#""**{}**""#, value.trim_matches('"'))
649            })))
650        );
651    }
652}