3
\$\begingroup\$

I have a text file that uses the convention #\d+ to mean ASCII character number \d+. I want to replace any such representations with the actual character. This is what I've come up with. It works but it feels kludgy. Suggestions to improve efficiency?

<?php

$line= 'stuff #65 more #66  and #67; other';
preg_match_all("/\#\d+/",$line,$p);//find any #numbers that should be translated to ascii
$r=array_map(
    function($x){ return chr(trim($x,'#')); },
    $p[0]); //assemble set of replacements

$line=str_replace($p[0],$r,$line); //replace
echo $line; //stuff A more B  and C; other
?>
\$\endgroup\$
3
  • \$\begingroup\$ You might want to check out the /e modifier. It's usually disabled on hardened systems, though. \$\endgroup\$
    – Dagg
    Feb 10, 2012 at 18:54
  • \$\begingroup\$ @GGG: nice idea! but unfortunately off on my system. \$\endgroup\$
    – dnagirl
    Feb 10, 2012 at 19:07
  • \$\begingroup\$ It only works on preg_replace. But if you're on shared hosting or using suhosin, yeah, it's probably disabled entirely. \$\endgroup\$
    – Dagg
    Feb 10, 2012 at 19:16

1 Answer 1

4
\$\begingroup\$

Warning: The code below is intended to be compatible with the original post. Its main benefit is that it retains all of the useful detail discovered in the pattern match (unmatched substrings, relative position of matches), eliminating the need to rescan the line. I'd expect it to be faster for that reason.

It retains the problem that chr is a blunt instrument that can effortlessly consume many digits (not just one, two, or three), using "wrapping" (a.k.a. modulo 256 math) to scale the resulting integer down to a legal byte value, possibly not the one intended.

In other words, this code would fail the conversion of a string with ASCII codes immediately followed by digits, e.g. if "From A1 to z52" needs to be expressable as "From #651 to #12252". So, a different approach may be needed.

One approach is for the conversion step to convert the digit string to integer digits one digit at a time, doing the (digits*10 + digit) math and stopping (or actually backing up one digit) when a value of 256 or greater is reached.

Another approach, probably faster, is for the conversion step to convert a substring (up to three digits) of the digit string to an integer and if it is 256 or greater "back up one digit", dividing the integer by 10.

In either case, any unused/backed-up digits would be appended to the result of chr applied to the integer.

In either of these approaches, but especially the first, some work might be saved by having the regex match at most three digits instead of any number of digits -- leaving the rest as unmatched characters at the head of the next "surrounding text" part. That way, there would be no need to test for integer overflow from way too many digits.

A "higher-tech" approach could go further, enabling the current simple $parts[$i] = chr($parts[$i]) conversion to be retained. This would require beefing up the regex so that it matched, in order of preference, a 3-digit number less than 256 OR a 2-digit number (that possibly had another unmatched digit following it) OR a 1-digit number (that was not followed by another digit).

That seems like a lot of trouble, except maybe for a regex aficionado, so I'd probably opt for the middle approach, converting and testing a 3-digit substring.

<?php
    $line= 'stuff #65 more #66  and #67; other';
    // break out any #numbers that should be translated to ascii, 
    // retaining the surrounding text and the numbers parts.
    // The '#' parts are kept outside the parens to be matched and discarded.
    $parts = preg_split("/\#(\d+)/",$line,NULL,PREG_SPLIT_DELIM_CAPTURE);
    $count = count($parts);
    // Even-indexed entries (possibly empty) contain unchanging surrounding text.

    // Odd-indexed entries are the numbers part of #numbers, so convert them.
    for ($i = 1; $i < $count; $i += 2) {
        $parts[$i] = chr($parts[$i]); // Problem here for input >= 256.
    }

    $line = implode($parts);
?>
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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