Linkify pipe words.

This required switching anchors from <a name> to id attributes, which
also works. HTML gets unhappy when you nest <a> tags inside each other
and tagging the elements is somewhat tidier.

Change-Id: I64094d35a0e820e37be9e5dc8db013a50774190f
Reviewed-on: https://boringssl-review.googlesource.com/6314
Reviewed-by: Adam Langley <alangley@gmail.com>
diff --git a/util/doc.go b/util/doc.go
index 48d65eb..ce7a3e8 100644
--- a/util/doc.go
+++ b/util/doc.go
@@ -42,6 +42,8 @@
 	// is a separate paragraph.
 	Preamble []string
 	Sections []HeaderSection
+	// AllDecls maps all decls to their URL fragments.
+	AllDecls map[string]string
 }
 
 type HeaderSection struct {
@@ -282,7 +284,8 @@
 	lines = lines[2:]
 
 	header := &HeaderFile{
-		Name: filepath.Base(path),
+		Name:     filepath.Base(path),
+		AllDecls: make(map[string]string),
 	}
 
 	for i, line := range lines {
@@ -391,6 +394,8 @@
 				// duplicate table-of-contents entries.
 				allAnchors[anchor] = struct{}{}
 
+				header.AllDecls[name] = anchor
+
 				section.Decls = append(section.Decls, HeaderDecl{
 					Comment: comment,
 					Name:    name,
@@ -426,7 +431,7 @@
 	return s
 }
 
-func markupPipeWords(s string) template.HTML {
+func markupPipeWords(allDecls map[string]string, s string) template.HTML {
 	ret := ""
 
 	for {
@@ -442,7 +447,14 @@
 		j := strings.Index(s, " ")
 		if i > 0 && (j == -1 || j > i) {
 			ret += "<tt>"
+			anchor, isLink := allDecls[s[:i]]
+			if isLink {
+				ret += fmt.Sprintf("<a href=\"%s\">", template.HTMLEscapeString(anchor))
+			}
 			ret += s[:i]
+			if isLink {
+				ret += "</a>"
+			}
 			ret += "</tt>"
 			s = s[i+1:]
 		} else {
@@ -460,6 +472,11 @@
 	if end > 0 {
 		end += start
 		w := strings.ToLower(string(s[start:end]))
+		// The first word was already marked up as an HTML tag. Don't
+		// mark it up further.
+		if strings.ContainsRune(w, '<') {
+			return s
+		}
 		if w == "a" || w == "an" {
 			start = end + 1
 			goto again
@@ -480,10 +497,12 @@
 }
 
 func generate(outPath string, config *Config) (map[string]string, error) {
+	allDecls := make(map[string]string)
+
 	headerTmpl := template.New("headerTmpl")
 	headerTmpl.Funcs(template.FuncMap{
 		"firstSentence":   firstSentence,
-		"markupPipeWords": markupPipeWords,
+		"markupPipeWords": func(s string) template.HTML { return markupPipeWords(allDecls, s) },
 		"markupFirstWord": markupFirstWord,
 		"newlinesToBR":    newlinesToBR,
 	})
@@ -514,23 +533,19 @@
 
     {{range .Sections}}
       {{if not .IsPrivate}}
-        <div class="section">
+        <div class="section" {{if .Anchor}}id="{{.Anchor}}"{{end}}>
         {{if .Preamble}}
           <div class="sectionpreamble">
-          <a{{if .Anchor}} name="{{.Anchor}}"{{end}}>
           {{range .Preamble}}<p>{{. | html | markupPipeWords}}</p>{{end}}
-          </a>
           </div>
         {{end}}
 
         {{range .Decls}}
-          <div class="decl">
-          <a{{if .Anchor}} name="{{.Anchor}}"{{end}}>
+          <div class="decl" {{if .Anchor}}id="{{.Anchor}}"{{end}}>
           {{range .Comment}}
             <p>{{. | html | markupPipeWords | newlinesToBR | markupFirstWord}}</p>
           {{end}}
           <pre>{{.Decl}}</pre>
-          </a>
           </div>
         {{end}}
         </div>
@@ -544,6 +559,7 @@
 	}
 
 	headerDescriptions := make(map[string]string)
+	var headers []*HeaderFile
 
 	for _, section := range config.Sections {
 		for _, headerPath := range section.Headers {
@@ -552,15 +568,23 @@
 				return nil, errors.New("while parsing " + headerPath + ": " + err.Error())
 			}
 			headerDescriptions[header.Name] = firstSentence(header.Preamble)
-			filename := filepath.Join(outPath, header.Name+".html")
-			file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
-			if err != nil {
-				panic(err)
+			headers = append(headers, header)
+
+			for name, anchor := range header.AllDecls {
+				allDecls[name] = fmt.Sprintf("%s#%s", header.Name+".html", anchor)
 			}
-			defer file.Close()
-			if err := headerTmpl.Execute(file, header); err != nil {
-				return nil, err
-			}
+		}
+	}
+
+	for _, header := range headers {
+		filename := filepath.Join(outPath, header.Name+".html")
+		file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
+		if err != nil {
+			panic(err)
+		}
+		defer file.Close()
+		if err := headerTmpl.Execute(file, header); err != nil {
+			return nil, err
 		}
 	}