private function to_struct($message) { // Initialize a few useful things. // Such as the current position within the string. $cur_pos = 0; // The index within $struct that contains the last text index, this way // we can append anything to it if we have to (we don't want one text // node after another). $last_text_index = -1; $length = $this->strlen($message); $prev_pos = 0; $struct = array(); // We also don't want to recalculate the size of $struct over and over // again. $struct_length = 0; // The current level. $current_level = 0; $opened_tags = array(); $opened_count = 0; // Let's look for a possible tag. while (($pos = $this->strpos($message, '[', $cur_pos)) !== false && $pos + 1 < $length && $this->substr($message, $pos + 1, 1) != ' ') { // Before we handle the possible preceding text, why don't we make sure // that this tag will work? $last_pos = $pos; while ($pos < $length) { $amp_pos = $this->strpos($message, '&', $pos); $brk_pos = $this->strpos($message, ']', $pos); // Does the bracket come before the ampersand? // But the ampersand may not even be a quote as well! if ($amp_pos === false || $brk_pos === false || $brk_pos < $amp_pos || !in_array($quote_type = $this->substr($message, $amp_pos, 6), array('"', '''))) { // Sweet! $pos = $brk_pos; break; } // Now, can we find the next quote? while ($amp_pos + 6 < $length && ($amp_pos = $this->strpos($message, $quote_type, $amp_pos + 6)) !== false && $this->substr($message, $amp_pos - 1, 1) == '\\') { } // Did our search come up with nothing? if ($amp_pos + 6 >= $length || $amp_pos === false) { break; } $pos = $amp_pos + 6; } // So, did we find a valid tag? if ($this->substr($message, $pos, 1) == ']') { // Yup, we sure did! So we will want to make a node to contain the // text preceding the tag. $saved = false; if ($prev_pos != $last_pos) { $node = new TextNode($this->substr($message, $prev_pos, $last_pos - $prev_pos), $current_level); $struct[$struct_length++] = $node; $last_text_index = $struct_length - 1; $saved = true; } // Now for the tag itself. $node = new TagNode($this->substr($message, $last_pos, $pos - $last_pos + 1), $this->substr($message, $last_pos + 1, 1) == '/', 0, $this); // Woah there, horsey! Do we even have a tag by that name? if (isset($this->index['names'][$node->getTag()])) { // Yup, we do. $struct[$struct_length++] = $node; $last_text_index = -1; // Maybe this tag is being opened? if (!$node->is_closing()) { // Set the tag nodes current level, along with adding it to the // list of opened tags. $node->setLevel($current_level); // We need to set the parent node of $node, along with adding // $node to the children of the parent. // This is pretty straightforward, as it is the most recently // opened tag. $node->parentNode($opened_count > 0 ? $opened_tags[$opened_count - 1]['tag'] : null); // If there was no parent node, then it can't really be a child, // can it? if ($node->parentNode() !== null) { // This will add this node along with its other children. $node->parentNode()->childNodes($node); } // Before we add this to the list of opened tags, we will check // whether it is an empty tag, because if it is, it doesn't get // opened, so it won't need to be closed ;-) if (!$this->tag_is_empty($node->getTag())) { $opened_tags[$opened_count++] = array('tag' => $node->getTag(), 'level' => $current_level++, 'pos' => $struct_length - 1); } } else { // First we need to see if this tag was ever opened. $stop = $opened_count; for ($index = $opened_count - 1; $index >= 0; $index--) { if ($opened_tags[$index]['tag'] == $node->getTag()) { // It looks like the tag was opened, at some point. $stop = $index; break; } } // Did we find the opening tag? if ($stop < $opened_count) { // We may need to move the current node back so we can insert // any tags that need closing. $current = $opened_count - 1; // We will want to overwrite the current node (which will be // added back after the loop). $struct_length = $struct_length - 1; while ($current > $stop) { // Take off the last tag... $opened_tag = $opened_tags[$current]; unset($opened_tags[$current]); // Also subtract one from the total opened tag count. $opened_count--; // Add the closing tag to the structure. $struct[$struct_length++] = new TagNode('[/' . $opened_tag['tag'] . ']', true, $opened_tag['level']); // !!! Do closing tags need parents? // Let's tell the opening tag where the ending tag is // located, which will make some things quite a bit faster // later. $struct[$opened_tag['pos']]->setClosingAt($struct_length - 1); // Move to the next. $current--; } // Now add the current tag we were supposed to be dealing with // back to $struct. $struct[$struct_length++] = $node; // Set a couple important things. $node->setLevel($opened_tags[$opened_count - 1]['level']); $struct[$opened_tags[$opened_count - 1]['pos']]->setClosingAt($struct_length - 1); // !!! Do closing tags need parents? // Now remove it from the list of opened tags. unset($opened_tags[--$opened_count]); // Now set the proper level. $current_level = $node->level() - 1; } else { // We will just ignore this tag, then. $node->setIgnore(true); } } // Now, everything has been handled up to this point. $prev_pos = $pos + 1; } elseif (!empty($saved)) { // We want to save this with the previous text node if we can. if ($last_text_index > -1) { $struct[$last_text_index]->appendText($node->text()); } else { $struct[$struct_length++] = new TextNode($node->text(), $current_level); $last_text_index = $struct_length - 1; // Text nodes have parents, but they don't have any children. if ($opened_count > 0) { $struct[$struct_length - 1]->parentNode($opened_tags[$opened_count - 1]['tag']); } } $prev_pos = $pos + 1; } } // Alright, let's move on! $cur_pos = $pos + 1; } // Was there some text left? if ($prev_pos < $length) { // Yup, and we don't want to leave it out! $node = new TextNode($this->substr($message, $prev_pos), $current_level); $struct[$struct_length++] = $node; } // Were there any tags that weren't closed by the end of the message? // That's fine, we can fix that. if ($opened_count > 0) { while (--$opened_count >= 0) { $struct[$struct_length++] = new TagNode('[/' . $opened_tags[$opened_count]['tag'] . ']', true, $opened_tags[$opened_count]['level']); $struct[$opened_tags[$opened_count]['pos']]->setClosingAt($struct_length - 1); } } $start_time = microtime(true); for ($index = 0; $index < $struct_length; $index++) { if ($struct[$index]->type() == 'tag') { $struct[$index]->checkConstraints(); if ($struct[$index]->ignore()) { $struct[$struct[$index]->closingAt()]->setIgnore(true); } } } // And here you go. I did my job... We will send along the structure // length as well. No need to have the other method recalculate it. return array($struct, $struct_length); }