# 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 ---