Merge commit '77d3a60869e7a780c6ae069e51530e1eacece5e2'
[fanfix.git] / src / jexer / help / Topic.java
CommitLineData
4941d2d6
KL
1/*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29package jexer.help;
30
31import java.util.ArrayList;
32import java.util.HashSet;
33import java.util.List;
34import java.util.Set;
35import java.util.ResourceBundle;
36import java.util.regex.Matcher;
37import java.util.regex.Pattern;
38
39/**
40 * A Topic is a page of help text with a title and possibly links to other
41 * Topics.
42 */
43public class Topic implements Comparable<Topic> {
44
45 /**
46 * Translated strings.
47 */
48 private static final ResourceBundle i18n = ResourceBundle.getBundle(Topic.class.getName());
49
50 // ------------------------------------------------------------------------
51 // Constants --------------------------------------------------------------
52 // ------------------------------------------------------------------------
53
54 /**
55 * The "not found" topic to display when a key or index term does not
56 * have an associated topic. Note package private access.
57 */
58 static Topic NOT_FOUND = null;
59
60 /**
61 * The regex for identifying index tags.
62 */
63 private static final String INDEX_REGEX_STR = "\\#\\{([^\\}]*)\\}";
64
65 /**
66 * The regex for identifying link tags.
67 */
68 private static final String LINK_REGEX_STR = "\\[([^\\]]*)\\]\\(([^\\)]*)\\)";
69
70 /**
71 * The regex for identifying words.
72 */
73 private static final String WORD_REGEX_STR = "[ \\t]+";
74
75 /**
76 * The index match regex.
77 */
78 private static Pattern INDEX_REGEX;
79
80 /**
81 * The link match regex.
82 */
83 private static Pattern LINK_REGEX;
84
85 /**
86 * The word match regex.
87 */
88 private static Pattern WORD_REGEX;
89
90 // ------------------------------------------------------------------------
91 // Variables --------------------------------------------------------------
92 // ------------------------------------------------------------------------
93
94 /**
95 * The title for this topic.
96 */
97 private String title;
98
99 /**
100 * The text for this topic.
101 */
102 private String text;
103
104 /**
105 * The index keys in this topic.
106 */
107 private Set<String> indexKeys = new HashSet<String>();
108
109 /**
110 * The links in this topic.
111 */
112 private List<Link> links = new ArrayList<Link>();
113
114 // ------------------------------------------------------------------------
115 // Constructors -----------------------------------------------------------
116 // ------------------------------------------------------------------------
117
118 /**
119 * Static constructor.
120 */
121 static {
122 try {
123 INDEX_REGEX = Pattern.compile(INDEX_REGEX_STR);
124 LINK_REGEX = Pattern.compile(LINK_REGEX_STR);
125 WORD_REGEX = Pattern.compile(WORD_REGEX_STR);
126
127 NOT_FOUND = new Topic(i18n.getString("topicNotFoundTitle"),
128 i18n.getString("topicNotFoundText"));
129 } catch (Exception e) {
130 e.printStackTrace();
131 }
132 }
133
134 /**
135 * Public constructor.
136 *
137 * @param title the topic title
138 * @param text the topic text
139 */
140 public Topic(final String title, final String text) {
141 this.title = title;
142 processText(text);
143 }
144
145 /**
146 * Package private constructor.
147 *
148 * @param title the topic title
149 * @param text the topic text
150 * @param links links to add after processing text
151 */
152 Topic(final String title, final String text, final List<Link> links) {
153 this.title = title;
154 processText(text);
155 this.links.addAll(links);
156 }
157
158 // ------------------------------------------------------------------------
159 // Topic ------------------------------------------------------------------
160 // ------------------------------------------------------------------------
161
162 /**
163 * Get the topic title.
164 *
165 * @return the title
166 */
167 public String getTitle() {
168 return title;
169 }
170
171 /**
172 * Get the topic text.
173 *
174 * @return the text
175 */
176 public String getText() {
177 return text;
178 }
179
180 /**
181 * Get the index keys.
182 *
183 * @return the keys
184 */
185 public Set<String> getIndexKeys() {
186 return indexKeys;
187 }
188
189 /**
190 * Get the links.
191 *
192 * @return the links
193 */
194 public List<Link> getLinks() {
195 return links;
196 }
197
198 /**
199 * Comparison operator.
200 *
201 * @param that another Topic instance
202 * @return comparison by topic title
203 */
204 public int compareTo(final Topic that) {
205 return title.compareTo(that.title);
206 }
207
208 /**
209 * Generate a human-readable string for this widget.
210 *
211 * @return a human-readable string
212 */
213 @Override
214 public String toString() {
215 return String.format("%s(%8x) topic %s text %s links %s indexKeys %s",
216 getClass().getName(), hashCode(), title, text, links, indexKeys);
217 }
218
219 /**
220 * Process a string through the regexes, building up the indexes and
221 * links.
222 *
223 * @param text the text to process
224 */
225 private void processText(final String text) {
226 StringBuilder sb = new StringBuilder();
227 String [] lines = text.split("\n");
228 int wordIndex = 0;
229 for (String line: lines) {
230 line = line.trim();
231
232 String cleanLine = "";
233
234 // System.err.println("LINE " + wordIndex + " : '" + line + "'");
235
236 Matcher index = INDEX_REGEX.matcher(line);
237 int start = 0;
238 while (index.find()) {
239 cleanLine += line.substring(start, index.start());
240 String key = index.group(1);
241 cleanLine += key;
242 start = index.end();
243 // System.err.println("ADD KEY: " + key);
244 indexKeys.add(key);
245 }
246 cleanLine += line.substring(start);
247
248 line = cleanLine;
249 cleanLine = "";
250
251 /*
252 System.err.println("line after removing #{index} tags: " +
253 wordIndex + " '" + line + "'");
254 */
255
256 Matcher link = LINK_REGEX.matcher(line);
257 start = 0;
258
259 boolean hasLink = link.find();
260
261 // System.err.println("hasLink " + hasLink);
262
263 while (true) {
264
265 if (hasLink == false) {
266 cleanLine += line.substring(start);
267
268 String remaining = line.substring(start).trim();
269 Matcher word = WORD_REGEX.matcher(remaining);
270 while (word.find()) {
271 // System.err.println("word.find() true");
272 wordIndex++;
273 }
274 if (remaining.length() > 0) {
275 // The last word on the line.
276 wordIndex++;
277 }
278 break;
279 }
280
281 assert (hasLink == true);
282
283 int linkWordIndex = link.start();
284 int cleanLineStart = cleanLine.length();
285 cleanLine += line.substring(start, linkWordIndex);
286 String linkText = link.group(1);
287 String topic = link.group(2);
288 cleanLine += linkText;
289 start = link.end();
290
291 // Increment wordIndex until we reach the first word of
292 // the link text.
293 Matcher word = WORD_REGEX.matcher(cleanLine.
294 substring(cleanLineStart));
295 while (word.find()) {
296 if (word.end() <= linkWordIndex) {
297 wordIndex++;
298 } else {
299 // We have found the word that matches the first
300 // word of link text, bail out.
301 break;
302 }
303 }
304 /*
305 System.err.println("ADD LINK --> " + topic + ": '" +
306 linkText + "' word index " + wordIndex);
307 */
308 links.add(new Link(topic, linkText, wordIndex));
309
310 // The rest of the words in the link text.
311 while (word.find()) {
312 wordIndex++;
313 }
314 // The final word after the last whitespace.
315 wordIndex++;
316
317 hasLink = link.find();
318 if (hasLink) {
319 wordIndex += 3;
320 }
321 }
322
323
324 /*
325 System.err.println("line after removing [link](...) tags: '" +
326 cleanLine + "'");
327 */
328
329 // Append the entire line.
330 sb.append(cleanLine);
331 sb.append("\n");
332
333 this.text = sb.toString();
334
335 } // for (String line: lines)
336
337 }
338
339}