func parseSections()

in present/parse.go [365:557]


func parseSections(ctx *Context, name, prefix string, lines *Lines, number []int) ([]Section, error) {
	isMarkdown := prefix[0] == '#'
	isHeading := isHeadingLegacy
	if isMarkdown {
		isHeading = isHeadingMarkdown
	}
	var sections []Section
	for i := 1; ; i++ {
		// Next non-empty line is title.
		text, ok := lines.nextNonEmpty()
		for ok && text == "" {
			text, ok = lines.next()
		}
		if !ok {
			break
		}
		if text != prefix && !strings.HasPrefix(text, prefix+" ") {
			lines.back()
			break
		}
		// Markdown sections can end in {#id} to set the HTML anchor for the section.
		// This is nicer than the default #TOC_1_2-style anchor.
		title := strings.TrimSpace(text[len(prefix):])
		id := ""
		if isMarkdown && strings.HasSuffix(title, "}") {
			j := strings.LastIndex(title, "{#")
			if j >= 0 {
				id = title[j+2 : len(title)-1]
				title = strings.TrimSpace(title[:j])
			}
		}
		section := Section{
			Number: append(append([]int{}, number...), i),
			Title:  title,
			ID:     id,
		}
		text, ok = lines.nextNonEmpty()
		for ok && !lesserHeading(isHeading, text, prefix) {
			var e Elem
			r, _ := utf8.DecodeRuneInString(text)
			switch {
			case !isMarkdown && unicode.IsSpace(r):
				i := strings.IndexFunc(text, func(r rune) bool {
					return !unicode.IsSpace(r)
				})
				if i < 0 {
					break
				}
				indent := text[:i]
				var s []string
				for ok && (strings.HasPrefix(text, indent) || text == "") {
					if text != "" {
						text = text[i:]
					}
					s = append(s, text)
					text, ok = lines.next()
				}
				lines.back()
				pre := strings.Join(s, "\n")
				raw := pre
				pre = strings.Replace(pre, "\t", "    ", -1) // browsers treat tabs badly
				pre = strings.TrimRightFunc(pre, unicode.IsSpace)
				e = Text{Lines: []string{pre}, Pre: true, Raw: raw}
			case !isMarkdown && strings.HasPrefix(text, "- "):
				var b []string
				for {
					if strings.HasPrefix(text, "- ") {
						b = append(b, text[2:])
					} else if len(b) > 0 && strings.HasPrefix(text, " ") {
						b[len(b)-1] += "\n" + strings.TrimSpace(text)
					} else {
						break
					}
					if text, ok = lines.next(); !ok {
						break
					}
				}
				lines.back()
				e = List{Bullet: b}
			case isSpeakerNote(text):
				section.Notes = append(section.Notes, trimSpeakerNote(text))
			case strings.HasPrefix(text, prefix+prefix[:1]+" ") || text == prefix+prefix[:1]:
				lines.back()
				subsecs, err := parseSections(ctx, name, prefix+prefix[:1], lines, section.Number)
				if err != nil {
					return nil, err
				}
				for _, ss := range subsecs {
					section.Elem = append(section.Elem, ss)
				}
			case strings.HasPrefix(text, prefix+prefix[:1]):
				return nil, fmt.Errorf("%s:%d: badly nested section inside %s: %s", name, lines.line, prefix, text)
			case strings.HasPrefix(text, "."):
				args := strings.Fields(text)
				if args[0] == ".background" {
					section.Classes = append(section.Classes, "background")
					section.Styles = append(section.Styles, "background-image: url('"+args[1]+"')")
					break
				}
				parser := parsers[args[0]]
				if parser == nil {
					return nil, fmt.Errorf("%s:%d: unknown command %q", name, lines.line, text)
				}
				t, err := parser(ctx, name, lines.line, text)
				if err != nil {
					return nil, err
				}
				e = t

			case isMarkdown:
				// Collect Markdown lines, including blank lines and indented text.
				var block []string
				endLine, endBlock := lines.line-1, -1 // end is last non-empty line
				for ok {
					trim := strings.TrimSpace(text)
					if trim != "" {
						// Command breaks text block.
						// Section heading breaks text block in markdown.
						if text[0] == '.' || text[0] == '#' || isSpeakerNote(text) {
							break
						}
						if strings.HasPrefix(text, `\.`) { // Backslash escapes initial period.
							text = text[1:]
						}
						endLine, endBlock = lines.line, len(block)
					}
					block = append(block, text)
					text, ok = lines.next()
				}
				block = block[:endBlock+1]
				lines.line = endLine + 1
				if len(block) == 0 {
					break
				}

				// Replace all leading tabs with 4 spaces,
				// which render better in code blocks.
				// CommonMark defines that for parsing the structure of the file
				// a tab is equivalent to 4 spaces, so this change won't
				// affect the later parsing at all.
				// An alternative would be to apply this to code blocks after parsing,
				// at the same time that we update <a> targets, but that turns out
				// to be quite difficult to modify in the AST.
				for i, line := range block {
					if len(line) > 0 && line[0] == '\t' {
						short := strings.TrimLeft(line, "\t")
						line = strings.Repeat("    ", len(line)-len(short)) + short
						block[i] = line
					}
				}
				html, err := renderMarkdown([]byte(strings.Join(block, "\n")))
				if err != nil {
					return nil, err
				}
				e = HTML{HTML: html}

			default:
				// Collect text lines.
				var block []string
				for ok && strings.TrimSpace(text) != "" {
					// Command breaks text block.
					// Section heading breaks text block in markdown.
					if text[0] == '.' || isSpeakerNote(text) {
						lines.back()
						break
					}
					if strings.HasPrefix(text, `\.`) { // Backslash escapes initial period.
						text = text[1:]
					}
					block = append(block, text)
					text, ok = lines.next()
				}
				if len(block) == 0 {
					break
				}
				e = Text{Lines: block}
			}
			if e != nil {
				section.Elem = append(section.Elem, e)
			}
			text, ok = lines.nextNonEmpty()
		}
		if isHeading.MatchString(text) {
			lines.back()
		}
		sections = append(sections, section)
	}

	if len(sections) == 0 {
		return nil, fmt.Errorf("%s:%d: unexpected line: %s", name, lines.line+1, lines.text[lines.line])
	}
	return sections, nil
}