Loading tools/internal/doc/docx/code.go +15 −0 Changes for tools/internal/doc/docx/code.go: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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 ( Loading tools/internal/doc/docx/write.go +20 −10 Changes for tools/internal/doc/docx/write.go: 20 added lines, 10 removed lines. Original line number Diff line number Diff line Loading @@ -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{}) Loading Loading @@ -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 Loading Loading @@ -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() Loading Loading @@ -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 { Loading tools/internal/doc/docx/write_test.go +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 Loading @@ -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") Loading Loading @@ -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", Loading Loading
tools/internal/doc/docx/code.go +15 −0 Changes for tools/internal/doc/docx/code.go: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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 ( Loading
tools/internal/doc/docx/write.go +20 −10 Changes for tools/internal/doc/docx/write.go: 20 added lines, 10 removed lines. Original line number Diff line number Diff line Loading @@ -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{}) Loading Loading @@ -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 Loading Loading @@ -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() Loading Loading @@ -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 { Loading
tools/internal/doc/docx/write_test.go +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 Loading @@ -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") Loading Loading @@ -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", Loading