@@ -39,27 +39,40 @@ function hasMarkup(text) {
3939
4040// Get the "topic" for cross-references like Wattsi:
4141// https://github.com/whatwg/wattsi/blob/b9c28036a2a174f7f87315164f001120596a95f1/src/wattsi.pas#L882-L894
42- function getTopic ( elem ) {
42+ //
43+ // Also return whether it came from a data-x attribute or text content, so that
44+ // data-x attribute values can be detected and processed further.
45+ function getTopicAndSource ( elem ) {
4346 let result ;
47+ let source ;
4448 while ( true ) {
4549 if ( elem . hasAttribute ( kCrossRefAttribute ) ) {
4650 result = elem . getAttribute ( kCrossRefAttribute ) ;
51+ source = kCrossRefAttribute ;
4752 break ;
4853 } else if ( isElement ( elem . firstChild ) && elem . firstChild === elem . lastChild ) {
4954 elem = elem . firstChild ;
5055 continue ;
5156 } else {
5257 result = elem . textContent ;
58+ source = 'textContent' ;
5359 break ;
5460 }
5561 }
5662 // This matches Wattsi's MungeStringToTopic in spirit,
5763 // but perhaps not in every detail:
58- return result
59- . replaceAll ( '#' , '' )
60- . replaceAll ( / \s + / g, ' ' )
61- . toLowerCase ( )
62- . trim ( ) ;
64+ return [
65+ result
66+ . replaceAll ( '#' , '' )
67+ . replaceAll ( / \s + / g, ' ' )
68+ . toLowerCase ( )
69+ . trim ( ) ,
70+ source
71+ ] ;
72+ }
73+
74+ function getTopic ( elem ) {
75+ return getTopicAndSource ( elem ) [ 0 ] ;
6376}
6477
6578// Convert a topic to an ID like Wattsi:
@@ -73,60 +86,90 @@ function getId(topic) {
7386
7487// Get the linking text like Bikeshed:
7588// https://github.com/speced/bikeshed/blob/50d0ec772915adcd5cec0c2989a27fa761d70e71/bikeshed/h/dom.py#L174-L201
76- function getBikeshedLinkTexts ( elem ) {
89+ function getBikeshedLinkTextSet ( elem ) {
90+ const texts = new Set ( ) ;
91+
7792 const dataLt = elem . getAttribute ( 'data-lt' ) ;
7893 if ( dataLt === '' ) {
79- return [ ] ;
94+ return texts ;
8095 }
8196
82- let texts = [ ] ;
97+ const add = ( x ) => texts . add ( x . trim ( ) . replaceAll ( / \s + / g, ' ' ) ) ;
98+
8399 if ( dataLt ) {
84100 // TODO: what's the `rawText in ["|", "||", "|||"]` condition for?
85- texts = dataLt . split ( '|' ) ;
101+ dataLt . split ( '|' ) . map ( add ) ;
86102 } else {
87103 switch ( elem . localName ) {
88104 case 'dfn' :
89105 case 'a' :
90- texts = [ elem . textContent ] ;
106+ add ( elem . textContent ) ;
91107 break ;
92108 case 'h2' :
93109 case 'h3' :
94110 case 'h4' :
95111 case 'h5' :
96112 case 'h6' :
97- texts = [ ( elem . querySelector ( '.content' ) ?? elem ) . textContent ] ;
113+ add ( ( elem . querySelector ( '.content' ) ?? elem ) . textContent ) ;
98114 break ;
99115 }
100116 }
101117
102- return texts . map ( ( x ) => x . trim ( ) . replaceAll ( / \s + / g, ' ' ) ) ;
118+ const dataLocalLt = elem . getAttribute ( 'data-local-lt' ) ;
119+ if ( dataLocalLt ) {
120+ if ( dataLocalLt . includes ( '|' ) ) {
121+ console . warn ( 'Ignoring data-local-lt value containing |:' , dataLocalLt ) ;
122+ } else {
123+ add ( dataLocalLt ) ;
124+ }
125+ }
126+
127+ return texts ;
128+ }
129+
130+ // Get the *first* linking text like Bikeshed:
131+ // https://github.com/speced/bikeshed/blob/50d0ec772915adcd5cec0c2989a27fa761d70e71/bikeshed/h/dom.py#L215-L220
132+ function getBikeshedLinkText ( elem ) {
133+ for ( const text of getBikeshedLinkTextSet ( elem ) ) {
134+ return text ;
135+ }
136+ return null ;
103137}
104138
105139// Add for and lt to ensure that Bikeshed will link the <a> to the right <dfn>.
106- function ensureLink ( a , dfn ) {
140+ function ensureLink ( a , dfn , dfnLtCounts ) {
107141 if ( dfn . hasAttribute ( 'for' ) ) {
108142 a . setAttribute ( 'for' , dfn . getAttribute ( 'for' ) ) ;
109143 // TODO: don't add when it's already unambiguous.
110144 }
111145
112- const dfnLts = getBikeshedLinkTexts ( dfn ) ;
113- if ( dfnLts . length === 0 ) {
146+ const dfnLts = getBikeshedLinkTextSet ( dfn ) ;
147+ if ( dfnLts . size === 0 ) {
114148 console . warn ( 'No linking text for' , dfn . outerHTML ) ;
115149 return ;
116150 }
117- const aLts = getBikeshedLinkTexts ( a ) ;
118- if ( aLts . length !== 1 ) {
119- console . warn ( 'Zero or too many linking texts for' , a . outerHTML ) ;
151+ const aLt = getBikeshedLinkText ( a ) ;
152+ if ( ! aLt ) {
153+ console . warn ( 'No linking text for' , a . outerHTML ) ;
154+ return ;
155+ }
156+
157+ if ( a . hasAttribute ( 'for' ) ) {
158+ // TODO: look in dfnLts when that tracks <dfn for>
159+ return ;
120160 }
121- if ( ! dfnLts . some ( ( lt ) => lt === aLts [ 0 ] ) ) {
122- // console.log('Fixing link from', a.outerHTML, 'to', dfn.outerHTML, 'with lt');
123- // Note: data-lt is rewritten to lt later. It would also work to remove
124- // any data-lt attribute here and just add lt.
125- a . setAttribute ( 'data-lt' , dfnLts [ 0 ] ) ;
161+
162+ for ( const lt of dfnLts ) {
163+ if ( dfnLtCounts . get ( lt ) === 1 ) {
164+ // This is a unique linking text.
165+ // Note: data-lt is rewritten to lt later. It would also work to remove
166+ // any data-lt attribute here and just add lt.
167+ a . setAttribute ( 'data-lt' , lt ) ;
168+ return ;
169+ }
126170 }
127171
128- // TODO: check if Bikeshed would now find the right <dfn> and if not
129- // add additional attributes to make it so.
172+ // TODO: handle cases that end up here.
130173}
131174
132175function convert ( infile , outfile ) {
@@ -147,28 +190,51 @@ function convert(infile, outfile) {
147190 // https://github.com/whatwg/wattsi/blob/b9c28036a2a174f7f87315164f001120596a95f1/src/wattsi.pas#L735-L759
148191
149192 // Scan all definitions
150- const crossRefs = new Map ( ) ;
193+ const crossRefs = new Map ( ) ; // map from Wattsi topic to <dfn>
194+ const dfnLtCounts = new Map ( ) ; // map from Bikeshed link text to number of uses in <dfn>
151195 for ( const dfn of document . querySelectorAll ( 'dfn' ) ) {
152196 if ( dfn . getAttribute ( kCrossRefAttribute ) === '' ) {
153197 continue ;
154198 }
155- const topic = getTopic ( dfn ) ;
199+ const [ topic , source ] = getTopicAndSource ( dfn ) ;
156200 if ( crossRefs . has ( topic ) ) {
157201 console . warn ( 'Duplicate <dfn> topic:' , topic ) ;
158202 }
159203 crossRefs . set ( topic , dfn ) ;
160204
161205 if ( ! dfn . hasAttribute ( 'id' ) ) {
206+ // TODO: avoid if Bikeshed would generate the same ID
162207 dfn . setAttribute ( 'id' , getId ( topic ) ) ;
163208 }
164209
210+ const lts = getBikeshedLinkTextSet ( dfn ) ;
211+
165212 // Remove "new" from the linking text of constructors.
166213 if ( dfn . hasAttribute ( 'constructor' ) && ! dfn . hasAttribute ( 'data-lt' ) ) {
167- const lt = getBikeshedLinkTexts ( dfn ) [ 0 ] ;
168- if ( lt ?. startsWith ( 'new ' ) ) {
169- dfn . setAttribute ( 'data-lt' , lt . substring ( 4 ) ) ;
214+ for ( const lt of lts ) {
215+ if ( lt . startsWith ( 'new ' ) ) {
216+ dfn . setAttribute ( 'data-lt' , lt . substring ( 4 ) ) ;
217+ break ;
218+ }
170219 }
171220 }
221+
222+ // Put data-x values into local-lt to enable disambiguating <dfn>s
223+ // with the same linking text, but only if it's not already in lts.
224+ if ( source === kCrossRefAttribute && ! lts . has ( topic ) ) {
225+ dfn . setAttribute ( 'data-local-lt' , topic ) ;
226+ lts . add ( topic ) ; // equivalent to calling getBikeshedLinkTextSet(dfn) again
227+ }
228+
229+ // Count uses of each Bikeshed linking text
230+ if ( dfn . hasAttribute ( 'for' ) ) {
231+ // TODO: track <dfn for> as well
232+ continue ;
233+ }
234+ for ( const lt of lts ) {
235+ const count = ( dfnLtCounts . get ( lt ) ?? 0 ) + 1
236+ dfnLtCounts . set ( lt , count ) ;
237+ }
172238 }
173239
174240 // Track used <dfn>s in order to identify the unused ones.
@@ -264,7 +330,7 @@ function convert(infile, outfile) {
264330 }
265331 span . replaceWith ( a ) ;
266332
267- ensureLink ( a , dfn ) ;
333+ ensureLink ( a , dfn , dfnLtCounts ) ;
268334 usedDfns . add ( dfn ) ;
269335 }
270336
@@ -287,7 +353,7 @@ function convert(infile, outfile) {
287353 i . parentNode . insertBefore ( a , i ) ;
288354 a . appendChild ( i ) ;
289355
290- ensureLink ( a , dfn ) ;
356+ ensureLink ( a , dfn , dfnLtCounts ) ;
291357 usedDfns . add ( dfn ) ;
292358 }
293359
@@ -359,15 +425,19 @@ function convert(infile, outfile) {
359425 code . replaceWith ( a ) ;
360426 a . appendChild ( code ) ;
361427
362- ensureLink ( a , dfn ) ;
428+ ensureLink ( a , dfn , dfnLtCounts ) ;
363429 usedDfns . add ( dfn ) ;
364430 }
365431
366- // Rewrite data-lt to lt.
432+ // Rewrite data-lt to lt and data-local-lt to local-lt .
367433 for ( const elem of document . querySelectorAll ( '[data-lt]' ) ) {
368434 elem . setAttribute ( 'lt' , elem . getAttribute ( 'data-lt' ) ) ;
369435 elem . removeAttribute ( 'data-lt' ) ;
370436 }
437+ for ( const elem of document . querySelectorAll ( '[data-local-lt]' ) ) {
438+ elem . setAttribute ( 'local-lt' , elem . getAttribute ( 'data-local-lt' ) ) ;
439+ elem . removeAttribute ( 'data-local-lt' ) ;
440+ }
371441
372442 for ( const elem of document . querySelectorAll ( '[data-x]' ) ) {
373443 elem . removeAttribute ( kCrossRefAttribute ) ;
0 commit comments