### ----------------------------------------------------
### -- HTML::Entity::Fast
### -- Licenses: Artistic-2.0
### -- Authors: Elizabeth Mattijsen
### -- File: lib/HTML/Entity/Fast.rakumod
### ----------------------------------------------------
### -- Chunk 3 of 3
ilibrium;', "⇌",'&EqualTilde;', "≂",'&Equal;', "⩵",'&Epsilon;',
  "Ε",'&Eopf;', "𝔼",'&Eogon;', "Ę",'&EmptyVerySmallSquare;', "▫",
  '&EmptySmallSquare;', "◻",'&Emacr;', "Ē",'&Element;', "∈",'&Egrave;',
  "È",'&Efr;', "𝔈",'&Edot;', "Ė",'&Ecy;', "Э",'&Ecirc;', "Ê",'&Ecaron;',
  "Ě",'&Eacute;', "É",'&ETH;', "Ð",'&ENG;', "Ŋ",'&Dstrok;', "Đ",'&Dscr;',
  "𝒟",'&Downarrow;', "⇓",'&DownTeeArrow;', "↧",'&DownTee;', "⊤",
  '&DownRightVectorBar;', "⥗",'&DownRightVector;', "⇁",
  '&DownRightTeeVector;', "⥟",'&DownLeftVectorBar;', "⥖",
  '&DownLeftVector;', "↽",'&DownLeftTeeVector;', "⥞",
  '&DownLeftRightVector;', "⥐",'&DownArrowUpArrow;', "⇵",'&DownArrowBar;',
  "⤓",'&DownArrow;', "↓",'&DoubleVerticalBar;', "∥",'&DoubleUpDownArrow;',
  "⇕",'&DoubleUpArrow;', "⇑",'&DoubleRightTee;', "⊨",'&DoubleRightArrow;',
  "⇒",'&DoubleLongRightArrow;', "⟹",'&DoubleLongLeftRightArrow;', "⟺",
  '&DoubleLongLeftArrow;', "⟸",'&DoubleLeftTee;', "⫤",
  '&DoubleLeftRightArrow;', "⇔",'&DoubleLeftArrow;', "⇐",
  '&DoubleDownArrow;', "⇓",'&DoubleDot;', "¨",'&DoubleContourIntegral;',
  "∯",'&DotEqual;', "≐",'&Dot;', "¨",'&Dopf;', "𝔻",'&DifferentialD;', "ⅆ",
  '&Diamond;', "⋄",'&DiacriticalTilde;', "˜",'&DiacriticalGrave;', "`",
  '&DiacriticalDoubleAcute;', "˝",'&DiacriticalDot;', "˙",
  '&DiacriticalAcute;', "´",'&Dfr;', "𝔇",'&Delta;', "Δ",'&Del;', "∇",
  '&Dcy;', "Д",'&Dcaron;', "Ď",'&Dashv;', "⫤",'&Darr;', "↡",'&Dagger;',
  "‡",'&DZcy;', "Џ",'&DScy;', "Ѕ",'&DJcy;', "Ђ",'&DDotrahd;', "⤑",'&DD;',
  "ⅅ",'&CupCap;', "≍",'&Cup;', "⋓",'&Cscr;', "𝒞",'&Cross;', "⨯",
  '&CounterClockwiseContourIntegral;', "∳",'&Coproduct;', "∐",'&Copf;',
  "ℂ",'&ContourIntegral;', "∮",'&Conint;', "∯",'&Congruent;', "≡",
  '&Colone;', "⩴",'&Colon;', "∷",'&CloseCurlyQuote;', "’",
  '&CloseCurlyDoubleQuote;', "”",'&ClockwiseContourIntegral;', "∲",
  '&CircleTimes;', "⊗",'&CirclePlus;', "⊕",'&CircleMinus;', "⊖",
  '&CircleDot;', "⊙",'&Chi;', "Χ",'&Cfr;', "ℭ",'&CenterDot;', "·",
  '&Cedilla;', "¸",'&Cdot;', "Ċ",'&Cconint;', "∰",'&Ccirc;', "Ĉ",
  '&Ccedil;', "Ç",'&Ccaron;', "Č",'&Cayleys;', "ℭ",
  '&CapitalDifferentialD;', "ⅅ",'&Cap;', "⋒",'&Cacute;', "Ć",'&COPY;', "©",
  '&CHcy;', "Ч",'&Bumpeq;', "≎",'&Bscr;', "ℬ",'&Breve;', "˘",'&Bopf;', "𝔹",
  '&Bfr;', "𝔅",'&Beta;', "Β",'&Bernoullis;', "ℬ",'&Because;', "∵",'&Bcy;',
  "Б",'&Barwed;', "⌆",'&Barv;', "⫧",'&Backslash;', "∖",'&Auml;', "Ä",
  '&Atilde;', "Ã",'&Assign;', "≔",'&Ascr;', "𝒜",'&Aring;', "Å",
  '&ApplyFunction;', "⁡",'&Aopf;', "𝔸",'&Aogon;', "Ą",'&And;', "⩓",
  '&Amacr;', "Ā",'&Alpha;', "Α",'&Agrave;', "À",'&Afr;', "𝔄",'&Acy;', "А",
  '&Acirc;', "Â",'&Abreve;', "Ă",'&Aacute;', "Á",'&AMP;', "\&",'&AElig;',
  "Æ";
#- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE
#- end of generated part of entities -------------------------------------------

my constant %encode = do {
    # chars we don't want to encode
    my int8 @dont-encode;
    @dont-encode[$_] = 1 for ("a".."z").map: *.ord;
    @dont-encode[$_] = 1 for ("A".."Z").map: *.ord;
    @dont-encode[$_] = 1 for ("0".."9").map: *.ord;

    my %h;
    %h{.value}.push(.key) unless @dont-encode[.value.ord] for %decode;
    $_ = .sort(*.chars).head.lc for %h.values;  # favor shortest lowercase
    %h.Map
}

# Naughty from here on
use nqp;

# NQP access to hashes
my $decode := nqp::getattr(%decode,Map,'$!storage');
my $encode := nqp::getattr(%encode,Map,'$!storage');

my sub whitelist-in-encode(*@whitelist) is export {
    my $whitelisted := nqp::clone($encode);
    nqp::deletekey($whitelisted, $_) for @whitelist;
    $whitelisted
}

my sub encode-html-entities(str $source, $Lookup = $encode) is export {
    my $lookup := nqp::decont($Lookup);

    my int $i     = -1;
    my int $from;
    my int $chars = nqp::chars($source);

    my $parts := nqp::list_s;
    my $entity;

    nqp::while(
      nqp::islt_i(++$i,$chars),
      nqp::unless(
        nqp::isnull($entity := nqp::atkey($lookup,nqp::substr($source,$i,1))),
        nqp::stmts(
          nqp::push_s($parts,nqp::substr($source,$from,$i - $from)),
          nqp::push_s($parts,$entity),
          ($from = $i + 1)
        )
      )
    );
    nqp::elems($parts)
      ?? nqp::join('',$parts) ~ nqp::substr($source,$from)
      !! $source
}

my sub whitelist-in-decode(*@whitelist) is export {
    my $whitelisted := nqp::clone($decode);
    nqp::deletekey($whitelisted, nqp::lc($_)) for @whitelist;
    $whitelisted
}

my sub decode-html-entities(str $source, $Lookup = $decode) is export {
    my $lookup := nqp::decont($Lookup);

    my int $chars = nqp::chars($source);
    my int $from;
    my int $start;
    my int $end;
    my     $replacement;
    my $parts := nqp::list_s;

    # Handle numerical entities
    sub numerical() {
        nqp::push_s($parts,nqp::substr($source,$from,$start - $from));

        my $radix := nqp::eqatic($source,'x',$start + 2)
          ?? nqp::radix(16,$source,$start + 3,0)
          !! nqp::radix(10,$source,$start + 2,0);

        nqp::push_s($parts, nqp::chr(nqp::atpos($radix,0)));
        $from = $end + 1;
    }

    nqp::until(
      nqp::iseq_i(($start = nqp::index($source,'&',$from)),-1),
      nqp::if(
        nqp::iseq_i(($end = nqp::index($source,';',$start)),-1),
        ($from = $start + 1),
        nqp::if(
          nqp::isnull($replacement := nqp::atkey(
            $lookup,nqp::lc(nqp::substr($source,$start,$end - $start + 1))
          )),
          nqp::if(       # meh, unknown entity
            nqp::eqat($source,'#',$start + 1),
            numerical(), # looks like a numerical
            nqp::stmts(  # still unknown
              nqp::push_s($parts,nqp::substr($source,$from,$start - $from + 1)),
              ($from = $start + 1)
            )
          ),
          nqp::stmts(    # known entity
            nqp::push_s($parts,nqp::substr($source,$from,$start - $from)),
            nqp::push_s($parts,$replacement),
            ($from = $end + 1)
          )
        )
      )
    );

    $from
      ?? nqp::concat(nqp::join('',$parts),nqp::substr($source,$from))
      !! $source
}

=begin pod

=head1 NAME

HTML::Entity::Fast - Encode / Decode HTML entities faster

=head1 SYNOPSIS

=begin code :lang<raku>

use HTML::Entity::Fast;

say encode-html-entities '<ent> & ©';                 # &lt;ent&gt; &amp; &copy;
say decode-html-entities '&lt;ent&gt; &amp; &copy;';  # <ent> & ©

my $w-encode = whitelist-in-encode("<",">");
say encode-html-entities '<ent> & ©', $w-encode;                 # <ent> &amp; &copy;

my $w-decode = whitelist-in-decode('&amp;');
say decode-html-entities '&lt;ent&gt; &amp; &copy;', $w-decode;  # <ent> &amp; ©

=end code

=head1 DESCRIPTION

HTML::Entity::Fast provides subroutines for encoding and decoding HTML
entities in a string.

=head1 SUBROUTINES

=head2 encode-html-entities

=begin code :lang<raku>

say encode-html-entities '<ent> & ©';             # &lt;ent&gt; &amp; &copy;

my $w-encode = whitelist-in-encode("<",">");
say encode-html-entities '<ent> & ©', $w-encode;  # <ent> &amp; &copy;

=end code

Encode any special characters in a given string to their HTML entity
counterpart.  Optionally takes a second argument indicating the exact
mapping to be used (which is usually the result of a call to the
C<whitelist-in-encode> subroutine).

=head2 decode-html-entities

=begin code :lang<raku>

say decode-html-entities '&lt;ent&gt; &amp; &copy;';  # <ent> & ©

my $w-decode = whitelist-in-decode('&amp;');
say decode-html-entities '&lt;ent&gt; &amp; &copy;', $w-decode;  # <ent> &amp; ©

=end code

Decode any HTML entities in a given string to their Unicode counterpart.
Optionally takes a second argument indicatig the exact mapping to be used
(which is usually the result of a call to the C<whitelist-in-decode>
subroutine).

=head2 whitelist-in-encode

=begin code :lang<raku>

my $w-encode = whitelist-in-encode("<",">");

=end code

The C<whitelist-in-encode> subroutine takes any number of arguments,
each indicating a character that should B<not> be converted by the
C<encode-html-entities> subroutine.  It returns a lookup map that can
be specified as the second positional argument to C<encode-html-entities>.

=head2 whitelist-in-decode

=begin code :lang<raku>

my $w-decode = whitelist-in-decode('&amp;');

=end code

The C<whitelist-in-decode> subroutine takes any number of arguments,
each indicating a HTML entity that should B<not> be converted by the
C<decode-html-entities> subroutine. It returns a lookup map that can
be specified as the second positional argument to C<decode-html-entities>.

=head1 AUTHOR

Elizabeth Mattijsen <liz@raku.rocks>

Source can be located at: https://github.com/lizmat/HTML-Entity-Fast .
Comments and Pull Requests are welcome.

If you like this module, or what I’m doing more generally, committing to a
L<small sponsorship|https://github.com/sponsors/lizmat/>  would mean a great
deal to me!

=head1 COPYRIGHT AND LICENSE

Copyright 2022, 2024 Elizabeth Mattijsen

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.

=end pod

# vim: expandtab shiftwidth=4


