2012-03-24 3 views
1

In ein paar verschiedenen Formen habe ich über diesen "Filter" hier und WPSE gefragt. Ich gehe jetzt einen anderen Ansatz ein und möchte es solide und zuverlässig machen.Zuverlässige und effektive benutzerdefinierte Suche und ersetzen Funktion - Preg oder Str ersetzen

Meine Situation:

  • Wenn ich einen Beitrag in meinem Wordpress-CMS erstellen, möchte ich einen Filter laufen zu lassen, die nach bestimmten Begriffen durchsucht und ersetzt sie durch Links.

  • Ich habe die Begriffe, nach denen ich in zwei Arrays suchen möchte: $glossary_terms und $species_terms.

  • $species_terms ist eine Liste von wissenschaftlichen Namen von Fischen, wie Apistogramma panduro.

  • $glossary_terms ist eine Liste der Aquaristik Glossar Begriffe wie abdomen, caudal-fin und Gram's Method.

Es gibt ein paar Nuancen erwähnenswert:

  • Geschwindigkeit ist nicht ein Problem, da ich diesen Filter im Hintergrund laufen statt, wenn ein Nutzer die Seite oder wenn ein Autor ein Artenprofil oder einen Beitrag einreicht/bearbeitet.

  • Einige der zu filternden Post-Inhalte können HTML mit diesen Begriffen wie <img src="image.jpg" title="Apistogramma panduro male" /> enthalten. Offensichtlich sollten diese nicht ersetzt werden.

  • Arten werden oft mit einem abgekürzten Genus bezeichnet, so dass Sie anstelle von Apistogramma panduro oft A. panduro sehen. Das bedeutet, ich brauche suchen & alle Arten Begriffe als Abkürzung ersetzen - Apistogramma panduro, A. panduro, Satanoperca daemon, S. daemon usw.

  • Wenn caudal-fin und caudal sowohl in den Glossarbegriffe vorhanden sind, sollten caudal-fin zuerst ersetzt werden.

ich in Erwägung zog einfach ein preg_replace Zugabe, die für die Begriffe gesucht, aber nur mit einem Raum auf der linken Seite (dh ()term) und ein Leerzeichen, Komma, Ausrufezeichen, Vollanschlag oder Bindestrich auf dem rechten Seite (dh term(, . ! -)) aber das wird mir nicht helfen, das Bild HTML nicht zu brechen.


Beispiel Inhalt

<br /> 
It looks very similar to fishes of the <i><a href="species/betta-foerschi" rel="species/betta-foerschi/?hover=true" class="link_species">B. foerschi</a></i> group/complex but its breeding strategy, adult size and observed behaviour preclude its inclusion in that <a href="glossary/a/assemblage" rel="glossary/a/assemblage?hover=true" class="link_glossary">assemblage</a>. 

Instead it appears to be a member of the <i><a href="species/betta-coccina" rel="species/betta-coccina/?hover=true" class="link_species">B. coccina</a></i> group which currently includes <i><a href="species/betta-brownorum" rel="species/betta-brownorum/?hover=true" class="link_species">B. brownorum</a></i>, <i><a href="species/betta-burdigala" rel="species/betta-burdigala/?hover=true" class="link_species">B. burdigala</a></i>, <i><a href="species/betta-coccina" rel="species/betta-coccina/?hover=true" class="link_species">B. coccina</a></i>, <i><a href="species/betta-livida" rel="species/betta-livida/?hover=true" class="link_species">B. livida</a></i>, <i>B. miniopinna</i>, <i><a href="species/betta-persephone" rel="species/betta-persephone/?hover=true" class="link_species">B. persephone</a></i>, <i>B. tussyae</i>, <i><a href="species/betta-rutilans" rel="species/betta-rutilans/?hover=true" class="link_species">B. rutilans</a></i> and <i><a href="species/betta-uberis" rel="species/betta-uberis/?hover=true" class="link_species">B. uberis</a></i>. 

Of these it's most similar in appearance to <i><a href="species/betta-uberis" rel="species/betta-uberis/?hover=true" class="link_species">B. uberis</a></i> but can be distinguished by its noticeably shorter <a href="glossary/d/dorsal" rel="glossary/d/dorsal?hover=true" class="link_glossary">dorsal</a>-<a href="glossary/f/fin" rel="glossary/f/fin?hover=true" class="link_glossary">fin</a> <a href="glossary/b/base" rel="glossary/b/base?hover=true" class="link_glossary">base</a> and overall blue-greenish (vs. green/reddish) colouration. 

Members of this group are characterised by their small adult size (&lt; 40 mm SL), a uniform red or black <a href="glossary/b/base" rel="glossary/b/base?hover=true" class="link_glossary">base</a> body colour, the presence of a <a href="glossary/m/midlateral" rel="glossary/m/midlateral?hover=true" class="link_glossary">midlateral</a> body blotch in some <a href="glossary/s/species" rel="glossary/s/species?hover=true" class="link_glossary">species</a> and the fact they have 9 abdominal <a href="glossary/v/vertebrae" rel="glossary/v/vertebrae?hover=true" class="link_glossary">vertebrae</a> compared with 10-12 in the other <a href="glossary/s/species" rel="glossary/s/species?hover=true" class="link_glossary">species</a> groups. In addition all are <a href="glossary/o/obligate" rel="glossary/o/obligate?hover=true" class="link_glossary">obligate</a> <a href="glossary/p/peat" rel="glossary/p/peat?hover=true" class="link_glossary">peat</a> <a href="glossary/s/swamp" rel="glossary/s/swamp?hover=true" class="link_glossary">swamp</a> dwellers (Tan and Ng, 2005).<br /> 

^^^ Dieses Beispiel hier wird manuell eingefügt, um die richtigen Verbindungen hat. Der Filter sollte diese Links nicht brechen!

It looks very similar to fishes of the B. foerschi group/complex but its breeding strategy, adult size and observed behaviour preclude its inclusion in that assemblage. 

Instead it appears to be a member of the B. coccina group which currently includes B. brownorum, B. burdigala, B. coccina, B. livida, B. miniopinna, B. persephone, B. tussyae, B. rutilans and B. uberis. 

Of these it's most similar in appearance to B. uberis but can be distinguished by its noticeably shorter dorsal-fin base and overall blue-greenish (vs. green/reddish) colouration. 

Members of this group are characterised by their small adult size (< 40 mm SL), a uniform red or black base body colour, the presence of a midlateral body blotch in some species and the fact they have 9 abdominal vertebrae compared with 10-12 in the other species groups. In addition all are obligate peat swamp dwellers (Tan and Ng, 2005). 

^^^ Das gleiche Beispiel Vorformatierung.

[caption id="attachment_542" align="alignleft" width="125" caption="Amazonas Magazine - now in English!"]<a href="http://www.seriouslyfish.comwp-content/uploads/2011/12/Amazonas-English-1.jpg"><img class="size-thumbnail wp-image-542" title="Amazonas English" src="/wp-content/uploads/2011/12/Amazonas-English-1-288x381.jpg" alt="Amazonas English" width="125" height="165" /></a>[/caption] 

Edited by Hans-Georg Evers, the magazine 'Amazonas' has been widely-regarded as among the finest regular publications in the hobby since its launch in 2005, an impressive achievment considering it's only been published in German to date. The long-awaited English version is just about to launch, and we think a subscription should be top of any serious fishkeeper's Xmas list... 

The magazine is published in a bi-monthly basis and the English version launches with the January/February 2012 issue with distributors already organised in the United States, Canada, the United Kingdom, South Africa, Australia, and New Zealand. There are also mobile apps availablen which allow digital subscribers to read on portable devices. 

It's fair to say that there currently exists no better publication for dedicated hobbyists with each issue featuring cutting-edge articles on fishes, invertebrates, aquatic plants, field trips to tropical destinations plus the latest in husbandry and breeding breakthroughs by expert aquarists, all accompanied by excellent photography throughout. 

U.S. residents can subscribe to the printed edition for just $29 USD per year, which also includes a free digital subscription, with the same offer available to Canadian readers for $41 USD or overseas subscribers for $49 USD. Please see the <a href="http://www.amazonasmagazine.com/">Amazonas website</a> for further information and a sample digital issue! 

Alternatively, subscribe directly to the print version <a href="https://www.amazonascustomerservice.com/subscribe/index2.php">here</a> or digital version <a href="https://www.amazonascustomerservice.com/subscribe/digital.php">here</a>. 

^^^ Dies wird wahrscheinlich nur einige Glossarbegriffe anstelle von irgendwelchen Artenverknüpfungen haben.


Beispiel Begriffe

$species_terms

339 => 'Aulonocara maylandi maylandi', 
340 => 'Aulonocara maylandi kandeensis', 
341 => 'Aulonocara sp. "walteri"', 
342 => 'Aulonocara sp. "stuartgranti maleri"', 
343 => 'Aulonocara stuartgranti', 
344 => 'Benthochromis tricoti', 
345 => 'Boulengerochromis microlepis', 
346 => 'Buccochromis lepturus', 
347 => 'Buccochromis nototaenia', 
348 => 'Betta brownorum', 
349 => 'Betta foerschi', 
350 => 'Betta coccina', 
351 => 'Betta uberis' 

Wie Sie oben sehen können, ist das allgemeine Format dieser wissenschaftlichen Namen "Genus species", kann aber oft auch "sp." oder "aff." (für Arten, die nicht offiziell beschrieben sind) und "Genus species subspecies" Formate.

$glossary_terms

1 => 'abdomen', 
2 => 'caudal', 
3 => 'caudal-fin', 
4 => 'caudal-fin peduncle', 
5 => 'Gram\'s Method' 

Wenn jemand mit einem Filter kommen kann, die diese Bedingungen und alle Anforderungen erfüllen, würde Ich mag eine Prämie anbieten zu können.

Vielen Dank im Voraus,

+0

Nur ein Gedanke. Wäre es möglich, zuerst alle '' mit 'preg_replace_callback' zu extrahieren und jede '' durch eine eindeutige Kennung zu ersetzen. Danach können Sie Ihren eigenen Preg_replace ausführen. Danach ersetzen Sie jeden eindeutigen Link durch seinen jeweiligen Inhalt. Voila? –

Antwort

4

Ich denke, es ist besser, DOMDocument Funktionalität als Regexps zu verwenden. Hier ist ein funktionsfähiger Prototyp:

// Each dynamically constructed regexp will contain at most 70 subpatterns 
define('GROUPS_PER_REGEXPS', 70); 

$speciesTerms = array(
    339 => '(?:Aulonocara|A\.) maylandi maylandi', 
    340 => '(?:Aulonocara|A\.) maylandi kandeensis', 
    344 => '(?:Benthochromis|B\.) tricoti', 
    345 => '(?:Boulengerochromis|B\.) microlepis', 
); 

function matchTerms($text) { 
    // Globals are not good. I left it for the simplicity 
    global $speciesTerms; 

    $result = array(); 
    $t = 0; 
    $speciesCount = count($speciesTerms); 
    reset($speciesTerms); 
    while ($t < $speciesCount) { 
    // Maps capturing group identifiers to term ids 
    $termMapping = array(); 

    // Dynamically construct regexp 
    $groups = ''; 
    $c = 1; 
    while (list($termId, $termPattern) = each($speciesTerms)) { 
     if (!empty($groups)) { 
     $groups .= '|'; 
     } 
     // Match word boundaries, so we don't capture "B. tricotisomeramblingstring" 
     $groups .= '(\b' . $termPattern . '\b)'; 
     $termMapping[$c++] = $termId; 
     if (++$t % GROUPS_PER_REGEXPS == 0) { 
     break; 
     } 
    } 
    $regexp = "/$groups/m"; 
    preg_match_all($regexp, $text, $matches, PREG_OFFSET_CAPTURE); 
    for ($i = 1; $i < $c; $i++) { 
     foreach ($matches[$i] as $matchData) { 
     // matchData[0] holds matched string, e.g. Benthochromis tricoti 
     // matchData[1] holds offset, e.g. 15 
     if (isset($matchData[0]) && !empty($matchData[0])) { 
      $result[] = array(
      'text' => $matchData[0], 
      'offset' => $matchData[1], 
      'id' => $termMapping[$i], 
     ); 
     } 
     } 
    } 
    } 
    // Sort by offset in descending order 
    usort($result, function($a, $b) { 
    return $a['offset'] > $b['offset'] ? -1 : 1; 
    }); 
    return $result; 
} 

$doc = DOMDocument::loadHTML($html); 

// Stack will be used to avoid recursive functions 
$stack = new SplStack; 
$stack->push($doc); 
while (!$stack->isEmpty()) { 
    $node = $stack->pop(); 
    if ($node->nodeType == XML_TEXT_NODE && $node->parentNode instanceof DOMElement) { 
    // $node represents text node 
    // and it's inside a tag (second condition in the statement above) 

    // Check that this text is not wrapped in <a> tag 
    // as we don't want to wrap it twice 
    if ($node->parentNode->tagName != 'a') { 
     $matches = matchTerms($node->wholeText); 
     foreach ($matches as $match) { 
     // Create new link element in the DOM 
     $link = $doc->createElement('a', $match['text']); 
     $link->setAttribute('href', 'species/' . $match['id']); 
     $link->setAttribute('class', 'link_species'); 

     // Save the text after the link 
     $remainingText = $node->splitText($match['offset'] + strlen($match['text'])); 
     // Save the text before the link 
     $linkText = $node->splitText($match['offset']); 

     // Replace $linkText with $link node 
     // i.e. 'something' becomes '<a href="..">something</a>' 
     $node->parentNode->replaceChild($link, $linkText); 
     } 
    } 
    } 
    if ($node->hasChildNodes()) { 
    foreach ($node->childNodes as $childNode) { 
     $stack->push($childNode); 
    } 
    } 
} 

$body = $doc->getElementsByTagName('body'); 
echo $doc->saveHTML($body->item(0)); 

Implementierungsdetails

ich nur gezeigt haben, wie Arten Begriffe zu ersetzen, Glossarbegriffe gleich sein. Links werden in der Form "species/$ id" gebildet. Abkürzungen werden korrekt behandelt. DOMDocument ist ein sehr zuverlässiger Parser, es kann mit gebrochenem Markup umgehen und ist schnell.

?: in Regexp ermöglicht es nicht, dieses Untermuster als Erfassungsgruppe zu zählen (documentation on subpatterns). Ohne das korrekte Zählen von Untermustern können wir die termId nicht abrufen. Die Idee ist, dass wir ein großes Regexp-Muster erstellen, indem wir alle im $speciesTerms-Array spezifizierten Regexps verbinden und sie mit einer Pipe trennen |. Schluss regexp für die ersten beiden Spezies wäre (Räume für Klarheit):

 First capturing group    Alternation  Second capturing group 
((?:Aulonocara|A\.) maylandi maylandi)  |  ((?:Aulonocara|A\.) maylandi kandeensis) 

So der Text "Beispiele: Aulonocara maylandi maylandi, A. maylandi kandeensis" werden folgende Begegnungen geben:

$matches[1] = array('Aulonocara maylandi maylandi') // Captured by the first group 
$matches[2] = array('A. maylandi kandeensis') // Captured by the second group 

Wir können klar sagen, dass sich alle Elemente in matches[1] auf die Spezies Aulonocara maylandi maylandi oder A. maylandi maylandi beziehen, die id = 339 hat.

Kurz: Verwenden Sie (?:) wenn Sie Subpattern in $speciesTerms verwenden.

UPDATE Jede dynamisch erstellt regexp hat eine Begrenzung auf eine maximale Anzahl von Untermustern, die als const an der Spitze definiert ist. Dies ermöglicht die Vermeidung der PCRE-Begrenzung für die Anzahl der Untermuster in regexp.

Wichtige Hinweise:

  • Wenn Sie viele Begriffe haben Sie sollten matchTerms umschreiben, weil regexp eine Grenze für eine Anzahl von Unter-Patterns hat. In diesem Fall ist es optimal, ein Array von Regexps aus allen N Termen zu erstellen.
  • matchTerms erzeugt regexp bei jedem Anruf, natürlich kann es nur einmal durchgeführt werden
  • Es ist möglich, erweiterte regexps zu verwenden, in speciesTerms
  • strlen =>mb_strlen wenn Sie Multibyte-Codierungen verwenden sind
  • Lieferung $html wird gewickelt werden in einem <body> Tag (es sei denn, es ist bereits verpackt)
+0

Fantastische Antwort, danke. Ich werde versuchen, es jetzt zu implementieren, obwohl ich etwas besorgt bin, dass es zu komplex ist, um es zu debuggen, wenn es nicht funktioniert! Was macht das '(?:' Vor den Arten Begriffen? – dunc

+1

@dunc Ja, es sieht aus wie eine Menge Code, aber es gibt Ihnen eine große Flexibilität. Außerdem kann der passende Code ('matchTerms') separat getestet werden Code zu ersetzen (Manipulationen mit 'DOMDocument'). Das bedeutet, es wird einfacher zu debuggen und zu testen – galymzhan

+0

Hallo @ Galymzhan, danke für Ihre Antwort. Leider, dank 1360 Arten Namen, bekomme ich den folgenden Fehler:' Warnung: preg_match_all(): Kompilierung fehlgeschlagen: Regulärer Ausdruck ist bei Offset 34743 in /dunc/test.php in Zeile 14 zu groß. Gibt es etwas, was man tun kann, um den Ausdruck zu optimieren? Ich habe versucht, die notwendigen pcre-Einstellungen mit 'zu erhöhen ini_set', aber es macht keinen Unterschied – dunc

2

Es wäre besser, den HTML-Code zu analysieren, anstatt zu versuchen, reguläre Ausdrücke zu verwenden. Regex ist gut, wenn Sie etwas Bestimmtes haben, das Sie abgleichen möchten, aber es wird etwas eigenartig, wenn Sie versuchen, bestimmte Dinge NICHT zu finden.

http://simplehtmldom.sourceforge.net/ Verwendung:

function addLinks(&$p, $species, $terms) { 

    // much easier to say "not in an anchor tag" with parsed content than with regex 
    if ($p->tag != 'a') { 

    // pull out existing elements so they aren't replaced 
    $children = array(); 
    $x = 0; 

    foreach ($p->children as &$e) { 
     $children[] = $e->outertext; 
     $e->outertext = '---child-'.$x.'---'; 
     $x++; 
    } 

    foreach($species as $s) { 
     $p->innertext = str_replace(
      $s, 
      '<a href="species/'.strtolower(str_replace(' ','-',$s)).'">'.$s.'</a>', 
      $p->innertext); 
    } 

    foreach($term as $t) { 
     $p->innertext = str_replace(
      $t, 
      '<a href="glossary/'. 
       strtolower($t[0]).'/'. 
       strtolower(str_replace(' ','-',$t)).'">'.$t.'</a>', 
      $p->innertext); 
    } 

    // restore previous child elements 
    foreach($children as $x => $e) { 
     $p->innertext = str_replace('---child-'.$x.'---', $e, $p->innertext); 
    } 

    foreach ($p->children() as &$e) { 
     addLinks($e, $species, $terms); 
    } 
    } 
} 


$html = new simple_html_dom(); 

// you may have to wrap $content in a div. not exactly sure how partial content is handled 
$html->load($content); 

addLinks($html, $species_terms, $glossary_terms); 
$content = $html->save(); 

ich nicht simple_html_dom eine ganze Menge verwendet haben, aber das sollte man in die richtige Richtung wies.

+0

Ich erkannte, dass das Ersetzen von Innertext wahrscheinlich das gleiche Problem mit vorhandenen Anchor-Tags und -Bildern verursacht, also fügte ich etwas hinzu, um existierende Kindelemente herauszuziehen und dann wiederherzustellen, so dass sie nicht durch das Ersetzen auf dem Elternteil betroffen sind. –

+0

Ich habe verstanden, was Sie bis zu diesen Änderungen gemacht haben! :) Kannst du genau erklären, wie das funktioniert? – dunc

+0

$ p-> internertext kann zurückgeben "das ist ein Text und hier ist a link das ist drin". Also fängt der str_replace immer noch den Text innerhalb dieses Ankers ab und verbindet ihn doppelt. Die Änderungen, die ich hinzugefügt habe, entfernen vorübergehend alle untergeordneten Elemente, führen die Ersetzungen durch und fügen sie dann wieder hinzu. Ich werde versuchen, das zu testen, was ich denke, wenn ich eine Chance bekomme. –