adq-doc-table.user.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. // ==UserScript==
  2. // @name GDT Table Transformer Kotlin
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description try to take over the world!
  6. // @author You
  7. // @match https://developers.e.qq.com/docs/*
  8. // @grant GM_setClipboard
  9. // @run-at document-start
  10. // ==/UserScript==
  11. (function() {
  12. 'use strict';
  13. const makeClassName = s => s?.replace(/(?:_|^)([a-z])/g, g => g.substring(g.length - 1).toUpperCase()) || ''
  14. const convertType = (type, name) => {
  15. const isList = type.endsWith('[]')
  16. if (isList) type = type.substring(0, type.length - 2)
  17. switch (type.toLowerCase()) {
  18. case 'float': type = 'Float'; break
  19. case 'double': type = 'Double'; break
  20. case 'number':
  21. case 'int':
  22. case 'int64':
  23. case 'long':
  24. case 'integer': type = 'Long'; break
  25. case 'boolean': type = 'Boolean'; break
  26. case 'string': type = 'String'; break
  27. case 'enum': type = makeClassName(name) || 'Enum'; break
  28. case 'struct':
  29. case 'object': type = makeClassName(name) || 'Object'; break
  30. }
  31. if (isList) type = `List<${type}>`
  32. return type
  33. }
  34. const parseName = (td) => {
  35. if (!td) return
  36. for (const node of td.querySelector('span.flex').childNodes) {
  37. if (node.nodeType === Node.TEXT_NODE) {
  38. const text = node.textContent.trim()
  39. if (text) return text.replace(/\n/g, '')
  40. }
  41. }
  42. }
  43. const parseRow = (tds) => {
  44. const name = parseName(tds[0])
  45. if (!name) return ''
  46. const optional = !!tds[0].querySelector('span.must')
  47. const type = tds[1].innerText.trim()
  48. const comment = tds[2].innerText.trim()
  49. return ` val ${name}: ${convertType(type, name)}${optional ? '? = null' : ''}, // ${comment.replace(/\n/g, ' ')}\n`
  50. }
  51. const parseEnumRow = (tds) => {
  52. const name = parseName(tds[0])
  53. if (!name) return ''
  54. let safeName = name.toUpperCase().replace(/\n/g, '').replace(/[^A-Z0-9_]/g, '_')
  55. if (!/^[A-Z]/.test(safeName)) safeName = `_${safeName}`
  56. const comment = tds[1].innerText.trim()
  57. return `${name === safeName ? '' : ` @SerializedName("${name}")\n`} ${safeName}, // ${comment.replace(/\n/g, ' ')}\n`
  58. }
  59. const parseTable = (table, level, parentLevel, result) => {
  60. const getRows = (lv) => table.querySelectorAll(`tbody > tr:has(td.${lv})`)
  61. const checkIsEnum = () => {
  62. const headers = table.querySelectorAll('thead td')
  63. if (headers.length !== 2) return false
  64. return headers[0].innerText?.trim() === '名称'
  65. }
  66. const isEnum = checkIsEnum()
  67. const getOuterTitle = () => {
  68. let title = 'Root'
  69. if (isEnum) {
  70. title = table.closest('div.colunm')?.querySelector('.colunm-tit')?.innerText
  71. } else {
  72. let node = table.closest('div.doc-intr').previousElementSibling
  73. while (node) {
  74. if (/^H\d$/.test(node.tagName)) {
  75. title = node.innerText.replace(/\s/g, '')
  76. break
  77. }
  78. node = node.previousElementSibling
  79. }
  80. }
  81. return title
  82. }
  83. const boundaries = parentLevel ? Array.from(getRows(parentLevel)).map(node => [makeClassName(parseName(node.querySelector('td'))), node]) : [[getOuterTitle(), null]] // [title as string, boundary as Node]
  84. const closingTag = isEnum ? '}' : ')'
  85. let content = ''
  86. const fields = []
  87. getRows(level).forEach(row => {
  88. const prevNode = row.previousSibling
  89. const boundary = boundaries.find(b => prevNode === b[1])
  90. if (boundary) {
  91. if (content) {
  92. // not first entity, close current item
  93. content += closingTag + '\n'
  94. }
  95. const title = boundary[0]
  96. content += isEnum ? `enum class ${title} {\n` : `data class ${title} (\n`
  97. }
  98. const name = parseName(row.querySelector('td')) // first column is name
  99. if (name) fields.push(name)
  100. content += (isEnum ? parseEnumRow : parseRow)(row.querySelectorAll('td'))
  101. })
  102. if (content) content += closingTag + '\n'
  103. GM_setClipboard(content)
  104. // For circumstance that need field name list
  105. content += fields.map(v => `'${v}'`).join(isEnum ? ' | ' : ', ') + '\n'
  106. result.innerText = content
  107. result.style.display = ''
  108. }
  109. const createButton = (table) => {
  110. if (table.dataset.done) return
  111. table.dataset.done = true
  112. if (!table.querySelector('thead')) return // not interested
  113. const levels = Array.from(table.querySelectorAll('tr>td:first-child')).reduce((s, row) => {
  114. if (row.className === '') {
  115. row.className = 'level-one'
  116. } else if (row.className?.startsWith('level-')) {
  117. s.add(row.className)
  118. }
  119. return s
  120. }, new Set(['level-one']))
  121. const container = document.createElement('div')
  122. const result = document.createElement('pre')
  123. result.style.display = 'none'
  124. const parent = table.parentNode
  125. parent.insertBefore(container, table)
  126. parent.insertBefore(result, table)
  127. const addBtn = (level, parentLevel) => {
  128. const btn = document.createElement('button')
  129. btn.append(document.createTextNode(level))
  130. btn.addEventListener('click', () => parseTable(table, level, parentLevel, result))
  131. container.append(btn)
  132. }
  133. Array.from(levels.values()).forEach((lv, i, a) => addBtn(lv, a[i - 1]))
  134. }
  135. document.addEventListener('DOMContentLoaded', () => {
  136. const ob = new MutationObserver(mutations => {
  137. mutations.forEach(({ target }) => {
  138. if (target?.tagName === 'ARTICLE') {
  139. console.log(target)
  140. target.querySelectorAll('table').forEach(createButton)
  141. }
  142. })
  143. })
  144. ob.observe(document.body, { subtree: true, childList: true })
  145. setTimeout(() => document.querySelectorAll('table').forEach(createButton), 3000)
  146. })
  147. })();