docs: private void frame(FramingContext state, Map<String, Object> nodes, Map<String, Object> frame,
              Object parent, String property) throws JsonLdError {
  
          // https://json-ld.org/spec/latest/json-ld-framing/#framing-algorithm
  
          // 2.
          // Initialize flags embed, explicit, and requireAll from object embed
          // flag,
          // explicit inclusion flag, and require all flag in state overriding
          // from
          // any property values for @embed, @explicit, and @requireAll in frame.
          // TODO: handle @requireAll
          final Embed embed = getFrameEmbed(frame, state.embed);
          final Boolean explicitOn = getFrameFlag(frame, JsonLdConsts.EXPLICIT, state.explicit);
          final Boolean requireAll = getFrameFlag(frame, JsonLdConsts.REQUIRE_ALL, state.requireAll);
          final Map<String, Object> flags = newMap();
          flags.put(JsonLdConsts.EXPLICIT, explicitOn);
          flags.put(JsonLdConsts.EMBED, embed);
          flags.put(JsonLdConsts.REQUIRE_ALL, requireAll);
  
          // 3.
          // Create a list of matched subjects by filtering subjects against frame
          // using the Frame Matching algorithm with state, subjects, frame, and
          // requireAll.
          final Map<String, Object> matches = filterNodes(state, nodes, frame, requireAll);
          final List<String> ids = new ArrayList<String>(matches.keySet());
          Collections.sort(ids);
  
          // 4.
          // Set link the the value of link in state associated with graph name in
          // state,
          // creating a new empty dictionary, if necessary.
          final Map<String, EmbedNode> link = state.uniqueEmbeds;
  
          // 5.
          // For each id and associated node object node from the set of matched
          // subjects, ordered by id:
          for (final String id : ids) {
              final Map<String, Object> subject = (Map<String, Object>) matches.get(id);
  
              // 5.1
              // Initialize output to a new dictionary with @id and id and add
              // output to link associated with id.
              final Map<String, Object> output = newMap();
              output.put(JsonLdConsts.ID, id);
  
              // 5.2
              // If embed is @link and id is in link, node already exists in
              // results.
              // Add the associated node object from link to parent and do not
              // perform
              // additional processing for this node.
              if (embed == Embed.LINK && state.uniqueEmbeds.containsKey(id)) {
                  addFrameOutput(state, parent, property, state.uniqueEmbeds.get(id));
                  continue;
              }
  
              // Occurs only at top level, compartmentalize each top-level match
              if (property == null) {
                  state.uniqueEmbeds = new HashMap<>();
              }
  
              // 5.3
              // Otherwise, if embed is @never or if a circular reference would be
              // created by an embed,
              // add output to parent and do not perform additional processing for
              // this node.
              if (embed == Embed.NEVER || createsCircularReference(id, state)) {
                  addFrameOutput(state, parent, property, output);
                  continue;
              }
  
              // 5.4
              // Otherwise, if embed is @last, remove any existing embedded node
              // from parent associated
              // with graph name in state. Requires sorting of subjects.
              if (embed == Embed.LAST) {
                  if (state.uniqueEmbeds.containsKey(id)) {
                      removeEmbed(state, id);
                  }
                  state.uniqueEmbeds.put(id, new EmbedNode(parent, property));
              }
  
              state.subjectStack.push(id);
  
              // 5.5 If embed is @last or @always
  
              // Skip 5.5.1
  
              // 5.5.2 For each property and objects in node, ordered by property:
              final Map<String, Object> element = (Map<String, Object>) matches.get(id);
              List<String> props = new ArrayList<String>(element.keySet());
              Collections.sort(props);
              for (final String prop : props) {
  
                  // 5.5.2.1 If property is a keyword, add property and objects to
                  // output.
                  if (isKeyword(prop)) {
                      output.put(prop, JsonLdUtils.clone(element.get(prop)));
                      continue;
                  }
  
                  // 5.5.2.2 Otherwise, if property is not in frame, and explicit
                  // is true, processors
                  // MUST NOT add any values for property to output, and the
                  // following steps are skipped.
                  if (explicitOn && !frame.containsKey(prop)) {
                      continue;
                  }
  
                  // add objects
                  final List<Object> value = (List<Object>) element.get(prop);
  
                  // 5.5.2.3 For each item in objects:
                  for (final Object item : value) {
                      if ((item instanceof Map)
                              && ((Map<String, Object>) item).containsKey(JsonLdConsts.LIST)) {
                          // add empty list
                          final Map<String, Object> list = newMap();
                          list.put(JsonLdConsts.LIST, new ArrayList<Object>());
                          addFrameOutput(state, output, prop, list);
  
                          // add list objects
                          for (final Object listitem : (List<Object>) ((Map<String, Object>) item)
                                  .get(JsonLdConsts.LIST)) {
                              // 5.5.2.3.1.1 recurse into subject reference
                              if (JsonLdUtils.isNodeReference(listitem)) {
                                  final Map<String, Object> tmp = newMap();
                                  final String itemid = (String) ((Map<String, Object>) listitem)
                                          .get(JsonLdConsts.ID);
                                  // TODO: nodes may need to be node_map,
                                  // which is global
                                  tmp.put(itemid, this.nodeMap.get(itemid));
                                  Map<String, Object> subframe;
                                  if (frame.containsKey(prop)) {
                                      subframe = (Map<String, Object>) ((List<Object>) frame
                                              .get(prop)).get(0);
                                  } else {
                                      subframe = flags;
                                  }
                                  frame(state, tmp, subframe, list, JsonLdConsts.LIST);
                              } else {
  
                                  // include other values automatcially (TODO:
                                  // may need JsonLdUtils.clone(n))
                                  addFrameOutput(state, list, JsonLdConsts.LIST, listitem);
                              }
                          }
                      }
                      // recurse into subject reference
                      else if (JsonLdUtils.isNodeReference(item)) {
                          final Map<String, Object> tmp = newMap();
                          final String itemid = (String) ((Map<String, Object>) item)
                                  .get(JsonLdConsts.ID);
                          // TODO: nodes may need to be node_map, which is
                          // global
                          tmp.put(itemid, this.nodeMap.get(itemid));
                          Map<String, Object> subframe;
                          if (frame.containsKey(prop)) {
                              subframe = (Map<String, Object>) ((List<Object>) frame.get(prop))
                                      .get(0);
                          } else {
                              subframe = flags;
                          }
                          frame(state, tmp, subframe, output, prop);
                      } else {
                          // include other values automatically (TODO: may
                          // need JsonLdUtils.clone(o))
                          addFrameOutput(state, output, prop, item);
                      }
                  }
              }
  
              // handle defaults
              props = new ArrayList<String>(frame.keySet());
              Collections.sort(props);
              for (final String prop : props) {
                  // skip keywords
                  if (isKeyword(prop)) {
                      continue;
                  }
  
                  final List<Object> pf = (List<Object>) frame.get(prop);
                  Map<String, Object> propertyFrame = pf.size() > 0 ? (Map<String, Object>) pf.get(0)
                          : null;
                  if (propertyFrame == null) {
                      propertyFrame = newMap();
                  }
                  final boolean omitDefaultOn = getFrameFlag(propertyFrame, JsonLdConsts.OMIT_DEFAULT,
                          state.omitDefault);
                  if (!omitDefaultOn && !output.containsKey(prop)) {
                      Object def = "@null";
                      if (propertyFrame.containsKey(JsonLdConsts.DEFAULT)) {
                          def = JsonLdUtils.clone(propertyFrame.get(JsonLdConsts.DEFAULT));
                      }
                      if (!(def instanceof List)) {
                          final List<Object> tmp = new ArrayList<Object>();
                          tmp.add(def);
                          def = tmp;
                      }
                      final Map<String, Object> tmp1 = newMap(JsonLdConsts.PRESERVE, def);
                      final List<Object> tmp2 = new ArrayList<Object>();
                      tmp2.add(tmp1);
                      output.put(prop, tmp2);
                  }
              }
  
              // add output to parent
              addFrameOutput(state, parent, property, output);
  
              state.subjectStack.pop();
          }
      }