|
@@ -0,0 +1,151 @@
|
|
|
|
|
+// ==UserScript==
|
|
|
|
|
+// @name GDT Table Transformer Kotlin
|
|
|
|
|
+// @namespace http://tampermonkey.net/
|
|
|
|
|
+// @version 1.0
|
|
|
|
|
+// @description try to take over the world!
|
|
|
|
|
+// @author You
|
|
|
|
|
+// @match https://developers.e.qq.com/docs/*
|
|
|
|
|
+// @grant GM_setClipboard
|
|
|
|
|
+// @run-at document-start
|
|
|
|
|
+// ==/UserScript==
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+(function() {
|
|
|
|
|
+ 'use strict';
|
|
|
|
|
+ const makeClassName = s => s?.replace(/(?:_|^)([a-z])/g, g => g.substring(g.length - 1).toUpperCase()) || ''
|
|
|
|
|
+ const convertType = (type, name) => {
|
|
|
|
|
+ const isList = type.endsWith('[]')
|
|
|
|
|
+ if (isList) type = type.substring(0, type.length - 2)
|
|
|
|
|
+ switch (type.toLowerCase()) {
|
|
|
|
|
+ case 'float': type = 'Float'; break
|
|
|
|
|
+ case 'double': type = 'Double'; break
|
|
|
|
|
+ case 'number':
|
|
|
|
|
+ case 'int':
|
|
|
|
|
+ case 'int64':
|
|
|
|
|
+ case 'long':
|
|
|
|
|
+ case 'integer': type = 'Long'; break
|
|
|
|
|
+ case 'boolean': type = 'Boolean'; break
|
|
|
|
|
+ case 'string': type = 'String'; break
|
|
|
|
|
+ case 'enum': type = makeClassName(name) || 'Enum'; break
|
|
|
|
|
+ case 'struct':
|
|
|
|
|
+ case 'object': type = makeClassName(name) || 'Object'; break
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isList) type = `List<${type}>`
|
|
|
|
|
+ return type
|
|
|
|
|
+ }
|
|
|
|
|
+ const parseName = (td) => {
|
|
|
|
|
+ if (!td) return
|
|
|
|
|
+ for (const node of td.querySelector('span.flex').childNodes) {
|
|
|
|
|
+ if (node.nodeType === Node.TEXT_NODE) {
|
|
|
|
|
+ const text = node.textContent.trim()
|
|
|
|
|
+ if (text) return text.replace(/\n/g, '')
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ const parseRow = (tds) => {
|
|
|
|
|
+ const name = parseName(tds[0])
|
|
|
|
|
+ if (!name) return ''
|
|
|
|
|
+ const optional = !!tds[0].querySelector('span.must')
|
|
|
|
|
+ const type = tds[1].innerText.trim()
|
|
|
|
|
+ const comment = tds[2].innerText.trim()
|
|
|
|
|
+ return ` val ${name}: ${convertType(type, name)}${optional ? '? = null' : ''}, // ${comment.replace(/\n/g, ' ')}\n`
|
|
|
|
|
+ }
|
|
|
|
|
+ const parseEnumRow = (tds) => {
|
|
|
|
|
+ const name = parseName(tds[0])
|
|
|
|
|
+ if (!name) return ''
|
|
|
|
|
+ let safeName = name.toUpperCase().replace(/\n/g, '').replace(/[^A-Z0-9_]/g, '_')
|
|
|
|
|
+ if (!/^[A-Z]/.test(safeName)) safeName = `_${safeName}`
|
|
|
|
|
+ const comment = tds[1].innerText.trim()
|
|
|
|
|
+ return `${name === safeName ? '' : ` @SerializedName("${name}")\n`} ${safeName}, // ${comment.replace(/\n/g, ' ')}\n`
|
|
|
|
|
+ }
|
|
|
|
|
+ const parseTable = (table, level, parentLevel, result) => {
|
|
|
|
|
+ const getRows = (lv) => table.querySelectorAll(`tbody > tr:has(td.${lv})`)
|
|
|
|
|
+ const checkIsEnum = () => {
|
|
|
|
|
+ const headers = table.querySelectorAll('thead td')
|
|
|
|
|
+ if (headers.length !== 2) return false
|
|
|
|
|
+ return headers[0].innerText?.trim() === '名称'
|
|
|
|
|
+ }
|
|
|
|
|
+ const isEnum = checkIsEnum()
|
|
|
|
|
+ const getOuterTitle = () => {
|
|
|
|
|
+ let title = 'Root'
|
|
|
|
|
+ if (isEnum) {
|
|
|
|
|
+ title = table.closest('div.colunm')?.querySelector('.colunm-tit')?.innerText
|
|
|
|
|
+ } else {
|
|
|
|
|
+ let node = table.closest('div.doc-intr').previousElementSibling
|
|
|
|
|
+ while (node) {
|
|
|
|
|
+ if (/^H\d$/.test(node.tagName)) {
|
|
|
|
|
+ title = node.innerText.replace(/\s/g, '')
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ node = node.previousElementSibling
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return title
|
|
|
|
|
+ }
|
|
|
|
|
+ const boundaries = parentLevel ? Array.from(getRows(parentLevel)).map(node => [makeClassName(parseName(node.querySelector('td'))), node]) : [[getOuterTitle(), null]] // [title as string, boundary as Node]
|
|
|
|
|
+ const closingTag = isEnum ? '}' : ')'
|
|
|
|
|
+ let content = ''
|
|
|
|
|
+ const fields = []
|
|
|
|
|
+ getRows(level).forEach(row => {
|
|
|
|
|
+ const prevNode = row.previousSibling
|
|
|
|
|
+ const boundary = boundaries.find(b => prevNode === b[1])
|
|
|
|
|
+ if (boundary) {
|
|
|
|
|
+ if (content) {
|
|
|
|
|
+ // not first entity, close current item
|
|
|
|
|
+ content += closingTag + '\n'
|
|
|
|
|
+ }
|
|
|
|
|
+ const title = boundary[0]
|
|
|
|
|
+ content += isEnum ? `enum class ${title} {\n` : `data class ${title} (\n`
|
|
|
|
|
+ }
|
|
|
|
|
+ const name = parseName(row.querySelector('td')) // first column is name
|
|
|
|
|
+ if (name) fields.push(name)
|
|
|
|
|
+ content += (isEnum ? parseEnumRow : parseRow)(row.querySelectorAll('td'))
|
|
|
|
|
+ })
|
|
|
|
|
+ if (content) content += closingTag + '\n'
|
|
|
|
|
+ GM_setClipboard(content)
|
|
|
|
|
+ // For circumstance that need field name list
|
|
|
|
|
+ content += fields.map(v => `'${v}'`).join(isEnum ? ' | ' : ', ') + '\n'
|
|
|
|
|
+ result.innerText = content
|
|
|
|
|
+ result.style.display = ''
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const createButton = (table) => {
|
|
|
|
|
+ if (table.dataset.done) return
|
|
|
|
|
+ table.dataset.done = true
|
|
|
|
|
+ if (!table.querySelector('thead')) return // not interested
|
|
|
|
|
+ const levels = Array.from(table.querySelectorAll('tr>td:first-child')).reduce((s, row) => {
|
|
|
|
|
+ if (row.className === '') {
|
|
|
|
|
+ row.className = 'level-one'
|
|
|
|
|
+ } else if (row.className?.startsWith('level-')) {
|
|
|
|
|
+ s.add(row.className)
|
|
|
|
|
+ }
|
|
|
|
|
+ return s
|
|
|
|
|
+ }, new Set(['level-one']))
|
|
|
|
|
+ const container = document.createElement('div')
|
|
|
|
|
+ const result = document.createElement('pre')
|
|
|
|
|
+ result.style.display = 'none'
|
|
|
|
|
+ const parent = table.parentNode
|
|
|
|
|
+ parent.insertBefore(container, table)
|
|
|
|
|
+ parent.insertBefore(result, table)
|
|
|
|
|
+ const addBtn = (level, parentLevel) => {
|
|
|
|
|
+ const btn = document.createElement('button')
|
|
|
|
|
+ btn.append(document.createTextNode(level))
|
|
|
|
|
+ btn.addEventListener('click', () => parseTable(table, level, parentLevel, result))
|
|
|
|
|
+ container.append(btn)
|
|
|
|
|
+ }
|
|
|
|
|
+ Array.from(levels.values()).forEach((lv, i, a) => addBtn(lv, a[i - 1]))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
+ const ob = new MutationObserver(mutations => {
|
|
|
|
|
+ mutations.forEach(({ target }) => {
|
|
|
|
|
+ if (target?.tagName === 'ARTICLE') {
|
|
|
|
|
+ console.log(target)
|
|
|
|
|
+ target.querySelectorAll('table').forEach(createButton)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ ob.observe(document.body, { subtree: true, childList: true })
|
|
|
|
|
+ setTimeout(() => document.querySelectorAll('table').forEach(createButton), 3000)
|
|
|
|
|
+ })
|
|
|
|
|
+})();
|