it-swarm.com.de

Extrahieren von Text aus einem contentEditable div

Ich habe ein div auf contentEditable gesetzt und mit "white-space:pre" gestylt, damit es Dinge wie Zeilenumbrüche behält. In Safari, FF und IE sieht das Div ziemlich genauso aus und funktioniert genauso. Alles ist gut. Was ich tun möchte, ist den Text aus diesem div zu extrahieren, aber so, dass die Formatierung nicht verloren geht - insbesondere die Zeilenumbrüche.

Wir verwenden jQuery, dessen text()-Funktion im Grunde eine DFS vorbestellt und den gesamten Inhalt in diesem Zweig des DOM zu einem einzigen Knoten zusammenfügt. Dadurch geht die Formatierung verloren.

Ich habe mir die html()-Funktion angesehen, aber es scheint, dass alle drei Browser unterschiedliche Dinge mit dem eigentlichen HTML-Code tun, der hinter den Kulissen in meiner contentEditable div generiert wird. Angenommen, ich gebe das in mein div ein:

1
2
3

Das sind die Ergebnisse:

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>

Pfui. Nichts sehr konsistent hier. Das Überraschende ist, dass MSIE am vernünftigsten ist! (Großgeschriebenes P-Tag und alle)

Das div hat ein dynamisch festgelegtes Styling (Schriftart, Farbe, Größe und Ausrichtung), das mit CSS ausgeführt wird. Ich bin mir also nicht sicher, ob ich ein pre-Tag verwenden kann (auf das ich auf einigen Seiten, die ich mit Google gefunden habe, verwiesen habe).

Kennt jemand JavaScript-Code und/oder ein jQuery-Plugin oder etwas, das Text aus einem contentEditable -Div so extrahiert, dass Zeilenumbrüche erhalten bleiben? Ich würde es vorziehen, ein Parser-Rad nicht neu zu erfinden, wenn ich nicht muss.

Update: Ich habe die getText-Funktion aus jQuery 1.4.2 erstellt und so modifiziert, dass sie mit weitgehend intaktem Whitespace extrahiert wird (ich habe nur eine Zeile in der Zeile, in der ich eine neue Zeile hinzufügte).

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;
}

Ich rufe diese Funktion auf und verwende ihre Ausgabe, um sie mit jQuery einem XML-Knoten zuzuordnen.

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

Das resultierende XML wird schließlich über einen Aufruf von AJAX an einen Server gesendet.

Dies funktioniert gut in Safari und Firefox.

Unter IE scheint nur das erste '\ n' irgendwie erhalten zu bleiben. Wenn Sie sich das genauer ansehen, sieht es so aus, als würde jQuery den Text so einstellen (Zeile 4004 von jQuery-1.4.2.js):

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

In Anlehnung an createTextNode scheint die Implementierung von IE den Whitespace zu zerstören. Ist das wahr oder mache ich etwas falsch?

45
Shaggy Frog

Ich habe diese Frage bis jetzt vergessen, als Nico eine Kopfprämie darauf schlug.

Ich habe das Problem gelöst, indem ich die Funktion geschrieben habe, die ich selbst brauchte, eine Funktion aus der vorhandenen jQuery-Codebase herausschneidet und sie so modifiziert, dass sie nach Bedarf funktioniert.

Ich habe diese Funktion mit Safari (WebKit), IE, Firefox und Opera getestet. Ich habe mir nicht die Mühe gemacht, nach anderen Browsern zu suchen, da die gesamte contentEditable-Sache nicht dem Standard entspricht. Es ist auch möglich, dass ein Update für einen Browser diese Funktion stört, wenn sie die Implementierung von contentEditable ändern. Also Programmierer aufgepasst.

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;
}
3
Shaggy Frog

Leider müssen Sie dies immer noch für den Fall pre einzeln pro Browser erledigen (ich akzeptiere die Erkennung von browser in vielen Fällen nicht, verwenden Sie die Erkennung von feature ... aber in diesem Fall ist es erforderlich). aber zum Glück können Sie sich auf diese Weise ganz prägnant um diese kümmern:

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();

Sie können es hier testen . IE ist besonders umständlich, da &nbsp; und neue Zeilen bei der Textkonvertierung verwendet werden. Aus diesem Grund wird die oben beschriebene <br>-Behandlung konsistent gemacht, sodass 2 Passes erforderlich sind, um korrekt verarbeitet zu werden.

Im obigen Code ist #edit die ID der contentEditable-Komponente. Ändern Sie also einfach das Out oder machen Sie eine Funktion, zum Beispiel:

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

Sie können das hier testen . Oder, da dies ohnehin auf jQuery-Methoden basiert, machen Sie es zu einem Plugin wie folgt:

$.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();
};

Dann können Sie es einfach mit $("#edit").getPreText() aufrufen, Sie können diese Version hier testen .

36
Nick Craver

Ich habe das heute in Firefox entdeckt:

Ich übergebe ein inhaltsfähiges div, dessen Leerraum auf diese Funktion eingestellt ist, und es funktioniert scharf. 

Ich habe eine Zeile hinzugefügt, um anzuzeigen, wie viele Knoten vorhanden sind, und eine Schaltfläche, mit der die Ausgabe in einen anderen PRE-Wert versetzt wird, nur um zu zeigen, dass die Zeilenumbrüche intakt sind.

Es sagt im Grunde Folgendes:

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.

Es gibt ein Problem. Wenn Sie am Ende einer beliebigen Zeile des ursprünglichen Textes die Eingabetaste drücken, wird anstelle eines LF ein "" eingefügt. Sie können erneut die Eingabetaste drücken und dort ein LF einfügen aber nicht das erste mal. Und Sie müssen das "Â" löschen (es sieht aus wie ein Leerzeichen). Gehen Sie Abbildung - ich denke, das ist ein Fehler.

Dies tritt in IE8 nicht auf. (textContent in innerText ändern) Es gibt einen anderen Fehler, Tho. Wenn Sie die Eingabetaste drücken, wird der Knoten wie in Firefox in zwei Knoten aufgeteilt. Die "data" -Eigenschaft jedes dieser Knoten wird dann "undefiniert".

Ich bin sicher, dass hier viel mehr los ist, als man auf den ersten Blick erkennt, so dass jeder Input zu dieser Angelegenheit aufschlussreich sein wird.

<!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>
1
alfadog67

siehe diese Geige

Oder diesen Beitrag

Wie editierbarer DIV-Text mit Browserkompatibilität analysiert wird

nach viel Mühe erstellt ...........

1
user10

hier ist eine Lösung (mit Unterstrich und Jquery), die unter iOS Safari (iOS 7 und 8), Safari 8, Chrome 43 und Firefox 36 unter OS X und IE6-11 unter Windows zu funktionieren scheint:

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

siehe Testseite hier: http://brokendisk.com/code/contenteditable.html

ich denke, die eigentliche Antwort ist, dass Sie das contenteditable-Attribut nicht verwenden sollten, wenn Sie nicht an der vom Browser bereitgestellten Markierung interessiert sind.

0
Jon z