% \iffalse meta-comment
%
%% File: tagpdf-struct.dtx
%
% Copyright (C) 2019-2020 Ulrike Fischer
%
% It may be distributed and/or modified under the conditions of the
% LaTeX Project Public License (LPPL), either version 1.3c of this
% license or (at your option) any later version.  The latest version
% of this license is in the file
%
%    https://www.latex-project.org/lppl.txt
%
% This file is part of the "tagpdf bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
%    https://github.com/u-fischer/tagpdf
%
% for those people who are interested.
%
% \fi
%
%    \begin{macrocode}
%<@@=tag>
%<*struct>
\ProvidesExplPackage {tagpdf-struct-code} {2021/02/23} {0.80}
 {part of tagpdf - code related to storing structure}

% I will use a latex counter for the structure count
% to have a chance to avoid double structures in align etc

\newcounter  { g_@@_struct_abs_int }
\int_gzero:N \c@g_@@_struct_abs_int

% ref code is in tagpdf.dtx

% a sequence stores structnum -> the obj numbers
% to allow easy mapping over the structures

\@@_seq_new:N  \g_@@_struct_objR_seq

% a sequence for the structure stack. When a sequence is opened it's number is put on the stack.
\seq_new:N    \g_@@_struct_stack_seq
\seq_gpush:Nn \g_@@_struct_stack_seq {0}

%this variables will hold the top entry and the parent on the stack
\tl_new:N     \l_@@_struct_stack_parent_tmp_tl
\tl_new:N     \g_@@_struct_stack_current_tl

% I need at least one structure: the StructTreeRoot
% normally it should have only one kid, e.g. the document element.

% The data of the StructTreeRoot and the StructElem are in properties:
% \g_@@_struct_0_prop for the root
% \g_@@_struct_N_prop, N >=1
% they have all the keys
% objnum    - number,
% Type      - StructTreeRoot or StructElem
% num - number (identical to the num in the name, or 0 for the root)
% and the keys from the two following lists
% (the root has a special set of properties).
% the values of the prop should be already escaped properly
% when the entries are created (title,lange,alt,E,actualtext)


\seq_const_from_clist:Nn \c_@@_struct_StructTreeRoot_entries_seq
  {%p. 857/858
    Type,              % always /StructTreeRoot
    K,                 % kid, dictionary or array of dictionaries
    IDTree,            % currently unused
    ParentTree,        % required,obj ref to the parent tree
    ParentTreeNextKey, %optional
    RoleMap,
    ClassMap
  }

\seq_const_from_clist:Nn \c_@@_struct_StructElem_entries_seq
  {%p 858 f
    Type,              %always /StructElem
    S,                 %tag/type
    P,                 %parent
    ID,                %optional
    Ref,               %optional, pdf 2.0 Use?
    Pg,                %obj num of starting page, optional
    K,                 %kids
    A,                 %attributes, probably unused
    C,                 %class ""
    %R,
    T,                 %title, value in () or <>
    Lang,              %language
    Alt,               % value in () or <>
    E,                 %abreviation
    ActualText,
    AF,                 %pdf 2.0, array of dict, associated files
    NS,                 %pdf 2.0, dict, namespace
    PhoneticAlphabet,   %pdf 2.0
    Phoneme             %pdf 2.0
  }

% I need an output handler for each prop, to get expandable output
% see https://tex.stackexchange.com/questions/424208/expandable-version-of-a-expl3-command/424213#424213

\cs_new:Nn \@@_struct_output_prop_aux:nn %#1 num, #2 key
  {
    \prop_if_in:cnT
      { g_@@_struct_#1_prop }
      { #2 }
      {
        \c_space_tl/#2~ \prop_item:cn{ g_@@_struct_#1_prop } { #2 }
      }
  }

\cs_new:Nn \@@_new_output_prop_handler:n
  {
    \cs_new:cn { @@_struct_output_prop_#1:n }
      {
        \@@_struct_output_prop_aux:nn {#1}{##1}
      }
  }


% the first one, the StructTreeRoot is special, so
% created manually:
\@@_prop_new:c { g_@@_struct_0_prop }
\@@_new_output_prop_handler:n {0}
\tl_gset:Nn \g_@@_struct_stack_current_tl {0}

\@@_seq_new:c  { g_@@_struct_kids_0_seq }

% deprecated
%\@@_prop_gput:cno
%  { g_@@_struct_0_prop }
%  { objnum}
%  { \c_@@_tree_obj_structtreeroot_tl }

% new
\@@_prop_gput:cno
  { g_@@_struct_0_prop }
  { objref}
  { \pdf_object_ref:n { c_@@_struct_0_obj } }

\@@_prop_gput:cno
  { g_@@_struct_0_prop }
  { Type }
  { /StructTreeRoot }

% the constants are defined in the tree code.
\@@_prop_gput:cnx
  { g_@@_struct_0_prop }
  { ParentTree }
  { \pdf_object_ref:n { c_@@_tree_parenttree_obj } }

\@@_prop_gput:cnx
  { g_@@_struct_0_prop }
  { RoleMap }
  { \pdf_object_ref:n { c_@@_tree_rolemap_obj } }

\@@_prop_gput:cno
  { g_@@_struct_0_prop }
  { entries }
  { StructTreeRoot }

\@@_prop_gput:cno
  { g_@@_struct_0_prop }
  { num }
  { 0 }

% commands to store the kids
% I don't compare the page objects number yet, but always add the /Pg key, perhaps later

\cs_new:Nn \@@_struct_kid_mc_gput_right:nn %#1 structure num, #2 MCID absnum%
  {
    %\@@_store_pdfpageref:Nn \l_tmpa_tl { \@@_ref_value:enn{mcid-#2}{tagabspage}{1} }
    \@@_seq_gput_right:cx
      { g_@@_struct_kids_#1_seq }
      {
        <<
        /Type \c_space_tl /MCR \c_space_tl
        /Pg %\c_space_tl \l_tmpa_tl \c_space_tl 0 \c_space_tl R \c_space_tl
          \c_space_tl
          \pdf_pageobject_ref:n { \@@_ref_value:enn{mcid-#2}{tagabspage}{1} }
        /MCID \c_space_tl \@@_ref_value:enn{mcid-#2}{tagmcid}{1}
        >>
      }
  }

\cs_new:Nn\@@_struct_kid_struct_gput_right:nn %#1 num of parent struct, #2 kid struct
  {
    \@@_seq_gput_right:cx
      { g_@@_struct_kids_#1_seq }
      {
        \prop_item:cn
          { g_@@_struct_#2_prop }
          { objref }
      }
 }

\cs_new:Nn\@@_struct_kid_link_gput_right:nn %#1 num of parent struct, #2 obj reference
  {
    \pdf_object_unnamed_write:nx
      { dict }
      {
        /Type \c_space_tl /OBJR \c_space_tl
        /Obj~#2
      }
    \@@_seq_gput_right:cx
      { g_@@_struct_kids_#1_seq }
      {
        \pdf_object_ref_last:
      }
  }

\cs_generate_variant:Nn\@@_struct_kid_link_gput_right:nn { nx }

\cs_new:Nn\@@_struct_exchange_kid_command:N %N= seq
  {
    \seq_gpop_left:NN #1 \l_tmpa_tl
    \regex_replace_once:nnN
      { \c{\@@_mc_insert_mcid_kids:n} }
      { \c{\@@_mc_insert_mcid_single_kids:n} }
      \l_tmpa_tl
   \seq_gput_left:NV #1 \l_tmpa_tl
  }

\cs_generate_variant:Nn\@@_struct_exchange_kid_command:N { c }

\cs_new:Nn \@@_struct_fill_kid_key:n %#1 is the struct num
  {
    \int_case:nnF
      {
        \seq_count:c
          {
            g_@@_struct_kids_\prop_item:cn{ g_@@_struct_#1_prop }{num}_seq
          }
      }
      {
        { 0 }
         { } %no kids, do nothing
        { 1 } % 1 kid, insert
         {
           % in this case we need a special command in
           %luamode to get the array right. See issue #13
           \bool_if:NT\g_@@_mode_lua_bool
             {
               \@@_struct_exchange_kid_command:c
                 {g_@@_struct_kids_\prop_item:cn{ g_@@_struct_#1_prop }{num}_seq}
             }
           \@@_prop_gput:cnx { g_@@_struct_#1_prop } {K}
             {
               \seq_item:cn
                 {
                   g_@@_struct_kids_\prop_item:cn{ g_@@_struct_#1_prop }{num}_seq
                 }
                 {1}
             }
         } %
      }
      { %many kids, use an array
        \@@_prop_gput:cnx { g_@@_struct_#1_prop } {K}
          {
            [
              \seq_use:cn
                {
                  g_@@_struct_kids_\prop_item:cn{ g_@@_struct_#1_prop }{num}_seq
                }
                {
                  \c_space_tl
                }
            ]
          }
      }
  }

% this command can be used for roots and structure elements
% #1 is a num

\tl_new:N \l_@@_struct_dict_content_tl

\cs_new:Nn \@@_struct_get_dict_content:n
  {
    %\tl_set:Nn \l_@@_struct_dict_content_tl {<<}
    \tl_clear:N \l_@@_struct_dict_content_tl
    \seq_map_inline:cn
      {
        c_@@_struct_\prop_item:cn{ g_@@_struct_#1_prop }{entries}_entries_seq
      }
      {
        \tl_put_right:Nx
          \l_@@_struct_dict_content_tl
          {
             \prop_if_in:cnT
               { g_@@_struct_#1_prop }
               { ##1 }
               {
                 \c_space_tl/##1~\prop_item:cn{ g_@@_struct_#1_prop } { ##1 }
               }
          }
      }
    %\tl_put_right:Nn \l_@@_struct_dict_content_tl { >> }
  }


% #1 is the struct num
\cs_new:Nn \@@_struct_write_obj:n
  {
    \prop_if_in:cnTF
      { g_@@_struct_#1_prop }
      { objref }
      {
        \@@_struct_fill_kid_key:n { #1 }
        %\prop_show:c { g_@@_struct_#1_prop }
        \@@_struct_get_dict_content:n { #1 }
        \exp_args:Nx
          \pdf_object_write:nx
            { c_@@_struct_#1_obj }
            {
              \l_@@_struct_dict_content_tl
            }
      }
      {
        \msg_error:nnn { tag } { struct-no-objnum } { #1}
      }
  }

% keys for the user commands
% should I pass the values (e.g. the tag) through an escape command?
\keys_define:nn { @@ / struct }
  {
    label .tl_set:N      = \l_@@_struct_key_label_tl,
    stash .bool_set:N    = \l_@@_struct_elem_stash_bool,
    tag   .code:n        = % S property
      {%%????????? \pdfescapename??
        \tl_set:Nx \l_@@_tmpa_tl { #1 }
        \bool_if:NT \g_@@_check_tags_bool
          {
            \@@_check_structure_tag:N \l_@@_tmpa_tl
          }
       \@@_prop_gput:cnx
         { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
         { S }
         { /\exp_not:V\l_@@_tmpa_tl }
      },
    title .code:n        = % T property
      {
        \str_set_convert:Nnon
          \l_@@_tmpa_str
          { #1 }
          { default }
          { utf16/hex }
        \@@_prop_gput:cnx
          { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
          { T }
          { <\l_@@_tmpa_str> }
      },
    title-o .code:n        = % T property
      {
        \str_set_convert:Nnon
          \l_@@_tmpa_str
          { #1 }
          { default }
          { utf16/hex }
        \@@_prop_gput:cnx
          { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
          { T }
          { <\l_@@_tmpa_str> }
      },
    alttext .code:n      = % Alt property
      {
        \str_set_convert:Nnon
          \l_@@_tmpa_str
          { #1 }
          { default }
          { utf16/hex }
        \@@_prop_gput:cnx
          { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
          { Alt }
          { <\l_@@_tmpa_str> }
      },
    alttext-o .code:n      = % Alt property
      {
        \str_set_convert:Noon
          \l_@@_tmpa_str
          { #1 }
          { default }
          { utf16/hex }
        \@@_prop_gput:cnx
          { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
          { Alt }
          { <\l_@@_tmpa_str> }
      },
    actualtext .code:n  = % ActualText property
      {
        \str_set_convert:Nnon
          \l_@@_tmpa_str
          { #1 }
          { default }
          { utf16/hex }
        \@@_prop_gput:cnx
          { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
          { ActualText }
          { <\l_@@_tmpa_str>}
      },
    actualtext-o .code:n  = % ActualText property
      {
        \str_set_convert:Noon
          \l_@@_tmpa_str
          { #1 }
          { default }
          { utf16/hex }
        \@@_prop_gput:cnx
          { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
          { ActualText }
          { <\l_@@_tmpa_str>}
      },
    lang .code:n        = % Lang property
      {
        \@@_prop_gput:cnx
          { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
          { Lang }
          { (#1) }
      },
    ref .code:n        = % Lang property
      {
        \tl_clear:N\l_@@_tmpa_tl
        \clist_map_inline:nn {#1}
          {
            \tl_put_right:Nx \l_@@_tmpa_tl
              {~\ref_value:nn{tagpdfstruct-##1}{tagstructobj} }
          }
        \@@_prop_gput:cnx
          { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
          { Ref }
          { [\l_@@_tmpa_tl] }
      },
    E .code:n        = % E property
      {
        \str_set_convert:Nnon
          \l_@@_tmpa_str
          { #1 }
          { default }
          { utf16/hex }
        \@@_prop_gput:cnx
          { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
          { E }
          { <\l_@@_tmpa_str> }
      },
  }

% keys for AF keys, they rely on l3pdffile!
% The stream variants use txt as extension to get the mimetype.
\keys_define:nn { @@ / struct }
 {
    AF .code:n        = % T property
      {
        \pdf_object_if_exist:nTF {#1}
          {
            \@@_prop_gput:cnx
             { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
             { AF }
             { \pdf_object_ref:n {#1} }
          }
          {

          }
      },
   ,AFinline .code:n =
     {
       \group_begin:
       \exp_args:Ne
       \pdf_object_if_exist:nF {@@/fileobj\int_use:N\c@g_@@_struct_abs_int}
        {
          \pdffile_embed_stream:nxx
            {#1}
            {tag-AFfile\int_use:N\c@g_@@_struct_abs_int.txt}
            {@@/fileobj\int_use:N\c@g_@@_struct_abs_int}
        }
       \@@_prop_gput:cnx
         { g_@@_struct_\int_use:N\c@g_@@_struct_abs_int _prop }
         { AF }
         { \pdf_object_ref:e {@@/fileobj\int_use:N\c@g_@@_struct_abs_int } }
       \group_end:
     }
   ,AFinline-o .code:n =
     {
       \group_begin:
       \exp_args:Ne
       \pdf_object_if_exist:nF {@@/fileobj\int_use:N\c@g_@@_struct_abs_int}
        {
          \pdffile_embed_stream:oxx
            {#1}
            {tag-AFfile\int_use:N\c@g_@@_struct_abs_int.txt}
            {@@/fileobj\int_use:N\c@g_@@_struct_abs_int}
        }
       \@@_prop_gput:cnx
         { g_@@_struct_\int_use:N\c@g_@@_struct_abs_int _prop }
         { AF }
         { \pdf_object_ref:e {@@/fileobj\int_use:N\c@g_@@_struct_abs_int } }
       \group_end:
     }
 }

\cs_new_protected:Nn \tag_struct_begin:n
  {
    \group_begin:
    \int_gincr:N \c@g_@@_struct_abs_int
    \@@_prop_new:c  { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
    \@@_new_output_prop_handler:n {\int_eval:n { \c@g_@@_struct_abs_int }}
    \@@_seq_new:c  { g_@@_struct_kids_\int_eval:n { \c@g_@@_struct_abs_int }_seq}
    %\@@_pdfreserveobjnum:N \l_tmpa_tl
    \exp_args:Ne
      \pdf_object_new:nn
        { c_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_obj }
        { dict }
    \@@_prop_gput:cnx
      { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
      { objref}
      {
        \exp_args:Ne
          \pdf_object_ref:n
            {c_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_obj}
      }
%    \prop_show:c { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
    \@@_prop_gput:cnx
      { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
      { num}
      { \int_eval:n { \c@g_@@_struct_abs_int } }
    \@@_prop_gput:cno
      { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
      { Type }
      { /StructElem }
    \@@_prop_gput:cno
      { g_@@_struct_\int_eval:n { \c@g_@@_struct_abs_int }_prop }
      { entries }
      { StructElem }
    \keys_set:nn { @@ / struct} { #1 }
    \@@_check_structure_has_tag:n { \int_eval:n {\c@g_@@_struct_abs_int} }
    \tl_if_empty:NF
      \l_@@_struct_key_label_tl
      {
        \@@_ref_label:en{tagpdfstruct-\l_@@_struct_key_label_tl}{struct}
      }
    %get the potential parent from the stack:
    \seq_get:NNF
      \g_@@_struct_stack_seq
      \l_@@_struct_stack_parent_tmp_tl
      {
        \msg_error:nn { tag } { struct-faulty-nesting }
      }
    \seq_gpush:NV \g_@@_struct_stack_seq        \c@g_@@_struct_abs_int
    \tl_gset:NV   \g_@@_struct_stack_current_tl \c@g_@@_struct_abs_int
    %\seq_show:N   \g_@@_struct_stack_seq
    \bool_if:NF
      \l_@@_struct_elem_stash_bool
      {%set the  parent
        \@@_prop_gput:cnx
          { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
          { P }
          {
            \prop_item:cn
              { g_@@_struct_\l_@@_struct_stack_parent_tmp_tl _prop}
              { objref }
          }
        %record this structure as kid:
        %\tl_show:N \g_@@_struct_stack_current_tl
        %\tl_show:N \l_@@_struct_stack_parent_tmp_tl
        \@@_struct_kid_struct_gput_right:nn
          { \l_@@_struct_stack_parent_tmp_tl }
          { \g_@@_struct_stack_current_tl }
        %\prop_show:c { g_@@_struct_\g_@@_struct_stack_current_tl _prop }
        %\seq_show:c {g_@@_struct_kids_\l_@@_struct_stack_parent_tmp_tl _seq}
      }
    %\prop_show:c { g_@@_struct_\g_@@_struct_stack_current_tl _prop }
    %\seq_show:c {g_@@_struct_kids_\l_@@_struct_stack_parent_tmp_tl _seq}
    \group_end:
  }

%deprecated
\cs_set_eq:NN  \uftag_struct_begin:n \tag_struct_begin:n

\cs_new_protected:Nn \tag_struct_end:
  { %take the current structure num from the stack:
    %the objects are written later, lua mode hasn't all needed info yet
    %\seq_show:N \g_@@_struct_stack_seq
    \seq_gpop:NNTF \g_@@_struct_stack_seq \l_tmpa_tl
      {
        \int_compare:nNnT {\l_@@_loglevel_int} > { 0 }
          {
            \@@_check_info_closing_struct:o { \g_@@_struct_stack_current_tl }
          }
      }
      { \@@_check_no_open_struck: }
    % get the previous one, shouldn't be empty as the root should be there
    \seq_get:NNTF \g_@@_struct_stack_seq \l_tmpa_tl
      {
        \tl_gset:NV   \g_@@_struct_stack_current_tl \l_tmpa_tl
      }
      {
        \@@_check_no_open_struck:
      }
  }

%deprecated
\cs_set_eq:NN  \uftag_struct_end: \tag_struct_end:

\cs_new_protected:Nn \tag_struct_use:n %#1 is the label
  {
    \prop_if_exist:cTF
      { g_@@_struct_\@@_ref_value:enn{tagpdfstruct-#1}{tagstruct}{unknown}_prop } %??????????
      {
        \@@_check_struct_used:n {#1}
        %add the label structure as kid to the current structure (can be the root)
        \@@_struct_kid_struct_gput_right:nn
          { \g_@@_struct_stack_current_tl }
          { \@@_ref_value:enn{tagpdfstruct-#1}{tagstruct}{0} }
        %add the current structure to the labeled one as parents
        \@@_prop_gput:cnx
          { g_@@_struct_\@@_ref_value:enn{tagpdfstruct-#1}{tagstruct}{0}_prop }
          { P }
          {
            \prop_item:cn
              { g_@@_struct_\g_@@_struct_stack_current_tl _prop}
              { objref }
          }
      }
      {
        \msg_warning:nnn{ tag }{struct-label-unknown}{#1}
      }
  }

\cs_set_eq:NN  \uftag_struct_use:n \tag_struct_use:n


%%%% Code to tag links
%%%% this works for url, see exp-link.pdf.
%%%% it must be checked for other links

\cs_new_protected:Nn \@@_struct_finish_link:
  {
    \bool_if:NT \g_@@_active_struct_bool
      {
        %get the number of the parent link structure:
        \seq_get:NNF
          \g_@@_struct_stack_seq
          \l_@@_struct_stack_parent_tmp_tl
          {
            \msg_error:nn { tag } { struct-faulty-nesting }
          }
        %put the obj number of link annot in the kid entry:
        \@@_struct_kid_link_gput_right:nx
          {
            \l_@@_struct_stack_parent_tmp_tl
          }
          {
            \pdfannot_link_ref_last:
          }
        % add the parent obj number to the parent tree:
        \@@_parenttree_add_objr:nn
          {
            \int_use:N\c@g_@@_parenttree_obj_int
          }
          {
            \prop_item:cn
              { g_@@_struct_\l_@@_struct_stack_parent_tmp_tl _prop }
              { objref }
          }
        % increase the int:
        \stepcounter{ g_@@_parenttree_obj_int }
      }
  }

%</struct>

%    \end{macrocode}
%    \begin{macrocode}
%<*attr>
\ProvidesExplPackage {tagpdf-attr-code} {2021/02/23} {0.80}
  {part of tagpdf - code related to attributes and attribute classes}

% the obj is written in tagpdf-tree-code.

\seq_new:N  \g_@@_attr_class_used_seq
\prop_new:N \g_@@_attr_objref_prop %will contain obj num of used attributes

\prop_new:N \g_@@_attr_entries_prop
\tl_new:N   \g_@@_attr_class_content_tl
\tl_new:N   \l_@@_attr_objtmp_tl
\tl_new:N   \l_@@_attr_value_tl


\cs_new_protected:Nn \@@_attr_new_entry:nn %#1:name, #2: content
  {
    \prop_gput:Nnn \g_@@_attr_entries_prop
      {#1}{#2}
  }

\keys_define:nn { @@ / setup }
  {
    newattribute .code:n =
      {
        \@@_attr_new_entry:nn #1
      }
  }


% the key for the structure:
\keys_define:nn { @@ / struct }
  {
    attribute-class .code:n =
     {
       \clist_set:No \l_tmpa_clist { #1 }
       \seq_set_from_clist:NN \l_tmpa_seq \l_tmpa_clist
       \seq_map_inline:Nn \l_tmpa_seq
         {
           \prop_if_in:NnF \g_@@_attr_entries_prop {##1}
             {
               \msg_error:nnn { tag } { attr-unknown } { ##1 }
             }
           \seq_gput_left:Nn\g_@@_attr_class_used_seq { ##1}
         }
       \seq_set_map:NNn \l_tmpb_seq \l_tmpa_seq
         {
           /##1
         }
       \tl_set:Nx \l_tmpa_tl
         {
           \int_compare:nT { \seq_count:N \l_tmpa_seq > 1 }{[}
           \seq_use:Nn \l_tmpb_seq  { \c_space_tl  }
           \int_compare:nT { \seq_count:N \l_tmpa_seq > 1 }{]}
         }
       \int_compare:nT { \seq_count:N \l_tmpa_seq > 0 }
         {
           \@@_prop_gput:cnx
             { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
             { C }
             { \l_tmpa_tl }
          %\prop_show:c  { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
         }
    }
  }

\keys_define:nn { @@ / struct }
  {
    attribute .code:n  = % A property (attribute, value currently a dictionary)
      {
        \clist_set:No          \l_tmpa_clist { #1 }
        \seq_set_from_clist:NN \l_tmpa_seq \l_tmpa_clist
        \tl_set:Nx \l_@@_attr_value_tl
          {
            \int_compare:nT { \seq_count:N \l_tmpa_seq > 1 }{[}%]
          }
        \seq_map_inline:Nn \l_tmpa_seq
          {
            \prop_if_in:NnF \g_@@_attr_entries_prop {##1}
              {
                \msg_error:nnn { tag } { attr-unknown } { ##1 }
              }
            \prop_if_in:NnF \g_@@_attr_objref_prop {##1}
              {%\prop_show:N \g_@@_attr_entries_prop
                \pdf_object_unnamed_write:nx
                  { dict }
                  {
                    \prop_item:Nn\g_@@_attr_entries_prop {##1}
                  }
                \prop_gput:Nnx \g_@@_attr_objref_prop {##1} {\pdf_object_ref_last:}
              }
            \tl_put_right:Nx \l_@@_attr_value_tl
              {
                \c_space_tl
                \prop_item:Nn \g_@@_attr_objref_prop {##1}
              }
 %     \tl_show:N \l_@@_attr_value_tl
          }
        \tl_put_right:Nx \l_@@_attr_value_tl
          { %[
            \int_compare:nT { \seq_count:N \l_tmpa_seq > 1 }{]}%
          }
 %     \tl_show:N \l_@@_attr_value_tl
        \@@_prop_gput:cnx
          { g_@@_struct_\int_eval:n {\c@g_@@_struct_abs_int}_prop }
          { A }
          { \l_@@_attr_value_tl }
    },
  }
%</attr>
%    \end{macrocode}
