From MAILER-DAEMON Wed Aug  1 10:39:51 2007
Date: 01 Aug 2007 10:39:51 -0400
From: Mail System Internal Data <MAILER-DAEMON@turing.acm.org>
Subject: DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA
X-IMAP: 1185979191 0000000000
Status: RO

This text is part of the internal format of your mail folder, and is not
a real message.  It is created automatically by the mail system software.
If deleted, important folder data will be lost, and it will be re-created
with the data reset to initial values.

From perlman@turing.acm.org Wed Aug  1 17:32:33 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Wed, 1 Aug 2007 17:32:32 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Lawrence Parsons <lawrencemparsons@gmail.com>
Subject: Re: hi, in montreal this week
In-Reply-To: <8538b5010708010635u5cbe7128q57a50f49e0a395a8@mail.gmail.com>
Message-ID: <Pine.LNX.4.64.0708011728250.14917@turing.acm.org>
References: <8538b5010708010635u5cbe7128q57a50f49e0a395a8@mail.gmail.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Hi Larry,

Good to hear from you. It's been about 20 years!

How about breakfast or lunch on Thursday (tomorrow) or breakfast Friday?

I'm booked for evenings already. Well, not for Thursday evening,
but I think you have the banquet. I'm free before or after the banquet.

My home phone is 514-482-4905. I work at home so I am generally available.

Gary

On Wed, 1 Aug 2007, Lawrence Parsons wrote:

> hi gary,
>    i'm in montreal for the music cogn conference until friday evening
> (maybe caroline told you already). it would be great to see you and chat,
> over a drink or something. are you around? my mobile (blackberry) is 0044
> 795 094 6520.
>
> cheers, larry
>

From perlman@turing.acm.org Thu Aug  2 00:33:59 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Thu, 2 Aug 2007 00:33:58 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Lawrence Parsons <lawrencemparsons@gmail.com>
Subject: Re: hi, in montreal this week
In-Reply-To: <8538b5010708011540r4a2e5a71j3ebb5e923c6cee9f@mail.gmail.com>
Message-ID: <Pine.LNX.4.64.0708020025160.21966@turing.acm.org>
References: <8538b5010708010635u5cbe7128q57a50f49e0a395a8@mail.gmail.com> 
 <Pine.LNX.4.64.0708011728250.14917@turing.acm.org>
 <8538b5010708011540r4a2e5a71j3ebb5e923c6cee9f@mail.gmail.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Great. I have a 2pm meeting, so we'll meet a little after noon.
I we don't coordinate any more, I'll be at the SE corner of Mackay
and de Maisonneuve (1455 de Maisonneuve), diagonally across from
the Norman Bethune statue. I think that is near your conference.
I might even carry my cell phone, if I remember 514-434-4905.

Gary

On Wed, 1 Aug 2007, Lawrence Parsons wrote:

> Great, Gary, how about lunch tomorrow? I can leave the conference at
> concordia about 12:10 and am free for a couple of hours. If that's good too
> for you, i can find a taxi to meet you somewhere you suggest.
>
> Larry
>
> On 8/1/07, Gary PERLMAN <perlman@turing.acm.org> wrote:
>>
>> Hi Larry,
>>
>> Good to hear from you. It's been about 20 years!
>>
>> How about breakfast or lunch on Thursday (tomorrow) or breakfast Friday?
>>
>> I'm booked for evenings already. Well, not for Thursday evening,
>> but I think you have the banquet. I'm free before or after the banquet.
>>
>> My home phone is 514-482-4905. I work at home so I am generally available.
>>
>> Gary
>>
>> On Wed, 1 Aug 2007, Lawrence Parsons wrote:
>>
>>> hi gary,
>>>    i'm in montreal for the music cogn conference until friday evening
>>> (maybe caroline told you already). it would be great to see you and
>> chat,
>>> over a drink or something. are you around? my mobile (blackberry) is
>> 0044
>>> 795 094 6520.
>>>
>>> cheers, larry
>>>
>>
>

From perlman@turing.acm.org Thu Aug  2 17:34:21 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Thu, 2 Aug 2007 17:34:20 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Georgios Marentakis <gmarentakis@music.mcgill.ca>
Subject: Re: |STAT request
In-Reply-To: <4C1D6A22-289B-4F4D-9D0B-34C837779D47@music.mcgill.ca>
Message-ID: <Pine.LNX.4.64.0708021734010.2305@turing.acm.org>
References: <4C1D6A22-289B-4F4D-9D0B-34C837779D47@music.mcgill.ca>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Thu, 2 Aug 2007, Georgios Marentakis wrote:

> Dear Dr. Pearlman,
> I writing to enquire whether it would be possible to obtain ISTAT for UNIX. 
> In addition I would like to ask whether you think it would run on MAC OS X.
>
> I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
> I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS.
>
>
> thank you for your time and all the great work,
> best regards,
> georgios
>
>
> Georgios Marentakis
> Research Fellow
> CIRMMT, Schulich School of Music
> McGill University
> 555 Sherbrooke Street West
> H3A 1E3
> Montreal,
> QC,CA
> tel: (1) 514 398 4535 ext 094837
>
>
>
>
>

From perlman@turing.acm.org Mon Aug  6 08:02:04 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Mon, 6 Aug 2007 08:02:04 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Monte Bateman <monte.bateman@nasa.gov>
Subject: Re: |STAT
In-Reply-To: <20070803204646.GA20777@updraft.nsstc.nasa.gov>
Message-ID: <Pine.LNX.4.64.0708060801550.8604@turing.acm.org>
References: <20070803204646.GA20777@updraft.nsstc.nasa.gov>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Fri, 3 Aug 2007, Monte Bateman wrote:

>
> I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
> I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS.
>
>

From perlman@turing.acm.org Mon Aug  6 08:02:25 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Mon, 6 Aug 2007 08:02:25 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Monte Bateman <wb5rzx@gmail.com>
Subject: Re: |STAT request
In-Reply-To: <6923ba970708031350y502883afu72d7845fa5bc3170@mail.gmail.com>
Message-ID: <Pine.LNX.4.64.0708060802190.8604@turing.acm.org>
References: <6923ba970708031350y502883afu72d7845fa5bc3170@mail.gmail.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Fri, 3 Aug 2007, Monte Bateman wrote:

>   I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
>   I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS.
>

From perlman@turing.acm.org Mon Aug  6 08:02:59 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Mon, 6 Aug 2007 08:02:59 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Clinton Evans <clinton.evans@rogers.com>
Subject: Re: |STAT and |WAVE Code
In-Reply-To: <200708051515.58834.clinton.evans@rogers.com>
Message-ID: <Pine.LNX.4.64.0708060802500.8604@turing.acm.org>
References: <200708051515.58834.clinton.evans@rogers.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Sun, 5 Aug 2007, Clinton Evans wrote:

>
> I would like to use the |WAVE Code and understand it requires some files
> from |STAT to compile. I would be grateful if you would make a copy available
> to me.
>
> I undertake to keep your software private and not distribute source code or
> binaries.
>
> Regards Clinton Evans
>

From perlman@turing.acm.org Mon Aug  6 11:44:16 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Mon, 6 Aug 2007 11:44:16 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Yoshitaka Ichihashi <y.ichihashi@gmail.com>
Subject: Re: lSTAT
In-Reply-To: <62baf35c0708060804h303ac6bco8f682583c6939782@mail.gmail.com>
Message-ID: <Pine.LNX.4.64.0708061144090.1488@turing.acm.org>
References: <62baf35c0708060804h303ac6bco8f682583c6939782@mail.gmail.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Tue, 7 Aug 2007, Yoshitaka Ichihashi wrote:

> Prof. Perlman
>
> I would like to use lSTAT for my psycholinguistic study.
>
>   I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
>   I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS.
>
> I will wait for the location of lSTAT files.
>
> Sincerely
>
>

From perlman@turing.acm.org Tue Aug  7 10:02:41 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Tue, 7 Aug 2007 10:02:40 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Stephen Peterson <Stephen.Peterson@itn.liu.se>
Subject: Re: Obtaining |STAT
In-Reply-To: <60BE2EC1A14A0F4B9C850468CC51552021D215@LKPMSX1.ad.liu.se>
Message-ID: <Pine.LNX.4.64.0708071002320.23569@turing.acm.org>
References: <60BE2EC1A14A0F4B9C850468CC51552021D215@LKPMSX1.ad.liu.se>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Mon, 6 Aug 2007, Stephen Peterson wrote:

> I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
> I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS.
>

From perlman@turing.acm.org Tue Aug  7 10:02:55 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Tue, 7 Aug 2007 10:02:54 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Clinton Evans <clinton.evans@rogers.com>
Subject: Re: |STAT and |WAVE Code
In-Reply-To: <200708062014.43915.clinton.evans@rogers.com>
Message-ID: <Pine.LNX.4.64.0708071002470.23569@turing.acm.org>
References: <200708051515.58834.clinton.evans@rogers.com>
 <Pine.LNX.4.64.0708060802500.8604@turing.acm.org> <200708062014.43915.clinton.evans@rogers.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Mon, 6 Aug 2007, Clinton Evans wrote:

>   I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
>   I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS.
>

From perlman@turing.acm.org Tue Aug  7 10:03:36 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Tue, 7 Aug 2007 10:03:35 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Dave du Feu <ddf@staffmail.ed.ac.uk>
Subject: Re: permission to use |STAT
In-Reply-To: <20070807145107.ri3gpfbp4wg8oows@www.staffmail.ed.ac.uk>
Message-ID: <Pine.LNX.4.64.0708071003280.23569@turing.acm.org>
References: <20070807145107.ri3gpfbp4wg8oows@www.staffmail.ed.ac.uk>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Tue, 7 Aug 2007, Dave du Feu wrote:

> Dear Gary
>
> I would be grateful for your permission and the necessary details to install 
> |STAT on our departmental Solaris server, which is used mainly for academic 
> medical statistics research.
>
> With thanks
> Dave du Feu
>
> Computing Officer
> Public Health Sciences
> University of Edinburgh

From perlman@turing.acm.org Tue Aug  7 10:07:10 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Tue, 7 Aug 2007 10:07:10 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Dave du Feu <ddf@staffmail.ed.ac.uk>
Subject: Re: Fwd: permission to use |STAT
In-Reply-To: <20070807145551.zcaww18gocgwksk8@www.staffmail.ed.ac.uk>
Message-ID: <Pine.LNX.4.64.0708071003500.23569@turing.acm.org>
References: <20070807145551.zcaww18gocgwksk8@www.staffmail.ed.ac.uk>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

It might work. You'd need to change the settings in transpose.c:

#define MAXCOLS  100
#define MAXLINES 100

As a test, transpose twice and see if you get back the original (ignoring whitespace).

Gary

On Tue, 7 Aug 2007, Dave du Feu wrote:

> Gary - further to this email, one thing we need to do is transpose some 
> matrices with about 1200 columns and 40000 rows.  Should this work ok in 
> |STAT using the transpose command? - they are very large matrices, at least 
> compared to what we are usually using here.
>
>
> ----- Forwarded message from ddf@staffmail.ed.ac.uk -----
>   Date: Tue, 07 Aug 2007 14:51:07 +0100
>   From: Dave du Feu <ddf@staffmail.ed.ac.uk>
> Reply-To: Dave du Feu <ddf@staffmail.ed.ac.uk>
> Subject: permission to use |STAT
>     To: perlman@acm.org
>
> Dear Gary
>
> I would be grateful for your permission and the necessary details to
> install |STAT on our departmental Solaris server, which is used mainly
> for academic medical statistics research.
>
> With thanks
> Dave du Feu
>
> Computing Officer
> Public Health Sciences
> University of Edinburgh
>
>
> ----- End forwarded message -----
>

From perlman@turing.acm.org Wed Aug  8 14:09:33 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Wed, 8 Aug 2007 14:09:32 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Petri Lehtinen <petri.lehtinen@tut.fi>
Subject: Re: |STAT package location request
In-Reply-To: <46B99505.6050401@tut.fi>
Message-ID: <Pine.LNX.4.64.0708081409260.14697@turing.acm.org>
References: <46B99505.6050401@tut.fi>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Wed, 8 Aug 2007, Petri Lehtinen wrote:

> I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
> I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS.
>

From perlman@turing.acm.org Wed Aug 15 12:36:15 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Wed, 15 Aug 2007 12:36:13 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Robert Martignoni <robert.martignoni@unisg.ch>
Subject: Re: |STAT
In-Reply-To: <00bc01c7df37$055b18c0$0301a8c0@Unisg.ch>
Message-ID: <Pine.LNX.4.64.0708151236050.28884@turing.acm.org>
References: <00bc01c7df37$055b18c0$0301a8c0@Unisg.ch>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Wed, 15 Aug 2007, Robert Martignoni wrote:

> I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
>
> I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS.
> Thanks,
> Robert
>

From perlman@turing.acm.org Sat Aug 25 13:44:28 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Sat, 25 Aug 2007 13:44:28 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Peo Boholm <pe0b@yahoo.se>
Subject: Re: Request for STAT download location
In-Reply-To: <515422.53182.qm@web23401.mail.ird.yahoo.com>
Message-ID: <Pine.LNX.4.64.0708251344150.6398@turing.acm.org>
References: <515422.53182.qm@web23401.mail.ird.yahoo.com>
MIME-Version: 1.0
Content-Type: MULTIPART/MIXED; BOUNDARY="-1463807999-1354095983-1188063868=:6398"

---1463807999-1354095983-1188063868=:6398
Content-Type: TEXT/PLAIN; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 8BIT

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Sat, 18 Aug 2007, Peo Boholm wrote:

> I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
> I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS.
>
>
>  regards
>  Peo
>
>
> ---------------------------------
> Låna pengar utan säkerhet.
> Sök och jämför hos Yahoo! Shopping.
---1463807999-1354095983-1188063868=:6398--

From perlman@turing.acm.org Sat Aug 25 13:44:44 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Sat, 25 Aug 2007 13:44:43 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Stuart Marshall <Stuart.Marshall@mcs.vuw.ac.nz>
Subject: Re: Request for |STAT package.
In-Reply-To: <46C4CBFE.9040104@mcs.vuw.ac.nz>
Message-ID: <Pine.LNX.4.64.0708251344340.6398@turing.acm.org>
References: <46C4CBFE.9040104@mcs.vuw.ac.nz>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Fri, 17 Aug 2007, Stuart Marshall wrote:

> Hi,
>
> I'm an academic at Victoria University of Wellington in New Zealand. I was 
> wondering if the |STAT package was still available for Linux. As per your 
> webpage, here is my agreement to the following conditions:
>
> I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
> I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS.
>
> Kind regards,
>
> Stu.
>
> ----
>
> Dr Stuart Marshall
> Lecturer
> School of Mathematics, Statistics & Computer Science
> Victoria University of Wellington
> New Zealand
> Phone: +64 4 463 6730
> Email: stuart@mcs.vuw.ac.nz
>

From perlman@turing.acm.org Sat Aug 25 13:55:52 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Sat, 25 Aug 2007 13:55:50 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Priscilla Ishida <lois0816@lingua.tsukuba.ac.jp>
Subject: Re: |STAT request
In-Reply-To: <003801c7e3d4$4c03fc50$c3e99e82@PRISCA>
Message-ID: <Pine.LNX.4.64.0708251355380.8489@turing.acm.org>
References: <003801c7e3d4$4c03fc50$c3e99e82@PRISCA>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Tue, 21 Aug 2007, Priscilla Ishida wrote:

> Gary Perlman
>
> I am writing to request the web location of |STAT so that I can use it for
> linguistic research. I agree to adhere to the conditions of using |STAT, and
> I also agree not to share the |STAT location with others.
>
> Thank you very much for your help.
>
>            Priscilla Ishida
>
> *********************************************
> Priscilla Ishida, Assistant Professor
> Doctoral Program in Literature and Linguistics
> Graduate School of Humanities and Social Sciences
> University of Tsukuba
> lois0816@lingua.tsukuba.ac.jp
>

From perlman@turing.acm.org Sat Aug 25 13:57:50 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Sat, 25 Aug 2007 13:57:49 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Paul Kieffaber <kieffaberp@upmc.edu>
Subject: Re: |STAT request
In-Reply-To: <1187950579.4597.8.camel@paul-desktop.CCN>
Message-ID: <Pine.LNX.4.64.0708251357420.8489@turing.acm.org>
References: <1187950579.4597.8.camel@paul-desktop.CCN>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Fri, 24 Aug 2007, Paul Kieffaber wrote:

>   I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
>   I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS.
>

From perlman@turing.acm.org Sat Aug 25 16:34:48 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Sat, 25 Aug 2007 16:34:48 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: ishelpdesk@hq.acm.org
cc: Gary perlman <perlman@turing.acm.org>
Subject: home page missing
Message-ID: <Pine.LNX.4.64.0708251631050.29060@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Hi,

My page
 	http://www.acm.org/perlman/
is missing. It was once here:
 	http://www.acm.org/~perlman/
but it migrated to the url without the tilde.
Could the server please be set up to refer the url to:
 	/home/perlman/public_html
on turing?

Gary Perlman, Director, HCI Bibliography Project
mailto:director@hcibib.org  http://hcibib.org/

From perlman@turing.acm.org Sun Aug 26 11:18:50 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Sun, 26 Aug 2007 11:18:50 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: webmaster@acm.org
cc: Gary perlman <perlman@turing.acm.org>
Subject: Re: home page missing
In-Reply-To: <Pine.LNX.4.64.0708251631050.29060@turing.acm.org>
Message-ID: <Pine.LNX.4.64.0708261114020.30471@turing.acm.org>
References: <Pine.LNX.4.64.0708251631050.29060@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

These often-accessed pages are no longer working:
 	http://www.acm.org/perlman/guide.html
 	http://www.acm.org/perlman/question.html
 	http://www.acm.org/perlman/stat/
Could you please set up the redirect?

Gary Perlman, Director, HCI Bibliography Project
mailto:director@hcibib.org  http://hcibib.org/

On Sat, 25 Aug 2007, Gary PERLMAN wrote:

> Hi,
>
> My page
> 	http://www.acm.org/perlman/
> is missing. It was once here:
> 	http://www.acm.org/~perlman/
> but it migrated to the url without the tilde.
> Could the server please be set up to refer the url to:
> 	/home/perlman/public_html
> on turing?
>
> Gary Perlman, Director, HCI Bibliography Project
> mailto:director@hcibib.org  http://hcibib.org/
>

From perlman@turing.acm.org Mon Aug 27 14:15:51 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Mon, 27 Aug 2007 14:15:51 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Gary Perlman at OCLC <perlman@oclc.org>
Subject: get
Message-ID: <Pine.LNX.4.64.0708271415460.14302@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

#! /usr/bin/perl

# http://www.xav.com/perl/site/lib/lwpcook.html

$year = 2007;

use LWP::Simple;

@alertbox = (
 	"%A Jakob Nielsen",
 	"%I useit.com",
 	"%S Alertbox: Web Usability Newsletter",
 	"%K hci-sites:alertbox",
 	);

&refer($ARGV[0]);

sub refer { # file
 	local ($file) = (@_);
 	$file = "http://" . $file unless ($file =~ m|^https?://|i);
 	local ($doc) = get $file;
 	# print $doc;

 	if ($ident = &ident($file)) {
 		print "%M $ident\n";
 	}
 	print "%0 INTERNET\n";
 	if ($file =~ /alertbox/) {
 		for $i (@alertbox) {
 			print "$i\n";
 		}
 	}
 	local ($i);
 	print "%W $file\n";
 	if ($title = &title($doc)) {
 		$title =~ s/ \(Jakob Nielsen's Alertbox\)//;
 		$title =~ s/ \(Alertbox[^)]*\)//;
 		print "%T $title\n";
 	}
 	if ($desc = &meta($doc, 'description')) {
 		print "%X $desc\n";
 	}
 	if ($keywords = &meta($doc, 'keywords')) {
 		print "%K $keywords\n";
 	}
 	if ($date = &date($file)) {
 		print "%D $date\n";
 	} else {
 		print "%D $year-\n";
 	}


}

sub clean {
 	local ($s) = (@_);
 	$s =~ s/^\s*//;
 	$s =~ s/\s*$//;
 	return $s;
}

sub meta {
 	local ($doc, $name) = (@_);
 	if ($doc =~ m|<meta name="$name"\s+content="([^"]*)"|si) {
 		return &clean($1);
 	}
 	return "";
}

sub title {
 	local ($doc) = (@_);
 	if ($doc =~ m|<title>([^<]*)</title>|i) {
 		return &clean($1);
 	}
 	return "";
}

sub ident {
 	local ($file) = (@_);
 	$file =~ s|.*/||;     # remove everything up to /
 	$file =~ s|.htm.*||;  # remove file suffix
 	if ($file =~ /alertbox/) {
 		$file = "Nielsen." . $file;
 	}
 	return "U.$file";
}

sub date {
 	local ($file) = (@_);
 	$file =~ s|.*/||;
 	$file =~ s|.htm.*||;
 	if ($file =~ /(\d\d\d\d)(\d\d)(\d\d)/) {
 		return "$1-$2-$3";
 	} elsif ($file =~ /(\d\d)(\d\d)(\d\d)/) {
 		return "19$1-$2-$3";
 	}
 	return "";
}


From perlman@turing.acm.org Tue Aug 28 08:13:44 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Tue, 28 Aug 2007 08:13:43 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Haritini Kanthou <kanthou@hq.acm.org>
cc: webmaster@acm.org
Subject: RE: home page missing
In-Reply-To: <DD04AC5BDEBD444D9A9133F6C5D238250119C73F@acm-02-ex.atlarge.net>
Message-ID: <Pine.LNX.4.64.0708280813020.12461@turing.acm.org>
References: <DD04AC5BDEBD444D9A9133F6C5D238250119C724@acm-02-ex.atlarge.net>
 <8121A635D723D1439452E134C4AB3BD801AE3D71@acm-01-ex.atlarge.net>
 <DD04AC5BDEBD444D9A9133F6C5D238250119C73F@acm-02-ex.atlarge.net>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Dear Haritini,

Thank you for your efforts.

Gary

On Mon, 27 Aug 2007, Haritini Kanthou wrote:

>
> Dear Gary,
>
> The URLs now resolve correctly.  Please let me know if we can help with
> anything else, and thanks, again, for your patience.
>
> Regards,
>
> Haritini Kanthou
> Application Developer
> ACM IS Dept.
> kanthou@hq.acm.org
> 212-626-0572
>
> -----Original Message-----
> From: Gary PERLMAN [mailto:perlman@turing.acm.org]
> Sent: Sunday, August 26, 2007 11:19 AM
> To: webmaster@acm.org
> Cc: Gary perlman
> Subject: Re: home page missing
>
> These often-accessed pages are no longer working:
> 	http://www.acm.org/perlman/guide.html
> 	http://www.acm.org/perlman/question.html
> 	http://www.acm.org/perlman/stat/
> Could you please set up the redirect?
>
> Gary Perlman, Director, HCI Bibliography Project
> mailto:director@hcibib.org  http://hcibib.org/
>
> On Sat, 25 Aug 2007, Gary PERLMAN wrote:
>
>> Hi,
>>
>> My page
>> 	http://www.acm.org/perlman/
>> is missing. It was once here:
>> 	http://www.acm.org/~perlman/
>> but it migrated to the url without the tilde.
>> Could the server please be set up to refer the url to:
>> 	/home/perlman/public_html
>> on turing?
>>
>> Gary Perlman, Director, HCI Bibliography Project
>> mailto:director@hcibib.org  http://hcibib.org/
>>
>

From perlman@turing.acm.org Tue Aug 28 20:48:51 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Tue, 28 Aug 2007 20:48:50 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Gary Perlman at OCLC <perlman@oclc.org>
Subject: chaccess.cgi
Message-ID: <Pine.LNX.4.64.0708282048390.24857@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

#! /usr/local/bin/perl

# TODO check for colspan and rowspan
# TODO check for skip navigation strings
# DONE label tag missing http://www.hcibib.org/perlman/chaccess.cgi?url=yahoo.com
# DONE http://www.hcibib.org/perlman/chaccess.cgi?url=hcibib.org hard tag for query
# OKAY http://www.hcibib.org/perlman/chaccess.cgi?url=hcibib.org/bs.cgi
# TODO label tag error http://www.hcibib.org/perlman/chaccess.cgi?url=worldcat.org/oclc/1
# TODO title tag for links and buttons
# TODO accesskey for buttons
# TODO textarea for html input
# TODO cookies for persistent options
# TODO report missing / on single tag items; missing quotes
# TODO catch bad alt text - bullet, xyz.gif
# TODO

use LWP::Simple;

$title = "Check Accessibility";
if ($ARGV[0]) {
 	&refer($ARGV[0]);
} else {
 	$cookiename = "chaccess";
 	%F = &formdata($cookiename);
 	$url = $F{'url'};
 	$url = "hcibib.org" unless $url;
  	$url = "http://" . $url unless ($url =~ m|^https?://|i);
 	print "Content-type: text/html\r\n\r\n";
 	print "<html lang=en><head>\n";
 	print '<meta name="description" content="Simple Accessibility Checker" />';
 	print '<meta name="keywords" content="accessibility;check;validation;form;label;image;img" />';
 	print "<title>$title</title>\n";
 	print "<base href=\"$url\" />\n";
 	print "</head><body>\n";
 	print "<h1>$title</h1>\n";
 	print "<form action=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}\" method=POST>\n";
 	print "<label for=url>URL</label>: <input name=url size=40 id=url value=\"$F{'url'}\" />\n";
 	print "<input type=submit accesskey=c value=\"$title\" />\n";
 	print "<div>\n";
 	print "<input type=checkbox value=checked name=source id=source $F{'source'}/><label for=source>Show HTML</label>\n";
 	print "<input type=checkbox value=checked name=images id=images $F{'images'}/><label for=images>Show Images</label>\n";
 	print "<input type=checkbox value=checked name=labels id=labels $F{'labels'}/><label for=labels>Show Labels</label>\n";
 	# print "<input type=checkbox value=checked name=links id=links $F{'links'}/><label for=links>Show Links</label>\n";
 	print "</div>\n";
 	print "</form>\n";
 	$cgi = 1;
 	&refer($url);
 	# print "<script type=\"text/javascript\" language=\"JavaScript\">\n\t<!--\n";
 	# print "\t\tsetCookie(\"$cookiename\", \"$parms\");\n";
 	# print "\t// -->\n</script>\n";
 	print "</body></html>\n";
}


sub refer { # url cgi
  	local ($url) = (@_);
  	local ($doc) = get $url; # FETCH THE URL
  	# print $doc;

 	$cgi && print "<table border=0 cellpadding=5 cellspacing=0>\n";
  	showIt("url", $url, "<a href=\"$url\">$url</a>");
  	showIt("length", length($doc));
 	&checkHeader($doc);

 	$F{'source'} && &dump($doc);
 	# $doc =~ s/\012/ /g; # map newlines to spaces
 	&checkImages($doc);
 	&checkForm($doc);
 	$F{'links'} && &checkLinks($doc);

 	$cgi && print "</table>\n";
}

sub checkHeader { # doc
 	local ($doc) = (@_);

  	if ($title = &getTitle($doc)) {
  		showIt("title", $title);
  	} else {
 		&showErr("NO TITLE") unless $title;
  	}
  	if ($desc = &getMeta($doc, 'description')) {
  		showIt("desc", $desc);
  	} else {
 		&showErr("NO DESCRIPTION") unless $desc;
 	}
  	if ($keywords = &getMeta($doc, 'keywords')) {
  		showIt("keywords", $keywords);
  	} else {
 		&showErr("NO KEYWORDS") unless $keywords;
 	}
}

sub checkImages { # doc
 	local ($doc) = (@_);
 	# <IMG
 	while ($doc =~ /(<img [^>]+>)/mi) {
 		$doc =~ s///;
 		$img = $1;
 		local ($extra) = $F{'images'} ? $img : "";
 		showIt('img', $img, $extra);
 		&checkAlt($img);
 	}
}

sub checkForm { # doc
 	local ($doc) = (@_);
 	# <INPUT # <TEXTAREA
 	%label = &initLabel($doc);
 	while ($doc =~ /(<(input|textarea|select)\b[^>]*>)/mi) {
 		$doc =~ s///;
 		$element = $1;
 		showIt('input', $1);
 		# showIt('type', $type);
 		$type = &getAttr($element, 'type');
 		$type = 'text' unless $type;
 		if ($type =~ /(image)/mi) {
 			&checkAlt($element);
 		} elsif ($type =~ /(text|radio|checkbox)/) {
 			$id = &getAttr($element, 'id');
 			if ($id) { # look for label tag
 				if ($doc =~ m@<label\b[^>]*for=("$id"|$id\b)[^>]*>(.+)</label>@mi) {
 					# label tag is present for $id
 					$match = $1;
 					$label = $2;
 					$label =~ s|</label>.*||mi; # make it shortest matching string
 					$F{'labels'} && &showIt("$id label", $label, 'info');
 				} elsif ($label{$id} ne "") {
 					$F{'labels'} && &showIt("$id label", $label{$id}, 'info');
 				} else {
 					&showErr("NO LABEL TAG FOR '$id'");
 					# &dump($doc);
 				}
 			} else {
 				&showErr("NO ID ATTRIBUTE, SO NO LABEL TAG POSSIBLE");
 			}
 		}
 	}
}

sub dump {
 	if ($cgi) {
 		local ($text) = (@_);
 		print "<tr><th align=left><label for=html>HTML</label></th><td><textarea id=html cols=80 rows=10>";
 		print &escape($text);
 		print "</textarea></td></tr>\n";
 	}
}

sub initLabel {
 	local ($doc) = (@_);
 	local ($attrs, $label, $for);
 	$doc =~ s/<label\b//gi;
 	$doc =~ s/<\/label>//gi;
 	local (%label);
 	while ($doc =~ /([^]*)/) {
 		$label = $1;
 		$doc =~ s///;
 		$attrs = $label;
 		$attrs =~ s/>.*$//; # keep the attributes
 		if ($attrs =~ /for="([^"]*)"/) {
 			$for = $1;
 			# &showErr("for-quote=$for");
 		} elsif ($attrs =~ /for=([\S]*)/) {
 			$for = $1;
 			# &showErr("for=$for");
 		}
 		if ($for) {
 			$label =~ s/[^>]*>//; # remove the attributes before first >
 			$label{$for} = $label;
 			# &showErr("[$for]=$label");
 		}
 	}
 	return %label;
}

sub checkLinks { # doc
 	local ($doc) = (@_);
 	while ($doc =~ m|<a\b[^>]*href="([^"]+)">([^<]+)</a>|mi) {
 		$doc =~ s///;
 		$href = $1;
 		$label = $2;
 		$qhref = $href;
 		$qhref = "$url/$qhref" unless ($qhref =~ m|^https?://|i);
 		$qhref = encode($qhref);
 		showIt("link", "$href - $label", "<a href=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}?url=$qhref\">$label</a>");
 	}
}

sub checkAlt { # tag
 	local ($tag) = (@_);
 	local ($alt) = &getAttr($tag, 'alt');
 	$F{'labels'} && &showIt("alt", "$alt", 'info');
 	if (!$alt) {
 		&showErr("NO ALT TEXT");
 	}
}

sub getAttr { # tag name
 	local ($tag, $name) = (@_);
 	if ($tag =~ m/\b$name="([^"]+)"/mi) {
 		return $1;
 	}
 	if ($tag =~ m/\b$name=([^ >]+)[ >]/mi) {
 		return $1;
 	}
 	return "";
}

sub showErr {
 	local ($msg) = (@_);
 	if ($cgi) {
 		local $label = 'error';
 		$count{$label}++;
 		print "<tr valign=top><th align=left nowrap>$label-$count{$label}</th><td bgcolor=\"#FFFFCC\">$msg</td></tr>\n";
 	} else {
 		print "\t******* $msg ******\n";
 	}
}

sub showIt {
 	local ($name, $value, $extra) = (@_);
 	$value = &escape($value) if $cgi;
 	if ($extra eq 'info') {
 		$extra = '';
 		$bgcolor = 'bgcolor="#CCCCFF"';
 	} else {
 		$bgcolor = 'bgcolor="#FFFFFF"';
 	}
 	$count{$name}++;
 	if ($cgi) {
 		print "<tr valign=top>
 				<th align=left nowrap>$name-$count{$name}</th>
 				<td $bgcolor >$value <div>$extra</div></td>
 			</tr>\n";
 	} else {
 		print "$name-$count{$name}\n\t$value\n";
 	}
}

sub clean {
  	local ($s) = (@_);
  	$s =~ s/^\s*//;
  	$s =~ s/\s*$//;
  	return $s;
}

sub getMeta {
  	local ($doc, $name) = (@_);
  	if ($doc =~ m|<meta name="$name"\s+content="([^"]*)"|si) {
  		return &clean($1);
  	}
  	return "";
}

sub getTitle {
  	local ($doc) = (@_);
  	if ($doc =~ m|<title>([^<]*)</title>|i) {
  		return &clean($1);
  	}
  	return "";
}




sub encode { # string
     local ($str) = (@_);
 	$str =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
 	$str =~ s/%20/+/g;
 	return $str;
}

sub escape { # string
 	local ($s) = (@_);
 	# $s =~ s/&/&amp;/g; # don't do this or sgml entities are broken
 	$s =~ s/"/&quot;/g;
 	$s =~ s/</&lt;/g;
 	$s =~ s/</&gt;/g;
 	return $s;
}

sub getCookie { # name
 	local ($ident, $format) = (@_);
 	local ($cookie);
 	for (split (/[;] */, $ENV{'HTTP_COOKIE'})) {
 		if (/^$ident=(.*)/) {
 			$cookie = $1;
 			$cookie =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg; # decode
 			# now $cookie looks like a=xxx&b=yyy&c=zzz
 			break;
 		}
 	}
 	if ($format) {
 		$cookie =~ s/&/<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/g;
 	}
 	return $cookie;
}

sub getNameValue { # name=value
 	local ($pair) = (@_);
 	local ($name, $value) = split (/=/, $pair, 2);
     $value =~ s/\+/ /g;
     $value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
 	return ($name, $value);
}

sub formdata { # [ident] -> %F
 	local ($ident) = (@_);
     local (*formdata);
 	# first process options stored in cookie
 	local ($cookie) = getCookie($ident); # looks like a=xxx&b=yyy&c=zzz
 	local (@cookie) = split(/&/, $cookie);
 	local ($name, $value, $pair);
 	for $pair (@cookie) {
         ($name, $value) = &getNameValue($pair);
 		# print "<pre>$name = $value</pre>\n";
 		$F{$name} = $value;
 	}
 	# second get the options from the POST or GET
     if ($ENV{'REQUEST_METHOD'} eq 'POST') {
         read (STDIN, $F, $ENV{'CONTENT_LENGTH'});
     } else {
         $F = $ENV{'QUERY_STRING'};
     }
     @formdata = split (/&/, $F);
     for $pair (@formdata) {
         ($name, $value) = &getNameValue($pair);
         # $F{$name} .= "\n" if $F{$name}; # additional values append to previous
         # $F{$name} .= $value;
         $F{$name} = $value; # additional values override previous
     }
 	# finally, handle clear TODO - why not just set %F = ()?
 	$F{'clear'} && clearForm();
     return (%F);
}

From perlman@turing.acm.org Tue Aug 28 21:29:03 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Tue, 28 Aug 2007 21:29:03 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Gary Perlman at OCLC <perlman@oclc.org>
Subject: chaccess.cgi with input box
Message-ID: <Pine.LNX.4.64.0708282128490.1609@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

#! /usr/local/bin/perl

# TODO check for colspan and rowspan
# TODO check for skip navigation strings
# DONE label tag missing http://www.hcibib.org/perlman/chaccess.cgi?url=yahoo.com
# DONE http://www.hcibib.org/perlman/chaccess.cgi?url=hcibib.org hard tag for query
# OKAY http://www.hcibib.org/perlman/chaccess.cgi?url=hcibib.org/bs.cgi
# TODO label tag error http://www.hcibib.org/perlman/chaccess.cgi?url=worldcat.org/oclc/1
# TODO title tag for links and buttons
# TODO accesskey for buttons
# TODO textarea for html input
# TODO cookies for persistent options
# TODO report missing / on single tag items; missing quotes
# TODO catch bad alt text - bullet, xyz.gif
# TODO

use LWP::Simple;

$title = "Check Accessibility";
if ($ARGV[0]) {
 	&refer($ARGV[0]);
} else {
 	$cookiename = "chaccess";
 	%F = &formdata($cookiename);
 	if ($F{'input'}) {
 		$url = "";
 	} else {
 		$url = $F{'url'};
 		$url = "hcibib.org" unless $url;
 		$url = "http://" . $url unless ($url =~ m|^https?://|i);
 	}
 	print "Content-type: text/html\r\n\r\n";
 	print "<html lang=en><head>\n";
 	print '<meta name="description" content="Simple Accessibility Checker" />';
 	print '<meta name="keywords" content="accessibility;check;validation;form;label;image;img" />';
 	print "<title>$title</title>\n";
 	print "<base href=\"$url\" />\n";
 	print "</head><body>\n";
 	print "<h1>$title</h1>\n";
 	print "<form action=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}\" method=POST>\n";
 	print "<label for=url>URL</label>: <input name=url size=40 id=url value=\"$F{'url'}\" />\n";
 	print "<input type=submit accesskey=c value=\"$title\" />\n";
 	print "<div>\n";
 	print "<input type=checkbox value=checked name=source id=source $F{'source'}/><label for=source>Show HTML</label>\n";
 	print "<input type=checkbox value=checked name=images id=images $F{'images'}/><label for=images>Show Images</label>\n";
 	print "<input type=checkbox value=checked name=labels id=labels $F{'labels'}/><label for=labels>Show Labels</label>\n";
 	# print "<input type=checkbox value=checked name=links id=links $F{'links'}/><label for=links>Show Links</label>\n";
 	print "</div>\n";
 	print "<div>\n";
 	print "<label for=input>HTML</label>: <textarea name=input id=input rows=2 cols=60></textarea>\n";
 	print "</div>\n";
 	print "</form>\n";
 	$cgi = 1;
 	&refer($url, $F{'input'});
 	# print "<script type=\"text/javascript\" language=\"JavaScript\">\n\t<!--\n";
 	# print "\t\tsetCookie(\"$cookiename\", \"$parms\");\n";
 	# print "\t// -->\n</script>\n";
 	print "</body></html>\n";
}


sub refer { # url
  	local ($url, $input) = (@_);
  	local ($doc);
 	if ($input) {
 		$doc = $input;
 	} else {
 		$doc = get $url; # FETCH THE URL
 	}
  	# print $doc;

 	$cgi && print "<table border=0 cellpadding=5 cellspacing=0>\n";
  	$url && showIt("url", $url, "<a href=\"$url\">$url</a>");
  	showIt("length", length($doc));
 	&checkHeader($doc);

 	$F{'source'} && &dump($doc);
 	# $doc =~ s/\012/ /g; # map newlines to spaces
 	&checkImages($doc);
 	&checkForm($doc);
 	$F{'links'} && &checkLinks($doc);

 	$cgi && print "</table>\n";
}

sub checkHeader { # doc
 	local ($doc) = (@_);

  	if ($title = &getTitle($doc)) {
  		showIt("title", $title);
  	} else {
 		&showErr("NO TITLE") unless $title;
  	}
  	if ($desc = &getMeta($doc, 'description')) {
  		showIt("desc", $desc);
  	} else {
 		&showErr("NO DESCRIPTION") unless $desc;
 	}
  	if ($keywords = &getMeta($doc, 'keywords')) {
  		showIt("keywords", $keywords);
  	} else {
 		&showErr("NO KEYWORDS") unless $keywords;
 	}
}

sub checkImages { # doc
 	local ($doc) = (@_);
 	# <IMG
 	while ($doc =~ /(<img [^>]+>)/mi) {
 		$doc =~ s///;
 		$img = $1;
 		local ($extra) = $F{'images'} ? $img : "";
 		showIt('img', $img, $extra);
 		&checkAlt($img);
 	}
}

sub checkForm { # doc
 	local ($doc) = (@_);
 	# <INPUT # <TEXTAREA
 	%label = &initLabel($doc);
 	while ($doc =~ /(<(input|textarea|select)\b[^>]*>)/mi) {
 		$doc =~ s///;
 		$element = $1;
 		showIt('input', $1);
 		# showIt('type', $type);
 		$type = &getAttr($element, 'type');
 		$type = 'text' unless $type;
 		if ($type =~ /(image)/mi) {
 			&checkAlt($element);
 		} elsif ($type =~ /(text|radio|checkbox)/) {
 			$id = &getAttr($element, 'id');
 			if ($id) { # look for label tag
 				if ($doc =~ m@<label\b[^>]*for=("$id"|$id\b)[^>]*>(.+)</label>@mi) {
 					# label tag is present for $id
 					$match = $1;
 					$label = $2;
 					$label =~ s|</label>.*||mi; # make it shortest matching string
 					$F{'labels'} && &showIt("$id label", $label, 'info');
 				} elsif ($label{$id} ne "") {
 					$F{'labels'} && &showIt("$id label", $label{$id}, 'info');
 				} else {
 					&showErr("NO LABEL TAG FOR '$id'");
 					# &dump($doc);
 				}
 			} else {
 				&showErr("NO ID ATTRIBUTE, SO NO LABEL TAG POSSIBLE");
 			}
 		}
 	}
}

sub dump {
 	if ($cgi) {
 		local ($text) = (@_);
 		print "<tr><th align=left><label for=html>HTML</label></th><td><textarea id=html cols=80 rows=10>";
 		print &escape($text);
 		print "</textarea></td></tr>\n";
 	}
}

sub initLabel {
 	local ($doc) = (@_);
 	local ($attrs, $label, $for);
 	$doc =~ s/<label\b/\001/gi;
 	$doc =~ s/<\/label>/\002/gi;
 	local (%label);
 	while ($doc =~ /\001([^\002]*)\002/) {
 		$label = $1;
 		$doc =~ s///;
 		$attrs = $label;
 		$attrs =~ s/>.*$//; # keep the attributes
 		if ($attrs =~ /for="([^"]*)"/) {
 			$for = $1;
 			# &showErr("for-quote=$for");
 		} elsif ($attrs =~ /for=([\S]*)/) {
 			$for = $1;
 			# &showErr("for=$for");
 		}
 		if ($for) {
 			$label =~ s/[^>]*>//; # remove the attributes before first >
 			$label{$for} = $label;
 			# &showErr("[$for]=$label");
 		}
 	}
 	return %label;
}

sub checkLinks { # doc
 	local ($doc) = (@_);
 	while ($doc =~ m|<a\b[^>]*href="([^"]+)">([^<]+)</a>|mi) {
 		$doc =~ s///;
 		$href = $1;
 		$label = $2;
 		$qhref = $href;
 		$qhref = "$url/$qhref" unless ($qhref =~ m|^https?://|i);
 		$qhref = encode($qhref);
 		showIt("link", "$href - $label", "<a href=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}?url=$qhref\">$label</a>");
 	}
}

sub checkAlt { # tag
 	local ($tag) = (@_);
 	local ($alt) = &getAttr($tag, 'alt');
 	$F{'labels'} && &showIt("alt", "$alt", 'info');
 	if (!$alt) {
 		&showErr("NO ALT TEXT");
 	}
}

sub getAttr { # tag name
 	local ($tag, $name) = (@_);
 	if ($tag =~ m/\b$name="([^"]+)"/mi) {
 		return $1;
 	}
 	if ($tag =~ m/\b$name=([^ >]+)[ >]/mi) {
 		return $1;
 	}
 	return "";
}

sub showErr {
 	local ($msg) = (@_);
 	if ($cgi) {
 		local $label = 'error';
 		$count{$label}++;
 		print "<tr valign=top><th align=left nowrap>$label-$count{$label}</th><td bgcolor=\"#FFFFCC\">$msg</td></tr>\n";
 	} else {
 		print "\t******* $msg ******\n";
 	}
}

sub showIt {
 	local ($name, $value, $extra) = (@_);
 	$value = &escape($value) if $cgi;
 	if ($extra eq 'info') {
 		$extra = '';
 		$bgcolor = 'bgcolor="#CCCCFF"';
 	} else {
 		$bgcolor = 'bgcolor="#FFFFFF"';
 	}
 	$count{$name}++;
 	if ($cgi) {
 		print "<tr valign=top>
 				<th align=left nowrap>$name-$count{$name}</th>
 				<td $bgcolor >$value <div>$extra</div></td>
 			</tr>\n";
 	} else {
 		print "$name-$count{$name}\n\t$value\n";
 	}
}

sub clean {
  	local ($s) = (@_);
  	$s =~ s/^\s*//;
  	$s =~ s/\s*$//;
  	return $s;
}

sub getMeta {
  	local ($doc, $name) = (@_);
  	if ($doc =~ m|<meta name="$name"\s+content="([^"]*)"|si) {
  		return &clean($1);
  	}
  	return "";
}

sub getTitle {
  	local ($doc) = (@_);
  	if ($doc =~ m|<title>([^<]*)</title>|i) {
  		return &clean($1);
  	}
  	return "";
}




sub encode { # string
     local ($str) = (@_);
 	$str =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
 	$str =~ s/%20/+/g;
 	return $str;
}

sub escape { # string
 	local ($s) = (@_);
 	# $s =~ s/&/&amp;/g; # don't do this or sgml entities are broken
 	$s =~ s/"/&quot;/g;
 	$s =~ s/</&lt;/g;
 	$s =~ s/</&gt;/g;
 	return $s;
}

sub getCookie { # name
 	local ($ident, $format) = (@_);
 	local ($cookie);
 	for (split (/[;] */, $ENV{'HTTP_COOKIE'})) {
 		if (/^$ident=(.*)/) {
 			$cookie = $1;
 			$cookie =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg; # decode
 			# now $cookie looks like a=xxx&b=yyy&c=zzz
 			break;
 		}
 	}
 	if ($format) {
 		$cookie =~ s/&/<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/g;
 	}
 	return $cookie;
}

sub getNameValue { # name=value
 	local ($pair) = (@_);
 	local ($name, $value) = split (/=/, $pair, 2);
     $value =~ s/\+/ /g;
     $value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
 	return ($name, $value);
}

sub formdata { # [ident] -> %F
 	local ($ident) = (@_);
     local (*formdata);
 	# first process options stored in cookie
 	local ($cookie) = getCookie($ident); # looks like a=xxx&b=yyy&c=zzz
 	local (@cookie) = split(/&/, $cookie);
 	local ($name, $value, $pair);
 	for $pair (@cookie) {
         ($name, $value) = &getNameValue($pair);
 		# print "<pre>$name = $value</pre>\n";
 		$F{$name} = $value;
 	}
 	# second get the options from the POST or GET
     if ($ENV{'REQUEST_METHOD'} eq 'POST') {
         read (STDIN, $F, $ENV{'CONTENT_LENGTH'});
     } else {
         $F = $ENV{'QUERY_STRING'};
     }
     @formdata = split (/&/, $F);
     for $pair (@formdata) {
         ($name, $value) = &getNameValue($pair);
         # $F{$name} .= "\n" if $F{$name}; # additional values append to previous
         # $F{$name} .= $value;
         $F{$name} = $value; # additional values override previous
     }
 	# finally, handle clear TODO - why not just set %F = ()?
 	$F{'clear'} && clearForm();
     return (%F);
}

From perlman@turing.acm.org Wed Aug 29 15:31:31 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Wed, 29 Aug 2007 15:31:31 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Alan Cantor <acantor@cantoraccess.com>
Subject: Re: Dilemma: What to do with Legacy PDF?
In-Reply-To: <NDBBIFAOLLCHBBKFDBJJCEPJGLAA.acantor@cantoraccess.com>
Message-ID: <Pine.LNX.4.64.0708291530470.27207@turing.acm.org>
References: <NDBBIFAOLLCHBBKFDBJJCEPJGLAA.acantor@cantoraccess.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Hi Alan,

I wrote a little script and was running it on sites that should be accessible...

http://www.hcibib.org/perlman/chaccess.cgi?url=http://www.cantoraccess.com/

Best wishes,

Gary

From perlman@turing.acm.org Wed Aug 29 16:57:29 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Wed, 29 Aug 2007 16:57:29 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: kenneth.rich@rochester.edu
Subject: Re: Obtaining |STAT
In-Reply-To: <200708292002.l7TK2M0E003987@lursa.cc.rochester.edu>
Message-ID: <Pine.LNX.4.64.0708291657220.11309@turing.acm.org>
References: <200708292002.l7TK2M0E003987@lursa.cc.rochester.edu>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Wed, 29 Aug 2007, kenneth.rich@rochester.edu wrote:

> I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
> I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS
>
>
> Hi-
>
> I'd like to get the Unix (we run Solaris) download of |STAT.
> Dheller at ling.rochester.edu requested that I install this software
> for her.
>
> Thanks.
>
> -ken rich           585-275-9137            kenneth.rich(at)rochester.edu
> =========================================================================
>    Unix Group,  Systems Mgmt & Support,  Networking & Communications
>    University Information Technology
>    University of Rochester,  727 Elmwood Ave,  Rochester, NY 14620
> =========================================================================
>

From perlman@turing.acm.org Wed Aug 29 20:04:24 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Wed, 29 Aug 2007 20:04:23 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Gary Perlman at OCLC <perlman@oclc.org>
Subject: chaccess
Message-ID: <Pine.LNX.4.64.0708292004080.15134@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

#! /usr/local/bin/perl

# TODO cookies for persistent options
# TODO correct the identification of the base url
# TODO 
####### Examples
# TODO label tag error http://www.hcibib.org/perlman/chaccess.cgi?url=worldcat.org/oclc/1
# DONE label tag missing http://www.hcibib.org/perlman/chaccess.cgi?url=yahoo.com
# DONE http://www.hcibib.org/perlman/chaccess.cgi?url=hcibib.org hard tag for query
# OKAY http://www.hcibib.org/perlman/chaccess.cgi?url=hcibib.org/bs.cgi
####### Possible Checks
# TODO check for colspan and rowspan
# TODO check for skip navigation strings
# TODO catch bad alt text - "", bullet, xyz.gif
# TODO report missing / on single tag items; missing quotes
# TODO count accesskeys for buttons
# TODO title tag for links and buttons
# TODO sizes on images to speed display
# TODO resizable text
# TODO matching alt and title text

use LWP::Simple;

$title = "Check Accessibility";
if ($ARGV[0]) {
 	$url = $ARGV[0];
 	$url = "http://" . $url unless ($url =~ m|^https?://|i);
 	&process($url);
} else {
 	$cgi = 1;
 	$cookiename = "chaccess";
 	%F = &formdata($cookiename);
 	if ($F{'input'}) {
 		$url = "";
 	} else {
 		$url = $F{'url'};
 		if (! $url) {
 			# $url = "$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}";
 			# $url =~ s|/[^/]*$||; # remove script name and leave directory
 			# $url .= "/chaccess.htm";
 			$url = "hcibib.org";
 			$F{'url'} = $url;
 		}
 		$url = "http://" . $url unless ($url =~ m|^https?://|i);
 	}
 	print "Content-type: text/html\r\n\r\n";
 	print "<html lang=en><head>\n";
 	print '<meta http-equiv="content-type" content="text/html; charset=UTF-8" />';
 	print '<meta name="description" content="Simple Accessibility Checker: reports missing alt text from images and missing label tags from form elements." />';
 	print '<meta name="keywords" content="accessibility;check;validation;form;input;label;image;img;alt-text" />';
 	print "<title>$title</title>\n";
 	print "<base href=\"$url\" />\n" if $url;
 	print "</head>\n<body>\n";
 	print "<h1>$title</h1>\n";
 	print "<form action=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}\" method=POST>\n";
 	&inputField('url', 'URL', "URL of web page to check", 0, 40);
 	print "<input type=submit accesskey=c value=\"$title\" />\n";
 	print "<div>\n";
 	&checkBox('source', 'Show HTML', "Show the HTML that was processed in a box");
 	&checkBox('images', 'Show Images', "Show the images in the output");
 	&checkBox('labels', 'Show Labels', "Show the labels for form elements and image alt text in the output");
 	# &checkBox('links', 'Show Links', "Show links in the document for followup checking");
 	print "</div>\n";
 	&inputField('input', 'HTML Input', "Provide HTML input here instead of a URL", 2, 60, 1);
 	print "</form>\n";
 	&process($url, $F{'input'});
 	# print "<script type=\"text/javascript\" language=\"JavaScript\">\n\t<!--\n";
 	# print "\t\tsetCookie(\"$cookiename\", \"$parms\");\n";
 	# print "\t// -->\n</script>\n";
 	print "</body>\n</html>\n";
}

sub checkBox {
  	local ($name, $label, $help) = (@_);
 	print "<input type=checkbox value=checked name=$name id=$name $F{$name}/><label title=\"$help\" for=$name>$label</label>\n";
}

sub inputField {
  	local ($name, $label, $help, $rows, $cols, $nodata) = (@_);
 	local ($value) = escape($F{$name}) unless $nodata;
 	if ($rows == 0) {
 		print "<label title=\"$help\" for=$name>$label</label>:\n";
 		print "<input name=$name id=$name title=\"$help\" size=$cols value=\"$value\" />\n";
 	} else {
 		print "<div>\n";
 		print "<label title=\"$help\" for=$name>$label</label>:\n";
 		print "<textarea rows=$rows cols=$cols value=checked name=$name id=$name title=\"$help\"/>";
 		print $value;
 		print "</textarea>\n";
 		print "</div>\n";
 	}
}

sub process { # url
  	local ($url, $input) = (@_);
  	local ($doc);
 	if ($input) {
 		$doc = $input;
 	} else {
 		$doc = get $url; # FETCH THE URL
 	}
  	# print $doc;
 	$doc = &remComments($doc);

 	$cgi && print "<table border=0 cellpadding=5 cellspacing=0>\n";
 	&checkHeader($doc, $url);

 	$F{'source'} && &dump($doc);
 	# $doc =~ s/\012/ /g; # map newlines to spaces
 	&checkImages($doc);
 	&checkForm($doc);
 	$F{'links'} && &checkLinks($doc);

 	$cgi && print "</table>\n";
}

sub checkHeader { # doc
 	local ($doc, $url) = (@_);

 	&showHeader("Document Information");
  	$url && showIt("url", $url, "<a href=\"$url\">$url</a>");
  	showIt("length", length($doc));

  	if ($title = &getTitle($doc)) {
  		showIt("title", $title);
  	} else {
 		&showErr("NO TITLE") unless $title;
  	}
  	if ($desc = &getMeta($doc, 'description')) {
  		showIt("desc", $desc);
  	} else {
 		&showErr("NO DESCRIPTION") unless $desc;
 	}
  	if ($keywords = &getMeta($doc, 'keywords')) {
  		showIt("keywords", $keywords);
  	} else {
 		&showErr("NO KEYWORDS") unless $keywords;
 	}
}

sub checkImages { # doc
 	local ($doc) = (@_);
 	# <IMG
 	&showHeader("Image Alt Text");
 	while ($doc =~ /(<img [^>]+>)/mi) {
 		$doc =~ s///;
 		$img = $1;
 		local ($extra) = $F{'images'} ? $img : "";
 		showIt('img', $img, $extra);
 		&checkAlt($img);
 	}
}

sub checkForm { # doc
 	local ($doc) = (@_);
 	# INPUT / TEXTAREA
 	%label = &initLabel($doc);
 	&showHeader("Form Label Tags");
 	while ($doc =~ /(<(input|textarea|select)\b[^>]*>)/mi) {
 		$doc =~ s///;
 		$element = $1;
 		showIt('input', $1);
 		# showIt('type', $type);
 		$type = &getAttr($element, 'type');
 		$type = 'text' unless $type;
 		if ($type =~ /(image)/mi) {
 			&checkAlt($element);
 		} elsif ($type =~ /(text|radio|checkbox)/) {
 			$id = &getAttr($element, 'id');
 			if ($id) { # look for label tag
 				if ($doc =~ m@<label\b[^>]*for=("$id"|$id\b)[^>]*>(.+)</label>@mi) {
 					# label tag is present for $id
 					$match = $1;
 					$label = $2;
 					$label =~ s|</label>.*||mi; # make it shortest matching string
 					$F{'labels'} && &showIt("$id label", $label, 'info');
 				} elsif ($label{$id} ne "") {
 					$F{'labels'} && &showIt("$id label", $label{$id}, 'info');
 				} else {
 					&showErr("NO LABEL TAG FOR '$id'");
 					# &dump($doc);
 				}
 			} else {
 				&showErr("NO ID ATTRIBUTE, SO NO LABEL TAG POSSIBLE");
 			}
 		}
 	}
}

sub dump {
 	if ($cgi) {
 		local ($text) = (@_);
 		print "<tr><th align=left><label for=html>HTML</label></th><td><textarea id=html cols=80 rows=10>";
 		print &escape($text);
 		print "</textarea></td></tr>\n";
 	}
}

sub initLabel {
 	local ($doc) = (@_);
 	local ($attrs, $label, $for);
 	$doc =~ s/<label\b/\001/gi;
 	$doc =~ s/<\/label>/\002/gi;
 	local (%label);
 	while ($doc =~ /\001([^\002]*)\002/) {
 		$label = $1;
 		$doc =~ s///;
 		$attrs = $label;
 		$attrs =~ s/>.*$//; # keep the attributes
 		if ($attrs =~ /for="([^"]*)"/) {
 			$for = $1;
 			# &showErr("for-quote=$for");
 		} elsif ($attrs =~ /for=([\S]*)/) {
 			$for = $1;
 			# &showErr("for=$for");
 		}
 		if ($for) {
 			$label =~ s/[^>]*>//; # remove the attributes before first >
 			$label{$for} = $label;
 			# &showErr("[$for]=$label");
 		}
 	}
 	return %label;
}

sub checkLinks { # doc
 	local ($doc) = (@_);
 	&showHeader("Links");
 	while ($doc =~ m|<a\b[^>]*href="([^"]+)">([^<]+)</a>|mi) {
 		$doc =~ s///;
 		$href = $1;
 		$label = $2;
 		$qhref = $href;
 		$qhref = "$url/$qhref" unless ($qhref =~ m|^https?://|i);
 		$qhref = encode($qhref);
 		showIt("link", "$href - $label", "<a href=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}?url=$qhref\">$label</a>");
 	}
}

sub checkAlt { # tag
 	local ($tag) = (@_);
 	local ($alt) = &getAttr($tag, 'alt');
 	$F{'labels'} && &showIt("alt", "$alt", 'info');
 	if (!$alt) {
 		&showErr("NO ALT TEXT");
 	}
}

sub getAttr { # tag name
 	local ($tag, $name) = (@_);
 	if ($tag =~ m/\b$name="([^"]*)"/mi) {
 		if ($1 eq "") {
 			return '""';
 		} else {
 			return $1;
 		}
 	}
 	if ($tag =~ m/\b$name=([^ >]+)[ >]/mi) {
 		return $1;
 	}
 	return "";
}

sub remComments {
 	local ($doc) = (@_);
 	$doc =~ s/-->/\002/g; # map closer to single char for minimal match
 	while ($doc =~ /<!--/) {
 		$doc =~ s/(<!--[^\002]*\002)//;
 		break unless $1; # make sure we made a change
 	}
 	$doc =~ s/\002/-->/g;
 	return $doc;
}

sub showMsg {
 	local ($msg, $label, $color) = (@_);
 	if ($cgi) {
 		$count{$label}++;
 		print "<tr valign=top><th align=left nowrap>$label-$count{$label}</th><td bgcolor=\"$color\">$msg</td></tr>\n";
 	} else {
 		print "\t******* $msg ******\n";
 	}
}

sub showErr {
 	local ($msg) = (@_);
 	&showMsg($msg, 'error', "#FFFFCC");
}

sub showHeader {
 	local ($msg) = (@_);
 	&showMsg($msg, 'ANALYSIS', "#CCCCCC");
}

sub showIt {
 	local ($name, $value, $extra) = (@_);
 	$value = &escape($value) if $cgi;
 	if ($extra eq 'info') {
 		$extra = '';
 		$bgcolor = 'bgcolor="#CCCCFF"';
 	} else {
 		$bgcolor = 'bgcolor="#FFFFFF"';
 	}
 	$count{$name}++;
 	if ($cgi) {
 		print "<tr valign=top>
 				<th align=left nowrap>$name-$count{$name}</th>
 				<td $bgcolor >$value <div>$extra</div></td>
 			</tr>\n";
 	} else {
 		print "$name-$count{$name}\n\t$value\n";
 	}
}

sub clean {
  	local ($s) = (@_);
  	$s =~ s/^\s*//;
  	$s =~ s/\s*$//;
  	return $s;
}

sub getMeta {
  	local ($doc, $name) = (@_);
  	if ($doc =~ m|<meta name="$name"\s+content="([^"]*)"|si) {
  		return &clean($1);
  	}
  	return "";
}

sub getTitle {
  	local ($doc) = (@_);
  	if ($doc =~ m|<title>([^<]*)</title>|i) {
  		return &clean($1);
  	}
  	return "";
}




sub encode { # string
     local ($str) = (@_);
 	$str =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
 	$str =~ s/%20/+/g;
 	return $str;
}

sub escape { # string
 	local ($s) = (@_);
 	# $s =~ s/&/&amp;/g; # don't do this or sgml entities are broken
 	$s =~ s/"/&quot;/g;
 	$s =~ s/</&lt;/g;
 	$s =~ s/</&gt;/g;
 	return $s;
}

sub getCookie { # name
 	local ($ident, $format) = (@_);
 	local ($cookie);
 	for (split (/[;] */, $ENV{'HTTP_COOKIE'})) {
 		if (/^$ident=(.*)/) {
 			$cookie = $1;
 			$cookie =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg; # decode
 			# now $cookie looks like a=xxx&b=yyy&c=zzz
 			break;
 		}
 	}
 	if ($format) {
 		$cookie =~ s/&/<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/g;
 	}
 	return $cookie;
}

sub getNameValue { # name=value
 	local ($pair) = (@_);
 	local ($name, $value) = split (/=/, $pair, 2);
     $value =~ s/\+/ /g;
     $value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
 	return ($name, $value);
}

sub formdata { # [ident] -> %F
 	local ($ident) = (@_);
     local (*formdata);
 	# first process options stored in cookie
 	local ($cookie) = getCookie($ident); # looks like a=xxx&b=yyy&c=zzz
 	local (@cookie) = split(/&/, $cookie);
 	local ($name, $value, $pair);
 	for $pair (@cookie) {
         ($name, $value) = &getNameValue($pair);
 		# print "<pre>$name = $value</pre>\n";
 		$F{$name} = $value;
 	}
 	# second get the options from the POST or GET
     if ($ENV{'REQUEST_METHOD'} eq 'POST') {
         read (STDIN, $F, $ENV{'CONTENT_LENGTH'});
     } else {
         $F = $ENV{'QUERY_STRING'};
     }
     @formdata = split (/&/, $F);
     for $pair (@formdata) {
         ($name, $value) = &getNameValue($pair);
         # $F{$name} .= "\n" if $F{$name}; # additional values append to previous
         # $F{$name} .= $value;
         $F{$name} = $value; # additional values override previous
     }
 	# finally, handle clear TODO - why not just set %F = ()?
 	$F{'clear'} && clearForm();
     return (%F);
}

From perlman@turing.acm.org Wed Aug 29 21:12:51 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Wed, 29 Aug 2007 21:12:51 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Gary Perlman at OCLC <perlman@oclc.org>
Subject: chaccess with support for single quoted attributes
Message-ID: <Pine.LNX.4.64.0708292112320.29105@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

#! /usr/local/bin/perl

# TODO cookies for persistent options
# TODO correct the identification of the base url
# TODO 
####### Examples
# TODO label tag error http://www.hcibib.org/perlman/chaccess.cgi?url=worldcat.org/oclc/1
# DONE label tag missing http://www.hcibib.org/perlman/chaccess.cgi?url=yahoo.com
# DONE http://www.hcibib.org/perlman/chaccess.cgi?url=hcibib.org hard tag for query
# OKAY http://www.hcibib.org/perlman/chaccess.cgi?url=hcibib.org/bs.cgi
####### Possible Checks
# TODO check for colspan and rowspan
# TODO check for skip navigation strings
# TODO catch bad alt text - "", bullet, xyz.gif
# TODO report missing / on single tag items; missing quotes
# TODO count accesskeys for buttons
# TODO title tag for links and buttons
# TODO sizes on images to speed display
# TODO resizable text
# TODO matching alt and title text

use LWP::Simple;

$title = "Check Accessibility";
if ($ARGV[0]) {
 	$url = $ARGV[0];
 	$url = "http://" . $url unless ($url =~ m|^https?://|i);
 	&process($url);
} else {
 	$cgi = 1;
 	$cookiename = "chaccess";
 	%F = &formdata($cookiename);
 	if ($F{'input'}) {
 		$url = "";
 	} else {
 		$url = $F{'url'};
 		if (! $url) {
 			# $url = "$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}";
 			# $url =~ s|/[^/]*$||; # remove script name and leave directory
 			# $url .= "/chaccess.htm";
 			$url = "hcibib.org";
 			$F{'url'} = $url;
 		}
 		$url = "http://" . $url unless ($url =~ m|^https?://|i);
 	}
 	print "Content-type: text/html\r\n\r\n";
 	print "<html lang=en><head>\n";
 	print '<meta http-equiv="content-type" content="text/html; charset=UTF-8" />';
 	print '<meta name="description" content="Simple Accessibility Checker: reports missing alt text from images and missing label tags from form elements." />';
 	print '<meta name="keywords" content="accessibility;check;validation;form;input;label;image;img;alt-text" />';
 	print "<title>$title</title>\n";
 	print "<base href=\"$url\" />\n" if $url;
 	print "</head>\n<body>\n";
 	print "<h1>$title</h1>\n";
 	print "<form action=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}\" method=POST>\n";
 	&inputField('url', 'URL', "URL of web page to check", 0, 40);
 	print "<input type=submit accesskey=c value=\"$title\" />\n";
 	print "<div>\n";
 	&checkBox('source', 'Show HTML', "Show the HTML that was processed in a box");
 	&checkBox('images', 'Show Images', "Show the images in the output");
 	&checkBox('labels', 'Show Labels', "Show the labels for form elements and image alt text in the output");
 	# &checkBox('links', 'Show Links', "Show links in the document for followup checking");
 	print "</div>\n";
 	&inputField('input', 'HTML Input', "Provide HTML input here instead of a URL", 2, 60, 1);
 	print "</form>\n";
 	&process($url, $F{'input'});
 	# print "<script type=\"text/javascript\" language=\"JavaScript\">\n\t<!--\n";
 	# print "\t\tsetCookie(\"$cookiename\", \"$parms\");\n";
 	# print "\t// -->\n</script>\n";
 	print "</body>\n</html>\n";
}

sub checkBox {
  	local ($name, $label, $help) = (@_);
 	print "<input type=checkbox value=checked name=$name id=$name $F{$name}/><label title=\"$help\" for=$name>$label</label>\n";
}

sub inputField {
  	local ($name, $label, $help, $rows, $cols, $nodata) = (@_);
 	local ($value) = escape($F{$name}) unless $nodata;
 	if ($rows == 0) {
 		print "<label title=\"$help\" for=$name>$label</label>:\n";
 		print "<input name=$name id=$name title=\"$help\" size=$cols value=\"$value\" />\n";
 	} else {
 		print "<div>\n";
 		print "<label title=\"$help\" for=$name>$label</label>:\n";
 		print "<textarea rows=$rows cols=$cols value=checked name=$name id=$name title=\"$help\"/>";
 		print $value;
 		print "</textarea>\n";
 		print "</div>\n";
 	}
}

sub process { # url
  	local ($url, $input) = (@_);
  	local ($doc);
 	if ($input) {
 		$doc = $input;
 	} else {
 		$doc = get $url; # FETCH THE URL
 	}
  	# print $doc;
 	$doc = &remComments($doc);

 	$cgi && print "<table border=0 cellpadding=5 cellspacing=0>\n";
 	&checkHeader($doc, $url);

 	$F{'source'} && &dump($doc);
 	# $doc =~ s/\012/ /g; # map newlines to spaces
 	&checkImages($doc);
 	&checkForm($doc);
 	$F{'links'} && &checkLinks($doc);

 	$cgi && print "</table>\n";
}

sub checkHeader { # doc
 	local ($doc, $url) = (@_);

 	&showHeader("Document Information");
  	$url && showIt("url", $url, "<a href=\"$url\">$url</a>");
  	showIt("length", length($doc));

  	if ($title = &getTitle($doc)) {
  		showIt("title", $title);
  	} else {
 		&showErr("NO TITLE") unless $title;
  	}
  	if ($desc = &getMeta($doc, 'description')) {
  		showIt("desc", $desc);
  	} else {
 		&showErr("NO DESCRIPTION") unless $desc;
 	}
  	if ($keywords = &getMeta($doc, 'keywords')) {
  		showIt("keywords", $keywords);
  	} else {
 		&showErr("NO KEYWORDS") unless $keywords;
 	}
}

sub checkImages { # doc
 	local ($doc) = (@_);
 	# <IMG
 	&showHeader("Image Alt Text");
 	while ($doc =~ /(<img [^>]+>)/mi) {
 		$doc =~ s///;
 		$img = $1;
 		local ($extra) = $F{'images'} ? $img : "";
 		showIt('img', $img, $extra);
 		&checkAlt($img);
 	}
}

sub checkForm { # doc
 	local ($doc) = (@_);
 	# INPUT / TEXTAREA
 	%label = &initLabel($doc);
 	&showHeader("Form Label Tags");
 	while ($doc =~ /(<(input|textarea|select)\b[^>]*>)/mi) {
 		$doc =~ s///;
 		$element = $1;
 		showIt('input', $1);
 		# showIt('type', $type);
 		$type = &getAttr($element, 'type');
 		$type = 'text' unless $type;
 		if ($type =~ /(image)/mi) {
 			&checkAlt($element);
 		} elsif ($type =~ /(text|radio|checkbox)/) {
 			$id = &getAttr($element, 'id');
 			if ($id) { # look for label tag for $id
 				if ($doc =~ m@<label\b[^>]*for=("$id"|'$id'|$id\b)[^>]*>(.+)</label>@mi) {
 					# label tag is present for $id
 					$match = $1;
 					$label = $2;
 					$label =~ s|</label>.*||mi; # make it shortest matching string
 					$F{'labels'} && &showIt("$id label", $label, 'info');
 				} elsif ($label{$id} ne "") {
 					$F{'labels'} && &showIt("$id label", $label{$id}, 'info');
 				} else {
 					&showErr("NO LABEL TAG FOR '$id'");
 					# &dump($doc);
 				}
 			} else {
 				&showErr("NO ID ATTRIBUTE, SO NO LABEL TAG POSSIBLE");
 			}
 		}
 	}
}

sub dump {
 	if ($cgi) {
 		local ($text) = (@_);
 		print "<tr><th align=left><label for=html>HTML</label></th><td><textarea id=html cols=80 rows=10>";
 		print &escape($text);
 		print "</textarea></td></tr>\n";
 	}
}

sub initLabel {
 	local ($doc) = (@_);
 	local ($attrs, $label, $for);
 	$doc =~ s/<label\b/\001/gi;
 	$doc =~ s/<\/label>/\002/gi;
 	local (%label);
 	while ($doc =~ /\001([^\002]*)\002/) {
 		$label = $1;
 		$doc =~ s///;
 		$attrs = $label;
 		$attrs =~ s/>.*$//; # keep the attributes
 		if ($attrs =~ /for="([^"]*)"/) {
 			$for = $1;
 			# &showErr("for-quote=$for");
 		} elsif ($attrs =~ /for=([\S]*)/) {
 			$for = $1;
 			# &showErr("for=$for");
 		}
 		if ($for) {
 			$label =~ s/[^>]*>//; # remove the attributes before first >
 			$label{$for} = $label;
 			# &showErr("[$for]=$label");
 		}
 	}
 	return %label;
}

sub checkLinks { # doc
 	local ($doc) = (@_);
 	&showHeader("Links");
 	# TODO only works for double quoted href attributes
 	while ($doc =~ m|<a\b[^>]*href="([^"]+)">([^<]+)</a>|mi) {
 		$doc =~ s///;
 		$href = $1;
 		$label = $2;
 		$qhref = $href;
 		$qhref = "$url/$qhref" unless ($qhref =~ m|^https?://|i);
 		$qhref = encode($qhref);
 		showIt("link", "$href - $label", "<a href=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}?url=$qhref\">$label</a>");
 	}
}

sub checkAlt { # tag
 	local ($tag) = (@_);
 	local ($alt) = &getAttr($tag, 'alt');
 	$F{'labels'} && &showIt("alt", "$alt", 'info');
 	if (!$alt) {
 		&showErr("NO ALT TEXT");
 	}
}

sub getAttr { # tag name
 	local ($tag, $name) = (@_);
 	# double quoted attr
 	if ($tag =~ m/\b$name="([^"]*)"/mi) {
 		if ($1 eq "") {
 			return '""';
 		} else {
 			return $1;
 		}
 	}
 	# single quoted attr
 	if ($tag =~ m/\b$name='([^']*)'/mi) {
 		if ($1 eq "") {
 			return "''";
 		} else {
 			return $1;
 		}
 	}
 	# unquoted attr
 	if ($tag =~ m/\b$name=([^ >]+)[ >]/mi) {
 		return $1;
 	}
 	return "";
}

sub remComments {
 	local ($doc) = (@_);
 	$doc =~ s/-->/\002/g; # map closer to single char for minimal match
 	while ($doc =~ /<!--/) {
 		$doc =~ s/(<!--[^\002]*\002)//;
 		break unless $1; # make sure we made a change
 	}
 	$doc =~ s/\002/-->/g;
 	return $doc;
}

sub showMsg {
 	local ($msg, $label, $color) = (@_);
 	if ($cgi) {
 		$count{$label}++;
 		print "<tr valign=top><th align=left nowrap>$label-$count{$label}</th><td bgcolor=\"$color\">$msg</td></tr>\n";
 	} else {
 		print "\t******* $msg ******\n";
 	}
}

sub showErr {
 	local ($msg) = (@_);
 	&showMsg($msg, 'error', "#FFFFCC");
}

sub showHeader {
 	local ($msg) = (@_);
 	&showMsg($msg, 'ANALYSIS', "#CCCCCC");
}

sub showIt {
 	local ($name, $value, $extra) = (@_);
 	$value = &escape($value) if $cgi;
 	if ($extra eq 'info') {
 		$extra = '';
 		$bgcolor = 'bgcolor="#CCCCFF"';
 	} else {
 		$bgcolor = 'bgcolor="#FFFFFF"';
 	}
 	$count{$name}++;
 	if ($cgi) {
 		print "<tr valign=top>
 				<th align=left nowrap>$name-$count{$name}</th>
 				<td $bgcolor >$value <div>$extra</div></td>
 			</tr>\n";
 	} else {
 		print "$name-$count{$name}\n\t$value\n";
 	}
}

sub clean {
  	local ($s) = (@_);
  	$s =~ s/^\s*//;
  	$s =~ s/\s*$//;
  	return $s;
}

sub getMeta {
  	local ($doc, $name) = (@_);
 	# find a meta tag with the desired name
  	if ($doc =~ m@(<meta name=("$name"|'$name'|$name\b).*)@si) {
 		local ($tag) = $1;          # meta tag not terminated
 		$tag =~ s/>.*/>/;           # get rid of stuff after this meta tag
 		return &clean( &getAttr($tag, 'content') );  # get the content
  	}
  	return "";
}

sub getTitle {
  	local ($doc) = (@_);
  	if ($doc =~ m|<title>([^<]*)</title>|i) {
  		return &clean($1);
  	}
  	return "";
}

sub encode { # string
     local ($str) = (@_);
 	$str =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
 	$str =~ s/%20/+/g;
 	return $str;
}

sub escape { # string
 	local ($s) = (@_);
 	# $s =~ s/&/&amp;/g; # don't do this or sgml entities are broken
 	$s =~ s/"/&quot;/g;
 	$s =~ s/</&lt;/g;
 	$s =~ s/</&gt;/g;
 	return $s;
}

sub getCookie { # name
 	local ($ident, $format) = (@_);
 	local ($cookie);
 	for (split (/[;] */, $ENV{'HTTP_COOKIE'})) {
 		if (/^$ident=(.*)/) {
 			$cookie = $1;
 			$cookie =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg; # decode
 			# now $cookie looks like a=xxx&b=yyy&c=zzz
 			break;
 		}
 	}
 	if ($format) {
 		$cookie =~ s/&/<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/g;
 	}
 	return $cookie;
}

sub getNameValue { # name=value
 	local ($pair) = (@_);
 	local ($name, $value) = split (/=/, $pair, 2);
     $value =~ s/\+/ /g;
     $value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
 	return ($name, $value);
}

sub formdata { # [ident] -> %F
 	local ($ident) = (@_);
     local (*formdata);
 	# first process options stored in cookie
 	local ($cookie) = getCookie($ident); # looks like a=xxx&b=yyy&c=zzz
 	local (@cookie) = split(/&/, $cookie);
 	local ($name, $value, $pair);
 	for $pair (@cookie) {
         ($name, $value) = &getNameValue($pair);
 		# print "<pre>$name = $value</pre>\n";
 		$F{$name} = $value;
 	}
 	# second get the options from the POST or GET
     if ($ENV{'REQUEST_METHOD'} eq 'POST') {
         read (STDIN, $F, $ENV{'CONTENT_LENGTH'});
     } else {
         $F = $ENV{'QUERY_STRING'};
     }
     @formdata = split (/&/, $F);
     for $pair (@formdata) {
         ($name, $value) = &getNameValue($pair);
         # $F{$name} .= "\n" if $F{$name}; # additional values append to previous
         # $F{$name} .= $value;
         $F{$name} = $value; # additional values override previous
     }
 	# finally, handle clear TODO - why not just set %F = ()?
 	$F{'clear'} && clearForm();
     return (%F);
}

From perlman@turing.acm.org Thu Aug 30 16:59:10 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Thu, 30 Aug 2007 16:59:10 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Gary Perlman at OCLC <perlman@oclc.org>
Subject: chaccess
Message-ID: <Pine.LNX.4.64.0708301658560.30619@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

#! /usr/local/bin/perl

# TODO cookies for persistent options
# TODO correct the identification of the base url
# TODO 
####### Examples
# TODO label tag error http://www.hcibib.org/perlman/chaccess.cgi?url=worldcat.org/oclc/1
# DONE label tag missing http://www.hcibib.org/perlman/chaccess.cgi?url=yahoo.com
# DONE http://www.hcibib.org/perlman/chaccess.cgi?url=hcibib.org hard tag for query
# OKAY http://www.hcibib.org/perlman/chaccess.cgi?url=hcibib.org/bs.cgi
####### Possible Checks
# TODO check for colspan and rowspan
# TODO check for skip navigation strings
# TODO catch bad alt text - "", bullet, xyz.gif
# TODO report missing / on single tag items; missing quotes
# TODO count accesskeys for buttons
# TODO title tag for links and buttons
# TODO sizes on images to speed display
# TODO resizable text
# TODO matching alt and title text

use LWP::Simple;

&init();

if ($ARGV[0]) {
 	$url = $ARGV[0];
 	$url = "http://" . $url unless ($url =~ m|^https?://|i);
 	&process($url);
} else {
 	$cgi = 1;
 	$cookiename = "$ENV{'SCRIPT_NAME'}";
 	$cookiename =~ s|.*/||;      # remove directory
 	$cookiename =~ s|[.].*$||;   # remove suffix
 	%F = &formdata($cookiename);
 	if ($F{'input'}) {
 		$url = "";
 	} else {
 		$url = $F{'url'};
 		if (! $url) {
 			# $url = "$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}";
 			# $url =~ s|/[^/]*$||; # remove script name and leave directory
 			# $url .= "/$cookiename.htm";
 			$url = "hcibib.org";
 			$F{'url'} = $url;
 		}
 		$url = "http://" . $url unless ($url =~ m|^https?://|i);
 	}
 	print "Content-type: text/html; charset=UTF-8\r\n\r\n";
 	print "<html lang=\"en\"><head>\n";
 	# charset set above
 	# print "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n";
 	print "<meta name=\"description\" content=\"$description\" />\n";
 	print "<meta name=\"keywords\" content=\"$keywords\" />\n";
 	print "<title>$title</title>\n";
 	print "<base href=\"$url\" />\n" if $url;
 	print "</head>\n<body bgcolor=\"$normalbg\">\n";
 	print "<h1 style=\"background: $titlebg\">$title</h1>\n";
 	print "<form action=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}\" method=POST>\n";
 	print "<DIV style=\"border: 1px solid #666666; padding: 5px;\">\n";
 	&inputField('url', 'URL', "URL of web page to check", 0, 40);
 	print "<input type=submit accesskey=c value=\"$title\" />\n";
 	print "<div style=\"margin-left: 6em\">\n";
 	&checkBox('source', 'Show HTML', "Show the HTML that was processed in a box");
 	&checkBox('images', 'Show Images', "Show the images in the output");
 	&checkBox('labels', 'Show Labels', "Show the labels for form elements and image alt text in the output");
 	# &checkBox('links', 'Show Links', "Show links in the document for followup checking");
 	print "</div>\n";
 	&inputField('input', 'HTML Input', "Provide HTML input here instead of a URL", 2, 60, 1);
 	print "</DIV>\n";
 	print "</form>\n";
 	&process($url, $F{'input'});
 	# print "<script type=\"text/javascript\" language=\"JavaScript\">\n\t<!--\n";
 	# print "\t\tsetCookie(\"$cookiename\", \"$parms\");\n";
 	# print "\t// -->\n</script>\n";
 	print "</body>\n</html>\n";
}

sub init {
 	$title    = "Check Accessibility";
 	$section  = 'Analysis';
 	$titlebg  = "#DDDDDD";
 	$infobg   = "#CCCCFF";
 	$errorbg  = "#FFFFCC";
 	$normalbg = "#FFFFFF";
 	$description = "Simple Accessibility Checker: reports missing alt text from images
 		and missing label tags from form elements.";
 	$keywords = "accessibility;check;validation;form;input;label;image;img;alt-text";
}

sub checkBox {
  	local ($name, $label, $help) = (@_);
 	print "<input type=checkbox value=checked name=$name id=$name $F{$name}/>\n";
 	print "\t<label title=\"$help\" for=$name>$label</label>&nbsp;\n";
}

sub inputField {
  	local ($name, $label, $help, $rows, $cols, $nodata) = (@_);
 	local ($value) = escape($F{$name}) unless $nodata;
 	local ($style) = 'style="width: 6em"';
 	if ($rows == 0) {
 		print "<label title=\"$help\" for=$name $style>$label:</label>\n";
 		print "<input name=$name id=$name title=\"$help\" size=$cols value=\"$value\" />\n";
 	} else {
 		print "<div>\n";
 		print "<label title=\"$help\" for=$name $style>$label:</label>\n";
 		print "<textarea rows=$rows cols=$cols value=checked name=$name id=$name title=\"$help\"/>";
 		print $value;
 		print "</textarea>\n";
 		print "</div>\n";
 	}
}

sub process { # url
  	local ($url, $input) = (@_);
  	local ($doc);
 	if ($input) {
 		$doc = $input;
 	} else {
 		$doc = get $url; # FETCH THE URL
 	}
  	# print $doc;
 	$doc = &remComments($doc);

 	if ($doc) {
 		$cgi && print "<table border=0 cellpadding=5 cellspacing=0>\n";
 		&checkHeader($doc, $url);

 		$F{'source'} && &dump($doc);
 		# $doc =~ s/\012/ /g; # map newlines to spaces
 		&checkImages($doc);
 		&checkForm($doc);
 		$F{'links'} && &checkLinks($doc);

 		$cgi && print "</table>\n";
 	} else {
 		&showErr("NO DOCUMENT");
 	}
}

sub checkHeader { # doc
 	local ($doc, $url) = (@_);

 	&showHeader("Document Information");
  	$url && showIt('Url', $url, "<a href=\"$url\">$url</a>");
  	showIt('Length', length($doc));

  	if ($title = &getTitle($doc)) {
  		showIt('Title', $title);
  	} else {
 		&showErr("NO TITLE") unless $title;
  	}
  	if ($desc = &getMeta($doc, 'description')) {
  		showIt('Description', $desc);
  	} else {
 		&showErr("NO DESCRIPTION") unless $desc;
 	}
  	if ($keywords = &getMeta($doc, 'keywords')) {
  		showIt('Keywords', $keywords);
  	} else {
 		&showErr("NO KEYWORDS") unless $keywords;
 	}
}

sub checkImages { # doc
 	local ($doc) = (@_);
 	# <IMG
 	&showHeader("Image Alt Text");
 	while ($doc =~ /(<img [^>]+>)/mi) {
 		$doc =~ s///;
 		$img = $1;
 		local ($extra) = $F{'images'} ? $img : "";
 		showIt('Image', $img, $extra);
 		&checkAlt($img);
 	}
}

sub checkForm { # doc
 	local ($doc) = (@_);
 	# INPUT / TEXTAREA
 	%label = &initLabel($doc);
 	&showHeader("Form Label Tags");
 	while ($doc =~ /(<(input|textarea|select)\b[^>]*>)/mi) {
 		$doc =~ s///;
 		$element = $1;
 		showIt('Input', $1);
 		# showIt('type', $type);
 		$type = &getAttr($element, 'type');
 		$type = 'text' unless $type;
 		if ($type =~ /(image)/mi) {
 			&checkAlt($element);
 		} elsif ($type =~ /(text|radio|checkbox)/) {
 			$id = &getAttr($element, 'id');
 			if ($id) { # look for label tag for $id
 				if ($doc =~ m@<label\b[^>]*for=("$id"|'$id'|$id\b)[^>]*>(.+)</label>@mi) {
 					# label tag is present for $id
 					$match = $1;
 					$label = $2;
 					$label =~ s|</label>.*||mi; # make it shortest matching string
 					$F{'labels'} && &showIt('Label', $label, 'info');
 				} elsif ($label{$id} ne "") {
 					$F{'labels'} && &showIt("$id label", $label{$id}, 'info');
 				} else {
 					&showErr("NO LABEL TAG FOR '$id'");
 					# &dump($doc);
 				}
 			} else {
 				&showErr("NO ID ATTRIBUTE, SO NO LABEL TAG POSSIBLE");
 			}
 		}
 	}
}

sub dump {
 	if ($cgi) {
 		local ($text) = (@_);
 		print "<tr>\n";
 		print "<th align=left><label for=html>HTML:</label></th>\n";
 		print "<td><textarea id=html cols=80 rows=10>";
 		print &escape($text);
 		print "</textarea></td></tr>\n";
 	}
}

sub initLabel {
 	local ($doc) = (@_);
 	local ($attrs, $label, $for);
 	$doc =~ s/<label\b/\001/gi;
 	$doc =~ s/<\/label>/\002/gi;
 	local (%label);
 	while ($doc =~ /\001([^\002]*)\002/) {
 		$label = $1;
 		$doc =~ s///;
 		$attrs = $label;
 		$attrs =~ s/>.*$//; # keep the attributes
 		if ($attrs =~ /for="([^"]*)"/) {
 			$for = $1;
 			# &showErr("for-quote=$for");
 		} elsif ($attrs =~ /for=([\S]*)/) {
 			$for = $1;
 			# &showErr("for=$for");
 		}
 		if ($for) {
 			$label =~ s/[^>]*>//; # remove the attributes before first >
 			$label{$for} = $label;
 			# &showErr("[$for]=$label");
 		}
 	}
 	return %label;
}

sub checkLinks { # doc
 	local ($doc) = (@_);
 	&showHeader("Links");
 	# TODO only works for double quoted href attributes
 	while ($doc =~ m|<a\b[^>]*href="([^"]+)">([^<]+)</a>|mi) {
 		$doc =~ s///;
 		$href = $1;
 		$label = $2;
 		$qhref = $href;
 		$qhref = "$url/$qhref" unless ($qhref =~ m|^https?://|i);
 		$qhref = encode($qhref);
 		showIt("link", "$href - $label", "<a href=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}?url=$qhref\">$label</a>");
 	}
}

sub checkAlt { # tag
 	local ($tag) = (@_);
 	local ($alt) = &getAttr($tag, 'alt');
 	$F{'labels'} && &showIt('Alt-text', "$alt", 'info');
 	if (!$alt) {
 		&showErr("NO ALT TEXT");
 	}
}

sub getAttr { # tag name
 	local ($tag, $name) = (@_);
 	# double quoted attr
 	if ($tag =~ m/\b$name="([^"]*)"/mi) {
 		if ($1 eq "") {
 			return '""';
 		} else {
 			return $1;
 		}
 	}
 	# single quoted attr
 	if ($tag =~ m/\b$name='([^']*)'/mi) {
 		if ($1 eq "") {
 			return "''";
 		} else {
 			return $1;
 		}
 	}
 	# unquoted attr
 	if ($tag =~ m/\b$name=([^ >]+)[ >]/mi) {
 		return $1;
 	}
 	return "";
}

sub remComments {
 	local ($doc) = (@_);
 	$doc =~ s/-->/\002/g; # map closer to single char for minimal match
 	while ($doc =~ /<!--/) {
 		$doc =~ s/(<!--[^\002]*\002)//;
 		break unless $1; # make sure we made a change
 	}
 	$doc =~ s/\002/-->/g;
 	return $doc;
}

sub showMsg {
 	local ($msg, $label, $color) = (@_);
 	if ($cgi) {
 		$count{$label}++;
 		print "<tr valign=top>\n";
 		print "<th align=left nowrap>";
 		print $label;
 		print "</th>\n";
 		print "<td bgcolor=\"$color\">";
 		print "<h2 style=\"margin: 0\">" if $label eq $section;
 		print $msg;
 		print "</h2>" if $label eq $section;
 		print "</td></tr>\n";
 	} else {
 		print "\t******* $msg ******\n";
 	}
}

sub showErr {
 	local ($msg) = (@_);
 	&showMsg($msg, 'Error', $errorbg);
}

sub showHeader {
 	local ($msg) = (@_);
 	&showMsg($msg, $section, $titlebg);
}

sub showIt {
 	local ($name, $value, $extra) = (@_);
 	$value = &escape($value) if $cgi;
 	if ($extra eq 'info') {
 		$extra = '';
 		$bgcolor = "bgcolor='$infobg'";
 	} else {
 		$bgcolor = "bgcolor='$normalbg'";
 	}
 	$count{$name}++;
 	if ($cgi) {
 		print "<tr valign=top>\n";
 		print "<th align=left nowrap>$name:</th>\n";
 		print "<td $bgcolor ><tt>$value</tt>\n\t<div>$extra</div></td>\n";
 		print "</tr>\n";
 	} else {
 		print "$name\n\t$value\n";
 	}
}

sub clean {
  	local ($s) = (@_);
  	$s =~ s/^\s*//;
  	$s =~ s/\s*$//;
  	return $s;
}

sub getMeta {
  	local ($doc, $name) = (@_);
 	# find a meta tag with the desired name
  	if ($doc =~ m@(<meta name=("$name"|'$name'|$name\b).*)@si) {
 		local ($tag) = $1;          # meta tag not terminated
 		$tag =~ s/>.*/>/;           # get rid of stuff after this meta tag
 		return &clean( &getAttr($tag, 'content') );  # get the content
  	}
  	return "";
}

sub getTitle {
  	local ($doc) = (@_);
  	if ($doc =~ m|<title>([^<]*)</title>|i) {
  		return &clean($1);
  	}
  	return "";
}

sub encode { # string
     local ($str) = (@_);
 	$str =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
 	$str =~ s/%20/+/g;
 	return $str;
}

sub escape { # string
 	local ($s) = (@_);
 	# $s =~ s/&/&amp;/g; # don't do this or sgml entities are broken
 	$s =~ s/"/&quot;/g;
 	$s =~ s/</&lt;/g;
 	$s =~ s/</&gt;/g;
 	return $s;
}

sub getCookie { # name
 	local ($ident, $format) = (@_);
 	local ($cookie);
 	for (split (/[;] */, $ENV{'HTTP_COOKIE'})) {
 		if (/^$ident=(.*)/) {
 			$cookie = $1;
 			$cookie =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg; # decode
 			# now $cookie looks like a=xxx&b=yyy&c=zzz
 			break;
 		}
 	}
 	if ($format) {
 		$cookie =~ s/&/<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/g;
 	}
 	return $cookie;
}

sub getNameValue { # name=value
 	local ($pair) = (@_);
 	local ($name, $value) = split (/=/, $pair, 2);
     $value =~ s/\+/ /g;
     $value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
 	return ($name, $value);
}

sub formdata { # [ident] -> %F
 	local ($ident) = (@_);
     local (*formdata);
 	# first process options stored in cookie
 	local ($cookie) = getCookie($ident); # looks like a=xxx&b=yyy&c=zzz
 	local (@cookie) = split(/&/, $cookie);
 	local ($name, $value, $pair);
 	for $pair (@cookie) {
         ($name, $value) = &getNameValue($pair);
 		# print "<pre>$name = $value</pre>\n";
 		$F{$name} = $value;
 	}
 	# second get the options from the POST or GET
     if ($ENV{'REQUEST_METHOD'} eq 'POST') {
         read (STDIN, $F, $ENV{'CONTENT_LENGTH'});
     } else {
         $F = $ENV{'QUERY_STRING'};
     }
     @formdata = split (/&/, $F);
     for $pair (@formdata) {
         ($name, $value) = &getNameValue($pair);
         # $F{$name} .= "\n" if $F{$name}; # additional values append to previous
         # $F{$name} .= $value;
         $F{$name} = $value; # additional values override previous
     }
 	# finally, handle clear TODO - why not just set %F = ()?
 	$F{'clear'} && clearForm();
     return (%F);
}

From perlman@turing.acm.org Thu Aug 30 19:41:35 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Thu, 30 Aug 2007 19:41:35 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Gary Perlman at OCLC <perlman@oclc.org>
Subject: chaccess with password input checking
Message-ID: <Pine.LNX.4.64.0708301941160.29484@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

#! /usr/local/bin/perl

# TODO cookies for persistent options
# TODO correct the identification of the base url
# TODO 
####### Examples
# TODO label tag error http://www.hcibib.org/perlman/chaccess.cgi?url=worldcat.org/oclc/1
# DONE label tag missing http://www.hcibib.org/perlman/chaccess.cgi?url=yahoo.com
# DONE http://www.hcibib.org/perlman/chaccess.cgi?url=hcibib.org hard tag for query
# OKAY http://www.hcibib.org/perlman/chaccess.cgi?url=hcibib.org/bs.cgi
####### Possible Checks
# TODO check for colspan and rowspan
# TODO check for skip navigation strings
# TODO catch bad alt text - "", bullet, xyz.gif
# TODO report missing / on single tag items; missing quotes
# TODO count accesskeys for buttons
# TODO title tag for links and buttons
# TODO sizes on images to speed display
# TODO resizable text
# TODO matching alt and title text

use LWP::Simple;

&init();

if ($ARGV[0]) {
 	$url = $ARGV[0];
 	$url = "http://" . $url unless ($url =~ m|^https?://|i);
 	&process($url);
} else {
 	$cgi = 1;
 	$cookiename = "$ENV{'SCRIPT_NAME'}";
 	$cookiename =~ s|.*/||;      # remove directory
 	$cookiename =~ s|[.].*$||;   # remove suffix
 	%F = &formdata($cookiename);
 	if ($F{'input'}) {
 		$url = "";
 	} else {
 		$url = $F{'url'};
 		if (! $url) {
 			# $url = "$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}";
 			# $url =~ s|/[^/]*$||; # remove script name and leave directory
 			# $url .= "/$cookiename.htm";
 			$url = "hcibib.org";
 			$F{'url'} = $url;
 		}
 		$url = "http://" . $url unless ($url =~ m|^https?://|i);
 	}
 	print "Content-type: text/html; charset=UTF-8\r\n\r\n";
 	print "<html lang=\"en\"><head>\n";
 	# charset set above
 	# print "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n";
 	print "<meta name=\"description\" content=\"$description\" />\n";
 	print "<meta name=\"keywords\" content=\"$keywords\" />\n";
 	print "<title>$title</title>\n";
 	print "<base href=\"$url\" />\n" if $url;
 	print "</head>\n<body bgcolor=\"$normalbg\">\n";
 	print "<h1 style=\"background: $titlebg\">$title</h1>\n";
 	print "<form action=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}\" method=POST>\n";
 	print "<DIV style=\"border: 1px solid #666666; padding: 5px;\">\n";
 	&inputField('url', 'URL', "URL of web page to check", 0, 40);
 	print "<input type=submit accesskey=c value=\"$title\" />\n";
 	print "<div style=\"margin-left: 6em\">\n";
 	&checkBox('source', 'Show HTML', "Show the HTML that was processed in a box");
 	&checkBox('images', 'Show Images', "Show the images in the output");
 	&checkBox('labels', 'Show Labels', "Show the labels for form elements and image alt text in the output");
 	# &checkBox('links', 'Show Links', "Show links in the document for followup checking");
 	print "</div>\n";
 	&inputField('input', 'HTML Input', "Provide HTML input here instead of a URL", 2, 60, 1);
 	print "</DIV>\n";
 	print "</form>\n";
 	&process($url, $F{'input'});
 	# print "<script type=\"text/javascript\" language=\"JavaScript\">\n\t<!--\n";
 	# print "\t\tsetCookie(\"$cookiename\", \"$parms\");\n";
 	# print "\t// -->\n</script>\n";
 	print "</body>\n</html>\n";
}

sub init {
 	$title    = "Check Accessibility";
 	$section  = 'Analysis';
 	$titlebg  = "#DDDDDD";
 	$infobg   = "#CCFFCC";
 	$errorbg  = "#FFCCCC";
 	$normalbg = "#FFFFFF";
 	$description = "Simple Accessibility Checker: reports missing alt text from images
 		and missing label tags from form elements.";
 	$keywords = "accessibility;check;validation;form;input;label;image;img;alt-text";
}

sub checkBox {
  	local ($name, $label, $help) = (@_);
 	print "<input type=checkbox value=checked name=$name id=$name $F{$name}/>\n";
 	print "\t<label title=\"$help\" for=$name>$label</label>&nbsp;\n";
}

sub inputField {
  	local ($name, $label, $help, $rows, $cols, $nodata) = (@_);
 	local ($value) = escape($F{$name}) unless $nodata;
 	local ($style) = 'style="width: 6em"';
 	if ($rows == 0) {
 		print "<label title=\"$help\" for=$name $style>$label:</label>\n";
 		print "<input name=$name id=$name title=\"$help\" size=$cols value=\"$value\" />\n";
 	} else {
 		print "<div>\n";
 		print "<label title=\"$help\" for=$name $style>$label:</label>\n";
 		print "<textarea rows=$rows cols=$cols value=checked name=$name id=$name title=\"$help\"/>";
 		print $value;
 		print "</textarea>\n";
 		print "</div>\n";
 	}
}

sub process { # url
  	local ($url, $input) = (@_);
  	local ($doc);
 	if ($input) {
 		$doc = $input;
 	} else {
 		$doc = get $url; # FETCH THE URL
 	}
  	# print $doc;
 	$doc = &remComments($doc);

 	if ($doc) {
 		$cgi && print "<table border=0 cellpadding=5 cellspacing=0>\n";
 		&checkHeader($doc, $url);

 		$F{'source'} && &dump($doc);
 		# $doc =~ s/\012/ /g; # map newlines to spaces
 		&checkImages($doc);
 		&checkForm($doc);
 		$F{'links'} && &checkLinks($doc);

 		$cgi && print "</table>\n";
 	} else {
 		&showErr("NO DOCUMENT");
 	}
}

sub checkHeader { # doc
 	local ($doc, $url) = (@_);

 	&showHeader("Document Information");
  	$url && showIt('Url', $url, "<a href=\"$url\">$url</a>");
  	showIt('Length', length($doc));

  	if ($title = &getTitle($doc)) {
  		showIt('Title', $title);
  	} else {
 		&showErr("NO TITLE") unless $title;
  	}
  	if ($desc = &getMeta($doc, 'description')) {
  		showIt('Description', $desc);
  	} else {
 		&showErr("NO DESCRIPTION") unless $desc;
 	}
  	if ($keywords = &getMeta($doc, 'keywords')) {
  		showIt('Keywords', $keywords);
  	} else {
 		&showErr("NO KEYWORDS") unless $keywords;
 	}
}

sub checkImages { # doc
 	local ($doc) = (@_);
 	# <IMG
 	&showHeader("Image Alt Text");
 	while ($doc =~ /(<img [^>]+>)/mi) {
 		$doc =~ s///;
 		$img = $1;
 		local ($extra) = $F{'images'} ? $img : "";
 		showIt('Image', $img, $extra);
 		&checkAlt($img);
 	}
}

sub checkForm { # doc
 	local ($doc) = (@_);
 	# INPUT / TEXTAREA / PASSWORD
 	%label = &initLabel($doc);
 	&showHeader("Form Label Tags");
 	while ($doc =~ /(<(input|textarea|select)\b[^>]*>)/mi) {
 		$doc =~ s///;
 		$element = $1;
 		showIt('Input', $1);
 		# showIt('type', $type);
 		$type = &getAttr($element, 'type');
 		$type = 'text' unless $type;
 		if ($type =~ /(image)/mi) {
 			&checkAlt($element);
 		} elsif ($type =~ /(text|radio|checkbox|password)/) {
 			$id = &getAttr($element, 'id');
 			if ($id) { # look for label tag for $id
 				if ($doc =~ m@<label\b[^>]*for=("$id"|'$id'|$id\b)[^>]*>(.+)</label>@mi) {
 					# label tag is present for $id
 					$match = $1;
 					$label = $2;
 					$label =~ s|</label>.*||mi; # make it shortest matching string
 					$F{'labels'} && &showIt('Label', $label, 'info');
 				} elsif ($label{$id} ne "") {
 					$F{'labels'} && &showIt("$id label", $label{$id}, 'info');
 				} else {
 					&showErr("NO LABEL TAG FOR '$id'");
 					# &dump($doc);
 				}
 			} else {
 				&showErr("NO ID ATTRIBUTE, SO NO LABEL TAG POSSIBLE");
 			}
 		}
 	}
}

sub dump {
 	if ($cgi) {
 		local ($text) = (@_);
 		print "<tr>\n";
 		print "<th align=left><label for=html>HTML:</label></th>\n";
 		print "<td><textarea id=html cols=80 rows=10>";
 		print &escape($text);
 		print "</textarea></td></tr>\n";
 	}
}

sub initLabel {
 	local ($doc) = (@_);
 	local ($attrs, $label, $for);
 	$doc =~ s/<label\b/\001/gi;
 	$doc =~ s/<\/label>/\002/gi;
 	local (%label);
 	while ($doc =~ /\001([^\002]*)\002/) {
 		$label = $1;
 		$doc =~ s///;
 		$attrs = $label;
 		$attrs =~ s/>.*$//; # keep the attributes
 		if ($attrs =~ /for="([^"]*)"/) {
 			$for = $1;
 			# &showErr("for-quote=$for");
 		} elsif ($attrs =~ /for=([\S]*)/) {
 			$for = $1;
 			# &showErr("for=$for");
 		}
 		if ($for) {
 			$label =~ s/[^>]*>//; # remove the attributes before first >
 			$label{$for} = $label;
 			# &showErr("[$for]=$label");
 		}
 	}
 	return %label;
}

sub checkLinks { # doc
 	local ($doc) = (@_);
 	&showHeader("Links");
 	# TODO only works for double quoted href attributes
 	while ($doc =~ m|<a\b[^>]*href="([^"]+)">([^<]+)</a>|mi) {
 		$doc =~ s///;
 		$href = $1;
 		$label = $2;
 		$qhref = $href;
 		$qhref = "$url/$qhref" unless ($qhref =~ m|^https?://|i);
 		$qhref = encode($qhref);
 		showIt("link", "$href - $label", "<a href=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}?url=$qhref\">$label</a>");
 	}
}

sub checkAlt { # tag
 	local ($tag) = (@_);
 	local ($alt) = &getAttr($tag, 'alt');
 	$F{'labels'} && &showIt('Alt-text', "$alt", 'info');
 	if (!$alt) {
 		&showErr("NO ALT TEXT");
 	}
}

sub getAttr { # tag name
 	local ($tag, $name) = (@_);
 	# double quoted attr
 	if ($tag =~ m/\b$name="([^"]*)"/mi) {
 		if ($1 eq "") {
 			return '""';
 		} else {
 			return $1;
 		}
 	}
 	# single quoted attr
 	if ($tag =~ m/\b$name='([^']*)'/mi) {
 		if ($1 eq "") {
 			return "''";
 		} else {
 			return $1;
 		}
 	}
 	# unquoted attr
 	if ($tag =~ m/\b$name=([^ >]+)[ >]/mi) {
 		return $1;
 	}
 	return "";
}

sub remComments {
 	local ($doc) = (@_);
 	$doc =~ s/-->/\002/g; # map closer to single char for minimal match
 	while ($doc =~ /<!--/) {
 		$doc =~ s/(<!--[^\002]*\002)//;
 		break unless $1; # make sure we made a change
 	}
 	$doc =~ s/\002/-->/g;
 	return $doc;
}

sub showMsg {
 	local ($msg, $label, $color) = (@_);
 	if ($cgi) {
 		$count{$label}++;
 		print "<tr valign=top>\n";
 		print "<th align=left nowrap>";
 		print $label;
 		print "</th>\n";
 		print "<td bgcolor=\"$color\">";
 		print "<h2 style=\"margin: 0\">" if $label eq $section;
 		print $msg;
 		print "</h2>" if $label eq $section;
 		print "</td></tr>\n";
 	} else {
 		print "\t******* $msg ******\n";
 	}
}

sub showErr {
 	local ($msg) = (@_);
 	&showMsg($msg, 'Error', $errorbg);
}

sub showHeader {
 	local ($msg) = (@_);
 	&showMsg($msg, $section, $titlebg);
}

sub showIt {
 	local ($name, $value, $extra) = (@_);
 	$value = &escape($value) if $cgi;
 	if ($extra eq 'info') {
 		$extra = '';
 		$bgcolor = "bgcolor='$infobg'";
 	} else {
 		$bgcolor = "bgcolor='$normalbg'";
 	}
 	$count{$name}++;
 	if ($cgi) {
 		print "<tr valign=top>\n";
 		print "<th align=left nowrap>$name:</th>\n";
 		print "<td $bgcolor ><tt>$value</tt>\n\t<div>$extra</div></td>\n";
 		print "</tr>\n";
 	} else {
 		print "$name\n\t$value\n";
 	}
}

sub clean {
  	local ($s) = (@_);
  	$s =~ s/^\s*//;
  	$s =~ s/\s*$//;
  	return $s;
}

sub getMeta { # find meta tag with supplied attribute name
  	local ($doc, $name) = (@_);
 	# find a meta tag with the desired name
  	if ($doc =~ m@(<meta\b[^>]*\bname=("$name"|'$name'|$name\b).*)@si) {
 		local ($tag) = $1;          # meta tag not terminated
 		$tag =~ s/>.*/>/;           # get rid of stuff after this meta tag
 		# &showIt('meta', $1);
 		return &clean( &getAttr($tag, 'content') );  # get the content
  	}
  	return "";
}

sub getTitle {
  	local ($doc) = (@_);
  	if ($doc =~ m|<title>([^<]*)</title>|i) {
  		return &clean($1);
  	}
  	return "";
}

sub encode { # string
     local ($str) = (@_);
 	$str =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
 	$str =~ s/%20/+/g;
 	return $str;
}

sub escape { # string
 	local ($s) = (@_);
 	# $s =~ s/&/&amp;/g; # don't do this or sgml entities are broken
 	$s =~ s/"/&quot;/g;
 	$s =~ s/</&lt;/g;
 	$s =~ s/</&gt;/g;
 	return $s;
}

sub getCookie { # name
 	local ($ident, $format) = (@_);
 	local ($cookie);
 	for (split (/[;] */, $ENV{'HTTP_COOKIE'})) {
 		if (/^$ident=(.*)/) {
 			$cookie = $1;
 			$cookie =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg; # decode
 			# now $cookie looks like a=xxx&b=yyy&c=zzz
 			break;
 		}
 	}
 	if ($format) {
 		$cookie =~ s/&/<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/g;
 	}
 	return $cookie;
}

sub getNameValue { # name=value
 	local ($pair) = (@_);
 	local ($name, $value) = split (/=/, $pair, 2);
     $value =~ s/\+/ /g;
     $value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
 	return ($name, $value);
}

sub formdata { # [ident] -> %F
 	local ($ident) = (@_);
     local (*formdata);
 	# first process options stored in cookie
 	local ($cookie) = getCookie($ident); # looks like a=xxx&b=yyy&c=zzz
 	local (@cookie) = split(/&/, $cookie);
 	local ($name, $value, $pair);
 	for $pair (@cookie) {
         ($name, $value) = &getNameValue($pair);
 		# print "<pre>$name = $value</pre>\n";
 		$F{$name} = $value;
 	}
 	# second get the options from the POST or GET
     if ($ENV{'REQUEST_METHOD'} eq 'POST') {
         read (STDIN, $F, $ENV{'CONTENT_LENGTH'});
     } else {
         $F = $ENV{'QUERY_STRING'};
     }
     @formdata = split (/&/, $F);
     for $pair (@formdata) {
         ($name, $value) = &getNameValue($pair);
         # $F{$name} .= "\n" if $F{$name}; # additional values append to previous
         # $F{$name} .= $value;
         $F{$name} = $value; # additional values override previous
     }
 	# finally, handle clear TODO - why not just set %F = ()?
 	$F{'clear'} && clearForm();
     return (%F);
}

From perlman@turing.acm.org Fri Aug 31 07:51:46 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Fri, 31 Aug 2007 07:51:45 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: access@hcibib.org
Subject: test to access
Message-ID: <Pine.LNX.4.64.0708310751380.3820@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed


From perlman@turing.acm.org Fri Aug 31 12:17:53 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Fri, 31 Aug 2007 12:17:53 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: HCI Webliography <apache@turing.acm.org>
cc: director@hcibib.org, technicalschoolsguide@gmail.com
Subject: Re: SUGGEST_a_LINK!: Technical Schools Guide was started by a college
 graduate for others to find information on technical schools, trade schools,
 and community colleges out of high school.
In-Reply-To: <200708311435.l7VEZ4BO022168@turing.acm.org>
Message-ID: <Pine.LNX.4.64.0708311217440.2648@turing.acm.org>
References: <200708311435.l7VEZ4BO022168@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

I am sorry, but the site below does not have specific HCI
content and will not be included in the HCI Bibliography.
 	http://hcibib.org/faq.html#Data-5

Gary Perlman, Director, HCI Bibliography Project
mailto:director@hcibib.org  http://hcibib.org/

On Fri, 31 Aug 2007, HCI Webliography wrote:

> Reply-To: director@hcibib.org
>
> This data is being sent to director@hcibib.org
> to be considered for inclusion in the HCI Bibliography
>
> %M U.technical-schools-guide.com   63.77.172.130
> %0 INTERNET
> %D 2007-08-31
> %K education:acm_sigchi education:programs
> %A Jason
> %I Technical-Schools-Guide.com
> %K Technical Schools, Vocational Schools, Community Colleges, Technical Programs
> %T Technical Schools Guide was started by a college graduate for others to find information on technical schools, trade schools, and community colleges out of high school.
> %U technicalschoolsguide@gmail.com
> %W http://www.technical-schools-guide.com
> %X Technical Schools Guide was started by a college graduate for others to find information on technical schools, trade schools, and community colleges out of high school.
>
>
>
>
> technical schools, degree program, school resources, vocational cources, career colleges
>

From perlman@turing.acm.org Fri Aug 31 20:08:56 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Fri, 31 Aug 2007 20:08:54 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Gary Perlman at OCLC <perlman@oclc.org>
Subject: chaccess with link analysis
Message-ID: <Pine.LNX.4.64.0708312008410.27676@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

#! /usr/local/bin/perl

# chaccess.cgi - Simple Accessibility Checker
# Copyright 2007 Gary Perlman (director@hcibib.org)
# Covered by the GNU General Public License: http://www.gnu.org/copyleft/gpl.html
# $Revision: 1.11 $ $Date: 2007/09/01 00:08:20 $

use LWP::Simple; # http://search.cpan.org/dist/libwww-perl/lib/LWP/Simple.pm

&init();

if ($ARGV[0]) {
 	$url = $ARGV[0];
 	$url = "http://" . $url unless ($url =~ m|^https?://|i);
 	&process($url);
} else {
 	$cgi = 1;
 	%F = &formdata($cookiename);
 	if ($F{'input'}) {
 		$url = "";
 	} else {
 		$url = $F{'url'};
 		if (! $url) { # maybe provide default analysis?
 			# $url = "$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}?url=hcibib.org";
 			# $F{'url'} = $url; # copy url into form?
 		} else {
 			$url = "http://" . $url unless ($url =~ m|^https?://|i);
 		}
 	}
 	print "Content-type: text/html; charset=UTF-8\r\n\r\n";
 	print "<html lang=\"en\"><head>\n";
 	# charset set above
 	# print "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n";
 	print "<meta name=\"description\" content=\"$description\" />\n";
 	print "<meta name=\"keywords\" content=\"$keywords\" />\n";
 	print "<title>$title</title>\n";
 	local ($base) = $url;
 	if ($base =~ m|[.].*/|) {   # there's a slash in the URL after the domain
 		$base =~ s|/[^/]*$||;  # remove averything after the last slash
 	}
 	print "<base href=\"$base\" />\n" if $base;
 	print "</head>\n<body bgcolor=\"$normalbg\">\n";
 	print "<table border=1 bgcolor='$titlebg' cellpadding=2 width='100%' cellspacing=0><tr><td>\n";
 	print "<h1 style=\"background: $titlebg; margin: 0\">$title</h1>\n";
 	print "</td><td width='20%' nowrap>$infolinks</td></tr></table\n";
 	print "<p>\n";
 	print "<form action=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}\" method=POST>\n";
 	print "<DIV style=\"border: 1px solid #666666; padding: 5px; background: $formbg;\">\n";
 	&inputField('url', 'URL', "URL of web page to check", 0, 60);
 	print "<input type=submit accesskey=c value=\"$title\" />\n";
 	print "<div style=\"margin-left: 6em\">\n";
 	&checkBox('source', 'Show HTML', "Show the HTML that was processed in a box");
 	&checkBox('images', 'Show Images', "Show the images in the output");
 	&checkBox('labels', 'Show Labels', "Show the labels for form elements and image alt text in the output");
 	&checkBox('links', 'Show Links', "Show links in the document for followup checking");
 	print "</div>\n";
 	&inputField('input', 'HTML Input', "Provide HTML input here instead of a URL", 2, 60, 1);
 	print "</DIV>\n";
 	print "</form>\n";
 	&process($url, $F{'input'});
 	# print "<script type=\"text/javascript\" language=\"JavaScript\">\n\t<!--\n";
 	# print "\t\tsetCookie(\"$cookiename\", \"$parms\");\n";
 	# print "\t// -->\n</script>\n";
 	print "</body>\n</html>\n";
}

sub init {
 	$title    = "Check Accessibility";
 	$section  = 'Analysis';
 	$titlebg  = "#EEEEEE";
 	$infobg   = "#CCFFCC";
 	$errorbg  = "#FFCCCC";
 	$normalbg = "#FFFFFF";
 	$formbg   = "#FFFFEE";
 	$cookiename = "$ENV{'SCRIPT_NAME'}";
 	$cookiename =~ s|.*/||;      # remove directory
 	$cookiename =~ s|[.].*$||;   # remove suffix
 	$description = "Simple Accessibility Checker: reports missing alt text from images
 		and missing label tags from form elements.";
 	$keywords = "accessibility;check;validation;form;input;label;image;img;alt-text";
 	$email = 'director@hcibib.org';
 	$infolinks = "Comments: <a href='mailto:$email?subject=$cookiename:%20Comments'>$email</a>\n";
 	$infolinks .= "<br>Source code: <a href='$cookiename.txt'>$cookiename.txt</a>\n";
}

sub checkBox {
  	local ($name, $label, $help) = (@_);
 	print "<input type=checkbox value=checked name=$name id=$name $F{$name}/>\n";
 	print "\t<label title=\"$help\" for=$name>$label</label>&nbsp;\n";
}

sub inputField {
  	local ($name, $label, $help, $rows, $cols, $nodata) = (@_);
 	local ($value) = escape($F{$name}) unless $nodata;
 	local ($style) = 'style="width: 6em"';
 	if ($rows == 0) {
 		print "<label title=\"$help\" for=$name $style>$label:</label>\n";
 		print "<input name=$name id=$name title=\"$help\" size=$cols value=\"$value\" />\n";
 	} else {
 		print "<div>\n";
 		print "<label title=\"$help\" for=$name $style>$label:</label>\n";
 		print "<textarea rows=$rows cols=$cols value=checked name=$name id=$name title=\"$help\"/>";
 		print $value;
 		print "</textarea>\n";
 		print "</div>\n";
 	}
}

sub process { # url
  	local ($url, $input) = (@_);
  	local ($doc);
 	$cgi && print "<table border=0 cellpadding=5 cellspacing=0>\n";
 	if ($input) {
 		$doc = $input;
 	} elsif ($url) {
 		($content_type, $document_length, $modified_time, $expires, $server) = head($url);
 		if ($content_type && ($content_type =~ m|^text/html|i)) {
 			$doc = get($url); # FETCH THE URL
 		} else {
 			&showErr("DOCUMENT IS NOT HTML ($content_type)");
 		}
 	}
  	# print $doc;
 	$doc = &remComments($doc);

 	if ($doc) {
 		&checkHeader($doc, $url);

 		$F{'source'} && &dump($doc);
 		# $doc =~ s/\012/ /g; # map newlines to spaces
 		&checkImages($doc);
 		&checkForm($doc);
 		$F{'links'} && &checkLinks($doc);
 	} elsif ($url) {
 		&showErr("NO DOCUMENT");
 	}
 	$cgi && print "</table>\n";
}

sub checkHeader { # doc
 	local ($doc, $url) = (@_);

 	&showHeader("Document Information");
  	$url && showIt('Url', $url, "<a href=\"$url\">$url</a>");
  	showIt('Length', length($doc));

  	if ($title = &getTitle($doc)) {
  		showIt('Title', $title);
  	} else {
 		&showErr("NO TITLE") unless $title;
  	}
  	if ($desc = &getMeta($doc, 'description')) {
  		showIt('Description', $desc);
  	} else {
 		&showErr("NO DESCRIPTION") unless $desc;
 	}
  	if ($keywords = &getMeta($doc, 'keywords')) {
  		showIt('Keywords', $keywords);
  	} else {
 		&showMsg("NO KEYWORDS", 'Note') unless $keywords;
 	}
}

sub checkImages { # doc
 	local ($doc) = (@_);
 	# <IMG
 	local ($numtags) = 0;
 	&showHeader("Image Alt Text");
 	while ($doc =~ /(<img [^>]+>)/si) {
 		$doc =~ s///;
 		$img = $1;
 		local ($extra) = $F{'images'} ? $img : "";
 		showIt('Image', $img, $extra);
 		&checkAlt($img);
 		$numtags++;
 	}
 	&showMsg("NO IMAGE TAGS FOUND", 'Note') unless $numtags;
}

sub checkForm { # doc
 	local ($doc) = (@_);
 	# INPUT / TEXTAREA / PASSWORD
 	%label = &initLabel($doc);
 	&showHeader("Form Label Tags");
 	local ($numtags) = 0;
 	while ($doc =~ /(<(input|textarea|select)\b[^>]*>)/si) {
 		$doc =~ s///;
 		$element = $1;
 		showIt('Input', $1);
 		# showIt('type', $type);
 		$type = &getAttr($element, 'type');
 		$type = 'text' unless $type;
 		$numtags++;
 		if ($type =~ /(image)/si) {
 			&checkAlt($element);
 		} elsif ($type =~ /(text|radio|checkbox|password)/) {
 			$id = &getAttr($element, 'id');
 			if ($id) { # look for label tag for $id
 				if ($doc =~ m@<label\b[^>]*for=("$id"|'$id'|$id\b)[^>]*>(.+)</label>@si) {
 					# label tag is present for $id
 					$match = $1;
 					$label = $2;
 					$label =~ s|</label>.*||si; # make it shortest matching string
 					$F{'labels'} && &showIt('Label', $label, 'info');
 				} elsif ($label{$id} ne "") {
 					$F{'labels'} && &showIt("$id label", $label{$id}, 'info');
 				} else {
 					&showErr("NO LABEL TAG FOR '$id'");
 					# &dump($doc);
 				}
 			} else {
 				&showErr("NO ID ATTRIBUTE, SO NO LABEL TAG POSSIBLE");
 			}
 		}
 	}
 	&showMsg("NO FORM ELEMENTS FOUND", 'Note') unless $numtags;
}

sub dump {
 	if ($cgi) {
 		local ($text) = (@_);
 		print "<tr>\n";
 		print "<th align=left><label for=html>HTML:</label></th>\n";
 		print "<td><textarea id=html cols=80 rows=10>";
 		print &escape($text);
 		print "</textarea></td></tr>\n";
 	}
}

sub initLabel {
 	local ($doc) = (@_);
 	local ($attrs, $label, $for);
 	$doc =~ s/<label\b/\001/gi;
 	$doc =~ s/<\/label>/\002/gi;
 	local (%label);
 	while ($doc =~ /\001([^\002]*)\002/) {
 		$label = $1;
 		$doc =~ s///;
 		$attrs = $label;
 		$attrs =~ s/>.*$//; # keep the attributes
 		if ($attrs =~ /for="([^"]*)"/) {
 			$for = $1;
 			# &showErr("for-quote=$for");
 		} elsif ($attrs =~ /for=([\S]*)/) {
 			$for = $1;
 			# &showErr("for=$for");
 		}
 		if ($for) {
 			$label =~ s/[^>]*>//; # remove the attributes before first >
 			$label{$for} = $label;
 			# &showErr("[$for]=$label");
 		}
 	}
 	return %label;
}

sub checkLinks { # doc
 	local ($doc) = (@_);
 	$doc =~ s|</a>|\002|gi; # simplify the matching
 	local ($numtags) = 0;
 	&showHeader("Links");
 	# TODO only works for double quoted href attributes
 	local ($script) = "http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}";
 	local ($options) = "&source=$F{'source'}&images=$F{'images'}&labels=$F{'labels'}&links=$F{'links'}";
 	while ($doc =~ m|<a\b[^>]*href="([^"]+)"[^>]*>([^\002]+)\002|si) {
 		$doc =~ s///;
 		$href = $1;
 		$label = $2;
 		# fix up the href depending on whether it's absolute or relative
 		$qhref = &makeHref($base, $href);
 		$qhref = encode($qhref);
 		local ($title) = $label;
 		$title =~ s/<[^>]*>//g; # remove any html from label
 		$title =~ s/^\s+//;     # remove leading space
 		$title = "this link" unless $title; # provide backup to image labels
 		$title = "title='Run $cookiename on &quot;$title&quot;'";
 		showIt('Link', "HREF=[$href] LABEL=[$label]", "<a href=\"$script?url=$qhref$options\" $title>$label</a>");
 		$numtags++;
 	}
 	&showMsg("NO LINKS FOUND", 'Note') unless $numtags;
}

sub makeHref { # base-url this-href
 	local ($base, $href) = (@_);
 	if ($href =~ m|^https?://|i) {
 		return $href;
 	} elsif ($href =~ m|^/|) {      # href starts with /, so prepend server
 		$base =~ s|^(https?://)||i; # get rid of protocol to safely remove /*
 		local ($prefix) = $1;       # save protocol to restore later
 		$prefix = "http://" unless $prefix; # make sure there is something restored
 		$base =~ s|/.*$||;          # convert base to domain server url
 		$base = "$prefix$base";
 		# &showErr("base=$base");
 		return "$base$href";
 	} else {
 		while ($href =~ m|^../|) {
 			$href =~ s|^...||;    # chop off the ..
 			$base =~ s|/[^/]*$||; # chdir to .. in $base
 		}
 		return "$base/$href"; # use $base with relative URL
 	}
}

sub checkAlt { # tag
 	local ($tag) = (@_);
 	local ($alt) = &getAttr($tag, 'alt');
 	$F{'labels'} && &showIt('Alt-text', "$alt", 'info');
 	if (!$alt) {
 		&showErr("NO ALT TEXT");
 	}
}

sub getAttr { # tag name
 	local ($tag, $name) = (@_);
 	# &showErr("tag = " . escape($tag));;
 	# double quoted attr
 	if ($tag =~ m/\b$name="([^"]*)"/si) {
 		# &showErr("double quote found for $name");
 		if ($1 eq "") {
 			return '""';
 		} else {
 			return $1;
 		}
 	}
 	# single quoted attr
 	if ($tag =~ m/\b$name='([^']*)'/si) {
 		# &showErr("single quote found for $name");
 		if ($1 eq "") {
 			return "''";
 		} else {
 			return $1;
 		}
 	}
 	# unquoted attr
 	if ($tag =~ m/\b$name=([^ >]+)[ >]/si) {
 		# &showErr("no quote found for $name");
 		return $1;
 	}
 	return "";
}

sub remComments {
 	local ($doc) = (@_);
 	$doc =~ s/-->/\002/g; # map closer to single char for minimal match
 	while ($doc =~ /<!--/) {
 		$doc =~ s/(<!--[^\002]*\002)//;
 		break unless $1; # make sure we made a change
 	}
 	$doc =~ s/\002/-->/g;
 	return $doc;
}

sub showMsg {
 	local ($msg, $label, $color) = (@_);
 	$color = $normalbg unless $color;
 	if ($cgi) {
 		$count{$label}++;
 		print "<tr valign=top>\n";
 		print "<th align=left nowrap>";
 		print $label;
 		print "</th>\n";
 		print "<td bgcolor=\"$color\">";
 		print "<h2 style=\"margin: 0\">" if $label eq $section;
 		print $msg;
 		print "</h2>" if $label eq $section;
 		print "</td></tr>\n";
 	} else {
 		print "\t******* $msg ******\n";
 	}
}

sub showErr {
 	local ($msg) = (@_);
 	&showMsg($msg, 'Error', $errorbg);
}

sub showHeader {
 	local ($msg) = (@_);
 	&showMsg($msg, $section, $titlebg);
}

sub showIt {
 	local ($name, $value, $extra) = (@_);
 	$value = &escape($value) if $cgi;
 	if ($extra eq 'info') {
 		$extra = '';
 		$bgcolor = "bgcolor='$infobg'";
 	} else {
 		$bgcolor = "bgcolor='$normalbg'";
 	}
 	$count{$name}++;
 	if ($cgi) {
 		print "<tr valign=top>\n";
 		print "<th align=left nowrap>$name:</th>\n";
 		print "<td $bgcolor ><tt>$value</tt>\n\t<div>$extra</div></td>\n";
 		print "</tr>\n";
 	} else {
 		print "$name\n\t$value\n";
 	}
}

sub clean {
  	local ($s) = (@_);
  	$s =~ s/^\s*//;
  	$s =~ s/\s*$//;
  	return $s;
}

sub getMeta { # find meta tag with supplied attribute name
  	local ($doc, $name) = (@_);
 	# find a meta tag with the desired name
  	if ($doc =~ m@(<meta\b[^>]*\bname=("$name"|'$name'|$name\b).*)@si) {
 		local ($tag) = $1;          # meta tag not terminated
 		$tag =~ s/>.*$/>/s;         # get rid of stuff after this meta tag
 		# &showIt('meta', $1);
 		return &clean( &getAttr($tag, 'content') );  # get the content
  	}
  	return "";
}

sub getTitle {
  	local ($doc) = (@_);
  	if ($doc =~ m|<title>([^<]*)</title>|i) {
  		return &clean($1);
  	}
  	return "";
}

sub encode { # string
     local ($str) = (@_);
 	$str =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
 	$str =~ s/%20/+/g;
 	return $str;
}

sub escape { # string
 	local ($s) = (@_);
 	# $s =~ s/&/&amp;/g; # don't do this or sgml entities are broken
 	$s =~ s/"/&quot;/g;
 	$s =~ s/</&lt;/g;
 	$s =~ s/</&gt;/g;
 	return $s;
}

sub getCookie { # name
 	local ($ident, $format) = (@_);
 	local ($cookie);
 	for (split (/[;] */, $ENV{'HTTP_COOKIE'})) {
 		if (/^$ident=(.*)/) {
 			$cookie = $1;
 			$cookie =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg; # decode
 			# now $cookie looks like a=xxx&b=yyy&c=zzz
 			break;
 		}
 	}
 	if ($format) {
 		$cookie =~ s/&/<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/g;
 	}
 	return $cookie;
}

sub getNameValue { # name=value
 	local ($pair) = (@_);
 	local ($name, $value) = split (/=/, $pair, 2);
     $value =~ s/\+/ /g;
     $value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
 	return ($name, $value);
}

sub formdata { # [ident] -> %F
 	local ($ident) = (@_);
     local (*formdata);
 	# first process options stored in cookie
 	local ($cookie) = getCookie($ident); # looks like a=xxx&b=yyy&c=zzz
 	local (@cookie) = split(/&/, $cookie);
 	local ($name, $value, $pair);
 	for $pair (@cookie) {
         ($name, $value) = &getNameValue($pair);
 		# print "<pre>$name = $value</pre>\n";
 		$F{$name} = $value;
 	}
 	# second get the options from the POST or GET
     if ($ENV{'REQUEST_METHOD'} eq 'POST') {
         read (STDIN, $F, $ENV{'CONTENT_LENGTH'});
     } else {
         $F = $ENV{'QUERY_STRING'};
     }
     @formdata = split (/&/, $F);
     for $pair (@formdata) {
         ($name, $value) = &getNameValue($pair);
         # $F{$name} .= "\n" if $F{$name}; # additional values append to previous
         # $F{$name} .= $value;
         $F{$name} = $value; # additional values override previous
     }
 	# finally, handle clear TODO - why not just set %F = ()?
 	$F{'clear'} && clearForm();
     return (%F);
}

From perlman@turing.acm.org Fri Aug 31 20:12:11 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Fri, 31 Aug 2007 20:12:08 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Aaron Genest <amg918@mail.usask.ca>
Subject: Re: |STAT request
In-Reply-To: <8A2AA338-29EB-4B91-BA5E-07D8451F00BF@mail.usask.ca>
Message-ID: <Pine.LNX.4.64.0708312012020.28161@turing.acm.org>
References: <8A2AA338-29EB-4B91-BA5E-07D8451F00BF@mail.usask.ca>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

Thank you for your interest in |STAT data manipulation and analysis software.

UNIX |STAT for is now (only) available via Web browsers at a secret location.
 	http://www.hcibib.org/stat/xyzzy/

To obtain UNIX |STAT files, please follow the instructions at:
 	http://www.acm.org/perlman/stat/#access
There are installation notes (e.g., for Mac OS X and Linux) at:
 	http://www.acm.org/perlman/stat/installation.txt

DOS |STAT executables and documentation are available as a WinZip file:
 	http://www.acm.org/perlman/stat/DOS-STAT.ZIP

HTML documentation is available from the |STAT home page:
 	http://www.acm.org/perlman/stat/

On Fri, 31 Aug 2007, Aaron Genest wrote:

> I AGREE TO ADHERE TO THE CONDITIONS OF USING |STAT.
> I AGREE NOT TO SHARE THE |STAT LOCATION WITH OTHERS.
>

From perlman@turing.acm.org Sat Sep  1 09:52:29 2007 -0400
Status: 
X-Status: 
X-Keywords:
Date: Sat, 1 Sep 2007 09:52:29 -0400 (EDT)
From: Gary PERLMAN <perlman@turing.acm.org>
To: Gary Perlman at OCLC <perlman@oclc.org>
Subject: chaccess - now using stylesheet
Message-ID: <Pine.LNX.4.64.0709010952110.13550@turing.acm.org>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed

#! /usr/local/bin/perl

# chaccess.cgi - Simple Accessibility Checker
# Copyright 2007 Gary Perlman (director@hcibib.org)
# Covered by the GNU General Public License: http://www.gnu.org/copyleft/gpl.html
# $Revision: 1.14 $ $Date: 2007/09/01 13:51:58 $

use LWP::Simple; # http://search.cpan.org/dist/libwww-perl/lib/LWP/Simple.pm

&init();

if ($ARGV[0]) {
 	$url = $ARGV[0];
 	$url = "http://" . $url unless ($url =~ m|^https?://|i);
 	&process($url);
} else {
 	$cgi = 1;
 	%F = &formdata($cookiename);
 	if ($F{'input'}) {
 		$url = "";
 	} else {
 		$url = $F{'url'};
 		if (! $url) { # maybe provide default analysis?
 			# $url = "$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}?url=hcibib.org";
 			# $F{'url'} = $url; # copy url into form?
 		} else {
 			$url = "http://" . $url unless ($url =~ m|^https?://|i);
 		}
 	}
 	print "Content-type: text/html; charset=UTF-8\r\n\r\n";
 	print "<html lang=\"en\"><head>\n";
 	# charset set above
 	# print "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n";
 	print "<meta name=\"description\" content=\"$description\" />\n";
 	print "<meta name=\"keywords\" content=\"$keywords\" />\n";
 	print "<title>$title</title>\n";
 	local ($base) = $url;
 	if ($base =~ m|[.].*/|) {   # there's a slash in the URL after the domain
 		$base =~ s|/[^/]*$||;  # remove averything after the last slash
 	}
 	print "<base href=\"$base\" />\n" if $base;
 	print "<style type='text/css'>\n";
 	print "<!--\n";
 	print "h1 {background: $titlebg; margin: 0; font-family: sans-serif}\n";
 	print "h2 {background: $titlebg; margin: 0; font-family: sans-serif}\n";
 	print "form {border: 1px solid #666666; padding: 5px; background: $formbg;}\n";
 	print "th {background: $titlebg; margin: 0; font-family: sans-serif}\n";
 	print ".label {font-size: 90%; font-family: sans-serif; }\n";
 	print ".infolinks {font-size: 80%; font-family: sans-serif; }\n";
 	print "-->\n";
 	print "</style>\n";
 	print "</head>\n<body bgcolor=\"$normalbg\">\n";
 	print "<table border=0 bgcolor='$titlebg' cellpadding=2 width='100%' cellspacing=0><tr><td>\n";
 	print "<h1>$title</h1>\n";
 	print "</td><td width='20%' nowrap class=infolinks>$infolinks</td></tr></table\n";
 	print "<p>\n";
 	print "<form action=\"http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}\" method=POST>\n";
 	&inputField('url', 'URL', "URL of web page to check", 0, 60);
 	print "<input type=submit accesskey=c value=\"$title\" />\n";
 	print "<div class=label style=\"margin-left: 6em\">\n";
 	&checkBox('links', 'Show Links', "Show links in the document for followup checking");
 	&checkBox('images', 'Show Images', "Show the images in the output");
 	&checkBox('labels', 'Show Labels', "Show the labels for form elements and image alt text in the output");
 	&checkBox('source', 'Show HTML', "Show the HTML that was processed in a box");
 	print "</div>\n";
 	&inputField('input', 'HTML Input', "Provide HTML input here instead of a URL", 2, 60, 1);
 	print "</form>\n";
 	&process($url, $F{'input'});
 	# print "<script type=\"text/javascript\" language=\"JavaScript\">\n\t<!--\n";
 	# print "\t\tsetCookie(\"$cookiename\", \"$parms\");\n";
 	# print "\t// -->\n</script>\n";
 	print "</body>\n</html>\n";
}

sub init {
 	$title    = "Check Accessibility";
 	$section  = 'Analysis';
 	$titlebg  = "#EEEEEE";
 	$infobg   = "#CCFFCC";
 	$errorbg  = "#FFCCCC";
 	$normalbg = "#FFFFFF";
 	$formbg   = "#FFFFEE";
 	$cookiename = "$ENV{'SCRIPT_NAME'}";
 	$cookiename =~ s|.*/||;      # remove directory
 	$cookiename =~ s|[.].*$||;   # remove suffix
 	$description = "Simple Accessibility Checker: reports missing alt text from images
 		and missing label tags from form elements.";
 	$keywords = "accessibility;check;validation;form;input;label;image;img;alt-text";
 	$email = 'director@hcibib.org';
 	$infolinks = "Comments: <a href='mailto:$email?subject=$cookiename:%20Comments'>$email</a>\n";
 	$infolinks .= "<br>Source code: <a href='$cookiename.txt'>$cookiename.txt</a>\n";
 	$infolinks .= "<br>Resources: <a href='http://hcibib.org/accessibility'>hcibib.org/accessibility</a>\n";
}

sub checkBox {
  	local ($name, $label, $help) = (@_);
 	print "<input type=checkbox value=checked name=$name id=$name $F{$name}/>\n";
 	print "\t<label title=\"$help\" for=$name>$label</label>&nbsp;\n";
}

sub inputField {
  	local ($name, $label, $help, $rows, $cols, $nodata) = (@_);
 	local ($value) = escape($F{$name}) unless $nodata;
 	local ($style) = 'style="width: 6em"';
 	if ($rows == 0) {
 		print "<label title=\"$help\" for=$name $style class=label>$label:</label>\n";
 		print "<input name=$name id=$name title=\"$help\" size=$cols value=\"$value\" />\n";
 	} else {
 		print "<div>\n";
 		print "<label title=\"$help\" for=$name $style class=label>$label:</label>\n";
 		print "<textarea rows=$rows cols=$cols value=checked name=$name id=$name title=\"$help\"/>";
 		print $value;
 		print "</textarea>\n";
 		print "</div>\n";
 	}
}

sub process { # url
  	local ($url, $input) = (@_);
  	local ($doc);
 	$cgi && print "<table border=0 cellpadding=5 cellspacing=0>\n";
 	if ($input) {
 		$doc = $input;
 	} elsif ($url) {
 		($content_type, $document_length, $modified_time, $expires, $server) = head($url);
 		if ($content_type && ($content_type =~ m|^text/html|i)) {
 			$doc = get($url); # FETCH THE URL
 		} else {
 			&showErr("DOCUMENT IS NOT HTML ($content_type)");
 		}
 	}
  	# print $doc;
 	$doc = &remComments($doc);

 	if ($doc) {
 		&checkHeader($doc, $url);

 		$F{'source'} && &dump($doc);
 		# $doc =~ s/\012/ /g; # map newlines to spaces
 		&checkImages($doc);
 		&checkForm($doc);
 		$F{'links'} && &checkLinks($doc);
 	} elsif ($url) {
 		&showErr("NO DOCUMENT");
 	}
 	$cgi && print "</table>\n";
}

sub checkHeader { # doc
 	local ($doc, $url) = (@_);

 	&showHeader("Document Information");
  	$url && showIt('Url', $url, "<a href=\"$url\">$url</a>");
  	showIt('Length', length($doc));

  	if ($title = &getTitle($doc)) {
  		showIt('Title', $title);
  	} else {
 		&showErr("NO TITLE") unless $title;
  	}
  	if ($desc = &getMeta($doc, 'description')) {
  		showIt('Description', $desc);
  	} else {
 		&showErr("NO DESCRIPTION") unless $desc;
 	}
  	if ($keywords = &getMeta($doc, 'keywords')) {
  		showIt('Keywords', $keywords);
  	} else {
 		&showMsg("NO KEYWORDS", 'Note') unless $keywords;
 	}
}

sub checkImages { # doc
 	local ($doc) = (@_);
 	# <IMG
 	local ($numtags) = 0;
 	&showHeader("Image Alt Text");
 	while ($doc =~ /(<img [^>]+>)/si) {
 		$doc =~ s///;
 		$img = $1;
 		local ($extra) = $F{'images'} ? $img : "";
 		showIt('Image', $img, $extra);
 		&checkAlt($img);
 		$numtags++;
 	}
 	&showMsg("NO IMAGE TAGS FOUND", 'Note') unless $numtags;
}

sub checkForm { # doc
 	local ($doc) = (@_);
 	# INPUT / TEXTAREA / PASSWORD
 	%label = &initLabel($doc);
 	&showHeader("Form Label Tags");
 	local ($numtags) = 0;
 	while ($doc =~ /(<(input|textarea|select)\b[^>]*>)/si) {
 		$doc =~ s///;
 		$element = $1;
 		showIt('Input', $1);
 		# showIt('type', $type);
 		$type = &getAttr($element, 'type');
 		$type = 'text' unless $type;
 		$numtags++;
 		if ($type =~ /(image)/si) {
 			&checkAlt($element);
 		} elsif ($type =~ /(text|radio|checkbox|password)/) {
 			$id = &getAttr($element, 'id');
 			if ($id) { # look for label tag for $id
 				if ($doc =~ m@<label\b[^>]*for=("$id"|'$id'|$id\b)[^>]*>(.+)</label>@si) {
 					# label tag is present for $id
 					$match = $1;
 					$label = $2;
 					$label =~ s|</label>.*||si; # make it shortest matching string
 					$F{'labels'} && &showIt('Label', $label, 'info');
 				} elsif ($label{$id} ne "") {
 					$F{'labels'} && &showIt("$id label", $label{$id}, 'info');
 				} else {
 					&showErr("NO LABEL TAG FOR '$id'");
 					# &dump($doc);
 				}
 			} else {
 				&showErr("NO ID ATTRIBUTE, SO NO LABEL TAG POSSIBLE");
 			}
 		}
 	}
 	&showMsg("NO FORM ELEMENTS FOUND", 'Note') unless $numtags;
}

sub dump {
 	if ($cgi) {
 		local ($text) = (@_);
 		print "<tr>\n";
 		print "<th align=left><label for=html>HTML:</label></th>\n";
 		print "<td><textarea id=html cols=80 rows=10>";
 		print &escape($text);
 		print "</textarea></td></tr>\n";
 	}
}

sub initLabel {
 	local ($doc) = (@_);
 	local ($attrs, $label, $for);
 	$doc =~ s/<label\b/\001/gi;
 	$doc =~ s/<\/label>/\002/gi;
 	local (%label);
 	while ($doc =~ /\001([^\002]*)\002/) {
 		$label = $1;
 		$doc =~ s///;
 		$attrs = $label;
 		$attrs =~ s/>.*$//; # keep the attributes
 		if ($attrs =~ /for="([^"]*)"/) {
 			$for = $1;
 			# &showErr("for-quote=$for");
 		} elsif ($attrs =~ /for=([\S]*)/) {
 			$for = $1;
 			# &showErr("for=$for");
 		}
 		if ($for) {
 			$label =~ s/[^>]*>//; # remove the attributes before first >
 			$label{$for} = $label;
 			# &showErr("[$for]=$label");
 		}
 	}
 	return %label;
}

sub checkLinks { # doc
 	local ($doc) = (@_);
 	$doc =~ s|</a\s*>|\002|gsi; # simplify the matching
 	local ($numtags) = 0;
 	&showHeader("Links");
 	# TODO only works for double quoted href attributes
 	local ($script) = "http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}";
 	local ($options) = "&source=$F{'source'}&images=$F{'images'}&labels=$F{'labels'}&links=$F{'links'}";
 	while ($doc =~ m|<a\b[^>]*href="([^"]+)"[^>]*>([^\002]+)\002|si) {
 		$doc =~ s///;
 		$href = $1;
 		$label = $2;
 		# fix up the href depending on whether it's absolute or relative
 		$qhref = &makeHref($base, $href);
 		$qhref = encode($qhref);
 		local ($title) = $label;
 		$title =~ s/<[^>]*>//g; # remove any html from label
 		$title =~ s/^\s+//;     # remove leading space
 		$title = "this link" unless $title; # provide backup to image labels
 		$title = "title='Run $cookiename on &quot;$title&quot;'";
 		showIt('Link', "HREF=[$href] LABEL=[$label]", "<a href=\"$script?url=$qhref$options\" $title>$label</a>");
 		$numtags++;
 	}
 	&showMsg("NO LINKS FOUND", 'Note') unless $numtags;
}

sub makeHref { # base-url this-href
 	local ($base, $href) = (@_);
 	if ($href =~ m|^https?://|i) {
 		return $href;
 	} elsif ($href =~ m|^/|) {      # href starts with /, so prepend server
 		$base =~ s|^(https?://)||i; # get rid of protocol to safely remove /*
 		local ($prefix) = $1;       # save protocol to restore later
 		$prefix = "http://" unless $prefix; # make sure there is something restored
 		$base =~ s|/.*$||;          # convert base to domain server url
 		$base = "$prefix$base";
 		# &showErr("base=$base");
 		return "$base$href";
 	} else {
 		while ($href =~ m|^../|) {
 			$href =~ s|^...||;    # chop off the ..
 			$base =~ s|/[^/]*$||; # chdir to .. in $base
 		}
 		return "$base/$href"; # use $base with relative URL
 	}
}

sub checkAlt { # tag
 	local ($tag) = (@_);
 	local ($alt) = &getAttr($tag, 'alt');
 	$F{'labels'} && &showIt('Alt-text', "$alt", 'info');
 	if (!$alt) {
 		&showErr("NO ALT TEXT");
 	}
}

sub getAttr { # tag name
 	local ($tag, $name) = (@_);
 	# &showErr("tag = " . escape($tag));;
 	# double quoted attr
 	if ($tag =~ m/\b$name="([^"]*)"/si) {
 		# &showErr("double quote found for $name");
 		if ($1 eq "") {
 			return '""';
 		} else {
 			return $1;
 		}
 	}
 	# single quoted attr
 	if ($tag =~ m/\b$name='([^']*)'/si) {
 		# &showErr("single quote found for $name");
 		if ($1 eq "") {
 			return "''";
 		} else {
 			return $1;
 		}
 	}
 	# unquoted attr
 	if ($tag =~ m/\b$name=([^ >]+)[ >]/si) {
 		# &showErr("no quote found for $name");
 		return $1;
 	}
 	return "";
}

sub remComments {
 	local ($doc) = (@_);
 	$doc =~ s/-->/\002/g; # map closer to single char for minimal match
 	while ($doc =~ /<!--/) {
 		$doc =~ s/(<!--[^\002]*\002)//;
 		break unless $1; # make sure we made a change
 	}
 	$doc =~ s/\002/-->/g;
 	return $doc;
}

sub showMsg {
 	local ($msg, $label, $color) = (@_);
 	$color = $normalbg unless $color;
 	if ($cgi) {
 		$count{$label}++;
 		print "<tr valign=top>\n";
 		print "<th align=left nowrap>";
 		print $label;
 		print "</th>\n";
 		print "<td bgcolor=\"$color\">";
 		print "<h2>" if $label eq $section;
 		print $msg;
 		print "</h2>" if $label eq $section;
 		print "</td></tr>\n";
 	} else {
 		print "\t******* $msg ******\n";
 	}
}

sub showErr {
 	local ($msg) = (@_);
 	&showMsg($msg, 'Error', $errorbg);
}

sub showHeader {
 	local ($msg) = (@_);
 	&showMsg($msg, $section, $titlebg);
}

sub showIt {
 	local ($name, $value, $extra) = (@_);
 	$value = &escape($value) if $cgi;
 	if ($extra eq 'info') {
 		$extra = '';
 		$bgcolor = "bgcolor='$infobg'";
 	} else {
 		$bgcolor = "bgcolor='$normalbg'";
 	}
 	$count{$name}++;
 	if ($cgi) {
 		print "<tr valign=top>\n";
 		print "<th align=left nowrap>$name:</th>\n";
 		print "<td $bgcolor ><tt>$value</tt>\n\t<div>$extra</div></td>\n";
 		print "</tr>\n";
 	} else {
 		print "$name\n\t$value\n";
 	}
}

sub clean {
  	local ($s) = (@_);
  	$s =~ s/^\s*//;
  	$s =~ s/\s*$//;
  	return $s;
}

sub getMeta { # find meta tag with supplied attribute name
  	local ($doc, $name) = (@_);
 	# find a meta tag with the desired name
  	if ($doc =~ m@(<meta\b[^>]*\bname=("$name"|'$name'|$name\b).*)@si) {
 		local ($tag) = $1;          # meta tag not terminated
 		$tag =~ s/>.*$/>/s;         # get rid of stuff after this meta tag
 		# &showIt('meta', $1);
 		return &clean( &getAttr($tag, 'content') );  # get the content
  	}
  	return "";
}

sub getTitle {
  	local ($doc) = (@_);
  	if ($doc =~ m|<title>([^<]*)</title>|i) {
  		return &clean($1);
  	}
  	return "";
}

sub encode { # string
     local ($str) = (@_);
 	$str =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
 	$str =~ s/%20/+/g;
 	return $str;
}

sub escape { # string
 	local ($s) = (@_);
 	# $s =~ s/&/&amp;/g; # don't do this or sgml entities are broken
 	$s =~ s/"/&quot;/g;
 	$s =~ s/</&lt;/g;
 	$s =~ s/</&gt;/g;
 	return $s;
}

sub getCookie { # name
 	local ($ident, $format) = (@_);
 	local ($cookie);
 	for (split (/[;] */, $ENV{'HTTP_COOKIE'})) {
 		if (/^$ident=(.*)/) {
 			$cookie = $1;
 			$cookie =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg; # decode
 			# now $cookie looks like a=xxx&b=yyy&c=zzz
 			break;
 		}
 	}
 	if ($format) {
 		$cookie =~ s/&/<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/g;
 	}
 	return $cookie;
}

sub getNameValue { # name=value
 	local ($pair) = (@_);
 	local ($name, $value) = split (/=/, $pair, 2);
     $value =~ s/\+/ /g;
     $value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
 	return ($name, $value);
}

sub formdata { # [ident] -> %F
 	local ($ident) = (@_);
     local (*formdata);
 	# first process options stored in cookie
 	local ($cookie) = getCookie($ident); # looks like a=xxx&b=yyy&c=zzz
 	local (@cookie) = split(/&/, $cookie);
 	local ($name, $value, $pair);
 	for $pair (@cookie) {
         ($name, $value) = &getNameValue($pair);
 		# print "<pre>$name = $value</pre>\n";
 		$F{$name} = $value;
 	}
 	# second get the options from the POST or GET
     if ($ENV{'REQUEST_METHOD'} eq 'POST') {
         read (STDIN, $F, $ENV{'CONTENT_LENGTH'});
     } else {
         $F = $ENV{'QUERY_STRING'};
     }
     @formdata = split (/&/, $F);
     for $pair (@formdata) {
         ($name, $value) = &getNameValue($pair);
         # $F{$name} .= "\n" if $F{$name}; # additional values append to previous
         # $F{$name} .= $value;
         $F{$name} = $value; # additional values override previous
     }
 	# finally, handle clear TODO - why not just set %F = ()?
 	$F{'clear'} && clearForm();
     return (%F);
}

