1
\$\begingroup\$

PHP program which extracts attribute list from the custom tag. It also strips out the single and double quote if it exists around attribute value.

<?php

function parseAnswerTag($tag)
{
    // input => <answer scale="10" points="7">
    // ouput => ["scale"=>10, "points"=>7]
    // get the answer tag and parse out the attributes of answer tag
    $returningObj = array();
    //get the splitted string as an array
    $arr = splitByDelimiter(array('delimiter' => " ", 'string'=> $tag));
    // sliced the array to just have attributes otherwise will have answer element too
    foreach (array_slice($arr, 1) as $key => $value)
    {
        $arr[$key] = splitByDelimiter(array('delimiter' => "=", 'string'=> $value)); 
        if(in_array('scale', $arr[$key]) or in_array('points', $arr[$key]))
        {
            // check to see if single or double quote exist in value of key
            if(preg_match("/['\"]/",$arr[$key][1]))
            {
                // makes a call to strip out the quotes
                $returningObj[$arr[$key][0]] = stripQuotes($arr[$key][1]); 
            }
        }
    }
    // returns the associate key of attribute in the tag
    return $returningObj;
}

function stripQuotes($str)
{
    // input => "10"
    // output => 10
    // gets attribute with value and returns unquote string
    var_dump(strpos($str, "'") === FALSE);
    $start_point = 0;
    $end_point = 0;
    if(strpos($str, "'") !== FALSE)
    {
        $start_point = strpos($str, "'");
        $end_point = strpos($str, "'", $start_point+1);
    }
    elseif (strpos($str, '"') !== FALSE) 
    {
        $start_point = strpos($str, '"');
        $end_point = strpos($str, '"', $start_point+1);
    }
    return substr($str, $start_point+1, $end_point - ($start_point+1));
} 

function splitByDelimiter($input)
{
    //gets a associate array with delimiter and string {text value to be splitted}, split them by passed delimiter and return them
    // $input['delimiter'] => value splitted by and $input['string'] => value to be splitted
    return explode($input['delimiter'], $input['string']);
}

Can you please give me some constructive suggestions so that I can improve?

\$\endgroup\$
4
  • \$\begingroup\$ Can you add an example usage? It would also be great if you could add a bit more description. Eg what is a tag in this case, exactly? How does it look? \$\endgroup\$
    – tim
    Jun 20 '16 at 10:20
  • \$\begingroup\$ Your input and output in lines 2 and 3 don't seem to match. Output doesn't include scale attribute. Is this intended? \$\endgroup\$ Jun 20 '16 at 11:17
  • \$\begingroup\$ I also notice that 'scale' and 'points' are hard-coded in parseAnswerTag. Does that mean that these are the only attributes you care to parse? \$\endgroup\$ Jun 20 '16 at 11:26
  • \$\begingroup\$ I would like at DOM manipulation not parsing tags as strings. \$\endgroup\$
    – Mike Brant
    Jun 20 '16 at 17:19
2
\$\begingroup\$

My function does not use hard-coded attributes, so it is more flexible than yours. It's also just a few lines of code (I think you made things over-complicated by having very thin wrappers for explode and trim.

function parseAttributesFromTag($tag){
    //The Regex pattern will match all instances of attribute="value"
    $pattern = '/(\w+)=[\'"]([^\'"]*)/';

    //preg_match_all used with the PREG_SET_ORDER flag will build an array
    //for each attribute-value pair present and put it in $matches. eg:
    /* with tag <answer scale='10' points="23">
    Array (
        [0] =>(
                [0] => scale='10
                [1] => scale
                [2] => 10
              )
        [1] => (
                [0] => points="23
                [1] => points
                [2] => 23
            )
    )
    */
    preg_match_all($pattern,$tag,$matches,PREG_SET_ORDER);

    $result = [];
    foreach($matches as $match){
        $attrName = $match[1];

        //parse the string value into an integer if it's numeric,
        // leave it as a string if it's not numeric,
        $attrValue = is_numeric($match[2])? (int)$match[2]: trim($match[2]);

        $result[$attrName] = $attrValue; //add match to results
    }

    return $result;
}

This will turn <tagname attr1="value1" attr2='value2'> into [attr1=>value1, attr2=>value2]. Unlike the other answer, this function supports spaces in attribute value and I have casted the value into a numeric type when it's really a number rather than a string

\$\endgroup\$
3
\$\begingroup\$

Here are some things I noticed:

  1. The function splitByDelimiter() is no more than a wrapper around explode(). Why not use explode() directly?

  2. The function stripQuotes($str) can be a lot shorter: trim($str," '\"");. Note: You should never use ' or " inside attributes, use their HTML equivalents.

  3. Inside parseAnswerTag() you use a foreach loop. Inside it you assign values back into $arr, but under the wrong keys because array_slice() will have generated new numeric keys.

  4. I don't understand why you select for scale and point?

I'm sorry but I cannot help you with other details, because I'm unsure about the origin of the HTML, and what you exactly are trying to accomplish. I did make a shorter version of your function:

function parseAnswerTag($tag)
{
    $inside = trim($tag,'<> ');
    $input  = explode(' ',$inside);
    array_shift($input);
    foreach ($input as $attr)
    {
        $parts = explode('=',$attr);
        $name  = trim($parts[0]);
        $value = trim($parts[1]," '\"");
        $output[$name] = $value;
    }
    return $output;
}

echo '<pre>';
print_r(parseAnswerTag('<answer scale="10" points="7">'));
echo '</pre>';

So trimming can be very useful.

Keep in mind: Algorithms like these are almost always slightly flawed. A simple thing like a double space could trip it up. It's very hard to get it completely right. Especially if you make it complicated or use regular expressions.

I also have to point out that PHP has a build in DOM to deal with HTML code. You can load HTML into it, and manipulate it there. See:

http://php.net/manual/en/class.domdocument.php

In more complex cases this is the preferred way to deal with HTML.

\$\endgroup\$
2
  • \$\begingroup\$ I like your solution; it's easy to follow. One point though: we don't know much about the types of values the OP expects to handle, but he or she should be aware that if there is a space character in the attribute value, this function will fail to parse it properly (it assumes that all spaces demarcate attribute-value pairs \$\endgroup\$ Jun 20 '16 at 14:36
  • \$\begingroup\$ Yes, splitting on spaces and assuming that you get nice name-value pairs is unlikely to work in practice. Preferably use some prebuild DOM stuff, for it has already solved all these problems. I only intended the code as an example that does the same thing, but with simpler code. Giving a DOM example would, I think, go beyond what was asked in the question. \$\endgroup\$ Jun 20 '16 at 14:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.