@@ -14,9 +14,43 @@ import {
1414} from '../helpers'
1515import { getStaticSpeaker } from '@/components/Speaker'
1616
17+ const HOST = 'https://www.collinsdictionary.com'
18+ const COLLINS_TYPE_PREFIX = 'definition.title.type.'
19+
20+ const SECTION_TYPE_LABELS : { [ key : string ] : string } = {
21+ cobuild : 'COBUILD' ,
22+ ced : 'Collins English Dictionary' ,
23+ english : 'Collins English Dictionary' ,
24+ american : 'American English Dictionary' ,
25+ aed : 'American English Dictionary' ,
26+ learner : 'COBUILD' ,
27+ penguin : 'Penguin Dictionary' ,
28+ esp_ret : 'Retail' ,
29+ examples : 'Examples' ,
30+ idioms : 'Idioms' ,
31+ collos : 'Collocations'
32+ }
33+
34+ const SKIPPED_SECTION_KEYS = new Set ( [
35+ 'video' ,
36+ 'trends' ,
37+ 'wordlists' ,
38+ 'translation' ,
39+ 'translations'
40+ ] )
41+
42+ const SKIPPED_SECTION_LABELS = new Set ( [
43+ 'video pronunciation' ,
44+ 'word lists' ,
45+ 'word usage trends' ,
46+ 'translations' ,
47+ '英语词汇表' ,
48+ '趋势'
49+ ] )
50+
1751export const getSrcPage : GetSrcPageFunction = text => {
1852 return (
19- `https://www.collinsdictionary.com /dictionary/english/` +
53+ `${ HOST } /dictionary/english/` +
2054 encodeURIComponent ( text . replace ( / \s + / g, '-' ) )
2155 )
2256}
@@ -114,44 +148,37 @@ async function handleDOM(
114148 }
115149 const audio : { uk ?: string ; us ?: string } = { }
116150
117- result . sections = [
118- ...doc . querySelectorAll < HTMLDivElement > ( `[data-type-block]` )
119- ]
120- . filter ( $section => {
121- const type = $section . dataset . typeBlock || ''
122- return (
123- type &&
124- type !== 'Video' &&
125- type !== 'Trends' &&
126- type !== '英语词汇表' &&
127- type !== '趋势'
128- )
151+ result . sections = getSectionNodes ( doc )
152+ . filter ( ( { meta } ) => {
153+ const type = getCleanLabel ( meta . dataset . typeBlock )
154+ return ! ! type && ! shouldSkipSectionType ( type )
129155 } )
130- . map ( $section => {
131- const type = $section . dataset . typeBlock || ''
132- const title = $section . dataset . titleBlock || ''
133- const num = $section . dataset . numBlock || ''
156+ . map ( ( { meta, content : $section } , i ) => {
157+ const rawType = getCleanLabel ( meta . dataset . typeBlock )
158+ const type = normalizeSectionType ( rawType )
159+ const title = getSectionTitle ( meta , $section , type )
160+ const num = getCleanLabel (
161+ meta . dataset . numBlock || $section . dataset . numBlock
162+ )
134163 const id = type + title + num
135164 const className = $section . className || ''
165+ const mp3 = getAudio ( $section )
136166
137- if ( type === 'Learner' ) {
167+ if ( isCobuildSection ( rawType ) ) {
138168 // const $frequency = $section.querySelector<HTMLSpanElement>('.word-frequency-img')
139169 // if ($frequency) {
140170 // const star = Number($frequency.dataset.band)
141171 // if (star) {
142172 // result.star = star
143173 // }
144174 // }
145- if ( ! audio . uk ) {
146- const mp3 = getAudio ( $section )
147- if ( mp3 ) {
148- audio . uk = mp3
149- }
175+ if ( ! audio . uk && mp3 ) {
176+ audio . uk = mp3
150177 }
151- } else if ( type === 'English' ) {
152- audio . uk = getAudio ( $section )
153- } else if ( type === 'American' ) {
154- audio . us = getAudio ( $section )
178+ } else if ( isAmericanSection ( rawType ) && mp3 ) {
179+ audio . us = mp3
180+ } else if ( mp3 && ( isEnglishSection ( rawType ) || ! audio . uk ) ) {
181+ audio . uk = mp3
155182 }
156183
157184 const $video = $section . querySelector < HTMLDivElement > ( '#videos .video' )
@@ -185,12 +212,12 @@ async function handleDOM(
185212 . forEach ( externalLink )
186213
187214 return {
188- id,
215+ id : id || String ( i ) ,
189216 className,
190217 type,
191218 title,
192219 num,
193- content : getInnerHTML ( 'https://www.collinsdictionary.com' , $section , {
220+ content : getInnerHTML ( HOST , $section , {
194221 transform
195222 } )
196223 }
@@ -203,6 +230,35 @@ async function handleDOM(
203230 return handleNoResult ( )
204231}
205232
233+ function getSectionNodes (
234+ doc : Document
235+ ) : Array < { meta : HTMLElement ; content : HTMLElement } > {
236+ const sections : Array < { meta : HTMLElement ; content : HTMLElement } > = [ ]
237+ const seen = new Set < HTMLElement > ( )
238+
239+ doc . querySelectorAll < HTMLElement > ( '[data-type-block]' ) . forEach ( meta => {
240+ const content = getSectionContentNode ( meta )
241+ if ( ! seen . has ( content ) ) {
242+ seen . add ( content )
243+ sections . push ( { meta, content } )
244+ }
245+ } )
246+
247+ return sections
248+ }
249+
250+ function getSectionContentNode ( $node : HTMLElement ) : HTMLElement {
251+ if ( ! $node . classList . contains ( 'cB-h' ) ) {
252+ return $node
253+ }
254+
255+ return (
256+ $node . closest < HTMLElement > (
257+ '.entry.dictionary.cB, .entry.cB, .asset, .cB'
258+ ) || $node
259+ )
260+ }
261+
206262function getAudio ( $section : HTMLElement ) : string | undefined {
207263 const $audio = $section . querySelector < HTMLAnchorElement > (
208264 '.pron .audio_play_button'
@@ -214,3 +270,99 @@ function getAudio($section: HTMLElement): string | undefined {
214270 }
215271 }
216272}
273+
274+ function shouldSkipSectionType ( type : string ) : boolean {
275+ const key = getSectionTypeKey ( type )
276+ const label = getCleanLabel ( type ) . toLowerCase ( )
277+
278+ return (
279+ SKIPPED_SECTION_KEYS . has ( key ) ||
280+ SKIPPED_SECTION_LABELS . has ( label ) ||
281+ / ^ t r a n s l a t i o n s ? (?: \s + o f \b ) ? / . test ( label )
282+ )
283+ }
284+
285+ function normalizeSectionType ( type : string ) : string {
286+ const key = getSectionTypeKey ( type )
287+ if ( SECTION_TYPE_LABELS [ key ] ) {
288+ return SECTION_TYPE_LABELS [ key ]
289+ }
290+
291+ if ( key !== type . toLowerCase ( ) ) {
292+ return key
293+ . split ( / [ _ \s - ] + / )
294+ . filter ( Boolean )
295+ . map ( word => word [ 0 ] . toUpperCase ( ) + word . slice ( 1 ) )
296+ . join ( ' ' )
297+ }
298+
299+ return type
300+ }
301+
302+ function getSectionTypeKey ( type : string ) : string {
303+ const lowerType = type . toLowerCase ( )
304+ return lowerType . startsWith ( COLLINS_TYPE_PREFIX )
305+ ? lowerType . slice ( COLLINS_TYPE_PREFIX . length )
306+ : lowerType
307+ }
308+
309+ function getSectionTitle (
310+ $meta : HTMLElement ,
311+ $section : HTMLElement ,
312+ type : string
313+ ) : string {
314+ const title = normalizeSectionTitle (
315+ getCleanLabel ( $meta . dataset . titleBlock ) ||
316+ getCleanLabel ( getTitleElement ( $meta ) ) ||
317+ getCleanLabel ( getTitleElement ( $section ) )
318+ )
319+
320+ return title && ! title . toLowerCase ( ) . startsWith ( type . toLowerCase ( ) )
321+ ? title
322+ : ''
323+ }
324+
325+ function getTitleElement ( $section : HTMLElement ) : Element | null {
326+ return $section . querySelector (
327+ [
328+ '.cB-h .entry_title' ,
329+ '.cB-h .h2_entry' ,
330+ '.entry_title' ,
331+ '.h2_entry' ,
332+ '.content-box-header .h2_entry' ,
333+ '.content-box-header h2'
334+ ] . join ( ',' )
335+ )
336+ }
337+
338+ function normalizeSectionTitle ( title : string ) : string {
339+ return getSectionTypeKey ( title ) !== title . toLowerCase ( )
340+ ? normalizeSectionType ( title )
341+ : title
342+ }
343+
344+ function getCleanLabel ( input ?: string | Element | null ) : string {
345+ const text =
346+ typeof input === 'string' ? input : input ? input . textContent || '' : ''
347+
348+ return text
349+ . replace ( / < [ ^ > ] * > / g, '' )
350+ . replace ( / \{ \d + \} / g, '' )
351+ . replace ( / \s + / g, ' ' )
352+ . trim ( )
353+ }
354+
355+ function isCobuildSection ( type : string ) : boolean {
356+ const key = getSectionTypeKey ( type )
357+ return key === 'cobuild' || key === 'learner' || type === 'Learner'
358+ }
359+
360+ function isEnglishSection ( type : string ) : boolean {
361+ const key = getSectionTypeKey ( type )
362+ return key === 'ced' || key === 'english' || type === 'English'
363+ }
364+
365+ function isAmericanSection ( type : string ) : boolean {
366+ const key = getSectionTypeKey ( type )
367+ return key === 'american' || key === 'aed' || type === 'American'
368+ }
0 commit comments