Намиране на точка в низ, която не е в BBCodes

Имам низ, който съдържа текста на статия. Това е поръсено с BBCodes (между квадратни скоби). Трябва да мога да хвана първите, да речем, 200 знака от статия, без да я прекъсвам в средата на bbcode. Така че имам нужда от индекс, където е безопасно да го отрежа. Това ще ми даде резюмето на статията.

  • Резюмето трябва да бъде минимум 200 знака, но може да бъде по-дълго, за да „избяга“ от bbcode. (тази стойност на дължина всъщност ще бъде параметър към функция).
  • Не трябва да ми дава точка в самостоятелен bbcode (вижте тръбата) като така: [lis|t].
  • Не трябва да ми дава точка между началния и крайния bbcode като така: [url="http://www.google.com"]Go To Goo|gle[/url].
  • Не трябва да ми дава точка вътре в началния или крайния bbcode или между тях в горния пример.

Трябва да ми даде "безопасен" индекс, който е след 200 и не прекъсва BBCodes.

Дано това има смисъл. От известно време се боря с това. Уменията ми за регулярни изрази са само умерени. Благодаря за всяка помощ!


person Sherri    schedule 28.07.2009    source източник
comment
Може да се наложи да преразгледам този проблем благодарение на коментара на krdluzni за това какво ще стане, ако bbcode е обвит около цялата статия. Това, което мисля, че трябва да направя, е да се уверя, че точката на прекъсване не е вътре в началния или крайния код, и след това да затворя всички незатворени тагове. Въпреки че не съм сигурен как да определя дали е вътре в bbcode таг...   -  person Sherri    schedule 20.08.2009


Отговори (5)


Първо, бих предложил да обмислите какво ще правите с публикация, която е изцяло обвита в BBкодове, както често е вярно в случай на етикет за шрифт. С други думи, решението на проблема, както е посочено, лесно ще доведе до „резюмета“, съдържащи цялата статия. Може да е по-ценно да идентифицирате кои етикети са все още отворени и да добавите необходимите BB кодове, за да ги затворите. Разбира се, в случай на връзка, това ще изисква допълнителна работа, за да се гарантира, че няма да я прекъснете.

person krdluzni    schedule 28.07.2009
comment
Оооо, това е много, много добра гледна точка за това какво ще стане, ако всичко е обвито в bbcode. Може да се наложи да преосмисля това. - person Sherri; 29.07.2009

Е, очевидният лесен отговор е да представите своето „резюме“ без никакво маркиране, управлявано от bbcode (regex по-долу, взет от тук)

$summary = substr( preg_replace( '|[[\/\!]*?[^\[\]]*?]|si', '', $article ), 0, 200 );

Въпреки това, вършете работата, която изрично описвате, ще изисква повече от просто регулярен израз. Lexer/parser ще свърши работа, но това е умерено сложна тема. Ще видя дали мога да измисля нещо.

РЕДАКТИРАНЕ

Ето една хубава гето версия на лексер, но за този пример работи. Това преобразува въведен низ в символи на bbcode.

<?php

class SimpleBBCodeLexer
{
  protected
      $tokens = array()
    , $patterns = array(
        self::TOKEN_OPEN_TAG  => "/\\[[a-z].*?\\]/"
      , self::TOKEN_CLOSE_TAG => "/\\[\\/[a-z].*?\\]/"
    );

  const TOKEN_TEXT      = 'TEXT';
  const TOKEN_OPEN_TAG  = 'OPEN_TAG';
  const TOKEN_CLOSE_TAG = 'CLOSE_TAG';

  public function __construct( $input )
  {
    for ( $i = 0, $l = strlen( $input ); $i < $l; $i++ )
    {
      $this->processChar( $input{$i} );
    }
    $this->processChar();
  }

  protected function processChar( $char=null )
  {
    static $tokenFragment = '';
    $tokenFragment = $this->processTokenFragment( $tokenFragment );
    if ( is_null( $char ) )
    {
      $this->addToken( $tokenFragment );
    } else {
      $tokenFragment .= $char;
    }
  }

  protected function processTokenFragment( $tokenFragment )
  {
    foreach ( $this->patterns as $type => $pattern )
    {
      if ( preg_match( $pattern, $tokenFragment, $matches ) )
      {
        if ( $matches[0] != $tokenFragment )
        {
          $this->addToken( substr( $tokenFragment, 0, -( strlen( $matches[0] ) ) ) );
        }
        $this->addToken( $matches[0], $type );
        return '';
      }
    }
    return $tokenFragment;
  }

  protected function addToken( $token, $type=self::TOKEN_TEXT )
  {
    $this->tokens[] = array( $type => $token );
  }

  public function getTokens()
  {
    return $this->tokens;
  }
}

$l = new SimpleBBCodeLexer( 'some [b]sample[/b] bbcode that [i] should [url="http://www.google.com"]support[/url] what [/i] you need.' );

echo '<pre>';
print_r( $l->getTokens() );
echo '</pre>';

Следващата стъпка би била да създадете анализатор, който преминава през тези токени и предприема действия, когато срещне всеки тип. Може би ще имам време да го направя по-късно...

person Peter Bailey    schedule 28.07.2009

Това не звучи като работа за (само) регулярен израз. Логиката на "обикновено програмиране" е по-добър вариант:

  • вземете символ, различен от '[', увеличете брояча;
  • ако срещнете отварящ таг, продължете да напредвате, докато стигнете до затварящия таг (не увеличавайте брояча!);
  • спрете да хващате текст, когато броячът ви достигне 200.
person Bart Kiers    schedule 28.07.2009

Ето едно начало. В момента нямам достъп до PHP, така че може да се нуждаете от някои настройки, за да го накарате да работи. Освен това това няма да гарантира, че етикетите са затворени (т.е. низът може да има [url] без [/url]). Също така, ако даден низ е невалиден (т.е. не всички квадратни скоби са съвпаднали), той може да не върне това, което искате.

function getIndex($str, $minLen = 200)
{
  //on short input, return the whole string
  if(strlen($str) <= $minLen)
    return strlen($str);

  //get first minLen characters
  $substr = substr($str, 0, $minLen);

  //does it have a '[' that is not closed?
  if(preg_match('/\[[^\]]*$/', $substr))
  {
    //find the next ']', if there is one
    $pos = strpos($str, ']', $minLen);

    //now, make the substr go all the way to that ']'
    if($pos !== false)
      $substr = substr($str, 0, $pos+1);
  }

  //now, it may be better to return $subStr, but you specifically
  //asked for the index, which is the length of this substring.
  return strlen($substr);
}
person Kip    schedule 28.07.2009

Написах тази функция, която трябва да прави точно това, което искате. Той преброява n броя символи (с изключение на тези в таговете) и след това затваря тагове, които трябва да бъдат затворени. Примерна употреба, включена в кода. Кодът е на Python, но трябва да бъде наистина лесен за пренасяне към други езици, като например php.

def limit(input, length):
  """Splits a text after (length) characters, preserving bbcode"""

  stack = []
  counter = 0
  output = ""
  tag = ""
  insideTag = 0           # 0 = Outside tag, 1 = Opening tag, 2 = Closing tag, 3 = Opening tag, parameters section

  for i in input:
    if counter >= length: # If we have reached the max length (add " and i == ' '") to not make it split in a word
      break
    elif i == '[':        # If we have reached a tag
      insideTag = 1
    elif i == '/':        # If we reach a slash...
      if insideTag == 1:  # And we are in an opening tag
        insideTag = 2
    elif i == '=':        # If we have reached the parameters
      if insideTag >= 1:  # If we actually are in a tag
        insideTag = 3
    elif i == ']':        # If we have reached the closing of a tag
      if insideTag == 2:  # If we are in a closing tag
        stack.pop()       # Pop the last tag, we closed it
      elif insideTag >= 1:# If we are in a tag, parameters or not
        stack.append(tag) # Add current tag to the tag-stack
      if insideTag >= 0:  # If are in some type of tag
        insideTag = 0
        tag = ""
    elif insideTag == 0:  # If we are not in a tag
      counter += 1
    elif insideTag <= 2:  # If we are in a tag and not among the parameters
      tag += i
    output += i

  while len(stack) > 0:
    output += '[/'+stack.pop()+']'   # Add the remaining tags

  return output

cutText = limit('[font]This should be easy:[img]yippee.png[/img][i][u][url="http://www.stackoverflow.com"]Check out this site[/url][/u]Should be cut here somewhere [/i][/font]', 60)
print cutText
person Håkon    schedule 15.08.2009