| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- // ==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)
- })
- })();
|