@@ -37,95 +37,53 @@ export class AttListDecl implements XMLNode {
3737 }
3838
3939 parseAttributes ( text : string ) {
40- let parts : string [ ] = this . split ( text ) ;
41- let index : number = 0 ;
42- while ( index < parts . length ) {
43- let name : string = parts [ index ++ ] ;
40+ const parts : string [ ] = this . split ( text ) ;
41+ const state = { index : 0 } ;
42+
43+ while ( state . index < parts . length ) {
44+ const name : string = parts [ state . index ++ ] ;
45+ if ( ! name ) {
46+ continue ;
47+ }
4448
45- // Validate attribute name
4649 if ( ! XMLUtils . isValidXMLName ( name ) ) {
4750 throw new Error ( `Invalid attribute name in ATTLIST declaration: "${ name } "` ) ;
4851 }
4952
50- let attType : string = parts [ index ++ ] ;
53+ if ( state . index >= parts . length ) {
54+ throw new Error ( `Missing attribute type for attribute "${ name } "` ) ;
55+ }
56+
57+ let attType : string = this . readAttributeType ( parts , state ) ;
5158 let defaultDecl : string = '' ;
5259 let defaultValue : string = '' ;
5360
54- if ( AttListDecl . attTypes . includes ( attType ) ) {
55- // Standard attribute type
56- if ( index < parts . length ) {
57- let nextPart = parts [ index ++ ] ;
58- if ( nextPart === '#REQUIRED' || nextPart === '#IMPLIED' ) {
59- defaultDecl = nextPart ;
60- } else if ( nextPart === '#FIXED' ) {
61- defaultDecl = nextPart ;
62- if ( index < parts . length ) {
63- defaultValue = parts [ index ++ ] ;
64- if ( defaultValue . startsWith ( '"' ) && defaultValue . endsWith ( '"' ) ) {
65- defaultValue = defaultValue . substring ( 1 , defaultValue . length - 1 ) ;
66- }
67- }
68- } else if ( nextPart && ( ( nextPart . startsWith ( '"' ) && nextPart . endsWith ( '"' ) ) || ( nextPart . startsWith ( "'" ) && nextPart . endsWith ( "'" ) ) ) ) {
69- // Direct default value
70- defaultDecl = nextPart ;
71- defaultValue = nextPart . substring ( 1 , nextPart . length - 1 ) ; // Remove quotes
72- } else {
73- // Invalid: unquoted default value
74- throw new Error ( `Invalid attribute declaration: default value "${ nextPart } " must be quoted` ) ;
75- }
76- }
77- } else {
78- if ( attType === 'NOTATION' ) {
79- // Parse the notations in the enumeration that follows
80- if ( index < parts . length ) {
81- let notations = parts [ index ++ ] ; // This should be like "(notation1|notation2|notation3)"
82- attType = 'NOTATION ' + notations ; // Store the full notation enumeration as the type
83- if ( index < parts . length ) {
84- let nextPart = parts [ index ++ ] ;
85- if ( nextPart === '#REQUIRED' || nextPart === '#IMPLIED' ) {
86- defaultDecl = nextPart ;
87- } else if ( nextPart === '#FIXED' ) {
88- defaultDecl = nextPart ;
89- if ( index < parts . length ) {
90- defaultValue = parts [ index ++ ] ;
91- if ( defaultValue . startsWith ( '"' ) && defaultValue . endsWith ( '"' ) ) {
92- defaultValue = defaultValue . substring ( 1 , defaultValue . length - 1 ) ;
93- }
94- }
95- } else if ( nextPart && nextPart . startsWith ( '"' ) && nextPart . endsWith ( '"' ) ) {
96- // Direct default value
97- defaultDecl = nextPart ;
98- defaultValue = nextPart . substring ( 1 , nextPart . length - 1 ) ; // Remove quotes
99- } else {
100- defaultDecl = nextPart || '' ;
101- }
102- }
103- }
104- } else {
105- // Handle other enumeration types (values in parentheses)
106- if ( index < parts . length ) {
107- let nextPart = parts [ index ++ ] ;
108- if ( nextPart === '#REQUIRED' || nextPart === '#IMPLIED' ) {
109- defaultDecl = nextPart ;
110- } else if ( nextPart === '#FIXED' ) {
111- defaultDecl = nextPart ;
112- if ( index < parts . length ) {
113- defaultValue = parts [ index ++ ] ;
114- if ( defaultValue . startsWith ( '"' ) && defaultValue . endsWith ( '"' ) ) {
115- defaultValue = defaultValue . substring ( 1 , defaultValue . length - 1 ) ;
116- }
117- }
118- } else if ( nextPart && nextPart . startsWith ( '"' ) && nextPart . endsWith ( '"' ) ) {
119- // Direct default value
120- defaultDecl = nextPart ;
121- defaultValue = nextPart . substring ( 1 , nextPart . length - 1 ) ; // Remove quotes
61+ if ( state . index < parts . length ) {
62+ const nextPart = parts [ state . index ] ;
63+ if ( nextPart === '#REQUIRED' || nextPart === '#IMPLIED' ) {
64+ defaultDecl = nextPart ;
65+ state . index ++ ;
66+ } else if ( nextPart === '#FIXED' ) {
67+ defaultDecl = nextPart ;
68+ state . index ++ ;
69+ if ( state . index < parts . length ) {
70+ const valueToken = parts [ state . index ++ ] ;
71+ if ( this . isQuotedValue ( valueToken ) ) {
72+ defaultValue = this . trimQuotes ( valueToken ) ;
12273 } else {
123- defaultDecl = nextPart || '' ;
74+ defaultValue = valueToken ;
12475 }
12576 }
77+ } else if ( nextPart && this . isQuotedValue ( nextPart ) ) {
78+ defaultDecl = nextPart ;
79+ defaultValue = this . trimQuotes ( nextPart ) ;
80+ state . index ++ ;
81+ } else if ( nextPart ) {
82+ throw new Error ( `Invalid attribute declaration: default value "${ nextPart } " must be quoted` ) ;
12683 }
12784 }
128- let att : AttDecl = new AttDecl ( name , attType , defaultDecl , defaultValue ) ;
85+
86+ const att : AttDecl = new AttDecl ( name , attType , defaultDecl , defaultValue ) ;
12987 this . attributes . set ( name , att ) ;
13088 }
13189 }
@@ -134,19 +92,19 @@ export class AttListDecl implements XMLNode {
13492 let result : string [ ] = [ ] ;
13593 let word : string = '' ;
13694 let inQuotes : boolean = false ;
95+ let quoteChar : string = '' ;
13796
13897 for ( let i : number = 0 ; i < text . length ; i ++ ) {
13998 let c : string = text . charAt ( i ) ;
14099
141- if ( c === '"' && ! inQuotes ) {
142- // Start of quoted string
100+ if ( ( c === '"' || c === "'" ) && ! inQuotes ) {
143101 inQuotes = true ;
102+ quoteChar = c ;
144103 word += c ;
145- } else if ( c === '"' && inQuotes ) {
146- // End of quoted string
104+ } else if ( inQuotes && c === quoteChar ) {
147105 inQuotes = false ;
106+ quoteChar = '' ;
148107 word += c ;
149- // Complete the quoted word
150108 if ( word . length > 0 ) {
151109 result . push ( word ) ;
152110 word = '' ;
@@ -169,6 +127,69 @@ export class AttListDecl implements XMLNode {
169127 return result ;
170128 }
171129
130+ private readAttributeType ( parts : string [ ] , state : { index : number } ) : string {
131+ let token : string = parts [ state . index ++ ] ;
132+
133+ if ( token === 'NOTATION' ) {
134+ if ( state . index >= parts . length ) {
135+ throw new Error ( 'Expected NOTATION enumeration in ATTLIST declaration' ) ;
136+ }
137+ let enumeration : string = parts [ state . index ++ ] ;
138+ enumeration = this . readParenthesized ( enumeration , parts , state ) ;
139+ return 'NOTATION ' + enumeration ;
140+ }
141+
142+ if ( token . includes ( '(' ) ) {
143+ return this . readParenthesized ( token , parts , state ) ;
144+ }
145+
146+ return token ;
147+ }
148+
149+ private readParenthesized ( initial : string , parts : string [ ] , state : { index : number } ) : string {
150+ let result = initial ;
151+ let balance : number = this . countParenthesis ( initial ) ;
152+
153+ while ( balance > 0 ) {
154+ if ( state . index >= parts . length ) {
155+ throw new Error ( 'Unterminated parenthesized list in ATTLIST declaration' ) ;
156+ }
157+ const next : string = parts [ state . index ++ ] ;
158+ result += ' ' + next ;
159+ balance += this . countParenthesis ( next ) ;
160+ }
161+
162+ return this . normalizeEnumeration ( result ) ;
163+ }
164+
165+ private countParenthesis ( value : string ) : number {
166+ let balance : number = 0 ;
167+ for ( const char of value ) {
168+ if ( char === '(' ) {
169+ balance ++ ;
170+ } else if ( char === ')' ) {
171+ balance -- ;
172+ }
173+ }
174+ return balance ;
175+ }
176+
177+ private normalizeEnumeration ( value : string ) : string {
178+ let normalized = value . replace ( / \s + / g, ' ' ) ;
179+ normalized = normalized . replace ( / \s * \| \s * / g, '|' ) ;
180+ normalized = normalized . replace ( / \( \s * / g, '(' ) ;
181+ normalized = normalized . replace ( / \s * \) / g, ')' ) ;
182+ return normalized . trim ( ) ;
183+ }
184+
185+ private isQuotedValue ( value : string ) : boolean {
186+ return ( value . startsWith ( '"' ) && value . endsWith ( '"' ) ) || ( value . startsWith ( "'" ) && value . endsWith ( "'" ) ) ;
187+ }
188+
189+ private trimQuotes ( value : string ) : string {
190+ return this . isQuotedValue ( value ) ? value . substring ( 1 , value . length - 1 ) : value ;
191+ }
192+
172193 getNodeType ( ) : number {
173194 return Constants . ATTRIBUTE_LIST_DECL_NODE ;
174195 }
0 commit comments