/** * Extracts attributes and content from an XML tag string. * * @param string $prefix The prefix of the tag definition. * @param string $name The name of the tag definition. * @param string $tagString The string, that contains the tag definition. * * @return string[] The attributes of the tag. * @throws ParserException In case of tag mismatch. * * @author Christian Achatz * @version * Version 0.1, 22.12.2006<br /> * Version 0.2, 30.12.2006 (Bug-fix: tag-to-attribute delimiter is now a constant value)<br /> * Version 0.3, 03.01.2007<br /> * Version 0.4, 13.01.2007 (Improved error messages)<br /> * Version 0.5, 16.11.2007 (Improved error message. Now affected tag string is displayed, too)<br /> * Version 0.6, 03.11.2008 (Fixed the issue, that a TAB character is no valid token to attributes delimiter)<br /> * Version 0.7, 04.11.2008 (Fixed issue, that a combination of TAB and SPACE characters leads to wrong attributes parsing)<br /> * Version 0.8, 05.11.2008 (Removed the TAB support due to performance and fault tolerance problems)<br /> * Version 0.9, 26.09.2012 (Introduced additional arguments for prefix and name to gain performance)<br /> * Version 1.0, 23.12.2013 (ID#112: fixed parser issue with nested tags of the same tag name)<br /> */ public static function getTagAttributes($prefix, $name, $tagString) { // search for taglib to attributes string delimiter $tagAttributeDel = strpos($tagString, ' '); // search for the first appearance of the closing sign after the attribute string $posTagClosingSign = strpos($tagString, '>'); // In case, the separator between tag and attribute is not found, or in case the tag // end position is located between the tag and the attribute, the end sign (">") is used // as separator. This allows tags without attributes. if ($tagAttributeDel === false || $tagAttributeDel > $posTagClosingSign) { $tagAttributeDel = strpos($tagString, '>'); } // extract the rest of the tag string. $attributesString = substr($tagString, $tagAttributeDel + 1, $posTagClosingSign - $tagAttributeDel); // ID#253: In case we are using an extended templating expression within a tag attribute // (e.g. "model[0]->getFoo()") the ending ">" is contained within the attribute and thus the first // substr() produces wrong results. For this reason, search for the last ">" with an even number of // quotes in the string to fix this. $parserLoops = 0; while (substr_count($attributesString, '"') % 2 !== 0) { $parserLoops++; // limit parse loop count to avoid endless searching if ($parserLoops > self::$maxParserLoops) { throw new ParserException('[XmlParser::getTagAttributes()] Error while parsing: "' . $tagString . '". Maximum number of loops ("' . self::$maxParserLoops . '") exceeded!', E_USER_ERROR); } $posTagClosingSign = strpos($tagString, '>', $posTagClosingSign + 1); $attributesString = substr($tagString, $tagAttributeDel + 1, $posTagClosingSign - $tagAttributeDel); } // parse the tag's attributes $tagAttributes = XmlParser::getAttributesFromString($attributesString); // Check, whether the tag is self-closing. If not, read the content. if (substr($tagString, $posTagClosingSign - 1, 1) == '/') { $content = ''; } else { // search for the outer-most explicit closing tag to support nested tag hierarchies $tagEndPos = strrpos($tagString, '</' . $prefix . ':' . $name . '>'); if ($tagEndPos === false) { throw new ParserException('[XmlParser::getTagAttributes()] No closing tag found for ' . 'tag "<' . $prefix . ':' . $name . ' />"! Tag string: "' . $tagString . '".', E_USER_ERROR); } // read the content of the tag $content = substr($tagString, $posTagClosingSign + 1, $tagEndPos - $posTagClosingSign - 1); } return ['attributes' => $tagAttributes, 'content' => $content]; }