String (List<? extends TagInfo> tags, String text, Function<TagInfo, String> computeExtraAttributes) { StringBuilder builder = new StringBuilder(text); // Need to sort tags for inserting them in reverse order to have valid offsets for yet not inserted tags. // Can't just sort in reverse order but do sort plus reverse instead to preserve final order for tags with the same offset. List<? extends TagInfo> sortedTagPoints = Lists.reverse(tags.stream().sorted().collect(Collectors.toList())); // Insert tags into text starting from the end for preventing invalidating previous tags offsets for (TagInfo point : sortedTagPoints) { String tagText; if (point.isStart) { String attributesString = point.getAttributesString(); String extraAttributes = computeExtraAttributes.apply(point); String allAttributes; if (extraAttributes != null) { allAttributes = attributesString + " " + extraAttributes; } else { allAttributes = attributesString; } String closeSuffix = point.isClosed ? "/" : ""; if (attributesString.isEmpty()) { tagText = String.format("<%s%s>", point.getName(), closeSuffix); } else { tagText = String.format("<%s %s%s>", point.getName(), allAttributes, closeSuffix); } } else { tagText = String.format("</%s>", point.getName()); } builder.insert(point.offset, tagText); } return builder.toString(); }