Current section
Files
Jump to
Current section
Files
src/xqerl_serialize.erl
%% -------------------------------------------------------------------
%%
%% xqerl - XQuery processor
%%
%% Copyright (c) 2018-2020 Zachary N. Dean All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License. You may obtain
%% a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% -------------------------------------------------------------------
-module(xqerl_serialize).
%% ====================================================================
%% API functions
%% ====================================================================
-export([
serialize/2,
default_opts/0
]).
-include("xqerl.hrl").
-define(XML, <<"http://www.w3.org/XML/1998/namespace">>).
-define(XHTML, <<"http://www.w3.org/1999/xhtml">>).
-define(SVG, <<"http://www.w3.org/2000/svg">>).
-define(MATHML, <<"http://www.w3.org/1998/Math/MathML">>).
% block array:array(_) warnings
-dialyzer(no_opaque).
-define(IS_ARRAY(A), is_tuple(A), element(1, A) =:= array).
serialize(Seq, Opts) ->
Opts1 = maps:merge(default_opts(), Opts),
do_serialize(Seq, Opts1).
default_opts() ->
#{
'allow-duplicate-names' => false,
'byte-order-mark' => false,
'cdata-section-elements' => [],
'doctype-public' => <<>>,
'doctype-system' => <<>>,
'encoding' => utf8,
'escape-uri-attributes' => false,
'html-version' => 5.0,
'include-content-type' => true,
'indent' => false,
'item-separator' => <<>>,
'json-node-output-method' => xml,
'media-type' => <<"application/xml">>,
'method' => xml,
'normalization-form' => none,
'omit-xml-declaration' => true,
'standalone' => omit,
'suppress-indentation' => [],
'undeclare-prefixes' => false,
'use-character-maps' => #{},
'version' => 1.0
}.
%% ====================================================================
%% Internal functions
%% ====================================================================
%% ====================================================================
%% Sequence Normalization: happens with all methods except json & adaptive.
%% ====================================================================
%% The result tree rooted at the document node that is created by the final
%% step of this sequence normalization process is the instance of the data
%% model to which the rules of the appropriate output method are applied. If
%% the sequence normalization process results in a serialization error, the
%% serializer MUST signal the error.
%%
%% The steps in computing the normalized sequence are:
normalize_seq(Seq, Opts) ->
S1 = norm_s1(Seq),
S2 = norm_s2(S1),
S3 = norm_s3(S2, Opts),
S4 = norm_s4(S3),
S5 = norm_s5(S4),
S6 = norm_s6(S5),
norm_s7(S6).
%% 1. If the sequence that is input to serialization is empty, create a
%% sequence S1 that consists of a zero-length string. Otherwise, copy each
%% item in the sequence that is input to serialization to create the new
%% sequence S1. Each item in the sequence that is an array is flattened by
%% calling the function array:flatten() before being copied.
norm_s1([]) ->
[<<>>];
norm_s1(Seq) when is_list(Seq) ->
norm_s1_(Seq);
norm_s1(Seq) ->
norm_s1([Seq]).
norm_s1_([A | T]) when ?IS_ARRAY(A) ->
xqerl_mod_array:flatten(#{}, A) ++ norm_s1_(T);
norm_s1_([H | T]) ->
[H | norm_s1_(T)];
norm_s1_([]) ->
[].
%% 2. For each item in S1, if the item is atomic, obtain the lexical
%% representation of the item by casting it to an xs:string and copy the
%% string representation to the new sequence; otherwise, copy the item to
%% the new sequence. The new sequence is S2.
norm_s2([#xqAtomicValue{} = H | T]) ->
[xqerl_types:cast_as(H, 'xs:string') | norm_s2(T)];
norm_s2([H | T]) when is_binary(H) ->
[H | norm_s2(T)];
norm_s2([H | T]) when is_number(H); is_atom(H) ->
[xqerl_types:cast_as(H, 'xs:string') | norm_s2(T)];
norm_s2([H | T]) ->
[H | norm_s2(T)];
norm_s2([]) ->
[].
%% 3. If the item-separator serialization parameter is absent, then for each
%% subsequence of adjacent strings in S2, copy a single string to the new
%% sequence equal to the values of the strings in the subsequence
%% concatenated in order, each separated by a single space. Copy all other
%% items to the new sequence. Otherwise, copy each item in S2 to the new
%% sequence, inserting between each pair of items a string whose value is
%% equal to the value of the item-separator parameter. The new sequence
%% is S3.
norm_s3(Seq, #{'item-separator' := <<>>}) ->
norm_s3_1(Seq);
norm_s3(Seq, #{'item-separator' := Sep}) ->
norm_s3_2(Seq, Sep).
norm_s3_1([V1, V2 | T]) when is_binary(V1), is_binary(V2) ->
V3 = <<V1/binary, " ", V2/binary>>,
norm_s3_1([V3 | T]);
norm_s3_1([H | T]) ->
[H | norm_s3_1(T)];
norm_s3_1([]) ->
[].
norm_s3_2([Seq], _) -> [Seq];
norm_s3_2([H | T], Sep) -> [H | norm_s3_3(T, Sep)].
norm_s3_3([H | T], Sep) ->
[Sep, H | norm_s3_3(T, Sep)];
norm_s3_3([], _) ->
[].
%% 4. For each item in S3, if the item is a string, create a text node in the
%% new sequence whose string value is equal to the string; otherwise, copy
%% the item to the new sequence. The new sequence is S4.
norm_s4([Val | T]) when is_binary(Val) ->
[xqldb_mem_nodes:text({make_ref(), 0}, Val) | norm_s4(T)];
norm_s4([H | T]) ->
[H | norm_s4(T)];
norm_s4([]) ->
[].
%% 5. For each item in S4, if the item is a document node, copy its children
%% to the new sequence; otherwise, copy the item to the new sequence.
%% The new sequence is S5.
%% also, if this is a DB node, do deep copy for serialization
norm_s5([
#{
id := {_, _, _},
nk := _
} = H
| T
]) ->
ReturnTo = self(),
F = fun() ->
D = xqldb_nodes:deep_copy_node(H),
ReturnTo ! {self(), D}
end,
S = erlang:spawn_link(F),
H1 = fun() ->
receive
{S, V} -> V
end
end,
norm_s5([H1 | T]);
norm_s5([#{nk := document, ch := []} | T]) ->
norm_s5(T);
norm_s5([#{nk := document} = H | T]) ->
xqldb_mem_nodes:children(H) ++ norm_s5(T);
norm_s5([H | T]) ->
[H | norm_s5(T)];
norm_s5([]) ->
[].
%% 6. For each subsequence of adjacent text nodes in S5, copy a single text
%% node to the new sequence equal to the values of the text nodes in the
%% subsequence concatenated in order. Any text nodes with values of zero
%% length are dropped. Copy all other items to the new sequence.
%% The new sequence is S6.
norm_s6([F | T]) when is_function(F, 0) ->
norm_s6([F() | T]);
norm_s6([#{nk := document, ch := C} | T]) ->
norm_s6(C ++ T);
norm_s6([#{nk := text, sv := <<>>} | T]) ->
norm_s6(T);
norm_s6([#{nk := text, sv := V1} = Tx, #{nk := text, sv := V2} | T]) ->
norm_s6([Tx#{sv := <<V1/binary, V2/binary>>} | T]);
norm_s6([H | T]) ->
[H | norm_s6(T)];
norm_s6([]) ->
[].
%% 7. It is a serialization error [err:SENR0001] if an item in S6 is an
%% attribute node, a namespace node or a function. Otherwise, construct a
%% new sequence, S7, that consists of a single document node and copy all
%% the items in the sequence, which are all nodes, as children of that
%% document node.
norm_s7(Seq) ->
Ch = norm_s7_(Seq),
Doc = xqldb_mem_nodes:document({make_ref(), 0}, <<>>, <<>>),
xqldb_mem_nodes:add_children(Doc, Ch).
norm_s7_([#xqFunction{} | _]) -> ?err('SENR0001');
norm_s7_([#{nk := attribute} | _]) -> ?err('SENR0001');
norm_s7_([#{nk := namespace} | _]) -> ?err('SENR0001');
norm_s7_([Map | _]) when is_map(Map), not is_map_key(nk, Map) -> ?err('SENR0001');
norm_s7_([H | T]) -> [H | norm_s7_(T)];
norm_s7_([]) -> [].
%% S7 is the normalized sequence.
%% ====================================================================
%% Phases of Serialization: XML, HTML, XHTML and Text. (after normalization)
%% ====================================================================
%% 1. A meta element is added to the sequence along with discarding an
%% existing meta element, as controlled by the include-content-type
%% parameter for the XHTML and HTML output methods. This step is skipped
%% for the other output methods defined by this specification.
html_head_meta(
#{
nn := {Ns, _, _},
id := {Ref, _}
} = Elem,
#{
'include-content-type' := true,
encoding := Enc
}
) ->
HttpEquiv = xqldb_mem_nodes:attribute(
{Ref, 0},
{<<>>, <<>>, <<"http-equiv">>},
<<"Content-Type">>,
false,
false,
none
),
ContText =
case Enc of
utf8 -> <<"text/html; charset=UTF-8">>;
utf16 -> <<"text/html; charset=UTF-16">>;
_ -> <<"text/html">>
end,
Content = xqldb_mem_nodes:attribute(
{Ref, 0},
{<<>>, <<>>, <<"content">>},
ContText,
false,
false,
none
),
Meta = xqldb_mem_nodes:element({Ref, 0}, {Ns, <<>>, <<"meta">>}, none),
Meta1 = xqldb_mem_nodes:add_attribute(Meta, Content),
Meta2 = xqldb_mem_nodes:add_attribute(Meta1, HttpEquiv),
Meta3 = xqldb_mem_nodes:add_children(Meta2, []),
#{ch := Ch} = Elem1 = delete_old_meta(Elem),
Elem1#{ch := [Meta3 | Ch]};
html_head_meta(Elem, _) ->
Elem.
delete_old_meta(Elem) ->
Children = xqldb_mem_nodes:children(Elem),
Children1 = delete_old_meta_1(Children),
Elem#{ch => Children1}.
delete_old_meta_1([#{nk := element, nn := {_, _, <<"meta">>}} = H | T]) ->
Atts = xqldb_mem_nodes:attributes(H),
case
[
1
|| #{
nn := {_, _, <<"http-equiv">>},
sv := Sv
} <- Atts,
string:lowercase(xqerl_lib:trim(Sv)) == <<"content-type">>
]
of
[] ->
[H | delete_old_meta_1(T)];
[1] ->
delete_old_meta_1(T)
end;
delete_old_meta_1([H | T]) ->
[H | delete_old_meta_1(T)];
delete_old_meta_1([]) ->
[].
%% 2. Markup generation produces the character representation of those parts
%% of the serialized result that describe the structure of the sequence.
%% In the cases of the XML, HTML and XHTML output methods, this phase
%% produces the character representations of the following:
%% - the document type declaration;
%% - start tags and end tags (except for attribute values, whose
%% representation is produced by the character expansion phase);
%% - processing instructions; and
%% - comments.
%% In the cases of the XML and XHTML output methods, this phase also
%% produces the following:
%% - the XML or text declaration; and
%% - empty element tags (except for the attribute values);
%% In the case of the text output method, this phase replaces the single
%% document node produced by sequence normalization with a new document
%% node that has exactly one child, which is a text node. The string value
%% of the new text node is the string value of the document node that was
%% produced by sequence normalization.
%%
%% 3. Character expansion is concerned with the representation of characters
%% appearing in text and attribute nodes in the sequence. For each text and
%% attribute node, the following rules are applied in sequence.
%% a. If the node is an attribute that is a URI attribute value and the
%% escape-uri-attributes parameter is set to require escaping of URI
%% attributes, apply URI escaping as defined below, and skip rules b-e.
%% Otherwise, continue with rule b.
%% [Definition: URI escaping consists of the following three steps
%% applied in sequence to the content of URI attribute values:]
%% i. normalize to NFC using the method defined in Section 5.4.6
%% fn:normalize-unicode FO31
%% ii. percent-encode any special characters in the URI using the
%% method defined in Section 6.4 fn:escape-html-uri FO31
%% iii. escape according to the rules of the XML or HTML output method,
%% whichever is applicable, any characters that require escaping,
%% and any characters that cannot be represented in the selected
%% encoding. For example, replace < with < (See also section
%% 7.3 Writing Character Data).
%% [Definition: The values of attributes listed in D List of URI
%% Attributes are URI attribute values. Attributes are not considered
%% to be URI attributes simply because they are namespace declaration
%% attributes or have the type annotation xs:anyURI.]
%% b. If the node is a text node whose parent element is selected by the
%% rules of the cdata-section-elements parameter for the applicable
%% output method, create CDATA sections as described below, and skip
%% rules c-e. Otherwise, continue with rule c.
%% Apply the following two processes in sequence to create CDATA sections
%% i. Unicode Normalization if requested by the normalization-form
%% parameter.
%% ii. The application of changes as detailed in the description of
%% the cdata-section-elements parameter for the applicable output
%% method.
%% c. Apply character mapping as determined by the use-character-maps
%% parameter for the applicable output method. For characters that
%% were substituted by this process, skip rules d and e. For the
%% remaining characters that were not modified by character mapping,
%% continue with rule d.
%% d. Apply Unicode Normalization if requested by the normalization-form
%% parameter.
%% [Definition: Unicode Normalization is the process of removing
%% alternative representations of equivalent sequences from textual
%% data, to convert the data into a form that can be binary-compared
%% for equivalence, as specified in [UAX #15: Unicode Normalization
%% Forms]. For specific recommendations for character normalization on
%% the World Wide Web, see [Character Model for the World Wide Web 1.0:
%% Normalization].]
%% The meanings associated with the possible values of the
%% normalization-form parameter are defined in section 5.1.9 XML Output
%% Method: the normalization-form Parameter.
%% Continue with step e.
%% e. Escape according to the rules of the XML or HTML output method,
%% whichever is applicable, any characters (such as < and &) where XML
%% or HTML requires escaping, and any characters that cannot be
%% represented in the selected encoding. For example, replace < with
%% <. (See also section 7.3 Writing Character Data). For characters
%% such as > where XML defines a built-in entity but does not require
%% its use in all circumstances, it is implementation-dependent whether
%% the character is escaped.
%%
%% 4. Indentation, as controlled by the indent parameter and the
%% suppress-indentation parameter, MAY add or remove whitespace according
%% to the rules defined by the applicable output method.
%%
%% 5. Encoding, as controlled by the encoding parameter, converts the character
%% stream produced by the previous phases into an octet stream.
-define(STR_REST(Str, Rest), <<Str, Rest/binary>>).
-define(CP_REST(Cp, Rest), <<Cp/utf8, Rest/binary>>).
encoder_fun(CMap) ->
TextPattern = binary:compile_pattern([<<"<">>, <<">">>, <<"&">>, <<"\r">>]),
fun(Bin) ->
case binary:match(Bin, TextPattern) of
nomatch ->
encode_m_text_(Bin, CMap);
_ ->
encode_text_(Bin, CMap)
end
end.
do_serialize(
Seq,
#{
method := adaptive,
'item-separator' := <<>>
} = Opts
) ->
do_serialize_adaptive(Seq, Opts#{'item-separator' := <<$\n>>});
do_serialize(Seq, #{method := adaptive} = Opts) ->
do_serialize_adaptive(Seq, Opts);
do_serialize(Seq, #{method := json} = Opts) ->
do_serialize_json(Seq, Opts);
do_serialize(Seq, #{method := text} = Opts) ->
do_serialize_text(Seq, Opts);
do_serialize(
Seq,
#{
method := xml,
'use-character-maps' := CMap
} = Opts
) ->
Norm = normalize_seq(Seq, Opts),
TextEncoder = encoder_fun(CMap),
do_serialize_xml(Norm, Opts#{text_encoder => TextEncoder});
do_serialize(Seq, #{method := xhtml} = Opts) ->
Norm = normalize_seq(Seq, Opts),
do_serialize_xhtml(Norm, Opts);
do_serialize(Seq, #{method := html} = Opts) ->
Norm = normalize_seq(Seq, Opts),
do_serialize_html(Norm, Opts);
do_serialize(Seq, _) ->
Seq.
do_serialize_json(
#{nk := _} = Node,
#{'json-node-output-method' := Method} = Opts
) ->
NewOpts = (default_opts())#{
method := Method,
'omit-xml-declaration' := true
},
Val = do_serialize(Node, NewOpts),
to_json_string(Val, Opts);
do_serialize_json(A, Opts) when ?IS_ARRAY(A) ->
Vals1 = [do_serialize_json(V, Opts) || V <- array:to_list(A)],
Vals2 = string_join(Vals1, <<$,>>),
<<$[, Vals2/binary, $]>>;
do_serialize_json(Map, Opts) when is_map(Map) ->
KVs = maps:values(Map),
KVs1 = [
begin
Key = to_json_string(K, Opts),
Val = do_serialize_json(V, Opts),
{Key, Val}
end
|| {K, V} <- KVs
],
ok =
case maps:get('allow-duplicate-names', Opts) of
true -> ok;
false -> check_duplicate_keys(KVs1, [])
end,
KVs2 = [<<(normalize_string_value(K, Opts))/binary, $:, V/binary>> || {K, V} <- KVs1],
Body = string_join(KVs2, <<$,>>),
<<"{", Body/binary, "}">>;
do_serialize_json(#xqAtomicValue{value = nan}, _Opts) ->
?err('SERE0020');
do_serialize_json(#xqAtomicValue{value = infinity}, _Opts) ->
?err('SERE0020');
do_serialize_json(#xqAtomicValue{value = neg_infinity}, _Opts) ->
?err('SERE0020');
do_serialize_json(#xqAtomicValue{value = neg_zero}, _Opts) ->
<<"-0">>;
do_serialize_json(nan, _Opts) ->
?err('SERE0020');
do_serialize_json(infinity, _Opts) ->
?err('SERE0020');
do_serialize_json(neg_infinity, _Opts) ->
?err('SERE0020');
do_serialize_json(neg_zero, _Opts) ->
<<"-0">>;
do_serialize_json(Val, _Opts) when is_number(Val) ->
xqerl_numeric:string(Val);
do_serialize_json(#xqAtomicValue{type = Type, value = Val}, _Opts) when ?xs_numeric(Type) ->
xqerl_numeric:string(Val);
do_serialize_json(true, _Opts) ->
<<"true">>;
do_serialize_json(false, _Opts) ->
<<"false">>;
do_serialize_json(Bin, Opts) when is_binary(Bin) ->
normalize_string_value(to_json_string(Bin, Opts), Opts);
do_serialize_json(#xqAtomicValue{} = Atomic, Opts) ->
normalize_string_value(to_json_string(Atomic, Opts), Opts);
do_serialize_json([], _Opts) ->
<<"null">>;
do_serialize_json([V], Opts) ->
do_serialize_json(V, Opts);
do_serialize_json([_ | _], _Opts) ->
?err('SERE0023');
do_serialize_json(_, _) ->
?err('SERE0021').
do_serialize_adaptive(Seq, #{'item-separator' := Sep} = Opts) when is_list(Seq) ->
Seq1 = [do_serialize_adaptive(S, Opts) || S <- Seq],
string_join(Seq1, Sep);
do_serialize_adaptive(#{nk := _} = Node, #{'use-character-maps' := CMap} = Opts) ->
TextEncoder = encoder_fun(CMap),
do_serialize_xml(Node, Opts#{text_encoder => TextEncoder});
do_serialize_adaptive(true, _Opts) ->
<<"true()">>;
do_serialize_adaptive(false, _Opts) ->
<<"false()">>;
do_serialize_adaptive(Val, _Opts) when is_binary(Val) ->
Q = duplicate_quotes(Val, <<>>),
<<$", Q/binary, $">>;
do_serialize_adaptive(#xqAtomicValue{type = Type, value = Val}, _Opts) when
Type == 'xs:untypedAtomic'; Type == 'xs:anyURI'; ?xs_string(Type)
->
Q = duplicate_quotes(Val, <<>>),
<<$", Q/binary, $">>;
do_serialize_adaptive(Val, _Opts) when is_integer(Val) ->
xqerl_numeric:string(Val);
do_serialize_adaptive(#xqAtomicValue{type = Type, value = Val}, _Opts) when ?xs_decimal(Type) ->
xqerl_numeric:string(Val);
do_serialize_adaptive(Val, _Opts) when
is_float(Val); Val == neg_zero; Val == infinity; Val == nan; Val == neg_infinity
->
Pic = <<"0.0##########################e0">>,
xqerl_mod_fn:'format-number'(#{}, Val, Pic, #dec_format{infinity = "INF"});
do_serialize_adaptive(
#xqAtomicValue{
type = Type,
value = #qname{
namespace = Ns,
local_name = Ln
}
},
_Opts
) when Type == 'xs:QName'; Type == 'xs:NOTATION' ->
<<$Q, ${, Ns/binary, $}, Ln/binary>>;
do_serialize_adaptive(#xqAtomicValue{type = Type} = Val, _Opts) when ?xs_duration(Type) ->
TypeBin = <<"xs:duration">>,
Str = xqerl_types:string_value(Val),
<<TypeBin/binary, $(, $", Str/binary, $", $)>>;
do_serialize_adaptive(#xqAtomicValue{type = Type} = Val, _Opts) ->
TypeBin = atom_to_binary(Type, utf8),
Str = xqerl_types:string_value(Val),
<<TypeBin/binary, $(, $", Str/binary, $", $)>>;
do_serialize_adaptive(A, Opts) when ?IS_ARRAY(A) ->
Vals1 = [
do_serialize_adaptive_1(V, Opts)
|| V <- array:to_list(A)
],
Vals2 = string_join(Vals1, <<$,>>),
<<$[, Vals2/binary, $]>>;
do_serialize_adaptive(Map, Opts) when is_map(Map) ->
KVs = maps:values(Map),
Entries = [
begin
Key = do_serialize_adaptive(K, Opts),
Val = do_serialize_adaptive_1(V, Opts),
<<Key/binary, $:, Val/binary>>
end
|| {K, V} <- KVs
],
Body = string_join(Entries, <<$,>>),
<<"map{", Body/binary, "}">>;
do_serialize_adaptive(#xqFunction{name = undefined, arity = Arity}, _Opts) ->
A = integer_to_binary(Arity),
<<"(anonymous-function)#", A/binary>>;
do_serialize_adaptive(
#xqFunction{
name = #qname{
namespace = Ns,
local_name = Ln
},
arity = Arity
},
_Opts
) ->
Head =
case Ns of
<<"http://www.w3.org/2005/xpath-functions">> ->
<<"fn:">>;
<<"http://www.w3.org/2005/xpath-functions/math">> ->
<<"math:">>;
<<"http://www.w3.org/2005/xpath-functions/map">> ->
<<"map:">>;
<<"http://www.w3.org/2005/xpath-functions/array">> ->
<<"array:">>;
<<"http://www.w3.org/2001/XMLSchema">> ->
<<"xs:">>;
_ ->
<<$Q, ${, Ns/binary, $}>>
end,
A = integer_to_binary(Arity),
<<Head/binary, Ln/binary, "#", A/binary>>;
do_serialize_adaptive(Seq, _Opts) ->
Seq.
do_serialize_adaptive_seq([V], Opts) ->
do_serialize_adaptive(V, Opts);
do_serialize_adaptive_seq(Vals, Opts) ->
Vals1 = [do_serialize_adaptive(V, Opts) || V <- Vals],
Vals2 = string_join(Vals1, <<$,>>),
<<$(, Vals2/binary, $)>>.
do_serialize_adaptive_1(V, Opts) when is_list(V) -> do_serialize_adaptive_seq(V, Opts);
do_serialize_adaptive_1(V, Opts) -> do_serialize_adaptive(V, Opts).
do_serialize_text(Seq, Opts) ->
Norm = normalize_seq(Seq, Opts),
xqldb_mem_nodes:string_value(Norm).
do_serialize_html(Seq, #{'html-version' := 5.0} = Opts) ->
Seq1 = normalize_prefixes(Seq, #{
<<>> => <<>>,
<<"xml">> => ?XML
}),
do_serialize_html(Seq1, Opts, #{
<<>> => <<>>,
<<"xml">> => ?XML
});
do_serialize_html(Seq, Opts) ->
do_serialize_html(Seq, Opts, #{
<<>> => <<>>,
<<"xml">> => ?XML
}).
do_serialize_html(#{nk := document} = Doc, Opts, NsInScope) ->
Ch = xqldb_mem_nodes:children(Doc),
IsWellFormed = is_well_formed(Ch),
[L | _] = [L || #{nn := {_, _, L}, nk := element} <- Ch],
Decl = do_xml_declaration(IsWellFormed, L, Opts#{'omit-xml-declaration' := true}),
Body = <<
<<(do_serialize_html(C, Opts, NsInScope))/binary>>
|| C <- Ch
>>,
<<Decl/binary, Body/binary>>;
do_serialize_html(
#{
nk := 'processing-instruction',
nn := {_, _, Ln}
} = Node,
_Opts,
_
) ->
% no trailing '?' in html
case xqldb_mem_nodes:string_value(Node) of
<<>> -> <<"<?", Ln/binary, " >">>;
Tgt -> <<"<?", Ln/binary, " ", Tgt/binary, ">">>
end;
do_serialize_html(#{nk := comment} = Node, _Opts, _) ->
Txt = xqldb_mem_nodes:string_value(Node),
<<"<!--", Txt/binary, "-->">>;
do_serialize_html(#{nk := text} = Node, _Opts, _) ->
Txt = xqldb_mem_nodes:string_value(Node),
encode_html_text(Txt);
do_serialize_html(#{nk := cdata} = Node, _Opts, _) ->
Txt = xqldb_mem_nodes:string_value(Node),
encode_cdata(Txt);
do_serialize_html(#{nk := script} = Node, _Opts, _) ->
xqldb_mem_nodes:string_value(Node);
do_serialize_html(
#{
nk := attribute,
nn := NodeName
} = Node,
#{
'escape-uri-attributes' := true,
parent_name := ParentNodeName,
'use-character-maps' := CMap
},
_
) ->
Txt = xqldb_mem_nodes:string_value(Node),
QNm = encode_qname(NodeName),
AttTxt = att_text(ParentNodeName, NodeName, Txt, CMap),
<<QNm/binary, "=\"", AttTxt/binary, "\"">>;
do_serialize_html(
#{
nk := attribute,
nn := NodeName
} = Node,
#{'use-character-maps' := CMap},
_
) ->
Txt = xqldb_mem_nodes:string_value(Node),
QNm = encode_qname(NodeName),
AttTxt = encode_att_text(Txt, CMap),
<<QNm/binary, "=\"", AttTxt/binary, "\"">>;
do_serialize_html(
#{
nk := element,
ch := _,
ns := Ns,
nn := {ENs, _, Ln} = NodeName
} = Node,
#{'html-version' := HtmlV} = Opts,
InScopeNamespaces
) ->
At = xqldb_mem_nodes:attributes(Node),
QNm = encode_qname(NodeName),
NewNs = get_new_namespaces(Ns, InScopeNamespaces),
%?dbg("NewNs", NewNs),
InScopeNamespaces1 = maps:merge(InScopeNamespaces, Ns),
%NsStr = <<>>,
NsStr = <<
<<" ", (encode_namespace(P, N))/binary>>
|| {P, N} <- NewNs
>>,
Atts = <<
<<" ", (do_serialize_html(A, Opts#{parent_name => NodeName}, InScopeNamespaces1))/binary>>
|| A <- At
>>,
IsCdataNode = is_cdata_node(NodeName, Opts),
Node1 =
case Ln of
<<"head">> ->
html_head_meta(Node, Opts);
_ ->
Node
end,
ChNds = xqldb_mem_nodes:children(Node1),
IsHtml = ENs == <<>> orelse (ENs == ?XHTML andalso HtmlV == 5.0),
ChFun = fun
(#{nk := text} = Txt) when Ln == <<"script">>; Ln == <<"style">> ->
Txt#{nk := script};
(#{nk := text} = Txt) when IsCdataNode andalso not IsHtml ->
Txt#{nk := cdata};
(O) ->
O
end,
Ch = lists:map(ChFun, ChNds),
{Indent, Opts2} = indent_opts(NodeName, ChNds, Opts),
Chld = <<
<<Indent/binary, (do_serialize_html(C, Opts2, InScopeNamespaces1))/binary>>
|| C <- Ch
>>,
Empty =
case HtmlV == 5.0 of
true ->
is_void_element(NodeName);
false ->
is_empty_element(NodeName)
end,
case Ch of
[] when Empty, IsHtml ->
<<"<", QNm/binary, NsStr/binary, Atts/binary, ">">>;
_ ->
<<"<", QNm/binary, NsStr/binary, Atts/binary, ">", Chld/binary, Indent/binary, "</",
QNm/binary, ">">>
end;
do_serialize_html(_, _, _) ->
?err('SENR0001').
do_serialize_xhtml(Seq, #{'html-version' := 5.0} = Opts) ->
Seq1 = normalize_prefixes(Seq, #{
<<>> => <<>>,
<<"xml">> => ?XML
}),
do_serialize_xhtml(Seq1, Opts, #{
<<>> => <<>>,
<<"xml">> => ?XML
});
do_serialize_xhtml(Seq, Opts) ->
do_serialize_xhtml(Seq, Opts, #{
<<>> => <<>>,
<<"xml">> => ?XML
}).
do_serialize_xhtml(#{nk := document} = Doc, Opts, NsInScope) ->
Ch = xqldb_mem_nodes:children(Doc),
IsWellFormed = is_well_formed(Ch),
Decl =
case html_doctype(Ch, Opts) of
<<>> ->
[L | _] = [L || #{nn := {_, _, L}, nk := element} <- Ch],
do_xml_declaration(IsWellFormed, L, Opts);
D ->
D
end,
Body = <<
<<(do_serialize_xhtml(C, Opts, NsInScope))/binary>>
|| C <- Ch
>>,
<<Decl/binary, Body/binary>>;
do_serialize_xhtml(
#{
nk := 'processing-instruction',
nn := {_, _, Ln}
} = Node,
_Opts,
_
) ->
case xqldb_mem_nodes:string_value(Node) of
<<>> ->
% no trailing whitespace allowed
<<"<?", Ln/binary, "?>">>;
Tgt ->
<<"<?", Ln/binary, " ", Tgt/binary, "?>">>
end;
do_serialize_xhtml(#{nk := comment} = Node, _Opts, _) ->
Txt = xqldb_mem_nodes:string_value(Node),
<<"<!--", Txt/binary, "-->">>;
do_serialize_xhtml(#{nk := text} = Node, _Opts, _) ->
Txt = xqldb_mem_nodes:string_value(Node),
encode_html_text(Txt);
do_serialize_xhtml(#{nk := cdata} = Node, _Opts, _) ->
Txt = xqldb_mem_nodes:string_value(Node),
encode_cdata(Txt);
do_serialize_xhtml(#{nk := script} = Node, _Opts, _) ->
xqldb_mem_nodes:string_value(Node);
do_serialize_xhtml(
#{
nk := attribute,
nn := NodeName
} = Node,
#{
'escape-uri-attributes' := true,
parent_name := ParentNodeName,
'use-character-maps' := CMap
},
_
) ->
Txt = xqldb_mem_nodes:string_value(Node),
QNm = encode_qname(NodeName),
AttTxt = att_text(ParentNodeName, NodeName, Txt, CMap),
<<QNm/binary, "=\"", AttTxt/binary, "\"">>;
do_serialize_xhtml(
#{
nk := attribute,
nn := NodeName
} = Node,
#{'use-character-maps' := CMap},
_
) ->
Txt = xqldb_mem_nodes:string_value(Node),
QNm = encode_qname(NodeName),
AttTxt = encode_att_text(Txt, CMap),
<<QNm/binary, "=\"", AttTxt/binary, "\"">>;
do_serialize_xhtml(
#{
nk := element,
ch := _,
ns := Ns,
nn := {_, _, Ln} = NodeName
} = Node,
#{'html-version' := HtmlV} = Opts,
InScopeNamespaces
) ->
At = xqldb_mem_nodes:attributes(Node),
QNm = encode_qname(NodeName),
NewNs = get_new_namespaces(Ns, InScopeNamespaces),
InScopeNamespaces1 = maps:merge(InScopeNamespaces, Ns),
NsStr = <<
<<" ", (encode_namespace(P, N))/binary>>
|| {P, N} <- NewNs
>>,
Atts = <<
<<" ", (do_serialize_xhtml(A, Opts#{parent_name => NodeName}, InScopeNamespaces1))/binary>>
|| A <- At
>>,
IsCdataNode = is_cdata_node(NodeName, Opts),
Node1 =
case Ln of
<<"head">> ->
html_head_meta(Node, Opts);
_ ->
Node
end,
ChNds = xqldb_mem_nodes:children(Node1),
ChFn = fun
(#{nk := text} = Txt) when Ln == <<"script">>; Ln == <<"style">> ->
Txt#{nk := script};
(#{nk := text} = Txt) when IsCdataNode ->
Txt#{nk := cdata};
(O) ->
O
end,
Ch = lists:map(ChFn, ChNds),
{Indent, Opts2} = indent_opts(NodeName, ChNds, Opts),
Chld = <<
<<Indent/binary, (do_serialize_xhtml(C, Opts2, InScopeNamespaces1))/binary>>
|| C <- Ch
>>,
Empty =
case HtmlV == 5.0 of
true ->
is_void_element(NodeName);
false ->
is_empty_element(NodeName)
end,
IsHtml = is_html_tag(NodeName, HtmlV),
case Ch of
[] when Empty, IsHtml ->
<<"<", QNm/binary, NsStr/binary, Atts/binary, " />", Indent/binary>>;
_ ->
<<"<", QNm/binary, NsStr/binary, Atts/binary, ">", Chld/binary, Indent/binary, "</",
QNm/binary, ">">>
end;
do_serialize_xhtml(_, _, _) ->
?err('SENR0001').
do_serialize_xml(Seq, Opts) ->
IoList = do_serialize_xml(Seq, Opts, #{
<<>> => <<>>,
<<"xml">> => ?XML
}),
iolist_to_binary(IoList).
do_serialize_xml(#{nk := document} = Doc, Opts, NsInScope) ->
Ch = xqldb_mem_nodes:children_no_p(Doc),
Decl =
case is_well_formed(Ch) of
true ->
[L | _] = [L || #{nn := {_, _, L}, nk := element} <- Ch],
do_xml_declaration(true, L, Opts);
false ->
do_xml_declaration(false, <<>>, Opts)
end,
Body = [
do_serialize_xml(C, Opts, NsInScope)
|| C <- Ch
],
[Decl | Body];
do_serialize_xml(
#{
nk := 'processing-instruction',
nn := {_, _, Ln}
} = Node,
_Opts,
_
) ->
case xqldb_mem_nodes:string_value(Node) of
<<>> -> <<"<?", Ln/binary, " ?>">>;
Tgt -> <<"<?", Ln/binary, " ", Tgt/binary, "?>">>
end;
do_serialize_xml(#{nk := comment} = Node, _Opts, _) ->
Txt = xqldb_mem_nodes:string_value(Node),
[<<"<!--">>, Txt, <<"-->">>];
do_serialize_xml(#{nk := text} = Node, Opts, _) ->
Txt = xqldb_mem_nodes:string_value(Node),
encode_text(Txt, Opts);
do_serialize_xml(#{nk := cdata} = Node, _Opts, _) ->
Txt = xqldb_mem_nodes:string_value(Node),
encode_cdata(Txt);
do_serialize_xml(
#{
nk := attribute,
nn := NodeName
} = Node,
#{'use-character-maps' := CMap},
_
) ->
Txt = xqldb_mem_nodes:string_value(Node),
QNm = encode_qname(NodeName),
AttTxt = encode_att_text(Txt, CMap),
[QNm, "=\"", AttTxt, "\""];
do_serialize_xml(
#{
nk := element,
ch := _,
ns := Ns,
nn := NodeName
} = Node,
Opts,
InScopeNamespaces
) ->
At = xqldb_mem_nodes:attributes_no_p(Node),
QNm = encode_qname(NodeName),
NewNs = get_new_namespaces(Ns, InScopeNamespaces),
InScopeNamespaces1 = maps:merge(InScopeNamespaces, Ns),
NsStr = [
[<<" ">>, encode_namespace(P, N)]
|| {P, N} <- NewNs, P =/= <<"xml">>
],
Atts = [
[<<" ">>, do_serialize_xml(A, Opts, InScopeNamespaces1)]
|| A <- At
],
IsCdataNode = is_cdata_node(NodeName, Opts),
ChNds = xqldb_mem_nodes:children_no_p(Node),
ChFun = fun
(#{nk := text} = Txt) when IsCdataNode ->
Txt#{nk := cdata};
(O) ->
O
end,
Ch = lists:map(ChFun, ChNds),
{Indent, Opts2} = indent_opts(NodeName, ChNds, Opts),
Chld = [
[Indent, do_serialize_xml(C, Opts2, InScopeNamespaces1)]
|| C <- Ch
],
case Ch of
[] ->
[<<"<">>, QNm, NsStr, Atts, <<"/>">>, Indent];
_ ->
[<<"<">>, QNm, NsStr, Atts, <<">">>, Chld, Indent, <<"</">>, QNm, <<">">>]
end;
do_serialize_xml(_, _, _) ->
?err('SENR0001').
indent_opts(NodeName, ChNds, Opts) ->
case can_indent(NodeName, ChNds, Opts) of
true -> {<<"\n">>, Opts};
false -> {<<>>, Opts#{indent := false}}
end.
encode_namespace(<<"xml">>, _) ->
<<>>;
encode_namespace(<<>>, Ns) ->
Ns1 = encode_ns_text(Ns),
<<"xmlns=\"", Ns1/binary, "\"">>;
encode_namespace(Px, Ns) ->
Ns1 = encode_ns_text(Ns),
<<"xmlns:", Px/binary, "=\"", Ns1/binary, "\"">>.
get_new_namespaces(New, Old) ->
[
{K, V}
|| {K, V} <- maps:to_list(New),
maps:find(K, Old) =/= {ok, V}
].
normalize_prefixes(#{nk := document} = Node, Nss) ->
Ch = xqldb_mem_nodes:children(Node),
Ch2 = [normalize_prefixes(N, Nss) || N <- Ch],
xqldb_mem_nodes:add_children(Node, Ch2);
normalize_prefixes(
#{
nk := element,
ns := ENs,
nn := {Ns, _, Ln}
} = Node,
Nss
) when Ns == ?XHTML; Ns == ?SVG; Ns == ?MATHML ->
Atts = xqldb_mem_nodes:attributes(Node),
Child = xqldb_mem_nodes:children(Node),
UsedPxs = [P || #{nn := {_, P, _}} <- Atts, P =/= <<>>],
Keep = maps:with(UsedPxs, maps:merge(Nss, ENs)),
Added = Keep#{<<>> => Ns},
NewChild = [normalize_prefixes(C, Added) || C <- Child],
Node#{
ns := Added,
nn := {Ns, <<>>, Ln},
ch => NewChild
};
normalize_prefixes(
#{
nk := element,
ns := ENs
} = Node,
Nss
) ->
Child = xqldb_mem_nodes:children(Node),
NewChild = [normalize_prefixes(C, maps:merge(Nss, ENs)) || C <- Child],
Node#{ch => NewChild};
normalize_prefixes(Node, _) ->
Node.
%% XXX move encoders to nifs
encode_text(Bin, #{text_encoder := TextEncoder}) ->
TextEncoder(Bin).
encode_m_text_(Bin, CMap) ->
case maps:size(CMap) of
0 ->
Bin;
_ ->
<<
<<
(case B of
_ when is_map_key(B, CMap) ->
maps:get(B, CMap);
_ ->
<<B>>
end)/binary
>>
|| <<B>> <= Bin
>>
end.
encode_text_(Bin, CMap) ->
<<
<<
(case B of
_ when is_map_key(B, CMap) ->
maps:get(B, CMap);
$& ->
<<"&">>;
$< ->
<<"<">>;
$> ->
<<">">>;
$" ->
<<""">>;
$' ->
<<"'">>;
$\r ->
<<"
">>;
_ ->
<<B>>
end)/binary
>>
|| <<B>> <= Bin
>>.
%% encode_text(<<>>,Acc) -> Acc;
%% encode_text(?STR_REST("<",Tail),Acc) ->
%% encode_text(Tail,<<Acc/binary,"<">>);
%% encode_text(?STR_REST(">",Tail),Acc) ->
%% encode_text(Tail,<<Acc/binary, ">">>);
%% encode_text(?STR_REST("&", Tail),Acc) ->
%% encode_text(Tail,<<Acc/binary,"&">>);
%% encode_text(<<H, Tail/binary>>, Acc) when H == 16#0d ->
%% encode_text(Tail,<<Acc/binary,"
">>);
%% encode_text(<<H, Tail/binary>>, Acc) when H < 127 ->
%% encode_text(Tail,<<Acc/binary, H>>);
%% encode_text(?CP_REST(H, Tail), Acc) when H >= 127 ->
%% encode_text(Tail,<<Acc/binary,"&#x",(integer_to_binary(H,16))/binary,";">>).
encode_html_text(Bin) ->
encode_html_text(Bin, <<>>).
encode_html_text(<<>>, Acc) ->
Acc;
encode_html_text(?STR_REST("<", Tail), Acc) ->
encode_html_text(Tail, <<Acc/binary, "<">>);
encode_html_text(?STR_REST(">", Tail), Acc) ->
encode_html_text(Tail, <<Acc/binary, ">">>);
encode_html_text(?STR_REST("&", Tail), Acc) ->
encode_html_text(Tail, <<Acc/binary, "&">>);
encode_html_text(?CP_REST(16#0d, Tail), Acc) ->
encode_html_text(Tail, <<Acc/binary, "
">>);
encode_html_text(?CP_REST(H, Tail), Acc) ->
if
H >= 127 ->
Ref = reference(H),
encode_html_text(Tail, <<Acc/binary, Ref/binary>>);
true ->
encode_html_text(Tail, <<Acc/binary, H/utf8>>)
end.
encode_cdata(Txt) -> encode_cdata(Txt, <<"<![CDATA[">>).
encode_cdata(?STR_REST("]]>", Tail), Acc) ->
encode_cdata(Tail, <<Acc/binary, "]]]]><![CDATA[>">>);
encode_cdata(?CP_REST(H, Tail), Acc) ->
encode_cdata(Tail, <<Acc/binary, H/utf8>>);
encode_cdata(<<>>, Acc) ->
<<Acc/binary, "]]>">>.
encode_att_text(Bin, CMap) -> encode_att_text(Bin, <<>>, CMap).
encode_att_text(<<>>, Acc, _) ->
Acc;
encode_att_text(?CP_REST(H, Tail), Acc, CMap) when is_map_key(H, CMap) ->
S = maps:get(H, CMap),
encode_att_text(Tail, <<Acc/binary, S/binary>>, CMap);
encode_att_text(?CP_REST(H, Tail), Acc, CMap) when H == 16#0d ->
encode_att_text(Tail, <<Acc/binary, "
">>, CMap);
encode_att_text(?CP_REST(H, Tail), Acc, CMap) when H == 16#0a ->
encode_att_text(Tail, <<Acc/binary, "
">>, CMap);
encode_att_text(?CP_REST(H, Tail), Acc, CMap) when H == 16#09 ->
encode_att_text(Tail, <<Acc/binary, "	">>, CMap);
encode_att_text(?CP_REST(H, Tail), Acc, CMap) when H >= 127 ->
encode_att_text(Tail, <<Acc/binary, "&#x", (integer_to_binary(H, 16))/binary, ";">>, CMap);
encode_att_text(?STR_REST("\"", Tail), Acc, CMap) ->
encode_att_text(Tail, <<Acc/binary, """>>, CMap);
encode_att_text(?STR_REST("<", Tail), Acc, CMap) ->
encode_att_text(Tail, <<Acc/binary, "<">>, CMap);
encode_att_text(?STR_REST(">", Tail), Acc, CMap) ->
encode_att_text(Tail, <<Acc/binary, ">">>, CMap);
encode_att_text(?STR_REST("&", Tail), Acc, CMap) ->
encode_att_text(Tail, <<Acc/binary, "&">>, CMap);
encode_att_text(?STR_REST("{{", Tail), Acc, CMap) ->
encode_att_text(Tail, <<Acc/binary, "{{">>, CMap);
encode_att_text(?STR_REST("{", Tail), Acc, CMap) ->
encode_att_text(Tail, <<Acc/binary, "{{">>, CMap);
encode_att_text(?STR_REST("}}", Tail), Acc, CMap) ->
encode_att_text(Tail, <<Acc/binary, "}}">>, CMap);
encode_att_text(?STR_REST("}", Tail), Acc, CMap) ->
encode_att_text(Tail, <<Acc/binary, "}}">>, CMap);
encode_att_text(?CP_REST(H, Tail), Acc, CMap) ->
encode_att_text(Tail, <<Acc/binary, H/utf8>>, CMap).
encode_uri_att_text(Bin) ->
Nfc = unicode:characters_to_nfc_binary(Bin),
Enc = <<(encode_uri_att_text_c(C)) || <<C>> <= Nfc>>,
encode_att_text(Enc, <<>>).
encode_uri_att_text_c(C) when C >= 32, C =< 126 -> <<C>>;
encode_uri_att_text_c(C) -> <<"%", (integer_to_binary(C, 16))/binary>>.
encode_ns_text(Bin) -> encode_ns_text(Bin, <<>>).
encode_ns_text(<<>>, Acc) ->
Acc;
encode_ns_text(?CP_REST(H, Tail), Acc) when H >= 255; H == 9; H == 10; H == 13 ->
encode_ns_text(Tail, <<Acc/binary, "&#", (integer_to_binary(H))/binary, ";">>);
encode_ns_text(?STR_REST("\"", Tail), Acc) ->
encode_ns_text(Tail, <<Acc/binary, """>>);
encode_ns_text(?STR_REST("<", Tail), Acc) ->
encode_ns_text(Tail, <<Acc/binary, "<">>);
encode_ns_text(?STR_REST(">", Tail), Acc) ->
encode_ns_text(Tail, <<Acc/binary, ">">>);
encode_ns_text(?STR_REST("&", Tail), Acc) ->
encode_ns_text(Tail, <<Acc/binary, "&">>);
encode_ns_text(?CP_REST(H, Tail), Acc) ->
encode_ns_text(Tail, <<Acc/binary, H/utf8>>).
encode_qname({_, Px, Ln}) when is_atom(Px); Px == <<>> ->
Ln;
encode_qname({_, Px, Ln}) ->
<<Px/binary, ":", Ln/binary>>.
encode_binary(Val, utf8) -> Val;
encode_binary(Val, Enc) -> unicode:characters_to_binary(Val, unicode, Enc).
is_uri_attribute({PNs, _, PLn}, {Ns, _, Ln}) when PNs == <<>> orelse PNs == ?XHTML, Ns == <<>> ->
Pn = string:lowercase(PLn),
case string:lowercase(Ln) of
<<"action">> ->
Pn == <<"form">>;
<<"archive">> ->
Pn == <<"object">>;
<<"background">> ->
Pn == <<"body">>;
<<"cite">> ->
lists:member(Pn, [<<"blockquote">>, <<"del">>, <<"ins">>, <<"q">>]);
<<"classid">> ->
Pn == <<"object">>;
<<"codebase">> ->
lists:member(Pn, [<<"applet">>, <<"object">>]);
<<"data">> ->
Pn == <<"object">>;
<<"datasrc">> ->
lists:member(Pn, [
<<"button">>,
<<"div">>,
<<"input">>,
<<"object">>,
<<"select">>,
<<"span">>,
<<"table">>,
<<"textarea">>
]);
<<"for">> ->
Pn == <<"script">>;
<<"formaction">> ->
lists:member(Pn, [<<"button">>, <<"input">>]);
<<"href">> ->
lists:member(Pn, [<<"a">>, <<"area">>, <<"base">>, <<"link">>]);
<<"icon">> ->
Pn == <<"command">>;
<<"longdesc">> ->
lists:member(Pn, [<<"frame">>, <<"iframe">>, <<"img">>]);
<<"manifest">> ->
Pn == <<"html">>;
<<"name">> ->
Pn == <<"a">>;
<<"poster">> ->
Pn == <<"video">>;
<<"profile">> ->
Pn == <<"head">>;
<<"src">> ->
lists:member(Pn, [
<<"audio">>,
<<"embed">>,
<<"frame">>,
<<"iframe">>,
<<"img">>,
<<"input">>,
<<"script">>,
<<"source">>,
<<"track">>,
<<"video">>
]);
<<"usemap">> ->
lists:member(Pn, [<<"img">>, <<"input">>, <<"object">>]);
<<"value">> ->
Pn == <<"input">>;
_ ->
false
end;
is_uri_attribute(_, _) ->
false.
att_text(ParentNodeName, NodeName, Txt, CMap) ->
case is_uri_attribute(ParentNodeName, NodeName) of
true ->
encode_uri_att_text(Txt);
false ->
encode_att_text(Txt, CMap)
end.
string_join([], _With) -> <<>>;
string_join([H], _With) -> H;
string_join([H | T], With) -> string_join(T, With, H).
string_join([], _, Acc) -> Acc;
string_join([H | T], With, Acc) -> string_join(T, With, <<Acc/binary, With/binary, H/binary>>).
duplicate_quotes(<<$", Rest/binary>>, Acc) ->
duplicate_quotes(Rest, <<Acc/binary, $", $">>);
duplicate_quotes(<<C/utf8, Rest/binary>>, Acc) ->
duplicate_quotes(Rest, <<Acc/binary, C/utf8>>);
duplicate_quotes(<<>>, Acc) ->
Acc.
to_json_string(Val, _Opts) ->
Str = xqerl_types:string_value(Val),
Esc = xqerl_json:xml_no_escape(Str, <<>>),
<<$", Esc/binary, $">>.
normalize_string_value(Val, #{
'normalization-form' := nfc,
'use-character-maps' := Map
}) ->
case maps:size(Map) of
0 ->
unicode:characters_to_nfc_binary(Val);
_ ->
norm_nfc_mapped(Val, Map, <<>>, <<>>)
end;
normalize_string_value(Val, #{
'normalization-form' := none,
'use-character-maps' := Map,
encoding := Enc
}) ->
case maps:size(Map) of
0 ->
encode_binary(Val, Enc);
_ ->
encode_binary(norm_mapped(Val, Map, <<>>), Enc)
end.
norm_nfc_mapped(<<C/utf8, Val/binary>>, Map, Acc, Buf) when is_map_key(C, Map) ->
Str = maps:get(C, Map),
Buf1 = unicode:characters_to_nfc_binary(Buf),
Acc1 = <<Acc/binary, Buf1/binary, Str/binary>>,
norm_nfc_mapped(Val, Map, Acc1, <<>>);
norm_nfc_mapped(<<C/utf8, Val/binary>>, Map, Acc, Buf) ->
norm_nfc_mapped(Val, Map, Acc, <<Buf/binary, C/utf8>>);
norm_nfc_mapped(<<>>, _Map, Acc, Buf) ->
Buf1 = unicode:characters_to_nfc_binary(Buf),
<<Acc/binary, Buf1/binary>>.
norm_mapped(<<C/utf8, Val/binary>>, Map, Acc) when is_map_key(C, Map) ->
Str = maps:get(C, Map),
Acc1 = <<Acc/binary, Str/binary>>,
norm_mapped(Val, Map, Acc1);
norm_mapped(<<C/utf8, Val/binary>>, Map, Acc) ->
norm_mapped(Val, Map, <<Acc/binary, C/utf8>>);
norm_mapped(<<>>, _Map, Acc) ->
Acc.
check_duplicate_keys([{K, _} | T], Acc) ->
case lists:member(K, Acc) of
true ->
?err('SERE0022');
false ->
check_duplicate_keys(T, [K | Acc])
end;
check_duplicate_keys([], _) ->
ok.
is_well_formed(Children) ->
[1 || #{nk := text} <- Children] == [] andalso
[1 || #{nk := element} <- Children] == [1].
can_indent(_, _, #{indent := false}) ->
false;
can_indent({Ns, _, Ln}, Children, #{'suppress-indentation' := Els}) ->
Ln0 = string:lowercase(Ln),
case
[
1
|| #qname{namespace = Ns1, local_name = Ln1} <- Els,
Ln0 == string:lowercase(Ln1),
Ns == Ns1
] =/= []
of
true ->
false;
false ->
[1 || #{nk := text} <- Children] == []
end.
is_cdata_node({Ns, _, Ln}, #{'cdata-section-elements' := Els}) ->
[
1
|| #qname{namespace = Ns1, local_name = Ln1} <- Els,
Ln == Ln1,
Ns == Ns1
] =/= [].
do_xml_declaration(
Wellformed,
ElementName,
#{
'omit-xml-declaration' := O,
standalone := Standalone,
'html-version' := HtmlVers,
method := Method,
version := 1.0,
'doctype-system' := D,
'doctype-public' := P,
encoding := Encoding
}
) ->
Enc =
case Encoding of
utf8 -> <<"UTF-8">>;
utf16 -> <<"UTF-16">>;
utf32 -> <<"UTF-32">>
end,
ElementNameL = string:lowercase(ElementName),
DocType = do_xml_declaration_1(Method, D, P, HtmlVers, ElementName, ElementNameL),
do_xml_declaration_2(Wellformed, Standalone, D, O, Enc, DocType).
do_xml_declaration_1(html, <<>>, <<>>, HtmlVers, _ElementName, <<"html">>) when HtmlVers == 5.0 ->
<<"<!DOCTYPE html>\n">>;
do_xml_declaration_1(html, <<>>, <<>>, _HtmlVers, _ElementName, _ElementNameL) ->
<<>>;
do_xml_declaration_1(html, <<>>, P, _HtmlVers, _ElementName, ElementNameL) ->
<<"<!DOCTYPE ", ElementNameL/binary, " PUBLIC \"", P/binary, "\">\n">>;
do_xml_declaration_1(_Method, <<>>, _P, _HtmlVers, _ElementName, _ElementNameL) ->
<<>>;
do_xml_declaration_1(_Method, D, <<>>, _HtmlVers, ElementName, _ElementNameL) ->
<<"<!DOCTYPE ", ElementName/binary, " SYSTEM \"", D/binary, "\">\n">>;
do_xml_declaration_1(_Method, D, P, _HtmlVers, ElementName, _ElementNameL) ->
<<"<!DOCTYPE ", ElementName/binary, " PUBLIC \"", P/binary, "\" \"", D/binary, "\">\n">>.
do_xml_declaration_2(false, _Standalone, D, _O, _Enc, _DocType) when D =/= <<>> ->
?err('SEPM0004');
do_xml_declaration_2(false, true, _D, _O, _Enc, _DocType) ->
?err('SEPM0004');
do_xml_declaration_2(_Wellformed, omit, _D, false, Enc, DocType) ->
<<"<?xml version=\"1.0\" encoding=\"", Enc/binary, "\"?>", DocType/binary>>;
do_xml_declaration_2(_Wellformed, true, _D, false, Enc, DocType) ->
<<"<?xml version=\"1.0\" encoding=\"", Enc/binary, "\" standalone=\"yes\"?>", DocType/binary>>;
do_xml_declaration_2(_Wellformed, _Standalone, _D, false, Enc, DocType) ->
<<"<?xml version=\"1.0\" encoding=\"", Enc/binary, "\" standalone=\"no\"?>", DocType/binary>>;
do_xml_declaration_2(_Wellformed, _Standalone, _D, _O, _Enc, DocType) ->
DocType.
% takes children of document node
html_doctype([#{nk := text, sv := Sv} | T], Opts) ->
case xqerl_lib:trim(Sv) of
<<>> ->
html_doctype(T, Opts);
_ ->
<<>>
end;
html_doctype([#{nn := {_, _, Html}} | _], #{
'html-version' := 5.0,
'doctype-system' := <<>>
}) ->
case string:lowercase(Html) of
<<"html">> ->
<<"<!DOCTYPE ", Html/binary, ">\n">>;
_ ->
<<>>
end;
html_doctype(_, _) ->
<<>>.
is_empty_element({_, _, <<"area">>}) -> true;
is_empty_element({_, _, <<"base">>}) -> true;
is_empty_element({_, _, <<"br">>}) -> true;
is_empty_element({_, _, <<"col">>}) -> true;
is_empty_element({_, _, <<"embed">>}) -> true;
is_empty_element({_, _, <<"hr">>}) -> true;
is_empty_element({_, _, <<"img">>}) -> true;
is_empty_element({_, _, <<"input">>}) -> true;
is_empty_element({_, _, <<"link">>}) -> true;
is_empty_element({_, _, <<"meta">>}) -> true;
is_empty_element({_, _, <<"basefont">>}) -> true;
is_empty_element({_, _, <<"frame">>}) -> true;
is_empty_element({_, _, <<"isindex">>}) -> true;
is_empty_element({_, _, <<"param">>}) -> true;
is_empty_element({_, _, _}) -> false.
is_void_element({_, _, <<"br">>}) -> true;
is_void_element({_, _, <<"hr">>}) -> true;
is_void_element({_, _, <<"link">>}) -> true;
is_void_element({_, _, <<"meta">>}) -> true;
is_void_element({_, _, <<"img">>}) -> true;
is_void_element({_, _, <<"area">>}) -> true;
is_void_element({_, _, <<"base">>}) -> true;
is_void_element({_, _, <<"col">>}) -> true;
is_void_element({_, _, <<"embed">>}) -> true;
is_void_element({_, _, <<"input">>}) -> true;
is_void_element({_, _, <<"keygen">>}) -> true;
is_void_element({_, _, <<"param">>}) -> true;
is_void_element({_, _, <<"source">>}) -> true;
is_void_element({_, _, <<"track">>}) -> true;
is_void_element({_, _, <<"wbr">>}) -> true;
is_void_element({_, _, _}) -> false.
is_html_tag({?XHTML, _, _}, _) -> true;
is_html_tag({<<>>, _, Tag}, 5.0) -> is_html_tag(string:lowercase(Tag));
is_html_tag(_, _) -> false.
is_html_tag(<<"a">>) -> true;
is_html_tag(<<"abbr">>) -> true;
is_html_tag(<<"acronym">>) -> true;
is_html_tag(<<"address">>) -> true;
is_html_tag(<<"applet">>) -> true;
is_html_tag(<<"area">>) -> true;
is_html_tag(<<"article">>) -> true;
is_html_tag(<<"aside">>) -> true;
is_html_tag(<<"audio">>) -> true;
is_html_tag(<<"b">>) -> true;
is_html_tag(<<"base">>) -> true;
is_html_tag(<<"basefont">>) -> true;
is_html_tag(<<"bdi">>) -> true;
is_html_tag(<<"bdo">>) -> true;
is_html_tag(<<"big">>) -> true;
is_html_tag(<<"blockquote">>) -> true;
is_html_tag(<<"body">>) -> true;
is_html_tag(<<"br">>) -> true;
is_html_tag(<<"button">>) -> true;
is_html_tag(<<"canvas">>) -> true;
is_html_tag(<<"caption">>) -> true;
is_html_tag(<<"center">>) -> true;
is_html_tag(<<"cite">>) -> true;
is_html_tag(<<"code">>) -> true;
is_html_tag(<<"col">>) -> true;
is_html_tag(<<"colgroup">>) -> true;
is_html_tag(<<"data">>) -> true;
is_html_tag(<<"datalist">>) -> true;
is_html_tag(<<"dd">>) -> true;
is_html_tag(<<"del">>) -> true;
is_html_tag(<<"details">>) -> true;
is_html_tag(<<"dfn">>) -> true;
is_html_tag(<<"dialog">>) -> true;
is_html_tag(<<"dir">>) -> true;
is_html_tag(<<"div">>) -> true;
is_html_tag(<<"dl">>) -> true;
is_html_tag(<<"dt">>) -> true;
is_html_tag(<<"em">>) -> true;
is_html_tag(<<"embed">>) -> true;
is_html_tag(<<"fieldset">>) -> true;
is_html_tag(<<"figcaption">>) -> true;
is_html_tag(<<"figure">>) -> true;
is_html_tag(<<"font">>) -> true;
is_html_tag(<<"footer">>) -> true;
is_html_tag(<<"form">>) -> true;
is_html_tag(<<"frame">>) -> true;
is_html_tag(<<"frameset">>) -> true;
is_html_tag(<<"h1">>) -> true;
is_html_tag(<<"head">>) -> true;
is_html_tag(<<"header">>) -> true;
is_html_tag(<<"hr">>) -> true;
is_html_tag(<<"html">>) -> true;
is_html_tag(<<"i">>) -> true;
is_html_tag(<<"iframe">>) -> true;
is_html_tag(<<"img">>) -> true;
is_html_tag(<<"input">>) -> true;
is_html_tag(<<"ins">>) -> true;
is_html_tag(<<"kbd">>) -> true;
is_html_tag(<<"keygen">>) -> true;
is_html_tag(<<"label">>) -> true;
is_html_tag(<<"legend">>) -> true;
is_html_tag(<<"li">>) -> true;
is_html_tag(<<"link">>) -> true;
is_html_tag(<<"main">>) -> true;
is_html_tag(<<"map">>) -> true;
is_html_tag(<<"mark">>) -> true;
is_html_tag(<<"meta">>) -> true;
is_html_tag(<<"meter">>) -> true;
is_html_tag(<<"nav">>) -> true;
is_html_tag(<<"noframes">>) -> true;
is_html_tag(<<"noscript">>) -> true;
is_html_tag(<<"object">>) -> true;
is_html_tag(<<"ol">>) -> true;
is_html_tag(<<"optgroup">>) -> true;
is_html_tag(<<"option">>) -> true;
is_html_tag(<<"output">>) -> true;
is_html_tag(<<"p">>) -> true;
is_html_tag(<<"param">>) -> true;
is_html_tag(<<"picture">>) -> true;
is_html_tag(<<"pre">>) -> true;
is_html_tag(<<"progress">>) -> true;
is_html_tag(<<"q">>) -> true;
is_html_tag(<<"rp">>) -> true;
is_html_tag(<<"rt">>) -> true;
is_html_tag(<<"ruby">>) -> true;
is_html_tag(<<"s">>) -> true;
is_html_tag(<<"samp">>) -> true;
is_html_tag(<<"script">>) -> true;
is_html_tag(<<"section">>) -> true;
is_html_tag(<<"select">>) -> true;
is_html_tag(<<"small">>) -> true;
is_html_tag(<<"source">>) -> true;
is_html_tag(<<"span">>) -> true;
is_html_tag(<<"strike">>) -> true;
is_html_tag(<<"strong">>) -> true;
is_html_tag(<<"style">>) -> true;
is_html_tag(<<"sub">>) -> true;
is_html_tag(<<"summary">>) -> true;
is_html_tag(<<"sup">>) -> true;
is_html_tag(<<"svg">>) -> true;
is_html_tag(<<"table">>) -> true;
is_html_tag(<<"tbody">>) -> true;
is_html_tag(<<"td">>) -> true;
is_html_tag(<<"template">>) -> true;
is_html_tag(<<"textarea">>) -> true;
is_html_tag(<<"tfoot">>) -> true;
is_html_tag(<<"th">>) -> true;
is_html_tag(<<"thead">>) -> true;
is_html_tag(<<"time">>) -> true;
is_html_tag(<<"title">>) -> true;
is_html_tag(<<"tr">>) -> true;
is_html_tag(<<"track">>) -> true;
is_html_tag(<<"tt">>) -> true;
is_html_tag(<<"u">>) -> true;
is_html_tag(<<"ul">>) -> true;
is_html_tag(<<"var">>) -> true;
is_html_tag(<<"video">>) -> true;
is_html_tag(<<"wbr">>) -> true;
is_html_tag(_) -> false.
%% html entity references that are shorter than decimal encoding
reference(160) -> <<" ">>;
reference(162) -> <<"¢">>;
reference(165) -> <<"¥">>;
reference(167) -> <<"§">>;
reference(168) -> <<"¨">>;
reference(169) -> <<"©">>;
reference(170) -> <<"ª">>;
reference(172) -> <<"¬">>;
reference(173) -> <<"­">>;
reference(174) -> <<"®">>;
reference(175) -> <<"¯">>;
reference(176) -> <<"°">>;
reference(177) -> <<"±">>;
reference(178) -> <<"²">>;
reference(179) -> <<"³">>;
reference(182) -> <<"¶">>;
reference(185) -> <<"¹">>;
reference(186) -> <<"º">>;
reference(189) -> <<"½">>;
reference(196) -> <<"Ä">>;
reference(203) -> <<"Ë">>;
reference(207) -> <<"Ï">>;
reference(208) -> <<"Ð">>;
reference(214) -> <<"Ö">>;
reference(220) -> <<"Ü">>;
reference(228) -> <<"ä">>;
reference(235) -> <<"ë">>;
reference(239) -> <<"ï">>;
reference(240) -> <<"ð">>;
reference(246) -> <<"ö">>;
reference(247) -> <<"÷">>;
reference(252) -> <<"ü">>;
reference(255) -> <<"ÿ">>;
reference(266) -> <<"Ċ">>;
reference(267) -> <<"ċ">>;
reference(278) -> <<"Ė">>;
reference(279) -> <<"ė">>;
reference(288) -> <<"Ġ">>;
reference(289) -> <<"ġ">>;
reference(304) -> <<"İ">>;
reference(330) -> <<"Ŋ">>;
reference(331) -> <<"ŋ">>;
reference(376) -> <<"Ÿ">>;
reference(379) -> <<"Ż">>;
reference(380) -> <<"ż">>;
reference(402) -> <<"ƒ">>;
reference(710) -> <<"ˆ">>;
reference(729) -> <<"˙">>;
reference(730) -> <<"˚">>;
reference(731) -> <<"˛">>;
reference(914) -> <<"Β">>;
reference(918) -> <<"Ζ">>;
reference(919) -> <<"Η">>;
reference(921) -> <<"Ι">>;
reference(924) -> <<"Μ">>;
reference(925) -> <<"Ν">>;
reference(926) -> <<"Ξ">>;
reference(928) -> <<"Π">>;
reference(929) -> <<"Ρ">>;
reference(932) -> <<"Τ">>;
reference(934) -> <<"Φ">>;
reference(935) -> <<"Χ">>;
reference(936) -> <<"Ψ">>;
reference(937) -> <<"Ω">>;
reference(946) -> <<"β">>;
reference(949) -> <<"ε">>;
reference(950) -> <<"ζ">>;
reference(951) -> <<"η">>;
reference(953) -> <<"ι">>;
reference(956) -> <<"μ">>;
reference(957) -> <<"ν">>;
reference(958) -> <<"ξ">>;
reference(960) -> <<"π">>;
reference(961) -> <<"ρ">>;
reference(964) -> <<"τ">>;
reference(965) -> <<"υ">>;
reference(966) -> <<"φ">>;
reference(967) -> <<"χ">>;
reference(968) -> <<"ψ">>;
reference(978) -> <<"ϒ">>;
reference(981) -> <<"ϕ">>;
reference(982) -> <<"ϖ">>;
reference(1009) -> <<"ϱ">>;
reference(1013) -> <<"ϵ">>;
reference(1014) -> <<"϶">>;
reference(1025) -> <<"Ё">>;
reference(1026) -> <<"Ђ">>;
reference(1027) -> <<"Ѓ">>;
reference(1028) -> <<"Є">>;
reference(1029) -> <<"Ѕ">>;
reference(1030) -> <<"І">>;
reference(1031) -> <<"Ї">>;
reference(1033) -> <<"Љ">>;
reference(1034) -> <<"Њ">>;
reference(1035) -> <<"Ћ">>;
reference(1036) -> <<"Ќ">>;
reference(1038) -> <<"Ў">>;
reference(1039) -> <<"Џ">>;
reference(1040) -> <<"А">>;
reference(1041) -> <<"Б">>;
reference(1042) -> <<"В">>;
reference(1043) -> <<"Г">>;
reference(1044) -> <<"Д">>;
reference(1045) -> <<"Е">>;
reference(1046) -> <<"Ж">>;
reference(1047) -> <<"З">>;
reference(1048) -> <<"И">>;
reference(1049) -> <<"Й">>;
reference(1050) -> <<"К">>;
reference(1051) -> <<"Л">>;
reference(1052) -> <<"М">>;
reference(1053) -> <<"Н">>;
reference(1054) -> <<"О">>;
reference(1055) -> <<"П">>;
reference(1056) -> <<"Р">>;
reference(1057) -> <<"С">>;
reference(1058) -> <<"Т">>;
reference(1059) -> <<"У">>;
reference(1060) -> <<"Ф">>;
reference(1061) -> <<"Х">>;
reference(1062) -> <<"Ц">>;
reference(1063) -> <<"Ч">>;
reference(1064) -> <<"Ш">>;
reference(1067) -> <<"Ы">>;
reference(1069) -> <<"Э">>;
reference(1070) -> <<"Ю">>;
reference(1071) -> <<"Я">>;
reference(1072) -> <<"а">>;
reference(1073) -> <<"б">>;
reference(1074) -> <<"в">>;
reference(1075) -> <<"г">>;
reference(1076) -> <<"д">>;
reference(1077) -> <<"е">>;
reference(1078) -> <<"ж">>;
reference(1079) -> <<"з">>;
reference(1080) -> <<"и">>;
reference(1081) -> <<"й">>;
reference(1082) -> <<"к">>;
reference(1083) -> <<"л">>;
reference(1084) -> <<"м">>;
reference(1085) -> <<"н">>;
reference(1086) -> <<"о">>;
reference(1087) -> <<"п">>;
reference(1088) -> <<"р">>;
reference(1089) -> <<"с">>;
reference(1090) -> <<"т">>;
reference(1091) -> <<"у">>;
reference(1092) -> <<"ф">>;
reference(1093) -> <<"х">>;
reference(1094) -> <<"ц">>;
reference(1095) -> <<"ч">>;
reference(1096) -> <<"ш">>;
reference(1099) -> <<"ы">>;
reference(1101) -> <<"э">>;
reference(1102) -> <<"ю">>;
reference(1103) -> <<"я">>;
reference(1105) -> <<"ё">>;
reference(1106) -> <<"ђ">>;
reference(1107) -> <<"ѓ">>;
reference(1108) -> <<"є">>;
reference(1109) -> <<"ѕ">>;
reference(1110) -> <<"і">>;
reference(1111) -> <<"ї">>;
reference(1113) -> <<"љ">>;
reference(1114) -> <<"њ">>;
reference(1115) -> <<"ћ">>;
reference(1116) -> <<"ќ">>;
reference(1118) -> <<"ў">>;
reference(1119) -> <<"џ">>;
reference(8194) -> <<" ">>;
reference(8195) -> <<" ">>;
reference(8199) -> <<" ">>;
reference(8204) -> <<"‌">>;
reference(8205) -> <<"‍">>;
reference(8206) -> <<"‎">>;
reference(8207) -> <<"‏">>;
reference(8208) -> <<"‐">>;
reference(8211) -> <<"–">>;
reference(8212) -> <<"—">>;
reference(8214) -> <<"‖">>;
reference(8216) -> <<"‘">>;
reference(8217) -> <<"’">>;
reference(8218) -> <<"‚">>;
reference(8220) -> <<"“">>;
reference(8221) -> <<"”">>;
reference(8222) -> <<"„">>;
reference(8226) -> <<"•">>;
reference(8229) -> <<"‥">>;
reference(8230) -> <<"…">>;
reference(8242) -> <<"′">>;
reference(8243) -> <<"″">>;
reference(8254) -> <<"‾">>;
reference(8257) -> <<"⁁">>;
reference(8260) -> <<"⁄">>;
reference(8271) -> <<"⁏">>;
reference(8289) -> <<"⁡">>;
reference(8290) -> <<"⁢">>;
reference(8291) -> <<"⁣">>;
reference(8364) -> <<"€">>;
reference(8411) -> <<"⃛">>;
reference(8450) -> <<"ℂ">>;
reference(8458) -> <<"ℊ">>;
reference(8459) -> <<"ℋ">>;
reference(8460) -> <<"ℌ">>;
reference(8461) -> <<"ℍ">>;
reference(8463) -> <<"ℏ">>;
reference(8464) -> <<"ℐ">>;
reference(8465) -> <<"ℑ">>;
reference(8466) -> <<"ℒ">>;
reference(8467) -> <<"ℓ">>;
reference(8469) -> <<"ℕ">>;
reference(8472) -> <<"℘">>;
reference(8473) -> <<"ℙ">>;
reference(8474) -> <<"ℚ">>;
reference(8475) -> <<"ℛ">>;
reference(8476) -> <<"ℜ">>;
reference(8477) -> <<"ℝ">>;
reference(8478) -> <<"℞">>;
reference(8482) -> <<"™">>;
reference(8484) -> <<"ℤ">>;
reference(8487) -> <<"℧">>;
reference(8488) -> <<"ℨ">>;
reference(8489) -> <<"℩">>;
reference(8492) -> <<"ℬ">>;
reference(8493) -> <<"ℭ">>;
reference(8495) -> <<"ℯ">>;
reference(8496) -> <<"ℰ">>;
reference(8497) -> <<"ℱ">>;
reference(8499) -> <<"ℳ">>;
reference(8500) -> <<"ℴ">>;
reference(8501) -> <<"ℵ">>;
reference(8502) -> <<"ℶ">>;
reference(8503) -> <<"ℷ">>;
reference(8517) -> <<"ⅅ">>;
reference(8518) -> <<"ⅆ">>;
reference(8519) -> <<"ⅇ">>;
reference(8520) -> <<"ⅈ">>;
reference(8592) -> <<"←">>;
reference(8593) -> <<"↑">>;
reference(8594) -> <<"→">>;
reference(8595) -> <<"↓">>;
reference(8596) -> <<"↔">>;
reference(8597) -> <<"↕">>;
reference(8598) -> <<"↖">>;
reference(8599) -> <<"↗">>;
reference(8600) -> <<"↘">>;
reference(8601) -> <<"↙">>;
reference(8602) -> <<"↚">>;
reference(8603) -> <<"↛">>;
reference(8605) -> <<"↝">>;
reference(8606) -> <<"↞">>;
reference(8607) -> <<"↟">>;
reference(8608) -> <<"↠">>;
reference(8609) -> <<"↡">>;
reference(8614) -> <<"↦">>;
reference(8621) -> <<"↭">>;
reference(8622) -> <<"↮">>;
reference(8624) -> <<"↰">>;
reference(8625) -> <<"↱">>;
reference(8626) -> <<"↲">>;
reference(8627) -> <<"↳">>;
reference(8629) -> <<"↵">>;
reference(8634) -> <<"↺">>;
reference(8635) -> <<"↻">>;
reference(8636) -> <<"↼">>;
reference(8637) -> <<"↽">>;
reference(8638) -> <<"↾">>;
reference(8639) -> <<"↿">>;
reference(8640) -> <<"⇀">>;
reference(8641) -> <<"⇁">>;
reference(8642) -> <<"⇂">>;
reference(8643) -> <<"⇃">>;
reference(8644) -> <<"⇄">>;
reference(8645) -> <<"⇅">>;
reference(8646) -> <<"⇆">>;
reference(8647) -> <<"⇇">>;
reference(8648) -> <<"⇈">>;
reference(8649) -> <<"⇉">>;
reference(8650) -> <<"⇊">>;
reference(8651) -> <<"⇋">>;
reference(8652) -> <<"⇌">>;
reference(8653) -> <<"⇍">>;
reference(8654) -> <<"⇎">>;
reference(8655) -> <<"⇏">>;
reference(8656) -> <<"⇐">>;
reference(8657) -> <<"⇑">>;
reference(8658) -> <<"⇒">>;
reference(8659) -> <<"⇓">>;
reference(8660) -> <<"⇔">>;
reference(8661) -> <<"⇕">>;
reference(8662) -> <<"⇖">>;
reference(8663) -> <<"⇗">>;
reference(8664) -> <<"⇘">>;
reference(8665) -> <<"⇙">>;
reference(8666) -> <<"⇚">>;
reference(8667) -> <<"⇛">>;
reference(8676) -> <<"⇤">>;
reference(8677) -> <<"⇥">>;
reference(8693) -> <<"⇵">>;
reference(8701) -> <<"⇽">>;
reference(8702) -> <<"⇾">>;
reference(8703) -> <<"⇿">>;
reference(8705) -> <<"∁">>;
reference(8706) -> <<"∂">>;
reference(8707) -> <<"∃">>;
reference(8709) -> <<"∅">>;
reference(8711) -> <<"∇">>;
reference(8712) -> <<"∈">>;
reference(8713) -> <<"∉">>;
reference(8715) -> <<"∋">>;
reference(8716) -> <<"∌">>;
reference(8719) -> <<"∏">>;
reference(8721) -> <<"∑">>;
reference(8722) -> <<"−">>;
reference(8723) -> <<"∓">>;
reference(8726) -> <<"∖">>;
reference(8730) -> <<"√">>;
reference(8733) -> <<"∝">>;
reference(8734) -> <<"∞">>;
reference(8735) -> <<"∟">>;
reference(8736) -> <<"∠">>;
reference(8739) -> <<"∣">>;
reference(8740) -> <<"∤">>;
reference(8741) -> <<"∥">>;
reference(8742) -> <<"∦">>;
reference(8743) -> <<"∧">>;
reference(8744) -> <<"∨">>;
reference(8745) -> <<"∩">>;
reference(8746) -> <<"∪">>;
reference(8747) -> <<"∫">>;
reference(8748) -> <<"∬">>;
reference(8749) -> <<"∭">>;
reference(8750) -> <<"∮">>;
reference(8753) -> <<"∱">>;
reference(8758) -> <<"∶">>;
reference(8759) -> <<"∷">>;
reference(8762) -> <<"∺">>;
reference(8764) -> <<"∼">>;
reference(8765) -> <<"∽">>;
reference(8766) -> <<"∾">>;
reference(8767) -> <<"∿">>;
reference(8768) -> <<"≀">>;
reference(8769) -> <<"≁">>;
reference(8770) -> <<"≂">>;
reference(8771) -> <<"≃">>;
reference(8772) -> <<"≄">>;
reference(8773) -> <<"≅">>;
reference(8774) -> <<"≆">>;
reference(8775) -> <<"≇">>;
reference(8776) -> <<"≈">>;
reference(8777) -> <<"≉">>;
reference(8778) -> <<"≊">>;
reference(8779) -> <<"≋">>;
reference(8780) -> <<"≌">>;
reference(8782) -> <<"≎">>;
reference(8783) -> <<"≏">>;
reference(8784) -> <<"≐">>;
reference(8785) -> <<"≑">>;
reference(8786) -> <<"≒">>;
reference(8787) -> <<"≓">>;
reference(8790) -> <<"≖">>;
reference(8791) -> <<"≗">>;
reference(8794) -> <<"≚">>;
reference(8796) -> <<"≜">>;
reference(8800) -> <<"≠">>;
reference(8801) -> <<"≡">>;
reference(8804) -> <<"≤">>;
reference(8805) -> <<"≥">>;
reference(8806) -> <<"≦">>;
reference(8807) -> <<"≧">>;
reference(8808) -> <<"≨">>;
reference(8809) -> <<"≩">>;
reference(8810) -> <<"≪">>;
reference(8811) -> <<"≫">>;
reference(8812) -> <<"≬">>;
reference(8814) -> <<"≮">>;
reference(8815) -> <<"≯">>;
reference(8816) -> <<"≰">>;
reference(8817) -> <<"≱">>;
reference(8818) -> <<"≲">>;
reference(8819) -> <<"≳">>;
reference(8820) -> <<"≴">>;
reference(8821) -> <<"≵">>;
reference(8822) -> <<"≶">>;
reference(8823) -> <<"≷">>;
reference(8824) -> <<"≸">>;
reference(8825) -> <<"≹">>;
reference(8826) -> <<"≺">>;
reference(8827) -> <<"≻">>;
reference(8828) -> <<"≼">>;
reference(8829) -> <<"≽">>;
reference(8830) -> <<"≾">>;
reference(8831) -> <<"≿">>;
reference(8832) -> <<"⊀">>;
reference(8833) -> <<"⊁">>;
reference(8834) -> <<"⊂">>;
reference(8835) -> <<"⊃">>;
reference(8836) -> <<"⊄">>;
reference(8837) -> <<"⊅">>;
reference(8838) -> <<"⊆">>;
reference(8839) -> <<"⊇">>;
reference(8840) -> <<"⊈">>;
reference(8841) -> <<"⊉">>;
reference(8842) -> <<"⊊">>;
reference(8843) -> <<"⊋">>;
reference(8846) -> <<"⊎">>;
reference(8847) -> <<"⊏">>;
reference(8848) -> <<"⊐">>;
reference(8851) -> <<"⊓">>;
reference(8852) -> <<"⊔">>;
reference(8853) -> <<"⊕">>;
reference(8856) -> <<"⊘">>;
reference(8857) -> <<"⊙">>;
reference(8858) -> <<"⊚">>;
reference(8859) -> <<"⊛">>;
reference(8861) -> <<"⊝">>;
reference(8862) -> <<"⊞">>;
reference(8865) -> <<"⊡">>;
reference(8866) -> <<"⊢">>;
reference(8867) -> <<"⊣">>;
reference(8868) -> <<"⊤">>;
reference(8869) -> <<"⊥">>;
reference(8872) -> <<"⊨">>;
reference(8873) -> <<"⊩">>;
reference(8875) -> <<"⊫">>;
reference(8882) -> <<"⊲">>;
reference(8883) -> <<"⊳">>;
reference(8884) -> <<"⊴">>;
reference(8885) -> <<"⊵">>;
reference(8887) -> <<"⊷">>;
reference(8888) -> <<"⊸">>;
reference(8895) -> <<"⊿">>;
reference(8896) -> <<"⋀">>;
reference(8897) -> <<"⋁">>;
reference(8898) -> <<"⋂">>;
reference(8899) -> <<"⋃">>;
reference(8900) -> <<"⋄">>;
reference(8901) -> <<"⋅">>;
reference(8902) -> <<"⋆">>;
reference(8909) -> <<"⋍">>;
reference(8910) -> <<"⋎">>;
reference(8911) -> <<"⋏">>;
reference(8912) -> <<"⋐">>;
reference(8913) -> <<"⋑">>;
reference(8914) -> <<"⋒">>;
reference(8915) -> <<"⋓">>;
reference(8916) -> <<"⋔">>;
reference(8917) -> <<"⋕">>;
reference(8918) -> <<"⋖">>;
reference(8919) -> <<"⋗">>;
reference(8920) -> <<"⋘">>;
reference(8921) -> <<"⋙">>;
reference(8922) -> <<"⋚">>;
reference(8923) -> <<"⋛">>;
reference(8926) -> <<"⋞">>;
reference(8927) -> <<"⋟">>;
reference(8934) -> <<"⋦">>;
reference(8935) -> <<"⋧">>;
reference(8938) -> <<"⋪">>;
reference(8939) -> <<"⋫">>;
reference(8943) -> <<"⋯">>;
reference(8944) -> <<"⋰">>;
reference(8945) -> <<"⋱">>;
reference(8946) -> <<"⋲">>;
reference(8948) -> <<"⋴">>;
reference(8953) -> <<"⋹">>;
reference(8954) -> <<"⋺">>;
reference(8955) -> <<"⋻">>;
reference(8956) -> <<"⋼">>;
reference(8968) -> <<"⌈">>;
reference(8969) -> <<"⌉">>;
reference(8976) -> <<"⌐">>;
reference(8994) -> <<"⌢">>;
reference(8995) -> <<"⌣">>;
reference(9021) -> <<"⌽">>;
reference(9140) -> <<"⎴">>;
reference(9141) -> <<"⎵">>;
reference(9251) -> <<"␣">>;
reference(9416) -> <<"Ⓢ">>;
reference(9472) -> <<"─">>;
reference(9474) -> <<"│">>;
reference(9484) -> <<"┌">>;
reference(9488) -> <<"┐">>;
reference(9492) -> <<"└">>;
reference(9496) -> <<"┘">>;
reference(9500) -> <<"├">>;
reference(9508) -> <<"┤">>;
reference(9516) -> <<"┬">>;
reference(9524) -> <<"┴">>;
reference(9532) -> <<"┼">>;
reference(9552) -> <<"═">>;
reference(9553) -> <<"║">>;
reference(9554) -> <<"╒">>;
reference(9555) -> <<"╓">>;
reference(9556) -> <<"╔">>;
reference(9557) -> <<"╕">>;
reference(9558) -> <<"╖">>;
reference(9559) -> <<"╗">>;
reference(9560) -> <<"╘">>;
reference(9561) -> <<"╙">>;
reference(9562) -> <<"╚">>;
reference(9563) -> <<"╛">>;
reference(9564) -> <<"╜">>;
reference(9565) -> <<"╝">>;
reference(9566) -> <<"╞">>;
reference(9567) -> <<"╟">>;
reference(9568) -> <<"╠">>;
reference(9569) -> <<"╡">>;
reference(9570) -> <<"╢">>;
reference(9571) -> <<"╣">>;
reference(9572) -> <<"╤">>;
reference(9573) -> <<"╥">>;
reference(9574) -> <<"╦">>;
reference(9575) -> <<"╧">>;
reference(9576) -> <<"╨">>;
reference(9577) -> <<"╩">>;
reference(9578) -> <<"╪">>;
reference(9579) -> <<"╫">>;
reference(9580) -> <<"╬">>;
reference(9600) -> <<"▀">>;
reference(9604) -> <<"▄">>;
reference(9608) -> <<"█">>;
reference(9617) -> <<"░">>;
reference(9618) -> <<"▒">>;
reference(9619) -> <<"▓">>;
reference(9633) -> <<"□">>;
reference(9642) -> <<"▪">>;
reference(9645) -> <<"▭">>;
reference(9649) -> <<"▱">>;
reference(9651) -> <<"△">>;
reference(9652) -> <<"▴">>;
reference(9653) -> <<"▵">>;
reference(9656) -> <<"▸">>;
reference(9657) -> <<"▹">>;
reference(9661) -> <<"▽">>;
reference(9662) -> <<"▾">>;
reference(9663) -> <<"▿">>;
reference(9666) -> <<"◂">>;
reference(9667) -> <<"◃">>;
reference(9674) -> <<"◊">>;
reference(9675) -> <<"○">>;
reference(9711) -> <<"◯">>;
reference(9720) -> <<"◸">>;
reference(9721) -> <<"◹">>;
reference(9722) -> <<"◺">>;
reference(9733) -> <<"★">>;
reference(9734) -> <<"☆">>;
reference(9742) -> <<"☎">>;
reference(9794) -> <<"♂">>;
reference(9827) -> <<"♣">>;
reference(9830) -> <<"♦">>;
reference(9834) -> <<"♪">>;
reference(9837) -> <<"♭">>;
reference(9838) -> <<"♮">>;
reference(9839) -> <<"♯">>;
reference(10003) -> <<"✓">>;
reference(10007) -> <<"✗">>;
reference(10016) -> <<"✠">>;
reference(10038) -> <<"✶">>;
reference(10098) -> <<"❲">>;
reference(10099) -> <<"❳">>;
reference(10214) -> <<"⟦">>;
reference(10215) -> <<"⟧">>;
reference(10216) -> <<"⟨">>;
reference(10217) -> <<"⟩">>;
reference(10218) -> <<"⟪">>;
reference(10219) -> <<"⟫">>;
reference(10220) -> <<"⟬">>;
reference(10221) -> <<"⟭">>;
reference(10229) -> <<"⟵">>;
reference(10230) -> <<"⟶">>;
reference(10231) -> <<"⟷">>;
reference(10232) -> <<"⟸">>;
reference(10233) -> <<"⟹">>;
reference(10234) -> <<"⟺">>;
reference(10236) -> <<"⟼">>;
reference(10498) -> <<"⤂">>;
reference(10499) -> <<"⤃">>;
reference(10500) -> <<"⤄">>;
reference(10501) -> <<"⤅">>;
reference(10508) -> <<"⤌">>;
reference(10509) -> <<"⤍">>;
reference(10510) -> <<"⤎">>;
reference(10511) -> <<"⤏">>;
reference(10512) -> <<"⤐">>;
reference(10518) -> <<"⤖">>;
reference(10521) -> <<"⤙">>;
reference(10522) -> <<"⤚">>;
reference(10523) -> <<"⤛">>;
reference(10524) -> <<"⤜">>;
reference(10525) -> <<"⤝">>;
reference(10526) -> <<"⤞">>;
reference(10531) -> <<"⤣">>;
reference(10532) -> <<"⤤">>;
reference(10533) -> <<"⤥">>;
reference(10534) -> <<"⤦">>;
reference(10535) -> <<"⤧">>;
reference(10536) -> <<"⤨">>;
reference(10537) -> <<"⤩">>;
reference(10538) -> <<"⤪">>;
reference(10547) -> <<"⤳">>;
reference(10550) -> <<"⤶">>;
reference(10551) -> <<"⤷">>;
reference(10553) -> <<"⤹">>;
reference(10565) -> <<"⥅">>;
reference(10594) -> <<"⥢">>;
reference(10595) -> <<"⥣">>;
reference(10596) -> <<"⥤">>;
reference(10597) -> <<"⥥">>;
reference(10602) -> <<"⥪">>;
reference(10603) -> <<"⥫">>;
reference(10604) -> <<"⥬">>;
reference(10605) -> <<"⥭">>;
reference(10606) -> <<"⥮">>;
reference(10607) -> <<"⥯">>;
reference(10609) -> <<"⥱">>;
reference(10613) -> <<"⥵">>;
reference(10614) -> <<"⥶">>;
reference(10616) -> <<"⥸">>;
reference(10620) -> <<"⥼">>;
reference(10621) -> <<"⥽">>;
reference(10622) -> <<"⥾">>;
reference(10623) -> <<"⥿">>;
reference(10629) -> <<"⦅">>;
reference(10630) -> <<"⦆">>;
reference(10635) -> <<"⦋">>;
reference(10636) -> <<"⦌">>;
reference(10641) -> <<"⦑">>;
reference(10642) -> <<"⦒">>;
reference(10643) -> <<"⦓">>;
reference(10644) -> <<"⦔">>;
reference(10645) -> <<"⦕">>;
reference(10646) -> <<"⦖">>;
reference(10652) -> <<"⦜">>;
reference(10660) -> <<"⦤">>;
reference(10661) -> <<"⦥">>;
reference(10677) -> <<"⦵">>;
reference(10678) -> <<"⦶">>;
reference(10679) -> <<"⦷">>;
reference(10681) -> <<"⦹">>;
reference(10684) -> <<"⦼">>;
reference(10686) -> <<"⦾">>;
reference(10687) -> <<"⦿">>;
reference(10688) -> <<"⧀">>;
reference(10689) -> <<"⧁">>;
reference(10691) -> <<"⧃">>;
reference(10692) -> <<"⧄">>;
reference(10693) -> <<"⧅">>;
reference(10697) -> <<"⧉">>;
reference(10701) -> <<"⧍">>;
reference(10716) -> <<"⧜">>;
reference(10723) -> <<"⧣">>;
reference(10731) -> <<"⧫">>;
reference(10742) -> <<"⧶">>;
reference(10752) -> <<"⨀">>;
reference(10753) -> <<"⨁">>;
reference(10754) -> <<"⨂">>;
reference(10756) -> <<"⨄">>;
reference(10758) -> <<"⨆">>;
reference(10764) -> <<"⨌">>;
reference(10769) -> <<"⨑">>;
reference(10789) -> <<"⨥">>;
reference(10793) -> <<"⨩">>;
reference(10797) -> <<"⨭">>;
reference(10798) -> <<"⨮">>;
reference(10799) -> <<"⨯">>;
reference(10800) -> <<"⨰">>;
reference(10803) -> <<"⨳">>;
reference(10807) -> <<"⨷">>;
reference(10808) -> <<"⨸">>;
reference(10812) -> <<"⨼">>;
reference(10815) -> <<"⨿">>;
reference(10816) -> <<"⩀">>;
reference(10818) -> <<"⩂">>;
reference(10819) -> <<"⩃">>;
reference(10820) -> <<"⩄">>;
reference(10821) -> <<"⩅">>;
reference(10822) -> <<"⩆">>;
reference(10823) -> <<"⩇">>;
reference(10826) -> <<"⩊">>;
reference(10827) -> <<"⩋">>;
reference(10828) -> <<"⩌">>;
reference(10829) -> <<"⩍">>;
reference(10835) -> <<"⩓">>;
reference(10836) -> <<"⩔">>;
reference(10837) -> <<"⩕">>;
reference(10838) -> <<"⩖">>;
reference(10842) -> <<"⩚">>;
reference(10843) -> <<"⩛">>;
reference(10844) -> <<"⩜">>;
reference(10845) -> <<"⩝">>;
reference(10847) -> <<"⩟">>;
reference(10854) -> <<"⩦">>;
reference(10858) -> <<"⩪">>;
reference(10862) -> <<"⩮">>;
reference(10863) -> <<"⩯">>;
reference(10864) -> <<"⩰">>;
reference(10865) -> <<"⩱">>;
reference(10866) -> <<"⩲">>;
reference(10867) -> <<"⩳">>;
reference(10868) -> <<"⩴">>;
reference(10869) -> <<"⩵">>;
reference(10871) -> <<"⩷">>;
reference(10873) -> <<"⩹">>;
reference(10874) -> <<"⩺">>;
reference(10877) -> <<"⩽">>;
reference(10878) -> <<"⩾">>;
reference(10879) -> <<"⩿">>;
reference(10880) -> <<"⪀">>;
reference(10885) -> <<"⪅">>;
reference(10886) -> <<"⪆">>;
reference(10887) -> <<"⪇">>;
reference(10888) -> <<"⪈">>;
reference(10889) -> <<"⪉">>;
reference(10890) -> <<"⪊">>;
reference(10891) -> <<"⪋">>;
reference(10892) -> <<"⪌">>;
reference(10893) -> <<"⪍">>;
reference(10894) -> <<"⪎">>;
reference(10895) -> <<"⪏">>;
reference(10896) -> <<"⪐">>;
reference(10897) -> <<"⪑">>;
reference(10898) -> <<"⪒">>;
reference(10899) -> <<"⪓">>;
reference(10900) -> <<"⪔">>;
reference(10901) -> <<"⪕">>;
reference(10902) -> <<"⪖">>;
reference(10903) -> <<"⪗">>;
reference(10904) -> <<"⪘">>;
reference(10905) -> <<"⪙">>;
reference(10906) -> <<"⪚">>;
reference(10909) -> <<"⪝">>;
reference(10910) -> <<"⪞">>;
reference(10911) -> <<"⪟">>;
reference(10912) -> <<"⪠">>;
reference(10916) -> <<"⪤">>;
reference(10917) -> <<"⪥">>;
reference(10918) -> <<"⪦">>;
reference(10919) -> <<"⪧">>;
reference(10920) -> <<"⪨">>;
reference(10921) -> <<"⪩">>;
reference(10922) -> <<"⪪">>;
reference(10923) -> <<"⪫">>;
reference(10924) -> <<"⪬">>;
reference(10925) -> <<"⪭">>;
reference(10926) -> <<"⪮">>;
reference(10927) -> <<"⪯">>;
reference(10928) -> <<"⪰">>;
reference(10931) -> <<"⪳">>;
reference(10932) -> <<"⪴">>;
reference(10933) -> <<"⪵">>;
reference(10934) -> <<"⪶">>;
reference(10935) -> <<"⪷">>;
reference(10936) -> <<"⪸">>;
reference(10937) -> <<"⪹">>;
reference(10938) -> <<"⪺">>;
reference(10939) -> <<"⪻">>;
reference(10940) -> <<"⪼">>;
reference(10941) -> <<"⪽">>;
reference(10942) -> <<"⪾">>;
reference(10949) -> <<"⫅">>;
reference(10950) -> <<"⫆">>;
reference(10951) -> <<"⫇">>;
reference(10952) -> <<"⫈">>;
reference(10955) -> <<"⫋">>;
reference(10956) -> <<"⫌">>;
reference(10959) -> <<"⫏">>;
reference(10960) -> <<"⫐">>;
reference(10961) -> <<"⫑">>;
reference(10962) -> <<"⫒">>;
reference(10963) -> <<"⫓">>;
reference(10964) -> <<"⫔">>;
reference(10965) -> <<"⫕">>;
reference(10966) -> <<"⫖">>;
reference(10969) -> <<"⫙">>;
reference(10971) -> <<"⫛">>;
reference(10980) -> <<"⫤">>;
reference(10982) -> <<"⫦">>;
reference(10983) -> <<"⫧">>;
reference(10984) -> <<"⫨">>;
reference(10985) -> <<"⫩">>;
reference(10987) -> <<"⫫">>;
reference(10988) -> <<"⫬">>;
reference(10989) -> <<"⫭">>;
reference(10990) -> <<"⫮">>;
reference(10991) -> <<"⫯">>;
reference(10992) -> <<"⫰">>;
reference(10993) -> <<"⫱">>;
reference(10994) -> <<"⫲">>;
reference(10995) -> <<"⫳">>;
reference(11005) -> <<"⫽">>;
reference(64256) -> <<"ff">>;
reference(64257) -> <<"fi">>;
reference(64258) -> <<"fl">>;
reference(64259) -> <<"ffi">>;
reference(64260) -> <<"ffl">>;
reference(119964) -> <<"𝒜">>;
reference(119966) -> <<"𝒞">>;
reference(119967) -> <<"𝒟">>;
reference(119970) -> <<"𝒢">>;
reference(119973) -> <<"𝒥">>;
reference(119974) -> <<"𝒦">>;
reference(119977) -> <<"𝒩">>;
reference(119978) -> <<"𝒪">>;
reference(119979) -> <<"𝒫">>;
reference(119980) -> <<"𝒬">>;
reference(119982) -> <<"𝒮">>;
reference(119983) -> <<"𝒯">>;
reference(119984) -> <<"𝒰">>;
reference(119985) -> <<"𝒱">>;
reference(119986) -> <<"𝒲">>;
reference(119987) -> <<"𝒳">>;
reference(119988) -> <<"𝒴">>;
reference(119989) -> <<"𝒵">>;
reference(119990) -> <<"𝒶">>;
reference(119991) -> <<"𝒷">>;
reference(119992) -> <<"𝒸">>;
reference(119993) -> <<"𝒹">>;
reference(119995) -> <<"𝒻">>;
reference(119997) -> <<"𝒽">>;
reference(119998) -> <<"𝒾">>;
reference(119999) -> <<"𝒿">>;
reference(120000) -> <<"𝓀">>;
reference(120001) -> <<"𝓁">>;
reference(120002) -> <<"𝓂">>;
reference(120003) -> <<"𝓃">>;
reference(120005) -> <<"𝓅">>;
reference(120006) -> <<"𝓆">>;
reference(120007) -> <<"𝓇">>;
reference(120008) -> <<"𝓈">>;
reference(120009) -> <<"𝓉">>;
reference(120010) -> <<"𝓊">>;
reference(120011) -> <<"𝓋">>;
reference(120012) -> <<"𝓌">>;
reference(120013) -> <<"𝓍">>;
reference(120014) -> <<"𝓎">>;
reference(120015) -> <<"𝓏">>;
reference(120068) -> <<"𝔄">>;
reference(120069) -> <<"𝔅">>;
reference(120071) -> <<"𝔇">>;
reference(120072) -> <<"𝔈">>;
reference(120073) -> <<"𝔉">>;
reference(120074) -> <<"𝔊">>;
reference(120077) -> <<"𝔍">>;
reference(120078) -> <<"𝔎">>;
reference(120079) -> <<"𝔏">>;
reference(120080) -> <<"𝔐">>;
reference(120081) -> <<"𝔑">>;
reference(120082) -> <<"𝔒">>;
reference(120083) -> <<"𝔓">>;
reference(120084) -> <<"𝔔">>;
reference(120086) -> <<"𝔖">>;
reference(120087) -> <<"𝔗">>;
reference(120088) -> <<"𝔘">>;
reference(120089) -> <<"𝔙">>;
reference(120090) -> <<"𝔚">>;
reference(120091) -> <<"𝔛">>;
reference(120092) -> <<"𝔜">>;
reference(120094) -> <<"𝔞">>;
reference(120095) -> <<"𝔟">>;
reference(120096) -> <<"𝔠">>;
reference(120097) -> <<"𝔡">>;
reference(120098) -> <<"𝔢">>;
reference(120099) -> <<"𝔣">>;
reference(120100) -> <<"𝔤">>;
reference(120101) -> <<"𝔥">>;
reference(120102) -> <<"𝔦">>;
reference(120103) -> <<"𝔧">>;
reference(120104) -> <<"𝔨">>;
reference(120105) -> <<"𝔩">>;
reference(120106) -> <<"𝔪">>;
reference(120107) -> <<"𝔫">>;
reference(120108) -> <<"𝔬">>;
reference(120109) -> <<"𝔭">>;
reference(120110) -> <<"𝔮">>;
reference(120111) -> <<"𝔯">>;
reference(120112) -> <<"𝔰">>;
reference(120113) -> <<"𝔱">>;
reference(120114) -> <<"𝔲">>;
reference(120115) -> <<"𝔳">>;
reference(120116) -> <<"𝔴">>;
reference(120117) -> <<"𝔵">>;
reference(120118) -> <<"𝔶">>;
reference(120119) -> <<"𝔷">>;
reference(120120) -> <<"𝔸">>;
reference(120121) -> <<"𝔹">>;
reference(120123) -> <<"𝔻">>;
reference(120124) -> <<"𝔼">>;
reference(120125) -> <<"𝔽">>;
reference(120126) -> <<"𝔾">>;
reference(120128) -> <<"𝕀">>;
reference(120129) -> <<"𝕁">>;
reference(120130) -> <<"𝕂">>;
reference(120131) -> <<"𝕃">>;
reference(120132) -> <<"𝕄">>;
reference(120134) -> <<"𝕆">>;
reference(120138) -> <<"𝕊">>;
reference(120139) -> <<"𝕋">>;
reference(120140) -> <<"𝕌">>;
reference(120141) -> <<"𝕍">>;
reference(120142) -> <<"𝕎">>;
reference(120143) -> <<"𝕏">>;
reference(120144) -> <<"𝕐">>;
reference(120146) -> <<"𝕒">>;
reference(120147) -> <<"𝕓">>;
reference(120148) -> <<"𝕔">>;
reference(120149) -> <<"𝕕">>;
reference(120150) -> <<"𝕖">>;
reference(120151) -> <<"𝕗">>;
reference(120152) -> <<"𝕘">>;
reference(120153) -> <<"𝕙">>;
reference(120154) -> <<"𝕚">>;
reference(120155) -> <<"𝕛">>;
reference(120156) -> <<"𝕜">>;
reference(120157) -> <<"𝕝">>;
reference(120158) -> <<"𝕞">>;
reference(120159) -> <<"𝕟">>;
reference(120160) -> <<"𝕠">>;
reference(120161) -> <<"𝕡">>;
reference(120162) -> <<"𝕢">>;
reference(120163) -> <<"𝕣">>;
reference(120164) -> <<"𝕤">>;
reference(120165) -> <<"𝕥">>;
reference(120166) -> <<"𝕦">>;
reference(120167) -> <<"𝕧">>;
reference(120168) -> <<"𝕨">>;
reference(120169) -> <<"𝕩">>;
reference(120170) -> <<"𝕪">>;
reference(120171) -> <<"𝕫">>;
reference(Cp) -> <<"&#", (integer_to_binary(Cp))/binary, ";">>.