package org.yinwang.pysonar.demos; import org.jetbrains.annotations.NotNull; import org.yinwang.pysonar.Util; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; /** * Turns a list of {@link StyleRun}s into HTML spans. */ class StyleApplier { // Empirically, adding the span tags multiplies length by 6 or more. private static final int SOURCE_BUF_MULTIPLIER = 6; @NotNull private SortedSet<Tag> tags = new TreeSet<Tag>(); private StringBuilder buffer; // html output buffer private String source; // input source code // Current offset into the source being copied into the html buffer. private int sourceOffset = 0; abstract class Tag implements Comparable<Tag>{ int offset; StyleRun style; @Override public int compareTo(@NotNull Tag other) { if (this == other) { return 0; } if (this.offset < other.offset) { return -1; } if (other.offset < this.offset) { return 1; } return this.hashCode() - other.hashCode(); } void insert() { // Copy source code up through this tag. if (offset > sourceOffset) { copySource(sourceOffset, offset); } } } class StartTag extends Tag { public StartTag(@NotNull StyleRun style) { offset = style.start(); this.style = style; } @Override void insert() { super.insert(); switch (style.type) { case ANCHOR: buffer.append("<a name='" + style.url + "'"); buffer.append(", id ='" + style.id + "'"); if (style.highlight != null && !style.highlight.isEmpty()) { String ids = Util.joinWithSep(style.highlight, "\",\"", "\"", "\""); buffer.append(", onmouseover='highlight(").append(ids).append(")'"); } break; case LINK: buffer.append("<a href='" + style.url + "'"); buffer.append(", id ='" + style.id + "'"); if (style.highlight != null && !style.highlight.isEmpty()) { String ids = Util.joinWithSep(style.highlight, "\",\"", "\"", "\""); buffer.append(", onmouseover='highlight(").append(ids).append(")'"); } break; default: buffer.append("<span class='"); buffer.append(toCSS(style)).append("'"); break; } if (style.message != null) { buffer.append(", title='"); buffer.append(style.message); buffer.append("'"); } buffer.append(">"); } } class EndTag extends Tag { public EndTag(@NotNull StyleRun style) { offset = style.end(); this.style = style; } @Override void insert() { super.insert(); switch (style.type) { case ANCHOR: case LINK: buffer.append("</a>"); break; default: buffer.append("</span>"); break; } } } public StyleApplier(String path, String src, @NotNull List<StyleRun> runs) { source = src; for (StyleRun run : runs) { tags.add(new StartTag(run)); tags.add(new EndTag(run)); } } /** * @return the html */ @NotNull public String apply() { buffer = new StringBuilder(source.length() * SOURCE_BUF_MULTIPLIER); for (Tag tag : tags) { tag.insert(); } // Copy in remaining source beyond last tag. if (sourceOffset < source.length()) { copySource(sourceOffset, source.length()); } return buffer.toString(); } /** * Copies code from the input source to the output html. * @param begin the starting source offset * @param end the end offset, or -1 to go to end of file */ private void copySource(int begin, int end) { // Be robust if the analyzer gives us bad offsets. try { String src = escape((end == -1) ? source.substring(begin) : source.substring(begin, end)); buffer.append(src); } catch (RuntimeException x) { // This can happen with files with weird encodings // Igore them because of the rareness } sourceOffset = end; } private String escape(@NotNull String s) { return s.replace("&", "&") .replace("'", "'") .replace("\"", """) .replace("<", "<") .replace(">", ">"); } private String toCSS(@NotNull StyleRun style) { return style.type.toString().toLowerCase().replace("_", "-"); } }