2011-11-17 02:44:16 +00:00
|
|
|
/* access.c -- carry out accessibility checks
|
|
|
|
|
|
|
|
Copyright University of Toronto
|
|
|
|
Portions (c) 1998-2009 (W3C) MIT, ERCIM, Keio University
|
|
|
|
See tidy.h for the copyright notice.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*********************************************************************
|
|
|
|
* AccessibilityChecks
|
|
|
|
*
|
|
|
|
* Carries out processes for all accessibility checks. Traverses
|
|
|
|
* through all the content within the tree and evaluates the tags for
|
|
|
|
* accessibility.
|
|
|
|
*
|
|
|
|
* To perform the following checks, 'AccessibilityChecks' must be
|
|
|
|
* called AFTER the tree structure has been formed.
|
|
|
|
*
|
|
|
|
* If, in the command prompt, there is no specification of which
|
|
|
|
* accessibility priorities to check, no accessibility checks will be
|
|
|
|
* performed. (ie. '1' for priority 1, '2' for priorities 1 and 2,
|
|
|
|
* and '3') for priorities 1, 2 and 3.)
|
|
|
|
*
|
|
|
|
* Copyright University of Toronto
|
|
|
|
* Programmed by: Mike Lam and Chris Ridpath
|
|
|
|
* Modifications by : Terry Teague (TRT)
|
|
|
|
*
|
|
|
|
* Reference document: http://www.w3.org/TR/WAI-WEBCONTENT/
|
|
|
|
*********************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
#include "tidy-int.h"
|
|
|
|
|
|
|
|
#if SUPPORT_ACCESSIBILITY_CHECKS
|
|
|
|
|
|
|
|
#include "access.h"
|
|
|
|
#include "message.h"
|
|
|
|
#include "tags.h"
|
|
|
|
#include "attrs.h"
|
|
|
|
#include "tmbstr.h"
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
The accessibility checks to perform depending on user's desire.
|
|
|
|
|
|
|
|
1. priority 1
|
|
|
|
2. priority 1 & 2
|
|
|
|
3. priority 1, 2, & 3
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* List of possible image types */
|
|
|
|
static const ctmbstr imageExtensions[] =
|
|
|
|
{".jpg", ".gif", ".tif", ".pct", ".pic", ".iff", ".dib",
|
|
|
|
".tga", ".pcx", ".png", ".jpeg", ".tiff", ".bmp"};
|
|
|
|
|
|
|
|
#define N_IMAGE_EXTS (sizeof(imageExtensions)/sizeof(ctmbstr))
|
|
|
|
|
|
|
|
/* List of possible sound file types */
|
|
|
|
static const ctmbstr soundExtensions[] =
|
|
|
|
{".wav", ".au", ".aiff", ".snd", ".ra", ".rm"};
|
|
|
|
|
|
|
|
static const int soundExtErrCodes[] =
|
|
|
|
{
|
|
|
|
AUDIO_MISSING_TEXT_WAV,
|
|
|
|
AUDIO_MISSING_TEXT_AU,
|
|
|
|
AUDIO_MISSING_TEXT_AIFF,
|
|
|
|
AUDIO_MISSING_TEXT_SND,
|
|
|
|
AUDIO_MISSING_TEXT_RA,
|
|
|
|
AUDIO_MISSING_TEXT_RM
|
|
|
|
};
|
|
|
|
|
|
|
|
#define N_AUDIO_EXTS (sizeof(soundExtensions)/sizeof(ctmbstr))
|
|
|
|
|
|
|
|
/* List of possible media extensions */
|
|
|
|
static const ctmbstr mediaExtensions[] =
|
|
|
|
{".mpg", ".mov", ".asx", ".avi", ".ivf", ".m1v", ".mmm", ".mp2v",
|
|
|
|
".mpa", ".mpe", ".mpeg", ".ram", ".smi", ".smil", ".swf",
|
|
|
|
".wm", ".wma", ".wmv"};
|
|
|
|
|
|
|
|
#define N_MEDIA_EXTS (sizeof(mediaExtensions)/sizeof(ctmbstr))
|
|
|
|
|
|
|
|
/* List of possible frame sources */
|
|
|
|
static const ctmbstr frameExtensions[] =
|
|
|
|
{".htm", ".html", ".shtm", ".shtml", ".cfm", ".cfml",
|
|
|
|
".asp", ".cgi", ".pl", ".smil"};
|
|
|
|
|
|
|
|
#define N_FRAME_EXTS (sizeof(frameExtensions)/sizeof(ctmbstr))
|
|
|
|
|
|
|
|
/* List of possible colour values */
|
|
|
|
static const int colorValues[][3] =
|
|
|
|
{
|
|
|
|
{ 0, 0, 0},
|
|
|
|
{128,128,128},
|
|
|
|
{192,192,192},
|
|
|
|
{255,255,255},
|
|
|
|
{192, 0, 0},
|
|
|
|
{255, 0, 0},
|
|
|
|
{128, 0,128},
|
|
|
|
{255, 0,255},
|
|
|
|
{ 0,128, 0},
|
|
|
|
{ 0,255, 0},
|
|
|
|
{128,128, 0},
|
|
|
|
{255,255, 0},
|
|
|
|
{ 0, 0,128},
|
|
|
|
{ 0, 0,255},
|
|
|
|
{ 0,128,128},
|
|
|
|
{ 0,255,255}
|
|
|
|
};
|
|
|
|
|
|
|
|
#define N_COLOR_VALS (sizeof(colorValues)/(sizeof(int[3]))
|
|
|
|
|
|
|
|
/* These arrays are used to convert color names to their RGB values */
|
|
|
|
static const ctmbstr colorNames[] =
|
|
|
|
{
|
|
|
|
"black",
|
|
|
|
"silver",
|
|
|
|
"grey",
|
|
|
|
"white",
|
|
|
|
"maroon",
|
|
|
|
"red",
|
|
|
|
"purple",
|
|
|
|
"fuchsia",
|
|
|
|
"green",
|
|
|
|
"lime",
|
|
|
|
"olive",
|
|
|
|
"yellow",
|
|
|
|
"navy",
|
|
|
|
"blue",
|
|
|
|
"teal",
|
|
|
|
"aqua"
|
|
|
|
};
|
|
|
|
|
|
|
|
#define N_COLOR_NAMES (sizeof(colorNames)/sizeof(ctmbstr))
|
|
|
|
#define N_COLORS N_COLOR_NAMES
|
|
|
|
|
|
|
|
|
|
|
|
/* function prototypes */
|
|
|
|
static void InitAccessibilityChecks( TidyDocImpl* doc, int level123 );
|
|
|
|
static void FreeAccessibilityChecks( TidyDocImpl* doc );
|
|
|
|
|
|
|
|
static Bool GetRgb( ctmbstr color, int rgb[3] );
|
|
|
|
static Bool CompareColors( const int rgbBG[3], const int rgbFG[3] );
|
|
|
|
static int ctox( tmbchar ch );
|
|
|
|
|
|
|
|
/*
|
|
|
|
static void CheckMapAccess( TidyDocImpl* doc, Node* node, Node* front);
|
|
|
|
static void GetMapLinks( TidyDocImpl* doc, Node* node, Node* front);
|
|
|
|
static void CompareAnchorLinks( TidyDocImpl* doc, Node* front, int counter);
|
|
|
|
static void FindMissingLinks( TidyDocImpl* doc, Node* node, int counter);
|
|
|
|
*/
|
|
|
|
static void CheckFormControls( TidyDocImpl* doc, Node* node );
|
|
|
|
static void MetaDataPresent( TidyDocImpl* doc, Node* node );
|
|
|
|
static void CheckEmbed( TidyDocImpl* doc, Node* node );
|
|
|
|
static void CheckListUsage( TidyDocImpl* doc, Node* node );
|
|
|
|
|
|
|
|
/*
|
|
|
|
GetFileExtension takes a path and returns the extension
|
|
|
|
portion of the path (if any).
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void GetFileExtension( ctmbstr path, tmbchar *ext, uint maxExt )
|
|
|
|
{
|
|
|
|
int i = TY_(tmbstrlen)(path) - 1;
|
|
|
|
|
|
|
|
ext[0] = '\0';
|
|
|
|
|
|
|
|
do {
|
|
|
|
if ( path[i] == '/' || path[i] == '\\' )
|
|
|
|
break;
|
|
|
|
else if ( path[i] == '.' )
|
|
|
|
{
|
|
|
|
TY_(tmbstrncpy)( ext, path+i, maxExt );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while ( --i > 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
* IsImage
|
|
|
|
*
|
|
|
|
* Checks if the given filename is an image file.
|
|
|
|
* Returns 'yes' if it is, 'no' if it's not.
|
|
|
|
************************************************************************/
|
|
|
|
|
|
|
|
static Bool IsImage( ctmbstr iType )
|
|
|
|
{
|
|
|
|
uint i;
|
|
|
|
|
|
|
|
/* Get the file extension */
|
|
|
|
tmbchar ext[20];
|
|
|
|
GetFileExtension( iType, ext, sizeof(ext) );
|
|
|
|
|
|
|
|
/* Compare it to the array of known image file extensions */
|
|
|
|
for (i = 0; i < N_IMAGE_EXTS; i++)
|
|
|
|
{
|
|
|
|
if ( TY_(tmbstrcasecmp)(ext, imageExtensions[i]) == 0 )
|
|
|
|
return yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
return no;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* IsSoundFile
|
|
|
|
*
|
|
|
|
* Checks if the given filename is a sound file.
|
|
|
|
* Returns 'yes' if it is, 'no' if it's not.
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
|
|
static int IsSoundFile( ctmbstr sType )
|
|
|
|
{
|
|
|
|
uint i;
|
|
|
|
tmbchar ext[ 20 ];
|
|
|
|
GetFileExtension( sType, ext, sizeof(ext) );
|
|
|
|
|
|
|
|
for (i = 0; i < N_AUDIO_EXTS; i++)
|
|
|
|
{
|
|
|
|
if ( TY_(tmbstrcasecmp)(ext, soundExtensions[i]) == 0 )
|
|
|
|
return soundExtErrCodes[i];
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* IsValidSrcExtension
|
|
|
|
*
|
|
|
|
* Checks if the 'SRC' value within the FRAME element is valid
|
|
|
|
* The 'SRC' extension must end in ".htm", ".html", ".shtm", ".shtml",
|
|
|
|
* ".cfm", ".cfml", ".asp", ".cgi", ".pl", or ".smil"
|
|
|
|
*
|
|
|
|
* Returns yes if it is, returns no otherwise.
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
|
|
static Bool IsValidSrcExtension( ctmbstr sType )
|
|
|
|
{
|
|
|
|
uint i;
|
|
|
|
tmbchar ext[20];
|
|
|
|
GetFileExtension( sType, ext, sizeof(ext) );
|
|
|
|
|
|
|
|
for (i = 0; i < N_FRAME_EXTS; i++)
|
|
|
|
{
|
|
|
|
if ( TY_(tmbstrcasecmp)(ext, frameExtensions[i]) == 0 )
|
|
|
|
return yes;
|
|
|
|
}
|
|
|
|
return no;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************
|
|
|
|
* IsValidMediaExtension
|
|
|
|
*
|
|
|
|
* Checks to warn the user that syncronized text equivalents are
|
|
|
|
* required if multimedia is used.
|
|
|
|
*********************************************************************/
|
|
|
|
|
|
|
|
static Bool IsValidMediaExtension( ctmbstr sType )
|
|
|
|
{
|
|
|
|
uint i;
|
|
|
|
tmbchar ext[20];
|
|
|
|
GetFileExtension( sType, ext, sizeof(ext) );
|
|
|
|
|
|
|
|
for (i = 0; i < N_MEDIA_EXTS; i++)
|
|
|
|
{
|
|
|
|
if ( TY_(tmbstrcasecmp)(ext, mediaExtensions[i]) == 0 )
|
|
|
|
return yes;
|
|
|
|
}
|
|
|
|
return no;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/************************************************************************
|
|
|
|
* IsWhitespace
|
|
|
|
*
|
|
|
|
* Checks if the given string is all whitespace.
|
|
|
|
* Returns 'yes' if it is, 'no' if it's not.
|
|
|
|
************************************************************************/
|
|
|
|
|
|
|
|
static Bool IsWhitespace( ctmbstr pString )
|
|
|
|
{
|
|
|
|
Bool isWht = yes;
|
|
|
|
ctmbstr cp;
|
|
|
|
|
|
|
|
for ( cp = pString; isWht && cp && *cp; ++cp )
|
|
|
|
{
|
|
|
|
isWht = TY_(IsWhite)( *cp );
|
|
|
|
}
|
|
|
|
return isWht;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Bool hasValue( AttVal* av )
|
|
|
|
{
|
|
|
|
return ( av && ! IsWhitespace(av->value) );
|
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* IsPlaceholderAlt
|
|
|
|
*
|
|
|
|
* Checks to see if there is an image and photo place holder contained
|
|
|
|
* in the ALT text.
|
|
|
|
*
|
|
|
|
* Returns 'yes' if there is, 'no' if not.
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
|
|
static Bool IsPlaceholderAlt( ctmbstr txt )
|
|
|
|
{
|
|
|
|
return ( strstr(txt, "image") != NULL ||
|
|
|
|
strstr(txt, "photo") != NULL );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* IsPlaceholderTitle
|
|
|
|
*
|
|
|
|
* Checks to see if there is an TITLE place holder contained
|
|
|
|
* in the 'ALT' text.
|
|
|
|
*
|
|
|
|
* Returns 'yes' if there is, 'no' if not.
|
|
|
|
|
|
|
|
static Bool IsPlaceHolderTitle( ctmbstr txt )
|
|
|
|
{
|
|
|
|
return ( strstr(txt, "title") != NULL );
|
|
|
|
}
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* IsPlaceHolderObject
|
|
|
|
*
|
|
|
|
* Checks to see if there is an OBJECT place holder contained
|
|
|
|
* in the 'ALT' text.
|
|
|
|
*
|
|
|
|
* Returns 'yes' if there is, 'no' if not.
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
|
|
static Bool IsPlaceHolderObject( ctmbstr txt )
|
|
|
|
{
|
|
|
|
return ( strstr(txt, "object") != NULL );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**********************************************************
|
|
|
|
* EndsWithBytes
|
|
|
|
*
|
|
|
|
* Checks to see if the ALT text ends with 'bytes'
|
|
|
|
* Returns 'yes', if true, 'no' otherwise.
|
|
|
|
**********************************************************/
|
|
|
|
|
|
|
|
static Bool EndsWithBytes( ctmbstr txt )
|
|
|
|
{
|
|
|
|
uint len = TY_(tmbstrlen)( txt );
|
|
|
|
return ( len >= 5 && TY_(tmbstrcmp)(txt+len-5, "bytes") == 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************
|
|
|
|
* textFromOneNode
|
|
|
|
*
|
|
|
|
* Returns a list of characters contained within one
|
|
|
|
* text node.
|
|
|
|
*******************************************************/
|
|
|
|
|
|
|
|
static ctmbstr textFromOneNode( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
uint i;
|
|
|
|
uint x = 0;
|
|
|
|
tmbstr txt = doc->access.text;
|
|
|
|
|
|
|
|
if ( node )
|
|
|
|
{
|
|
|
|
/* Copy contents of a text node */
|
|
|
|
for (i = node->start; i < node->end; ++i, ++x )
|
|
|
|
{
|
|
|
|
txt[x] = doc->lexer->lexbuf[i];
|
|
|
|
|
|
|
|
/* Check buffer overflow */
|
|
|
|
if ( x >= sizeof(doc->access.text)-1 )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
txt[x] = '\0';
|
|
|
|
return txt;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************
|
|
|
|
* getTextNode
|
|
|
|
*
|
|
|
|
* Locates text nodes within a container element.
|
|
|
|
* Retrieves text that are found contained within
|
|
|
|
* text nodes, and concatenates the text.
|
|
|
|
*********************************************************/
|
|
|
|
|
|
|
|
static void getTextNode( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
tmbstr txtnod = doc->access.textNode;
|
|
|
|
|
|
|
|
/*
|
|
|
|
Continues to traverse through container element until it no
|
|
|
|
longer contains any more contents
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* If the tag of the node is NULL, then grab the text within the node */
|
|
|
|
if ( TY_(nodeIsText)(node) )
|
|
|
|
{
|
|
|
|
uint i;
|
|
|
|
|
|
|
|
/* Retrieves each character found within the text node */
|
|
|
|
for (i = node->start; i < node->end; i++)
|
|
|
|
{
|
|
|
|
/* The text must not exceed buffer */
|
|
|
|
if ( doc->access.counter >= TEXTBUF_SIZE-1 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
txtnod[ doc->access.counter++ ] = doc->lexer->lexbuf[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Traverses through the contents within a container element */
|
|
|
|
for ( node = node->content; node != NULL; node = node->next )
|
|
|
|
getTextNode( doc, node );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**********************************************************
|
|
|
|
* getTextNodeClear
|
|
|
|
*
|
|
|
|
* Clears the current 'textNode' and reloads it with new
|
|
|
|
* text. The textNode must be cleared before use.
|
|
|
|
**********************************************************/
|
|
|
|
|
|
|
|
static tmbstr getTextNodeClear( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
/* Clears list */
|
|
|
|
TidyClearMemory( doc->access.textNode, TEXTBUF_SIZE );
|
|
|
|
doc->access.counter = 0;
|
|
|
|
|
|
|
|
getTextNode( doc, node->content );
|
|
|
|
return doc->access.textNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************
|
|
|
|
* LevelX_Enabled
|
|
|
|
*
|
|
|
|
* Tell whether access "X" is enabled.
|
|
|
|
**********************************************************/
|
|
|
|
|
|
|
|
static Bool Level1_Enabled( TidyDocImpl* doc )
|
|
|
|
{
|
|
|
|
return doc->access.PRIORITYCHK == 1 ||
|
|
|
|
doc->access.PRIORITYCHK == 2 ||
|
|
|
|
doc->access.PRIORITYCHK == 3;
|
|
|
|
}
|
|
|
|
static Bool Level2_Enabled( TidyDocImpl* doc )
|
|
|
|
{
|
|
|
|
return doc->access.PRIORITYCHK == 2 ||
|
|
|
|
doc->access.PRIORITYCHK == 3;
|
|
|
|
}
|
|
|
|
static Bool Level3_Enabled( TidyDocImpl* doc )
|
|
|
|
{
|
|
|
|
return doc->access.PRIORITYCHK == 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
/********************************************************
|
|
|
|
* CheckColorAvailable
|
|
|
|
*
|
|
|
|
* Verify that information conveyed with color is
|
|
|
|
* available without color.
|
|
|
|
********************************************************/
|
|
|
|
|
|
|
|
static void CheckColorAvailable( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
if ( nodeIsIMG(node) )
|
|
|
|
TY_(ReportAccessWarning)( doc, node, INFORMATION_NOT_CONVEYED_IMAGE );
|
|
|
|
|
|
|
|
else if ( nodeIsAPPLET(node) )
|
|
|
|
TY_(ReportAccessWarning)( doc, node, INFORMATION_NOT_CONVEYED_APPLET );
|
|
|
|
|
|
|
|
else if ( nodeIsOBJECT(node) )
|
|
|
|
TY_(ReportAccessWarning)( doc, node, INFORMATION_NOT_CONVEYED_OBJECT );
|
|
|
|
|
|
|
|
else if ( nodeIsSCRIPT(node) )
|
|
|
|
TY_(ReportAccessWarning)( doc, node, INFORMATION_NOT_CONVEYED_SCRIPT );
|
|
|
|
|
|
|
|
else if ( nodeIsINPUT(node) )
|
|
|
|
TY_(ReportAccessWarning)( doc, node, INFORMATION_NOT_CONVEYED_INPUT );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************
|
|
|
|
* CheckColorContrast
|
|
|
|
*
|
|
|
|
* Checks elements for color contrast. Must have valid contrast for
|
|
|
|
* valid visibility.
|
|
|
|
*
|
|
|
|
* This logic is extremely fragile as it does not recognize
|
|
|
|
* the fact that color is inherited by many components and
|
|
|
|
* that BG and FG colors are often set separately. E.g. the
|
|
|
|
* background color may be set by for the body or a table
|
|
|
|
* or a cell. The foreground color may be set by any text
|
|
|
|
* element (p, h1, h2, input, textarea), either explicitly
|
|
|
|
* or by style. Ergo, this test will not handle most real
|
|
|
|
* world cases. It's a start, however.
|
|
|
|
*********************************************************************/
|
|
|
|
|
|
|
|
static void CheckColorContrast( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
int rgbBG[3] = {255,255,255}; /* Black text on white BG */
|
|
|
|
|
|
|
|
if (Level3_Enabled( doc ))
|
|
|
|
{
|
|
|
|
Bool gotBG = yes;
|
|
|
|
AttVal* av;
|
|
|
|
|
|
|
|
/* Check for 'BGCOLOR' first to compare with other color attributes */
|
|
|
|
for ( av = node->attributes; av; av = av->next )
|
|
|
|
{
|
|
|
|
if ( attrIsBGCOLOR(av) )
|
|
|
|
{
|
|
|
|
if ( hasValue(av) )
|
|
|
|
gotBG = GetRgb( av->value, rgbBG );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Search for COLOR attributes to compare with background color
|
|
|
|
Must have valid colour contrast
|
|
|
|
*/
|
|
|
|
for ( av = node->attributes; gotBG && av != NULL; av = av->next )
|
|
|
|
{
|
|
|
|
uint errcode = 0;
|
|
|
|
if ( attrIsTEXT(av) )
|
|
|
|
errcode = COLOR_CONTRAST_TEXT;
|
|
|
|
else if ( attrIsLINK(av) )
|
|
|
|
errcode = COLOR_CONTRAST_LINK;
|
|
|
|
else if ( attrIsALINK(av) )
|
|
|
|
errcode = COLOR_CONTRAST_ACTIVE_LINK;
|
|
|
|
else if ( attrIsVLINK(av) )
|
|
|
|
errcode = COLOR_CONTRAST_VISITED_LINK;
|
|
|
|
|
|
|
|
if ( errcode && hasValue(av) )
|
|
|
|
{
|
|
|
|
int rgbFG[3] = {0, 0, 0}; /* Black text */
|
|
|
|
|
|
|
|
if ( GetRgb(av->value, rgbFG) &&
|
|
|
|
!CompareColors(rgbBG, rgbFG) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, errcode );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************************************
|
|
|
|
* CompareColors
|
|
|
|
*
|
|
|
|
* Compares two RGB colors for good contrast.
|
|
|
|
**************************************************************/
|
|
|
|
static int minmax( int i1, int i2 )
|
|
|
|
{
|
|
|
|
return MAX(i1, i2) - MIN(i1,i2);
|
|
|
|
}
|
|
|
|
static int brightness( const int rgb[3] )
|
|
|
|
{
|
|
|
|
return ((rgb[0]*299) + (rgb[1]*587) + (rgb[2]*114)) / 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Bool CompareColors( const int rgbBG[3], const int rgbFG[3] )
|
|
|
|
{
|
|
|
|
int brightBG = brightness( rgbBG );
|
|
|
|
int brightFG = brightness( rgbFG );
|
|
|
|
|
|
|
|
int diffBright = minmax( brightBG, brightFG );
|
|
|
|
|
|
|
|
int diffColor = minmax( rgbBG[0], rgbFG[0] )
|
|
|
|
+ minmax( rgbBG[1], rgbFG[1] )
|
|
|
|
+ minmax( rgbBG[2], rgbFG[2] );
|
|
|
|
|
|
|
|
return ( diffBright > 180 &&
|
|
|
|
diffColor > 500 );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************
|
|
|
|
* GetRgb
|
|
|
|
*
|
|
|
|
* Gets the red, green and blue values for this attribute for the
|
|
|
|
* background.
|
|
|
|
*
|
|
|
|
* Example: If attribute is BGCOLOR="#121005" then red = 18, green = 16,
|
|
|
|
* blue = 5.
|
|
|
|
*********************************************************************/
|
|
|
|
|
|
|
|
static Bool GetRgb( ctmbstr color, int rgb[] )
|
|
|
|
{
|
|
|
|
uint x;
|
|
|
|
|
|
|
|
/* Check if we have a color name */
|
|
|
|
for (x = 0; x < N_COLORS; x++)
|
|
|
|
{
|
|
|
|
if ( strstr(colorNames[x], color) != NULL )
|
|
|
|
{
|
|
|
|
rgb[0] = colorValues[x][0];
|
|
|
|
rgb[1] = colorValues[x][1];
|
|
|
|
rgb[2] = colorValues[x][2];
|
|
|
|
return yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
No color name so must be hex values
|
|
|
|
Is this a number in hexadecimal format?
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Must be 7 characters in the RGB value (including '#') */
|
|
|
|
if ( TY_(tmbstrlen)(color) == 7 && color[0] == '#' )
|
|
|
|
{
|
|
|
|
rgb[0] = (ctox(color[1]) * 16) + ctox(color[2]);
|
|
|
|
rgb[1] = (ctox(color[3]) * 16) + ctox(color[4]);
|
|
|
|
rgb[2] = (ctox(color[5]) * 16) + ctox(color[6]);
|
|
|
|
return yes;
|
|
|
|
}
|
|
|
|
return no;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
* ctox
|
|
|
|
*
|
|
|
|
* Converts a character to a number.
|
|
|
|
* Example: if given character is 'A' then returns 10.
|
|
|
|
*
|
|
|
|
* Returns the number that the character represents. Returns -1 if not a
|
|
|
|
* valid number.
|
|
|
|
*******************************************************************/
|
|
|
|
|
|
|
|
static int ctox( tmbchar ch )
|
|
|
|
{
|
|
|
|
if ( ch >= '0' && ch <= '9' )
|
|
|
|
{
|
|
|
|
return ch - '0';
|
|
|
|
}
|
|
|
|
else if ( ch >= 'a' && ch <= 'f' )
|
|
|
|
{
|
|
|
|
return ch - 'a' + 10;
|
|
|
|
}
|
|
|
|
else if ( ch >= 'A' && ch <= 'F' )
|
|
|
|
{
|
|
|
|
return ch - 'A' + 10;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************
|
|
|
|
* CheckImage
|
|
|
|
*
|
|
|
|
* Checks all image attributes for specific elements to
|
|
|
|
* check for validity of the values contained within
|
|
|
|
* the attributes. An appropriate warning message is displayed
|
|
|
|
* to indicate the error.
|
|
|
|
***********************************************************/
|
|
|
|
|
|
|
|
static void CheckImage( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Bool HasAlt = no;
|
|
|
|
Bool HasIsMap = no;
|
|
|
|
Bool HasLongDesc = no;
|
|
|
|
Bool HasDLINK = no;
|
|
|
|
Bool HasValidHeight = no;
|
|
|
|
Bool HasValidWidthBullet = no;
|
|
|
|
Bool HasValidWidthHR = no;
|
|
|
|
Bool HasTriggeredMissingLongDesc = no;
|
|
|
|
|
|
|
|
AttVal* av;
|
|
|
|
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Checks all image attributes for invalid values within attributes */
|
|
|
|
for (av = node->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
Checks for valid ALT attribute.
|
|
|
|
The length of the alt text must be less than 150 characters
|
|
|
|
long.
|
|
|
|
*/
|
|
|
|
if ( attrIsALT(av) )
|
|
|
|
{
|
|
|
|
if (av->value != NULL)
|
|
|
|
{
|
|
|
|
if ((TY_(tmbstrlen)(av->value) < 150) &&
|
|
|
|
(IsPlaceholderAlt (av->value) == no) &&
|
|
|
|
(IsPlaceHolderObject (av->value) == no) &&
|
|
|
|
(EndsWithBytes (av->value) == no) &&
|
|
|
|
(IsImage (av->value) == no))
|
|
|
|
{
|
|
|
|
HasAlt = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (TY_(tmbstrlen)(av->value) > 150)
|
|
|
|
{
|
|
|
|
HasAlt = yes;
|
|
|
|
TY_(ReportAccessWarning)( doc, node, IMG_ALT_SUSPICIOUS_TOO_LONG );
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (IsImage (av->value) == yes)
|
|
|
|
{
|
|
|
|
HasAlt = yes;
|
|
|
|
TY_(ReportAccessWarning)( doc, node, IMG_ALT_SUSPICIOUS_FILENAME);
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (IsPlaceholderAlt (av->value) == yes)
|
|
|
|
{
|
|
|
|
HasAlt = yes;
|
|
|
|
TY_(ReportAccessWarning)( doc, node, IMG_ALT_SUSPICIOUS_PLACEHOLDER);
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (EndsWithBytes (av->value) == yes)
|
|
|
|
{
|
|
|
|
HasAlt = yes;
|
|
|
|
TY_(ReportAccessWarning)( doc, node, IMG_ALT_SUSPICIOUS_FILE_SIZE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Checks for width values of 'bullets' and 'horizontal
|
|
|
|
rules' for validity.
|
|
|
|
|
|
|
|
Valid pixel width for 'bullets' must be < 30, and > 150 for
|
|
|
|
horizontal rules.
|
|
|
|
*/
|
|
|
|
else if ( attrIsWIDTH(av) )
|
|
|
|
{
|
|
|
|
/* Longdesc attribute needed if width attribute is not present. */
|
|
|
|
if ( hasValue(av) )
|
|
|
|
{
|
|
|
|
int width = atoi( av->value );
|
|
|
|
if ( width < 30 )
|
|
|
|
HasValidWidthBullet = yes;
|
|
|
|
|
|
|
|
if ( width > 150 )
|
|
|
|
HasValidWidthHR = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Checks for height values of 'bullets' and horizontal
|
|
|
|
rules for validity.
|
|
|
|
|
|
|
|
Valid pixel height for 'bullets' and horizontal rules
|
|
|
|
mustt be < 30.
|
|
|
|
*/
|
|
|
|
else if ( attrIsHEIGHT(av) )
|
|
|
|
{
|
|
|
|
/* Longdesc attribute needed if height attribute not present. */
|
|
|
|
if ( hasValue(av) && atoi(av->value) < 30 )
|
|
|
|
HasValidHeight = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Checks for longdesc and determines validity.
|
|
|
|
The length of the 'longdesc' must be > 1
|
|
|
|
*/
|
|
|
|
else if ( attrIsLONGDESC(av) )
|
|
|
|
{
|
|
|
|
if ( hasValue(av) && TY_(tmbstrlen)(av->value) > 1 )
|
|
|
|
HasLongDesc = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Checks for 'USEMAP' attribute. Ensures that
|
|
|
|
text links are provided for client-side image maps
|
|
|
|
*/
|
|
|
|
else if ( attrIsUSEMAP(av) )
|
|
|
|
{
|
|
|
|
if ( hasValue(av) )
|
|
|
|
doc->access.HasUseMap = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if ( attrIsISMAP(av) )
|
|
|
|
{
|
|
|
|
HasIsMap = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Check to see if a dLINK is present. The ANCHOR element must
|
|
|
|
be present following the IMG element. The text found between
|
|
|
|
the ANCHOR tags must be < 6 characters long, and must contain
|
|
|
|
the letter 'd'.
|
|
|
|
*/
|
|
|
|
if ( nodeIsA(node->next) )
|
|
|
|
{
|
|
|
|
node = node->next;
|
|
|
|
|
|
|
|
/*
|
|
|
|
Node following the anchor must be a text node
|
|
|
|
for dLINK to exist
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (node->content != NULL && (node->content)->tag == NULL)
|
|
|
|
{
|
|
|
|
/* Number of characters found within the text node */
|
|
|
|
ctmbstr word = textFromOneNode( doc, node->content);
|
|
|
|
|
|
|
|
if ((TY_(tmbstrcmp)(word,"d") == 0)||
|
|
|
|
(TY_(tmbstrcmp)(word,"D") == 0))
|
|
|
|
{
|
|
|
|
HasDLINK = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Special case check for dLINK. This will occur if there is
|
|
|
|
whitespace between the <img> and <a> elements. Ignores
|
|
|
|
whitespace and continues check for dLINK.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ( node->next && !node->next->tag )
|
|
|
|
{
|
|
|
|
node = node->next;
|
|
|
|
|
|
|
|
if ( nodeIsA(node->next) )
|
|
|
|
{
|
|
|
|
node = node->next;
|
|
|
|
|
|
|
|
/*
|
|
|
|
Node following the ANCHOR must be a text node
|
|
|
|
for dLINK to exist
|
|
|
|
*/
|
|
|
|
if (node->content != NULL && node->content->tag == NULL)
|
|
|
|
{
|
|
|
|
/* Number of characters found within the text node */
|
|
|
|
ctmbstr word = textFromOneNode( doc, node->content );
|
|
|
|
|
|
|
|
if ((TY_(tmbstrcmp)(word, "d") == 0)||
|
|
|
|
(TY_(tmbstrcmp)(word, "D") == 0))
|
|
|
|
{
|
|
|
|
HasDLINK = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((HasAlt == no)&&
|
|
|
|
(HasValidWidthBullet == yes)&&
|
|
|
|
(HasValidHeight == yes))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((HasAlt == no)&&
|
|
|
|
(HasValidWidthHR == yes)&&
|
|
|
|
(HasValidHeight == yes))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HasAlt == no)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, IMG_MISSING_ALT);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((HasLongDesc == no)&&
|
|
|
|
(HasValidHeight ==yes)&&
|
|
|
|
((HasValidWidthHR == yes)||
|
|
|
|
(HasValidWidthBullet == yes)))
|
|
|
|
{
|
|
|
|
HasTriggeredMissingLongDesc = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HasTriggeredMissingLongDesc == no)
|
|
|
|
{
|
|
|
|
if ((HasDLINK == yes)&&
|
|
|
|
(HasLongDesc == no))
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, IMG_MISSING_LONGDESC);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((HasLongDesc == yes)&&
|
|
|
|
(HasDLINK == no))
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, IMG_MISSING_DLINK);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((HasLongDesc == no)&&
|
|
|
|
(HasDLINK == no))
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, IMG_MISSING_LONGDESC_DLINK);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HasIsMap == yes)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, IMAGE_MAP_SERVER_SIDE_REQUIRES_CONVERSION);
|
|
|
|
|
|
|
|
TY_(ReportAccessWarning)( doc, node, IMG_MAP_SERVER_REQUIRES_TEXT_LINKS);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************
|
|
|
|
* CheckApplet
|
|
|
|
*
|
|
|
|
* Checks APPLET element to check for validity pertaining
|
|
|
|
* the 'ALT' attribute. An appropriate warning message is
|
|
|
|
* displayed to indicate the error. An appropriate warning
|
|
|
|
* message is displayed to indicate the error. If no 'ALT'
|
|
|
|
* text is present, then there must be alternate content
|
|
|
|
* within the APPLET element.
|
|
|
|
***********************************************************/
|
|
|
|
|
|
|
|
static void CheckApplet( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Bool HasAlt = no;
|
|
|
|
Bool HasDescription = no;
|
|
|
|
|
|
|
|
AttVal* av;
|
|
|
|
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Checks for attributes within the APPLET element */
|
|
|
|
for (av = node->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
Checks for valid ALT attribute.
|
|
|
|
The length of the alt text must be > 4 characters in length
|
|
|
|
but must be < 150 characters long.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ( attrIsALT(av) )
|
|
|
|
{
|
|
|
|
if (av->value != NULL)
|
|
|
|
{
|
|
|
|
HasAlt = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HasAlt == no)
|
|
|
|
{
|
|
|
|
/* Must have alternate text representation for that element */
|
|
|
|
if (node->content != NULL)
|
|
|
|
{
|
|
|
|
ctmbstr word = NULL;
|
|
|
|
|
|
|
|
if ( node->content->tag == NULL )
|
|
|
|
word = textFromOneNode( doc, node->content);
|
|
|
|
|
|
|
|
if ( node->content->content != NULL &&
|
|
|
|
node->content->content->tag == NULL )
|
|
|
|
{
|
|
|
|
word = textFromOneNode( doc, node->content->content);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( word != NULL && !IsWhitespace(word) )
|
|
|
|
HasDescription = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !HasDescription && !HasAlt )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, APPLET_MISSING_ALT );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
* CheckObject
|
|
|
|
*
|
|
|
|
* Checks to verify whether the OBJECT element contains
|
|
|
|
* 'ALT' text, and to see that the sound file selected is
|
|
|
|
* of a valid sound file type. OBJECT must have an alternate text
|
|
|
|
* representation.
|
|
|
|
*******************************************************************/
|
|
|
|
|
|
|
|
static void CheckObject( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Bool HasAlt = no;
|
|
|
|
Bool HasDescription = no;
|
|
|
|
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
if ( node->content != NULL)
|
|
|
|
{
|
|
|
|
if ( node->content->type != TextNode )
|
|
|
|
{
|
|
|
|
Node* tnode = node->content;
|
|
|
|
AttVal* av;
|
|
|
|
|
|
|
|
for ( av=tnode->attributes; av; av = av->next )
|
|
|
|
{
|
|
|
|
if ( attrIsALT(av) )
|
|
|
|
{
|
|
|
|
HasAlt = yes;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Must have alternate text representation for that element */
|
|
|
|
if ( !HasAlt )
|
|
|
|
{
|
|
|
|
ctmbstr word = NULL;
|
|
|
|
|
|
|
|
if ( TY_(nodeIsText)(node->content) )
|
|
|
|
word = textFromOneNode( doc, node->content );
|
|
|
|
|
|
|
|
if ( word == NULL &&
|
|
|
|
TY_(nodeIsText)(node->content->content) )
|
|
|
|
{
|
|
|
|
word = textFromOneNode( doc, node->content->content );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( word != NULL && !IsWhitespace(word) )
|
|
|
|
HasDescription = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !HasAlt && !HasDescription )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, OBJECT_MISSING_ALT );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***************************************************************
|
|
|
|
* CheckMissingStyleSheets
|
|
|
|
*
|
|
|
|
* Ensures that stylesheets are used to control the presentation.
|
|
|
|
***************************************************************/
|
|
|
|
|
|
|
|
static Bool CheckMissingStyleSheets( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
AttVal* av;
|
|
|
|
Node* content;
|
|
|
|
Bool sspresent = no;
|
|
|
|
|
|
|
|
for ( content = node->content;
|
|
|
|
!sspresent && content != NULL;
|
|
|
|
content = content->next )
|
|
|
|
{
|
|
|
|
sspresent = ( nodeIsLINK(content) ||
|
|
|
|
nodeIsSTYLE(content) ||
|
|
|
|
nodeIsFONT(content) ||
|
|
|
|
nodeIsBASEFONT(content) );
|
|
|
|
|
|
|
|
for ( av = content->attributes;
|
|
|
|
!sspresent && av != NULL;
|
|
|
|
av = av->next )
|
|
|
|
{
|
|
|
|
sspresent = ( attrIsSTYLE(av) || attrIsTEXT(av) ||
|
|
|
|
attrIsVLINK(av) || attrIsALINK(av) ||
|
|
|
|
attrIsLINK(av) );
|
|
|
|
|
|
|
|
if ( !sspresent && attrIsREL(av) )
|
|
|
|
{
|
|
|
|
sspresent = AttrValueIs(av, "stylesheet");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! sspresent )
|
|
|
|
sspresent = CheckMissingStyleSheets( doc, content );
|
|
|
|
}
|
|
|
|
return sspresent;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
* CheckFrame
|
|
|
|
*
|
|
|
|
* Checks if the URL is valid and to check if a 'LONGDESC' is needed
|
|
|
|
* within the FRAME element. If a 'LONGDESC' is needed, the value must
|
|
|
|
* be valid. The URL must end with the file extension, htm, or html.
|
|
|
|
* Also, checks to ensure that the 'SRC' and 'TITLE' values are valid.
|
|
|
|
*******************************************************************/
|
|
|
|
|
|
|
|
static void CheckFrame( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Bool HasTitle = no;
|
|
|
|
AttVal* av;
|
|
|
|
|
|
|
|
doc->access.numFrames++;
|
|
|
|
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Checks for attributes within the FRAME element */
|
|
|
|
for (av = node->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
/* Checks if 'LONGDESC' value is valid only if present */
|
|
|
|
if ( attrIsLONGDESC(av) )
|
|
|
|
{
|
|
|
|
if ( hasValue(av) && TY_(tmbstrlen)(av->value) > 1 )
|
|
|
|
{
|
|
|
|
doc->access.HasCheckedLongDesc++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks for valid 'SRC' value within the frame element */
|
|
|
|
else if ( attrIsSRC(av) )
|
|
|
|
{
|
|
|
|
if ( hasValue(av) && !IsValidSrcExtension(av->value) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, FRAME_SRC_INVALID );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks for valid 'TITLE' value within frame element */
|
|
|
|
else if ( attrIsTITLE(av) )
|
|
|
|
{
|
|
|
|
if ( hasValue(av) )
|
|
|
|
HasTitle = yes;
|
|
|
|
|
|
|
|
if ( !HasTitle )
|
|
|
|
{
|
|
|
|
if ( av->value == NULL || TY_(tmbstrlen)(av->value) == 0 )
|
|
|
|
{
|
|
|
|
HasTitle = yes;
|
|
|
|
TY_(ReportAccessError)( doc, node, FRAME_TITLE_INVALID_NULL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( IsWhitespace(av->value) && TY_(tmbstrlen)(av->value) > 0 )
|
|
|
|
{
|
|
|
|
HasTitle = yes;
|
|
|
|
TY_(ReportAccessError)( doc, node, FRAME_TITLE_INVALID_SPACES );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !HasTitle )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, FRAME_MISSING_TITLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( doc->access.numFrames==3 && doc->access.HasCheckedLongDesc<3 )
|
|
|
|
{
|
|
|
|
doc->access.numFrames = 0;
|
|
|
|
TY_(ReportAccessWarning)( doc, node, FRAME_MISSING_LONGDESC );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************
|
|
|
|
* CheckIFrame
|
|
|
|
*
|
|
|
|
* Checks if 'SRC' value is valid. Must end in appropriate
|
|
|
|
* file extension.
|
|
|
|
****************************************************************/
|
|
|
|
|
|
|
|
static void CheckIFrame( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Checks for valid 'SRC' value within the IFRAME element */
|
|
|
|
AttVal* av = attrGetSRC( node );
|
|
|
|
if ( hasValue(av) )
|
|
|
|
{
|
|
|
|
if ( !IsValidSrcExtension(av->value) )
|
|
|
|
TY_(ReportAccessError)( doc, node, FRAME_SRC_INVALID );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
|
|
* CheckAnchorAccess
|
|
|
|
*
|
|
|
|
* Checks that the sound file is valid, and to ensure that
|
|
|
|
* text transcript is present describing the 'HREF' within the
|
|
|
|
* ANCHOR element. Also checks to see ensure that the 'TARGET' attribute
|
|
|
|
* (if it exists) is not NULL and does not contain '_new' or '_blank'.
|
|
|
|
**********************************************************************/
|
|
|
|
|
|
|
|
static void CheckAnchorAccess( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
AttVal* av;
|
|
|
|
Bool HasDescription = no;
|
|
|
|
Bool HasTriggeredLink = no;
|
|
|
|
|
|
|
|
/* Checks for attributes within the ANCHOR element */
|
|
|
|
for ( av = node->attributes; av != NULL; av = av->next )
|
|
|
|
{
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Must be of valid sound file type */
|
|
|
|
if ( attrIsHREF(av) )
|
|
|
|
{
|
|
|
|
if ( hasValue(av) )
|
|
|
|
{
|
|
|
|
tmbchar ext[ 20 ];
|
|
|
|
GetFileExtension (av->value, ext, sizeof(ext) );
|
|
|
|
|
|
|
|
/* Checks to see if multimedia is used */
|
|
|
|
if ( IsValidMediaExtension(av->value) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, MULTIMEDIA_REQUIRES_TEXT );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Checks for validity of sound file, and checks to see if
|
|
|
|
the file is described within the document, or by a link
|
|
|
|
that is present which gives the description.
|
|
|
|
*/
|
|
|
|
if ( TY_(tmbstrlen)(ext) < 6 && TY_(tmbstrlen)(ext) > 0 )
|
|
|
|
{
|
|
|
|
int errcode = IsSoundFile( av->value );
|
|
|
|
if ( errcode )
|
|
|
|
{
|
|
|
|
if (node->next != NULL)
|
|
|
|
{
|
|
|
|
if (node->next->tag == NULL)
|
|
|
|
{
|
|
|
|
ctmbstr word = textFromOneNode( doc, node->next);
|
|
|
|
|
|
|
|
/* Must contain at least one letter in the text */
|
|
|
|
if (IsWhitespace (word) == no)
|
|
|
|
{
|
|
|
|
HasDescription = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Must contain text description of sound file */
|
|
|
|
if ( !HasDescription )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, errcode );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Checks 'TARGET' attribute for validity if it exists */
|
|
|
|
if ( attrIsTARGET(av) )
|
|
|
|
{
|
|
|
|
if (AttrValueIs(av, "_new"))
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, NEW_WINDOWS_REQUIRE_WARNING_NEW);
|
|
|
|
}
|
|
|
|
else if (AttrValueIs(av, "_blank"))
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, NEW_WINDOWS_REQUIRE_WARNING_BLANK);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
if ((node->content != NULL)&&
|
|
|
|
(node->content->tag == NULL))
|
|
|
|
{
|
|
|
|
ctmbstr word = textFromOneNode( doc, node->content);
|
|
|
|
|
|
|
|
if ((word != NULL)&&
|
|
|
|
(IsWhitespace (word) == no))
|
|
|
|
{
|
|
|
|
if (TY_(tmbstrcmp) (word, "more") == 0)
|
|
|
|
{
|
|
|
|
HasTriggeredLink = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (TY_(tmbstrcmp) (word, "click here") == 0)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, LINK_TEXT_NOT_MEANINGFUL_CLICK_HERE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HasTriggeredLink == no)
|
|
|
|
{
|
|
|
|
if (TY_(tmbstrlen)(word) < 6)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, LINK_TEXT_NOT_MEANINGFUL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (TY_(tmbstrlen)(word) > 60)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, LINK_TEXT_TOO_LONG);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node->content == NULL)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, LINK_TEXT_MISSING);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/************************************************************
|
|
|
|
* CheckArea
|
|
|
|
*
|
|
|
|
* Checks attributes within the AREA element to
|
|
|
|
* determine if the 'ALT' text and 'HREF' values are valid.
|
|
|
|
* Also checks to see ensure that the 'TARGET' attribute
|
|
|
|
* (if it exists) is not NULL and does not contain '_new'
|
|
|
|
* or '_blank'.
|
|
|
|
************************************************************/
|
|
|
|
|
|
|
|
static void CheckArea( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Bool HasAlt = no;
|
|
|
|
AttVal* av;
|
|
|
|
|
|
|
|
/* Checks all attributes within the AREA element */
|
|
|
|
for (av = node->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
Checks for valid ALT attribute.
|
|
|
|
The length of the alt text must be > 4 characters long
|
|
|
|
but must be less than 150 characters long.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ( attrIsALT(av) )
|
|
|
|
{
|
|
|
|
/* The check for validity */
|
|
|
|
if (av->value != NULL)
|
|
|
|
{
|
|
|
|
HasAlt = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
if ( attrIsTARGET(av) )
|
|
|
|
{
|
|
|
|
if (AttrValueIs(av, "_new"))
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, NEW_WINDOWS_REQUIRE_WARNING_NEW);
|
|
|
|
}
|
|
|
|
else if (AttrValueIs(av, "_blank"))
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, NEW_WINDOWS_REQUIRE_WARNING_BLANK);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* AREA must contain alt text */
|
|
|
|
if (HasAlt == no)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, AREA_MISSING_ALT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***************************************************
|
|
|
|
* CheckScript
|
|
|
|
*
|
|
|
|
* Checks the SCRIPT element to ensure that a
|
|
|
|
* NOSCRIPT section follows the SCRIPT.
|
|
|
|
***************************************************/
|
|
|
|
|
|
|
|
static void CheckScriptAcc( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* NOSCRIPT element must appear immediately following SCRIPT element */
|
|
|
|
if ( node->next == NULL || !nodeIsNOSCRIPT(node->next) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, SCRIPT_MISSING_NOSCRIPT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**********************************************************
|
|
|
|
* CheckRows
|
|
|
|
*
|
|
|
|
* Check to see that each table has a row of headers if
|
|
|
|
* a column of columns doesn't exist.
|
|
|
|
**********************************************************/
|
|
|
|
|
|
|
|
static void CheckRows( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
int numTR = 0;
|
|
|
|
int numValidTH = 0;
|
|
|
|
|
|
|
|
doc->access.CheckedHeaders++;
|
|
|
|
|
|
|
|
for (; node != NULL; node = node->next )
|
|
|
|
{
|
|
|
|
numTR++;
|
|
|
|
if ( nodeIsTH(node->content) )
|
|
|
|
{
|
|
|
|
doc->access.HasTH = yes;
|
|
|
|
if ( TY_(nodeIsText)(node->content->content) )
|
|
|
|
{
|
|
|
|
ctmbstr word = textFromOneNode( doc, node->content->content);
|
|
|
|
if ( !IsWhitespace(word) )
|
|
|
|
numValidTH++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (numTR == numValidTH)
|
|
|
|
doc->access.HasValidRowHeaders = yes;
|
|
|
|
|
|
|
|
if ( numTR >= 2 &&
|
|
|
|
numTR > numValidTH &&
|
|
|
|
numValidTH >= 2 &&
|
|
|
|
doc->access.HasTH == yes )
|
|
|
|
doc->access.HasInvalidRowHeader = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**********************************************************
|
|
|
|
* CheckColumns
|
|
|
|
*
|
|
|
|
* Check to see that each table has a column of headers if
|
|
|
|
* a row of columns doesn't exist.
|
|
|
|
**********************************************************/
|
|
|
|
|
|
|
|
static void CheckColumns( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Node* tnode;
|
|
|
|
int numTH = 0;
|
|
|
|
Bool isMissingHeader = no;
|
|
|
|
|
|
|
|
doc->access.CheckedHeaders++;
|
|
|
|
|
|
|
|
/* Table must have row of headers if headers for columns don't exist */
|
|
|
|
if ( nodeIsTH(node->content) )
|
|
|
|
{
|
|
|
|
doc->access.HasTH = yes;
|
|
|
|
|
|
|
|
for ( tnode = node->content; tnode; tnode = tnode->next )
|
|
|
|
{
|
|
|
|
if ( nodeIsTH(tnode) )
|
|
|
|
{
|
|
|
|
if ( TY_(nodeIsText)(tnode->content) )
|
|
|
|
{
|
|
|
|
ctmbstr word = textFromOneNode( doc, tnode->content);
|
|
|
|
if ( !IsWhitespace(word) )
|
|
|
|
numTH++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
isMissingHeader = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !isMissingHeader && numTH > 0 )
|
|
|
|
doc->access.HasValidColumnHeaders = yes;
|
|
|
|
|
|
|
|
if ( isMissingHeader && numTH >= 2 )
|
|
|
|
doc->access.HasInvalidColumnHeader = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*****************************************************
|
|
|
|
* CheckTH
|
|
|
|
*
|
|
|
|
* Checks to see if the header provided for a table
|
|
|
|
* requires an abbreviation. (only required if the
|
|
|
|
* length of the header is greater than 15 characters)
|
|
|
|
*****************************************************/
|
|
|
|
|
|
|
|
static void CheckTH( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Bool HasAbbr = no;
|
|
|
|
ctmbstr word = NULL;
|
|
|
|
AttVal* av;
|
|
|
|
|
|
|
|
if (Level3_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Checks TH element for 'ABBR' attribute */
|
|
|
|
for (av = node->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
if ( attrIsABBR(av) )
|
|
|
|
{
|
|
|
|
/* Value must not be NULL and must be less than 15 characters */
|
|
|
|
if ((av->value != NULL)&&
|
|
|
|
(IsWhitespace (av->value) == no))
|
|
|
|
{
|
|
|
|
HasAbbr = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((av->value == NULL)||
|
|
|
|
(TY_(tmbstrlen)(av->value) == 0))
|
|
|
|
{
|
|
|
|
HasAbbr = yes;
|
|
|
|
TY_(ReportAccessWarning)( doc, node, TABLE_MAY_REQUIRE_HEADER_ABBR_NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((IsWhitespace (av->value) == yes)&&
|
|
|
|
(TY_(tmbstrlen)(av->value) > 0))
|
|
|
|
{
|
|
|
|
HasAbbr = yes;
|
|
|
|
TY_(ReportAccessWarning)( doc, node, TABLE_MAY_REQUIRE_HEADER_ABBR_SPACES);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the header is greater than 15 characters, an abbreviation is needed */
|
|
|
|
word = textFromOneNode( doc, node->content);
|
|
|
|
|
|
|
|
if ((word != NULL)&&
|
|
|
|
(IsWhitespace (word) == no))
|
|
|
|
{
|
|
|
|
/* Must have 'ABBR' attribute if header is > 15 characters */
|
|
|
|
if ((TY_(tmbstrlen)(word) > 15)&&
|
|
|
|
(HasAbbr == no))
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, TABLE_MAY_REQUIRE_HEADER_ABBR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*****************************************************************
|
|
|
|
* CheckMultiHeaders
|
|
|
|
*
|
|
|
|
* Layout tables should make sense when linearized.
|
|
|
|
* TABLE must contain at least one TH element.
|
|
|
|
* This technique applies only to tables used for layout purposes,
|
|
|
|
* not to data tables. Checks for column of multiple headers.
|
|
|
|
*****************************************************************/
|
|
|
|
|
|
|
|
static void CheckMultiHeaders( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Node* TNode;
|
|
|
|
Node* temp;
|
|
|
|
|
|
|
|
Bool validColSpanRows = yes;
|
|
|
|
Bool validColSpanColumns = yes;
|
|
|
|
|
|
|
|
int flag = 0;
|
|
|
|
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
if (node->content != NULL)
|
|
|
|
{
|
|
|
|
TNode = node->content;
|
|
|
|
|
|
|
|
/*
|
|
|
|
Checks for column of multiple headers found
|
|
|
|
within a data table.
|
|
|
|
*/
|
|
|
|
while (TNode != NULL)
|
|
|
|
{
|
|
|
|
if ( nodeIsTR(TNode) )
|
|
|
|
{
|
2015-03-04 18:38:59 +00:00
|
|
|
flag = 0; /* Issue #168 - access test 5-2-1-2 */
|
2011-11-17 02:44:16 +00:00
|
|
|
if (TNode->content != NULL)
|
|
|
|
{
|
|
|
|
temp = TNode->content;
|
|
|
|
|
|
|
|
/* The number of TH elements found within TR element */
|
|
|
|
if (flag == 0)
|
|
|
|
{
|
|
|
|
while (temp != NULL)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
Must contain at least one TH element
|
|
|
|
within in the TR element
|
|
|
|
*/
|
|
|
|
if ( nodeIsTH(temp) )
|
|
|
|
{
|
|
|
|
AttVal* av;
|
|
|
|
for (av = temp->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
if ( attrIsCOLSPAN(av)
|
|
|
|
&& (atoi(av->value) > 1) )
|
|
|
|
validColSpanColumns = no;
|
|
|
|
|
|
|
|
if ( attrIsROWSPAN(av)
|
|
|
|
&& (atoi(av->value) > 1) )
|
|
|
|
validColSpanRows = no;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
temp = temp->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
flag = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TNode = TNode->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Displays HTML 4 Table Algorithm when multiple column of headers used */
|
|
|
|
if (validColSpanRows == no)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, DATA_TABLE_REQUIRE_MARKUP_ROW_HEADERS );
|
|
|
|
TY_(DisplayHTMLTableAlgorithm)( doc );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (validColSpanColumns == no)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, DATA_TABLE_REQUIRE_MARKUP_COLUMN_HEADERS );
|
|
|
|
TY_(DisplayHTMLTableAlgorithm)( doc );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/****************************************************
|
|
|
|
* CheckTable
|
|
|
|
*
|
|
|
|
* Checks the TABLE element to ensure that the
|
|
|
|
* table is not missing any headers. Must have either
|
|
|
|
* a row or column of headers.
|
|
|
|
****************************************************/
|
|
|
|
|
|
|
|
static void CheckTable( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Node* TNode;
|
|
|
|
Node* temp;
|
|
|
|
|
|
|
|
tmbstr word = NULL;
|
|
|
|
|
|
|
|
int numTR = 0;
|
|
|
|
|
|
|
|
Bool HasSummary = no;
|
|
|
|
Bool HasCaption = no;
|
|
|
|
|
|
|
|
if (Level3_Enabled( doc ))
|
|
|
|
{
|
|
|
|
AttVal* av;
|
|
|
|
/* Table must have a 'SUMMARY' describing the purpose of the table */
|
|
|
|
for (av = node->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
if ( attrIsSUMMARY(av) )
|
|
|
|
{
|
|
|
|
if ( hasValue(av) )
|
|
|
|
{
|
|
|
|
HasSummary = yes;
|
|
|
|
|
|
|
|
if (AttrContains(av, "summary") &&
|
|
|
|
AttrContains(av, "table"))
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, TABLE_SUMMARY_INVALID_PLACEHOLDER );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( av->value == NULL || TY_(tmbstrlen)(av->value) == 0 )
|
|
|
|
{
|
|
|
|
HasSummary = yes;
|
|
|
|
TY_(ReportAccessError)( doc, node, TABLE_SUMMARY_INVALID_NULL );
|
|
|
|
}
|
|
|
|
else if ( IsWhitespace(av->value) && TY_(tmbstrlen)(av->value) > 0 )
|
|
|
|
{
|
|
|
|
HasSummary = yes;
|
|
|
|
TY_(ReportAccessError)( doc, node, TABLE_SUMMARY_INVALID_SPACES );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TABLE must have content. */
|
|
|
|
if (node->content == NULL)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, DATA_TABLE_MISSING_HEADERS);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Checks for multiple headers */
|
|
|
|
CheckMultiHeaders( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Table must have a CAPTION describing the purpose of the table */
|
|
|
|
if ( nodeIsCAPTION(node->content) )
|
|
|
|
{
|
|
|
|
TNode = node->content;
|
|
|
|
|
|
|
|
if (TNode->content && TNode->content->tag == NULL)
|
|
|
|
{
|
|
|
|
word = getTextNodeClear( doc, TNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !IsWhitespace(word) )
|
|
|
|
{
|
|
|
|
HasCaption = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HasCaption == no)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, TABLE_MISSING_CAPTION);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (node->content != NULL)
|
|
|
|
{
|
|
|
|
if ( nodeIsCAPTION(node->content) && nodeIsTR(node->content->next) )
|
|
|
|
{
|
|
|
|
CheckColumns( doc, node->content->next );
|
|
|
|
}
|
|
|
|
else if ( nodeIsTR(node->content) )
|
|
|
|
{
|
|
|
|
CheckColumns( doc, node->content );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! doc->access.HasValidColumnHeaders )
|
|
|
|
{
|
|
|
|
if (node->content != NULL)
|
|
|
|
{
|
|
|
|
if ( nodeIsCAPTION(node->content) && nodeIsTR(node->content->next) )
|
|
|
|
{
|
|
|
|
CheckRows( doc, node->content->next);
|
|
|
|
}
|
|
|
|
else if ( nodeIsTR(node->content) )
|
|
|
|
{
|
|
|
|
CheckRows( doc, node->content);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (Level3_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Suppress warning for missing 'SUMMARY for HTML 2.0 and HTML 3.2 */
|
|
|
|
if (HasSummary == no)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, TABLE_MISSING_SUMMARY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
if (node->content != NULL)
|
|
|
|
{
|
|
|
|
temp = node->content;
|
|
|
|
|
|
|
|
while (temp != NULL)
|
|
|
|
{
|
|
|
|
if ( nodeIsTR(temp) )
|
|
|
|
{
|
|
|
|
numTR++;
|
|
|
|
}
|
|
|
|
|
|
|
|
temp = temp->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (numTR == 1)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, LAYOUT_TABLES_LINEARIZE_PROPERLY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( doc->access.HasTH )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, LAYOUT_TABLE_INVALID_MARKUP);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
if ( doc->access.CheckedHeaders == 2 )
|
|
|
|
{
|
|
|
|
if ( !doc->access.HasValidRowHeaders &&
|
|
|
|
!doc->access.HasValidColumnHeaders &&
|
|
|
|
!doc->access.HasInvalidRowHeader &&
|
|
|
|
!doc->access.HasInvalidColumnHeader )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, DATA_TABLE_MISSING_HEADERS);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !doc->access.HasValidRowHeaders &&
|
|
|
|
doc->access.HasInvalidRowHeader )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, DATA_TABLE_MISSING_HEADERS_ROW);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !doc->access.HasValidColumnHeaders &&
|
|
|
|
doc->access.HasInvalidColumnHeader )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, DATA_TABLE_MISSING_HEADERS_COLUMN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***************************************************
|
|
|
|
* CheckASCII
|
|
|
|
*
|
|
|
|
* Checks for valid text equivalents for XMP and PRE
|
|
|
|
* elements for ASCII art. Ensures that there is
|
|
|
|
* a skip over link to skip multi-lined ASCII art.
|
|
|
|
***************************************************/
|
|
|
|
|
|
|
|
static void CheckASCII( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Node* temp1;
|
|
|
|
Node* temp2;
|
|
|
|
|
|
|
|
tmbstr skipOver = NULL;
|
|
|
|
Bool IsAscii = no;
|
|
|
|
int HasSkipOverLink = 0;
|
|
|
|
|
|
|
|
uint i, x;
|
|
|
|
int newLines = -1;
|
|
|
|
tmbchar compareLetter;
|
|
|
|
int matchingCount = 0;
|
|
|
|
AttVal* av;
|
|
|
|
|
|
|
|
if (Level1_Enabled( doc ) && node->content)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
Checks the text within the PRE and XMP tags to see if ascii
|
|
|
|
art is present
|
|
|
|
*/
|
|
|
|
for (i = node->content->start + 1; i < node->content->end; i++)
|
|
|
|
{
|
|
|
|
matchingCount = 0;
|
|
|
|
|
|
|
|
/* Counts the number of lines of text */
|
|
|
|
if (doc->lexer->lexbuf[i] == '\n')
|
|
|
|
{
|
|
|
|
newLines++;
|
|
|
|
}
|
|
|
|
|
|
|
|
compareLetter = doc->lexer->lexbuf[i];
|
|
|
|
|
|
|
|
/* Counts consecutive character matches */
|
|
|
|
for (x = i; x < i + 5; x++)
|
|
|
|
{
|
|
|
|
if (doc->lexer->lexbuf[x] == compareLetter)
|
|
|
|
{
|
|
|
|
matchingCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Must have at least 5 consecutive character matches */
|
|
|
|
if (matchingCount >= 5)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Must have more than 6 lines of text OR 5 or more consecutive
|
|
|
|
letters that are the same for there to be ascii art
|
|
|
|
*/
|
|
|
|
if (newLines >= 6 || matchingCount >= 5)
|
|
|
|
{
|
|
|
|
IsAscii = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks for skip over link if ASCII art is present */
|
|
|
|
if (IsAscii == yes)
|
|
|
|
{
|
|
|
|
if (node->prev != NULL && node->prev->prev != NULL)
|
|
|
|
{
|
|
|
|
temp1 = node->prev->prev;
|
|
|
|
|
|
|
|
/* Checks for 'HREF' attribute */
|
|
|
|
for (av = temp1->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
if ( attrIsHREF(av) && hasValue(av) )
|
|
|
|
{
|
|
|
|
skipOver = av->value;
|
|
|
|
HasSkipOverLink++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
Checks for A element following PRE to ensure proper skipover link
|
|
|
|
only if there is an A element preceding PRE.
|
|
|
|
*/
|
|
|
|
if (HasSkipOverLink == 1)
|
|
|
|
{
|
|
|
|
if ( nodeIsA(node->next) )
|
|
|
|
{
|
|
|
|
temp2 = node->next;
|
|
|
|
|
|
|
|
/* Checks for 'NAME' attribute */
|
|
|
|
for (av = temp2->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
if ( attrIsNAME(av) && hasValue(av) )
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
Value within the 'HREF' attribute must be the same
|
|
|
|
as the value within the 'NAME' attribute for valid
|
|
|
|
skipover.
|
|
|
|
*/
|
|
|
|
if ( strstr(skipOver, av->value) != NULL )
|
|
|
|
{
|
|
|
|
HasSkipOverLink++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IsAscii == yes)
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, ASCII_REQUIRES_DESCRIPTION);
|
|
|
|
if (Level3_Enabled( doc ) && (HasSkipOverLink < 2))
|
|
|
|
TY_(ReportAccessError)( doc, node, SKIPOVER_ASCII_ART);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************
|
|
|
|
* CheckFormControls
|
|
|
|
*
|
|
|
|
* <form> must have valid 'FOR' attribute, and <label> must
|
|
|
|
* have valid 'ID' attribute for valid form control.
|
|
|
|
***********************************************************/
|
|
|
|
|
|
|
|
static void CheckFormControls( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if ( !doc->access.HasValidFor &&
|
|
|
|
doc->access.HasValidId )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, ASSOCIATE_LABELS_EXPLICITLY_FOR);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !doc->access.HasValidId &&
|
|
|
|
doc->access.HasValidFor )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, ASSOCIATE_LABELS_EXPLICITLY_ID);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !doc->access.HasValidId &&
|
|
|
|
!doc->access.HasValidFor )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, ASSOCIATE_LABELS_EXPLICITLY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/************************************************************
|
|
|
|
* CheckLabel
|
|
|
|
*
|
|
|
|
* Check for valid 'FOR' attribute within the LABEL element
|
|
|
|
************************************************************/
|
|
|
|
|
|
|
|
static void CheckLabel( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Checks for valid 'FOR' attribute */
|
|
|
|
AttVal* av = attrGetFOR( node );
|
|
|
|
if ( hasValue(av) )
|
|
|
|
doc->access.HasValidFor = yes;
|
|
|
|
|
|
|
|
if ( ++doc->access.ForID == 2 )
|
|
|
|
{
|
|
|
|
doc->access.ForID = 0;
|
|
|
|
CheckFormControls( doc, node );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/************************************************************
|
|
|
|
* CheckInputLabel
|
|
|
|
*
|
|
|
|
* Checks for valid 'ID' attribute within the INPUT element.
|
|
|
|
* Checks to see if there is a LABEL directly before
|
|
|
|
* or after the INPUT element determined by the 'TYPE'.
|
|
|
|
* Each INPUT element must have a LABEL describing the form.
|
|
|
|
************************************************************/
|
|
|
|
|
|
|
|
static void CheckInputLabel( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
AttVal* av;
|
|
|
|
|
|
|
|
/* Checks attributes within the INPUT element */
|
|
|
|
for (av = node->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
/* Must have valid 'ID' value */
|
|
|
|
if ( attrIsID(av) && hasValue(av) )
|
|
|
|
doc->access.HasValidId = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ++doc->access.ForID == 2 )
|
|
|
|
{
|
|
|
|
doc->access.ForID = 0;
|
|
|
|
CheckFormControls( doc, node );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***************************************************************
|
|
|
|
* CheckInputAttributes
|
|
|
|
*
|
|
|
|
* INPUT element must have a valid 'ALT' attribute if the
|
|
|
|
* 'VALUE' attribute is present.
|
|
|
|
***************************************************************/
|
|
|
|
|
|
|
|
static void CheckInputAttributes( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Bool HasAlt = no;
|
|
|
|
Bool MustHaveAlt = no;
|
|
|
|
AttVal* av;
|
|
|
|
|
|
|
|
/* Checks attributes within the INPUT element */
|
|
|
|
for (av = node->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
/* 'VALUE' must be found if the 'TYPE' is 'text' or 'checkbox' */
|
|
|
|
if ( attrIsTYPE(av) && hasValue(av) )
|
|
|
|
{
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
if (AttrValueIs(av, "image"))
|
|
|
|
{
|
|
|
|
MustHaveAlt = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( attrIsALT(av) && hasValue(av) )
|
|
|
|
{
|
|
|
|
HasAlt = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( MustHaveAlt && !HasAlt )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, IMG_BUTTON_MISSING_ALT );
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***************************************************************
|
|
|
|
* CheckFrameSet
|
|
|
|
*
|
|
|
|
* Frameset must have valid NOFRAME section. Must contain some
|
|
|
|
* text but must not contain information telling user to update
|
|
|
|
* browsers,
|
|
|
|
***************************************************************/
|
|
|
|
|
|
|
|
static void CheckFrameSet( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Node* temp;
|
|
|
|
Bool HasNoFrames = no;
|
|
|
|
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
if ( doc->badAccess & BA_INVALID_LINK_NOFRAMES )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, NOFRAMES_INVALID_LINK);
|
|
|
|
doc->badAccess &= ~BA_INVALID_LINK_NOFRAMES; /* emit only once */
|
|
|
|
}
|
|
|
|
for ( temp = node->content; temp != NULL ; temp = temp->next )
|
|
|
|
{
|
|
|
|
if ( nodeIsNOFRAMES(temp) )
|
|
|
|
{
|
|
|
|
HasNoFrames = yes;
|
|
|
|
|
|
|
|
if ( temp->content && nodeIsP(temp->content->content) )
|
|
|
|
{
|
|
|
|
Node* para = temp->content->content;
|
|
|
|
if ( TY_(nodeIsText)(para->content) )
|
|
|
|
{
|
|
|
|
ctmbstr word = textFromOneNode( doc, para->content );
|
|
|
|
if ( word && strstr(word, "browser") != NULL )
|
|
|
|
TY_(ReportAccessError)( doc, para, NOFRAMES_INVALID_CONTENT );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (temp->content == NULL)
|
|
|
|
TY_(ReportAccessError)( doc, temp, NOFRAMES_INVALID_NO_VALUE);
|
|
|
|
else if ( temp->content &&
|
|
|
|
IsWhitespace(textFromOneNode(doc, temp->content)) )
|
|
|
|
TY_(ReportAccessError)( doc, temp, NOFRAMES_INVALID_NO_VALUE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HasNoFrames == no)
|
|
|
|
TY_(ReportAccessError)( doc, node, FRAME_MISSING_NOFRAMES);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************
|
|
|
|
* CheckHeaderNesting
|
|
|
|
*
|
|
|
|
* Checks for heading increases and decreases. Headings must
|
|
|
|
* not increase by more than one header level, but may
|
|
|
|
* decrease at from any level to any level. Text within
|
|
|
|
* headers must not be more than 20 words in length.
|
|
|
|
***********************************************************/
|
|
|
|
|
|
|
|
static void CheckHeaderNesting( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Node* temp;
|
|
|
|
uint i;
|
|
|
|
int numWords = 1;
|
|
|
|
|
|
|
|
Bool IsValidIncrease = no;
|
|
|
|
Bool NeedsDescription = no;
|
|
|
|
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
Text within header element cannot contain more than 20 words without
|
|
|
|
a separate description
|
|
|
|
*/
|
|
|
|
if (node->content != NULL && node->content->tag == NULL)
|
|
|
|
{
|
|
|
|
ctmbstr word = textFromOneNode( doc, node->content);
|
|
|
|
|
|
|
|
for (i = 0; i < TY_(tmbstrlen)(word); i++)
|
|
|
|
{
|
|
|
|
if (word[i] == ' ')
|
|
|
|
{
|
|
|
|
numWords++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (numWords > 20)
|
|
|
|
{
|
|
|
|
NeedsDescription = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Header following must be same level or same plus 1 for
|
|
|
|
** valid heading increase size. E.g. H1 -> H1, H2. H3 -> H3, H4
|
|
|
|
*/
|
|
|
|
if ( TY_(nodeIsHeader)(node) )
|
|
|
|
{
|
|
|
|
uint level = TY_(nodeHeaderLevel)( node );
|
|
|
|
IsValidIncrease = yes;
|
|
|
|
|
|
|
|
for ( temp = node->next; temp != NULL; temp = temp->next )
|
|
|
|
{
|
|
|
|
uint nested = TY_(nodeHeaderLevel)( temp );
|
|
|
|
if ( nested >= level )
|
|
|
|
{
|
|
|
|
IsValidIncrease = ( nested <= level + 1 );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !IsValidIncrease )
|
|
|
|
TY_(ReportAccessWarning)( doc, node, HEADERS_IMPROPERLY_NESTED );
|
|
|
|
|
|
|
|
if ( NeedsDescription )
|
|
|
|
TY_(ReportAccessWarning)( doc, node, HEADER_USED_FORMAT_TEXT );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************
|
|
|
|
* CheckParagraphHeader
|
|
|
|
*
|
|
|
|
* Checks to ensure that P elements are not headings. Must be
|
|
|
|
* greater than 10 words in length, and they must not be in bold,
|
|
|
|
* or italics, or underlined, etc.
|
|
|
|
*************************************************************/
|
|
|
|
|
|
|
|
static void CheckParagraphHeader( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Bool IsNotHeader = no;
|
|
|
|
Node* temp;
|
|
|
|
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Cannot contain text formatting elements */
|
|
|
|
if (node->content != NULL)
|
|
|
|
{
|
|
|
|
if (node->content->tag != NULL)
|
|
|
|
{
|
|
|
|
temp = node->content;
|
|
|
|
|
|
|
|
while (temp != NULL)
|
|
|
|
{
|
|
|
|
if (temp->tag == NULL)
|
|
|
|
{
|
|
|
|
IsNotHeader = yes;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
temp = temp->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !IsNotHeader )
|
|
|
|
{
|
|
|
|
if ( nodeIsSTRONG(node->content) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, POTENTIAL_HEADER_BOLD);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( nodeIsU(node->content) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, POTENTIAL_HEADER_UNDERLINE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( nodeIsEM(node->content) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, POTENTIAL_HEADER_ITALICS);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************
|
|
|
|
* CheckEmbed
|
|
|
|
*
|
|
|
|
* Checks to see if 'SRC' is a multimedia type. Must have
|
|
|
|
* syncronized captions if used.
|
|
|
|
****************************************************************/
|
|
|
|
|
|
|
|
static void CheckEmbed( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
AttVal* av = attrGetSRC( node );
|
|
|
|
if ( hasValue(av) && IsValidMediaExtension(av->value) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, MULTIMEDIA_REQUIRES_TEXT );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************
|
|
|
|
* CheckHTMLAccess
|
|
|
|
*
|
|
|
|
* Checks HTML element for valid 'LANG' attribute. Must be a valid
|
|
|
|
* language. ie. 'fr' or 'en'
|
|
|
|
********************************************************************/
|
|
|
|
|
|
|
|
static void CheckHTMLAccess( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Bool ValidLang = no;
|
|
|
|
|
|
|
|
if (Level3_Enabled( doc ))
|
|
|
|
{
|
|
|
|
AttVal* av = attrGetLANG( node );
|
|
|
|
if ( av )
|
|
|
|
{
|
|
|
|
ValidLang = yes;
|
|
|
|
if ( !hasValue(av) )
|
|
|
|
TY_(ReportAccessError)( doc, node, LANGUAGE_INVALID );
|
|
|
|
}
|
|
|
|
if ( !ValidLang )
|
|
|
|
TY_(ReportAccessError)( doc, node, LANGUAGE_NOT_IDENTIFIED );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/********************************************************
|
|
|
|
* CheckBlink
|
|
|
|
*
|
|
|
|
* Document must not contain the BLINK element.
|
|
|
|
* It is invalid HTML/XHTML.
|
|
|
|
*********************************************************/
|
|
|
|
|
|
|
|
static void CheckBlink( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Checks to see if text is found within the BLINK element. */
|
|
|
|
if ( TY_(nodeIsText)(node->content) )
|
|
|
|
{
|
|
|
|
ctmbstr word = textFromOneNode( doc, node->content );
|
|
|
|
if ( !IsWhitespace(word) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, REMOVE_BLINK_MARQUEE );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/********************************************************
|
|
|
|
* CheckMarquee
|
|
|
|
*
|
|
|
|
* Document must not contain the MARQUEE element.
|
|
|
|
* It is invalid HTML/XHTML.
|
|
|
|
********************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
static void CheckMarquee( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Checks to see if there is text in between the MARQUEE element */
|
|
|
|
if ( TY_(nodeIsText)(node) )
|
|
|
|
{
|
|
|
|
ctmbstr word = textFromOneNode( doc, node->content);
|
|
|
|
if ( !IsWhitespace(word) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, REMOVE_BLINK_MARQUEE );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**********************************************************
|
|
|
|
* CheckLink
|
|
|
|
*
|
|
|
|
* 'REL' attribute within the LINK element must not contain
|
|
|
|
* 'stylesheet'. HTML/XHTML document is unreadable when
|
|
|
|
* style sheets are applied. -- CPR huh?
|
|
|
|
**********************************************************/
|
|
|
|
|
|
|
|
static void CheckLink( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Bool HasRel = no;
|
|
|
|
Bool HasType = no;
|
|
|
|
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
AttVal* av;
|
|
|
|
/* Check for valid 'REL' and 'TYPE' attribute */
|
|
|
|
for (av = node->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
if ( attrIsREL(av) && hasValue(av) )
|
|
|
|
{
|
|
|
|
if (AttrContains(av, "stylesheet"))
|
|
|
|
HasRel = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( attrIsTYPE(av) && hasValue(av) )
|
|
|
|
{
|
|
|
|
HasType = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HasRel && HasType)
|
|
|
|
TY_(ReportAccessWarning)( doc, node, STYLESHEETS_REQUIRE_TESTING_LINK );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************
|
|
|
|
* CheckStyle
|
|
|
|
*
|
|
|
|
* Document must not contain STYLE element. HTML/XHTML
|
|
|
|
* document is unreadable when style sheets are applied.
|
|
|
|
*******************************************************/
|
|
|
|
|
|
|
|
static void CheckStyle( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, STYLESHEETS_REQUIRE_TESTING_STYLE_ELEMENT );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************
|
|
|
|
* DynamicContent
|
|
|
|
*
|
|
|
|
* Verify that equivalents of dynamic content are updated and
|
|
|
|
* available as often as the dynamic content.
|
|
|
|
*************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
static void DynamicContent( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
uint msgcode = 0;
|
|
|
|
if ( nodeIsAPPLET(node) )
|
|
|
|
msgcode = TEXT_EQUIVALENTS_REQUIRE_UPDATING_APPLET;
|
|
|
|
else if ( nodeIsSCRIPT(node) )
|
|
|
|
msgcode = TEXT_EQUIVALENTS_REQUIRE_UPDATING_SCRIPT;
|
|
|
|
else if ( nodeIsOBJECT(node) )
|
|
|
|
msgcode = TEXT_EQUIVALENTS_REQUIRE_UPDATING_OBJECT;
|
|
|
|
|
|
|
|
if ( msgcode )
|
|
|
|
TY_(ReportAccessWarning)( doc, node, msgcode );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************
|
|
|
|
* ProgrammaticObjects
|
|
|
|
*
|
|
|
|
* Verify that the page is usable when programmatic objects
|
|
|
|
* are disabled.
|
|
|
|
*************************************************************/
|
|
|
|
|
|
|
|
static void ProgrammaticObjects( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
int msgcode = 0;
|
|
|
|
if ( nodeIsSCRIPT(node) )
|
|
|
|
msgcode = PROGRAMMATIC_OBJECTS_REQUIRE_TESTING_SCRIPT;
|
|
|
|
else if ( nodeIsOBJECT(node) )
|
|
|
|
msgcode = PROGRAMMATIC_OBJECTS_REQUIRE_TESTING_OBJECT;
|
|
|
|
else if ( nodeIsEMBED(node) )
|
|
|
|
msgcode = PROGRAMMATIC_OBJECTS_REQUIRE_TESTING_EMBED;
|
|
|
|
else if ( nodeIsAPPLET(node) )
|
|
|
|
msgcode = PROGRAMMATIC_OBJECTS_REQUIRE_TESTING_APPLET;
|
|
|
|
|
|
|
|
if ( msgcode )
|
|
|
|
TY_(ReportAccessWarning)( doc, node, msgcode );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************
|
|
|
|
* AccessibleCompatible
|
|
|
|
*
|
|
|
|
* Verify that programmatic objects are directly accessible.
|
|
|
|
*************************************************************/
|
|
|
|
|
|
|
|
static void AccessibleCompatible( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
int msgcode = 0;
|
|
|
|
if ( nodeIsSCRIPT(node) )
|
|
|
|
msgcode = ENSURE_PROGRAMMATIC_OBJECTS_ACCESSIBLE_SCRIPT;
|
|
|
|
else if ( nodeIsOBJECT(node) )
|
|
|
|
msgcode = ENSURE_PROGRAMMATIC_OBJECTS_ACCESSIBLE_OBJECT;
|
|
|
|
else if ( nodeIsEMBED(node) )
|
|
|
|
msgcode = ENSURE_PROGRAMMATIC_OBJECTS_ACCESSIBLE_EMBED;
|
|
|
|
else if ( nodeIsAPPLET(node) )
|
|
|
|
msgcode = ENSURE_PROGRAMMATIC_OBJECTS_ACCESSIBLE_APPLET;
|
|
|
|
|
|
|
|
if ( msgcode )
|
|
|
|
TY_(ReportAccessWarning)( doc, node, msgcode );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/********************************************************
|
|
|
|
* WordCount
|
|
|
|
*
|
|
|
|
* Counts the number of words in the document. Must have
|
|
|
|
* more than 3 words to verify changes in natural
|
|
|
|
* language of document.
|
|
|
|
*
|
|
|
|
* CPR - Not sure what intent is here, but this
|
|
|
|
* routine has nothing to do with changes in language.
|
|
|
|
* It seems like a bad idea to emit this message for
|
|
|
|
* every document with _more_ than 3 words!
|
|
|
|
********************************************************/
|
|
|
|
#if 0
|
|
|
|
static int WordCount( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
int wc = 0;
|
|
|
|
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Count the number of words found within a text node */
|
|
|
|
if ( TY_(nodeIsText)( node ) )
|
|
|
|
{
|
|
|
|
tmbchar ch;
|
|
|
|
ctmbstr word = textFromOneNode( doc, node );
|
|
|
|
if ( !IsWhitespace(word) )
|
|
|
|
{
|
|
|
|
++wc;
|
|
|
|
while ( (ch = *word++) && wc < 5 )
|
|
|
|
{
|
|
|
|
if ( ch == ' ')
|
|
|
|
++wc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( node = node->content; wc < 5 && node; node = node->next )
|
|
|
|
{
|
|
|
|
wc += WordCount( doc, node );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return wc;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/**************************************************
|
|
|
|
* CheckFlicker
|
|
|
|
*
|
|
|
|
* Verify that the page does not cause flicker.
|
|
|
|
**************************************************/
|
|
|
|
|
|
|
|
static void CheckFlicker( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
int msgcode = 0;
|
|
|
|
if ( nodeIsSCRIPT(node) )
|
|
|
|
msgcode = REMOVE_FLICKER_SCRIPT;
|
|
|
|
else if ( nodeIsOBJECT(node) )
|
|
|
|
msgcode = REMOVE_FLICKER_OBJECT;
|
|
|
|
else if ( nodeIsEMBED(node) )
|
|
|
|
msgcode = REMOVE_FLICKER_EMBED;
|
|
|
|
else if ( nodeIsAPPLET(node) )
|
|
|
|
msgcode = REMOVE_FLICKER_APPLET;
|
|
|
|
|
|
|
|
/* Checks for animated gif within the <img> tag. */
|
|
|
|
else if ( nodeIsIMG(node) )
|
|
|
|
{
|
|
|
|
AttVal* av = attrGetSRC( node );
|
|
|
|
if ( hasValue(av) )
|
|
|
|
{
|
|
|
|
tmbchar ext[20];
|
|
|
|
GetFileExtension( av->value, ext, sizeof(ext) );
|
|
|
|
if ( TY_(tmbstrcasecmp)(ext, ".gif") == 0 )
|
|
|
|
msgcode = REMOVE_FLICKER_ANIMATED_GIF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( msgcode )
|
|
|
|
TY_(ReportAccessWarning)( doc, node, msgcode );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**********************************************************
|
|
|
|
* CheckDeprecated
|
|
|
|
*
|
|
|
|
* APPLET, BASEFONT, CENTER, FONT, ISINDEX,
|
|
|
|
* S, STRIKE, and U should not be used. Becomes deprecated
|
|
|
|
* HTML if any of the above are used.
|
|
|
|
**********************************************************/
|
|
|
|
|
|
|
|
static void CheckDeprecated( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
int msgcode = 0;
|
|
|
|
if ( nodeIsAPPLET(node) )
|
|
|
|
msgcode = REPLACE_DEPRECATED_HTML_APPLET;
|
|
|
|
else if ( nodeIsBASEFONT(node) )
|
|
|
|
msgcode = REPLACE_DEPRECATED_HTML_BASEFONT;
|
|
|
|
else if ( nodeIsCENTER(node) )
|
|
|
|
msgcode = REPLACE_DEPRECATED_HTML_CENTER;
|
|
|
|
else if ( nodeIsDIR(node) )
|
|
|
|
msgcode = REPLACE_DEPRECATED_HTML_DIR;
|
|
|
|
else if ( nodeIsFONT(node) )
|
|
|
|
msgcode = REPLACE_DEPRECATED_HTML_FONT;
|
|
|
|
else if ( nodeIsISINDEX(node) )
|
|
|
|
msgcode = REPLACE_DEPRECATED_HTML_ISINDEX;
|
|
|
|
else if ( nodeIsMENU(node) )
|
|
|
|
msgcode = REPLACE_DEPRECATED_HTML_MENU;
|
|
|
|
else if ( nodeIsS(node) )
|
|
|
|
msgcode = REPLACE_DEPRECATED_HTML_S;
|
|
|
|
else if ( nodeIsSTRIKE(node) )
|
|
|
|
msgcode = REPLACE_DEPRECATED_HTML_STRIKE;
|
|
|
|
else if ( nodeIsU(node) )
|
|
|
|
msgcode = REPLACE_DEPRECATED_HTML_U;
|
|
|
|
|
|
|
|
if ( msgcode )
|
|
|
|
TY_(ReportAccessError)( doc, node, msgcode );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/************************************************************
|
|
|
|
* CheckScriptKeyboardAccessible
|
|
|
|
*
|
|
|
|
* Elements must have a device independent event handler if
|
|
|
|
* they have any of the following device dependent event
|
|
|
|
* handlers.
|
|
|
|
************************************************************/
|
|
|
|
|
|
|
|
static void CheckScriptKeyboardAccessible( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Node* content;
|
|
|
|
int HasOnMouseDown = 0;
|
|
|
|
int HasOnMouseUp = 0;
|
|
|
|
int HasOnClick = 0;
|
|
|
|
int HasOnMouseOut = 0;
|
|
|
|
int HasOnMouseOver = 0;
|
|
|
|
int HasOnMouseMove = 0;
|
|
|
|
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
AttVal* av;
|
|
|
|
/* Checks all elements for their attributes */
|
|
|
|
for (av = node->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
/* Must also have 'ONKEYDOWN' attribute with 'ONMOUSEDOWN' */
|
|
|
|
if ( attrIsOnMOUSEDOWN(av) )
|
|
|
|
HasOnMouseDown++;
|
|
|
|
|
|
|
|
/* Must also have 'ONKEYUP' attribute with 'ONMOUSEUP' */
|
|
|
|
if ( attrIsOnMOUSEUP(av) )
|
|
|
|
HasOnMouseUp++;
|
|
|
|
|
|
|
|
/* Must also have 'ONKEYPRESS' attribute with 'ONCLICK' */
|
|
|
|
if ( attrIsOnCLICK(av) )
|
|
|
|
HasOnClick++;
|
|
|
|
|
|
|
|
/* Must also have 'ONBLUR' attribute with 'ONMOUSEOUT' */
|
|
|
|
if ( attrIsOnMOUSEOUT(av) )
|
|
|
|
HasOnMouseOut++;
|
|
|
|
|
|
|
|
if ( attrIsOnMOUSEOVER(av) )
|
|
|
|
HasOnMouseOver++;
|
|
|
|
|
|
|
|
if ( attrIsOnMOUSEMOVE(av) )
|
|
|
|
HasOnMouseMove++;
|
|
|
|
|
|
|
|
if ( attrIsOnKEYDOWN(av) )
|
|
|
|
HasOnMouseDown++;
|
|
|
|
|
|
|
|
if ( attrIsOnKEYUP(av) )
|
|
|
|
HasOnMouseUp++;
|
|
|
|
|
|
|
|
if ( attrIsOnKEYPRESS(av) )
|
|
|
|
HasOnClick++;
|
|
|
|
|
|
|
|
if ( attrIsOnBLUR(av) )
|
|
|
|
HasOnMouseOut++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( HasOnMouseDown == 1 )
|
|
|
|
TY_(ReportAccessError)( doc, node, SCRIPT_NOT_KEYBOARD_ACCESSIBLE_ON_MOUSE_DOWN);
|
|
|
|
|
|
|
|
if ( HasOnMouseUp == 1 )
|
|
|
|
TY_(ReportAccessError)( doc, node, SCRIPT_NOT_KEYBOARD_ACCESSIBLE_ON_MOUSE_UP);
|
|
|
|
|
|
|
|
if ( HasOnClick == 1 )
|
|
|
|
TY_(ReportAccessError)( doc, node, SCRIPT_NOT_KEYBOARD_ACCESSIBLE_ON_CLICK);
|
|
|
|
if ( HasOnMouseOut == 1 )
|
|
|
|
TY_(ReportAccessError)( doc, node, SCRIPT_NOT_KEYBOARD_ACCESSIBLE_ON_MOUSE_OUT);
|
|
|
|
|
|
|
|
if ( HasOnMouseOver == 1 )
|
|
|
|
TY_(ReportAccessError)( doc, node, SCRIPT_NOT_KEYBOARD_ACCESSIBLE_ON_MOUSE_OVER);
|
|
|
|
|
|
|
|
if ( HasOnMouseMove == 1 )
|
|
|
|
TY_(ReportAccessError)( doc, node, SCRIPT_NOT_KEYBOARD_ACCESSIBLE_ON_MOUSE_MOVE);
|
|
|
|
|
|
|
|
/* Recursively check all child nodes.
|
|
|
|
*/
|
|
|
|
for ( content = node->content; content != NULL; content = content->next )
|
|
|
|
CheckScriptKeyboardAccessible( doc, content );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**********************************************************
|
|
|
|
* CheckMetaData
|
|
|
|
*
|
|
|
|
* Must have at least one of these elements in the document.
|
|
|
|
* META, LINK, TITLE or ADDRESS. <meta> must contain
|
|
|
|
* a "content" attribute that doesn't contain a URL, and
|
|
|
|
* an "http-Equiv" attribute that doesn't contain 'refresh'.
|
|
|
|
**********************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
static Bool CheckMetaData( TidyDocImpl* doc, Node* node, Bool HasMetaData )
|
|
|
|
{
|
|
|
|
Bool HasHttpEquiv = no;
|
|
|
|
Bool HasContent = no;
|
|
|
|
Bool ContainsAttr = no;
|
|
|
|
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
if ( nodeIsMETA(node) )
|
|
|
|
{
|
|
|
|
AttVal* av;
|
|
|
|
for (av = node->attributes; av != NULL; av = av->next)
|
|
|
|
{
|
|
|
|
if ( attrIsHTTP_EQUIV(av) && hasValue(av) )
|
|
|
|
{
|
|
|
|
ContainsAttr = yes;
|
|
|
|
|
|
|
|
/* Must not have an auto-refresh */
|
|
|
|
if (AttrValueIs(av, "refresh"))
|
|
|
|
{
|
|
|
|
HasHttpEquiv = yes;
|
|
|
|
TY_(ReportAccessError)( doc, node, REMOVE_AUTO_REFRESH );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( attrIsCONTENT(av) && hasValue(av) )
|
|
|
|
{
|
|
|
|
ContainsAttr = yes;
|
|
|
|
|
|
|
|
/* If the value is not an integer, then it must not be a URL */
|
|
|
|
if ( TY_(tmbstrncmp)(av->value, "http:", 5) == 0)
|
|
|
|
{
|
|
|
|
HasContent = yes;
|
|
|
|
TY_(ReportAccessError)( doc, node, REMOVE_AUTO_REDIRECT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( HasContent || HasHttpEquiv )
|
|
|
|
{
|
|
|
|
HasMetaData = yes;
|
|
|
|
TY_(ReportAccessError)( doc, node, METADATA_MISSING_REDIRECT_AUTOREFRESH);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( ContainsAttr && !HasContent && !HasHttpEquiv )
|
|
|
|
HasMetaData = yes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !HasMetaData &&
|
|
|
|
nodeIsADDRESS(node) &&
|
|
|
|
nodeIsA(node->content) )
|
|
|
|
{
|
|
|
|
HasMetaData = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !HasMetaData &&
|
|
|
|
!nodeIsTITLE(node) &&
|
|
|
|
TY_(nodeIsText)(node->content) )
|
|
|
|
{
|
|
|
|
ctmbstr word = textFromOneNode( doc, node->content );
|
|
|
|
if ( !IsWhitespace(word) )
|
|
|
|
HasMetaData = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !HasMetaData && nodeIsLINK(node) )
|
|
|
|
{
|
|
|
|
AttVal* av = attrGetREL(node);
|
|
|
|
if( !AttrContains(av, "stylesheet") )
|
|
|
|
HasMetaData = yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check for MetaData */
|
|
|
|
for ( node = node->content; node; node = node->next )
|
|
|
|
{
|
|
|
|
HasMetaData = CheckMetaData( doc, node, HasMetaData );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return HasMetaData;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************
|
|
|
|
* MetaDataPresent
|
|
|
|
*
|
|
|
|
* Determines if MetaData is present in document
|
|
|
|
*******************************************************/
|
|
|
|
|
|
|
|
static void MetaDataPresent( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, METADATA_MISSING );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*****************************************************
|
|
|
|
* CheckDocType
|
|
|
|
*
|
|
|
|
* Checks that every HTML/XHTML document contains a
|
|
|
|
* '!DOCTYPE' before the root node. ie. <HTML>
|
|
|
|
*****************************************************/
|
|
|
|
|
|
|
|
static void CheckDocType( TidyDocImpl* doc )
|
|
|
|
{
|
|
|
|
if (Level2_Enabled( doc ))
|
|
|
|
{
|
|
|
|
Node* DTnode = TY_(FindDocType)(doc);
|
|
|
|
|
|
|
|
/* If the doctype has been added by tidy, DTnode->end will be 0. */
|
|
|
|
if (DTnode && DTnode->end != 0)
|
|
|
|
{
|
|
|
|
ctmbstr word = textFromOneNode( doc, DTnode);
|
|
|
|
if ((strstr (word, "HTML PUBLIC") == NULL) &&
|
|
|
|
(strstr (word, "html PUBLIC") == NULL))
|
|
|
|
DTnode = NULL;
|
|
|
|
}
|
|
|
|
if (!DTnode)
|
|
|
|
TY_(ReportAccessError)( doc, &doc->root, DOCTYPE_MISSING);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/********************************************************
|
|
|
|
* CheckMapLinks
|
|
|
|
*
|
|
|
|
* Checks to see if an HREF for A element matches HREF
|
|
|
|
* for AREA element. There must be an HREF attribute
|
|
|
|
* of an A element for every HREF of an AREA element.
|
|
|
|
********************************************************/
|
|
|
|
|
|
|
|
static Bool urlMatch( ctmbstr url1, ctmbstr url2 )
|
|
|
|
{
|
|
|
|
/* TODO: Make host part case-insensitive and
|
|
|
|
** remainder case-sensitive.
|
|
|
|
*/
|
|
|
|
return ( TY_(tmbstrcmp)( url1, url2 ) == 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
static Bool FindLinkA( TidyDocImpl* doc, Node* node, ctmbstr url )
|
|
|
|
{
|
|
|
|
Bool found = no;
|
|
|
|
for ( node = node->content; !found && node; node = node->next )
|
|
|
|
{
|
|
|
|
if ( nodeIsA(node) )
|
|
|
|
{
|
|
|
|
AttVal* href = attrGetHREF( node );
|
|
|
|
found = ( hasValue(href) && urlMatch(url, href->value) );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
found = FindLinkA( doc, node, url );
|
|
|
|
}
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void CheckMapLinks( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Node* child;
|
|
|
|
|
|
|
|
if (!Level3_Enabled( doc ))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Stores the 'HREF' link of an AREA element within a MAP element */
|
|
|
|
for ( child = node->content; child != NULL; child = child->next )
|
|
|
|
{
|
|
|
|
if ( nodeIsAREA(child) )
|
|
|
|
{
|
|
|
|
/* Checks for 'HREF' attribute */
|
|
|
|
AttVal* href = attrGetHREF( child );
|
|
|
|
if ( hasValue(href) &&
|
|
|
|
!FindLinkA( doc, &doc->root, href->value ) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessError)( doc, node, IMG_MAP_CLIENT_MISSING_TEXT_LINKS );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/****************************************************
|
|
|
|
* CheckForStyleAttribute
|
|
|
|
*
|
|
|
|
* Checks all elements within the document to check
|
|
|
|
* for the use of 'STYLE' attribute.
|
|
|
|
****************************************************/
|
|
|
|
|
|
|
|
static void CheckForStyleAttribute( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Node* content;
|
|
|
|
if (Level1_Enabled( doc ))
|
|
|
|
{
|
|
|
|
/* Must not contain 'STYLE' attribute */
|
|
|
|
AttVal* style = attrGetSTYLE( node );
|
|
|
|
if ( hasValue(style) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, STYLESHEETS_REQUIRE_TESTING_STYLE_ATTR );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Recursively check all child nodes.
|
|
|
|
*/
|
|
|
|
for ( content = node->content; content != NULL; content = content->next )
|
|
|
|
CheckForStyleAttribute( doc, content );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*****************************************************
|
|
|
|
* CheckForListElements
|
|
|
|
*
|
|
|
|
* Checks document for list elements (<ol>, <ul>, <li>)
|
|
|
|
*****************************************************/
|
|
|
|
|
|
|
|
static void CheckForListElements( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
if ( nodeIsLI(node) )
|
|
|
|
{
|
|
|
|
doc->access.ListElements++;
|
|
|
|
}
|
|
|
|
else if ( nodeIsOL(node) || nodeIsUL(node) )
|
|
|
|
{
|
|
|
|
doc->access.OtherListElements++;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( node = node->content; node != NULL; node = node->next )
|
|
|
|
{
|
|
|
|
CheckForListElements( doc, node );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************
|
|
|
|
* CheckListUsage
|
|
|
|
*
|
|
|
|
* Ensures that lists are properly used. <ol> and <ul>
|
|
|
|
* must contain <li> within itself, and <li> must not be
|
|
|
|
* by itself.
|
|
|
|
******************************************************/
|
|
|
|
|
|
|
|
static void CheckListUsage( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
int msgcode = 0;
|
|
|
|
|
|
|
|
if (!Level2_Enabled( doc ))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( nodeIsOL(node) )
|
|
|
|
msgcode = LIST_USAGE_INVALID_OL;
|
|
|
|
else if ( nodeIsUL(node) )
|
|
|
|
msgcode = LIST_USAGE_INVALID_UL;
|
|
|
|
|
|
|
|
if ( msgcode )
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
** Check that OL/UL
|
|
|
|
** a) has LI child,
|
|
|
|
** b) was not added by Tidy parser
|
|
|
|
** IFF OL/UL node is implicit
|
|
|
|
*/
|
|
|
|
if ( !nodeIsLI(node->content) ) {
|
|
|
|
TY_(ReportAccessWarning)( doc, node, msgcode );
|
|
|
|
} else if ( node->implicit ) { /* if a tidy added node */
|
|
|
|
TY_(ReportAccessWarning)( doc, node, LIST_USAGE_INVALID_LI );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( nodeIsLI(node) )
|
|
|
|
{
|
|
|
|
/* Check that LI parent
|
|
|
|
** a) exists,
|
|
|
|
** b) is either OL or UL
|
|
|
|
** IFF the LI parent was added by Tidy
|
|
|
|
** ie, if it is marked 'implicit', then
|
|
|
|
** emit warnings LIST_USAGE_INVALID_UL or
|
|
|
|
** warning LIST_USAGE_INVALID_OL tests
|
|
|
|
*/
|
|
|
|
if ( node->parent == NULL ||
|
|
|
|
( !nodeIsOL(node->parent) && !nodeIsUL(node->parent) ) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, LIST_USAGE_INVALID_LI );
|
|
|
|
} else if ( node->implicit && node->parent &&
|
|
|
|
( nodeIsOL(node->parent) || nodeIsUL(node->parent) ) ) {
|
|
|
|
/* if tidy added LI node, then */
|
|
|
|
msgcode = nodeIsUL(node->parent) ?
|
|
|
|
LIST_USAGE_INVALID_UL : LIST_USAGE_INVALID_OL;
|
|
|
|
TY_(ReportAccessWarning)( doc, node, msgcode );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************
|
|
|
|
* InitAccessibilityChecks
|
|
|
|
*
|
|
|
|
* Initializes the AccessibilityChecks variables as necessary
|
|
|
|
************************************************************/
|
|
|
|
|
|
|
|
static void InitAccessibilityChecks( TidyDocImpl* doc, int level123 )
|
|
|
|
{
|
|
|
|
TidyClearMemory( &doc->access, sizeof(doc->access) );
|
|
|
|
doc->access.PRIORITYCHK = level123;
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************
|
|
|
|
* CleanupAccessibilityChecks
|
|
|
|
*
|
|
|
|
* Cleans up the AccessibilityChecks variables as necessary
|
|
|
|
************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
static void FreeAccessibilityChecks( TidyDocImpl* ARG_UNUSED(doc) )
|
|
|
|
{
|
|
|
|
/* free any memory allocated for the lists
|
|
|
|
|
|
|
|
Linked List of Links not used. Just search document as
|
|
|
|
AREA tags are encountered. Same algorithm, but no
|
|
|
|
data structures necessary.
|
|
|
|
|
|
|
|
current = start;
|
|
|
|
while (current)
|
|
|
|
{
|
|
|
|
void *templink = (void *)current;
|
|
|
|
|
|
|
|
current = current->next;
|
|
|
|
TidyDocFree(doc, templink);
|
|
|
|
}
|
|
|
|
start = NULL;
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************
|
|
|
|
* AccessibilityChecks
|
|
|
|
*
|
|
|
|
* Traverses through the individual nodes of the tree
|
|
|
|
* and checks attributes and elements for accessibility.
|
|
|
|
* after the tree structure has been formed.
|
|
|
|
************************************************************/
|
|
|
|
|
|
|
|
static void AccessibilityCheckNode( TidyDocImpl* doc, Node* node )
|
|
|
|
{
|
|
|
|
Node* content;
|
|
|
|
|
|
|
|
/* Check BODY for color contrast */
|
|
|
|
if ( nodeIsBODY(node) )
|
|
|
|
{
|
|
|
|
CheckColorContrast( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks document for MetaData */
|
|
|
|
else if ( nodeIsHEAD(node) )
|
|
|
|
{
|
|
|
|
if ( !CheckMetaData( doc, node, no ) )
|
|
|
|
MetaDataPresent( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the ANCHOR tag */
|
|
|
|
else if ( nodeIsA(node) )
|
|
|
|
{
|
|
|
|
CheckAnchorAccess( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the IMAGE tag */
|
|
|
|
else if ( nodeIsIMG(node) )
|
|
|
|
{
|
|
|
|
CheckFlicker( doc, node );
|
|
|
|
CheckColorAvailable( doc, node );
|
|
|
|
CheckImage( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks MAP for client-side text links */
|
|
|
|
else if ( nodeIsMAP(node) )
|
|
|
|
{
|
|
|
|
CheckMapLinks( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the AREA tag */
|
|
|
|
else if ( nodeIsAREA(node) )
|
|
|
|
{
|
|
|
|
CheckArea( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the APPLET tag */
|
|
|
|
else if ( nodeIsAPPLET(node) )
|
|
|
|
{
|
|
|
|
CheckDeprecated( doc, node );
|
|
|
|
ProgrammaticObjects( doc, node );
|
|
|
|
DynamicContent( doc, node );
|
|
|
|
AccessibleCompatible( doc, node );
|
|
|
|
CheckFlicker( doc, node );
|
|
|
|
CheckColorAvailable( doc, node );
|
|
|
|
CheckApplet(doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the OBJECT tag */
|
|
|
|
else if ( nodeIsOBJECT(node) )
|
|
|
|
{
|
|
|
|
ProgrammaticObjects( doc, node );
|
|
|
|
DynamicContent( doc, node );
|
|
|
|
AccessibleCompatible( doc, node );
|
|
|
|
CheckFlicker( doc, node );
|
|
|
|
CheckColorAvailable( doc, node );
|
|
|
|
CheckObject( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the FRAME tag */
|
|
|
|
else if ( nodeIsFRAME(node) )
|
|
|
|
{
|
|
|
|
CheckFrame( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the IFRAME tag */
|
|
|
|
else if ( nodeIsIFRAME(node) )
|
|
|
|
{
|
|
|
|
CheckIFrame( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the SCRIPT tag */
|
|
|
|
else if ( nodeIsSCRIPT(node) )
|
|
|
|
{
|
|
|
|
DynamicContent( doc, node );
|
|
|
|
ProgrammaticObjects( doc, node );
|
|
|
|
AccessibleCompatible( doc, node );
|
|
|
|
CheckFlicker( doc, node );
|
|
|
|
CheckColorAvailable( doc, node );
|
|
|
|
CheckScriptAcc( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the TABLE tag */
|
|
|
|
else if ( nodeIsTABLE(node) )
|
|
|
|
{
|
|
|
|
CheckColorContrast( doc, node );
|
|
|
|
CheckTable( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the PRE for ASCII art */
|
|
|
|
else if ( nodeIsPRE(node) || nodeIsXMP(node) )
|
|
|
|
{
|
|
|
|
CheckASCII( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check the LABEL tag */
|
|
|
|
else if ( nodeIsLABEL(node) )
|
|
|
|
{
|
|
|
|
CheckLabel( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check INPUT tag for validity */
|
|
|
|
else if ( nodeIsINPUT(node) )
|
|
|
|
{
|
|
|
|
CheckColorAvailable( doc, node );
|
|
|
|
CheckInputLabel( doc, node );
|
|
|
|
CheckInputAttributes( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks FRAMESET element for NOFRAME section */
|
|
|
|
else if ( nodeIsFRAMESET(node) )
|
|
|
|
{
|
|
|
|
CheckFrameSet( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks for header elements for valid header increase */
|
|
|
|
else if ( TY_(nodeIsHeader)(node) )
|
|
|
|
{
|
|
|
|
CheckHeaderNesting( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks P element to ensure that it is not a header */
|
|
|
|
else if ( nodeIsP(node) )
|
|
|
|
{
|
|
|
|
CheckParagraphHeader( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks HTML elemnt for valid 'LANG' */
|
|
|
|
else if ( nodeIsHTML(node) )
|
|
|
|
{
|
|
|
|
CheckHTMLAccess( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks BLINK for any blinking text */
|
|
|
|
else if ( nodeIsBLINK(node) )
|
|
|
|
{
|
|
|
|
CheckBlink( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks MARQUEE for any MARQUEE text */
|
|
|
|
else if ( nodeIsMARQUEE(node) )
|
|
|
|
{
|
|
|
|
CheckMarquee( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks LINK for 'REL' attribute */
|
|
|
|
else if ( nodeIsLINK(node) )
|
|
|
|
{
|
|
|
|
CheckLink( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks to see if STYLE is used */
|
|
|
|
else if ( nodeIsSTYLE(node) )
|
|
|
|
{
|
|
|
|
CheckColorContrast( doc, node );
|
|
|
|
CheckStyle( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks to see if EMBED is used */
|
|
|
|
else if ( nodeIsEMBED(node) )
|
|
|
|
{
|
|
|
|
CheckEmbed( doc, node );
|
|
|
|
ProgrammaticObjects( doc, node );
|
|
|
|
AccessibleCompatible( doc, node );
|
|
|
|
CheckFlicker( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Deprecated HTML if the following tags are found in the document */
|
|
|
|
else if ( nodeIsBASEFONT(node) ||
|
|
|
|
nodeIsCENTER(node) ||
|
|
|
|
nodeIsISINDEX(node) ||
|
|
|
|
nodeIsU(node) ||
|
|
|
|
nodeIsFONT(node) ||
|
|
|
|
nodeIsDIR(node) ||
|
|
|
|
nodeIsS(node) ||
|
|
|
|
nodeIsSTRIKE(node) ||
|
|
|
|
nodeIsMENU(node) )
|
|
|
|
{
|
|
|
|
CheckDeprecated( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks for 'ABBR' attribute if needed */
|
|
|
|
else if ( nodeIsTH(node) )
|
|
|
|
{
|
|
|
|
CheckTH( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Ensures that lists are properly used */
|
|
|
|
else if ( nodeIsLI(node) || nodeIsOL(node) || nodeIsUL(node) )
|
|
|
|
{
|
|
|
|
CheckListUsage( doc, node );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Recursively check all child nodes.
|
|
|
|
*/
|
|
|
|
for ( content = node->content; content != NULL; content = content->next )
|
|
|
|
{
|
|
|
|
AccessibilityCheckNode( doc, content );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void TY_(AccessibilityChecks)( TidyDocImpl* doc )
|
|
|
|
{
|
|
|
|
/* Initialize */
|
|
|
|
InitAccessibilityChecks( doc, cfg(doc, TidyAccessibilityCheckLevel) );
|
|
|
|
|
|
|
|
/* Hello there, ladies and gentlemen... */
|
|
|
|
TY_(AccessibilityHelloMessage)( doc );
|
|
|
|
|
|
|
|
/* Checks all elements for script accessibility */
|
|
|
|
CheckScriptKeyboardAccessible( doc, &doc->root );
|
|
|
|
|
|
|
|
/* Checks entire document for the use of 'STYLE' attribute */
|
|
|
|
CheckForStyleAttribute( doc, &doc->root );
|
|
|
|
|
|
|
|
/* Checks for '!DOCTYPE' */
|
|
|
|
CheckDocType( doc );
|
|
|
|
|
|
|
|
|
|
|
|
/* Checks to see if stylesheets are used to control the layout */
|
|
|
|
if ( Level2_Enabled( doc )
|
|
|
|
&& ! CheckMissingStyleSheets( doc, &doc->root ) )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, &doc->root, STYLE_SHEET_CONTROL_PRESENTATION );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check to see if any list elements are found within the document */
|
|
|
|
CheckForListElements( doc, &doc->root );
|
|
|
|
|
|
|
|
/* Checks for natural language change */
|
|
|
|
/* Must contain more than 3 words of text in the document
|
|
|
|
**
|
|
|
|
** CPR - Not sure what intent is here, but this
|
|
|
|
** routine has nothing to do with changes in language.
|
|
|
|
** It seems like a bad idea to emit this message for
|
|
|
|
** every document with _more_ than 3 words!
|
|
|
|
|
|
|
|
if ( WordCount(doc, &doc->root) > 3 )
|
|
|
|
{
|
|
|
|
TY_(ReportAccessWarning)( doc, node, INDICATE_CHANGES_IN_LANGUAGE);
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/* Recursively apply all remaining checks to
|
|
|
|
** each node in document.
|
|
|
|
*/
|
|
|
|
AccessibilityCheckNode( doc, &doc->root );
|
|
|
|
|
|
|
|
/* Cleanup */
|
|
|
|
FreeAccessibilityChecks( doc );
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* local variables:
|
|
|
|
* mode: c
|
|
|
|
* indent-tabs-mode: nil
|
|
|
|
* c-basic-offset: 4
|
|
|
|
* eval: (c-set-offset 'substatement-open 0)
|
|
|
|
* end:
|
|
|
|
*/
|