# webtag.icn: Object to represent one HTML tag
#--
$define WEBTAG_REVISION "$Revision: 1.13 $"
$define WEBTAG_DATE "$Date: 1996/10/29 23:40:51 $"
#================================================================
# Class Webtag: Each instance represents one HTML tag. It is
# assumed that a Web tag has this syntax, in extended BNF:
# "<"["/"][[=]]...">"
# or, for comments:
# ""
# where:
# has the form [|]...
# is whitespace (any number of spaces and tabs)
# has the form [||"_"]...
# is a string enclosed in double-quotes
#----------------------------------------------------------------
# Exported methods
#----------------------------------------------------------------
# webtag := Webtag_Scan ( scan )
# [ if scan looks like a valid HTML tag ->
# scan := scan advanced past the tag
# returns a new webtag encoding it, with and
# all strings lowercased
# else if scan looks like an HTML tag but it's invalid ->
# scan := scan advanced past the valid-looking part
# scan.log ||:= scan errors
# fail
# else if scan doesn't look like an HTML tag at all ->
# fail
# ]
#--
# Webtag_Is_Close ( self )
# [ if self represents a closing tag ->
# return &null
# | else ->
# fail
# ]
#--
# Webtag_Name ( self )
# [ returns the string, lowercased
# ]
#--
# Webtag_N_Attrs ( self )
# [ returns the number of self's attributes
# ]
#--
# Webtag_Gen_Attrs ( self )
# [ generates the attributes of self as tagAttr objects
# ]
#--
# Webtag_Source ( self )
# [ returns reconstituted source for the tag
# ]
#--
# Webtag_Name_Start_Cset ( )
# [ returns the cset of name start chars
# ]
#--
# Webtag_Name_Cset ( )
# [ returns the cset of name chars
# ]
#--
# - - - State - - -
record webtagTag ( # State for one webtag object
name, # Name, lowercased, or WEBTAG_COMMENT_NAME if a comment
isClose, # 1 if this is a closing tag, else &null
attrList ) # A list of tagAttr objects for attributes, or &null,
# or the comment text if a comment
# - - - Defines - - -
$define WEBTAG_COMMENT_NAME "!" # Name field value for comments
$define WEBTAG_COMMENT_HEAD "!--" # Starts a comment
$define WEBTAG_COMMENT_TAIL "-->" # Ends a comment
# - - - W e b t a g _ I s _ C l o s e - - -
procedure Webtag_Is_Close ( self )
if \ self.isClose then
return &null
else
fail;
end
# - - - W e b t a g _ N a m e - - -
procedure Webtag_Name ( self )
return self.name;
end
# - - - W e b t a g _ G e n _ A t t r s - - -
procedure Webtag_Gen_Attrs ( self )
every suspend ( ! \ self.attrList );
fail;
end
# - - - W e b t a g _ N _ A t t r s - - -
procedure Webtag_N_Attrs ( self )
return ( * \ self.attrList ) | 0;
end
# - - - W e b t a g _ S c a n - - -
procedure Webtag_Scan ( scan )
local self
#-- 1 --
#-[ if scan starts with "<" ->
# scan := scan advanced past the "<"
# | else ->
# fail
#-]
if not = "<" then
fail;
#-- 2 --
#-[ if scan starts with WEBTAG_COMMENT_HEAD followed by arbitrary
# text and then (possibly on a different line) WEBTAG_COMMENT_TAIL ->
# scan := scan advanced past the WEBTAG_COMMENT_TAIL
# return a Webtag with name (WEBTAG_COMMENT_NAME) and
# attrList (the comment text)
# else if scan starts with WEBTAG_COMMENT_HEAD but that is not
# followed by a valid comment ->
# scan := scan advanced past the valid part, possibly to EOF
# scan ||:= error message(s)
# fail
# else -> I
#-]
if = WEBTAG_COMMENT_HEAD then
return Webtag_Scan_Comment ( scan );
#-- 3 --
self := webtagTag ( );
#-- 4 --
#-[ if next character in scan is "/" ->
# self.isClose := 1
# scan := scan advanced past "/"
# | else -> I
#-]
if = "/" then
self.isClose := 1;
#-- 5 --
#-[ if scan starts with a valid HTML tag name ->
# scan := scan advanced past that name
# self.name := the name, lowercased
# else ->
# scan ||:= error message(s)
# fail
#-]
if not ( self.name := Webtag_Scan_Name ( scan ) ) then
fail;
#-- 6 --
Scan_Deblank ( scan );
#-- 7 --
#-[ if scan contains one or more attributes separated by whitespace ->
# self.attrList := list of tagAttr objects representing those
# attributes
# scan := scan advanced past all those attributes
# and any trailing whitespace
# | else if scan contains one or more badly-formed attributes ->
# scan := scan, possibly advanced over valid-looking parts
# scan.log ||:= scan error message(s)
# fail
# | else -> I
#-]
if not Webtag_Scan_Attr_List ( self, scan ) then
fail;
#-- 8 --
#-[ if scan doesn't start with ">" ->
# scan.log ||:= scan error message, expecting ">"
# fail
# | else ->
# scan := scan advanced past the ">"
# return self
#-]
if = ">" then
return self
else
return Scan_Error ( scan, "Expecting the '>' at the end of an HTML tag" );
end # --- Webtag_Scan ---
# - - - W e b t a g _ S c a n _ C o m m e n t - - -
#-[ if scan starts with arbitrary text followed (possibly on a
# different line) by WEBTAG_COMMENT_TAIL ->
# scan := scan advanced past the WEBTAG_COMMENT_TAIL
# return a Webtag with name (WEBTAG_COMMENT_NAME) and
# attrList (the comment text)
# else if scan starts with WEBTAG_COMMENT_HEAD but that is not
# followed by WEBTAG_COMMENT_TAIL anywhere in scan ->
# scan := scan advanced to end of file
# scan ||:= error message(s)
# fail
# ]
procedure Webtag_Scan_Comment ( scan )
local self # Webtag to be returned
local text # The comment text
#-- 1 --
#-[ self := a new Webtag object with .name (WEBTAG_COMMENT_NAME),
# .attrList (""), and .isClose (&null)
#-]
self := webtagTag ( );
self.name := WEBTAG_COMMENT_NAME;
self.attrList := "";
#-- 2 --
#-[ if there is a line containing WEBTAG_COMMENT_TAIL in scan
# somewhere before EOF ->
# scan := scan advanced to the start of the next line
# containing WEBTAG_COMMENT_TAIL
# self.attrList ||:= text up to the start of the next line containing
# WEBTAG_COMMENT_TAIL
# else ->
# scan := scan advanced to EOF
# scan ||:= error message
# fail
#-]
#-- 2 top --
#-[ if scan is at end of file or contains WEBTAG_COMMENT_TAIL -> I
# else:
#-]
while ( not Scan_End_File ( scan ) ) &
( not find ( WEBTAG_COMMENT_TAIL ) ) do
{ #-- 2 body --
#-[ self.attrList ||:= (text from scan up to end of line) || (a newline)
# scan := scan advanced to the start of the next line
#-]
self.attrList ||:= tab ( 0 ) || "\n";
Scan_Next_Line ( scan );
} #-- 2 body --
#-- 3 --
#-[ if the current line contains WEBTAG_COMMENT_TAIL ->
# scan := scan advanced up to the first occurrence of
# WEBTAG_COMMENT_TAIL
# self.attrList ||:= text up to the first occurrence of
# WEBTAG_COMMENT_TAIL
# else ->
# scan ||:= error message
# fail
#-]
if not ( self.attrList ||:= tab ( find ( WEBTAG_COMMENT_TAIL ) ) ) then
return Scan_Error ( scan,
"Could not find the comment close string `",
WEBTAG_COMMENT_TAIL,
"'." );
#-- 4 --
#-[ if scan starts with WEBTAG_COMMENT_TAIL ->
# scan := scan advanced past that
#-]
= WEBTAG_COMMENT_TAIL;
#-- 4 --
return self;
end # --- Webtag_Scan_Comment ---
# - - - W e b t a g _ S c a n _ N a m e - - -
# [ if scan starts with a valid HTML tag name ->
# scan := scan advanced past that name
# return the name, lowercased
# else ->
# scan ||:= error message(s)
# fail
# ]
procedure Webtag_Scan_Name ( scan )
local result
#-- 1 --
#-[ if the next character from scan is in Webtag_Name_Start_Cset() ->
# result := next character from scan
# else ->
# scan ||:= error message
# fail
#-]
if not ( result := tab ( any ( Webtag_Name_Start_Cset ( ) ) ) ) then
return Scan_Error ( scan, "HTML tags must start with a name" );
#-- 2 --
#-[ if scan starts with a letter in Webtag_Name_Cset() ->
# scan := scan advanced past all leading characters in that cset
# result ||:= all leading characters in that cset
# else -> I
#-]
result ||:= tab ( many ( Webtag_Name_Cset ( ) ) );
#-- 3 --
#-[ return result, lowercased
#-]
return map ( result );
end # --- Webtag_Scan_Name --
# - - - W e b t a g _ S c a n _ A t t r _ L i s t - - -
#-[ if scan contains one or more attributes separated by whitespace ->
# self.attrList := list of tagAttr objects representing those
# attributes
# scan := scan advanced past all those attributes
# and any trailing whitespace
# return &null
# | else if scan contains one or more badly-formed attributes ->
# scan := scan, possibly advanced over valid-looking parts
# scan.log ||:= scan error message(s)
# fail
# | else ->
# return &null
#-]
procedure Webtag_Scan_Attr_List ( self, scan )
local tagAttr
#-- 1 --
#-- 1 top --
#-[ if scan doesn't look like an attr -> I
# else if scan is a valid attribute ->
# scan := scan advanced past the attribute
# tagAttr := a TagAttr object representing the attribute
#
# else if scan starts with a badly formed attribute ->
# scan := scan advanced past the valid parts
# scan ||:= error messages
# tagAttr := &null
#
#-]
while ( tagAttr := Tag_Attr_Scan ( scan ) ) do
{ #-- 1 body --
#-[ if tagAttr is &null -> I
# else if self.attrList is &null ->
# self.attrList := a list containing tagAttr
# else ->
# self.attrList ||:= tagAttr
#-]
if \ tagAttr then
{ #-- 1.1 --
#-[ if self.attrList is &null ->
# self.attrList := a list containing tagAttr
# else ->
# self.attrList ||:= tagAttr
#-]
/ self.attrList := [];
put ( self.attrList, tagAttr );
} #-- 1.1 --
} #-- 1 body --
#-- 2 --
return;
end # --- Webtag_Scan_Attr_List ---
# - - - W e b t a g _ S o u r c e - - -
procedure Webtag_Source ( self )
local result # Result string to be returned
local attr # Holds each attribute in turn
#-- 1 --
#-[ if self is a comment ->
# return WEBTAG_COMMENT_HEAD || self.attrList || WEBTAG_COMMENT_TAIL;
# else -> I
#-]
if self.name == WEBTAG_COMMENT_NAME then
return "<" || WEBTAG_COMMENT_HEAD || self.attrList || WEBTAG_COMMENT_TAIL;
#-- 2 --
result := "<"; # Start with the tag-start character
#-- 3 --
#-[ if self is a close ->
# result ||:= "/"
# else -> I
#-]
if \ self.isClose then # If this is a closing tag...
result ||:= "/"; # ...append a slash
#-- 4 --
result ||:= self.name;
#-- 5 --
#-[ result := (external form of all attributes of self, if any,
# each preceded by a space)
#-]
every attr := Webtag_Gen_Attrs ( self ) do
result ||:= " " || Tag_Attr_Show ( attr );
#-- 6 --
result ||:= ">"; # Append the tag-end character
#-- 7 --
return result;
end # --- Webtag_Source ---
# - - - W e b t a g _ N a m e _ S t a r t _ C s e t - - -
procedure Webtag_Name_Start_Cset ( )
return &letters;
end # --- Webtag_Name_Start_Cset ---
# - - - W e b t a g _ N a m e _ C s e t - - -
procedure Webtag_Name_Cset ( )
static result
initial
{ result := &letters ++ &digits ++ '_';
}
return result;
end # --- Webtag_Name_Cset ---