blob: 6d9e5677bb2433810e3644161eaf3fe1a742fe5d [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;
Matthias Sohnc156c962023-09-30 22:15:23 +020028import javax.annotation.Nullable;
Dave Borowitz3b744b12016-08-19 16:11:10 -040029import org.commonmark.node.Heading;
30import org.commonmark.node.Node;
31import org.eclipse.jgit.util.RawParseUtils;
Shawn Pearce99cdbce2015-02-10 12:05:45 -080032
33class Navbar {
Shawn Pearcef485a312017-07-01 12:10:27 -070034 private static final Pattern META_LINK =
35 Pattern.compile(
36 "^\\[(logo|home|extensions)\\]:\\s*(.+)$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
David Pursehouse374c9992019-06-15 14:06:40 +090037 private static final Pattern EXTENSIONS_LINK =
38 Pattern.compile("^\\[extensions\\]:\\s*(.+)$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
Shawn Pearce12c8fab2016-05-15 16:55:21 -070039
Shawn Pearcef485a312017-07-01 12:10:27 -070040 private MarkdownConfig cfg;
Shawn Pearce47fd6562016-05-28 14:15:15 -070041 private MarkdownToHtml fmt;
42 private Node node;
43 private String siteTitle;
44 private String logoUrl;
45 private String homeUrl;
Shawn Pearce99cdbce2015-02-10 12:05:45 -080046
Shawn Pearce47fd6562016-05-28 14:15:15 -070047 Navbar() {}
48
Shawn Pearcef485a312017-07-01 12:10:27 -070049 MarkdownConfig getConfig() {
50 return cfg;
51 }
52
53 Navbar setConfig(MarkdownConfig cfg) {
54 this.cfg = cfg;
55 return this;
56 }
57
Shawn Pearce47fd6562016-05-28 14:15:15 -070058 Navbar setFormatter(MarkdownToHtml html) {
59 this.fmt = html;
60 return this;
61 }
62
Shawn Pearcef485a312017-07-01 12:10:27 -070063 Navbar setMarkdown(byte[] md) {
Shawn Pearce47fd6562016-05-28 14:15:15 -070064 if (md != null && md.length > 0) {
Shawn Pearcef485a312017-07-01 12:10:27 -070065 parse(RawParseUtils.decode(md));
Shawn Pearce47fd6562016-05-28 14:15:15 -070066 }
67 return this;
68 }
69
70 Map<String, Object> toSoyData() {
71 Map<String, Object> data = new HashMap<>();
72 data.put("siteTitle", siteTitle);
73 data.put("logoUrl", logo());
74 data.put("homeUrl", homeUrl != null ? fmt.href(homeUrl) : null);
75 data.put("navbarHtml", node != null ? fmt.toSoyHtml(node) : null);
76 return data;
77 }
78
Matthias Sohnc156c962023-09-30 22:15:23 +020079 private @Nullable Object logo() {
Shawn Pearce47fd6562016-05-28 14:15:15 -070080 if (logoUrl == null) {
81 return null;
Shawn Pearce99cdbce2015-02-10 12:05:45 -080082 }
83
Shawn Pearce47fd6562016-05-28 14:15:15 -070084 String url = fmt.image(logoUrl);
85 if (HtmlBuilder.isValidHttpUri(url)) {
86 return url;
87 } else if (HtmlBuilder.isImageDataUri(url)) {
88 return Sanitizers.filterImageDataUri(url);
89 } else {
Dave Borowitz12181942018-02-12 09:22:45 -050090 return SoyConstants.IMAGE_URI_INNOCUOUS_OUTPUT;
Shawn Pearce47fd6562016-05-28 14:15:15 -070091 }
92 }
93
Shawn Pearcef485a312017-07-01 12:10:27 -070094 private void parse(String markdown) {
Vlad Bokovfbda0c92019-04-17 13:47:44 +070095 String md = extractMetadata(markdown);
96 node = GitilesMarkdown.parse(cfg, md);
Shawn Pearce47fd6562016-05-28 14:15:15 -070097 extractSiteTitle();
Shawn Pearce47fd6562016-05-28 14:15:15 -070098 }
99
100 private void extractSiteTitle() {
101 for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
Shawn Pearce12c8fab2016-05-15 16:55:21 -0700102 if (c instanceof Heading) {
103 Heading h = (Heading) c;
Shawn Pearce99cdbce2015-02-10 12:05:45 -0800104 if (h.getLevel() == 1) {
Shawn Pearce47fd6562016-05-28 14:15:15 -0700105 siteTitle = MarkdownUtil.getInnerText(h);
Shawn Pearce12c8fab2016-05-15 16:55:21 -0700106 h.unlink();
Shawn Pearce99cdbce2015-02-10 12:05:45 -0800107 break;
108 }
109 }
110 }
Shawn Pearce47fd6562016-05-28 14:15:15 -0700111 }
Shawn Pearce99cdbce2015-02-10 12:05:45 -0800112
Vlad Bokovfbda0c92019-04-17 13:47:44 +0700113 private String extractMetadata(String markdown) {
David Pursehouse374c9992019-06-15 14:06:40 +0900114 boolean extensionsFound = false;
Shawn Pearcef485a312017-07-01 12:10:27 -0700115 Matcher m = META_LINK.matcher(markdown);
David Pursehouse374c9992019-06-15 14:06:40 +0900116 while (m.find()) {
117 String key = m.group(1).toLowerCase();
118 String url = m.group(2).trim();
119 switch (key) {
120 case "logo":
121 logoUrl = url;
122 break;
123 case "home":
124 homeUrl = url;
125 break;
126 case "extensions":
127 extensionsFound = true;
128 Set<String> names = splitExtensionNames(url);
129 cfg = cfg.copyWithExtensions(enabled(names), disabled(names));
130 break;
131 }
132 }
133 if (extensionsFound) {
134 return EXTENSIONS_LINK.matcher(markdown).replaceAll("");
Shawn Pearce99cdbce2015-02-10 12:05:45 -0800135 }
Vlad Bokovfbda0c92019-04-17 13:47:44 +0700136 return markdown;
Shawn Pearce99cdbce2015-02-10 12:05:45 -0800137 }
Shawn Pearcef485a312017-07-01 12:10:27 -0700138
139 private static Set<String> splitExtensionNames(String url) {
Matthias Sohn77c23252024-11-21 23:23:42 +0100140 return Splitter.on(CharMatcher.whitespace().or(CharMatcher.is(',')))
141 .trimResults()
142 .omitEmptyStrings()
143 .splitToList(url)
144 .stream()
Shawn Pearcef485a312017-07-01 12:10:27 -0700145 .map(String::toLowerCase)
146 .collect(toSet());
147 }
148
149 private static Set<String> enabled(Set<String> names) {
150 return names.stream().filter(n -> !n.startsWith("!")).collect(toSet());
151 }
152
153 private static Set<String> disabled(Set<String> names) {
154 return names.stream().filter(n -> n.startsWith("!")).map(n -> n.substring(1)).collect(toSet());
155 }
Shawn Pearce99cdbce2015-02-10 12:05:45 -0800156}