Javascript 修改从显示空白的网页复制文本的方式

Javascript 修改从显示空白的网页复制文本的方式,javascript,html,typescript,parsing,clipboard,Javascript,Html,Typescript,Parsing,Clipboard,我一直在致力于创建一个HTML解析器和格式化程序,我刚刚添加了一个功能,通过将空格替换为中间点字符、为制表符和换行符添加箭头等,可以选择显示空白 完整的正在进行的源代码在这里:,对HTML进行CSS样式化的最相关文件在这里: 虽然在需要时能够可视化空白是很好的,但如果选择并复制文本,则不希望将空白变成中间点字符。但事实就是如此,至少在没有任何干预的情况下 我已经找到了一个简单的方法,用一点JavaScript来解决这个问题,我在这里把它放进了一个代码笔: 然而,我想知道是否有更好的方法来做到这一

我一直在致力于创建一个HTML解析器和格式化程序,我刚刚添加了一个功能,通过将空格替换为中间点字符、为制表符和换行符添加箭头等,可以选择显示空白

完整的正在进行的源代码在这里:,对HTML进行CSS样式化的最相关文件在这里:

虽然在需要时能够可视化空白是很好的,但如果选择并复制文本,则不希望将空白变成中间点字符。但事实就是如此,至少在没有任何干预的情况下

我已经找到了一个简单的方法,用一点JavaScript来解决这个问题,我在这里把它放进了一个代码笔:

然而,我想知道是否有更好的方法来做到这一点

我的第一个选择是完全不依赖JavaScript的东西,比如,如果有某种方式通过CSS或者一些与可访问性相关的HTML属性,本质上说,这是应该复制的真实文本,而不是你在屏幕上看到的文本

我的第二个选择是,如果有人能向我指出JavaScript剪贴板功能的详细文档,而不是我所能找到的,因为如果我必须依赖JavaScript,我至少希望我的JavaScript更智能。快速而肮脏的解决方案将每个中间点字符都转换为一个空格,即使它最初确实应该是一个中间点


剪贴板对象中是否有足够的信息来确定所选文本中的哪一个具有CSS样式,这样我就可以知道仅转换包含我的空白类的s中的文本,并且还可以按适当的顺序找到其余的非空白文本,要将它们重新组合起来?

我仍然找不到关于选择对象如何工作的很多文档,但我在web控制台中对它们进行了研究,最终找到了足够的解决方法

这是我提出的JavaScript:

function restoreWhitespaceStrict(s) {
  return s.replace(/·|[\u2400-\u241F]|\S/g, ch => ch === '·' ? ' ' :
           ch.charCodeAt(0) >= 0x2400 ? String.fromCharCode(ch.charCodeAt(0) - 0x2400) : '');
}

const wsReplacements = {
  '·': ' ',
  '→\t': '\t',
  '↵\n': '\n',
  '␍\r': '\r',
  '␍↵\r\n': '\r\n'
}

function restoreWhitespace(s) {
  return s.replace(/·|→\t|↵\n|␍\r|␍↵\r\n|→|↵|␍|[\u2400-\u241F]/g, ws =>
    wsReplacements[ws] || (ws.charCodeAt(0) >= 0x2400 ? String.fromCharCode(ws.charCodeAt(0) - 0x2400) : ''));
}

document.body.addEventListener('copy', (event) => {
  const selection = document.getSelection();
  let newSelection;
  let copied = false;

  if (selection.anchorNode && selection.getRangeAt) {
    try {
      const nodes = selection.getRangeAt(0).cloneContents().childNodes;
      let parts = [];

      // nodes isn't a "real" array - no forEach!
      for (let i = 0; i < nodes.length; ++i) {
        const node = nodes[i];

        if (node.classList && node.classList.contains('whitespace'))
          parts.push(restoreWhitespaceStrict(node.innerText));
        else if (node.localName === 'span')
          parts.push(node.innerText);
        else
          parts.push(node.nodeValue);
      }

      newSelection = parts.join('');
      copied = true;
    }
    catch (err) {}
  }

  if (!copied)
    newSelection = restoreWhitespace(selection.toString());

  event.clipboardData.setData('text/plain', newSelection);
  event.preventDefault();
});
我在Chrome、Firefox和Safari三种浏览器上都试过了,现在都可以用了,但我还是采取了预防措施,测试了一些预期的对象属性,然后还使用了try/catch,以防我碰到不兼容的浏览器,在这种情况下,不太聪明的修复剪贴板的版本将接管


看起来选择是作为常规DOM节点列表处理的。Chrome的选择对象有一个anchorNode和一个extentNode来标记选择的开始和结束,但是Firefox只有一个anchorNode,我没有检查Safari的extentNode。但是,我找不到任何方法直接获取节点的完整列表。我只能使用cloneContents方法获取完整列表。以这种方式获得的第一个和最后一个节点通过限制在每个节点中选择的文本内容部分而与原始的开始和结束节点不同。

由于您已经添加了样式,似乎您可以通过将每个空格包装在一个跨距中来更进一步,并在内容设置为中间点的跨距上添加一个:after,并添加额外的CSS以覆盖空间。CSS内容不会复制,但空间会复制。这听起来确实可行,但哇,这可能会有很多额外的标记,因为目前我将空白组合成一个跨度。不过,它不需要任何JavaScript,这是一个很好的优点。不过,我刚刚编写了JavaScript!:
function restoreWhitespaceStrict(s) {
  return s.replace(/·|[\u2400-\u241F]|\S/g, ch => ch === '·' ? ' ' :
           ch.charCodeAt(0) >= 0x2400 ? String.fromCharCode(ch.charCodeAt(0) - 0x2400) : '');
}

const wsReplacements = {
  '·': ' ',
  '→\t': '\t',
  '↵\n': '\n',
  '␍\r': '\r',
  '␍↵\r\n': '\r\n'
}

function restoreWhitespace(s) {
  return s.replace(/·|→\t|↵\n|␍\r|␍↵\r\n|→|↵|␍|[\u2400-\u241F]/g, ws =>
    wsReplacements[ws] || (ws.charCodeAt(0) >= 0x2400 ? String.fromCharCode(ws.charCodeAt(0) - 0x2400) : ''));
}

document.body.addEventListener('copy', (event) => {
  const selection = document.getSelection();
  let newSelection;
  let copied = false;

  if (selection.anchorNode && selection.getRangeAt) {
    try {
      const nodes = selection.getRangeAt(0).cloneContents().childNodes;
      let parts = [];

      // nodes isn't a "real" array - no forEach!
      for (let i = 0; i < nodes.length; ++i) {
        const node = nodes[i];

        if (node.classList && node.classList.contains('whitespace'))
          parts.push(restoreWhitespaceStrict(node.innerText));
        else if (node.localName === 'span')
          parts.push(node.innerText);
        else
          parts.push(node.nodeValue);
      }

      newSelection = parts.join('');
      copied = true;
    }
    catch (err) {}
  }

  if (!copied)
    newSelection = restoreWhitespace(selection.toString());

  event.clipboardData.setData('text/plain', newSelection);
  event.preventDefault();
});