18113188b8ac358b19af20ff62a845a91d7be9b1
1 package be
.nikiroo
.fanfix
.supported
;
3 import java
.awt
.image
.BufferedImage
;
4 import java
.io
.BufferedReader
;
5 import java
.io
.ByteArrayInputStream
;
7 import java
.io
.IOException
;
8 import java
.io
.InputStream
;
9 import java
.io
.InputStreamReader
;
10 import java
.net
.MalformedURLException
;
12 import java
.util
.ArrayList
;
13 import java
.util
.Date
;
14 import java
.util
.HashMap
;
15 import java
.util
.List
;
17 import java
.util
.Map
.Entry
;
18 import java
.util
.Scanner
;
20 import be
.nikiroo
.fanfix
.Instance
;
21 import be
.nikiroo
.fanfix
.bundles
.Config
;
22 import be
.nikiroo
.fanfix
.bundles
.StringId
;
23 import be
.nikiroo
.fanfix
.data
.Chapter
;
24 import be
.nikiroo
.fanfix
.data
.MetaData
;
25 import be
.nikiroo
.fanfix
.data
.Paragraph
;
26 import be
.nikiroo
.fanfix
.data
.Paragraph
.ParagraphType
;
27 import be
.nikiroo
.fanfix
.data
.Story
;
28 import be
.nikiroo
.utils
.IOUtils
;
29 import be
.nikiroo
.utils
.Progress
;
30 import be
.nikiroo
.utils
.StringUtils
;
33 * This class is the base class used by the other support classes. It can be
34 * used outside of this package, and have static method that you can use to get
35 * access to the correct support class.
37 * It will be used with 'resources' (usually web pages or files).
41 public abstract class BasicSupport
{
43 * The supported input types for which we can get a {@link BasicSupport}
48 public enum SupportType
{
49 /** EPUB files created with this program */
51 /** Pure text file with some rules */
53 /** TEXT but with associated .info file */
55 /** My Little Pony fanfictions */
57 /** Fanfictions from a lot of different universes */
59 /** Website with lots of Mangas */
61 /** Furry website with comics support */
63 /** Furry website with stories */
71 * A description of this support type (more information than the
72 * {@link BasicSupport#getSourceName()}).
74 * @return the description
76 public String
getDesc() {
77 String desc
= Instance
.getTrans().getStringX(StringId
.INPUT_DESC
,
81 desc
= Instance
.getTrans().getString(StringId
.INPUT_DESC
, this);
88 * The name of this support type (a short version).
92 public String
getSourceName() {
93 BasicSupport support
= BasicSupport
.getSupport(this);
94 if (support
!= null) {
95 return support
.getSourceName();
102 public String
toString() {
103 return super.toString().toLowerCase();
107 * Call {@link SupportType#valueOf(String.toUpperCase())}.
110 * the possible type name
112 * @return NULL or the type
114 public static SupportType
valueOfUC(String typeName
) {
115 return SupportType
.valueOf(typeName
== null ?
null : typeName
120 * Call {@link SupportType#valueOf(String.toUpperCase())} but return
121 * NULL for NULL instead of raising exception.
124 * the possible type name
126 * @return NULL or the type
128 public static SupportType
valueOfNullOkUC(String typeName
) {
129 if (typeName
== null) {
133 return SupportType
.valueOfUC(typeName
);
137 * Call {@link SupportType#valueOf(String.toUpperCase())} but return
138 * NULL in case of error instead of raising an exception.
141 * the possible type name
143 * @return NULL or the type
145 public static SupportType
valueOfAllOkUC(String typeName
) {
147 return SupportType
.valueOfUC(typeName
);
148 } catch (Exception e
) {
154 private InputStream in
;
155 private SupportType type
;
156 private URL currentReferer
; // with only one 'r', as in 'HTTP'...
159 private char openQuote
= Instance
.getTrans().getChar(
160 StringId
.OPEN_SINGLE_QUOTE
);
161 private char closeQuote
= Instance
.getTrans().getChar(
162 StringId
.CLOSE_SINGLE_QUOTE
);
163 private char openDoubleQuote
= Instance
.getTrans().getChar(
164 StringId
.OPEN_DOUBLE_QUOTE
);
165 private char closeDoubleQuote
= Instance
.getTrans().getChar(
166 StringId
.CLOSE_DOUBLE_QUOTE
);
169 * The name of this support class.
173 protected abstract String
getSourceName();
176 * Check if the given resource is supported by this {@link BasicSupport}.
179 * the resource to check for
181 * @return TRUE if it is
183 protected abstract boolean supports(URL url
);
186 * Return TRUE if the support will return HTML encoded content values for
187 * the chapters content.
189 * @return TRUE for HTML
191 protected abstract boolean isHtml();
193 protected abstract MetaData
getMeta(URL source
, InputStream in
)
197 * Return the story description.
200 * the source of the story
202 * the input (the main resource)
204 * @return the description
206 * @throws IOException
207 * in case of I/O error
209 protected abstract String
getDesc(URL source
, InputStream in
)
213 * Return the list of chapters (name and resource).
216 * the source of the story
218 * the input (the main resource)
220 * @return the chapters
222 * @throws IOException
223 * in case of I/O error
225 protected abstract List
<Entry
<String
, URL
>> getChapters(URL source
,
226 InputStream in
) throws IOException
;
229 * Return the content of the chapter (possibly HTML encoded, if
230 * {@link BasicSupport#isHtml()} is TRUE).
233 * the source of the story
235 * the input (the main resource)
239 * @return the content
241 * @throws IOException
242 * in case of I/O error
244 protected abstract String
getChapterContent(URL source
, InputStream in
,
245 int number
) throws IOException
;
248 * Log into the support (can be a no-op depending upon the support).
250 * @throws IOException
251 * in case of I/O error
253 public void login() throws IOException
{
258 * Return the list of cookies (values included) that must be used to
259 * correctly fetch the resources.
261 * You are expected to call the super method implementation if you override
264 * @return the cookies
266 * @throws IOException
267 * in case of I/O error
269 public Map
<String
, String
> getCookies() throws IOException
{
270 return new HashMap
<String
, String
>();
274 * Return the canonical form of the main {@link URL}.
277 * the source {@link URL}
279 * @return the canonical form of this {@link URL}
281 * @throws IOException
282 * in case of I/O error
284 public URL
getCanonicalUrl(URL source
) throws IOException
{
289 * Process the given story resource into a partially filled {@link Story}
290 * object containing the name and metadata, except for the description.
295 * @return the {@link Story}
297 * @throws IOException
298 * in case of I/O error
300 public Story
processMeta(URL url
) throws IOException
{
301 return processMeta(url
, true, false);
305 * Process the given story resource into a partially filled {@link Story}
306 * object containing the name and metadata.
312 * close "this" and "in" when done
314 * @return the {@link Story}
316 * @throws IOException
317 * in case of I/O error
319 protected Story
processMeta(URL url
, boolean close
, boolean getDesc
)
323 url
= getCanonicalUrl(url
);
325 setCurrentReferer(url
);
333 preprocess(url
, getInput());
335 Story story
= new Story();
336 MetaData meta
= getMeta(url
, getInput());
337 if (meta
.getCreationDate() == null
338 || meta
.getCreationDate().isEmpty()) {
339 meta
.setCreationDate(StringUtils
.fromTime(new Date().getTime()));
343 if (meta
!= null && meta
.getCover() == null) {
344 meta
.setCover(getDefaultCover(meta
.getSubject()));
348 String descChapterName
= Instance
.getTrans().getString(
349 StringId
.DESCRIPTION
);
350 story
.getMeta().setResume(
351 makeChapter(url
, 0, descChapterName
,
352 getDesc(url
, getInput())));
360 } catch (IOException e
) {
369 setCurrentReferer(null);
374 * Process the given story resource into a fully filled {@link Story}
380 * the optional progress reporter
382 * @return the {@link Story}
384 * @throws IOException
385 * in case of I/O error
387 public Story
process(URL url
, Progress pg
) throws IOException
{
391 pg
.setMinMax(0, 100);
394 url
= getCanonicalUrl(url
);
397 Story story
= processMeta(url
, false, true);
404 pg
.setName("Retrieving " + story
.getMeta().getTitle());
406 setCurrentReferer(url
);
408 story
.setChapters(new ArrayList
<Chapter
>());
410 List
<Entry
<String
, URL
>> chapters
= getChapters(url
, getInput());
414 if (chapters
!= null) {
415 Progress pgChaps
= new Progress(0, chapters
.size());
416 pg
.addProgress(pgChaps
, 80);
419 for (Entry
<String
, URL
> chap
: chapters
) {
420 setCurrentReferer(chap
.getValue());
421 InputStream chapIn
= Instance
.getCache().open(
422 chap
.getValue(), this, true);
424 Chapter cc
= makeChapter(url
, i
, chap
.getKey(),
425 getChapterContent(url
, chapIn
, i
));
426 words
+= cc
.getWords();
427 story
.getChapters().add(cc
);
428 if (story
.getMeta() != null) {
429 story
.getMeta().setWords(words
);
435 pgChaps
.setProgress(i
++);
446 } catch (IOException e
) {
454 setCurrentReferer(null);
463 public SupportType
getType() {
468 * The current referer {@link URL} (only one 'r', as in 'HTML'...), i.e.,
469 * the current {@link URL} we work on.
471 * @return the referer
473 public URL
getCurrentReferer() {
474 return currentReferer
;
478 * The current referer {@link URL} (only one 'r', as in 'HTML'...), i.e.,
479 * the current {@link URL} we work on.
481 * @param currentReferer
484 protected void setCurrentReferer(URL currentReferer
) {
485 this.currentReferer
= currentReferer
;
496 protected BasicSupport
setType(SupportType type
) {
502 * Prepare the support if needed before processing.
505 * the source of the story
507 * the input (the main resource)
509 * @throws IOException
512 protected void preprocess(URL source
, InputStream in
) throws IOException
{
516 * Now that we have processed the {@link Story}, close the resources if any.
518 * @throws IOException
521 protected void close() throws IOException
{
525 * Create a {@link Chapter} object from the given information, formatting
526 * the content as it should be.
533 * the chapter content
535 * @return the {@link Chapter}
537 * @throws IOException
538 * in case of I/O error
540 protected Chapter
makeChapter(URL source
, int number
, String name
,
541 String content
) throws IOException
{
542 // Chapter name: process it correctly, then remove the possible
543 // redundant "Chapter x: " in front of it
544 String chapterName
= processPara(name
).getContent().trim();
545 for (String lang
: Instance
.getConfig().getString(Config
.CHAPTER
)
547 String chapterWord
= Instance
.getConfig().getStringX(
548 Config
.CHAPTER
, lang
);
549 if (chapterName
.startsWith(chapterWord
)) {
550 chapterName
= chapterName
.substring(chapterWord
.length())
556 if (chapterName
.startsWith(Integer
.toString(number
))) {
557 chapterName
= chapterName
.substring(
558 Integer
.toString(number
).length()).trim();
561 if (chapterName
.startsWith(":")) {
562 chapterName
= chapterName
.substring(1).trim();
566 Chapter chap
= new Chapter(number
, chapterName
);
568 if (content
!= null) {
569 List
<Paragraph
> paras
= makeParagraphs(source
, content
);
571 for (Paragraph para
: paras
) {
572 words
+= para
.getWords();
574 chap
.setParagraphs(paras
);
575 chap
.setWords(words
);
583 * Convert the given content into {@link Paragraph}s.
586 * the source URL of the story
588 * the textual content
590 * @return the {@link Paragraph}s
592 * @throws IOException
593 * in case of I/O error
595 protected List
<Paragraph
> makeParagraphs(URL source
, String content
)
598 // Special <HR> processing:
599 content
= content
.replaceAll("(<hr [^>]*>)|(<hr/>)|(<hr>)",
603 List
<Paragraph
> paras
= new ArrayList
<Paragraph
>();
605 if (content
!= null && !content
.trim().isEmpty()) {
607 for (String line
: content
.split("(<p>|</p>|<br>|<br/>)")) {
608 paras
.add(makeParagraph(source
, line
.trim()));
611 BufferedReader buff
= null;
613 buff
= new BufferedReader(
614 new InputStreamReader(new ByteArrayInputStream(
615 content
.getBytes("UTF-8")), "UTF-8"));
616 for (String line
= buff
.readLine(); line
!= null; line
= buff
618 paras
.add(makeParagraph(source
, line
.trim()));
627 // Check quotes for "bad" format
628 List
<Paragraph
> newParas
= new ArrayList
<Paragraph
>();
629 for (Paragraph para
: paras
) {
630 newParas
.addAll(requotify(para
));
634 // Remove double blanks/brks
635 fixBlanksBreaks(paras
);
642 * Convert the given line into a single {@link Paragraph}.
645 * the source URL of the story
647 * the textual content of the paragraph
649 * @return the {@link Paragraph}
651 private Paragraph
makeParagraph(URL source
, String line
) {
653 if (line
.startsWith("[") && line
.endsWith("]")) {
654 image
= getImageUrl(this, source
,
655 line
.substring(1, line
.length() - 1).trim());
659 return new Paragraph(image
);
661 return processPara(line
);
666 * Fix the {@link ParagraphType#BLANK}s and {@link ParagraphType#BREAK}s of
667 * those {@link Paragraph}s.
669 * The resulting list will not contain a starting or trailing blank/break
670 * nor 2 blanks or breaks following each other.
673 * the list of {@link Paragraph}s to fix
675 protected void fixBlanksBreaks(List
<Paragraph
> paras
) {
676 boolean space
= false;
678 for (int i
= 0; i
< paras
.size(); i
++) {
679 Paragraph para
= paras
.get(i
);
680 boolean thisSpace
= para
.getType() == ParagraphType
.BLANK
;
681 boolean thisBrk
= para
.getType() == ParagraphType
.BREAK
;
683 if (i
> 0 && space
&& thisBrk
) {
686 } else if ((space
|| brk
) && (thisSpace
|| thisBrk
)) {
695 // Remove blank/brk at start
697 && (paras
.get(0).getType() == ParagraphType
.BLANK
|| paras
.get(
698 0).getType() == ParagraphType
.BREAK
)) {
702 // Remove blank/brk at end
703 int last
= paras
.size() - 1;
705 && (paras
.get(last
).getType() == ParagraphType
.BLANK
|| paras
706 .get(last
).getType() == ParagraphType
.BREAK
)) {
712 * Get the default cover related to this subject (see <tt>.info</tt> files).
717 * @return the cover if any, or NULL
719 static BufferedImage
getDefaultCover(String subject
) {
720 if (subject
!= null && !subject
.isEmpty()
721 && Instance
.getCoverDir() != null) {
723 File fileCover
= new File(Instance
.getCoverDir(), subject
);
724 return getImage(null, fileCover
.toURI().toURL(), subject
);
725 } catch (MalformedURLException e
) {
733 * Return the list of supported image extensions.
735 * @param emptyAllowed
736 * TRUE to allow an empty extension on first place, which can be
737 * used when you may already have an extension in your input but
738 * are not sure about it
740 * @return the extensions
742 static String
[] getImageExt(boolean emptyAllowed
) {
744 return new String
[] { "", ".png", ".jpg", ".jpeg", ".gif", ".bmp" };
746 return new String
[] { ".png", ".jpg", ".jpeg", ".gif", ".bmp" };
751 * Check if the given resource can be a local image or a remote image, then
752 * refresh the cache with it if it is.
757 * the resource to check
759 * @return the image if found, or NULL
762 static BufferedImage
getImage(BasicSupport support
, URL source
, String line
) {
763 URL url
= getImageUrl(support
, source
, line
);
765 InputStream in
= null;
767 in
= Instance
.getCache().open(url
, getSupport(url
), true);
768 return IOUtils
.toImage(in
);
769 } catch (IOException e
) {
774 } catch (IOException e
) {
784 * Check if the given resource can be a local image or a remote image, then
785 * refresh the cache with it if it is.
790 * the resource to check
792 * @return the image URL if found, or NULL
795 static URL
getImageUrl(BasicSupport support
, URL source
, String line
) {
801 if (source
!= null) {
802 path
= new File(source
.getFile()).getParent();
804 String basePath
= new File(new File(path
), line
.trim())
806 for (String ext
: getImageExt(true)) {
807 if (new File(basePath
+ ext
).exists()) {
808 url
= new File(basePath
+ ext
).toURI().toURL();
811 } catch (Exception e
) {
812 // Nothing to do here
819 for (String ext
: getImageExt(true)) {
820 if (Instance
.getCache().check(new URL(line
+ ext
))) {
821 url
= new URL(line
+ ext
);
828 for (String ext
: getImageExt(true)) {
830 url
= new URL(line
+ ext
);
831 Instance
.getCache().refresh(url
, support
, true);
833 } catch (IOException e
) {
834 // no image with this ext
839 } catch (MalformedURLException e
) {
844 // refresh the cached file
847 Instance
.getCache().refresh(url
, support
, true);
848 } catch (IOException e
) {
849 // woops, broken image
859 * Open the input file that will be used through the support.
862 * the source {@link URL}
864 * @return the {@link InputStream}
866 * @throws IOException
867 * in case of I/O error
869 protected InputStream
openInput(URL source
) throws IOException
{
870 return Instance
.getCache().open(source
, this, false);
874 * Reset the given {@link InputStream} and return it.
877 * the {@link InputStream} to reset
879 * @return the same {@link InputStream} after reset
881 protected InputStream
reset(InputStream in
) {
884 } catch (IOException e
) {
890 * Reset then return {@link BasicSupport#in}.
892 * @return {@link BasicSupport#in}
894 protected InputStream
getInput() {
899 * Fix the author name if it is prefixed with some "by" {@link String}.
902 * the author with a possible prefix
904 * @return the author without prefixes
906 protected String
fixAuthor(String author
) {
907 if (author
!= null) {
908 for (String suffix
: new String
[] { " ", ":" }) {
909 for (String byString
: Instance
.getConfig()
910 .getString(Config
.BYS
).split(",")) {
912 if (author
.toUpperCase().startsWith(byString
.toUpperCase())) {
913 author
= author
.substring(byString
.length()).trim();
918 // Special case (without suffix):
919 if (author
.startsWith("©")) {
920 author
= author
.substring(1);
928 * Check quotes for bad format (i.e., quotes with normal paragraphs inside)
929 * and requotify them (i.e., separate them into QUOTE paragraphs and other
930 * paragraphs (quotes or not)).
933 * the paragraph to requotify (not necessarily a quote)
935 * @return the correctly (or so we hope) quotified paragraphs
937 protected List
<Paragraph
> requotify(Paragraph para
) {
938 List
<Paragraph
> newParas
= new ArrayList
<Paragraph
>();
940 if (para
.getType() == ParagraphType
.QUOTE
941 && para
.getContent().length() > 2) {
942 String line
= para
.getContent();
943 boolean singleQ
= line
.startsWith("" + openQuote
);
944 boolean doubleQ
= line
.startsWith("" + openDoubleQuote
);
946 // Do not try when more than one quote at a time
947 // (some stories are not easily readable if we do)
949 && line
.indexOf(closeQuote
, 1) < line
950 .lastIndexOf(closeQuote
)) {
955 && line
.indexOf(closeDoubleQuote
, 1) < line
956 .lastIndexOf(closeDoubleQuote
)) {
962 if (!singleQ
&& !doubleQ
) {
963 line
= openDoubleQuote
+ line
+ closeDoubleQuote
;
964 newParas
.add(new Paragraph(ParagraphType
.QUOTE
, line
, para
967 char open
= singleQ ? openQuote
: openDoubleQuote
;
968 char close
= singleQ ? closeQuote
: closeDoubleQuote
;
971 boolean inQuote
= false;
973 for (char car
: line
.toCharArray()) {
976 } else if (car
== close
) {
978 } else if (car
== '.' && !inQuote
) {
986 String rest
= line
.substring(posDot
+ 1).trim();
987 line
= line
.substring(0, posDot
+ 1).trim();
989 for (char car
: line
.toCharArray()) {
994 newParas
.add(new Paragraph(ParagraphType
.QUOTE
, line
, words
));
995 if (!rest
.isEmpty()) {
996 newParas
.addAll(requotify(processPara(rest
)));
1010 * Process a {@link Paragraph} from a raw line of text.
1012 * Will also fix quotes and HTML encoding if needed.
1017 * @return the processed {@link Paragraph}
1019 protected Paragraph
processPara(String line
) {
1020 line
= ifUnhtml(line
).trim();
1022 boolean space
= true;
1024 boolean quote
= false;
1025 boolean tentativeCloseQuote
= false;
1030 StringBuilder builder
= new StringBuilder();
1031 for (char car
: line
.toCharArray()) {
1033 if (dashCount
> 0) {
1034 // dash, ndash and mdash: - – —
1035 // currently: always use mdash
1036 builder
.append(dashCount
== 1 ?
'-' : '—');
1041 if (tentativeCloseQuote
) {
1042 tentativeCloseQuote
= false;
1043 if (Character
.isLetterOrDigit(car
)) {
1044 builder
.append("'");
1046 // handle double-single quotes as double quotes
1048 builder
.append(closeDoubleQuote
);
1051 builder
.append(closeQuote
);
1057 case ' ': // note: unbreakable space
1060 case '\n': // just in case
1061 case '\r': // just in case
1062 if (builder
.length() > 0
1063 && builder
.charAt(builder
.length() - 1) != ' ') {
1066 builder
.append(' ');
1070 if (space
|| (brk
&& quote
)) {
1072 // handle double-single quotes as double quotes
1074 builder
.deleteCharAt(builder
.length() - 1);
1075 builder
.append(openDoubleQuote
);
1077 builder
.append(openQuote
);
1079 } else if (prev
== ' ' || prev
== car
) {
1080 // handle double-single quotes as double quotes
1082 builder
.deleteCharAt(builder
.length() - 1);
1083 builder
.append(openDoubleQuote
);
1085 builder
.append(openQuote
);
1088 // it is a quote ("I'm off") or a 'quote' ("This
1089 // 'good' restaurant"...)
1090 tentativeCloseQuote
= true;
1095 if (space
|| (brk
&& quote
)) {
1097 builder
.append(openDoubleQuote
);
1098 } else if (prev
== ' ') {
1099 builder
.append(openDoubleQuote
);
1101 builder
.append(closeDoubleQuote
);
1126 builder
.append(car
);
1135 if (space
|| (brk
&& quote
)) {
1137 builder
.append(openQuote
);
1139 // handle double-single quotes as double quotes
1141 builder
.deleteCharAt(builder
.length() - 1);
1142 builder
.append(openDoubleQuote
);
1144 builder
.append(openQuote
);
1158 // handle double-single quotes as double quotes
1160 builder
.deleteCharAt(builder
.length() - 1);
1161 builder
.append(closeDoubleQuote
);
1163 builder
.append(closeQuote
);
1172 if (space
|| (brk
&& quote
)) {
1174 builder
.append(openDoubleQuote
);
1176 builder
.append(openDoubleQuote
);
1189 builder
.append(closeDoubleQuote
);
1195 builder
.append(car
);
1202 if (tentativeCloseQuote
) {
1203 tentativeCloseQuote
= false;
1204 builder
.append(closeQuote
);
1207 line
= builder
.toString().trim();
1209 ParagraphType type
= ParagraphType
.NORMAL
;
1211 type
= ParagraphType
.BLANK
;
1213 type
= ParagraphType
.BREAK
;
1215 type
= ParagraphType
.QUOTE
;
1218 return new Paragraph(type
, line
, words
);
1222 * Remove the HTML from the input <b>if</b> {@link BasicSupport#isHtml()} is
1228 * @return the no html version if needed
1230 private String
ifUnhtml(String input
) {
1231 if (isHtml() && input
!= null) {
1232 return StringUtils
.unhtml(input
);
1239 * Return a {@link BasicSupport} implementation supporting the given
1240 * resource if possible.
1243 * the story resource
1245 * @return an implementation that supports it, or NULL
1247 public static BasicSupport
getSupport(URL url
) {
1252 // TEXT and INFO_TEXT always support files (not URLs though)
1253 for (SupportType type
: SupportType
.values()) {
1254 if (type
!= SupportType
.TEXT
&& type
!= SupportType
.INFO_TEXT
) {
1255 BasicSupport support
= getSupport(type
);
1256 if (support
!= null && support
.supports(url
)) {
1262 for (SupportType type
: new SupportType
[] { SupportType
.INFO_TEXT
,
1263 SupportType
.TEXT
}) {
1264 BasicSupport support
= getSupport(type
);
1265 if (support
!= null && support
.supports(url
)) {
1274 * Return a {@link BasicSupport} implementation supporting the given type.
1279 * @return an implementation that supports it, or NULL
1281 public static BasicSupport
getSupport(SupportType type
) {
1284 return new Epub().setType(type
);
1286 return new InfoText().setType(type
);
1288 return new Fimfiction().setType(type
);
1290 return new Fanfiction().setType(type
);
1292 return new Text().setType(type
);
1294 return new MangaFox().setType(type
);
1296 return new E621().setType(type
);
1298 return new YiffStar().setType(type
);
1300 return new Cbz().setType(type
);
1302 return new Html().setType(type
);
1309 * Return the first line from the given input which correspond to the given
1315 * a string that must be found inside the target line (also
1316 * supports "^" at start to say "only if it starts with" the
1318 * @param relativeLine
1319 * the line to return based upon the target line position (-1 =
1320 * the line before, 0 = the target line...)
1324 static String
getLine(InputStream in
, String needle
, int relativeLine
) {
1325 return getLine(in
, needle
, relativeLine
, true);
1329 * Return a line from the given input which correspond to the given
1335 * a string that must be found inside the target line (also
1336 * supports "^" at start to say "only if it starts with" the
1338 * @param relativeLine
1339 * the line to return based upon the target line position (-1 =
1340 * the line before, 0 = the target line...)
1342 * takes the first result (as opposed to the last one, which will
1343 * also always spend the input)
1347 static String
getLine(InputStream in
, String needle
, int relativeLine
,
1353 } catch (IOException e
) {
1357 List
<String
> lines
= new ArrayList
<String
>();
1358 @SuppressWarnings("resource")
1359 Scanner scan
= new Scanner(in
, "UTF-8");
1361 scan
.useDelimiter("\\n");
1362 while (scan
.hasNext()) {
1363 lines
.add(scan
.next());
1366 if (needle
.startsWith("^")) {
1367 if (lines
.get(lines
.size() - 1).startsWith(
1368 needle
.substring(1))) {
1369 index
= lines
.size() - 1;
1373 if (lines
.get(lines
.size() - 1).contains(needle
)) {
1374 index
= lines
.size() - 1;
1379 if (index
>= 0 && index
+ relativeLine
< lines
.size()) {
1380 rep
= lines
.get(index
+ relativeLine
);