リビジョン | 81 (tree) |
---|---|
日時 | 2019-01-24 19:50:36 |
作者 | ![]() |
* flexmark-ext-highlight 0.1.2 タイトル指定を追加しました。クリップボードコピー機能を追加しました。HTML属性追加機能を追加しました。flexmarkのバージョンを 0.40.12 に変更しました。
@@ -2,6 +2,8 @@ | ||
2 | 2 | |
3 | 3 | import java.util.HashSet; |
4 | 4 | import java.util.Set; |
5 | +import java.util.regex.Matcher; | |
6 | +import java.util.regex.Pattern; | |
5 | 7 | |
6 | 8 | import com.codewaves.codehighlight.core.Highlighter; |
7 | 9 | import com.codewaves.codehighlight.core.Highlighter.HighlightResult; |
@@ -20,7 +22,23 @@ | ||
20 | 22 | import com.vladsch.flexmark.util.sequence.BasedSequence; |
21 | 23 | |
22 | 24 | public class HighlightRenderer implements NodeRenderer { |
23 | - public static final AttributablePart CODE_CONTENT = new AttributablePart("FENCED_CODE_CONTENT"); | |
25 | + private static final AttributablePart CODE_CONTENT = new AttributablePart("FENCED_CODE_CONTENT"); | |
26 | + private static final String ADDITIONAL_CHARS = ""; | |
27 | + private static final String ATTRIBUTENAME = "[a-zA-Z" + ADDITIONAL_CHARS + "_:][a-zA-Z0-9" + ADDITIONAL_CHARS + ":._-]*"; | |
28 | + private static final String UNQUOTEDVALUE = "[^\"'=<>{}`\u0000-\u0020]+"; | |
29 | + private static final String SINGLEQUOTEDVALUE = "'[^']*'"; | |
30 | + private static final String DOUBLEQUOTEDVALUE = "\"[^\"]*\""; | |
31 | + private static final String ATTRIBUTEVALUE = "(?:" + UNQUOTEDVALUE + "|" + SINGLEQUOTEDVALUE + "|" + DOUBLEQUOTEDVALUE + ")"; | |
32 | + private static final Pattern ATTRIBUTES_TAG = Pattern.compile( | |
33 | + "\\{((?:[#.])|(?:" + "\\s*([#.]" + UNQUOTEDVALUE + "|" + ATTRIBUTENAME + ")\\s*(?:=\\s*(" + ATTRIBUTEVALUE + ")?" + ")?" + ")" + | |
34 | + "(?:" + "\\s+([#.]" + UNQUOTEDVALUE + "|" + ATTRIBUTENAME + ")\\s*(?:=\\s*(" + ATTRIBUTEVALUE + ")?" + ")?" + ")*" + "\\s*)\\}$"); | |
35 | + private static final Pattern ATTRIBUTE = Pattern.compile("\\s*([#.]" + UNQUOTEDVALUE + "|" + ATTRIBUTENAME + ")\\s*(?:=\\s*(" + ATTRIBUTEVALUE + ")?" + ")?"); | |
36 | + | |
37 | + private static final String COPY_SCRIPT = | |
38 | + "document.getSelection().selectAllChildren(event.target.parentNode.nextSibling);" + | |
39 | + "document.execCommand('copy');" + | |
40 | + "document.getSelection().removeAllRanges();" + | |
41 | + "return false;"; | |
24 | 42 | |
25 | 43 | private final boolean codeContentBlock; |
26 | 44 | private final Highlighter highlighter = new Highlighter(new StyleRendererFactory() { |
@@ -59,15 +77,72 @@ | ||
59 | 77 | */ |
60 | 78 | |
61 | 79 | String languageName = null; |
80 | + BasedSequence language = BasedSequence.NULL; | |
81 | + BasedSequence title = BasedSequence.NULL; | |
82 | + boolean isCopyable = false; | |
62 | 83 | |
63 | 84 | html.line(); |
64 | - html.srcPosWithTrailingEOL(node.getChars()).withAttr().tag("pre").openPre(); | |
65 | 85 | |
66 | 86 | BasedSequence info = node.getInfo(); |
67 | 87 | if (info.isNotNull() && !info.isBlank()) { |
68 | - BasedSequence language = node.getInfoDelimitedByAny(" "); | |
88 | + Matcher matcher = ATTRIBUTES_TAG.matcher(info); | |
89 | + if(matcher.find()) { | |
90 | + BasedSequence attributesText = info.subSequence(matcher.start(1), matcher.end(1)).trim(); | |
91 | + if(!attributesText.isEmpty()) { | |
92 | + Matcher attributeMatcher = ATTRIBUTE.matcher(attributesText); | |
93 | + while (attributeMatcher.find()) { | |
94 | + BasedSequence attributeName = attributesText.subSequence(attributeMatcher.start(1), attributeMatcher.end(1)); | |
95 | + if(attributeName.isNotNull() && attributeName.length() > 0) { | |
96 | + if(attributeName.charAt(0) == '.') { | |
97 | + BasedSequence cls = attributeName.subSequence(1); | |
98 | + html.attr("class", cls); | |
99 | + if(cls.startsWith("cop")) { | |
100 | + isCopyable = true; | |
101 | + } | |
102 | + } else if(attributeName.charAt(0) == '#') { | |
103 | + html.attr("id", attributeName.subSequence(1)); | |
104 | + } else { | |
105 | + BasedSequence attributeValue = attributeMatcher.groupCount() == 1 || attributeMatcher.start(2) == -1 ? BasedSequence.NULL : attributesText.subSequence(attributeMatcher.start(2), attributeMatcher.end(2)); | |
106 | + boolean isQuoted = attributeValue.length() >= 2 && (attributeValue.charAt(0) == '"' && attributeValue.endCharAt(1) == '"' || attributeValue.charAt(0) == '\'' && attributeValue.endCharAt(1) == '\''); | |
107 | + if(isQuoted) { | |
108 | + attributeValue = attributeValue.midSequence(1, -1); | |
109 | + } | |
110 | + if(attributeValue.isNotNull()) { | |
111 | + html.attr(attributeName, attributeValue); | |
112 | + } | |
113 | + } | |
114 | + } | |
115 | + } | |
116 | + } | |
117 | + info = info.subSequence(0, matcher.start()).trim(); | |
118 | + } | |
119 | + | |
120 | + if (info.isNotNull() && !info.isBlank()) { | |
121 | + int space = info.indexOfAny(" "); | |
122 | + if (space == -1) { | |
123 | + language = info; | |
124 | + } else { | |
125 | + language = info.subSequence(0, space); | |
126 | + title = info.subSequence(space).trim(); | |
127 | + } | |
128 | + languageName = language.unescape(); | |
129 | + } | |
130 | + } | |
131 | + html.srcPosWithTrailingEOL(node.getChars()).withAttr().tag("pre").openPre(); | |
132 | + | |
133 | + if(title.isNotNull() || isCopyable) { | |
134 | + html.attr("class", "title").withAttr().tag("div"); | |
135 | + if(title.isNotNull()) { | |
136 | + html.append(title); | |
137 | + } | |
138 | + if(isCopyable) { | |
139 | + html.attr("class", "copy-button").attr("onclick", COPY_SCRIPT).withAttr().tag("span").tag("/span"); | |
140 | + } | |
141 | + html.tag("/div"); | |
142 | + } | |
143 | + | |
144 | + if(language.isNotNull()) { | |
69 | 145 | html.attr("class", context.getHtmlOptions().languageClassPrefix + language.unescape()); |
70 | - languageName = language.unescape(); | |
71 | 146 | } else { |
72 | 147 | String noLanguageClass = context.getHtmlOptions().noLanguageClass.trim(); |
73 | 148 | if (!noLanguageClass.isEmpty()) { |
@@ -74,7 +149,6 @@ | ||
74 | 149 | html.attr("class", noLanguageClass); |
75 | 150 | } |
76 | 151 | } |
77 | - | |
78 | 152 | html.srcPosWithEOL(node.getContentChars()).withAttr(CODE_CONTENT).tag("code"); |
79 | 153 | if (codeContentBlock) { |
80 | 154 | context.renderChildren(node); |