🏠 Home 

table-copier

适用于任意网站,快速复制表格为纯文本、HTML、图片


Installer dette script?
// ==UserScript==
// @name            table-copier
// @namespace       http://tampermonkey.net/
// @version         0.6
// @description     适用于任意网站,快速复制表格为纯文本、HTML、图片
// @match           *://*/*
// @require         https://cdn.staticfile.org/html2canvas/1.4.1/html2canvas.min.js
// @grant           none
// @run-at          document-idle
// @license         GPL-3.0-only
// @create          2023-06-27
// ==/UserScript==
(function() {
"use strict";
const SCRIPTS = [
["html2canvas", "https://cdn.staticfile.org/html2canvas/1.4.1/html2canvas.min.js"]
];
const BTN = `<button class="table-copier-btn" style="width: 70px; height: 30px;" onclick="copy_table(this)">复制表格</button>`;
const BOOT_DELAY = 2000;
/**
* 元素选择器
* @param {string} selector
* @returns {Array<HTMLElement>}
*/
function $(selector) {
const self = this?.querySelectorAll ? this : document;
return [...self.querySelectorAll(selector)];
}
/**
* 加载CDN脚本
* @param {string} url
*/
async function load_script(url) {
try {
const code = await (await fetch(url)).text();
Function(code)();
} catch(e) {
return new Promise(resolve => {
console.error(e);
// 嵌入<script>方式
const script = document.createElement("script");
script.src = url;
script.onload = resolve;
document.body.appendChild(script);
});
}
}
async function until_scripts_loaded() {
return gather(SCRIPTS.map(
// kv: [prop, url]
kv => (async () => {
if (window[kv[0]]) return;
await load_script(kv[1]);
})()
));
}
/**
* 等待全部任务落定后返回值的列表
* @param {Iterable<Promise>} tasks
* @returns {Promise<Array>} values
*/
async function gather(tasks) {
const r###lts = await Promise.allSettled(tasks);
const filtered = [];
for (const r###lt of r###lts) {
if (r###lt.value) {
filtered.push(r###lt.value);
}
}
return filtered;
}
/**
* 递归的修正表内元素
* @param {HTMLElement} elem
*/
function adjust_table(elem) {
for (const child of elem.children) {
adjust_table(child);
for (const attr of child.attributes) {
// 链接补全
const name = attr.name;
if (["src", "href"].includes(name)) {
child.setAttribute(name, child[name]);
}
}
}
}
/**
* canvas转blob
* @param {HTMLCanvasElement} canvas
* @param {string} type
* @returns {Promise<Blob>}
*/
function canvas_to_blob(canvas) {
return new Promise(
resolve => canvas.toBlob(resolve, "image/png")
);
}
/**
* @param {HTMLTableCellElement} cell
* @returns {string}
*/
function cell_to_text(cell) {
return cell
.textContent
.replace(/\n/g, " ")
.replace(/\t/g, "    ")
.trim();
// const children = cell.children;
// if (children.length === 0) {
//     return cell
//         .textContent
//         .replace(/\n/g, " ")
//         .replace(/\t/g, "    ")
//         .trim();
// }
// return [...children].map(
//     (child) => cell_to_text(child, depth + 1)
// ).join(" ".repeat(depth));
}
/**
* 表格转tsv字符串
* @param {HTMLTableElement} table
* @returns {string}
*/
function table_to_tsv(table) {
return [...table.rows].map((row) => {
return [...row.cells].map((cell) => {
return cell_to_text(cell);
}).join("\t")
}).join("\n");
}
/**
* 以包含变量名getter属性的对象输出到控制台,延迟计算
* @param {*} obj
*/
function log_as_getter(obj) {
const name = Object.getOwnPropertyNames(obj)[0];
const getter = {
get [name]() {
return obj[name];
}
};
console.log(name + ":", getter);
}
/**
* @param {HTMLTableElement} table
* @returns {Promise<Blob>}
*/
async function table_to_text_blob(table) {
console.log("table to text");
// table 转 tsv 格式文本
const text = table_to_tsv(table);
log_as_getter({ text });
return new Blob([text], { type: "text/plain" });
}
/**
* @param {HTMLTableElement} table
* @returns {Promise<Blob>}
*/
async function table_to_html_blob(table) {
console.log("table to html");
const _table = table.cloneNode(true);
adjust_table(_table);
return new Blob([_table.outerHTML], { type: "text/html" });
}
/**
* @param {HTMLTableElement} table
* @returns {Promise<Blob>}
*/
async function table_to_image_blob(table) {
console.log("table to image");
let canvas;
try {
canvas = await window.html2canvas(table);
} catch(e) {
console.error(e);
}
log_as_getter({ canvas });
if (!canvas) return;
return canvas_to_blob(canvas);
}
/**
* 使用过时的 execCommand 复制文本
* @param {string} text
* @returns {Promise<string>}
*/
async function copy_text_legacy(text) {
return new Promise(resolve => {
document.oncopy = event => {
event.clipboardData.setData("text/plain", text);
event.preventDefault();
resolve();
};
document.execCommand("copy");
});
}
/**
* @param {Array<Blob>} blobs
* @returns {Object}
*/
function merge_blobs_to_bundle(blobs) {
const bundle = Object.create(null);
for (const blob of blobs) {
bundle[blob.type] = blob;
}
return bundle;
}
/**
* @param {Array<Blob>} blobs
* @returns {Promise<void>}
*/
function copy(blobs) {
const bundle = merge_blobs_to_bundle(blobs);
const item = new ClipboardItem(bundle);
log_as_getter({ bundle });
log_as_getter({ item });
return navigator.clipboard.write([item]);
}
/**
* @param {HTMLTableElement} table
* @returns {Promise<void>}
*/
async function copy_table_as_multi_types(table) {
const converts = [
table_to_text_blob,
table_to_html_blob,
table_to_image_blob,
];
const blobs = await gather(converts.map(
convert => convert(table)
));
try {
await copy(blobs);
alert("复制成功!");
} catch(e) {
console.error(e);
alert("复制失败!");
}
}
/**
* @param {HTMLTableElement} table
* @returns {Promise<void>}
*/
async function copy_table_as_text_legacy(table) {
try {
await copy_text_legacy(table_to_tsv(table));
alert("复制成功!");
} catch(e) {
console.error(e);
alert("复制失败!");
}
}
/**
* 异步的复制表格到剪贴板
* @param {HTMLButtonElement} btn
* @param {boolean} ret_val 是否返回值(tsv)而不是复制到剪贴板(默认 false)
* @returns {Promise<null | string>}
*
*/
async function copy_table(btn, ret_val=false) {
const table = btn.closest("table");
if (!table) {
alert("出错了:按钮外部没有表格");
return;
}
if (ret_val) {
return table_to_tsv(table);
}
// 移除按钮
$(".table-copier-btn").forEach(
btn => btn.remove()
);
// 复制表格
if (!navigator.clipboard) {
await copy_table_as_text_legacy(table);
} else {
await copy_table_as_multi_types(table);
}
// 增加按钮
add_btns();
return null;
}
function add_btns() {
for (const table of $("table")) {
// 跳过隐藏的表格
if (!table.getClientRects()[0]) continue;
table.insertAdjacentHTML("afterbegin", BTN);
}
}
async function main() {
try {
await until_scripts_loaded();
} catch(e) {
console.error(e);
}
window.copy_table = copy_table;
add_btns();
// 递归的注入自身到iframe
$("iframe").forEach(iframe => {
try {
iframe.contentWindow.eval(main.toString());
} catch(e) {}
});
};
setTimeout(main, BOOT_DELAY);
})();