public final void substitute(@NotNull Element element, boolean caseSensitive, boolean recursively, @Nullable PathMacroFilter filter) {
    if (filter != null && filter.skipPathMacros(element)) {
      return;
    }

    for (Content child : element.getContent()) {
      if (child instanceof Element) {
        substitute((Element)child, caseSensitive, recursively, filter);
      }
      else if (child instanceof Text) {
        Text t = (Text)child;
        String oldText = t.getText();
        String newText = recursively ? substituteRecursively(oldText, caseSensitive).toString() : substitute(oldText, caseSensitive);
        if (!Strings.areSameInstance(oldText, newText)) {
          // it is faster to call 'setText' right away than perform additional 'equals' check
          t.setText(newText);
        }
      }
      else {
        LOG.error("Wrong content: " + child.getClass());
      }
    }

    if (!element.hasAttributes()) {
      return;
    }

    for (Attribute attribute : element.getAttributes()) {
      if (filter == null || !filter.skipPathMacros(attribute)) {
        String newValue = getAttributeValue(attribute, filter, caseSensitive, recursively);
        if (!Strings.areSameInstance(attribute.getValue(), newValue)) {
          // it is faster to call 'setValue' right away than perform additional 'equals' check
          attribute.setValue(newValue);
        }
      }
    }
  }
public final void substitute(@NotNull Element element, boolean caseSensitive, boolean recursively, @Nullable PathMacroFilter filter)