package tyxml-syntax

  1. Overview
  2. Docs

Source file attributes.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
(* TyXML
 * http://www.ocsigen.org/tyxml
 * Copyright (C) 2016 Anton Bachin
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, with linking exception;
 * either version 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1301, USA.
*)

let parse loc (language, element_name) attributes =
  let (module Reflected) =
    Namespace.get language in

  (* For prefix ["prefix"] and attribute names ["prefix-foo"], evaluates to
     [Some "foo"], otherwise evaluates to [None].

     Used to parse user-data attributes (prefixed by "data-") and ARIA
     attributes (prefixed by "aria-").
  *)
  let parse_prefixed prefix name =
    let length = String.length prefix in

    let is_prefixed =
      try String.sub name 0 length = prefix
      with Invalid_argument _ -> false
    in

    if not is_prefixed then None
    else Some (String.sub name length (String.length name - length))
  in

  (* Applied to each attribute. Accumulates individually labeled attributes,
     such as img/src, in "labeled," and attributes passed in ~a in "regular." *)
  let parse_attribute (labeled, regular) ((_, local_name), value) =
    (* Convert the markup name of the attribute to a TyXML name without regard
       to renamed attributes such as "a_input_max." Renaming will be accounted
       for later. *)
    let tyxml_name = Name_convention.attrib local_name in

    let test_labeled (e, a, _) = e = element_name && a = local_name in
    let test_blacklisted (a, _, _) = a = tyxml_name in
    let test_renamed (_, a, es) = a = local_name && List.mem element_name es in

    let unknown () =
      Common.error loc "Unknown attribute in %s element: %s"
        (Common.lang language) local_name
    in

    (* Check whether this attribute is individually labeled. Parse its argument
       and accumulate the attribute if so. *)
    match Common.find test_labeled Reflected.labeled_attributes with
    | Some (_, label, parser) ->
      let e =
        match parser language loc local_name value with
        | None ->
          Common.error loc
            "Internal error: labeled attribute %s without an argument" label
        | Some e -> e
      in

      (Common.Label.labelled label, e)::labeled, regular

    | None ->
      (* The attribute is not individually labeled, so it is passed in ~a.

         First, check if the default TyXML name of this attribute collides with
         the TyXML name of a renamed attribute. For example, if the language is
         HTML, and this attribute has markup name "input-max" (which is
         invalid), then its default TyXML name will be "a_input_max", which is a
         *valid* value in TyXML. We want to avoid mapping "input-max" to
         "a_input_max", because "input-max" is invalid, and because
         "a_input_max" maps to "max" instead. *)
      if List.exists test_blacklisted Reflected.renamed_attributes then
        unknown ()
      else
        let parse_prefixed_attribute tag tyxml_name =
          let parser =
            try List.assoc tyxml_name Reflected.attribute_parsers
            with Not_found ->
              Common.error loc "Internal error: no parser for %s" tyxml_name
          in

          let identifier = Common.make ~loc language tyxml_name in
          let tag = Common.string loc tag in

          let e =
            match parser language loc local_name value with
            | Some e' -> [%expr [%e identifier] [%e tag] [%e e']] [@metaloc loc]
            | None ->
              Common.error loc "Internal error: no expression for %s"
                tyxml_name
          in

          labeled, e::regular
        in

        (* Check if this is a "data-foo" or "aria-foo" attribute. Parse the
           attribute value, and accumulate it in the list of attributes passed
           in ~a. *)
        match parse_prefixed "data-" local_name,
              parse_prefixed "aria-" local_name
        with
        | Some tag, _ -> parse_prefixed_attribute tag "a_user_data"
        | _, Some tag -> parse_prefixed_attribute tag "a_aria"
        | None, None ->
          let tyxml_name =
            match Common.find test_renamed Reflected.renamed_attributes with
            | Some (name, _, _) -> name
            | None -> tyxml_name
          in

          let parser =
            try List.assoc tyxml_name Reflected.attribute_parsers
            with Not_found -> unknown ()
          in

          let identifier = Common.make ~loc language tyxml_name in

          let e =
            match parser language loc local_name value with
            | None -> identifier
            | Some e' -> [%expr [%e identifier] [%e e']] [@metaloc loc]
          in

          labeled, e::regular
  in

  let labeled, regular =
    List.fold_left parse_attribute ([], []) attributes in

  (* If there are any attributes to pass in ~a, assemble them into a parse tree
     for a list, and prefix that with the ~a label. *)
  if regular = [] then List.rev labeled
  else
    let regular =
      Common.Label.labelled "a",
      Common.list loc (List.rev regular)
    in
    List.rev (regular::labeled)
OCaml

Innovation. Community. Security.