applyInheritance function

Future<Document?> applyInheritance(
  1. Document? document,
  2. Directory currentDirectory,
  3. void onError(
    1. JaelError error
    )?,
  4. Iterable<Patcher>? patch,
)

Folds any extend declarations.

Implementation

Future<Document?> applyInheritance(
    Document? document,
    Directory currentDirectory,
    void Function(JaelError error)? onError,
    Iterable<Patcher>? patch) async {
  if (document == null) {
    return null;
  }
  if (document.root.tagName.name != 'extend') {
    // This is not an inherited template, so just fill in the existing blocks.
    var root =
        replaceChildrenOfElement(document.root, {}, onError, true, false);
    return Document(document.doctype, root);
  }

  var element = document.root;
  var attr = element.attributes.firstWhereOrNull((a) => a.name == 'src');
  if (attr == null) {
    onError!(JaelError(JaelErrorSeverity.warning,
        'Missing "src" attribute in "extend" tag.', element.tagName.span));
    return null;
  } else if (attr.value is! StringLiteral) {
    onError!(JaelError(
        JaelErrorSeverity.warning,
        'The "src" attribute in an "extend" tag must be a string literal.',
        element.tagName.span));
    return null;
  } else {
    // In theory, there exists:
    // * A single root template with a number of blocks
    // * Some amount of <extend src="..."> templates.

    // To produce an accurate representation, we need to:
    // 1. Find the root template, and store a copy in a variable.
    // 2: For each <extend> template:
    //  a. Enumerate the block overrides it defines
    //  b. Replace matching blocks in the current document
    //  c. If there is no block, and this is the LAST <extend>, fill in the default block content.
    var hierarchy = await resolveHierarchy(document, currentDirectory, onError);
    var out = hierarchy?.root;

    if (out is! RegularElement) {
      return hierarchy!.rootDocument;
    }

    Element setOut(Element out, Map<String?, RegularElement> definedOverrides,
        bool anyTemplatesRemain) {
      var children = <ElementChild>[];

      // Replace matching blocks, etc.
      for (var c in out.children) {
        if (c is Element) {
          children.addAll(replaceBlocks(
              c, definedOverrides, onError, false, anyTemplatesRemain));
        } else {
          children.add(c);
        }
      }

      var root = hierarchy!.root as RegularElement;
      return RegularElement(root.lt, root.tagName, root.attributes, root.gt,
          children, root.lt2, root.slash, root.tagName2, root.gt2);
    }

    // Loop through all extends, filling in blocks.
    while (hierarchy!.extendsTemplates.isNotEmpty) {
      var tmpl = hierarchy.extendsTemplates.removeFirst();
      var definedOverrides = findBlockOverrides(tmpl, onError);
      //if (definedOverrides == null) break;
      out =
          setOut(out!, definedOverrides, hierarchy.extendsTemplates.isNotEmpty);
    }

    // Lastly, just default-fill any remaining blocks.
    var definedOverrides = findBlockOverrides(out!, onError);
    out = setOut(out, definedOverrides, false);

    // Return our processed document.
    return Document(document.doctype, out);
  }
}