@@ -313,6 +313,13 @@ pub enum LabelText<'a> {
313
313
/// are also the escape sequences `\l` which left-justifies the
314
314
/// preceding line and `\r` which right-justifies it.
315
315
EscStr ( Cow < ' a , str > ) ,
316
+
317
+ /// This uses a graphviz [HTML string label][html]. The string is
318
+ /// printed exactly as given, but between `<` and `>`. **No
319
+ /// escaping is performed.**
320
+ ///
321
+ /// [html]: http://www.graphviz.org/content/node-shapes#html
322
+ HtmlStr ( Cow < ' a , str > ) ,
316
323
}
317
324
318
325
/// The style for a node or edge.
@@ -453,6 +460,14 @@ pub trait Labeller<'a,N,E> {
453
460
/// is a valid DOT identifier.
454
461
fn node_id ( & ' a self , n : & N ) -> Id < ' a > ;
455
462
463
+ /// Maps `n` to one of the [graphviz `shape` names][1]. If `None`
464
+ /// is returned, no `shape` attribute is specified.
465
+ ///
466
+ /// [1]: http://www.graphviz.org/content/node-shapes
467
+ fn node_shape ( & ' a self , _node : & N ) -> Option < LabelText < ' a > > {
468
+ None
469
+ }
470
+
456
471
/// Maps `n` to a label that will be used in the rendered output.
457
472
/// The label need not be unique, and may be the empty string; the
458
473
/// default is just the output from `node_id`.
@@ -479,6 +494,16 @@ pub trait Labeller<'a,N,E> {
479
494
}
480
495
}
481
496
497
+ /// Escape tags in such a way that it is suitable for inclusion in a
498
+ /// Graphviz HTML label.
499
+ pub fn escape_html ( s : & str ) -> String {
500
+ s
501
+ . replace ( "&" , "&" )
502
+ . replace ( "\" " , """ )
503
+ . replace ( "<" , "<" )
504
+ . replace ( ">" , ">" )
505
+ }
506
+
482
507
impl < ' a > LabelText < ' a > {
483
508
pub fn label < S : IntoCow < ' a , str > > ( s : S ) -> LabelText < ' a > {
484
509
LabelStr ( s. into_cow ( ) )
@@ -488,6 +513,10 @@ impl<'a> LabelText<'a> {
488
513
EscStr ( s. into_cow ( ) )
489
514
}
490
515
516
+ pub fn html < S : IntoCow < ' a , str > > ( s : S ) -> LabelText < ' a > {
517
+ HtmlStr ( s. into_cow ( ) )
518
+ }
519
+
491
520
fn escape_char < F > ( c : char , mut f : F ) where F : FnMut ( char ) {
492
521
match c {
493
522
// not escaping \\, since Graphviz escString needs to
@@ -505,10 +534,12 @@ impl<'a> LabelText<'a> {
505
534
}
506
535
507
536
/// Renders text as string suitable for a label in a .dot file.
508
- pub fn escape ( & self ) -> String {
537
+ /// This includes quotes or suitable delimeters.
538
+ pub fn to_dot_string ( & self ) -> String {
509
539
match self {
510
- & LabelStr ( ref s) => s. escape_default ( ) ,
511
- & EscStr ( ref s) => LabelText :: escape_str ( & s[ ..] ) ,
540
+ & LabelStr ( ref s) => format ! ( "\" {}\" " , s. escape_default( ) ) ,
541
+ & EscStr ( ref s) => format ! ( "\" {}\" " , LabelText :: escape_str( & s[ ..] ) ) ,
542
+ & HtmlStr ( ref s) => format ! ( "<{}>" , s) ,
512
543
}
513
544
}
514
545
@@ -524,6 +555,7 @@ impl<'a> LabelText<'a> {
524
555
} else {
525
556
s
526
557
} ,
558
+ HtmlStr ( s) => s,
527
559
}
528
560
}
529
561
@@ -612,14 +644,15 @@ pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N
612
644
try!( indent ( w) ) ;
613
645
let id = g. node_id ( n) ;
614
646
615
- let escaped = & g. node_label ( n) . escape ( ) ;
647
+ let escaped = & g. node_label ( n) . to_dot_string ( ) ;
648
+ let shape;
616
649
617
650
let mut text = vec ! [ id. as_slice( ) ] ;
618
651
619
652
if !options. contains ( & RenderOption :: NoNodeLabels ) {
620
- text. push ( "[label=\" " ) ;
653
+ text. push ( "[label=" ) ;
621
654
text. push ( escaped) ;
622
- text. push ( "\" ]" ) ;
655
+ text. push ( "]" ) ;
623
656
}
624
657
625
658
let style = g. node_style ( n) ;
@@ -629,12 +662,19 @@ pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N
629
662
text. push ( "\" ]" ) ;
630
663
}
631
664
665
+ if let Some ( s) = g. node_shape ( n) {
666
+ shape = s. to_dot_string ( ) ;
667
+ text. push ( "[shape=" ) ;
668
+ text. push ( & shape) ;
669
+ text. push ( "]" ) ;
670
+ }
671
+
632
672
text. push ( ";" ) ;
633
673
try!( writeln ( w, & text) ) ;
634
674
}
635
675
636
676
for e in g. edges ( ) . iter ( ) {
637
- let escaped_label = & g. edge_label ( e) . escape ( ) ;
677
+ let escaped_label = & g. edge_label ( e) . to_dot_string ( ) ;
638
678
try!( indent ( w) ) ;
639
679
let source = g. source ( e) ;
640
680
let target = g. target ( e) ;
@@ -644,9 +684,9 @@ pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N
644
684
let mut text = vec ! [ source_id. as_slice( ) , " -> " , target_id. as_slice( ) ] ;
645
685
646
686
if !options. contains ( & RenderOption :: NoEdgeLabels ) {
647
- text. push ( "[label=\" " ) ;
687
+ text. push ( "[label=" ) ;
648
688
text. push ( escaped_label) ;
649
- text. push ( "\" ]" ) ;
689
+ text. push ( "]" ) ;
650
690
}
651
691
652
692
let style = g. edge_style ( e) ;
@@ -667,7 +707,7 @@ pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N
667
707
mod tests {
668
708
use self :: NodeLabels :: * ;
669
709
use super :: { Id , Labeller , Nodes , Edges , GraphWalk , render, Style } ;
670
- use super :: LabelText :: { self , LabelStr , EscStr } ;
710
+ use super :: LabelText :: { self , LabelStr , EscStr , HtmlStr } ;
671
711
use std:: io;
672
712
use std:: io:: prelude:: * ;
673
713
use std:: borrow:: IntoCow ;
@@ -805,12 +845,12 @@ mod tests {
805
845
fn node_id ( & ' a self , n : & Node ) -> Id < ' a > { self . graph . node_id ( n) }
806
846
fn node_label ( & ' a self , n : & Node ) -> LabelText < ' a > {
807
847
match self . graph . node_label ( n) {
808
- LabelStr ( s) | EscStr ( s) => EscStr ( s) ,
848
+ LabelStr ( s) | EscStr ( s) | HtmlStr ( s ) => EscStr ( s) ,
809
849
}
810
850
}
811
851
fn edge_label ( & ' a self , e : & & ' a Edge ) -> LabelText < ' a > {
812
852
match self . graph . edge_label ( e) {
813
- LabelStr ( s) | EscStr ( s) => EscStr ( s) ,
853
+ LabelStr ( s) | EscStr ( s) | HtmlStr ( s ) => EscStr ( s) ,
814
854
}
815
855
}
816
856
}
0 commit comments