ocean-engine-doc-maker.user.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. // ==UserScript==
  2. // @name OceanEngine Table Transformer Kotlin
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0
  5. // @description try to take over the world!
  6. // @author You
  7. // @match https://open.oceanengine.com/labels/7/docs/*
  8. // @grant GM_setClipboard
  9. // @run-at document-start
  10. // ==/UserScript==
  11. (function() {
  12. 'use strict';
  13. const convertType = (type) => {
  14. const isList = type.endsWith('[]')
  15. if (isList) type = type.substring(0, type.length - 2)
  16. switch (type.toLowerCase()) {
  17. case 'float': type = 'Float'; break
  18. case 'double': type = 'Double'; break
  19. case 'number':
  20. case 'int':
  21. case 'long':
  22. case 'integer': type = 'Long'; break
  23. case 'boolean': type = 'Boolean'; break
  24. case 'string': type = 'String'; break
  25. case 'json':
  26. case 'object': type = 'Object'; break
  27. }
  28. if (isList) type = `List<${type}>`
  29. return type
  30. }
  31. const parseName = (childNodes) => {
  32. if (!childNodes) return
  33. for (const node of childNodes) {
  34. if (node.nodeType === Node.TEXT_NODE) {
  35. const text = node.textContent.trim()
  36. if (text) return text
  37. }
  38. }
  39. }
  40. const parseRow = (tds) => {
  41. const name = parseName(tds[0].querySelector('div')?.childNodes)
  42. if (!name) return ''
  43. const optional = tds[0].querySelector('span.tag')?.innerText?.trim() !== '必填'
  44. const type = tds[1].innerText.trim()
  45. const comment = tds[2].innerText.trim()
  46. return ` val ${name.replace(/\n/g, '')}: ${convertType(type)}${optional ? '? = null' : ''}, // ${comment.replace(/\n/g, ' ')}\n`
  47. }
  48. const parseEnumRow = (tds) => {
  49. const name = parseName(tds[0].querySelector('div')?.childNodes)
  50. if (!name) return ''
  51. let safeName = name.toUpperCase().replace(/\n/g, '').replace(/[^A-Z0-9_]/g, '_')
  52. if (!/^[A-Z]/.test(safeName)) safeName = `_${safeName}`
  53. const comment = tds[1].innerText.trim()
  54. return `${name === safeName ? '' : ` @SerializedName("${name}")\n`} ${safeName}, // ${comment.replace(/\n/g, ' ')}\n`
  55. }
  56. const parseTable = (table, selector, parentSelector, result) => {
  57. const getOuterTitle = () => {
  58. let node = table.previousElementSibling
  59. let title = 'Root'
  60. while (node) {
  61. if (/^H\d$/.test(node.tagName)) {
  62. title = node.innerText.replace(/\s/g, '')
  63. break
  64. }
  65. node = node.previousElementSibling
  66. }
  67. return title
  68. }
  69. const boundaries = parentSelector ? Array.from(table.querySelectorAll(parentSelector)).map(node => [parseName(node.querySelector('div.tdData').childNodes)?.replace(/(?:_|^)([a-z])/g, g => g.substring(g.length - 1).toUpperCase()), node]) : [[getOuterTitle(), null]] // [title as string, boundary as Node]
  70. console.log(boundaries)
  71. const isEnum = table.querySelector('th')?.innerText?.trim() === '值'
  72. const closingTag = isEnum ? '}' : ')'
  73. let content = ''
  74. table.querySelectorAll(selector).forEach(row => {
  75. const prevNode = row.previousSibling
  76. const boundary = boundaries.find(b => prevNode === b[1])
  77. if (boundary) {
  78. if (content) {
  79. // not first entity, close current item
  80. content += closingTag + '\n'
  81. }
  82. const title = boundary[0]
  83. content += isEnum ? `enum class ${title} {\n` : `data class ${title} (\n`
  84. }
  85. content += (isEnum ? parseEnumRow : parseRow)(row.querySelectorAll('td'))
  86. })
  87. if (content) content += closingTag
  88. result.innerText = content
  89. result.style.display = ''
  90. GM_setClipboard(result.innerText)
  91. }
  92. const ENUM_HINT = ['允许值', '枚举值']
  93. const hasEnumHit = s => ENUM_HINT.some(hint => s?.includes?.(hint))
  94. const processEnumerationNode = (node) => {
  95. if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'CODE') {
  96. if (node.previousSibling?.tagName !== 'BR') node.parentNode.insertBefore(document.createElement('br'), node)
  97. node.innerText = node.innerText.replace(/["'\s]/g, '')
  98. } else if (node.nodeType === Node.TEXT_NODE && node.previousSibling?.tagName === 'CODE') {
  99. const comment = node.textContent.trim().replace(/、|,|,$/g, '')
  100. node.textContent = comment ? `,\u00A0//\u00A0${comment}` : ','
  101. }
  102. }
  103. const formatEnumerations = (table) => {
  104. table.querySelectorAll('tr > td:nth-child(3) > div').forEach(div => {
  105. if (hasEnumHit(div.innerText)) {
  106. // 提示模式 允许值:blahblah
  107. let started = false
  108. let node = div.childNodes[0]
  109. while (node) {
  110. if (started) {
  111. processEnumerationNode(node)
  112. } else if (node.nodeType === Node.TEXT_NODE && hasEnumHit(node.textContent)) {
  113. started = true
  114. }
  115. node = node.nextSibling
  116. }
  117. } else {
  118. // 行首模式
  119. let node = div.querySelector('br + code')
  120. if (node?.previousSibling?.tagName !== 'BR') return
  121. while (node) {
  122. processEnumerationNode(node)
  123. node = node.nextSibling
  124. }
  125. }
  126. })
  127. }
  128. const createButton = (table) => {
  129. if (table.dataset.done) return
  130. table.dataset.done = true
  131. formatEnumerations(table)
  132. const levels = Array.from(table.querySelectorAll('tr')).reduce((s, row) => {
  133. if (row.className) s.add(row.className)
  134. return s
  135. }, new Set())
  136. const container = document.createElement('div')
  137. const result = document.createElement('pre')
  138. result.style.display = 'none'
  139. table.parentNode.insertBefore(container, table)
  140. table.parentNode.insertBefore(result, table)
  141. const addBtn = (level, parentLevel) => {
  142. const btn = document.createElement('button')
  143. btn.append(document.createTextNode(level))
  144. btn.addEventListener('click', () => parseTable(table, `tbody > tr.${level}`, parentLevel ? `tbody > tr.${parentLevel}` : undefined, result))
  145. container.append(btn)
  146. }
  147. Array.from(levels.values()).forEach((lv, i, a) => addBtn(lv, a[i - 1]))
  148. }
  149. const ob = new MutationObserver(mutations => {
  150. mutations.forEach(({ target }) => {
  151. if (target?.tagName === 'DIV' && target?.classList?.contains('doc-content-body')) {
  152. target.querySelectorAll('table').forEach(createButton)
  153. }
  154. })
  155. })
  156. ob.observe(document.body, { subtree: true, childList: true })
  157. setTimeout(() => document.querySelectorAll('table').forEach(createButton), 3000)
  158. })();