天道酬勤,学无止境

How to wrap part of a text in a node with JavaScript

I have a challenging problem to solve. I'm working on a script which takes a regex as an input. This script then finds all matches for this regex in a document and wraps each match in its own <span> element. The hard part is that the text is a formatted html document, so my script needs to navigate through the DOM and apply the regex across multiple text nodes at once, while figuring out where it has to split text nodes if needed.

For example, with a regex that captures full sentences starting with a capital letter and ending with a period, this document:

<p>
  <b>HTML</b> is a language used to make <b>websites.</b>
  It was developed by <i>CERN</i> employees in the early 90s.
<p>

Would be turned into this:

<p>
  <span><b>HTML</b> is a language used to make <b>websites.</b></span>
  <span>It was developed by <i>CERN</i> employees in the early 90s.</span>
<p>

The script then returns the list of all created spans.

I already have some code which finds all the text nodes and stores them in a list along with their position across the whole document and their depth. You don't really need to understand that code to help me and its recursive structure can be a bit confusing. The first part I'm not sure how to do is figure out which elements should be included within the span.

function SmartNode(node, depth, start) {
  this.node = node;
  this.depth = depth;
  this.start = start;
}


function findTextNodes(node, depth, start) {
  var list = [];
  var start = start || 0;
  depth = (typeof depth !== "undefined" ? depth : -1);

  if(node.nodeType === Node.TEXT_NODE) {
    list.push(new SmartNode(node, depth, start));
  } else {
    for(var i=0; i < node.childNodes.length; ++i) {
      list = list.concat(findTextNodes(node.childNodes[i], depth+1, start));
      if(list.length) start += list[list.length-1].node.nodeValue.length;
    }
  }

  return list;
}

I figure I'll make a string out of all the document, run the regex through it and use the list to find which nodes correspond to witch regex matches and then split the text nodes accordingly.

But an issue arrives when I have a document like this:

<p>
  This program is <a href="beta.html">not stable yet. Do not use this in production yet.</a>
</p>

There's a sentence which starts outside of the <a> tag but ends inside it. Now I don't want the script to split that link in two tags. In a more complex document, it could ruin the page if it did. The code could either wrap two sentences together:

<p>
  <span>This program is <a href="beta.html">not stable yet. Do not use this in production yet.</a></span>
</p>

Or just wrap each part in its own element:

<p>
  <span>This program is </span>
  <a href="beta.html">
    <span>not stable yet.</span>
    <span>Do not use this in production yet.</span>
  </a>
</p>

There could be a parameter to specify what it should do. I'm just not sure how to figure out when an impossible cut is about to happen, and how to recover from it.

Another issue comes when I have whitespace inside a child element like this:

<p>This is a <b>sentence. </b></p>

Technically, the regex match would end right after the period, before the end of the <b> tag. However, it would be much better to consider the space as part of the match and wrap it like this:

<p><span>This is a <b>sentence. </b></span></p>

Than this:

<p><span>This is a </span><b><span>sentence.</span> </b></p>

But that's a minor issue. After all, I could just allow extra white-space to be included within the regex.

I know this might sound like a "do it for me" question and its not the kind of quick question we see on SO on a daily basis, but I've been stuck on this for a while and it's for an open-source library I'm working on. Solving this problem is the last obstacle. If you think another SE site is best suited for this question, redirect me please.

评论

Here are two ways to deal with this.

I don't know if the following will exactly match your needs. It's a simple enough solution to the problem, but at least it doesn't use RegEx to manipulate HTML tags. It performs pattern matching against the raw text and then uses the DOM to manipulate the content.


First approach

This approach creates only one <span> tag per match, leveraging some less common browser APIs.
(See the main problem of this approach below the demo, and if not sure, use the second approach).

The Range class represents a text fragment. It has a surroundContents function that lets you wrap a range in an element. Except it has a caveat:

This method is nearly equivalent to newNode.appendChild(range.extractContents()); range.insertNode(newNode). After surrounding, the boundary points of the range include newNode.

An exception will be thrown, however, if the Range splits a non-Text node with only one of its boundary points. That is, unlike the alternative above, if there are partially selected nodes, they will not be cloned and instead the operation will fail.

Well, the workaround is provided in the MDN, so all's good.

So here's an algorithm:

  • Make a list of Text nodes and keep their start indices in the text
  • Concatenate these nodes' values to get the text
  • Find matches over the text, and for each match:

    • Find the start and end nodes of the match, comparing the the nodes' start indices to the match position
    • Create a Range over the match
    • Let the browser do the dirty work using the trick above
    • Rebuild the node list since the last action changed the DOM

Here's my implementation with a demo:

function highlight(element, regex) {
    var document = element.ownerDocument;
    
    var getNodes = function() {
        var nodes = [],
            offset = 0,
            node,
            nodeIterator = document.createNodeIterator(element, NodeFilter.SHOW_TEXT, null, false);
            
        while (node = nodeIterator.nextNode()) {
            nodes.push({
                textNode: node,
                start: offset,
                length: node.nodeValue.length
            });
            offset += node.nodeValue.length
        }
        return nodes;
    }
    
    var nodes = getNodes(nodes);
    if (!nodes.length)
        return;
    
    var text = "";
    for (var i = 0; i < nodes.length; ++i)
        text += nodes[i].textNode.nodeValue;

    var match;
    while (match = regex.exec(text)) {
        // Prevent empty matches causing infinite loops        
        if (!match[0].length)
        {
            regex.lastIndex++;
            continue;
        }
        
        // Find the start and end text node
        var startNode = null, endNode = null;
        for (i = 0; i < nodes.length; ++i) {
            var node = nodes[i];
            
            if (node.start + node.length <= match.index)
                continue;
            
            if (!startNode)
                startNode = node;
            
            if (node.start + node.length >= match.index + match[0].length)
            {
                endNode = node;
                break;
            }
        }
        
        var range = document.createRange();
        range.setStart(startNode.textNode, match.index - startNode.start);
        range.setEnd(endNode.textNode, match.index + match[0].length - endNode.start);
        
        var spanNode = document.createElement("span");
        spanNode.className = "highlight";

        spanNode.appendChild(range.extractContents());
        range.insertNode(spanNode);
        
        nodes = getNodes();
    }
}

// Test code
var testDiv = document.getElementById("test-cases");
var originalHtml = testDiv.innerHTML;
function test() {
    testDiv.innerHTML = originalHtml;
    try {
        var regex = new RegExp(document.getElementById("regex").value, "g");
        highlight(testDiv, regex);
    }
    catch(e) {
        testDiv.innerText = e;
    }
}
document.getElementById("runBtn").onclick = test;
test();
.highlight {
  background-color: yellow;
  border: 1px solid orange;
  border-radius: 5px;
}

.section {
  border: 1px solid gray;
  padding: 10px;
  margin: 10px;
}
<form class="section">
  RegEx: <input id="regex" type="text" value="[A-Z].*?\." /> <button id="runBtn">Highlight</button>
</form>

<div id="test-cases" class="section">
  <div>foo bar baz</div>
  <p>
    <b>HTML</b> is a language used to make <b>websites.</b>
	It was developed by <i>CERN</i> employees in the early 90s.
  <p>
  <p>
    This program is <a href="beta.html">not stable yet. Do not use this in production yet.</a>
  </p>
  <div>foo bar baz</div>
</div>

Ok, that was the lazy approach which, unfortunately doesn't work for some cases. It works well if you only highlight across inline elements, but breaks when there are block elements along the way because of the following property of the extractContents function:

Partially selected nodes are cloned to include the parent tags necessary to make the document fragment valid.

That's bad. It'll just duplicate block-level nodes. Try the previous demo with the baz\s+HTML regex if you want to see how it breaks.


Second approach

This approach iterates over the matching nodes, creating <span> tags along the way.

The overall algorithm is straightforward as it just wraps each matching node in its own <span>. But this means we have to deal with partially matching text nodes, which requires some more effort.

If a text node matches partially, it's split with the splitText function:

After the split, the current node contains all the content up to the specified offset point, and a newly created node of the same type contains the remaining text. The newly created node is returned to the caller.

function highlight(element, regex) {
    var document = element.ownerDocument;
    
    var nodes = [],
        text = "",
        node,
        nodeIterator = document.createNodeIterator(element, NodeFilter.SHOW_TEXT, null, false);
        
    while (node = nodeIterator.nextNode()) {
        nodes.push({
            textNode: node,
            start: text.length
        });
        text += node.nodeValue
    }
    
    if (!nodes.length)
        return;

    var match;
    while (match = regex.exec(text)) {
        var matchLength = match[0].length;
        
        // Prevent empty matches causing infinite loops        
        if (!matchLength)
        {
            regex.lastIndex++;
            continue;
        }
        
        for (var i = 0; i < nodes.length; ++i) {
            node = nodes[i];
            var nodeLength = node.textNode.nodeValue.length;
            
            // Skip nodes before the match
            if (node.start + nodeLength <= match.index)
                continue;
        
            // Break after the match
            if (node.start >= match.index + matchLength)
                break;
            
            // Split the start node if required
            if (node.start < match.index) {
                nodes.splice(i + 1, 0, {
                    textNode: node.textNode.splitText(match.index - node.start),
                    start: match.index
                });
                continue;
            }
            
            // Split the end node if required
            if (node.start + nodeLength > match.index + matchLength) {
                nodes.splice(i + 1, 0, {
                    textNode: node.textNode.splitText(match.index + matchLength - node.start),
                    start: match.index + matchLength
                });
            }
            
            // Highlight the current node
            var spanNode = document.createElement("span");
            spanNode.className = "highlight";
            
            node.textNode.parentNode.replaceChild(spanNode, node.textNode);
            spanNode.appendChild(node.textNode);
        }
    }
}

// Test code
var testDiv = document.getElementById("test-cases");
var originalHtml = testDiv.innerHTML;
function test() {
    testDiv.innerHTML = originalHtml;
    try {
        var regex = new RegExp(document.getElementById("regex").value, "g");
        highlight(testDiv, regex);
    }
    catch(e) {
        testDiv.innerText = e;
    }
}
document.getElementById("runBtn").onclick = test;
test();
.highlight {
  background-color: yellow;
}

.section {
  border: 1px solid gray;
  padding: 10px;
  margin: 10px;
}
<form class="section">
  RegEx: <input id="regex" type="text" value="[A-Z].*?\." /> <button id="runBtn">Highlight</button>
</form>

<div id="test-cases" class="section">
  <div>foo bar baz</div>
  <p>
    <b>HTML</b> is a language used to make <b>websites.</b>
	It was developed by <i>CERN</i> employees in the early 90s.
  <p>
  <p>
    This program is <a href="beta.html">not stable yet. Do not use this in production yet.</a>
  </p>
  <div>foo bar baz</div>
</div>

This should be good enough for most cases I hope. If you need to minimize the number of <span> tags it can be done by extending this function, but I wanted to keep it simple for now.

function parseText( element ){
  var stack = [ element ];
  var group = false;
  var re = /(?!\s|$).*?(\.|$)/;
  while ( stack.length > 0 ){
    var node = stack.shift();
    if ( node.nodeType === Node.TEXT_NODE )
    {
      if ( node.textContent.trim() != "" )
      {
        var match;
        while( node && (match = re.exec( node.textContent )) )
        {
          var start  = group ? 0 : match.index;
          var length = match[0].length + match.index - start;
          if ( start > 0 )
          {
            node = node.splitText( start );
          }
          var wrapper = document.createElement( 'span' );
          var next    = null;
          if ( match[1].length > 0 ){
            if ( node.textContent.length > length )
              next = node.splitText( length );
            group = false;
            wrapper.className = "sentence sentence-end";
          }
          else
          {
            wrapper.className = "sentence";
            group = true;
          }
          var parent  = node.parentNode;
          var sibling = node.nextSibling;
          wrapper.appendChild( node );
          if ( sibling )
            parent.insertBefore( wrapper, sibling );
          else
            parent.appendChild( wrapper );
          node = next;
        }
      }
    }
    else if ( node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE )
    {
      stack.unshift.apply( stack, node.childNodes );
    }
  }
}

parseText( document.body );
.sentence {
  text-decoration: underline wavy red;
}

.sentence-end {
  border-right: 1px solid red;
}
<p>This is a sentence. This is another sentence.</p>
<p>This sentence has <strong>emphasis</strong> inside it.</p>
<p><span>This sentence spans</span><span> two elements.</span></p>

I would use "flat DOM" representation for such task.

In flat DOM this paragraph

<p>abc <a href="beta.html">def. ghij.</p>

will be represented by two vectors:

chars: "abc def. ghij.",
props:  ....aaaaaaaaaa, 

You will use normal regexp on chars to mark span areas on props vector:

chars: "abc def. ghij."
props:  ssssaaaaaaaaaa  
            ssss sssss

I am using schematic representation here, it's real structure is an array of arrays:

props: [
  [s],
  [s],
  [s],
  [s],
  [a,s],
  [a,s],
  ...
]

conversion tree-DOM <-> flat-DOM can use simple state automata.

At the end you will convert flat DOM to tree DOM that will look like:

<p><s>abc </s><a href="beta.html"><s>def.</s> <s>ghij.</s></p>

Just in case: I am using this approach in my HTML WYSIWYG editors.

As everyone has already said, this is more of an academic question since this shouldn't really be the way you do it. That being said, it seemed like fun so here's one approach.

EDIT: I think I got the gist of it now.

function myReplace(str) {
  myRegexp = /((^<[^>*]>)+|([^<>\.]*|(<[^\/>]*>[^<>\.]+<\/[^>]*>)+)*[^<>\.]*\.\s*|<[^>]*>|[^\.<>]+\.*\s*)/g; 
  arr = str.match(myRegexp);
  var out = "";
  for (i in arr) {
var node = arr[i];
if (node.indexOf("<")===0) out += node;
else out += "<span>"+node+"</span>"; // Here is where you would run whichever 
                                     // regex you want to match by
  }
  document.write(out.replace(/</g, "&lt;").replace(/>/g, "&gt;")+"<br>");
  console.log(out);
}

myReplace('<p>This program is <a href="beta.html">not stable yet. Do not use this in production yet.</a></p>');
myReplace('<p>This is a <b>sentence. </b></p>');
myReplace('<p>This is a <b>another</b> and <i>more complex</i> even <b>super complex</b> sentence.</p>');
myReplace('<p>This is a <b>a sentence</b>. Followed <i>by</i> another one.</p>');
myReplace('<p>This is a <b>an even</b> more <i>complex sentence. </i></p>');

/* Will output:
<p><span>This program is </span><a href="beta.html"><span>not stable yet. </span><span>Do not use this in production yet.</span></a></p>
<p><span>This is a </span><b><span>sentence. </span></b></p>
<p><span>This is a <b>another</b> and <i>more complex</i> even <b>super complex</b> sentence.</span></p>
<p><span>This is a <b>a sentence</b>. </span><span>Followed <i>by</i> another one.</span></p>
<p><span>This is a </span><b><span>an even</span></b><span> more </span><i><span>complex sentence. </span></i></p>
*/

I have spent a long time implementing all of approaches given in this thread.

  1. Node iterator
  2. Html parsing
  3. Flat Dom

For any of this approaches you have to come up with technique to split entire html into sentences and wrap into span (some might want words in span). As soon as we do this we will run into performance issues (I should say beginner like me will run into performance issues).

Performance Bottleneck

I couldn't scale any of this approach to 70k - 200k words and still do it in milli seconds. Wrapping time keeps increasing as words in pages keep increasing.

With complex html pages with combinations of text-node and different elements we soon run into trouble and with this technical debt keeps increasing.

Best approach : Mark.js (according to me)

Note: if you do this right you can process any number of words in millis.

Just use Ranges I want to recommend Mark.js and following example,

var instance = new Mark(document.body);
instance.markRanges([{
    start: 15,
    length: 5
}, {
    start: 25:
    length: 8
}]); /

With this we can treat entire body.textContent as string and just keep highlighting substring.

No DOM structure is modified here. And you can easily fix complex use cases and technical debt doesn't increase with more if and else.

Additionally once text is highlighted with html5 mark tag you can post process these tags to find out bounding rectangles.

Also look into Splitting.js if you just want split html documents into words/chars/lines and many more... But one draw back for this approach is that Splitting.js collapses additional spaces in the document so we loose little bit of info.

Thanks.

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐
  • 可以使用innerHTML插入脚本吗?(Can scripts be inserted with innerHTML?)
    问题 我试图在<div>上使用innerHTML将一些脚本加载到页面中。 脚本似乎已加载到DOM中,但从未执行(至少在Firefox和Chrome中)。 使用innerHTML插入脚本时,是否可以执行脚本? 样例代码: <!DOCTYPE html> <html> <body onload="document.getElementById('loader').innerHTML = '<script>alert(\'hi\')<\/script>'"> Shouldn't an alert saying 'hi' appear? <div id="loader"></div> </body> </html> 回答1 您必须使用eval()来执行作为DOM文本插入的任何脚本代码。 MooTools会自动为您执行此操作,并且我确定jQuery也会这样做(取决于版本。jQuery1.6+版使用eval )。 这节省了解析<script>标记和转义内容的麻烦,以及一堆其他“陷阱”的麻烦。 通常,如果您要自己使用eval() ,则要创建/发送没有任何HTML标记(例如<script>的脚本代码,因为它们不会正确地eval() 。 回答2 这是一个非常有趣的解决方案:http://24ways.org/2005/have-your-dom-and-script-it-too 因此
  • 使用div的innerHTML创建的脚本标签不起作用(script tag create with innerHTML of a div doesn't work)
    问题 这是JS代码: var wrap = document.createElement("div"); wrap.innerHTML = '<script type="text/javascript" src="'+scriptUrl+'"></script>'; var wrapscript = wrap.childNodes[0]; document.body.appendChild(wrapscript) 主体确实插入了script元素,但是没有加载JS资源,甚至没有http请求。 有人可以解释为什么会这样吗? 问题出在Zeptojs的$方法上 $('<script type="text/javascript" src="'+scriptUrl+'"></script>').appendTo($("bdoy")) 它的工作方式类似于上面的代码,并导致该错误。 回答1 这是微不足道的。 如规范(8.4解析HTML片段和8.2.3.5其他解析状态标志)中所述,引用: 使用innerHTML ,浏览器将 创建一个新的Document节点,并将其标记为HTML文档。 如果存在一个context元素,并且context元素的Document处于quirks模式,则让Document处于quirks模式。 否则,如果存在一个context元素
  • How to move an iFrame in the DOM without losing its state?
    Take a look at this simple HTML: <div id="wrap1"> <iframe id="iframe1"></iframe> </div> <div id="warp2"> <iframe id="iframe2"></iframe> </div> Let's say I wanted to move the wraps so that the #wrap2 would be before the #wrap1. The iframes are polluted by JavaScript. I am aware of jQuery's .insertAfter() and .insertBefore(). However, when I use those, the iFrame loses all of its HTML, and JavaScript variables and events. Lets say the following was the iFrame's HTML: <html> <head> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript"> // The variable below would
  • How to wrap async function calls into a sync function in Node.js or Javascript?
    Suppose you maintain a library that exposes a function getData. Your users call it to get actual data: var output = getData(); Under the hood data is saved in a file so you implemented getData using Node.js built-in fs.readFileSync. It's obvious both getData and fs.readFileSync are sync functions. One day you were told to switch the underlying data source to a repo such as MongoDB which can only be accessed asynchronously. You were also told to avoid pissing off your users, getData API cannot be changed to return merely a promise or demand a callback parameter. How do you meet both
  • JavaScript raises SyntaxError with data rendered in Jinja template
    I am trying to pass data as JSON from a Flask route to a Jinja template rendering JavaScript. I want to iterate over the data using JavaScript. The browser shows SyntaxError: Unexpected token '&'. Expected a property name. when JSON.parse is called on the rendered data. How do I use rendered JSON data in JavaScript? var obj = JSON.parse({{ data }}) for (i in obj){ document.write(obj[i].text + "<br />"); } def get_nodes(node): d = {} if node == "Root": d["text"] = node else: d["text"] = node.name getchildren = get_children(node) if getchildren: d["nodes"] = [get_nodes(child) for child in
  • apply style to range of text with javascript in uiwebview
    I am displaying some simple styled text as html in a UIWebView on iPhone. It is basically a series of paragraphs with the occasional strong or emphasized phrase. At runtime I need to apply styles to ranges of text. There are a few similar scenarios, one of which is highlighting search results. If the user has searched for "something" I would like to change the background color behind occurrences of the word, then later restore the original background. Is it possible to apply styles to ranges of text using javascript? A key part of this is also being able to unset the styles. There seem to be
  • 如何使用jQuery选择文本节点?(How do I select text nodes with jQuery?)
    问题 我想获取一个元素的所有后代文本节点,作为jQuery集合。 最好的方法是什么? 回答1 jQuery对此没有方便的功能。 您需要将contents()仅提供子节点但包括文本节点)与find() ,后者将提供所有后代元素,但不提供文本节点。 这是我想出的: var getTextNodesIn = function(el) { return $(el).find(":not(iframe)").addBack().contents().filter(function() { return this.nodeType == 3; }); }; getTextNodesIn(el); 注意:如果您使用的是jQuery 1.7或更早版本,则上面的代码将不起作用。 要解决此问题,请用andSelf()替换addBack()。 andSelf()从1.8开始不推荐使用addBack() 。 与纯DOM方法相比,这种方法效率低下,并且必须为jQuery的content()函数的重载提供一个丑陋的解决方法(感谢注释中的@rabidsnail指出),因此这是使用简单递归的非jQuery解决方案功能。 includeWhitespaceNodes参数控制输出中是否包含空格文本节点(在jQuery中,它们会自动过滤掉)。 更新:修复了includeWhitespaceNodes虚假时的错误。
  • 用jQuery突出显示一个单词(Highlight a word with jQuery)
    问题 我基本上需要在文本块中突出显示一个特定的单词。 例如,假装我想在文本中突出显示“ dolor”一词: <p> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. </p> <p> Quisque bibendum sem ut lacus. Integer dolor ullamcorper libero. Aliquam rhoncus eros at augue. Suspendisse vitae mauris. </p> 我如何将以上内容转换为如下形式: <p> Lorem ipsum <span class="myClass">dolor</span> sit amet, consectetuer adipiscing elit. </p> <p> Quisque bibendum sem ut lacus. Integer <span class="myClass">dolor</span> ullamcorper libero. Aliquam rhoncus eros at augue. Suspendisse vitae mauris. </p> jQuery有可能吗? 编辑:正如塞巴斯蒂安指出的那样,如果没有jQuery,这是完全有可能的-但我希望可以有一种特殊的jQuery方法
  • How to pass variable from jade template file to a script file?
    I'm having trouble with a variable (config) declared in a jade template file (index.jade) that isn't passed to a javascript file, which then makes my javascript crash. Here is the file (views/index.jade): h1 #{title} script(src='./socket.io/socket.io.js') script(type='text/javascript') var config = {}; config.address = '#{address}'; config.port = '#{port}'; script(src='./javascripts/app.js') Here is a part of my app.js (server side): app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(__dirname + '/public')); }); app.configure(
  • 使用JavaScript打印漂亮的JSON(pretty-print JSON using JavaScript)
    问题 如何以易于阅读的格式(对人类读者而言)显示JSON? 我主要是在寻找缩进和空格,甚至可能是颜色/字体样式/等等。 回答1 漂亮打印是在JSON.stringify()中本地实现的。 第三个参数启用漂亮的打印并设置要使用的间距: var str = JSON.stringify(obj, null, 2); // spacing level = 2 如果您需要语法高亮显示,则可以使用一些正则表达式魔术,如下所示: function syntaxHighlight(json) { if (typeof json != 'string') { json = JSON.stringify(json, undefined, 2); } json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { var cls = 'number'; if (/^"/.test(match)) { if (/:$/.test(match))
  • 如何创建一个 tag with Javascript?(How to create a <style> tag with Javascript?)
    问题 我正在寻找一种使用JavaScript将<style>标记插入HTML页面的方法。 到目前为止,我发现的最好方法是: var divNode = document.createElement("div"); divNode.innerHTML = "<br><style>h1 { background: red; }</style>"; document.body.appendChild(divNode); 这在Firefox,Opera和Internet Explorer中有效,但在Google Chrome中不起作用。 IE前面的<br>也有点丑陋。 有谁知道创建<style>标签的方法吗? 更好使用Chrome吗? 或许 这是我应该避免的非标准事情三种运行良好的浏览器都很棒,到底谁在使用Chrome? 回答1 尝试将style元素添加到head而不是body 。 这已在IE(7-9),Firefox,Opera和Chrome中进行了测试: var css = 'h1 { background: red; }', head = document.head || document.getElementsByTagName('head')[0], style = document.createElement('style'); head.appendChild(style)
  • 如何防止冗长的单词打断我的div?(How to prevent long words from breaking my div?)
    问题 从TABLE布局切换到DIV布局以来,一个普遍的问题仍然存在: 问题:您用动态文本填充DIV,并且不可避免地会有一个超长单词延伸到div列的边缘,使您的网站看起来不专业。 RETRO-WHINING :表格布局从未发生过这种情况。 一个表格单元格总是可以很好地扩展到最长单词的宽度。 严重性:我什至在大多数主要站点上都遇到了这个问题,尤其是在德国站点上,这些站点甚至连“限速”之类的通用词都非常长(“ Geschwindigkeitsbegrenzung”)。 有人对此有可行的解决方案吗? 回答1 软连字符 您可以通过插入软连字符( ­ )来告诉浏览器在哪里拆分长字: averyvery­longword 可能被渲染为 veryverylongword 或者 每个- 长字 一个好的正则表达式可以确保除非必要,否则不会插入它们: /([^\s-]{5})([^\s-]{5})/ → $1­$2 浏览器和搜索引擎足够聪明,可以在搜索文本时忽略该字符,而Chrome和Firefox(尚未测试其他字符)在将文本复制到剪贴板时会忽略该字符。 <wbr>元素 另一个选择是注入<wbr> ,它是以前的IE-ism,现在已在HTML5中使用: averyvery<wbr>longword 不带连字符的换行符: 每个长字 您可以使用零宽度空格字符​来实现相同的功能​ (或&#x200B )。
  • SVG文本中的自动换行(Auto line-wrapping in SVG text)
    问题 我想在SVG中显示一个<text> ,它将与HTML文本填充<div>元素的方式一样自动换行到容器<rect> 。 有办法吗? 我不想通过使用<tspan>来稀疏地定位行。 回答1 文本换行不是当前实现的规范SVG1.1的一部分。 您应该通过<foreignObject/>元素使用HTML。 <svg ...> <switch> <foreignObject x="20" y="90" width="150" height="200"> <p xmlns="http://www.w3.org/1999/xhtml">Text goes here</p> </foreignObject> <text x="20" y="20">Your SVG viewer cannot display html.</text> </switch> </svg> 回答2 这是一个替代方案: <svg ...> <switch> <g requiredFeatures="http://www.w3.org/Graphics/SVG/feature/1.2/#TextFlow"> <textArea width="200" height="auto"> Text goes here </textArea> </g> <foreignObject width="200" height="200"
  • Pure javascript method to wrap content in a div
    I want to wrap all the nodes within the #slidesContainer div with JavaScript. I know it is easily done in jQuery, but I am interested in knowing how to do it with pure JS. Here is the code: <div id="slidesContainer"> <div class="slide">slide 1</div> <div class="slide">slide 2</div> <div class="slide">slide 3</div> <div class="slide">slide 4</div> </div> I want to wrap the divs with a class of "slide" collectively within another div with id="slideInner".
  • Javascript Regex to replace text NOT in html attributes [duplicate]
    This question already has answers here: Highlight search terms (select only leaf nodes) (7 answers) Closed 8 years ago. I'd like a Javascript Regex to wrap a given list of of words in a given start (<span>) and end tag (i.e. </span>), but only if the word is actually "visible text" on the page, and not inside of an html attribute (such as a link's title tag, or inside of a <script></script> block. I've created a JS Fiddle with the basics setup: http://jsfiddle.net/4YCR6/1/
  • Best replacement for font tag in html
    Since the font tag in HTML is being deprecated in HTML5 (and I understand why) is there a clean solution for applying certain attributes and styles to only portions of a paragraph text? I'm using JavaScript to parse an XML file that relies on the fact that the font tag allows portions of wrapping text to be formatted using class-based CSS. I realize the "anchor" (a) tag could also be used for this purpose, but that way seems very backwards and unnatural. EDIT When I asked this question (a couple years ago now) I was failing to understand that every DOM element falls into a display category
  • Shrink DIV to text that's wrapped to its max-width?
    Shrink wrapping a div to some text is pretty straightforward. But if the text wraps to a second line (or more) due to a max-width (as an example) then the size of the DIV does not shrink to the newly wrapped text. It is still expanded to the break point (the max-width value in this case), causing a fair amount of margin on the right side of the DIV. This is problematic when wanting to center this DIV so that the wrapped text appears centered. It will not because the DIV does not shrink to multiple lines of text that wrap. One solution is to use justified text, but that isn't always practical
  • How to make part of the text Bold in android at runtime?
    A ListView in my application has many string elements like name, experience, date of joining, etc. I just want to make name bold. All the string elements will be in a single TextView. my XML: <ImageView android:id="@+id/logo" android:layout_width="55dp" android:layout_height="55dp" android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:layout_marginTop="15dp" > </ImageView> <TextView android:id="@+id/label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/logo" android:padding="5dp" android:textSize="12dp" > </TextView> My
  • Databinding TextBlock Runs in Silverlight / WP7
    I'm using Silverlight on Windows Phone 7. I want to display the first part of some text in a TextBlock in bold, and the rest in normal font. The complete text must wrap. I want the bolded part to contain text from one property in my ViewModel, and the plain text to contain text from a different property. The TextBlock is defined in a DataTemplate associated with a LongListSelector. My initial attempt was: <TextBlock TextWrapping="Wrap"> <TextBlock.Inlines> <Run Text="{Binding Property1}" FontWeight="Bold"/> <Run Text="{Binding Property2}"/> </TextBlock.Inlines> </TextBlock> This fails at
  • Load “Vanilla” Javascript Libraries into Node.js
    There are some third party Javascript libraries that have some functionality I would like to use in a Node.js server. (Specifically I want to use a QuadTree javascript library that I found.) But these libraries are just straightforward .js files and not "Node.js libraries". As such, these libraries don't follow the exports.var_name syntax that Node.js expects for its modules. As far as I understand that means when you do module = require('module_name'); or module = require('./path/to/file.js'); you'll end up with a module with no publicly accessible functions, etc. My question then is "How do