2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
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:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
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.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.io
.InputStream
;
32 import java
.io
.IOException
;
33 import java
.text
.MessageFormat
;
34 import java
.util
.ArrayList
;
35 import java
.util
.Collections
;
36 import java
.util
.HashMap
;
37 import java
.util
.List
;
38 import java
.util
.ResourceBundle
;
39 import java
.util
.regex
.Matcher
;
40 import java
.util
.regex
.Pattern
;
41 import javax
.xml
.parsers
.DocumentBuilderFactory
;
42 import javax
.xml
.parsers
.DocumentBuilder
;
43 import javax
.xml
.parsers
.ParserConfigurationException
;
44 import org
.xml
.sax
.SAXException
;
45 import org
.w3c
.dom
.Document
;
46 import org
.w3c
.dom
.Element
;
47 import org
.w3c
.dom
.NamedNodeMap
;
48 import org
.w3c
.dom
.Node
;
49 import org
.w3c
.dom
.NodeList
;
52 * A HelpFile is a collection of Topics with a table of contents and index of
55 public class HelpFile
{
60 private static final ResourceBundle i18n
= ResourceBundle
.getBundle(HelpFile
.class.getName());
62 // ------------------------------------------------------------------------
63 // Variables --------------------------------------------------------------
64 // ------------------------------------------------------------------------
69 private static DocumentBuilder domBuilder
;
72 * The map of topics by title.
74 private HashMap
<String
, Topic
> topicsByTitle
;
77 * The map of topics by index key term.
79 private HashMap
<String
, Topic
> topicsByTerm
;
82 * The special "table of contents" topic.
84 private Topic tableOfContents
;
87 * The special "index" topic.
92 * The name of this help file.
94 private String name
= "";
97 * The version of this help file.
99 private String version
= "";
102 * The help file author.
104 private String author
= "";
107 * The help file copyright/written by date.
109 private String date
= "";
111 // ------------------------------------------------------------------------
112 // Constructors -----------------------------------------------------------
113 // ------------------------------------------------------------------------
115 // ------------------------------------------------------------------------
116 // HelpFile ---------------------------------------------------------------
117 // ------------------------------------------------------------------------
120 * Load a help file from an input stream.
122 * @param input the input strem
123 * @throws IOException if an I/O error occurs
124 * @throws ParserConfigurationException if no XML parser is available
125 * @throws SAXException if XML parsing fails
127 public void load(final InputStream input
) throws IOException
,
128 ParserConfigurationException
, SAXException
{
130 topicsByTitle
= new HashMap
<String
, Topic
>();
131 topicsByTerm
= new HashMap
<String
, Topic
>();
136 // Always generate the TOC and Index from what was read.
137 generateTableOfContents();
143 * Get a topic by title.
145 * @param title the title for the topic
146 * @return the topic, or the "not found" topic if title is not found
148 public Topic
getTopic(final String title
) {
149 Topic topic
= topicsByTitle
.get(title
);
151 return Topic
.NOT_FOUND
;
157 * Get the special "search results" topic.
159 * @param searchString a regular expression search string
160 * @return an index topic containing topics with text that matches the
163 public Topic
getSearchResults(final String searchString
) {
164 List
<Topic
> allTopics
= new ArrayList
<Topic
>();
165 allTopics
.addAll(topicsByTitle
.values());
166 Collections
.sort(allTopics
);
168 List
<Topic
> results
= new ArrayList
<Topic
>();
169 Pattern pattern
= Pattern
.compile(searchString
);
170 Pattern patternLower
= Pattern
.compile(searchString
.toLowerCase());
172 for (Topic topic
: allTopics
) {
173 Matcher match
= pattern
.matcher(topic
.getText().toLowerCase());
178 match
= pattern
.matcher(topic
.getTitle().toLowerCase());
183 match
= patternLower
.matcher(topic
.getText().toLowerCase());
188 match
= patternLower
.matcher(topic
.getTitle().toLowerCase());
195 StringBuilder text
= new StringBuilder();
197 List
<Link
> links
= new ArrayList
<Link
>();
198 for (Topic topic
: results
) {
199 text
.append(topic
.getTitle());
202 Link link
= new Link(topic
.getTitle(), topic
.getTitle(), wordIndex
);
203 wordIndex
+= link
.getWordCount();
207 return new Topic(MessageFormat
.format(i18n
.getString("searchResults"),
208 searchString
), text
.toString(), links
);
212 * Get the special "table of contents" topic.
214 * @return the table of contents topic
216 public Topic
getTableOfContents() {
217 return tableOfContents
;
221 * Get the special "index" topic.
223 * @return the index topic
225 public Topic
getIndex() {
230 * Generate the table of contents topic.
232 private void generateTableOfContents() {
233 List
<Topic
> allTopics
= new ArrayList
<Topic
>();
234 allTopics
.addAll(topicsByTitle
.values());
235 Collections
.sort(allTopics
);
237 StringBuilder text
= new StringBuilder();
239 List
<Link
> links
= new ArrayList
<Link
>();
240 for (Topic topic
: allTopics
) {
241 text
.append(topic
.getTitle());
244 Link link
= new Link(topic
.getTitle(), topic
.getTitle(), wordIndex
);
245 wordIndex
+= link
.getWordCount();
249 tableOfContents
= new Topic(i18n
.getString("tableOfContents"),
250 text
.toString(), links
);
254 * Generate the index topic.
256 private void generateIndex() {
257 List
<Topic
> allTopics
= new ArrayList
<Topic
>();
258 allTopics
.addAll(topicsByTitle
.values());
260 HashMap
<String
, ArrayList
<Topic
>> allKeys
;
261 allKeys
= new HashMap
<String
, ArrayList
<Topic
>>();
262 for (Topic topic
: allTopics
) {
263 for (String key
: topic
.getIndexKeys()) {
264 key
= key
.toLowerCase();
265 ArrayList
<Topic
> topics
= allKeys
.get(key
);
266 if (topics
== null) {
267 topics
= new ArrayList
<Topic
>();
268 allKeys
.put(key
, topics
);
273 List
<String
> keys
= new ArrayList
<String
>();
274 keys
.addAll(allKeys
.keySet());
275 Collections
.sort(keys
);
277 StringBuilder text
= new StringBuilder();
279 List
<Link
> links
= new ArrayList
<Link
>();
281 for (String key
: keys
) {
282 List
<Topic
> topics
= allKeys
.get(key
);
283 assert (topics
!= null);
284 for (Topic topic
: topics
) {
285 String line
= String
.format("%15s %15s", key
, topic
.getTitle());
289 wordIndex
+= key
.split("\\s+").length
;
290 Link link
= new Link(topic
.getTitle(), topic
.getTitle(), wordIndex
);
291 wordIndex
+= link
.getWordCount();
296 index
= new Topic(i18n
.getString("index"), text
.toString(), links
);
300 * Load topics from a help file into the topics pool.
302 * @param input the input strem
303 * @throws IOException if an I/O error occurs
304 * @throws ParserConfigurationException if no XML parser is available
305 * @throws SAXException if XML parsing fails
307 private void loadTopics(final InputStream input
) throws IOException
,
308 ParserConfigurationException
, SAXException
{
310 if (domBuilder
== null) {
311 DocumentBuilderFactory dbFactory
= DocumentBuilderFactory
.
313 domBuilder
= dbFactory
.newDocumentBuilder();
315 Document doc
= domBuilder
.parse(input
);
317 // Get the document's root XML node
318 Node root
= doc
.getChildNodes().item(0);
319 NodeList level1
= root
.getChildNodes();
320 for (int i
= 0; i
< level1
.getLength(); i
++) {
321 Node node
= level1
.item(i
);
322 String name
= node
.getNodeName();
323 String value
= node
.getTextContent();
325 if (name
.equals("name")) {
328 if (name
.equals("version")) {
329 this.version
= value
;
331 if (name
.equals("author")) {
334 if (name
.equals("date")) {
337 if (name
.equals("topics")) {
338 NodeList topics
= node
.getChildNodes();
339 for (int j
= 0; j
< topics
.getLength(); j
++) {
340 Node topic
= topics
.item(j
);
348 * Add a topic to this help file.
350 * @param xmlNode the topic XML node
351 * @throws IOException if a java.io operation throws
353 private void addTopic(final Node xmlNode
) throws IOException
{
357 NamedNodeMap attributes
= xmlNode
.getAttributes();
358 if (attributes
!= null) {
359 for (int i
= 0; i
< attributes
.getLength(); i
++) {
360 Node attr
= attributes
.item(i
);
361 if (attr
.getNodeName().equals("title")) {
362 title
= attr
.getNodeValue().trim();
366 NodeList level2
= xmlNode
.getChildNodes();
367 for (int i
= 0; i
< level2
.getLength(); i
++) {
368 Node node
= level2
.item(i
);
369 String nodeName
= node
.getNodeName();
370 String nodeValue
= node
.getTextContent();
371 if (nodeName
.equals("text")) {
372 text
= nodeValue
.trim();
375 if (title
.length() > 0) {
376 Topic topic
= new Topic(title
, text
);
377 topicsByTitle
.put(title
, topic
);