1 package be
.nikiroo
.fanfix
.supported
;
3 import java
.io
.BufferedReader
;
4 import java
.io
.ByteArrayInputStream
;
6 import java
.io
.IOException
;
7 import java
.io
.InputStream
;
8 import java
.io
.InputStreamReader
;
9 import java
.net
.MalformedURLException
;
11 import java
.util
.ArrayList
;
12 import java
.util
.Date
;
13 import java
.util
.List
;
14 import java
.util
.Map
.Entry
;
15 import java
.util
.Scanner
;
17 import be
.nikiroo
.fanfix
.Instance
;
18 import be
.nikiroo
.fanfix
.bundles
.Config
;
19 import be
.nikiroo
.fanfix
.bundles
.StringId
;
20 import be
.nikiroo
.fanfix
.data
.Chapter
;
21 import be
.nikiroo
.fanfix
.data
.MetaData
;
22 import be
.nikiroo
.fanfix
.data
.Paragraph
;
23 import be
.nikiroo
.fanfix
.data
.Paragraph
.ParagraphType
;
24 import be
.nikiroo
.fanfix
.data
.Story
;
25 import be
.nikiroo
.utils
.Image
;
26 import be
.nikiroo
.utils
.Progress
;
27 import be
.nikiroo
.utils
.StringUtils
;
30 * DEPRECATED: use the new Jsoup 'Node' system.
32 * This class is the base class used by the other support classes. It can be
33 * used outside of this package, and have static method that you can use to get
34 * access to the correct support class.
36 * It will be used with 'resources' (usually web pages or files).
41 public abstract class BasicSupport_Deprecated
extends BasicSupport
{
42 private InputStream in
;
43 private URL currentReferer
; // with only one 'r', as in 'HTTP'...
46 private char openQuote
= Instance
.getTrans().getCharacter(
47 StringId
.OPEN_SINGLE_QUOTE
);
48 private char closeQuote
= Instance
.getTrans().getCharacter(
49 StringId
.CLOSE_SINGLE_QUOTE
);
50 private char openDoubleQuote
= Instance
.getTrans().getCharacter(
51 StringId
.OPEN_DOUBLE_QUOTE
);
52 private char closeDoubleQuote
= Instance
.getTrans().getCharacter(
53 StringId
.CLOSE_DOUBLE_QUOTE
);
55 // New methods not used in Deprecated mode
57 protected String
getDesc() throws IOException
{
58 throw new RuntimeException("should not be used by legacy code");
62 protected MetaData
getMeta() throws IOException
{
63 throw new RuntimeException("should not be used by legacy code");
67 protected List
<Entry
<String
, URL
>> getChapters(Progress pg
)
69 throw new RuntimeException("should not be used by legacy code");
73 protected String
getChapterContent(URL chapUrl
, int number
, Progress pg
)
75 throw new RuntimeException("should not be used by legacy code");
79 public Story
process(Progress pg
) throws IOException
{
80 return process(getSource(), pg
);
86 * Return the {@link MetaData} of this story.
89 * the source of the story
91 * the input (the main resource)
93 * @return the associated {@link MetaData}, never NULL
96 * in case of I/O error
98 protected abstract MetaData
getMeta(URL source
, InputStream in
)
102 * Return the story description.
105 * the source of the story
107 * the input (the main resource)
109 * @return the description
111 * @throws IOException
112 * in case of I/O error
114 protected abstract String
getDesc(URL source
, InputStream in
)
118 * Return the list of chapters (name and resource).
121 * the source of the story
123 * the input (the main resource)
125 * the optional progress reporter
127 * @return the chapters
129 * @throws IOException
130 * in case of I/O error
132 protected abstract List
<Entry
<String
, URL
>> getChapters(URL source
,
133 InputStream in
, Progress pg
) throws IOException
;
136 * Return the content of the chapter (possibly HTML encoded, if
137 * {@link BasicSupport_Deprecated#isHtml()} is TRUE).
140 * the source of the story
142 * the input (the main resource)
146 * the optional progress reporter
148 * @return the content
150 * @throws IOException
151 * in case of I/O error
153 protected abstract String
getChapterContent(URL source
, InputStream in
,
154 int number
, Progress pg
) throws IOException
;
157 * Process the given story resource into a partially filled {@link Story}
158 * object containing the name and metadata, except for the description.
163 * @return the {@link Story}
165 * @throws IOException
166 * in case of I/O error
168 public Story
processMeta(URL url
) throws IOException
{
169 return processMeta(url
, true, false, null);
173 * Process the given story resource into a partially filled {@link Story}
174 * object containing the name and metadata.
179 * close "this" and "in" when done
181 * retrieve the description of the story, or not
183 * the optional progress reporter
185 * @return the {@link Story}, never NULL
187 * @throws IOException
188 * in case of I/O error
190 protected Story
processMeta(URL url
, boolean close
, boolean getDesc
,
191 Progress pg
) throws IOException
{
195 pg
.setMinMax(0, 100);
201 url
= getCanonicalUrl(url
);
203 setCurrentReferer(url
);
205 in
= openInput(url
); // NULL allowed here
207 preprocess(url
, getInput());
210 Story story
= new Story();
211 MetaData meta
= getMeta(url
, getInput());
212 if (meta
.getCreationDate() == null
213 || meta
.getCreationDate().isEmpty()) {
214 meta
.setCreationDate(StringUtils
.fromTime(new Date().getTime()));
220 if (meta
.getCover() == null) {
221 meta
.setCover(getDefaultCover(meta
.getSubject()));
227 String descChapterName
= Instance
.getTrans().getString(
228 StringId
.DESCRIPTION
);
229 story
.getMeta().setResume(
230 makeChapter(url
, 0, descChapterName
,
231 getDesc(url
, getInput()), null));
248 * Process the given story resource into a fully filled {@link Story}
254 * the optional progress reporter
256 * @return the {@link Story}, never NULL
258 * @throws IOException
259 * in case of I/O error
261 protected Story
process(URL url
, Progress pg
) throws IOException
{
265 pg
.setMinMax(0, 100);
268 url
= getCanonicalUrl(url
);
271 Progress pgMeta
= new Progress();
272 pg
.addProgress(pgMeta
, 10);
273 Story story
= processMeta(url
, false, true, pgMeta
);
274 if (!pgMeta
.isDone()) {
275 pgMeta
.setProgress(pgMeta
.getMax()); // 10%
278 pg
.setName("Retrieving " + story
.getMeta().getTitle());
280 setCurrentReferer(url
);
282 Progress pgGetChapters
= new Progress();
283 pg
.addProgress(pgGetChapters
, 10);
284 story
.setChapters(new ArrayList
<Chapter
>());
285 List
<Entry
<String
, URL
>> chapters
= getChapters(url
, getInput(),
287 if (!pgGetChapters
.isDone()) {
288 pgGetChapters
.setProgress(pgGetChapters
.getMax()); // 20%
291 if (chapters
!= null) {
292 Progress pgChaps
= new Progress("Extracting chapters", 0,
293 chapters
.size() * 300);
294 pg
.addProgress(pgChaps
, 80);
298 for (Entry
<String
, URL
> chap
: chapters
) {
299 pgChaps
.setName("Extracting chapter " + i
);
300 InputStream chapIn
= null;
301 if (chap
.getValue() != null) {
302 setCurrentReferer(chap
.getValue());
303 chapIn
= Instance
.getCache().open(chap
.getValue(),
306 pgChaps
.setProgress(i
* 100);
308 Progress pgGetChapterContent
= new Progress();
309 Progress pgMakeChapter
= new Progress();
310 pgChaps
.addProgress(pgGetChapterContent
, 100);
311 pgChaps
.addProgress(pgMakeChapter
, 100);
313 String content
= getChapterContent(url
, chapIn
, i
,
314 pgGetChapterContent
);
315 if (!pgGetChapterContent
.isDone()) {
316 pgGetChapterContent
.setProgress(pgGetChapterContent
320 Chapter cc
= makeChapter(url
, i
, chap
.getKey(),
321 content
, pgMakeChapter
);
322 if (!pgMakeChapter
.isDone()) {
323 pgMakeChapter
.setProgress(pgMakeChapter
.getMax());
326 words
+= cc
.getWords();
327 story
.getChapters().add(cc
);
328 story
.getMeta().setWords(words
);
330 if (chapIn
!= null) {
338 pgChaps
.setName("Extracting chapters");
355 * Prepare the support if needed before processing.
358 * the source of the story
360 * the input (the main resource)
362 * @throws IOException
365 @SuppressWarnings("unused")
366 protected void preprocess(URL source
, InputStream in
) throws IOException
{
370 * Create a {@link Chapter} object from the given information, formatting
371 * the content as it should be.
374 * the source of the story
380 * the chapter content
382 * the optional progress reporter
384 * @return the {@link Chapter}
386 * @throws IOException
387 * in case of I/O error
389 protected Chapter
makeChapter(URL source
, int number
, String name
,
390 String content
, Progress pg
) throws IOException
{
391 // Chapter name: process it correctly, then remove the possible
392 // redundant "Chapter x: " in front of it, or "-" (as in
393 // "Chapter 5: - Fun!" after the ": " was automatically added)
394 String chapterName
= processPara(name
).getContent().trim();
395 for (String lang
: Instance
.getConfig().getString(Config
.CHAPTER
)
397 String chapterWord
= Instance
.getConfig().getStringX(
398 Config
.CHAPTER
, lang
);
399 if (chapterName
.startsWith(chapterWord
)) {
400 chapterName
= chapterName
.substring(chapterWord
.length())
406 if (chapterName
.startsWith(Integer
.toString(number
))) {
407 chapterName
= chapterName
.substring(
408 Integer
.toString(number
).length()).trim();
411 while (chapterName
.startsWith(":") || chapterName
.startsWith("-")) {
412 chapterName
= chapterName
.substring(1).trim();
416 Chapter chap
= new Chapter(number
, chapterName
);
418 if (content
!= null) {
419 List
<Paragraph
> paras
= makeParagraphs(source
, content
, pg
);
421 for (Paragraph para
: paras
) {
422 words
+= para
.getWords();
424 chap
.setParagraphs(paras
);
425 chap
.setWords(words
);
433 * Convert the given content into {@link Paragraph}s.
436 * the source URL of the story
438 * the textual content
440 * the optional progress reporter
442 * @return the {@link Paragraph}s
444 * @throws IOException
445 * in case of I/O error
447 protected List
<Paragraph
> makeParagraphs(URL source
, String content
,
448 Progress pg
) throws IOException
{
454 // Special <HR> processing:
455 content
= content
.replaceAll("(<hr [^>]*>)|(<hr/>)|(<hr>)",
459 List
<Paragraph
> paras
= new ArrayList
<Paragraph
>();
461 if (content
!= null && !content
.trim().isEmpty()) {
463 String
[] tab
= content
.split("(<p>|</p>|<br>|<br/>)");
464 pg
.setMinMax(0, tab
.length
);
466 for (String line
: tab
) {
467 if (line
.startsWith("[") && line
.endsWith("]")) {
468 pg
.setName("Extracting image " + i
);
470 paras
.add(makeParagraph(source
, line
.trim()));
475 List
<String
> lines
= new ArrayList
<String
>();
476 BufferedReader buff
= null;
478 buff
= new BufferedReader(
479 new InputStreamReader(new ByteArrayInputStream(
480 content
.getBytes("UTF-8")), "UTF-8"));
481 for (String line
= buff
.readLine(); line
!= null; line
= buff
483 lines
.add(line
.trim());
491 pg
.setMinMax(0, lines
.size());
493 for (String line
: lines
) {
494 if (line
.startsWith("[") && line
.endsWith("]")) {
495 pg
.setName("Extracting image " + i
);
497 paras
.add(makeParagraph(source
, line
));
503 // Check quotes for "bad" format
504 List
<Paragraph
> newParas
= new ArrayList
<Paragraph
>();
505 for (Paragraph para
: paras
) {
506 newParas
.addAll(requotify(para
));
510 // Remove double blanks/brks
511 fixBlanksBreaks(paras
);
518 * Convert the given line into a single {@link Paragraph}.
521 * the source URL of the story
523 * the textual content of the paragraph
525 * @return the {@link Paragraph}
527 private Paragraph
makeParagraph(URL source
, String line
) {
529 if (line
.startsWith("[") && line
.endsWith("]")) {
530 image
= getImage(this, source
, line
.substring(1, line
.length() - 1)
535 return new Paragraph(image
);
538 return processPara(line
);
542 * Fix the {@link ParagraphType#BLANK}s and {@link ParagraphType#BREAK}s of
543 * those {@link Paragraph}s.
545 * The resulting list will not contain a starting or trailing blank/break
546 * nor 2 blanks or breaks following each other.
549 * the list of {@link Paragraph}s to fix
551 protected void fixBlanksBreaks(List
<Paragraph
> paras
) {
552 boolean space
= false;
554 for (int i
= 0; i
< paras
.size(); i
++) {
555 Paragraph para
= paras
.get(i
);
556 boolean thisSpace
= para
.getType() == ParagraphType
.BLANK
;
557 boolean thisBrk
= para
.getType() == ParagraphType
.BREAK
;
559 if (i
> 0 && space
&& thisBrk
) {
562 } else if ((space
|| brk
) && (thisSpace
|| thisBrk
)) {
571 // Remove blank/brk at start
573 && (paras
.get(0).getType() == ParagraphType
.BLANK
|| paras
.get(
574 0).getType() == ParagraphType
.BREAK
)) {
578 // Remove blank/brk at end
579 int last
= paras
.size() - 1;
581 && (paras
.get(last
).getType() == ParagraphType
.BLANK
|| paras
582 .get(last
).getType() == ParagraphType
.BREAK
)) {
588 * Get the default cover related to this subject (see <tt>.info</tt> files).
593 * @return the cover if any, or NULL
595 static Image
getDefaultCover(String subject
) {
596 if (subject
!= null && !subject
.isEmpty()
597 && Instance
.getCoverDir() != null) {
599 File fileCover
= new File(Instance
.getCoverDir(), subject
);
600 return getImage(null, fileCover
.toURI().toURL(), subject
);
601 } catch (MalformedURLException e
) {
609 * Return the list of supported image extensions.
611 * @param emptyAllowed
612 * TRUE to allow an empty extension on first place, which can be
613 * used when you may already have an extension in your input but
614 * are not sure about it
616 * @return the extensions
618 static String
[] getImageExt(boolean emptyAllowed
) {
620 return new String
[] { "", ".png", ".jpg", ".jpeg", ".gif", ".bmp" };
623 return new String
[] { ".png", ".jpg", ".jpeg", ".gif", ".bmp" };
627 * Check if the given resource can be a local image or a remote image, then
628 * refresh the cache with it if it is.
633 * the resource to check
635 * @return the image if found, or NULL
638 static Image
getImage(BasicSupport_Deprecated support
, URL source
,
640 URL url
= getImageUrl(support
, source
, line
);
642 if ("file".equals(url
.getProtocol())) {
643 if (new File(url
.getPath()).isDirectory()) {
647 InputStream in
= null;
649 in
= Instance
.getCache().open(url
, getSupport(url
), true);
650 return new Image(in
);
651 } catch (IOException e
) {
656 } catch (IOException e
) {
666 * Check if the given resource can be a local image or a remote image, then
667 * refresh the cache with it if it is.
672 * the resource to check
674 * @return the image URL if found, or NULL
677 static URL
getImageUrl(BasicSupport_Deprecated support
, URL source
,
683 if (source
!= null) {
686 String relPath
= null;
687 String absPath
= null;
689 String path
= new File(source
.getFile()).getParent();
690 relPath
= new File(new File(path
), line
.trim())
692 } catch (Exception e
) {
693 // Cannot be converted to path (one possibility to take
694 // into account: absolute path on Windows)
697 absPath
= new File(line
.trim()).getAbsolutePath();
698 } catch (Exception e
) {
699 // Cannot be converted to path (at all)
702 for (String ext
: getImageExt(true)) {
703 File absFile
= new File(absPath
+ ext
);
704 File relFile
= new File(relPath
+ ext
);
705 if (absPath
!= null && absFile
.exists()
706 && absFile
.isFile()) {
707 url
= absFile
.toURI().toURL();
708 } else if (relPath
!= null && relFile
.exists()
709 && relFile
.isFile()) {
710 url
= relFile
.toURI().toURL();
713 } catch (Exception e
) {
714 // Should not happen since we control the correct arguments
721 for (String ext
: getImageExt(true)) {
722 if (Instance
.getCache()
723 .check(new URL(line
+ ext
), true)) {
724 url
= new URL(line
+ ext
);
731 for (String ext
: getImageExt(true)) {
733 url
= new URL(line
+ ext
);
734 Instance
.getCache().refresh(url
, support
, true);
736 } catch (IOException e
) {
737 // no image with this ext
742 } catch (MalformedURLException e
) {
747 // refresh the cached file
750 Instance
.getCache().refresh(url
, support
, true);
751 } catch (IOException e
) {
752 // woops, broken image
762 * Open the input file that will be used through the support.
764 * Can return NULL, in which case you are supposed to work without an
765 * {@link InputStream}.
768 * the source {@link URL}
770 * @return the {@link InputStream}
772 * @throws IOException
773 * in case of I/O error
775 protected InputStream
openInput(URL source
) throws IOException
{
776 return Instance
.getCache().open(source
, this, false);
780 * Reset then return {@link BasicSupport_Deprecated#in}.
782 * @return {@link BasicSupport_Deprecated#in}
784 protected InputStream
getInput() {
789 * Check quotes for bad format (i.e., quotes with normal paragraphs inside)
790 * and requotify them (i.e., separate them into QUOTE paragraphs and other
791 * paragraphs (quotes or not)).
794 * the paragraph to requotify (not necessarily a quote)
796 * @return the correctly (or so we hope) quotified paragraphs
798 protected List
<Paragraph
> requotify(Paragraph para
) {
799 List
<Paragraph
> newParas
= new ArrayList
<Paragraph
>();
801 if (para
.getType() == ParagraphType
.QUOTE
802 && para
.getContent().length() > 2) {
803 String line
= para
.getContent();
804 boolean singleQ
= line
.startsWith("" + openQuote
);
805 boolean doubleQ
= line
.startsWith("" + openDoubleQuote
);
807 // Do not try when more than one quote at a time
808 // (some stories are not easily readable if we do)
810 && line
.indexOf(closeQuote
, 1) < line
811 .lastIndexOf(closeQuote
)) {
816 && line
.indexOf(closeDoubleQuote
, 1) < line
817 .lastIndexOf(closeDoubleQuote
)) {
823 if (!singleQ
&& !doubleQ
) {
824 line
= openDoubleQuote
+ line
+ closeDoubleQuote
;
825 newParas
.add(new Paragraph(ParagraphType
.QUOTE
, line
, para
828 char open
= singleQ ? openQuote
: openDoubleQuote
;
829 char close
= singleQ ? closeQuote
: closeDoubleQuote
;
832 boolean inQuote
= false;
834 for (char car
: line
.toCharArray()) {
837 } else if (car
== close
) {
839 } else if (car
== '.' && !inQuote
) {
847 String rest
= line
.substring(posDot
+ 1).trim();
848 line
= line
.substring(0, posDot
+ 1).trim();
850 for (char car
: line
.toCharArray()) {
855 newParas
.add(new Paragraph(ParagraphType
.QUOTE
, line
, words
));
856 if (!rest
.isEmpty()) {
857 newParas
.addAll(requotify(processPara(rest
)));
871 * Process a {@link Paragraph} from a raw line of text.
873 * Will also fix quotes and HTML encoding if needed.
878 * @return the processed {@link Paragraph}
880 protected Paragraph
processPara(String line
) {
881 line
= ifUnhtml(line
).trim();
883 boolean space
= true;
885 boolean quote
= false;
886 boolean tentativeCloseQuote
= false;
891 StringBuilder builder
= new StringBuilder();
892 for (char car
: line
.toCharArray()) {
895 // dash, ndash and mdash: - – —
896 // currently: always use mdash
897 builder
.append(dashCount
== 1 ?
'-' : '—');
902 if (tentativeCloseQuote
) {
903 tentativeCloseQuote
= false;
904 if (Character
.isLetterOrDigit(car
)) {
907 // handle double-single quotes as double quotes
909 builder
.append(closeDoubleQuote
);
913 builder
.append(closeQuote
);
918 case ' ': // note: unbreakable space
921 case '\n': // just in case
922 case '\r': // just in case
923 if (builder
.length() > 0
924 && builder
.charAt(builder
.length() - 1) != ' ') {
931 if (space
|| (brk
&& quote
)) {
933 // handle double-single quotes as double quotes
935 builder
.deleteCharAt(builder
.length() - 1);
936 builder
.append(openDoubleQuote
);
938 builder
.append(openQuote
);
940 } else if (prev
== ' ' || prev
== car
) {
941 // handle double-single quotes as double quotes
943 builder
.deleteCharAt(builder
.length() - 1);
944 builder
.append(openDoubleQuote
);
946 builder
.append(openQuote
);
949 // it is a quote ("I'm off") or a 'quote' ("This
950 // 'good' restaurant"...)
951 tentativeCloseQuote
= true;
956 if (space
|| (brk
&& quote
)) {
958 builder
.append(openDoubleQuote
);
959 } else if (prev
== ' ') {
960 builder
.append(openDoubleQuote
);
962 builder
.append(closeDoubleQuote
);
996 if (space
|| (brk
&& quote
)) {
998 builder
.append(openQuote
);
1000 // handle double-single quotes as double quotes
1002 builder
.deleteCharAt(builder
.length() - 1);
1003 builder
.append(openDoubleQuote
);
1005 builder
.append(openQuote
);
1019 // handle double-single quotes as double quotes
1021 builder
.deleteCharAt(builder
.length() - 1);
1022 builder
.append(closeDoubleQuote
);
1024 builder
.append(closeQuote
);
1033 if (space
|| (brk
&& quote
)) {
1035 builder
.append(openDoubleQuote
);
1037 builder
.append(openDoubleQuote
);
1050 builder
.append(closeDoubleQuote
);
1056 builder
.append(car
);
1063 if (tentativeCloseQuote
) {
1064 tentativeCloseQuote
= false;
1065 builder
.append(closeQuote
);
1068 line
= builder
.toString().trim();
1070 ParagraphType type
= ParagraphType
.NORMAL
;
1072 type
= ParagraphType
.BLANK
;
1074 type
= ParagraphType
.BREAK
;
1076 type
= ParagraphType
.QUOTE
;
1079 return new Paragraph(type
, line
, words
);
1083 * Remove the HTML from the input <b>if</b>
1084 * {@link BasicSupport_Deprecated#isHtml()} is true.
1089 * @return the no html version if needed
1091 private String
ifUnhtml(String input
) {
1092 if (isHtml() && input
!= null) {
1093 return StringUtils
.unhtml(input
);
1100 * Reset the given {@link InputStream} and return it.
1103 * the {@link InputStream} to reset
1105 * @return the same {@link InputStream} after reset
1107 static protected InputStream
reset(InputStream in
) {
1112 } catch (IOException e
) {
1119 * Return the first line from the given input which correspond to the given
1125 * a string that must be found inside the target line (also
1126 * supports "^" at start to say "only if it starts with" the
1128 * @param relativeLine
1129 * the line to return based upon the target line position (-1 =
1130 * the line before, 0 = the target line...)
1134 static protected String
getLine(InputStream in
, String needle
,
1136 return getLine(in
, needle
, relativeLine
, true);
1140 * Return a line from the given input which correspond to the given
1146 * a string that must be found inside the target line (also
1147 * supports "^" at start to say "only if it starts with" the
1149 * @param relativeLine
1150 * the line to return based upon the target line position (-1 =
1151 * the line before, 0 = the target line...)
1153 * takes the first result (as opposed to the last one, which will
1154 * also always spend the input)
1158 static protected String
getLine(InputStream in
, String needle
,
1159 int relativeLine
, boolean first
) {
1164 List
<String
> lines
= new ArrayList
<String
>();
1165 @SuppressWarnings("resource")
1166 Scanner scan
= new Scanner(in
, "UTF-8");
1168 scan
.useDelimiter("\\n");
1169 while (scan
.hasNext()) {
1170 lines
.add(scan
.next());
1173 if (needle
.startsWith("^")) {
1174 if (lines
.get(lines
.size() - 1).startsWith(
1175 needle
.substring(1))) {
1176 index
= lines
.size() - 1;
1180 if (lines
.get(lines
.size() - 1).contains(needle
)) {
1181 index
= lines
.size() - 1;
1186 if (index
>= 0 && index
+ relativeLine
< lines
.size()) {
1187 rep
= lines
.get(index
+ relativeLine
);
1198 * Return the text between the key and the endKey (and optional subKey can
1199 * be passed, in this case we will look for the key first, then take the
1200 * text between the subKey and the endKey).
1202 * Will only match the first line with the given key if more than one are
1203 * possible. Which also means that if the subKey or endKey is not found on
1204 * that line, NULL will be returned.
1209 * the key to match (also supports "^" at start to say
1210 * "only if it starts with" the key)
1212 * the sub key or NULL if none
1214 * the end key or NULL for "up to the end"
1215 * @return the text or NULL if not found
1217 static protected String
getKeyLine(InputStream in
, String key
,
1218 String subKey
, String endKey
) {
1219 return getKeyText(getLine(in
, key
, 0), key
, subKey
, endKey
);
1223 * Return the text between the key and the endKey (and optional subKey can
1224 * be passed, in this case we will look for the key first, then take the
1225 * text between the subKey and the endKey).
1230 * the key to match (also supports "^" at start to say
1231 * "only if it starts with" the key)
1233 * the sub key or NULL if none
1235 * the end key or NULL for "up to the end"
1236 * @return the text or NULL if not found
1238 static protected String
getKeyText(String in
, String key
, String subKey
,
1240 String result
= null;
1243 if (line
!= null && line
.contains(key
)) {
1244 line
= line
.substring(line
.indexOf(key
) + key
.length());
1245 if (subKey
== null || subKey
.isEmpty() || line
.contains(subKey
)) {
1246 if (subKey
!= null) {
1247 line
= line
.substring(line
.indexOf(subKey
)
1250 if (endKey
== null || line
.contains(endKey
)) {
1251 if (endKey
!= null) {
1252 line
= line
.substring(0, line
.indexOf(endKey
));
1263 * Return the text between the key and the endKey (optional subKeys can be
1264 * passed, in this case we will look for the subKeys first, then take the
1265 * text between the key and the endKey).
1272 * the end key or NULL for "up to the end"
1274 * the sub-keys to find before checking for key/endKey
1276 * @return the text or NULL if not found
1278 static protected String
getKeyTextAfter(String in
, String key
,
1279 String endKey
, String
... afters
) {
1281 if (in
!= null && !in
.isEmpty()) {
1282 int pos
= indexOfAfter(in
, 0, afters
);
1287 in
= in
.substring(pos
);
1290 return getKeyText(in
, key
, null, endKey
);
1294 * Return the first index after all the given "afters" have been found in
1295 * the {@link String}, or -1 if it was not possible.
1300 * start at this position in the string
1302 * the sub-keys to find before checking for key/endKey
1304 * @return the text or NULL if not found
1306 static protected int indexOfAfter(String in
, int startAt
, String
... afters
) {
1308 if (in
!= null && !in
.isEmpty()) {
1310 if (afters
!= null) {
1311 for (int i
= 0; pos
>= 0 && i
< afters
.length
; i
++) {
1312 String subKey
= afters
[i
];
1313 if (!subKey
.isEmpty()) {
1314 pos
= in
.indexOf(subKey
, pos
);
1316 pos
+= subKey
.length();