//Copyright 2013 GoGraphviz Authors // //Licensed under the Apache License, Version 2.0 (the "License"); //you may not use this file except in compliance with the License. //You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // //Unless required by applicable law or agreed to in writing, software //distributed under the License is distributed on an "AS IS" BASIS, //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //See the License for the specific language governing permissions and //limitations under the License. package gographviz import ( "fmt" "github.com/awalterschulze/gographviz/scanner" "github.com/awalterschulze/gographviz/token" "strings" "text/template" "unicode" ) type Escape struct { *Graph } //Returns a graph which will try to escape some strings when required func NewEscape() *Escape { return &Escape{NewGraph()} } func isHtml(s string) bool { if len(s) == 0 { return false } ss := strings.TrimSpace(s) if ss[0] != '<' { return false } count := 0 for _, c := range ss { if c == '<' { count += 1 } if c == '>' { count -= 1 } } if count == 0 { return true } return false } func isLetter(ch rune) bool { return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) && ch != 'ε' } func isId(s string) bool { i := 0 pos := false for _, c := range s { if i == 0 { if !isLetter(c) { return false } pos = true } if unicode.IsSpace(c) { return false } if c == '-' { return false } i++ } return pos } func isDigit(ch rune) bool { return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) } func isNumber(s string) bool { state := 0 for _, c := range s { if state == 0 { if isDigit(c) || c == '.' { state = 2 } else if c == '-' { state = 1 } else { return false } } else if state == 1 { if isDigit(c) || c == '.' { state = 2 } } else if c != '.' && !isDigit(c) { return false } } return (state == 2) } func isStringLit(s string) bool { lex := &scanner.Scanner{} lex.Init([]byte(s), token.DOTTokens) tok, _ := lex.Scan() if tok.Type != token.DOTTokens.Type("string_lit") { return false } tok, _ = lex.Scan() if tok.Type != token.EOF { return false } return true } func esc(s string) string { if len(s) == 0 { return s } if isHtml(s) { return s } ss := strings.TrimSpace(s) if ss[0] == '<' { return fmt.Sprintf("\"%s\"", strings.Replace(s, "\"", "\\\"", -1)) } if isId(s) { return s } if isNumber(s) { return s } if isStringLit(s) { return s } return fmt.Sprintf("\"%s\"", template.HTMLEscapeString(s)) } func escAttrs(attrs map[string]string) map[string]string { newAttrs := make(map[string]string) for k, v := range attrs { newAttrs[esc(k)] = esc(v) } return newAttrs } func (this *Escape) SetName(name string) { this.Graph.SetName(esc(name)) } func (this *Escape) AddPortEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string) { this.Graph.AddPortEdge(esc(src), srcPort, esc(dst), dstPort, directed, escAttrs(attrs)) } func (this *Escape) AddEdge(src, dst string, directed bool, attrs map[string]string) { this.AddPortEdge(src, "", dst, "", directed, attrs) } func (this *Escape) AddNode(parentGraph string, name string, attrs map[string]string) { this.Graph.AddNode(esc(parentGraph), esc(name), escAttrs(attrs)) } func (this *Escape) AddAttr(parentGraph string, field, value string) { this.Graph.AddAttr(esc(parentGraph), esc(field), esc(value)) } func (this *Escape) AddSubGraph(parentGraph string, name string, attrs map[string]string) { this.Graph.AddSubGraph(esc(parentGraph), esc(name), escAttrs(attrs)) } func (this *Escape) IsNode(name string) bool { return this.Graph.IsNode(esc(name)) } func (this *Escape) IsSubGraph(name string) bool { return this.Graph.IsSubGraph(esc(name)) }