2010-08-11 42 views
44

Tôi có một bộ div là contentEditable và được tạo kiểu với "white-space:pre" để nó giữ những thứ như vạch nét. Trong Safari, FF và IE, div trông khá giống nhau và hoạt động giống nhau. Tất cả đều tốt. Những gì tôi muốn làm là trích xuất văn bản từ div này, nhưng theo cách như vậy sẽ không mất định dạng - cụ thể, ngắt dòng.Trích xuất văn bản từ một contentEditable div

Chúng tôi đang sử dụng jQuery, có chức năng text() về cơ bản thực hiện DFS đặt hàng trước và dán tất cả nội dung trong nhánh đó của DOM vào một khối duy nhất. Điều này sẽ mất định dạng.

Tôi đã xem xét chức năng html(), nhưng có vẻ như cả ba trình duyệt đều làm những việc khác nhau với HTML thực tế được tạo phía sau hậu trường trong div contentEditable của tôi. Giả sử tôi gõ này vào div của tôi:

1 
2 
3 

Đây là kết quả:

Safari 4:

1 
<div>2</div> 
<div>3</div> 

Firefox 3.6:

1 
<br _moz_dirty=""> 
2 
<br _moz_dirty=""> 
3 
<br _moz_dirty=""> 
<br _moz_dirty="" type="_moz"> 

IE 8:

<P>1</P><P>2</P><P>3</P> 

Ugh. Không có gì rất phù hợp ở đây. Điều đáng ngạc nhiên là MSIE trông có vẻ lành mạnh nhất! (Thẻ được viết hoa P và tất cả)

Div sẽ tự động đặt kiểu (khuôn mặt, màu, kích thước và căn chỉnh) được thực hiện bằng CSS, vì vậy tôi không chắc liệu mình có thể sử dụng thẻ pre hay không ám chỉ đến một số trang tôi đã tìm thấy bằng Google).

Có ai biết về bất kỳ mã JavaScript và/hoặc plugin jQuery nào hay thứ gì đó sẽ trích xuất văn bản từ div nội dung có thể chỉnh sửa theo cách sao cho bảo vệ ngắt dòng không? Tôi không muốn phát minh lại bánh xe phân tích nếu tôi không phải làm như vậy.

Cập nhật: Tôi đã nới lỏng chức năng getText từ jQuery 1.4.2 và sửa đổi nó để trích xuất nó với khoảng trắng hầu như nguyên vẹn (tôi chỉ chnaged một dòng mà tôi thêm một dòng mới);

function extractTextWithWhitespace(elems) { 
    var ret = "", elem; 

    for (var i = 0; elems[i]; i++) { 
     elem = elems[i]; 

     // Get the text from text nodes and CDATA nodes 
     if (elem.nodeType === 3 || elem.nodeType === 4) { 
      ret += elem.nodeValue + "\n"; 

     // Traverse everything else, except comment nodes 
     } else if (elem.nodeType !== 8) { 
      ret += extractTextWithWhitespace2(elem.childNodes); 
     } 
    } 

    return ret; 
} 

tôi gọi chức năng này và sử dụng sản lượng của nó để gán nó vào một nút XML với jQuery, một cái gì đó như:

var extractedText = extractTextWithWhitespace($(this)); 
var $someXmlNode = $('<someXmlNode/>'); 
$someXmlNode.text(extractedText); 

kết quả XML cuối cùng được gửi đến một máy chủ thông qua một cuộc gọi AJAX.

Tính năng này hoạt động tốt trong Safari và Firefox.

Trên IE, chỉ lần đầu tiên '\ n' dường như được giữ lại bằng cách nào đó. Nhìn vào nó nhiều hơn, có vẻ như jQuery là thiết lập các văn bản như vậy (dòng 4004 của jQuery-1.4.2.js):

return this.empty().append((this[0] && this[0].ownerDocument || document).createTextNode(text)); 

Đọc lên trên createTextNode, dường như thực hiện của IE có thể nghiền nát lên khoảng trắng . Điều này đúng hay tôi đang làm điều gì sai?

+2

Thật thú vị, không ngạc nhiên khi IE hoạt động mạnh nhất: contentEditable ban đầu là độc quyền của IE; nó nằm trong IE từ 5.5, vì vậy tôi đoán họ đã có nhiều thời gian nhất để nó hoạt động tốt. – Yahel

Trả lời

3

Tôi đã quên câu hỏi này cho đến bây giờ, khi Nico tát tiền thưởng vào đó.

Tôi đã giải quyết được vấn đề bằng cách viết hàm tôi cần, nới lỏng một hàm từ mã nguồn jQuery hiện tại và sửa đổi nó để hoạt động khi cần.

Tôi đã thử nghiệm chức năng này với Safari (WebKit), IE, Firefox và Opera. Tôi không bận tâm kiểm tra bất kỳ trình duyệt nào khác vì toàn bộ nội dung có thể chỉnh sửa là không chuẩn. Cũng có thể là bản cập nhật cho bất kỳ trình duyệt nào có thể phá vỡ chức năng này nếu chúng thay đổi cách chúng triển khai contentEditable. Vì vậy, lập trình hãy cẩn thận.

function extractTextWithWhitespace(elems) 
{ 
    var lineBreakNodeName = "BR"; // Use <br> as a default 
    if ($.browser.webkit) 
    { 
     lineBreakNodeName = "DIV"; 
    } 
    else if ($.browser.msie) 
    { 
     lineBreakNodeName = "P"; 
    } 
    else if ($.browser.mozilla) 
    { 
     lineBreakNodeName = "BR"; 
    } 
    else if ($.browser.opera) 
    { 
     lineBreakNodeName = "P"; 
    } 
    var extractedText = extractTextWithWhitespaceWorker(elems, lineBreakNodeName); 

    return extractedText; 
} 

// Cribbed from jQuery 1.4.2 (getText) and modified to retain whitespace 
function extractTextWithWhitespaceWorker(elems, lineBreakNodeName) 
{ 
    var ret = ""; 
    var elem; 

    for (var i = 0; elems[i]; i++) 
    { 
     elem = elems[i]; 

     if (elem.nodeType === 3  // text node 
      || elem.nodeType === 4) // CDATA node 
     { 
      ret += elem.nodeValue; 
     } 

     if (elem.nodeName === lineBreakNodeName) 
     { 
      ret += "\n"; 
     } 

     if (elem.nodeType !== 8) // comment node 
     { 
      ret += extractTextWithWhitespace(elem.childNodes, lineBreakNodeName); 
     } 
    } 

    return ret; 
} 
+0

điều này cũng bị hỏng trong Chrome - 1) nhập 1,2,3,4 trên các dòng riêng biệt 2) quay lại dòng 1 3) gõ một vài từ 4) đi đến đầu dòng hai, nhấn backspace, nhấn enter, nhấn backspace 5) xem kết quả, dòng 2 sẽ có thêm ngắt dòng sau –

35

Đáng tiếc là bạn vẫn phải xử lý việc này đối với trường hợp pre mỗi trình duyệt riêng (Tôi không bỏ qua trình duyệt phát hiện trong nhiều trường hợp, sử dụng tính năng phát hiện ... nhưng trong trường hợp này đó là cần thiết), nhưng may mắn là bạn có thể chăm sóc tất cả chúng một cách chính xác, như sau:

var ce = $("<pre />").html($("#edit").html()); 
if($.browser.webkit) 
    ce.find("div").replaceWith(function() { return "\n" + this.innerHTML; });  
if($.browser.msie) 
    ce.find("p").replaceWith(function() { return this.innerHTML + "<br>"; }); 
if($.browser.mozilla || $.browser.opera ||$.browser.msie) 
    ce.find("br").replaceWith("\n"); 

var textWithWhiteSpaceIntact = ce.text(); 

You can test it out here. IE đặc biệt là một rắc rối vì cách thức là &nbsp; và dòng mới trong chuyển đổi văn bản, đó là lý do tại sao nó được điều trị <br> ở trên để làm cho nó phù hợp, vì vậy nó cần 2 vượt qua để được xử lý một cách chính xác.

Ở phía trên #edit là ID của các thành phần contentEditable, vì vậy chỉ cần thay đổi điều đó ra, hoặc làm cho một hàm, ví dụ:

function getContentEditableText(id) { 
    var ce = $("<pre />").html($("#" + id).html()); 
    if ($.browser.webkit) 
     ce.find("div").replaceWith(function() { return "\n" + this.innerHTML; }); 
    if ($.browser.msie) 
     ce.find("p").replaceWith(function() { return this.innerHTML + "<br>"; }); 
    if ($.browser.mozilla || $.browser.opera || $.browser.msie) 
     ce.find("br").replaceWith("\n"); 

    return ce.text(); 
} 

You can test that here. Hoặc, vì điều này được xây dựng trên phương pháp jQuery dù sao, làm cho nó một plugin, như thế này:

$.fn.getPreText = function() { 
    var ce = $("<pre />").html(this.html()); 
    if ($.browser.webkit) 
     ce.find("div").replaceWith(function() { return "\n" + this.innerHTML; }); 
    if ($.browser.msie) 
     ce.find("p").replaceWith(function() { return this.innerHTML + "<br>"; }); 
    if ($.browser.mozilla || $.browser.opera || $.browser.msie) 
     ce.find("br").replaceWith("\n"); 

    return ce.text(); 
}; 

Sau đó, bạn chỉ có thể gọi nó với $("#edit").getPreText(), you can test that version here.

+0

Ick. Khi bạn quan sát, phát hiện trình duyệt là xấu. May mắn thay, nó là tránh được ở đây: xem câu trả lời của tôi. –

+0

@Tim - Tôi không thể có được cách tiếp cận của bạn để làm việc trong IE hoặc Opera mặc dù: http://www.jsfiddle.net/UjZEN/3/ –

+0

bất kỳ bản cập nhật này? bạn có thể giải quyết nó hoàn toàn trên tất cả các trình duyệt không ??? – gsagrawal

1

tôi phát hiện ra hôm nay trong Firefox:

Tôi chuyển một div có thể chỉnh sửa được không gian trắng được đặt thành "tiền" cho chức năng này và hoạt động mạnh.

Tôi đã thêm một dòng để hiển thị số lượng nút có và một nút đặt đầu ra vào một PRE khác, chỉ để chứng minh rằng các dấu ngắt dòng là nguyên vẹn.

Nó về cơ bản nói điều này:

For each child node of the DIV, 
    if it contains the 'data' property, 
     add the data value to the output 
    otherwise 
     add an LF (or a CRLF for Windows) 
} 
and return the result. 

Có một vấn đề, tho. Khi bạn nhấn enter ở cuối dòng bất kỳ của văn bản gốc, thay vì đặt một LF vào, nó sẽ đặt "Â" vào. Bạn có thể nhấn enter lần nữa và nó đặt một LF vào đó, nhưng không phải là lần đầu tiên. Và bạn phải xóa "Â" (nó trông giống như một không gian). Về hình - Tôi đoán đó là một lỗi.

Điều này không xảy ra trong IE8. (Thay đổi textContent thành innerText) Có một lỗi khác ở đó, tho. Khi bạn nhấn Enter, nó chia nút thành 2 nút, giống như trong Firefox, nhưng thuộc tính "dữ liệu" của mỗi nút trong số đó sẽ trở thành "không xác định".

Tôi chắc rằng có nhiều điều xảy ra ở đây hơn là gặp mắt, vì vậy mọi thông tin đầu vào về vấn đề sẽ được khai sáng.

<!DOCTYPE html> 
<html> 
<HEAD> 
<SCRIPT type="text/javascript"> 
    function htmlToText(elem) { 
     var outText=""; 
     for(var x=0; x<elem.childNodes.length; x++){ 
      if(elem.childNodes[x].data){ 
       outText+=elem.childNodes[x].data; 
      }else{ 
       outText+="\n"; 
      } 
     } 
     alert(elem.childNodes.length + " Nodes: \r\n\r\n" + outText); 
     return(outText); 
    } 
</SCRIPT> 
</HEAD> 
<body> 

<div style="white-space:pre;" contenteditable=true id=test>Text in a pre element 
is displayed in a fixed-width 
font, and it preserves 
both  spaces and 
line breaks 
</DIV> 
<INPUT type=button value="submit" onclick="document.getElementById('test2').textContent=htmlToText(document.getElementById('test'))"> 
<PRE id=test2> 
</PRE> 
</body> 
</html> 
+0

Hoạt động tốt cho tôi (trong FF và Chrome). Đã không đánh giá nó tính toán so với các tùy chọn '$ .browser' khác nhưng cho Jquery không gửi plugin đó nữa, điều này dễ dàng hơn để thả vào. Tôi sẽ lo lắng về hiệu suất một ngày khác :) – Oli

0

đây là một giải pháp (sử dụng gạch chân và jquery) mà dường như làm việc trong iOS Safari (iOS 7 và 8), Safari 8, Chrome 43 và Firefox 36 trong OS X, và IE6-11 trên Windows:

_.reduce($editable.contents(), function(text, node) { 
    return text + (node.nodeValue || '\n' + 
     (_.isString(node.textContent) ? node.textContent : node.innerHTML)); 
}, '') 

see trang kiểm tra ở đây: http://brokendisk.com/code/contenteditable.html

mặc dù tôi nghĩ câu trả lời thực sự là nếu bạn không quan tâm đến việc đánh dấu được cung cấp bởi các trình duyệt, bạn không nên sử dụng thuộc tính contenteditable - một textarea sẽ là công cụ thích hợp cho công việc.

+1

Tôi sử dụng div có thể chỉnh sửa được vì lợi ích của việc hiển thị HTML bên trong ví dụ: văn bản làm nổi bật các ký tự dư thừa như twitter. Tôi không quan tâm đến việc lưu định dạng đó vào cơ sở dữ liệu của mình. – Amicable

+0

@Amicable Bạn đã thử chức năng chưa? Hãy cho tôi biết nếu nó có vẻ làm việc cho bạn. Cũng lưu ý rằng thông thường w/một phần tử có thể chỉnh sửa khi bạn sao chép/dán HTML định dạng được giữ lại - bạn có thể muốn làm như Twitter và lọc ra đánh dấu trong tình huống này. Tuy nhiên, –

+0

Giải pháp sạch đẹp, nó không hoạt động trong trường hợp trình duyệt không phù hợp với các lớp. Tức là, chrome không bao gồm div làm phần tử đầu tiên khi nhập nhưng thực hiện ngay khi bạn nhấn enter. Tôi thấy giải pháp này đã không hoàn toàn xử lý trường hợp đó. – Lukus

-1
this.editableVal = function(cont, opts) 
{ 
    if (!cont) return ''; 
    var el = cont.firstChild; 
    var v = ''; 
    var contTag = new RegExp('^(DIV|P|LI|OL|TR|TD|BLOCKQUOTE)$'); 
    while (el) { 
    switch (el.nodeType) { 
     case 3: 
     var str = el.data.replace(/^\n|\n$/g, ' ').replace(/[\n\xa0]/g, ' ').replace(/[ ]+/g, ' '); 
     v += str; 
     break; 
     case 1: 
     var str = this.editableVal(el); 
     if (el.tagName && el.tagName.match(contTag) && str) { 
      if (str.substr(-1) != '\n') { 
      str += '\n'; 
      } 

      var prev = el.previousSibling; 
      while (prev && prev.nodeType == 3 && PHP.trim(prev.nodeValue) == '') { 
      prev = prev.previousSibling; 
      } 
      if (prev && !(prev.tagName && (prev.tagName.match(contTag) || prev.tagName == 'BR'))) { 
      str = '\n' + str; 
      } 

     }else if (el.tagName == 'BR') { 
      str += '\n'; 
     } 
     v += str; 
     break; 
    } 
    el = el.nextSibling; 
    } 
    return v; 
} 
+2

Xin chào! Cảm ơn câu trả lời của bạn và chào mừng bạn đến với Stackoverflow. Vui lòng xem [cách trả lời] (https://stackoverflow.com/help/how-to-answer) và cố gắng cải thiện câu trả lời của bạn một chút. Thêm giải thích về cách OP bị sai hoặc mã của bạn giúp cải thiện chất lượng câu trả lời của bạn tốt hơn. – Ortund

Các vấn đề liên quan