-module(css_select@parser). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/css_select/parser.gleam"). -export([new/0, parse/2]). -export_type([parser/0, parse_error/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -opaque parser() :: {parser, splitter:splitter(), splitter:splitter(), splitter:splitter()}. -type parse_error() :: {parse_error, binary()}. -file("src/css_select/parser.gleam", 32). ?DOC( " Create a new parser instance with pre-built splitters.\n" "\n" " Use this when parsing multiple selectors to avoid the overhead\n" " of creating splitters on each call.\n" "\n" " ```gleam\n" " let parser = parser.new()\n" " parser.parse_simple_selector_with_parser(parser, \"div#foo\")\n" " parser.parse_simple_selector_with_parser(parser, \".bar\")\n" " ```\n" ). -spec new() -> parser(). new() -> {parser, splitter:new([<<"."/utf8>>, <<"#"/utf8>>, <<"["/utf8>>, <<":"/utf8>>]), splitter:new( [<<"^="/utf8>>, <<"$="/utf8>>, <<"*="/utf8>>, <<"="/utf8>>, <<"]"/utf8>>] ), splitter:new([<<"\""/utf8>>])}. -file("src/css_select/parser.gleam", 120). -spec parse_attr_value(binary(), parser()) -> {binary(), binary()}. parse_attr_value(Input, Parser) -> case Input of <<"\""/utf8, Rest/binary>> -> {Parts, _, Remaining} = splitter_ffi:split( erlang:element(4, Parser), Rest ), case Remaining of <<"\""/utf8, After/binary>> -> {Parts, After}; _ -> {Parts, Remaining} end; _ -> splitter_ffi:split_before(erlang:element(3, Parser), Input) end. -file("src/css_select/parser.gleam", 106). -spec parse_attr_with_op( fun((binary(), binary()) -> css_select@selector:attribute_selector()), binary(), binary(), parser() ) -> {css_select@selector:attribute_selector(), binary()}. parse_attr_with_op(Constructor, Key, Input, Parser) -> {Value, After_value} = parse_attr_value(Input, Parser), case After_value of <<"]"/utf8, Remaining/binary>> -> {Constructor(Key, Value), Remaining}; _ -> {Constructor(Key, Value), After_value} end. -file("src/css_select/parser.gleam", 90). -spec parse_bracket_attr(binary(), parser()) -> {css_select@selector:attribute_selector(), binary()}. parse_bracket_attr(Input, Parser) -> {Key, Op_and_rest} = splitter_ffi:split_before( erlang:element(3, Parser), Input ), case Op_and_rest of <<"]"/utf8, Remaining/binary>> -> {{attribute_exists, Key}, Remaining}; <<"^="/utf8, Rest/binary>> -> parse_attr_with_op( fun(Field@0, Field@1) -> {attribute_prefix, Field@0, Field@1} end, Key, Rest, Parser ); <<"$="/utf8, Rest@1/binary>> -> parse_attr_with_op( fun(Field@0, Field@1) -> {attribute_suffix, Field@0, Field@1} end, Key, Rest@1, Parser ); <<"*="/utf8, Rest@2/binary>> -> parse_attr_with_op( fun(Field@0, Field@1) -> {attribute_includes, Field@0, Field@1} end, Key, Rest@2, Parser ); <<"="/utf8, Rest@3/binary>> -> parse_attr_with_op( fun(Field@0, Field@1) -> {attribute_equal, Field@0, Field@1} end, Key, Rest@3, Parser ); _ -> {{attribute_exists, Key}, Op_and_rest} end. -file("src/css_select/parser.gleam", 64). -spec parse_attrs(binary(), parser()) -> list(css_select@selector:attribute_selector()). parse_attrs(Input, Parser) -> case Input of <<""/utf8>> -> []; <<"."/utf8, Rest/binary>> -> {Name, Remaining} = splitter_ffi:split_before( erlang:element(2, Parser), Rest ), [{class, Name} | parse_attrs(Remaining, Parser)]; <<"#"/utf8, Rest@1/binary>> -> {Name@1, Remaining@1} = splitter_ffi:split_before( erlang:element(2, Parser), Rest@1 ), [{id, Name@1} | parse_attrs(Remaining@1, Parser)]; <<":"/utf8, Rest@2/binary>> -> {Name@2, Remaining@2} = splitter_ffi:split_before( erlang:element(2, Parser), Rest@2 ), [{psuedo, Name@2} | parse_attrs(Remaining@2, Parser)]; <<"["/utf8, Rest@3/binary>> -> {Attr, Remaining@3} = parse_bracket_attr(Rest@3, Parser), [Attr | parse_attrs(Remaining@3, Parser)]; _ -> [] end. -file("src/css_select/parser.gleam", 49). ?DOC( " Parse a CSS selector string using a pre-created `Parser`.\n" "\n" " This avoids the overhead of creating splitters on each call.\n" " Create a `Parser` once with `new()` and reuse it for all parsing.\n" ). -spec parse(parser(), binary()) -> {ok, css_select@selector:selector()} | {error, parse_error()}. parse(Parser, Input) -> {Tag_part, Rest} = splitter_ffi:split_before( erlang:element(2, Parser), Input ), Tag = case Tag_part of <<""/utf8>> -> any; _ -> {tag, Tag_part} end, Attrs = parse_attrs(Rest, Parser), {ok, {element_selector, Tag, Attrs}}.