···11+(** SVG attribute and element validation checker.
22+33+ Validates SVG elements and attributes according to SVG 1.1/2 specifications. *)
44+55+type state = {
66+ mutable in_svg : bool;
77+ mutable element_stack : string list;
88+}
99+1010+let create () = { in_svg = false; element_stack = [] }
1111+let reset state = state.in_svg <- false; state.element_stack <- []
1212+1313+(* SVG namespace - the DOM stores this as "svg" shorthand *)
1414+let svg_ns = "svg"
1515+1616+(* Full SVG namespace URL for validation *)
1717+let svg_ns_url = "http://www.w3.org/2000/svg"
1818+1919+(* Global SVG attributes allowed on all elements *)
2020+let global_svg_attrs = [
2121+ "id"; "class"; "style"; "tabindex"; "lang"; "xml:lang"; "xml:space";
2222+ "requiredExtensions"; "requiredFeatures"; "systemLanguage";
2323+ (* XLink attributes *)
2424+ "xlink:href"; "xlink:type"; "xlink:role"; "xlink:arcrole"; "xlink:title";
2525+ "xlink:show"; "xlink:actuate";
2626+ (* Event attributes *)
2727+ "onload"; "onunload"; "onabort"; "onerror"; "onresize"; "onscroll"; "onzoom";
2828+ "onfocusin"; "onfocusout"; "onactivate"; "onclick"; "onmousedown"; "onmouseup";
2929+ "onmouseover"; "onmousemove"; "onmouseout"; "onbegin"; "onend"; "onrepeat";
3030+ (* Presentation attributes - comprehensive list *)
3131+ "alignment-baseline"; "baseline-shift";
3232+ "clip"; "clip-path"; "clip-rule"; "color"; "color-interpolation"; "color-interpolation-filters";
3333+ "color-profile"; "color-rendering";
3434+ "cursor"; "direction"; "display"; "dominant-baseline";
3535+ "enable-background";
3636+ "fill"; "fill-opacity"; "fill-rule"; "filter";
3737+ "flood-color"; "flood-opacity"; "font-family"; "font-size"; "font-size-adjust";
3838+ "font-stretch"; "font-style"; "font-variant"; "font-weight";
3939+ "glyph-orientation-horizontal"; "glyph-orientation-vertical";
4040+ "image-rendering";
4141+ "kerning";
4242+ "letter-spacing"; "lighting-color";
4343+ "marker"; "marker-end"; "marker-mid"; "marker-start"; "mask";
4444+ "opacity"; "overflow";
4545+ "pointer-events";
4646+ "shape-rendering";
4747+ "stop-color"; "stop-opacity"; "stroke"; "stroke-dasharray"; "stroke-dashoffset";
4848+ "stroke-linecap"; "stroke-linejoin"; "stroke-miterlimit"; "stroke-opacity";
4949+ "stroke-width";
5050+ "text-anchor"; "text-decoration"; "text-rendering";
5151+ "transform"; "transform-origin";
5252+ "unicode-bidi";
5353+ "vector-effect"; "visibility";
5454+ "word-spacing"; "writing-mode";
5555+ (* Data attributes *)
5656+ "data-*";
5757+ (* ARIA attributes *)
5858+ "role"; "aria-activedescendant"; "aria-atomic"; "aria-autocomplete"; "aria-busy";
5959+ "aria-checked"; "aria-colcount"; "aria-colindex"; "aria-colspan"; "aria-controls";
6060+ "aria-current"; "aria-describedby"; "aria-details"; "aria-disabled"; "aria-dropeffect";
6161+ "aria-errormessage"; "aria-expanded"; "aria-flowto"; "aria-grabbed"; "aria-haspopup";
6262+ "aria-hidden"; "aria-invalid"; "aria-keyshortcuts"; "aria-label"; "aria-labelledby";
6363+ "aria-level"; "aria-live"; "aria-modal"; "aria-multiline"; "aria-multiselectable";
6464+ "aria-orientation"; "aria-owns"; "aria-placeholder"; "aria-posinset"; "aria-pressed";
6565+ "aria-readonly"; "aria-relevant"; "aria-required"; "aria-roledescription"; "aria-rowcount";
6666+ "aria-rowindex"; "aria-rowspan"; "aria-selected"; "aria-setsize"; "aria-sort";
6767+ "aria-valuemax"; "aria-valuemin"; "aria-valuenow"; "aria-valuetext";
6868+]
6969+7070+(* Element-specific attributes *)
7171+let element_attrs = [
7272+ ("svg", ["xmlns"; "xmlns:xlink"; "version"; "baseProfile"; "x"; "y"; "width"; "height";
7373+ "viewBox"; "preserveAspectRatio"; "zoomAndPan"; "contentScriptType";
7474+ "contentStyleType"]);
7575+ ("g", ["transform"]);
7676+ ("defs", []);
7777+ ("symbol", ["viewBox"; "preserveAspectRatio"; "refX"; "refY"]);
7878+ ("use", ["href"; "xlink:href"; "x"; "y"; "width"; "height"]);
7979+ ("image", ["href"; "xlink:href"; "x"; "y"; "width"; "height"; "preserveAspectRatio";
8080+ "crossorigin"; "decoding"]);
8181+ ("switch", []);
8282+ ("foreignObject", ["x"; "y"; "width"; "height"]);
8383+8484+ (* Shape elements *)
8585+ ("circle", ["cx"; "cy"; "r"; "pathLength"]);
8686+ ("ellipse", ["cx"; "cy"; "rx"; "ry"; "pathLength"]);
8787+ ("line", ["x1"; "y1"; "x2"; "y2"; "pathLength"]);
8888+ ("polygon", ["points"; "pathLength"]);
8989+ ("polyline", ["points"; "pathLength"]);
9090+ ("rect", ["x"; "y"; "width"; "height"; "rx"; "ry"; "pathLength"]);
9191+ ("path", ["d"; "pathLength"]);
9292+9393+ (* Text elements *)
9494+ ("text", ["x"; "y"; "dx"; "dy"; "rotate"; "textLength"; "lengthAdjust"]);
9595+ ("tspan", ["x"; "y"; "dx"; "dy"; "rotate"; "textLength"; "lengthAdjust"]);
9696+ ("textPath", ["href"; "xlink:href"; "startOffset"; "method"; "spacing"; "path"; "side"]);
9797+9898+ (* Gradient elements *)
9999+ ("linearGradient", ["gradientUnits"; "gradientTransform"; "spreadMethod";
100100+ "href"; "xlink:href"; "x1"; "y1"; "x2"; "y2"]);
101101+ ("radialGradient", ["gradientUnits"; "gradientTransform"; "spreadMethod";
102102+ "href"; "xlink:href"; "cx"; "cy"; "r"; "fx"; "fy"; "fr"]);
103103+ ("stop", ["offset"]);
104104+105105+ (* Pattern *)
106106+ ("pattern", ["patternUnits"; "patternContentUnits"; "patternTransform";
107107+ "href"; "xlink:href"; "x"; "y"; "width"; "height"; "viewBox";
108108+ "preserveAspectRatio"]);
109109+110110+ (* Clipping and masking *)
111111+ ("clipPath", ["clipPathUnits"]);
112112+ ("mask", ["maskUnits"; "maskContentUnits"; "x"; "y"; "width"; "height"]);
113113+114114+ (* Filter elements *)
115115+ ("filter", ["filterUnits"; "primitiveUnits"; "x"; "y"; "width"; "height";
116116+ "href"; "xlink:href"]);
117117+ ("feBlend", ["in"; "in2"; "mode"; "x"; "y"; "width"; "height"; "result"]);
118118+ ("feColorMatrix", ["in"; "type"; "values"; "x"; "y"; "width"; "height"; "result"]);
119119+ ("feComponentTransfer", ["in"; "x"; "y"; "width"; "height"; "result"]);
120120+ ("feFuncR", ["type"; "tableValues"; "slope"; "intercept"; "amplitude"; "exponent"; "offset"]);
121121+ ("feFuncG", ["type"; "tableValues"; "slope"; "intercept"; "amplitude"; "exponent"; "offset"]);
122122+ ("feFuncB", ["type"; "tableValues"; "slope"; "intercept"; "amplitude"; "exponent"; "offset"]);
123123+ ("feFuncA", ["type"; "tableValues"; "slope"; "intercept"; "amplitude"; "exponent"; "offset"]);
124124+ ("feComposite", ["in"; "in2"; "operator"; "k1"; "k2"; "k3"; "k4"; "x"; "y"; "width"; "height"; "result"]);
125125+ ("feConvolveMatrix", ["in"; "order"; "kernelMatrix"; "divisor"; "bias"; "targetX"; "targetY";
126126+ "edgeMode"; "preserveAlpha"; "x"; "y"; "width"; "height"; "result"]);
127127+ ("feDiffuseLighting", ["in"; "surfaceScale"; "diffuseConstant"; "x"; "y"; "width"; "height"; "result"]);
128128+ ("feDisplacementMap", ["in"; "in2"; "scale"; "xChannelSelector"; "yChannelSelector";
129129+ "x"; "y"; "width"; "height"; "result"]);
130130+ ("feDropShadow", ["in"; "dx"; "dy"; "stdDeviation"; "x"; "y"; "width"; "height"; "result"]);
131131+ ("feFlood", ["x"; "y"; "width"; "height"; "result"]);
132132+ ("feGaussianBlur", ["in"; "stdDeviation"; "edgeMode"; "x"; "y"; "width"; "height"; "result"]);
133133+ ("feImage", ["href"; "xlink:href"; "preserveAspectRatio"; "crossorigin";
134134+ "x"; "y"; "width"; "height"; "result"]);
135135+ ("feMerge", ["x"; "y"; "width"; "height"; "result"]);
136136+ ("feMergeNode", ["in"]);
137137+ ("feMorphology", ["in"; "operator"; "radius"; "x"; "y"; "width"; "height"; "result"]);
138138+ ("feOffset", ["in"; "dx"; "dy"; "x"; "y"; "width"; "height"; "result"]);
139139+ ("fePointLight", ["x"; "y"; "z"]);
140140+ ("feSpecularLighting", ["in"; "surfaceScale"; "specularConstant"; "specularExponent";
141141+ "x"; "y"; "width"; "height"; "result"]);
142142+ ("feSpotLight", ["x"; "y"; "z"; "pointsAtX"; "pointsAtY"; "pointsAtZ";
143143+ "specularExponent"; "limitingConeAngle"]);
144144+ ("feTile", ["in"; "x"; "y"; "width"; "height"; "result"]);
145145+ ("feTurbulence", ["type"; "baseFrequency"; "numOctaves"; "seed"; "stitchTiles";
146146+ "x"; "y"; "width"; "height"; "result"]);
147147+148148+ (* Marker *)
149149+ ("marker", ["viewBox"; "preserveAspectRatio"; "refX"; "refY"; "markerUnits";
150150+ "markerWidth"; "markerHeight"; "orient"]);
151151+152152+ (* Descriptive elements *)
153153+ ("title", []);
154154+ ("desc", []);
155155+ ("metadata", []);
156156+157157+ (* Animation elements *)
158158+ ("animate", ["attributeName"; "attributeType"; "from"; "to"; "by"; "values";
159159+ "begin"; "dur"; "end"; "min"; "max"; "restart"; "repeatCount";
160160+ "repeatDur"; "fill"; "calcMode"; "keyTimes"; "keySplines";
161161+ "additive"; "accumulate"; "href"; "xlink:href"]);
162162+ ("animateMotion", ["path"; "keyPoints"; "rotate"; "origin";
163163+ "begin"; "dur"; "end"; "min"; "max"; "restart"; "repeatCount";
164164+ "repeatDur"; "fill"; "calcMode"; "keyTimes"; "keySplines";
165165+ "additive"; "accumulate"; "href"; "xlink:href"]);
166166+ ("animateTransform", ["attributeName"; "attributeType"; "type"; "from"; "to"; "by"; "values";
167167+ "begin"; "dur"; "end"; "min"; "max"; "restart"; "repeatCount";
168168+ "repeatDur"; "fill"; "calcMode"; "keyTimes"; "keySplines";
169169+ "additive"; "accumulate"; "href"; "xlink:href"]);
170170+ ("set", ["attributeName"; "attributeType"; "to";
171171+ "begin"; "dur"; "end"; "min"; "max"; "restart"; "repeatCount";
172172+ "repeatDur"; "fill"; "href"; "xlink:href"]);
173173+ ("mpath", ["href"; "xlink:href"]);
174174+175175+ (* Font elements (deprecated but still valid SVG 1.1) *)
176176+ ("font", ["horiz-origin-x"; "horiz-origin-y"; "horiz-adv-x"; "vert-origin-x";
177177+ "vert-origin-y"; "vert-adv-y"]);
178178+ ("font-face", ["font-family"; "font-style"; "font-variant"; "font-weight";
179179+ "font-stretch"; "font-size"; "unicode-range"; "units-per-em";
180180+ "panose-1"; "stemv"; "stemh"; "slope"; "cap-height"; "x-height";
181181+ "accent-height"; "ascent"; "descent"; "widths"; "bbox";
182182+ "ideographic"; "alphabetic"; "mathematical"; "hanging";
183183+ "v-ideographic"; "v-alphabetic"; "v-mathematical"; "v-hanging";
184184+ "underline-position"; "underline-thickness"; "strikethrough-position";
185185+ "strikethrough-thickness"; "overline-position"; "overline-thickness"]);
186186+ ("font-face-src", []);
187187+ ("font-face-uri", ["href"; "xlink:href"]);
188188+ ("font-face-format", ["string"]);
189189+ ("font-face-name", ["name"]);
190190+ ("glyph", ["unicode"; "glyph-name"; "d"; "orientation"; "arabic-form"; "lang";
191191+ "horiz-adv-x"; "vert-origin-x"; "vert-origin-y"; "vert-adv-y"]);
192192+ ("missing-glyph", ["d"; "horiz-adv-x"; "vert-origin-x"; "vert-origin-y"; "vert-adv-y"]);
193193+ ("hkern", ["u1"; "g1"; "u2"; "g2"; "k"]);
194194+ ("vkern", ["u1"; "g1"; "u2"; "g2"; "k"]);
195195+196196+ (* Link *)
197197+ ("a", ["href"; "xlink:href"; "target"; "download"; "ping"; "rel"; "hreflang"; "type";
198198+ "referrerpolicy"]);
199199+200200+ (* Script and style *)
201201+ ("script", ["href"; "xlink:href"; "type"; "crossorigin"]);
202202+ ("style", ["type"; "media"; "title"]);
203203+204204+ (* View *)
205205+ ("view", ["viewBox"; "preserveAspectRatio"; "zoomAndPan"; "viewTarget"]);
206206+]
207207+208208+(* Required attributes for certain elements *)
209209+let required_attrs = [
210210+ ("feConvolveMatrix", ["order"]);
211211+ ("rect", ["width"; "height"]);
212212+ ("font", ["horiz-adv-x"]);
213213+]
214214+215215+(* Attributes that are NOT allowed on specific elements - overrides global/element attrs *)
216216+(* NOTE: Element names must be lowercase for lookup to work *)
217217+let disallowed_attrs = [
218218+ (* fill/stroke not valid on image *)
219219+ ("image", ["fill"; "fill-opacity"; "fill-rule"]);
220220+ (* stop-color/stop-opacity only valid on stop element and containers for inheritance *)
221221+ ("rect", ["stop-color"; "stop-opacity"]);
222222+ (* marker shorthand not valid on container elements *)
223223+ ("g", ["marker"]);
224224+ ("svg", ["marker"; "contentScriptType"; "contentStyleType"]);
225225+ (* x,y not valid on clipPath *)
226226+ ("clippath", ["x"; "y"; "width"; "height"]);
227227+]
228228+229229+(* Required child elements - for future use *)
230230+let _required_children = [
231231+ ("font", ["missing-glyph"]);
232232+]
233233+234234+(* Check if attribute name matches a pattern like "data-*" or "aria-*" - case insensitive *)
235235+let matches_pattern attr pattern =
236236+ let attr_lower = String.lowercase_ascii attr in
237237+ let pattern_lower = String.lowercase_ascii pattern in
238238+ if String.ends_with ~suffix:"-*" pattern_lower then
239239+ let prefix = String.sub pattern_lower 0 (String.length pattern_lower - 1) in
240240+ String.starts_with ~prefix attr_lower
241241+ else
242242+ attr_lower = pattern_lower
243243+244244+(* Check if attribute is valid for element *)
245245+let is_valid_attr element attr =
246246+ (* First check if this attribute is specifically disallowed on this element *)
247247+ (match List.assoc_opt element disallowed_attrs with
248248+ | Some disallowed ->
249249+ if List.exists (matches_pattern attr) disallowed then false
250250+ else true
251251+ | None -> true) &&
252252+ (* Then check global attrs *)
253253+ (if List.exists (matches_pattern attr) global_svg_attrs then true
254254+ else
255255+ (* Check element-specific attrs *)
256256+ match List.assoc_opt element element_attrs with
257257+ | Some attrs -> List.exists (matches_pattern attr) attrs
258258+ | None ->
259259+ (* Unknown SVG element - be permissive *)
260260+ true)
261261+262262+(* Validate xmlns attributes *)
263263+let validate_xmlns_attr attr value element collector =
264264+ match attr with
265265+ | "xmlns" when element = "svg" ->
266266+ if value <> svg_ns_url then
267267+ Message_collector.add_error collector
268268+ ~message:(Printf.sprintf
269269+ "Bad value \xe2\x80\x9c%s\xe2\x80\x9d for the attribute \xe2\x80\x9cxmlns\xe2\x80\x9d (only \xe2\x80\x9c%s\xe2\x80\x9d permitted here)."
270270+ value svg_ns_url)
271271+ ~element
272272+ ~attribute:attr
273273+ ()
274274+ | "xmlns:xlink" ->
275275+ if value <> "http://www.w3.org/1999/xlink" then
276276+ Message_collector.add_error collector
277277+ ~message:(Printf.sprintf
278278+ "Bad value \xe2\x80\x9c%s\xe2\x80\x9d for the attribute \xe2\x80\x9cxmlns:xlink\xe2\x80\x9d (only \xe2\x80\x9chttp://www.w3.org/1999/xlink\xe2\x80\x9d permitted here)."
279279+ value)
280280+ ~element
281281+ ~attribute:attr
282282+ ()
283283+ | _ when String.starts_with ~prefix:"xmlns:" attr && attr <> "xmlns:xlink" ->
284284+ (* Other xmlns declarations are not allowed in HTML-embedded SVG *)
285285+ Message_collector.add_error collector
286286+ ~message:(Printf.sprintf "Attribute \xe2\x80\x9c%s\xe2\x80\x9d not allowed here." attr)
287287+ ~element
288288+ ~attribute:attr
289289+ ()
290290+ | _ -> ()
291291+292292+(* Validate SVG path data *)
293293+let validate_path_data d element collector =
294294+ (* Simple path data validation - check for obviously invalid characters *)
295295+ let len = String.length d in
296296+ let i = ref 0 in
297297+ let context_start = ref 0 in
298298+ while !i < len do
299299+ let c = d.[!i] in
300300+ match c with
301301+ | 'M' | 'm' | 'L' | 'l' | 'H' | 'h' | 'V' | 'v' | 'C' | 'c' | 'S' | 's'
302302+ | 'Q' | 'q' | 'T' | 't' | 'A' | 'a' | 'Z' | 'z'
303303+ | '0'..'9' | '.' | '-' | '+' | ',' | ' ' | '\t' | '\n' | '\r' | 'e' | 'E' ->
304304+ incr i
305305+ | '#' ->
306306+ let ctx_end = min (String.length d) (!i + 1) in
307307+ let context = String.sub d !context_start (ctx_end - !context_start) in
308308+ Message_collector.add_error collector
309309+ ~message:(Printf.sprintf
310310+ "Bad value \xe2\x80\x9c%s\xe2\x80\x9d for attribute \xe2\x80\x9cd\xe2\x80\x9d on element \xe2\x80\x9c%s\xe2\x80\x9d: Bad SVG path data: Expected command but found \xe2\x80\x9c#\xe2\x80\x9d (context: \xe2\x80\x9c%s\xe2\x80\x9d)."
311311+ d element context)
312312+ ~element
313313+ ~attribute:"d"
314314+ ();
315315+ i := len (* Stop processing *)
316316+ | _ ->
317317+ incr i
318318+ done;
319319+ (* Check arc command flags - they must be 0 or 1 *)
320320+ (* This is a simplified check - look for 'a' or 'A' followed by numbers *)
321321+ let arc_re = Str.regexp "[aA][ \t\n]*\\([0-9.+-]+\\)[ \t\n,]*\\([0-9.+-]+\\)[ \t\n,]*\\([0-9.+-]+\\)[ \t\n,]*\\([0-9]+\\)" in
322322+ try
323323+ let _ = Str.search_forward arc_re d 0 in
324324+ let flag = Str.matched_group 4 d in
325325+ if flag <> "0" && flag <> "1" then begin
326326+ let pos = Str.match_beginning () in
327327+ let ctx_end = min (String.length d) (pos + 25) in
328328+ let ctx_start = max 0 (pos - 10) in
329329+ let context = String.sub d ctx_start (ctx_end - ctx_start) in
330330+ Message_collector.add_error collector
331331+ ~message:(Printf.sprintf
332332+ "Bad value \xe2\x80\x9c%s\xe2\x80\x9d for attribute \xe2\x80\x9cd\xe2\x80\x9d on element \xe2\x80\x9c%s\xe2\x80\x9d: Bad SVG path data: Expected \xe2\x80\x9c0\xe2\x80\x9d or \xe2\x80\x9c1\xe2\x80\x9d for large-arc-flag for \xe2\x80\x9ca\xe2\x80\x9d command but found \xe2\x80\x9c%s\xe2\x80\x9d instead (context: \xe2\x80\x9c%s\xe2\x80\x9d)."
333333+ d element flag context)
334334+ ~element
335335+ ~attribute:"d"
336336+ ()
337337+ end
338338+ with Not_found -> ()
339339+340340+let start_element state ~name ~namespace ~attrs collector =
341341+ let is_svg_element = namespace = Some svg_ns in
342342+343343+ (* Track if we're in SVG context *)
344344+ if name = "svg" && is_svg_element then
345345+ state.in_svg <- true;
346346+347347+ if is_svg_element || state.in_svg then begin
348348+ state.element_stack <- name :: state.element_stack;
349349+350350+ let name_lower = String.lowercase_ascii name in
351351+352352+ (* Check each attribute *)
353353+ List.iter (fun (attr, value) ->
354354+ let attr_lower = String.lowercase_ascii attr in
355355+356356+ (* Validate xmlns attributes *)
357357+ if String.starts_with ~prefix:"xmlns" attr_lower then
358358+ validate_xmlns_attr attr_lower value name_lower collector
359359+ (* Check xml:* attributes - most are not allowed *)
360360+ else if attr_lower = "xml:id" || attr_lower = "xml:base" then
361361+ Message_collector.add_error collector
362362+ ~message:(Printf.sprintf
363363+ "Attribute \xe2\x80\x9c%s\xe2\x80\x9d not allowed on element \xe2\x80\x9c%s\xe2\x80\x9d at this point."
364364+ attr name_lower)
365365+ ~element:name_lower
366366+ ~attribute:attr_lower
367367+ ()
368368+ (* Validate path data *)
369369+ else if attr_lower = "d" && name_lower = "path" then
370370+ validate_path_data value name_lower collector
371371+ (* Check if attribute is valid for this element *)
372372+ else if not (is_valid_attr name_lower attr_lower) then
373373+ Message_collector.add_error collector
374374+ ~message:(Printf.sprintf
375375+ "Attribute \xe2\x80\x9c%s\xe2\x80\x9d not allowed on element \xe2\x80\x9c%s\xe2\x80\x9d at this point."
376376+ attr name_lower)
377377+ ~element:name_lower
378378+ ~attribute:attr_lower
379379+ ()
380380+ ) attrs;
381381+382382+ (* Check required attributes *)
383383+ (match List.assoc_opt name_lower required_attrs with
384384+ | Some req_attrs ->
385385+ List.iter (fun req_attr ->
386386+ if not (List.exists (fun (a, _) -> String.lowercase_ascii a = req_attr) attrs) then
387387+ Message_collector.add_error collector
388388+ ~message:(Printf.sprintf
389389+ "Element \xe2\x80\x9c%s\xe2\x80\x9d is missing required attribute \xe2\x80\x9c%s\xe2\x80\x9d."
390390+ name_lower req_attr)
391391+ ~element:name_lower
392392+ ()
393393+ ) req_attrs
394394+ | None -> ())
395395+ end
396396+397397+let end_element state ~name ~namespace _collector =
398398+ let is_svg_element = namespace = Some svg_ns in
399399+400400+ if is_svg_element || state.in_svg then begin
401401+ (* Pop from stack *)
402402+ (match state.element_stack with
403403+ | _ :: rest -> state.element_stack <- rest
404404+ | [] -> ());
405405+406406+ (* Exit SVG context *)
407407+ if name = "svg" && is_svg_element then
408408+ state.in_svg <- false
409409+ end
410410+411411+let characters _state _text _collector = ()
412412+413413+let end_document _state _collector = ()
414414+415415+let checker =
416416+ (module struct
417417+ type nonrec state = state
418418+ let create = create
419419+ let reset = reset
420420+ let start_element = start_element
421421+ let end_element = end_element
422422+ let characters = characters
423423+ let end_document = end_document
424424+ end : Checker.S)
+5
lib/html5_checker/specialized/svg_checker.mli
···11+(** SVG attribute and element validation checker.
22+33+ Validates SVG elements and attributes according to SVG 1.1/2 specifications. *)
44+55+val checker : Checker.t
+11-17
test/debug_check.ml
···11let () =
22- let test_file = "validator/tests/xhtml/elements/menu/menu-containing-hr-novalid.xhtml" in
22+ let test_file = "validator/tests/xhtml/elements/progress/002-isvalid.xhtml" in
33 let ic = open_in test_file in
44 let html = really_input_string ic (in_channel_length ic) in
55 close_in ic;
66 let reader = Bytesrw.Bytes.Reader.of_string html in
77 let doc = Html5rw.parse ~collect_errors:true reader in
88 let root = Html5rw.root doc in
99- print_endline "=== DOM Structure ===";
99+ print_endline "=== DOM Structure (with namespaces) ===";
1010 let rec print_node indent (node : Html5rw.Dom.node) =
1111 let open Html5rw.Dom in
1212 match node.name with
1313- | "#text" ->
1414- let text = String.trim node.data in
1515- if String.length text > 0 then
1616- Printf.printf "%sTEXT: %s\n" indent text
1313+ | "#text" -> ()
1714 | "#document" | "#document-fragment" ->
1815 Printf.printf "%s%s\n" indent node.name;
1916 List.iter (print_node (indent ^ " ")) node.children
2020- | "!doctype" -> Printf.printf "%s<!DOCTYPE>\n" indent
1717+ | "!doctype" -> ()
2118 | "#comment" -> ()
2219 | _ ->
2323- Printf.printf "%s<%s>\n" indent node.name;
2020+ let ns = match node.namespace with Some ns -> ns | None -> "none" in
2121+ Printf.printf "%s<%s ns=%s>\n" indent node.name ns;
2222+ List.iter (fun (k, v) ->
2323+ if k = "foo" then Printf.printf "%s @%s=%s\n" indent k v
2424+ ) node.attrs;
2425 List.iter (print_node (indent ^ " ")) node.children
2526 in
2627 print_node "" root;
2727- print_endline "\n=== Now checking ===";
2828+ print_endline "\n=== Checking... ===";
2829 let reader2 = Bytesrw.Bytes.Reader.of_string html in
2930 let result = Html5_checker.check ~collect_parse_errors:true ~system_id:test_file reader2 in
3031 let errors = Html5_checker.errors result in
3131- let warnings = Html5_checker.warnings result in
3232 print_endline "=== Errors ===";
3333 List.iter (fun e -> print_endline e.Html5_checker.Message.message) errors;
3434- print_endline "=== Warnings ===";
3535- List.iter (fun e -> print_endline e.Html5_checker.Message.message) warnings;
3634 print_endline "\n=== Expected ===";
3737- print_endline "Element \xe2\x80\x9chr\xe2\x80\x9d not allowed as child of element \xe2\x80\x9cmenu\xe2\x80\x9d in this context. (Suppressing further errors from this subtree.)";
3838- if List.length errors > 0 then
3939- print_endline "\nPASS (has errors)"
4040- else
4141- print_endline "\nFAIL (no errors)"
3535+ print_endline "Element \xe2\x80\x9crect\xe2\x80\x9d is missing required attribute \xe2\x80\x9cheight\xe2\x80\x9d."