Commit 7a1c2124 authored by Matthias Simon's avatar Matthias Simon
Browse files

Fix spaces

parent 3380250a
Loading
Loading
Loading
Loading
+15 −0
Changes for tools/internal/doc/docx/code.go: 15 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -31,6 +31,21 @@ var (
// PL run, without the body-text gap of a Normal paragraph.
const codeGap = `<w:spacing w:before="0" w:after="0"/>`

// closeCode ends a listing before the next heading or body block.
//
// PL has no space after it, and the following paragraph has no space before
// it, so the next text would sit on the listing. The published core language
// leaves a blank PL paragraph there. The reader drops that blank line.
// Adjacent listings do not come through here; code separates those itself.
func (w *writer) closeCode() {
	if !w.prevCode {
		return
	}
	w.openPara("PL", "")
	w.closePara()
	w.prevCode = false
}

// codeTabCols is the width of one PL tab stop in Courier New 8pt characters.
// codeTabTwips is that stop in twips (4 × 96).
const (
+20 −10
Changes for tools/internal/doc/docx/write.go: 20 added lines, 10 removed lines.
Original line number Diff line number Diff line
@@ -258,7 +258,7 @@ func (w *writer) section(s *doc.Section) error {
}

func (w *writer) heading(s *doc.Section) error {
	w.prevCode = false
	w.closeCode()
	w.openPara(headingStyle(s), "")
	if s.Number != "" {
		w.text(s.Number, props{})
@@ -289,31 +289,35 @@ func headingStyle(s *doc.Section) string {

func (w *writer) block(b doc.Block, c place) error {
	switch b := b.(type) {
	case *doc.Comment:
		return nil
	case *doc.Code:
		return w.code(b, c)
	case *doc.Paragraph:
		w.prevCode = false
		w.closeCode()
		return w.paragraph("Normal", indentAttr(c), b.Content)
	case *doc.List:
		w.prevCode = false
		w.closeCode()
		return w.list(b, c.level+1)
	case *doc.Quote:
		w.prevCode = false
		w.closeCode()
		return w.quote(b)
	case *doc.Note:
		w.prevCode = false
		w.closeCode()
		return w.note(b)
	case *doc.Table:
		w.prevCode = false
		w.closeCode()
		return w.table(b)
	case *doc.Comment:
		return nil
	case *doc.Code:
		return w.code(b, c)
	default:
		return fmt.Errorf("docx: unsupported block %T", b)
	}
}

func (w *writer) paragraph(style, extra string, ins []doc.Inline) error {
	// A paragraph written from a note or quotation does not pass through
	// block, so the listing has to be closed here too. Table cells call this
	// only after closeCode, which keeps the blank line out of the cell.
	w.closeCode()
	w.openPara(style, extra)
	if err := w.inlines(ins, props{}); err != nil {
		return err
@@ -414,6 +418,9 @@ func (w *writer) list(l *doc.List, depth int) error {
}

func (w *writer) item(blocks []doc.Block, style, numPr string, depth int) error {
	// The item text is written here, not through paragraph, so a listing that
	// ended on the previous item has to be closed before this marker.
	w.closeCode()
	if len(blocks) == 0 {
		w.openPara(style, numPr)
		w.closePara()
@@ -452,6 +459,9 @@ func onlyLists(blocks []doc.Block) bool {
}

func (w *writer) table(t *doc.Table) error {
	// Close any listing before the table markup. A later cell paragraph must
	// not emit the blank PL line inside a cell.
	w.closeCode()
	cols := 0
	for _, row := range t.Rows {
		if len(row) > cols {
+95 −0
Changes for tools/internal/doc/docx/write_test.go: 95 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -121,6 +121,9 @@ func TestWriteRoundTrip(t *testing.T) {
	if strings.Count(docXML, `<w:spacing w:before="0" w:after="0"/>`) != 3 {
		t.Errorf("code gaps = %d", strings.Count(docXML, `<w:spacing w:before="0" w:after="0"/>`))
	}
	if strings.Count(docXML, `<w:p><w:pPr><w:pStyle w:val="PL"/></w:pPr></w:p>`) != 1 {
		t.Errorf("blank lines after code = %d", strings.Count(docXML, `<w:p><w:pPr><w:pStyle w:val="PL"/></w:pPr></w:p>`))
	}
	quoted := `<w:t>"var"</w:t>`
	if i := strings.Index(docXML, quoted); i < 0 {
		t.Fatal("missing quoted var")
@@ -221,6 +224,98 @@ func TestWriteRoundTrip(t *testing.T) {
	}
}

func TestSpaceAfterCode(t *testing.T) {
	d := &doc.Document{Sections: []*doc.Section{{
		Number: "A",
		Level:  1,
		Title:  []doc.Inline{doc.Text("Grammar")},
		Blocks: []doc.Block{
			&doc.Code{Lang: "ebnf", Lines: []string{"Grammar ::= Production."}},
			&doc.Paragraph{Content: []doc.Inline{doc.Text("The grammar is parsed from left to right.")}},
			&doc.Code{Lang: "ttcn3", Lines: []string{"var integer x;"}},
			&doc.Code{Lang: "ttcn3", Lines: []string{"var integer y;"}},
			&doc.List{Items: [][]doc.Block{
				{
					&doc.Code{Lang: "ttcn3", Lines: []string{"a();"}},
					&doc.Paragraph{Content: []doc.Inline{doc.Text("after the sample")}},
				},
				{&doc.Paragraph{Content: []doc.Inline{doc.Text("next item")}}},
			}},
		},
		Sections: []*doc.Section{{
			Number: "A.1",
			Level:  2,
			Title:  []doc.Inline{doc.Text("Tokens")},
			Blocks: []doc.Block{
				&doc.Paragraph{Content: []doc.Inline{doc.Strong{doc.Text("Semantic description")}}},
				&doc.Code{Lang: "ebnf", Lines: []string{"Token ::= Name."}},
				&doc.Paragraph{Content: []doc.Inline{doc.Text("Tokens form the vocabulary.")}},
			},
		}},
	}}}
	var buf bytes.Buffer
	if err := Write(&buf, d); err != nil {
		t.Fatal(err)
	}
	docXML := zipPart(t, buf.Bytes(), "word/document.xml")
	const blank = `<w:p><w:pPr><w:pStyle w:val="PL"/></w:pPr></w:p>`
	// After each listing that is followed by prose or a heading, and after
	// the sample inside the list item. Not between the two adjacent listings.
	if strings.Count(docXML, blank) != 4 {
		t.Fatalf("blank PL after code = %d", strings.Count(docXML, blank))
	}
	if strings.Count(docXML, codeGap) != 1 {
		t.Fatalf("gaps between listings = %d", strings.Count(docXML, codeGap))
	}
	// Highlighting splits a line into runs, so the markers are whole text nodes.
	for _, pair := range [][2]string{
		{">Production</w:t>", "The grammar is parsed from left to right."},
		{">y</w:t>", ">a</w:t>"},
		{">a</w:t>", "after the sample"},
		{">Name</w:t>", "Tokens form the vocabulary."},
	} {
		i := strings.Index(docXML, pair[0])
		j := strings.Index(docXML, pair[1])
		if i < 0 || j < i {
			t.Fatalf("missing %q then %q", pair[0], pair[1])
		}
		if !strings.Contains(docXML[i:j], blank) {
			t.Fatalf("no blank PL between %q and %q", pair[0], pair[1])
		}
	}
	x := strings.Index(docXML, ">x</w:t>")
	y := strings.Index(docXML, ">y</w:t>")
	if x < 0 || y < x || strings.Contains(docXML[x:y], blank) || !strings.Contains(docXML[x:y], codeGap) {
		t.Fatal("adjacent listings were spaced like body text")
	}

	got := &doc.Document{}
	if err := Read(got, "A-grammar.docx", buf.Bytes()); err != nil {
		t.Fatal(err)
	}
	var lines []string
	var walk func([]doc.Block)
	walk = func(bs []doc.Block) {
		for _, b := range bs {
			switch b := b.(type) {
			case *doc.Code:
				lines = append(lines, b.Lines...)
			case *doc.List:
				for _, item := range b.Items {
					walk(item)
				}
			}
		}
	}
	walk(got.Sections[0].Blocks)
	walk(got.Sections[0].Sections[0].Blocks)
	gotLines := strings.Join(lines, "\n")
	want := "Grammar ::= Production.\nvar integer x;\nvar integer y;\na();\nToken ::= Name."
	if gotLines != want {
		t.Fatalf("lines:\n%s", gotLines)
	}
}

func TestTTCN3Highlight(t *testing.T) {
	d := &doc.Document{Sections: []*doc.Section{{
		Number: "1",