@@ -18,27 +18,36 @@ import (
1818
1919// qdocVerbatim names the commands whose content runs to a matching `\end<X>`
2020// and is not prose.
21- var qdocVerbatim = map [string ]struct {}{
22- "badcode" : {},
23- "code" : {},
24- "css" : {},
25- "js" : {},
26- "qml" : {},
27- "raw" : {},
21+ // The command each one ends at is not always `\end<X>`: the code variants all
22+ // close on `\endcode`, and `\oldcode` runs through `\newcode` because both
23+ // halves are code.
24+ var qdocVerbatim = map [string ]string {
25+ "badcode" : "endcode" ,
26+ "code" : "endcode" ,
27+ "css" : "endcss" ,
28+ "js" : "endjs" ,
29+ "newcode" : "endcode" ,
30+ "oldcode" : "endcode" ,
31+ "qml" : "endqml" ,
32+ "raw" : "endraw" ,
2833}
2934
3035// qdocSkipLine names the topic, context, quoting, build-system, and
3136// conditional commands whose whole line is markup: identifiers, file paths,
3237// cross-references, and expressions, not prose.
3338var qdocSkipLine = map [string ]struct {}{
39+ "abstract" : {},
3440 "annotatedlist" : {},
41+ "attribution" : {},
3542 "class" : {},
3643 "cmakecomponent" : {},
3744 "cmakepackage" : {},
3845 "cmaketargetitem" : {},
3946 "codeline" : {},
47+ "compares" : {},
4048 "compareswith" : {},
4149 "contentspage" : {},
50+ "default" : {},
4251 "dontdocument" : {},
4352 "dots" : {},
4453 "else" : {},
@@ -54,6 +63,7 @@ var qdocSkipLine = map[string]struct{}{
5463 "if" : {},
5564 "include" : {},
5665 "indexpage" : {},
66+ "inheaderfile" : {},
5767 "ingroup" : {},
5868 "inherits" : {},
5969 "inmodule" : {},
@@ -64,23 +74,30 @@ var qdocSkipLine = map[string]struct{}{
6474 "macro" : {},
6575 "meta" : {},
6676 "module" : {},
77+ "modulestate" : {},
6778 "namespace" : {},
6879 "nativetype" : {},
6980 "nextpage" : {},
7081 "noautolist" : {},
7182 "nonreentrant" : {},
83+ "notranslate" : {},
84+ "omitvalue" : {},
7285 "overload" : {},
7386 "page" : {},
87+ "preliminary" : {},
7488 "previouspage" : {},
7589 "printline" : {},
7690 "printto" : {},
7791 "printuntil" : {},
7892 "property" : {},
93+ "qmlabstract" : {},
7994 "qmlattachedproperty" : {},
8095 "qmlattachedsignal" : {},
8196 "qmlbasictype" : {},
8297 "qmlclass" : {},
98+ "qmldefault" : {},
8399 "qmlenum" : {},
100+ "qmlenumeratorsfrom" : {},
84101 "qmlmethod" : {},
85102 "qmlmodule" : {},
86103 "qmlproperty" : {},
@@ -93,9 +110,11 @@ var qdocSkipLine = map[string]struct{}{
93110 "qtvariable" : {},
94111 "quotefile" : {},
95112 "quotefromfile" : {},
113+ "readonly" : {},
96114 "reentrant" : {},
97115 "reimp" : {},
98116 "relates" : {},
117+ "required" : {},
99118 "sa" : {},
100119 "since" : {},
101120 "sincelist" : {},
@@ -107,6 +126,8 @@ var qdocSkipLine = map[string]struct{}{
107126 "tableofcontents" : {},
108127 "target" : {},
109128 "threadsafe" : {},
129+ "toc" : {},
130+ "tocentry" : {},
110131 "typealias" : {},
111132 "typedef" : {},
112133 "variable" : {},
@@ -133,13 +154,42 @@ var (
133154 // The braced {text} that may follow a braced `\l` target or `\span`
134155 // attribute.
135156 qdocLinkText = regexp .MustCompile (`^\s*\{[^{}]*\}` )
157+ // A `\div` or `\span` attribute list: `{class="header"}`.
158+ qdocClassAttr = regexp .MustCompile (`class\s*=\s*"([^"]*)"` )
159+ // A table cell's `{rows,cols}` span, which precedes the cell's prose.
160+ qdocCellSpan = regexp .MustCompile (`^\{\d+\s*,\s*\d+\}\s*` )
161+ // The `[since]` qualifier that may open a `\deprecated` line.
162+ qdocSinceQual = regexp .MustCompile (`^\[[^\]\n]*\]\s*` )
136163)
137164
165+ // qdocClass returns the class named by a `\div` or `\span` attribute list, or
166+ // the argument itself when it is a bare name -- `\div {note}`. An unusable
167+ // class is dropped rather than guessed at, leaving an unclassed element.
168+ func qdocClass (arg string ) string {
169+ arg = qdocArg (arg )
170+ if m := qdocClassAttr .FindStringSubmatch (arg ); m != nil {
171+ return m [1 ]
172+ } else if strings .ContainsAny (arg , `="' ` ) {
173+ return ""
174+ }
175+ return arg
176+ }
177+
178+ // qdocOpenDiv writes a `<div>` carrying `arg`'s class, if it names one.
179+ func (c * qdocConv ) openDiv (arg string ) {
180+ c .flush ()
181+ if class := qdocClass (arg ); class != "" {
182+ c .html .WriteString (`<div class="` + class + "\" >\n " )
183+ return
184+ }
185+ c .html .WriteString ("<div>\n " )
186+ }
187+
138188// qdocInlineNames names the inline commands, so that one starting a line is
139189// not mistaken for a block command.
140190var qdocInlineNames = map [string ]struct {}{
141- "a" : {}, "b" : {}, "c " : {}, "e " : {}, "l " : {}, "sub " : {}, "sup " : {},
142- "tt" : {}, "uicontrol" : {}, "underline" : {},
191+ "a" : {}, "b" : {}, "bold " : {}, "c " : {}, "e " : {}, "i " : {}, "l " : {},
192+ "sub" : {}, "sup" : {}, " tt" : {}, "uicontrol" : {}, "underline" : {},
143193}
144194
145195// qdocArg strips the braces from a command argument.
@@ -181,9 +231,9 @@ func qdocInline(text string) string {
181231 switch name {
182232 case "c" , "tt" , "a" :
183233 out .WriteString ("<code>" + qdocArg (arg ) + "</code>" )
184- case "b" , "uicontrol" :
234+ case "b" , "bold" , " uicontrol" :
185235 out .WriteString ("<strong>" + qdocArg (arg ) + "</strong>" )
186- case "e" :
236+ case "e" , "i" :
187237 out .WriteString ("<em>" + qdocArg (arg ) + "</em>" )
188238 case "underline" :
189239 out .WriteString ("<u>" + qdocArg (arg ) + "</u>" )
@@ -199,14 +249,18 @@ func qdocInline(text string) string {
199249 }
200250 out .WriteString (`<a href="#">` + label + "</a>" )
201251 case "span" :
202- // `\span {class="x"} {text}`: the attribute is markup, the
203- // braced text that follows is prose.
252+ // `\span {class="x"} {text}`: the attribute names a class a rule
253+ // can be scoped to, the braced text that follows is prose.
204254 label := ""
205255 if m := qdocLinkText .FindString (rest ); m != "" {
206256 label = qdocArg (strings .TrimSpace (m ))
207257 rest = rest [len (m ):]
208258 }
209- out .WriteString ("<span>" + label + "</span>" )
259+ if class := qdocClass (arg ); class != "" {
260+ out .WriteString (`<span class="` + class + `">` + label + "</span>" )
261+ } else {
262+ out .WriteString ("<span>" + label + "</span>" )
263+ }
210264 case "image" , "inlineimage" :
211265 // The argument is a file name; a caption, if any, follows as
212266 // ordinary prose.
@@ -276,6 +330,9 @@ func (c *qdocConv) item(rest string) {
276330 top := & c .stack [n - 1 ]
277331 top .inItem = true
278332
333+ // A table cell may open with a `{rows,cols}` span, which is markup.
334+ rest = qdocCellSpan .ReplaceAllString (rest , "" )
335+
279336 tag := "li"
280337 if top .kind == "table" {
281338 tag = "td"
@@ -322,12 +379,31 @@ func qdocBlankDelims(raw string) string {
322379 return raw
323380}
324381
382+ // endsVerbatim reports whether name closes the open verbatim block. Both the
383+ // command QDoc documents and the symmetrical `\end<X>` are accepted, so a
384+ // source that writes `\endbadcode` is not read as unterminated.
385+ func (c * qdocConv ) endsVerbatim (name string ) bool {
386+ return name == qdocVerbatim [c .verbatim ] || name == "end" + c .verbatim
387+ }
388+
325389func (c * qdocConv ) line (raw string ) { //nolint:gocyclo // one case per command family
390+ // A verbatim or omitted block cannot outlive the comment holding it. An
391+ // unterminated one -- a `\code` whose `\endcode` was never written, or a
392+ // spelling this converter does not know -- would otherwise swallow the
393+ // rest of the file.
394+ if strings .HasSuffix (strings .TrimSpace (raw ), "*/" ) {
395+ if c .verbatim != "" {
396+ c .html .WriteString ("</code></pre>\n " )
397+ c .verbatim = ""
398+ }
399+ c .omitted = false
400+ }
401+
326402 raw = qdocBlankDelims (raw )
327403 trimmed := strings .TrimSpace (raw )
328404
329405 if c .verbatim != "" {
330- if m := qdocBlockCmd .FindStringSubmatch (raw ); m != nil && m [1 ] == c . verbatim {
406+ if m := qdocBlockCmd .FindStringSubmatch (raw ); m != nil && c . endsVerbatim ( m [1 ]) {
331407 c .verbatim = ""
332408 c .html .WriteString ("</code></pre>\n " )
333409 }
@@ -364,7 +440,7 @@ func (c *qdocConv) line(raw string) { //nolint:gocyclo // one case per command f
364440 c .omitted = true
365441 case func () bool { _ , ok := qdocVerbatim [name ]; return ok }():
366442 c .flush ()
367- c .verbatim = "end" + name
443+ c .verbatim = name
368444 c .html .WriteString ("<pre><code>" )
369445 case func () bool { _ , ok := qdocSkipLine [name ]; return ok }():
370446 c .flush ()
@@ -378,6 +454,16 @@ func (c *qdocConv) line(raw string) { //nolint:gocyclo // one case per command f
378454 case name == "title" :
379455 c .flush ()
380456 c .html .WriteString ("<h1>" + qdocInline (rest ) + "</h1>\n " )
457+ case name == "subtitle" :
458+ c .flush ()
459+ c .html .WriteString (`<h2 class="subtitle">` + qdocInline (rest ) + "</h2>\n " )
460+ case name == "deprecated" :
461+ // `\deprecated [6.0] Use \l Foo instead.`: the version is markup,
462+ // the replacement advice that may follow it is prose.
463+ c .flush ()
464+ if rest = qdocSinceQual .ReplaceAllString (rest , "" ); rest != "" {
465+ c .para = append (c .para , rest )
466+ }
381467 case strings .HasPrefix (name , "section" ) && len (name ) == 8 && name [7 ] >= '1' && name [7 ] <= '4' :
382468 c .closeDiv ()
383469 level := string ('1' + name [7 ] - '0' )
@@ -425,9 +511,15 @@ func (c *qdocConv) line(raw string) { //nolint:gocyclo // one case per command f
425511 c .flush ()
426512 c .html .WriteString ("</blockquote>\n " )
427513 case name == "div" :
514+ c .openDiv (rest )
515+ case name == "details" :
516+ // `\details {Summary text}`: the summary is prose of its own.
428517 c .flush ()
429- c .html .WriteString ("<div>\n " )
430- case name == "enddiv" :
518+ c .html .WriteString (`<div class="details">` + "\n " )
519+ if summary := qdocArg (rest ); summary != "" {
520+ c .html .WriteString (`<p class="summary">` + qdocInline (summary ) + "</p>\n " )
521+ }
522+ case name == "enddiv" , name == "enddetails" :
431523 c .flush ()
432524 c .html .WriteString ("</div>\n " )
433525 case name == "legalese" :
0 commit comments