~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~

TidyLib
tidy/src/access.c

Version: ~ [ 1.0 ] ~

  1 /* access.c -- carry out accessibility checks
  2 
  3   Copyright University of Toronto
  4   Portions (c) 1998-2005 (W3C) MIT, ERCIM, Keio University
  5   See tidy.h for the copyright notice.
  6   
  7   CVS Info :
  8 
  9     $Author: arnaud02 $ 
 10     $Date: 2005/04/12 12:43:40 $ 
 11     $Revision: 1.28 $ 
 12 
 13 */
 14 
 15 /*********************************************************************
 16 * AccessibilityChecks
 17 *
 18 * Carries out processes for all accessibility checks.  Traverses
 19 * through all the content within the tree and evaluates the tags for
 20 * accessibility.
 21 *
 22 * To perform the following checks, 'AccessibilityChecks' must be
 23 * called AFTER the tree structure has been formed.
 24 *
 25 * If, in the command prompt, there is no specification of which
 26 * accessibility priorities to check, no accessibility checks will be 
 27 * performed.  (ie. '1' for priority 1, '2' for priorities 1 and 2, 
 28 *                  and '3') for priorities 1, 2 and 3.)
 29 *
 30 * Copyright University of Toronto
 31 * Programmed by: Mike Lam and Chris Ridpath
 32 * Modifications by : Terry Teague (TRT)
 33 *
 34 *********************************************************************/
 35 
 36 #include "tidy-int.h"
 37 
 38 #if SUPPORT_ACCESSIBILITY_CHECKS
 39 
 40 #include "access.h"
 41 #include "message.h"
 42 #include "tags.h"
 43 #include "attrs.h"
 44 #include "tmbstr.h"
 45 
 46 
 47 /* 
 48     The accessibility checks to perform depending on user's desire.
 49 
 50     1. priority 1
 51     2. priority 1 & 2
 52     3. priority 1, 2, & 3
 53 */
 54 
 55 /* List of possible image types */
 56 static const ctmbstr imageExtensions[] =
 57 {".jpg", ".gif", ".tif", ".pct", ".pic", ".iff", ".dib",
 58  ".tga", ".pcx", ".png", ".jpeg", ".tiff", ".bmp"};
 59 
 60 #define N_IMAGE_EXTS (sizeof(imageExtensions)/sizeof(ctmbstr))
 61 
 62 /* List of possible sound file types */
 63 static const ctmbstr soundExtensions[] =
 64 {".wav", ".au", ".aiff", ".snd", ".ra", ".rm"};
 65 
 66 static const int soundExtErrCodes[] = 
 67 {
 68     AUDIO_MISSING_TEXT_WAV,
 69     AUDIO_MISSING_TEXT_AU,
 70     AUDIO_MISSING_TEXT_AIFF,
 71     AUDIO_MISSING_TEXT_SND,
 72     AUDIO_MISSING_TEXT_RA,
 73     AUDIO_MISSING_TEXT_RM
 74 };
 75 
 76 #define N_AUDIO_EXTS (sizeof(soundExtensions)/sizeof(ctmbstr))
 77 
 78 /* List of possible media extensions */
 79 static const ctmbstr mediaExtensions[] = 
 80 {".mpg", ".mov", ".asx", ".avi", ".ivf", ".m1v", ".mmm", ".mp2v",
 81  ".mpa", ".mpe", ".mpeg", ".ram", ".smi", ".smil", ".swf",
 82  ".wm", ".wma", ".wmv"};
 83 
 84 #define N_MEDIA_EXTS (sizeof(mediaExtensions)/sizeof(ctmbstr))
 85 
 86 /* List of possible frame sources */
 87 static const ctmbstr frameExtensions[] =
 88 {".htm", ".html", ".shtm", ".shtml", ".cfm", ".cfml",
 89 ".asp", ".cgi", ".pl", ".smil"};
 90 
 91 #define N_FRAME_EXTS (sizeof(frameExtensions)/sizeof(ctmbstr))
 92 
 93 /* List of possible colour values */
 94 static const int colorValues[][3] =
 95 {
 96   {  0,  0,  0},
 97   {128,128,128},
 98   {192,192,192},
 99   {255,255,255},
100   {192,  0,  0},
101   {255,  0,  0},
102   {128,  0,128},
103   {255,  0,255},
104   {  0,128,  0},
105   {  0,255,  0},
106   {128,128,  0},
107   {255,255,  0},  
108   {  0,  0,128},
109   {  0,  0,255},
110   {  0,128,128},
111   {  0,255,255}
112 };
113 
114 #define N_COLOR_VALS (sizeof(colorValues)/(sizeof(int[3]))
115 
116 /* These arrays are used to convert color names to their RGB values */
117 static const ctmbstr colorNames[] =
118 {
119   "black",
120   "silver",
121   "grey",
122   "white",
123   "maroon",
124   "red",
125   "purple",
126   "fuchsia",
127   "green",
128   "lime",
129   "olive",
130   "yellow", 
131   "navy",
132   "blue",
133   "teal",
134   "aqua"
135 };
136 
137 #define N_COLOR_NAMES (sizeof(colorNames)/sizeof(ctmbstr))
138 #define N_COLORS N_COLOR_NAMES
139 
140 
141 /* function prototypes */
142 static void InitAccessibilityChecks( TidyDocImpl* doc, int level123 );
143 static void FreeAccessibilityChecks( TidyDocImpl* doc );
144 
145 static Bool GetRgb( ctmbstr color, int rgb[3] );
146 static Bool CompareColors( const int rgbBG[3], const int rgbFG[3] );
147 static int  ctox( tmbchar ch );
148 
149 /*
150 static void CheckMapAccess( TidyDocImpl* doc, Node* node, Node* front);
151 static void GetMapLinks( TidyDocImpl* doc, Node* node, Node* front);
152 static void CompareAnchorLinks( TidyDocImpl* doc, Node* front, int counter);
153 static void FindMissingLinks( TidyDocImpl* doc, Node* node, int counter);
154 */
155 static void CheckFormControls( TidyDocImpl* doc, Node* node );
156 static void MetaDataPresent( TidyDocImpl* doc, Node* node );
157 static void CheckEmbed( TidyDocImpl* doc, Node* node );
158 static void CheckListUsage( TidyDocImpl* doc, Node* node );
159 
160 /*
161     GetFileExtension takes a path and returns the extension
162     portion of the path (if any).
163 */
164 
165 static void GetFileExtension( ctmbstr path, tmbchar *ext, uint maxExt )
166 {
167     int i = tmbstrlen(path) - 1;
168     
169     ext[0] = '\0';
170     
171     do {
172         if ( path[i] == '/' || path[i] == '\\' )
173             break;
174         else if ( path[i] == '.' )
175         {
176             tmbstrncpy( ext, path+i, maxExt );
177             break;
178         }
179     } while ( --i > 0 );
180 }
181 
182 /************************************************************************
183 * IsImage
184 *
185 * Checks if the given filename is an image file.
186 * Returns 'yes' if it is, 'no' if it's not.
187 ************************************************************************/
188 
189 static Bool IsImage( ctmbstr iType )
190 {
191     uint i;
192 
193     /* Get the file extension */
194     tmbchar ext[20];
195     GetFileExtension( iType, ext, sizeof(ext) );
196 
197     /* Compare it to the array of known image file extensions */
198     for (i = 0; i < N_IMAGE_EXTS; i++)
199     {
200         if ( tmbstrcasecmp(ext, imageExtensions[i]) == 0 )
201             return yes;
202     }
203     
204     return no;
205 }
206 
207 
208 /***********************************************************************
209 * IsSoundFile
210 *
211 * Checks if the given filename is a sound file.
212 * Returns 'yes' if it is, 'no' if it's not.
213 ***********************************************************************/
214 
215 static int IsSoundFile( ctmbstr sType )
216 {
217     uint i;
218     tmbchar ext[ 20 ];
219     GetFileExtension( sType, ext, sizeof(ext) );
220 
221     for (i = 0; i < N_AUDIO_EXTS; i++)
222     {
223         if ( tmbstrcasecmp(ext, soundExtensions[i]) == 0 )
224             return soundExtErrCodes[i];
225     }
226     return 0;
227 }
228 
229 
230 /***********************************************************************
231 * IsValidSrcExtension
232 *
233 * Checks if the 'SRC' value within the FRAME element is valid
234 * The 'SRC' extension must end in ".htm", ".html", ".shtm", ".shtml", 
235 * ".cfm", ".cfml", ".asp", ".cgi", ".pl", or ".smil"
236 *
237 * Returns yes if it is, returns no otherwise.
238 ***********************************************************************/
239 
240 static Bool IsValidSrcExtension( ctmbstr sType )
241 {
242     uint i;
243     tmbchar ext[20];
244     GetFileExtension( sType, ext, sizeof(ext) );
245 
246     for (i = 0; i < N_FRAME_EXTS; i++)
247     {
248         if ( tmbstrcasecmp(ext, frameExtensions[i]) == 0 )
249             return yes;
250     }
251     return no;
252 }
253 
254 
255 /*********************************************************************
256 * IsValidMediaExtension
257 *
258 * Checks to warn the user that syncronized text equivalents are 
259 * required if multimedia is used.
260 *********************************************************************/
261 
262 static Bool IsValidMediaExtension( ctmbstr sType )
263 {
264     uint i;
265     tmbchar ext[20];
266     GetFileExtension( sType, ext, sizeof(ext) );
267 
268     for (i = 0; i < N_MEDIA_EXTS; i++)
269     {
270         if ( tmbstrcasecmp(ext, mediaExtensions[i]) == 0 )
271             return yes;
272     }
273     return no;
274 }
275 
276 
277 /************************************************************************
278 * IsWhitespace
279 *
280 * Checks if the given string is all whitespace.
281 * Returns 'yes' if it is, 'no' if it's not.
282 ************************************************************************/
283 
284 static Bool IsWhitespace( ctmbstr pString )
285 {
286     Bool isWht = yes;
287     ctmbstr cp;
288 
289     for ( cp = pString; isWht && cp && *cp; ++cp )
290     {
291         isWht = IsWhite( *cp );
292     }
293     return isWht;
294 }
295 
296 static Bool hasValue( AttVal* av )
297 {
298   return ( av && ! IsWhitespace(av->value) );
299 }
300 
301 /***********************************************************************
302 * IsPlaceholderAlt
303 *  
304 * Checks to see if there is an image and photo place holder contained
305 * in the ALT text.
306 *
307 * Returns 'yes' if there is, 'no' if not.
308 ***********************************************************************/
309 
310 static Bool IsPlaceholderAlt( ctmbstr txt )
311 {
312     return ( strstr(txt, "image") != NULL || 
313              strstr(txt, "photo") != NULL );
314 }
315 
316 
317 /***********************************************************************
318 * IsPlaceholderTitle
319 *  
320 * Checks to see if there is an TITLE place holder contained
321 * in the 'ALT' text.
322 *
323 * Returns 'yes' if there is, 'no' if not.
324 
325 static Bool IsPlaceHolderTitle( ctmbstr txt )
326 {
327     return ( strstr(txt, "title") != NULL );
328 }
329 ***********************************************************************/
330 
331 
332 /***********************************************************************
333 * IsPlaceHolderObject
334 *  
335 * Checks to see if there is an OBJECT place holder contained
336 * in the 'ALT' text.
337 *
338 * Returns 'yes' if there is, 'no' if not.
339 ***********************************************************************/
340 
341 static Bool IsPlaceHolderObject( ctmbstr txt )
342 {
343     return ( strstr(txt, "object") != NULL );
344 }
345 
346 
347 /**********************************************************
348 * EndsWithBytes
349 *
350 * Checks to see if the ALT text ends with 'bytes'
351 * Returns 'yes', if true, 'no' otherwise.
352 **********************************************************/
353 
354 static Bool EndsWithBytes( ctmbstr txt )
355 {
356     uint len = tmbstrlen( txt );
357     return ( len >= 5 && strcmp(txt+len-5, "bytes") == 0 );
358 }
359 
360 
361 /*******************************************************
362 * textFromOneNode
363 *
364 * Returns a list of characters contained within one
365 * text node.
366 *******************************************************/
367 
368 static ctmbstr textFromOneNode( TidyDocImpl* doc, Node* node )
369 {
370     uint i;
371     uint x = 0;
372     tmbstr txt = doc->access.text;
373     
374     if ( node )
375     {
376         /* Copy contents of a text node */
377         for (i = node->start; i < node->end; ++i, ++x )
378         {
379             txt[x] = doc->lexer->lexbuf[i];
380 
381             /* Check buffer overflow */
382             if ( x >= sizeof(doc->access.text)-1 )
383                 break;
384         }
385     }
386 
387     txt[x] = '\0';
388     return txt;
389 }
390 
391 
392 /*********************************************************
393 * getTextNode
394 *
395 * Locates text nodes within a container element.
396 * Retrieves text that are found contained within 
397 * text nodes, and concatenates the text.
398 *********************************************************/
399     
400 static void getTextNode( TidyDocImpl* doc, Node* node )
401 {
402     tmbstr txtnod = doc->access.textNode;       
403     
404     /* 
405        Continues to traverse through container element until it no
406        longer contains any more contents 
407     */
408 
409     /* If the tag of the node is NULL, then grab the text within the node */
410     if ( nodeIsText(node) )
411     {
412         uint i;
413 
414         /* Retrieves each character found within the text node */
415         for (i = node->start; i < node->end; i++)
416         {
417             /* The text must not exceed buffer */
418             if ( doc->access.counter >= TEXTBUF_SIZE-1 )
419                 return;
420 
421             txtnod[ doc->access.counter++ ] = doc->lexer->lexbuf[i];
422         }
423 
424         /* Traverses through the contents within a container element */
425         for ( node = node->content; node != NULL; node = node->next )
426             getTextNode( doc, node );
427     }   
428 }
429 
430 
431 /**********************************************************
432 * getTextNodeClear
433 *
434 * Clears the current 'textNode' and reloads it with new
435 * text.  The textNode must be cleared before use.
436 **********************************************************/
437 
438 static tmbstr getTextNodeClear( TidyDocImpl* doc, Node* node )
439 {
440     /* Clears list */
441     ClearMemory( doc->access.textNode, TEXTBUF_SIZE );
442     doc->access.counter = 0;
443 
444     getTextNode( doc, node->content );
445     return doc->access.textNode;
446 }
447 
448 /**********************************************************
449 * LevelX_Enabled
450 *
451 * Tell whether access "X" is enabled.
452 **********************************************************/
453 
454 static Bool Level1_Enabled( TidyDocImpl* doc )
455 {
456    return doc->access.PRIORITYCHK == 1 ||
457           doc->access.PRIORITYCHK == 2 ||
458           doc->access.PRIORITYCHK == 3;
459 }
460 static Bool Level2_Enabled( TidyDocImpl* doc )
461 {
462     return doc->access.PRIORITYCHK == 2 ||
463            doc->access.PRIORITYCHK == 3;
464 }
465 static Bool Level3_Enabled( TidyDocImpl* doc )
466 {
467     return doc->access.PRIORITYCHK == 3;
468 }
469 
470 /********************************************************
471 * CheckColorAvailable
472 *
473 * Verify that information conveyed with color is 
474 * available without color.
475 ********************************************************/
476 
477 static void CheckColorAvailable( TidyDocImpl* doc, Node* node )
478 {
479     if (Level1_Enabled( doc ))
480     {
481         if ( nodeIsIMG(node) )
482             ReportAccessWarning( doc, node, INFORMATION_NOT_CONVEYED_IMAGE );
483 
484         else if ( nodeIsAPPLET(node) )
485             ReportAccessWarning( doc, node, INFORMATION_NOT_CONVEYED_APPLET );
486 
487         else if ( nodeIsOBJECT(node) )
488             ReportAccessWarning( doc, node, INFORMATION_NOT_CONVEYED_OBJECT );
489 
490         else if ( nodeIsSCRIPT(node) )
491             ReportAccessWarning( doc, node, INFORMATION_NOT_CONVEYED_SCRIPT );
492 
493         else if ( nodeIsINPUT(node) )
494             ReportAccessWarning( doc, node, INFORMATION_NOT_CONVEYED_INPUT );
495     }
496 }
497 
498 /*********************************************************************
499 * CheckColorContrast
500 *
501 * Checks elements for color contrast.  Must have valid contrast for
502 * valid visibility.
503 *
504 * This logic is extremely fragile as it does not recognize
505 * the fact that color is inherited by many components and
506 * that BG and FG colors are often set separately.  E.g. the
507 * background color may be set by for the body or a table 
508 * or a cell.  The foreground color may be set by any text
509 * element (p, h1, h2, input, textarea), either explicitly
510 * or by style.  Ergo, this test will not handle most real
511 * world cases.  It's a start, however.
512 *********************************************************************/
513 
514 static void CheckColorContrast( TidyDocImpl* doc, Node* node )
515 {
516     int rgbBG[3] = {255,255,255};   /* Black text on white BG */
517 
518     if (Level3_Enabled( doc ))
519     {
520         Bool gotBG = yes;
521         AttVal* av;
522 
523         /* Check for 'BGCOLOR' first to compare with other color attributes */
524         for ( av = node->attributes; av; av = av->next )
525         {            
526             if ( attrIsBGCOLOR(av) )
527             {
528                 if ( hasValue(av) )
529                     gotBG = GetRgb( av->value, rgbBG );
530             }
531         }
532         
533         /* 
534            Search for COLOR attributes to compare with background color
535            Must have valid colour contrast
536         */
537         for ( av = node->attributes; gotBG && av != NULL; av = av->next )
538         {
539             uint errcode = 0;
540             if ( attrIsTEXT(av) )
541                 errcode = COLOR_CONTRAST_TEXT;
542             else if ( attrIsLINK(av) )
543                 errcode = COLOR_CONTRAST_LINK;
544             else if ( attrIsALINK(av) )
545                 errcode = COLOR_CONTRAST_ACTIVE_LINK;
546             else if ( attrIsVLINK(av) )
547                 errcode = COLOR_CONTRAST_VISITED_LINK;
548 
549             if ( errcode && hasValue(av) )
550             {
551                 int rgbFG[3] = {0, 0, 0};  /* Black text */
552 
553                 if ( GetRgb(av->value, rgbFG) &&
554                      !CompareColors(rgbBG, rgbFG) )
555                 {
556                     ReportAccessWarning( doc, node, errcode );
557                 }
558             }
559         }
560     }
561 }
562 
563 
564 /**************************************************************
565 * CompareColors
566 *
567 * Compares two RGB colors for good contrast.
568 **************************************************************/
569 static int minmax( int i1, int i2 )
570 {
571    return MAX(i1, i2) - MIN(i1,i2);
572 }
573 static int brightness( const int rgb[3] )
574 {
575    return ((rgb[0]*299) + (rgb[1]*587) + (rgb[2]*114)) / 1000;
576 }
577 
578 static Bool CompareColors( const int rgbBG[3], const int rgbFG[3] )
579 {
580     int brightBG = brightness( rgbBG );
581     int brightFG = brightness( rgbFG );
582 
583     int diffBright = minmax( brightBG, brightFG );
584 
585     int diffColor = minmax( rgbBG[0], rgbFG[0] )
586                   + minmax( rgbBG[1], rgbFG[1] )
587                   + minmax( rgbBG[2], rgbFG[2] );
588 
589     return ( diffBright > 180 &&
590              diffColor > 500 );
591 }
592 
593 
594 /*********************************************************************
595 * GetRgb
596 *
597 * Gets the red, green and blue values for this attribute for the 
598 * background.
599 *
600 * Example: If attribute is BGCOLOR="#121005" then red = 18, green = 16,
601 * blue = 5.
602 *********************************************************************/
603 
604 static Bool GetRgb( ctmbstr color, int rgb[] )
605 {
606     uint x;
607 
608     /* Check if we have a color name */
609     for (x = 0; x < N_COLORS; x++)
610     {
611         if ( strstr(colorNames[x], color) != NULL )
612         {
613             rgb[0] = colorValues[x][0];
614             rgb[1] = colorValues[x][1];
615             rgb[2] = colorValues[x][2];
616             return yes;
617         }
618     }
619 
620     /*
621        No color name so must be hex values 
622        Is this a number in hexadecimal format?
623     */
624     
625     /* Must be 7 characters in the RGB value (including '#') */
626     if ( tmbstrlen(color) == 7 && color[0] == '#' )
627     {
628         rgb[0] = (ctox(color[1]) * 16) + ctox(color[2]);
629         rgb[1] = (ctox(color[3]) * 16) + ctox(color[4]);
630         rgb[2] = (ctox(color[5]) * 16) + ctox(color[6]);
631         return yes;
632     }
633     return no;
634 } 
635 
636 
637 
638 /*******************************************************************
639 * ctox
640 *
641 * Converts a character to a number.
642 * Example: if given character is 'A' then returns 10.
643 *
644 * Returns the number that the character represents. Returns -1 if not a
645 * valid number.
646 *******************************************************************/
647 
648 static int ctox( tmbchar ch )
649 {
650     if ( ch >= '' && ch <= '9' )
651     {
652          return ch - '';
653     }
654     else if ( ch >= 'a' && ch <= 'f' )
655     {
656         return ch - 'a' + 10;
657     }
658     else if ( ch >= 'A' && ch <= 'F' )
659     {
660         return ch - 'A' + 10;
661     }
662     return -1;
663 }
664 
665 
666 /***********************************************************
667 * CheckImage
668 *
669 * Checks all image attributes for specific elements to
670 * check for validity of the values contained within
671 * the attributes.  An appropriate warning message is displayed
672 * to indicate the error.  
673 ***********************************************************/
674 
675 static void CheckImage( TidyDocImpl* doc, Node* node )
676 {
677     Bool HasAlt = no;
678     Bool HasIsMap = no;
679     Bool HasLongDesc = no;
680     Bool HasDLINK = no;
681     Bool HasValidHeight = no;
682     Bool HasValidWidthBullet = no;
683     Bool HasValidWidthHR = no; 
684     Bool HasTriggeredMissingLongDesc = no;
685 
686     AttVal* av;
687                 
688     if (Level1_Enabled( doc ))
689     {
690         /* Checks all image attributes for invalid values within attributes */
691         for (av = node->attributes; av != NULL; av = av->next)
692         {
693             /* 
694                Checks for valid ALT attribute.
695                The length of the alt text must be less than 150 characters 
696                long.
697             */
698             if ( attrIsALT(av) )
699             {
700                 if (av->value != NULL) 
701                 {
702                     if ((tmbstrlen(av->value) < 150) &&
703                         (IsPlaceholderAlt (av->value) == no) &&
704                         (IsPlaceHolderObject (av->value) == no) &&
705                         (EndsWithBytes (av->value) == no) &&
706                         (IsImage (av->value) == no))
707                     {
708                         HasAlt = yes;
709                     }
710 
711                     else if (tmbstrlen (av->value) > 150)
712                     {
713                         HasAlt = yes;
714                         ReportAccessWarning( doc, node, IMG_ALT_SUSPICIOUS_TOO_LONG );
715                     }
716 
717                     else if (IsImage (av->value) == yes)
718                     {
719                         HasAlt = yes;
720                         ReportAccessWarning( doc, node, IMG_ALT_SUSPICIOUS_FILENAME);
721                     }
722             
723                     else if (IsPlaceholderAlt (av->value) == yes)
724                     {
725                         HasAlt = yes;
726                         ReportAccessWarning( doc, node, IMG_ALT_SUSPICIOUS_PLACEHOLDER);
727                     }
728 
729                     else if (EndsWithBytes (av->value) == yes)
730                     {
731                         HasAlt = yes;
732                         ReportAccessWarning( doc, node, IMG_ALT_SUSPICIOUS_FILE_SIZE);
733                     }
734                 }
735             }
736 
737             /* 
738                Checks for width values of 'bullets' and 'horizontal
739                rules' for validity.
740 
741                Valid pixel width for 'bullets' must be < 30, and > 150 for
742                horizontal rules.
743             */
744             else if ( attrIsWIDTH(av) )
745             {
746                 /* Longdesc attribute needed if width attribute is not present. */
747                 if ( hasValue(av) )
748                 {
749                     int width = atoi( av->value );
750                     if ( width < 30 )
751                         HasValidWidthBullet = yes;
752 
753                     if ( width > 150 )
754                         HasValidWidthHR = yes;
755                 }
756             }
757 
758             /* 
759                Checks for height values of 'bullets' and horizontal
760                rules for validity.
761 
762                Valid pixel height for 'bullets' and horizontal rules 
763                mustt be < 30.
764             */
765             else if ( attrIsHEIGHT(av) )
766             {
767                 /* Longdesc attribute needed if height attribute not present. */
768                 if ( hasValue(av) && atoi(av->value) < 30 )
769                     HasValidHeight = yes;
770             }
771 
772             /* 
773                Checks for longdesc and determines validity.  
774                The length of the 'longdesc' must be > 1
775             */
776             else if ( attrIsLONGDESC(av) )
777             {
778                 if ( hasValue(av) && tmbstrlen(av->value) > 1 )
779                     HasLongDesc = yes;
780               }
781 
782             /* 
783                Checks for 'USEMAP' attribute.  Ensures that
784                text links are provided for client-side image maps
785             */
786             else if ( attrIsUSEMAP(av) )
787             {
788                 if ( hasValue(av) )
789                     doc->access.HasUseMap = yes;
790             }    
791 
792             else if ( attrIsISMAP(av) )
793             {
794                 HasIsMap = yes;
795             }
796         }    
797         
798         
799         /* 
800             Check to see if a dLINK is present.  The ANCHOR element must
801             be present following the IMG element.  The text found between 
802             the ANCHOR tags must be < 6 characters long, and must contain
803             the letter 'd'.
804         */
805         if ( nodeIsA(node->next) )
806         {
807             node = node->next;
808             
809             /* 
810                 Node following the anchor must be a text node
811                 for dLINK to exist 
812             */
813 
814             if(node->content != NULL && (node->content)->tag == NULL)
815             {
816                 /* Number of characters found within the text node */
817                 ctmbstr word = textFromOneNode( doc, node->content);
818                     
819                 if ((strcmp(word,"d") == 0)||
820                     (strcmp(word,"D") == 0))
821                 {
822                     HasDLINK = yes;
823                 }
824             }
825         }
826                     
827         /*
828             Special case check for dLINK.  This will occur if there is 
829             whitespace between the <img> and <a> elements.  Ignores 
830             whitespace and continues check for dLINK.
831         */
832         
833         if ( node->next && !node->next->tag )
834         {
835             node = node->next;
836 
837             if ( nodeIsA(node->next) )
838             {
839                 node = node->next;
840 
841                 /* 
842                     Node following the ANCHOR must be a text node
843                     for dLINK to exist 
844                 */
845                 if(node->content != NULL && node->content->tag == NULL)
846                 {
847                     /* Number of characters found within the text node */
848                     ctmbstr word = textFromOneNode( doc, node->content );
849 
850                     if ((strcmp(word, "d") == 0)||
851                         (strcmp(word, "D") == 0))
852                     {
853                         HasDLINK = yes;
854                     }
855                 }
856             }
857         }
858 
859         if ((HasAlt == no)&&
860             (HasValidWidthBullet == yes)&&
861             (HasValidHeight == yes))
862         {
863         }
864 
865         if ((HasAlt == no)&&
866             (HasValidWidthHR == yes)&&
867             (HasValidHeight == yes))
868         {
869         }
870 
871         if (HasAlt == no)
872         {
873             ReportAccessError( doc, node, IMG_MISSING_ALT);
874         }
875 
876         if ((HasLongDesc == no)&&
877             (HasValidHeight ==yes)&&
878             ((HasValidWidthHR == yes)||
879              (HasValidWidthBullet == yes)))
880         {
881             HasTriggeredMissingLongDesc = yes;
882         }
883 
884         if (HasTriggeredMissingLongDesc == no)
885         {
886             if ((HasDLINK == yes)&&
887                 (HasLongDesc == no))
888             {
889                 ReportAccessWarning( doc, node, IMG_MISSING_LONGDESC);
890             }
891 
892             if ((HasLongDesc == yes)&&
893                 (HasDLINK == no))
894             {
895                 ReportAccessWarning( doc, node, IMG_MISSING_DLINK);
896             }
897 
898             if ((HasLongDesc == no)&&
899                 (HasDLINK == no))
900             {
901                 ReportAccessWarning( doc, node, IMG_MISSING_LONGDESC_DLINK);
902             }
903         }
904 
905         if (HasIsMap == yes)
906         {
907             ReportAccessError( doc, node, IMAGE_MAP_SERVER_SIDE_REQUIRES_CONVERSION);
908 
909             ReportAccessWarning( doc, node, IMG_MAP_SERVER_REQUIRES_TEXT_LINKS);
910         }
911     }
912 }
913 
914 
915 /***********************************************************
916 * CheckApplet
917 *
918 * Checks APPLET element to check for validity pertaining 
919 * the 'ALT' attribute.  An appropriate warning message is 
920 * displayed  to indicate the error. An appropriate warning 
921 * message is displayed to indicate the error.  If no 'ALT'
922 * text is present, then there must be alternate content
923 * within the APPLET element.
924 ***********************************************************/
925 
926 static void CheckApplet( TidyDocImpl* doc, Node* node )
927 {
928     Bool HasAlt = no;
929     Bool HasDescription = no;
930 
931     AttVal* av;
932         
933     if (Level1_Enabled( doc ))
934     {
935         /* Checks for attributes within the APPLET element */
936         for (av = node->attributes; av != NULL; av = av->next)
937         {
938             /*
939                Checks for valid ALT attribute.
940                The length of the alt text must be > 4 characters in length
941                but must be < 150 characters long.
942             */
943 
944             if ( attrIsALT(av) )
945             {
946                 if (av->value != NULL)
947                 {
948                     HasAlt = yes;
949                 }
950             }
951         }
952 
953         if (HasAlt == no)
954         {
955             /* Must have alternate text representation for that element */
956             if (node->content != NULL) 
957             {
958                 ctmbstr word = NULL;
959 
960                 if ( node->content->tag == NULL )
961                     word = textFromOneNode( doc, node->content);
962 
963                 if ( node->content->content != NULL &&
964                      node->content->content->tag == NULL )
965                 {
966                     word = textFromOneNode( doc, node->content->content);
967                 }
968                 
969                 if ( word != NULL && !IsWhitespace(word) )
970                     HasDescription = yes;
971             }
972         }
973 
974         if ( !HasDescription && !HasAlt )
975         {
976             ReportAccessError( doc, node, APPLET_MISSING_ALT );
977         }
978     }
979 }
980 
981 
982 /*******************************************************************
983 * CheckObject
984 *
985 * Checks to verify whether the OBJECT element contains
986 * 'ALT' text, and to see that the sound file selected is 
987 * of a valid sound file type.  OBJECT must have an alternate text 
988 * representation.
989 *******************************************************************/
990 
991 static void CheckObject( TidyDocImpl* doc, Node* node )
992 {
993     Bool HasAlt = no;
994     Bool HasDescription = no;
995 
996     if (Level1_Enabled( doc ))
997     {
998         if ( node->content != NULL)
999         {
1000             if ( node->content->type != TextNode )
1001             {
1002                 Node* tnode = node->content;
1003                 AttVal* av;
1004 
1005                 for ( av=tnode->attributes; av; av = av->next )
1006                 {
1007                     if ( attrIsALT(av) )
1008                     {
1009                         HasAlt = yes;
1010                         break;
1011                     }
1012                 }
1013             }
1014 
1015             /* Must have alternate text representation for that element */
1016             if ( !HasAlt )
1017             {
1018                 ctmbstr word = NULL;
1019 
1020                 if ( nodeIsText(node->content) )
1021                     word = textFromOneNode( doc, node->content );
1022 
1023                 if ( word == NULL &&
1024                      nodeIsText(node->content->content) )
1025                 {
1026                     word = textFromOneNode( doc, node->content->content );
1027                 }
1028                     
1029                 if ( word != NULL && !IsWhitespace(word) )
1030                     HasDescription = yes;
1031             }
1032         }
1033 
1034         if ( !HasAlt && !HasDescription )
1035         {
1036             ReportAccessError( doc, node, OBJECT_MISSING_ALT );
1037         }
1038     }
1039 }
1040 
1041 
1042 /***************************************************************
1043 * CheckMissingStyleSheets
1044 *
1045 * Ensures that stylesheets are used to control the presentation.
1046 ***************************************************************/
1047 
1048 static Bool CheckMissingStyleSheets( TidyDocImpl* doc, Node* node )
1049 {
1050     AttVal* av;
1051     Node* content;
1052     Bool sspresent = no;
1053 
1054     for ( content = node->content;
1055           !sspresent && content != NULL;
1056           content = content->next )
1057     {
1058         sspresent = ( nodeIsLINK(content)  ||
1059                       nodeIsSTYLE(content) ||
1060                       nodeIsFONT(content)  ||
1061                       nodeIsBASEFONT(content) );
1062 
1063         for ( av = content->attributes;
1064               !sspresent && av != NULL;
1065               av = av->next )
1066         {
1067             sspresent = ( attrIsSTYLE(av) || attrIsTEXT(av)  ||
1068                           attrIsVLINK(av) || attrIsALINK(av) ||
1069                           attrIsLINK(av) );
1070 
1071             if ( !sspresent && attrIsREL(av) )
1072             {
1073                 sspresent = AttrValueIs(av, "stylesheet");
1074             }
1075         }
1076 
1077         if ( ! sspresent )
1078             sspresent = CheckMissingStyleSheets( doc, content );
1079     }
1080     return sspresent;
1081 }
1082 
1083 
1084 /*******************************************************************
1085 * CheckFrame
1086 *
1087 * Checks if the URL is valid and to check if a 'LONGDESC' is needed
1088 * within the FRAME element.  If a 'LONGDESC' is needed, the value must 
1089 * be valid. The URL must end with the file extension, htm, or html. 
1090 * Also, checks to ensure that the 'SRC' and 'TITLE' values are valid. 
1091 *******************************************************************/
1092 
1093 static void CheckFrame( TidyDocImpl* doc, Node* node )
1094 {
1095     Bool HasTitle = no;
1096     AttVal* av;
1097 
1098     doc->access.numFrames++;
1099 
1100     if (Level1_Enabled( doc ))
1101     {
1102         /* Checks for attributes within the FRAME element */
1103         for (av = node->attributes; av != NULL; av = av->next)
1104         {
1105             /* Checks if 'LONGDESC' value is valid only if present */
1106             if ( attrIsLONGDESC(av) )
1107             {
1108                 if ( hasValue(av) && tmbstrlen(av->value) > 1 )
1109                 {
1110                     doc->access.HasCheckedLongDesc++;
1111                 }
1112             }
1113 
1114             /* Checks for valid 'SRC' value within the frame element */
1115             else if ( attrIsSRC(av) )
1116             {
1117                 if ( hasValue(av) && !IsValidSrcExtension(av->value) )
1118                 {
1119                     ReportAccessError( doc, node, FRAME_SRC_INVALID );
1120                 }
1121             }
1122 
1123             /* Checks for valid 'TITLE' value within frame element */
1124             else if ( attrIsTITLE(av) )
1125             {
1126                 if ( hasValue(av) )
1127                     HasTitle = yes;
1128 
1129                 if ( !HasTitle )
1130                 {
1131                     if ( av->value == NULL || tmbstrlen(av->value) == 0 )
1132                     {
1133                         HasTitle = yes;
1134                         ReportAccessError( doc, node, FRAME_TITLE_INVALID_NULL);
1135                     }
1136                     else
1137                     {
1138                         if ( IsWhitespace(av->value) && tmbstrlen(av->value) > 0 )
1139                         {
1140                             HasTitle = yes;
1141                             ReportAccessError( doc, node, FRAME_TITLE_INVALID_SPACES );
1142                         }
1143                     }
1144                 }
1145             }
1146         }
1147 
1148         if ( !HasTitle )
1149         {
1150             ReportAccessError( doc, node, FRAME_MISSING_TITLE);
1151         }
1152 
1153         if ( doc->access.numFrames==3 && doc->access.HasCheckedLongDesc<3 )
1154         {
1155             doc->access.numFrames = 0;
1156             ReportAccessWarning( doc, node, FRAME_MISSING_LONGDESC );
1157         }
1158     }
1159 }
1160 
1161 
1162 /****************************************************************
1163 * CheckIFrame
1164 *
1165 * Checks if 'SRC' value is valid.  Must end in appropriate
1166 * file extension.
1167 ****************************************************************/
1168 
1169 static void CheckIFrame( TidyDocImpl* doc, Node* node )
1170 {
1171     if (Level1_Enabled( doc ))
1172     {
1173         /* Checks for valid 'SRC' value within the IFRAME element */
1174         AttVal* av = attrGetSRC( node );
1175         if ( hasValue(av) )
1176         {
1177             if ( !IsValidSrcExtension(av->value) )
1178                 ReportAccessError( doc, node, FRAME_SRC_INVALID );
1179         }
1180     }
1181 }
1182 
1183 
1184 /**********************************************************************
1185 * CheckAnchorAccess
1186 *
1187 * Checks that the sound file is valid, and to ensure that
1188 * text transcript is present describing the 'HREF' within the 
1189 * ANCHOR element.  Also checks to see ensure that the 'TARGET' attribute
1190 * (if it exists) is not NULL and does not contain '_new' or '_blank'.
1191 **********************************************************************/
1192 
1193 static void CheckAnchorAccess( TidyDocImpl* doc, Node* node )
1194 {
1195     AttVal* av;
1196     Bool HasDescription = no;
1197     Bool HasTriggeredLink = no;
1198 
1199     /* Checks for attributes within the ANCHOR element */
1200     for ( av = node->attributes; av != NULL; av = av->next )
1201     {
1202         if (Level1_Enabled( doc ))
1203         {
1204             /* Must be of valid sound file type */
1205             if ( attrIsHREF(av) )
1206             {
1207                 if ( hasValue(av) )
1208                 {
1209                     tmbchar ext[ 20 ];
1210                     GetFileExtension (av->value, ext, sizeof(ext) );
1211 
1212                     /* Checks to see if multimedia is used */
1213                     if ( IsValidMediaExtension(av->value) )
1214                     {
1215                         ReportAccessError( doc, node, MULTIMEDIA_REQUIRES_TEXT );
1216                     }
1217             
1218                     /* 
1219                         Checks for validity of sound file, and checks to see if 
1220                         the file is described within the document, or by a link
1221                         that is present which gives the description.
1222                     */
1223                     if ( tmbstrlen(ext) < 6 && tmbstrlen(ext) > 0 )
1224                     {
1225                         int errcode = IsSoundFile( av->value );
1226                         if ( errcode )
1227                         {
1228                             if (node->next != NULL)
1229                             {
1230                                 if (node->next->tag == NULL)
1231                                 {
1232                                     ctmbstr word = textFromOneNode( doc, node->next);
1233                                 
1234                                     /* Must contain at least one letter in the text */
1235                                     if (IsWhitespace (word) == no)
1236                                     {
1237                                         HasDescription = yes;
1238                                     }
1239                                 }
1240                             }
1241 
1242                             /* Must contain text description of sound file */
1243                             if ( !HasDescription )
1244                             {
1245                                 ReportAccessError( doc, node, errcode );
1246                             }
1247                         }
1248                     }
1249                 }
1250             }
1251         }
1252 
1253         if (Level2_Enabled( doc ))
1254         {
1255             /* Checks 'TARGET' attribute for validity if it exists */
1256             if ( attrIsTARGET(av) )
1257             {
1258                 if (AttrValueIs(av, "_new"))
1259                 {
1260                     ReportAccessWarning( doc, node, NEW_WINDOWS_REQUIRE_WARNING_NEW);
1261                 }
1262                 else if (AttrValueIs(av, "_blank"))
1263                 {
1264                     ReportAccessWarning( doc, node, NEW_WINDOWS_REQUIRE_WARNING_BLANK);
1265                 }
1266             }
1267         }
1268     }
1269     
1270     if (Level2_Enabled( doc ))
1271     {
1272         if ((node->content != NULL)&&
1273             (node->content->tag == NULL))
1274         {
1275             ctmbstr word = textFromOneNode( doc, node->content);
1276 
1277             if ((word != NULL)&&
1278                 (IsWhitespace (word) == no))
1279             {
1280                 if (strcmp (word, "more") == 0)
1281                 {
1282                     HasTriggeredLink = yes;
1283                 }
1284 
1285                 if (strcmp (word, "click here") == 0)
1286                 {
1287                     ReportAccessWarning( doc, node, LINK_TEXT_NOT_MEANINGFUL_CLICK_HERE);
1288                 }
1289 
1290                 if (HasTriggeredLink == no)
1291                 {
1292                     if (tmbstrlen (word) < 6)
1293                     {
1294                         ReportAccessWarning( doc, node, LINK_TEXT_NOT_MEANINGFUL);
1295                     }
1296                 }
1297 
1298                 if (tmbstrlen (word) > 60)
1299                 {
1300                     ReportAccessWarning( doc, node, LINK_TEXT_TOO_LONG);
1301                 }
1302 
1303             }
1304         }
1305         
1306         if (node->content == NULL)
1307         {
1308             ReportAccessWarning( doc, node, LINK_TEXT_MISSING);
1309         }
1310     }
1311 }
1312 
1313 
1314 /************************************************************
1315 * CheckArea
1316 *
1317 * Checks attributes within the AREA element to 
1318 * determine if the 'ALT' text and 'HREF' values are valid.
1319 * Also checks to see ensure that the 'TARGET' attribute
1320 * (if it exists) is not NULL and does not contain '_new' 
1321 * or '_blank'.
1322 ************************************************************/
1323 
1324 static void CheckArea( TidyDocImpl* doc, Node* node )
1325 {
1326     Bool HasAlt = no;
1327     AttVal* av;
1328 
1329     /* Checks all attributes within the AREA element */
1330     for (av = node->attributes; av != NULL; av = av->next)
1331     {
1332         if (Level1_Enabled( doc ))
1333         {
1334             /*
1335               Checks for valid ALT attribute.
1336               The length of the alt text must be > 4 characters long
1337               but must be less than 150 characters long.
1338             */
1339                 
1340             if ( attrIsALT(av) )
1341             {
1342                 /* The check for validity */
1343                 if (av->value != NULL) 
1344                 {
1345                     HasAlt = yes;
1346                 }
1347             }
1348         }
1349 
1350         if (Level2_Enabled( doc ))
1351         {
1352             if ( attrIsTARGET(av) )
1353             {
1354                 if (AttrValueIs(av, "_new"))