blob: 4914297ca20ed48a2260af7a387a79f9f8a20256 [file] [log] [blame]
Shawn Pearce99cdbce2015-02-10 12:05:45 -08001// Copyright 2015 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.gitiles.doc;
16
David Pursehousef39cadc2017-07-07 08:47:51 +090017import static java.util.stream.Collectors.toSet;
18
Shawn Pearcef485a312017-07-01 12:10:27 -070019import com.google.common.base.CharMatcher;
20import com.google.common.base.Splitter;
Shawn Pearce99cdbce2015-02-10 12:05:45 -080021import com.google.gitiles.doc.html.HtmlBuilder;
Soy Authorsbffb5fd2018-02-02 18:38:08 -050022import com.google.template.soy.shared.internal.Sanitizers;
Shawn Pearce99cdbce2015-02-10 12:05:45 -080023import java.util.HashMap;
Shawn Pearce99cdbce2015-02-10 12:05:45 -080024import java.util.Map;
Shawn Pearcef485a312017-07-01 12:10:27 -070025import java.util.Set;
Shawn Pearce12c8fab2016-05-15 16:55:21 -070026import java.util.regex.Matcher;
27import java.util.regex.Pattern;
Dave Borowitz3b744b12016-08-19 16:11:10 -040028import org.commonmark.node.Heading;
29import org.commonmark.node.Node;
30import org.eclipse.jgit.util.RawParseUtils;
Shawn Pearce99cdbce2015-02-10 12:05:45 -080031
32class Navbar {
Shawn Pearcef485a312017-07-01 12:10:27 -070033 private static final Pattern META_LINK =
34 Pattern.compile(
35 "^\\[(logo|home|extensions)\\]:\\s*(.+)$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
Shawn Pearce12c8fab2016-05-15 16:55:21 -070036
Shawn Pearcef485a312017-07-01 12:10:27 -070037 private MarkdownConfig cfg;
Shawn Pearce47fd6562016-05-28 14:15:15 -070038 private MarkdownToHtml fmt;
39 private Node node;
40 private String siteTitle;
41 private String logoUrl;
42 private String homeUrl;
Shawn Pearce99cdbce2015-02-10 12:05:45 -080043
Shawn Pearce47fd6562016-05-28 14:15:15 -070044 Navbar() {}
45
Shawn Pearcef485a312017-07-01 12:10:27 -070046 MarkdownConfig getConfig() {
47 return cfg;
48 }
49
50 Navbar setConfig(MarkdownConfig cfg) {
51 this.cfg = cfg;
52 return this;
53 }
54
Shawn Pearce47fd6562016-05-28 14:15:15 -070055 Navbar setFormatter(MarkdownToHtml html) {
56 this.fmt = html;
57 return this;
58 }
59
Shawn Pearcef485a312017-07-01 12:10:27 -070060 Navbar setMarkdown(byte[] md) {
Shawn Pearce47fd6562016-05-28 14:15:15 -070061 if (md != null && md.length > 0) {
Shawn Pearcef485a312017-07-01 12:10:27 -070062 parse(RawParseUtils.decode(md));
Shawn Pearce47fd6562016-05-28 14:15:15 -070063 }
64 return this;
65 }
66
67 Map<String, Object> toSoyData() {
68 Map<String, Object> data = new HashMap<>();
69 data.put("siteTitle", siteTitle);
70 data.put("logoUrl", logo());
71 data.put("homeUrl", homeUrl != null ? fmt.href(homeUrl) : null);
72 data.put("navbarHtml", node != null ? fmt.toSoyHtml(node) : null);
73 return data;
74 }
75
76 private Object logo() {
77 if (logoUrl == null) {
78 return null;
Shawn Pearce99cdbce2015-02-10 12:05:45 -080079 }
80
Shawn Pearce47fd6562016-05-28 14:15:15 -070081 String url = fmt.image(logoUrl);
82 if (HtmlBuilder.isValidHttpUri(url)) {
83 return url;
84 } else if (HtmlBuilder.isImageDataUri(url)) {
85 return Sanitizers.filterImageDataUri(url);
86 } else {
Dave Borowitz12181942018-02-12 09:22:45 -050087 return SoyConstants.IMAGE_URI_INNOCUOUS_OUTPUT;
Shawn Pearce47fd6562016-05-28 14:15:15 -070088 }
89 }
90
Shawn Pearcef485a312017-07-01 12:10:27 -070091 private void parse(String markdown) {
92 extractMetadata(markdown);
Shawn Pearceb55cf2b2017-06-29 21:56:37 -070093 node = GitilesMarkdown.parse(cfg, markdown);
Shawn Pearce47fd6562016-05-28 14:15:15 -070094 extractSiteTitle();
Shawn Pearce47fd6562016-05-28 14:15:15 -070095 }
96
97 private void extractSiteTitle() {
98 for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
Shawn Pearce12c8fab2016-05-15 16:55:21 -070099 if (c instanceof Heading) {
100 Heading h = (Heading) c;
Shawn Pearce99cdbce2015-02-10 12:05:45 -0800101 if (h.getLevel() == 1) {
Shawn Pearce47fd6562016-05-28 14:15:15 -0700102 siteTitle = MarkdownUtil.getInnerText(h);
Shawn Pearce12c8fab2016-05-15 16:55:21 -0700103 h.unlink();
Shawn Pearce99cdbce2015-02-10 12:05:45 -0800104 break;
105 }
106 }
107 }
Shawn Pearce47fd6562016-05-28 14:15:15 -0700108 }
Shawn Pearce99cdbce2015-02-10 12:05:45 -0800109
Shawn Pearcef485a312017-07-01 12:10:27 -0700110 private void extractMetadata(String markdown) {
111 Matcher m = META_LINK.matcher(markdown);
Shawn Pearce12c8fab2016-05-15 16:55:21 -0700112 while (m.find()) {
113 String key = m.group(1).toLowerCase();
114 String url = m.group(2).trim();
115 switch (key) {
116 case "logo":
Shawn Pearce47fd6562016-05-28 14:15:15 -0700117 logoUrl = url;
Shawn Pearce12c8fab2016-05-15 16:55:21 -0700118 break;
Shawn Pearce12c8fab2016-05-15 16:55:21 -0700119 case "home":
Shawn Pearce47fd6562016-05-28 14:15:15 -0700120 homeUrl = url;
Shawn Pearce12c8fab2016-05-15 16:55:21 -0700121 break;
Shawn Pearcef485a312017-07-01 12:10:27 -0700122 case "extensions":
123 Set<String> names = splitExtensionNames(url);
124 cfg = cfg.copyWithExtensions(enabled(names), disabled(names));
125 break;
Shawn Pearce99cdbce2015-02-10 12:05:45 -0800126 }
127 }
Shawn Pearce99cdbce2015-02-10 12:05:45 -0800128 }
Shawn Pearcef485a312017-07-01 12:10:27 -0700129
130 private static Set<String> splitExtensionNames(String url) {
131 return Splitter.on(CharMatcher.whitespace().or(CharMatcher.is(',')))
132 .trimResults()
133 .omitEmptyStrings()
134 .splitToList(url)
135 .stream()
136 .map(String::toLowerCase)
137 .collect(toSet());
138 }
139
140 private static Set<String> enabled(Set<String> names) {
141 return names.stream().filter(n -> !n.startsWith("!")).collect(toSet());
142 }
143
144 private static Set<String> disabled(Set<String> names) {
145 return names.stream().filter(n -> n.startsWith("!")).map(n -> n.substring(1)).collect(toSet());
146 }
Shawn Pearce99cdbce2015-02-10 12:05:45 -0800147}