HTML metni işaretle

5 Cevap php

Ben bazı düz metin ve html var. Ben aynı html dönecektir PHP yöntem oluşturmak gerekir, fakat <span class="marked"> ondan sonra metin ve </span> herhangi örneklerini önce.

Metin blabla yani o bla<b>bla</b> veya <a href="http://abc.com">bla</a>bla olduğunda işaretlemek gerekiyorsa örneğin html (etiketleri desteklemesi gerektiğini unutmayın.

Bu örtmek duyarlı olmak ve ya (multilines vb) uzun metin desteklemek gerekir.

Ben metin ile bu işlev çağrısı Örneğin, ve aşağıdaki html "Benim adım Josh":

<html>
<head>
    <title>My Name Is Josh!!!</title>
</head>
<body>
    <h1>my name is <b>josh</b></h1>
    <div>
        <a href="http://www.names.com">my name</a> is josh
    </div>

    <u>my</u> <i>name</i> <b>is</b> <span style="font-family: Tahoma;">Josh</span>.
</body>
</html>

... Geri dönmek gerekir:

<html>
<head>
    <title><span class="marked">My Name Is Josh</span>!!!</title>
</head>
<body>
    <h1><span class="marked">my name is <b>josh</b></span></h1>
    <div>
        <span class="marked"><a href="http://www.names.com">my name</a> is josh</span>
    </div>

    <span class="marked"><u>my</u> <i>name</i> <b>is</b> <span style="font-family: Tahoma;">Josh</span></span>.
</body>
</html>

Teşekkürler.

5 Cevap

Bu zor olacak.

Eğer bir etiketi içinde bir şey, naif gibi bir şey yok sayarak, basit regex hack ile yapabilirdi ederken:

preg_replace(
    'My(<[^>]>)*\s+(<[^>]>)*name(<[^>]>)*\s+(<[^>]>)*is(<[^>]>)*\s+(<[^>]>)*Josh',
    '<span class="marked">$0</span>', $html
)

bu hiç de güvenilir değil. Kısmen HTML regex çözümlenen olamaz: bir öznitelik değerinde > koymak geçerli, ve yorum gibi diğer non-element yapıları yanlış ayrıştırılır olacak. <[^>\s]*(\s+([^>\s]+(\s*=\s*([^"'\s>][\s>]*|"[^"]*"|'[^']*')\s*))?)*\s*\/?>, hala giriş HTML geçerli garanti değil, özellikle eğer, aynı sorunların birçok olurdu gibi korkunç hantal bir şey - hatta etiketleri maç için daha titiz bir ifade ile.

Bu bile işliyoruz HTML güvenilmeyen, sanki o betik enjeksiyonu sonucunda, nitelikleri içine metin içeriği dönüm içine Ayrıştırıcı aptal olabilir, bir security issue olabilir.

Ama bu bile görmezden, uygun eleman yuvalama sağlamak mümkün olmaz. Yani açmak olabilir:

<em>My name is <strong>Josh</strong>!!!</em>

içine misnested ve geçersiz:

<span class="marked"><em>My name is <strong>Josh</strong></span>!!!</em>

veya:

My
<table><tr><td>name is</td></tr></table>
Josh

Bu elemanlar bir yayılma ile sarılmış olamaz nerede. Eğer şanssız iseniz, tarayıcı fixups 'doğru' daki geçersiz çıktı 'işaretlenmiş' yarım sayfa bırakarak, ya da sayfa düzeni karışıklık bitebileceğini için.

Yani bir çözümlü-DOM düzeyde yerine dize hack ile bunu yapmak zorunda olacaktır. PHP, süreç o ve yeniden tefrika kullanarak tüm dizeyi ayrıştırmak olabilir, ama o bakış bir erişilebilirlik açısından kabul edilebilir ise, muhtemelen içerik zaten ayrıştırılır JavaScript içinde tarayıcı sonunda bunu yapmak daha kolay olacaktır DOM düğümleri.

Hala oldukça zor olacak. This question metin tüm aynı metin düğümü içinde olacaktır nerede kolları, ama bu çok daha basit bir durum.

Ne etkili yapmak gerekir olacaktır:

for each Element that may contain a <span>:
    for each child node in the element:
       generate the text content of this node and all following siblings
       match the target string/regex against the whole text
       if there is no match:
           break the outer loop - on to the next element.
       if the current node is an element node and the index of the match is not 0:
           break the inner loop - on to the next sibling node
       if the current node is a text node and the index of the match is > the length of the Text node data:
           break the inner loop - on to the next sibling node
       // now we have to find the position of the end of the match
       n is the length of the match string
       iterate through the remaining text node data and sibling text content:
           compare the length of the text content with n
           less?:
               subtract length from n and continue
           same?:
               we've got a match on a node boundary
               split the first text node if necessary
               insert a new span into the document
               move all the nodes from the first text node to this boundary inside the span
               break to outer loop, next element
           greater?:
               we've got a match ending inside the node.
               is the node a text node?:
                   then we can split the text node
                   also split the first text node if necessary
                   insert a new span into the document
                   move all contained nodes inside the span
                   break to outer loop, next element
               no, an element?:
                   oh dear! We can't insert a span here

Ahh.

Burada ayrı bir maçın parçası olan her bir metin düğümü sarmak için kabul edilebilir ise, biraz daha az kötü olan alternatif bir öneri bulunuyor. Yani:

<p>Oh, my</p> name <div><div>is</div><div> Josh

çıkışı ile bırakacaktı:

<p>Oh, <span class="marked">my</span></p>
<span class="marked"> name </span>
<div><div><span class="marked">is</span></div></div>
<span class="marked"> Josh</span>

hangi maçları şekillendirme konum nasıl bağlı, OK görünebilir. Ayrıca kısmen öğeleri içinde kibrit misnesting sorunu çözmek olacaktır.

ETA: Oh pseudocode kahretsin, ben daha fazla veya daha az zaten şimdi kod yazdık, hem de bunu bitirmek olabilir. İşte ikinci yaklaşımın bir JavaScript versiyonu:

markTextInElement(document.body, /My\s+name\s+is\s+Josh/gi);


function markTextInElement(element, regexp) {
    var nodes= [];
    collectTextNodes(nodes, element);
    var datas= nodes.map(function(node) { return node.data; });
    var text= datas.join('');

    // Get list of [startnodei, startindex, endnodei, endindex] matches
    //
    var matches= [], match;
    while (match= regexp.exec(text)) {
        var p0= getPositionInStrings(datas, match.index, false);
        var p1= getPositionInStrings(datas, match.index+match[0].length, true);
        matches.push([p0[0], p0[1], p1[0], p1[1]]);
    }

    // Get list of nodes for each match, splitted at the edges of the
    // text. Reverse-iterate to avoid the splitting changing nodes we
    // have yet to process.
    //
    for (var i= matches.length; i-->0;) {
        var ni0= matches[i][0], ix0= matches[i][1], ni1= matches[i][2], ix1= matches[i][3];
        var mnodes= nodes.slice(ni0, ni1+1);
        if (ix1<nodes[ni1].length)
            nodes[ni1].splitText(ix1);
        if (ix0>0)
            mnodes[0]= nodes[ni0].splitText(ix0);

        // Replace each text node in the sublist with a wrapped version
        //
        mnodes.forEach(function(node) {
            var span= document.createElement('span');
            span.className= 'marked';
            node.parentNode.replaceChild(span, node);
            span.appendChild(node);
        });
    }
}

function collectTextNodes(texts, element) {
    var textok= [
        'applet', 'col', 'colgroup', 'dl', 'iframe', 'map', 'object', 'ol',
        'optgroup', 'option', 'script', 'select', 'style', 'table',
        'tbody', 'textarea', 'tfoot', 'thead', 'tr', 'ul'
    ].indexOf(element.tagName.toLowerCase()===-1)
    for (var i= 0; i<element.childNodes.length; i++) {
        var child= element.childNodes[i];
        if (child.nodeType===3 && textok)
            texts.push(child);
        if (child.nodeType===1)
            collectTextNodes(texts, child);
    };
}

function getPositionInStrings(strs, index, toend) {
    var ix= 0;
    for (var i= 0; i<strs.length; i++) {
        var n= index-ix, l= strs[i].length;
        if (toend? l>=n : l>n)
            return [i, n];
        ix+= l;
    }
    return [i, 0];
}


// We've used a few ECMAScript Fifth Edition Array features.
// Make them work in browsers that don't support them natively.
//
if (!('indexOf' in Array.prototype)) {
    Array.prototype.indexOf= function(find, i /*opt*/) {
        if (i===undefined) i= 0;
        if (i<0) i+= this.length;
        if (i<0) i= 0;
        for (var n= this.length; i<n; i++)
            if (i in this && this[i]===find)
                return i;
        return -1;
    };
}
if (!('forEach' in Array.prototype)) {
    Array.prototype.forEach= function(action, that /*opt*/) {
        for (var i= 0, n= this.length; i<n; i++)
            if (i in this)
                action.call(that, this[i], i, this);
    };
}
if (!('map' in Array.prototype)) {
    Array.prototype.map= function(mapper, that /*opt*/) {
        var other= new Array(this.length);
        for (var i= 0, n= this.length; i<n; i++)
            if (i in this)
                other[i]= mapper.call(that, this[i], i, this);
        return other;
    };
}

Derin Bunun için Regex karanlık ormanda içine almak gerekir, ama her eleman için aynı sınıf uygulamak istiyorsanız ne olurdu değeri bunu yaparken emin değilim. Eğer cehennem bükük yeni bir yayılma olan her eleman üzerinde iseniz, o zaman bu sayfa yardımcı olabilir: http://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx

Gerçekten yapmak daha mantıklı şey sadece sayfadaki her şeyi bir yinelenen sınıf eklemek için iyi bir nedeniniz var sürece class = gövde elemanına "işaretli" uygulamak olacaktır.

http://www.php.net/manual/en/function.preg-quote.php alınan

$textbody = "This book is very difficult to find.";
$word = "very";
$textbody = preg_replace ("/" . preg_quote($word) . "/",
                          "<i>" . $word . "</i>",
                          $textbody);

XSL iş bu tür için doğru araçtır. Böyle bir şey yapabilirsiniz,

<?php    
$oldXml= <<<EOT
<html>
<head>
    <title>My Name Is Josh!!!</title>
</head>
<body>
    <h1>my name is <b>josh</b></h1>
    <div>
        <a href="http://www.names.com">my name</a> is josh
    </div>

    <u>my</u> <i>name</i> <b>is</b> <span style="font-family: Tahoma;">Josh</span>.
</body>
</html>
EOT;

$temp = <<<EOT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*">
<xsl:copy><xsl:copy-of select="@*"/><xsl:apply-templates/></xsl:copy>
</xsl:template>
<xsl:template match="text()">
<span class="marked">
<xsl:value-of select="current()"/>
</span>
</xsl:template>
</xsl:stylesheet>
EOT;    

$xml = new DOMDocument;
$xml->loadXML($oldXml);
$xsl = new DOMDocument;
$xsl->loadXML($temp);
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl); // attach the xsl rules    
$newXml = $proc->transformToXML($xml);
echo $newXml;

HTML, bu iş için iyi biçimlendirilmiş XHTML olmalıdır.

Burada ne istediğinizi tam olarak gönderebilir.

$string='<html>
<head>
    <title>My Name Is Josh!!!</title>
</head>
<body>
    <h1>my name is <b>josh</b></h1>
    <div>
        <a href="http://www.names.com">my name</a> is josh
    </div>

    <u>my</u> <i>name</i> <b>is</b> <span style="font-family: Tahoma;">Josh</span>.
</body>
';
$string=preg_replace('/>.+</','><span class="marked">$0</span><',$string);
$string=str_replace('<<','<',$string);
$string=str_replace('>>','>',$string);
echo $string;