public function translate($string)
 {
     $exploder = new StringExploder();
     $flagMap = array();
     $descriptorMap = array();
     foreach ($this->descriptors as $descriptor) {
         $openTag = $descriptor->getTag();
         $closeTag = '/' . $openTag;
         $exploder->addDescriptor(new StringExploderDescriptor("#\\[{$openTag}(=[^\\]]+)?\\]#", $openTag));
         if ($descriptor->hasCloseTag()) {
             $exploder->addDescriptor(new StringExploderDescriptor("#\\[{$closeTag}\\]#", $closeTag));
             $flagMap[$openTag] = $closeTag;
         }
         $descriptorMap[$openTag] = $descriptor;
     }
     $array = $exploder->parse($string);
     while (count($array) > 1 || count($array) > 0 && is_array($array[0])) {
         $closeIndex = -1;
         foreach ($array as $index => $chunk) {
             if (is_array($chunk) && $chunk[1][0] === '/') {
                 $closeIndex = $index;
                 break;
             }
         }
         $openIndex = -1;
         foreach ($array as $index => $chunk) {
             if (is_array($chunk) && $chunk[1][0] !== '/') {
                 if ($closeIndex !== -1 && $index >= $closeIndex) {
                     break;
                 } else {
                     $openIndex = $index;
                 }
             }
         }
         if ($openIndex === -1) {
             if ($closeIndex === -1) {
                 break;
                 // no more tag can be computed
             } else {
                 $tag = $array[$closeIndex][0];
                 throw new Exception("Alone closing tag '{$tag}' at {$closeIndex}");
             }
         }
         $openTag = $array[$openIndex][0];
         $openFlag = $array[$openIndex][1];
         $descriptor = $descriptorMap[$openFlag];
         unset($array[$openIndex]);
         $extractParameter = function ($openTag) {
             $parts = preg_split('#=#', substr($openTag, 1, strlen($openTag) - 2), 2, PREG_SPLIT_NO_EMPTY);
             return count($parts) > 1 ? $parts[1] : null;
         };
         if (!$descriptor->hasCloseTag()) {
             $descriptor = new BBCodeDescriptor($descriptor->getTag(), $descriptor->getOpenTagCallback());
             $descriptor->setContent(null);
             $descriptor->setParameter($extractParameter($openTag));
             $array[$openIndex] = $descriptor;
         } else {
             if ($closeIndex === -1) {
                 throw new Exception("Alone opening tag '{$openTag}' at {$openIndex}");
             } else {
                 $closeTag = $array[$closeIndex][0];
                 $closeFlag = $array[$closeIndex][1];
                 unset($array[$closeIndex]);
                 if ($flagMap[$openFlag] !== $closeFlag) {
                     $extract = substr($string, $openIndex, $closeIndex - $openIndex + strlen($closeTag));
                     throw new Exception("Crossing tags for '{$extract}' at {$openIndex}");
                 } else {
                     $content = array();
                     $contentIndex = $openIndex + strlen($openTag);
                     while ($contentIndex < $closeIndex) {
                         $row = $array[$contentIndex];
                         unset($array[$contentIndex]);
                         $content[] = $row;
                         $row = $row instanceof BBCodeDescriptor ? $row->toString() : $row;
                         $contentIndex += strlen($row);
                     }
                     if (count($content) == 1) {
                         $content = $content[0];
                     } else {
                         if (count($content) == 0) {
                             $content = null;
                         }
                     }
                     $descriptor = new BBCodeDescriptor($descriptor->getTag(), $descriptor->getOpenTagCallback(), $descriptor->getCloseTagCallback(), $descriptor->getContentCallback());
                     $descriptor->setContent($content);
                     $descriptor->setParameter($extractParameter($openTag));
                     $array[$openIndex] = $descriptor;
                 }
             }
         }
         ksort($array);
     }
     $root = new BBCodeDescriptor(null, null);
     $root->setContent($array);
     $html = $root->generateHTML();
     return $html;
 }