web-dev-qa-db-fra.com

Extraction de texte à partir d'un contenu contentEditable

J'ai un ensemble défini à contentEditable et stylé avec "white-space:pre" afin qu'il garde des choses comme les sauts de ligne. Dans Safari, FF et IE, la division ressemble et fonctionne de la même manière. Tout est bien. Ce que je veux faire, c'est extraire le texte de cette div, mais de manière à ne pas perdre la mise en forme - en particulier les sauts de ligne.

Nous utilisons jQuery, dont la fonction text() effectue essentiellement une DFS de précommande et colle tout le contenu de cette branche du DOM en un seul bloc. Cela perd la mise en forme.

J'ai jeté un œil à la fonction html(), mais il semble que les trois navigateurs font des choses différentes avec le code HTML réel généré en coulisse dans ma contentEditable div. En supposant que je tape ceci dans mon div:

1
2
3

Ce sont les résultats:

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>

Pouah. Rien de très cohérent ici. La chose surprenante est que MSIE semble le plus sain d'esprit! (Tag P en majuscule et tout)

La div aura un style défini de manière dynamique (police, couleur, taille et alignement), ce qui est réalisé à l'aide de CSS. Je ne suis donc pas sûr de pouvoir utiliser une balise pre (à laquelle certaines pages que j'ai trouvées avec Google).

Est-ce que quelqu'un connaît un code JavaScript et/ou un plugin jQuery ou quelque chose qui va extraire le texte d'un div contentEditable de manière à préserver les sauts de ligne? Je préférerais ne pas réinventer une roue d'analyse si ce n'est pas nécessaire.

Mise à jour: j'ai écrit la fonction getText de jQuery 1.4.2 et l'a modifiée pour l'extraire avec des espaces presque intacts (je n'ai modifié qu'une ligne à laquelle j'ai ajouté une nouvelle ligne);

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

J'appelle cette fonction et utilise sa sortie pour l'assigner à un nœud XML avec jQuery, quelque chose comme:

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

Le fichier XML résultant est finalement envoyé à un serveur via un appel AJAX.

Cela fonctionne bien dans Safari et Firefox.

Sous IE, seul le premier '\ n' semble avoir été retenu. En y regardant de plus près, on dirait que jQuery définit le texte comme suit (ligne 4004 de jQuery-1.4.2.js):

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

En lisant createTextNode, il semble que l'implémentation d'IE peut brouiller les espaces. Est-ce vrai ou est-ce que je fais quelque chose de mal?

45
Shaggy Frog

J'avais oublié cette question jusqu'à maintenant, quand Nico l'a giflé.

J'ai résolu le problème en écrivant la fonction dont j'avais besoin, en ajoutant une fonction à partir de la base de code jQuery existante et en la modifiant pour qu'elle fonctionne correctement.

J'ai testé cette fonction avec Safari (WebKit), IE, Firefox et Opera. Je n'ai pas pris la peine de vérifier les autres navigateurs, car l'ensemble de l'élément contentEditable est non standard. Il est également possible qu'une mise à jour de n'importe quel navigateur puisse rompre cette fonction si elle modifie la manière dont elle implémente contentEditable. Alors programmeur méfiez-vous.

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

Malheureusement, vous devez toujours gérer cela pour le cas pre individuellement par navigateur (je ne tolère pas la détection browser dans de nombreux cas, utilisez la détection feature ... mais dans ce cas, c'est nécessaire), mais heureusement, vous pouvez vous en occuper tous de manière assez concise, comme ceci:

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

Vous pouvez le tester ici . IE en particulier est embêtant à cause de la façon dont est fait &nbsp; et de nouvelles lignes dans la conversion de texte, c'est pourquoi il reçoit le traitement <br> ci-dessus pour le rendre cohérent, il a donc besoin de 2 passes pour être traité correctement.

#edit ci-dessus correspond à l'ID du composant contentEditable. Modifiez-le simplement ou faites-en une fonction, par exemple:

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

Vous pouvez tester cela ici . Ou, puisque celui-ci est construit sur des méthodes jQuery de toute façon, faites-en un plugin, comme ceci:

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

Ensuite, vous pouvez simplement l'appeler avec $("#edit").getPreText(), vous pouvez tester cette version ici .

36
Nick Craver

J'ai découvert cela aujourd'hui dans Firefox:

Je passe un div contenteditable qui blanc-espace est réglé sur "pre" à cette fonction, et cela fonctionne très bien. 

J'ai ajouté une ligne pour indiquer le nombre de nœuds et un bouton qui place la sortie dans un autre PRE, simplement pour prouver que les sauts de ligne sont intacts.

Il dit essentiellement ceci:

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.

Il y a un problème, tho. Lorsque vous appuyez sur entrée à la fin d'une ligne du texte original, au lieu de mettre un LF, vous mettez un "Â". Vous pouvez appuyer à nouveau sur Entrée et un LF dedans, mais pas la première fois. Et vous devez supprimer le "Â" (cela ressemble à un espace). Allez comprendre - je suppose que c'est un bug.

Cela ne se produit pas dans IE8. (changez textContent en innerText) Il y a un autre bogue, tho. Lorsque vous appuyez sur Entrée, le nœud est divisé en 2 nœuds, comme dans Firefox, mais la propriété "data" de chacun de ces nœuds devient "indéfinie".

Je suis sûr qu'il y a beaucoup plus de choses qui se passent ici qu'il n'y paraît, alors toute contribution à ce sujet sera éclairante.

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

voir ceci violon

Ou ce post

Comment analyser le texte modifiable de DIV avec la compatibilité du navigateur

créé après beaucoup d'effort ...........

1
user10

voici une solution (utilisant le trait de soulignement et jquery) qui semble fonctionner sous iOS Safari (iOS 7 et 8), Safari 8, Chrome 43 et Firefox 36 sous OS X et IE6-11 sous Windows:

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

voir la page de test ici: http://brokendisk.com/code/contenteditable.html

bien que je pense que la vraie réponse est que si vous n'êtes pas intéressé par le balisage fourni par le navigateur, vous ne devriez pas utiliser l'attribut contenteditable - une zone de texte serait l'outil approprié pour le travail.

0
Jon z