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