Blog posts tagged "Code"

Semi-Intelligent String Cropping with PHP

May 25th, 2004

Problem: You want to display the first N characters of a string.

Solution 1: Use substr.

Unfortunately substr will more often then not leave a word fragment dangling at the end of your new string, which looks broken.

Solution 2: Use substr, but add an ellipsis (…) at the end of the string.

Now the user has a clue as to why there is a dangling word fragment, but the ellipsis is misleading if the your original string was actually less then N characters long initially.

Solution 3: Check if the string is longer then N characters, and if it is use Solution 2, otherwise leave it alone.

This is an improvement, but in practice those dangling fragments, even with their ellipses are kind of unsightly.

Solution 4: Try to find a natural breaking point within the desired crop length, and break on that. If not found revert to Solution 2.

Below is some quick code to do this in PHP. It uses strpos instead of regex for speed. It doesn’t handle “quoted text” intelligently, and it doesn’t look for paragraph breaks (I’m using it for shorter crop lengths, 100-150 characters), but it is a starting point. Also when cropping strings, it is often important to strip the HTML from them first, or you’re essentially guaranteed to end up with broken HTML.

function crop($str, $len) {
    if ( strlen($str) <= $len ) {
        return $str;
    }

    // find the longest possible match
    $pos = 0;
    foreach ( array('. ', '? ', '! ') as $punct ) {
        $npos = strpos($str, $punct);
        if ( $npos > $pos && $npos < $len ) {
            $pos = $npos;
        }
    }

    if ( !$pos ) {
        // substr $len-3, because the ellipsis adds 3 chars
        return substr($str, 0, $len-3) . '...'; 
    }
    else {
        // $pos+1 to grab punctuation mark
        return substr($str, 0, $pos+1);
    }
}

OO::Form, initial release

November 24th, 2003

Several months ago, inspired as I recall by Kate’s How to Avoid Writing Code article, I started playing with Class::DBI and Template Toolkit as a rapid development environment.

Not So Fast

Turns out rapid development isn’t really my style. I’m still futzing around with the earliest stages of functionality of my first app I started developing in this brave new way. Part of the problem is I get distracted into “doing things right”, which is about as antithetical to RAD as one can get. I’ve fallen into a number of these sorts of traps on this project, but a major one was the lack of a form class that integrated nicely into this dev. environment. After all you can’t really say you’re doing rapid web development, and then hand build, and process every form, can you?

A Better Params Trap

CGI::FormBuilder is the current standard, and its very impressive in its way, but it didn’t meet my needs. In particular it does too much, and therefore plays poorly with other technologies. It was also totally unsubclassable.

So with great pleasure I present OO::Form 0.01-alpha-experimental. (which has been kicking around for a week fews, but I finally added some POD)

A Curiosity

I don’t really expect anyone to want to use this code yet, but if you’re curious, and/or want to tell me what I’m doing wrong, take a look at it. It is currently incomplete as I’ve been fleshing out the features as needed, but what is in place, is working well (everything documented is working). Also if you’re really interested I’ll send you the code I’m working on that uses it, so you can see it in action. (beyond the example in the POD below)

My Favorite Feature

Besides being all cool, and OO, and integrating well with Class::DBI, and TT2 (and presumably any other template system, but I wouldn’t know), OO::Form allows you to define arbitrary field types. So besides text, password, select, checkbox, and textarea (I’ve been adding the standards as I need them), OO::Form also ships with an OO::Form::Field::date, which does intelligent date, and timezone handling, and while currently displayed as 3 pull down menus, could be easily subclassed (just override the ashtml method) to display via a totally different widget.

OO::Form – pod2html


NAME

OO::Form – an object oriented framework for building, displaying, and processing forms.


SYNOPSIS

package MyApp::AddWidgetForm;

use base ‘OO::Form’;

  our $FIELDPROFILE = {
          fields  => 
                  [qw(name widgetstyle note expirationdate) ],
          types   => { 
                  widgetstyle     => 'select', 
                  note             => 'textarea', 
                  expirationdate  => 'date'
          },
          options  => {
                  widgetstyle     => [qw(blue green chrome)]
          },
          required  => [qw(name widgetstyle)]
  };
  sub new {
          my $class = shift;
          my $self = $class->SUPER::new(@);
          $self->addfields($FIELDPROFILE);
          $self->submitfield('addwidget');
          return $self;
  }

….

  (else where in your application)
  my $form = MyApp::AddWidgetForm->new({query => $query, ...});
  if ($form->issubmitted and $form->validates ) {
          MyAdd::Widget->newwidget({
                  name => $form->name->value,
                  widgetstyle => $form->widgetstyle->value,
                  note => $form->note->value,
                  expiration-date => $form->expirationdate->value
}); display
widgetaddedconfirmation(); } else { # form hasn't been submitted, or we encountered an error displaypage($template, { form => $form } ); }

…,

  (mean while, in a nearby template)
  [% IF form.errors %]
  <ul class="errors">
    [% FOREACH e = form.errors %]<li>[% e %]</li>[% END %]
  </ul>
  [% END %]
  Name:       [% form.name.html(size => '40') %]<br />
  Style:      [% form.widgetstyle.html(default => 'blue') %] <br />
  Expiration: [% form.expirationdate.html %]
  Note: <br />
  [% form.note.html(rows =>4, cols=>50) %]<br />
  <input type="submit" name="[%form.submitfield%]" value="Add Widget" />               

...,


DESCRIPTION

OO::Form represents an HTML form as a collection of OO::Form::Field objects, a state (is submitted), and some basic error handling. It was inspired by CGI::FormBuilder, and by the difficulty I had integrating CGI::FormBuilder into my application. The OO::Form was designed to be used in conjuction with the Template Toolkit, and Class::DBI though they aren’t tightly coupled.

The most straightforward way to use OO::Form is to create your own subclass of OO::Form for each form in your application. (though create and update pages can usually re-use the same form object, and for very simple forms, it is probably less useful)

Then pass a ‘fields profile’ to the OO::Form->addfields() method which is a factory method for creating object of type OO::Form::Field and its varied subclasses.


USAGE

Field Profile

A field profile is a compact form of specifying all the fields in your form. (Alternately you could insantiate OO::Form:Field::* objects one by one and add each one with $form->field($field))

A field profile is a hashref with 6 fields.

  • fields
  • An arrayref of the names of fields to instantiate. Names must be unique, and they must not have the same name as any method of your form object (as they will be accessed by calling $form->$field
    name())

  • types
  • A hashref of names mapped to their field types. Any field which is not given a type will be a generic field object, and will be drawn as a text field if their html() method is called.

    OO::Form::Field-factory()> will be called for each field, and be passed its type. The default factory() method tries to instantiate an object of type OO::Form::Field::$type. You can subclass OO::Form::Field to provide a whole set of new field types, or new behaviour for old field types. Additionally if anyone has suggestions for a smarter factory method, I’m interested.

    Current types are the basic HTML fields: text, password, checkbox, textarea, select

    Also the composite field type: date (represented as 3 pulldowns) is available.

    See OO::Form::Field for more detail

  • options
  • For field types with options (selects aka pulldown menus, and radio/checkbox groups) values can be passed with the options field.

    A hashref of field names pointing to either an arrayref of options (when you want value and text to be the same) or a hashref of value to text mappings.

  • values
  • A hashref of field name to value mappings. If you want your field to start preconfigured with certain values, pass in a values hash.

    If your values hashref contains a subclass of Class::Accessor (like a Class::DBI object) then the objects get() method will be called, allowing forms to be rapidly populated from a database.

  • required
  • An arrayref of names of the required fields. OO::Form-validate> will return false (and populate an errors array) if any required fields are missing

2 Error Handling and Validation

There are hooks for a future implementation of a full blown validation framework (probably using Data::FormValidator).

In the meantime OO::Form checks that required fields have been filled in, and that none of the various fields through their own error.

Calling the instance method validate() (validates() is a synonym), returns true or false depending on whether there are any error conditions in the current form. It also populates and errors array, useful for re-displaying the form page with a list of errors.

A useful idiom for calling validates in demonstrated in the synops.

Besides the form wide errors array (accessible at $form->errors() ), each field can have their own error (though only one) accessible at $form->widgetstyle->error(), this is useful for re-displaying a form, with error messages inlined.


METHODS

OO::Form is pretty straightforward, much of the complexity is actually in OO::Form::Field. Almost all OO::Form methods accept a hashref of named arguments.

Note this is all experimental.

  • new($params)
  • Construct a new form. Often you’ll want to override this with your own constructor. The default constructor looks for (and will supply defaults if it doesn’t find): query'',submit
    field”, “time_zone”.

    * query is a CGI.pm object, or something else which masquerades as one

    * submitfield is the name of the button that submits this form (checked by issubmitted.

    * time_zone is used by OO::Form::Field::date to do intelligent localization of date/time values

    Additionally in the future OO::Form::new should take a locale.

  • addfields($fieldprofile)
  • Take a field profile (as discussed in the previous section) an populate a form.

  • field(...)
  • A field accessor. Can take a hashref (or field object) and passes it to OO::Form::Field-factory()>, and attach the results to the current form.

    Otherwise assume a string was passed in, and return the field with that name. (AUTOLOAD will call field($field_name) for unknown methods)

Tagged: Uncategorized , , ,

Maintaining Date/Timezone Sanity with DateTime.pm and Class::DBI

September 25th, 2003

Calendaring can be a fraught and tricky business. Probably doesn’t compare to building software to do precise robotic control of surgical tools (though I have a friend who worked for a summer putting Windows into operating rooms), or high volume real time transaction management, but its does have its pitfalls and tricks.

The Case of the Missing Timezone

A classic mistake when one sets out to write calendar software, particularly web calendar software, is to ignore timezones. “Now” is treated as the current time on the web server, new events are added to the database without reference to the timezone of their creator. And amazingly, by and large this will work, for a while. Often your first set of users will be in your timezone, or thereabouts, perhaps you aren’t displaying hours, so it’s just a few hours early in the morning, and late at night when people notice your calendar is displaying the wrong date.

Problems start to crop up as your audience becomes more international, or you start trying to add more time sensitive services. So you try to retro-fit timezones, add them on. And now you’re living in a world of pain.

Read the rest of this entry »

Tagged: Uncategorized , , , , , ,

Conditional GET with LWP & Perl

March 1st, 2003

I was arguing recently that implementing a conditional GET with LWP is trivial and there was no reason why someone wouldn’t support it. I assumed there must be a dozen examples of how to do this. Afterall O’reilly has “open sourced” their original LWP book, there is an LWP cookbook, and reams of POD.

No Such Luck

Well a quick search didn’t turn up anything. A more concerted one might have but it was easier to write this example then keep searching. If you’re looking for more general info on Conditional GETs try Charles Miller’s HTTP Conditional Get for RSS Hackers. If you’re looking for an implementation in PHP, you might look in rss_fetch of my RSS parser/aggregator Magpie.

Conditional GET

The basic idea is, when you request a file you remember the ETag and Last-Modified HTTP headers, passing them along with your next request as If-None-Match and If-Last-Modified. If the file has changed then you’ll get the content as normal, if the file hasn’t changed you’ll get a ‘304 Not Modified’ header.

This is something of a toy example, but I try to be as correct as possible with it. Noteable in its absence is doing anything with the file you’ve fetched. (for example parsing and storing an RSS feed) Also I use a simple file to store ETag and Last-Modified, you might want to use a different backend.
See the Code

Example Code


use LWP::UserAgent;
use HTTP::Request;

my $url = "http://localhost/rss/laughingmeme.rdf";
my $cache_file = 'cache';
my %headers;

if ( -e $cache_file ) {
    open (CACHE, "< $cache_file") or die "Couldn't open: $!";
    %headers = (
        If_None_Match => <CACHE>,
        If_Last_Modified => <CACHE>
    );
    close CACHE;
}

my $ua = new LWP::UserAgent();
$ua->agent("Conditionally Enabled v0.1");

my $req = HTTP::Request->new( GET => $url );
$req->header(%headers);

my $res = $ua->request($req);
if ($res->is_success) {
    print "new!\n";
    # save ETag & Last-Modified
    open (CACHE, "> $cache_file") or die "Couldn't open: $!";
    print CACHE $res->header('ETag'), "\n";
    print CACHE $res->header('Last-Modified'), "\n";
    close CACHE;
}
elsif ( $res->code() eq '304' ) {
    print "not modified, go to cache\n";
    # do logic for RSS not modified
}
else {
    print "fooey! somthing went wrong\n";
}

Tagged: Uncategorized , , , , ,

cvs2rss 0.2

February 26th, 2003

I released cvs2rss v0.2 (the “Fading Dawn” release) yesterday. Its only a minor tweak of cvs2rss 0.1 which has been happily working for me for a month now. (and needed surprisingly little fixing given that it came into this world under a cloud) Ben announced it, but I figured I should probably put something on my own site about it :)

Some future directions

  • Handle interactive CVS prompt better
  • Make the project self hosting
  • Make the config file more powerful
  • Incorporate all the lovely user feedback I get (hint)

Some example feeds I currently track.

Allconsuming Soap

January 22nd, 2003

So I wrote up my own little SOAP client to Allconsuming, which, while not nearly as cool as booktalk, works nicely to maintain my little READING sidebar. (though as you can see in the case of Applying Patterns there are still some aesthetic tweaks to make). Get the script and the template.

By the way, it looks a little different then DJ’s because SOAP::Lite’s autodispatch+ feature breaks Template Toolkit (and is kind of icky anyway)

Tagged: Uncategorized , , , , ,