1 package be
.nikiroo
.fanfix
.supported
;
3 import java
.io
.ByteArrayInputStream
;
5 import java
.io
.IOException
;
6 import java
.io
.InputStream
;
7 import java
.net
.MalformedURLException
;
9 import java
.nio
.charset
.StandardCharsets
;
10 import java
.util
.ArrayList
;
11 import java
.util
.HashMap
;
12 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
.Story
;
24 import be
.nikiroo
.fanfix
.data
.Paragraph
.ParagraphType
;
25 import be
.nikiroo
.utils
.StringUtils
;
28 * This class is the base class used by the other support classes. It can be
29 * used outside of this package, and have static method that you can use to get
30 * access to the correct support class.
32 * It will be used with 'resources' (usually web pages or files).
36 public abstract class BasicSupport
{
38 * The supported input types for which we can get a {@link BasicSupport}
43 public enum SupportType
{
44 /** EPUB files created with this program */
46 /** Pure text file with some rules */
48 /** TEXT but with associated .info file */
50 /** My Little Pony fanfictions */
52 /** Fanfictions from a lot of different universes */
54 /** Website with lots of Mangas */
56 /** Furry website with comics support */
62 * A description of this support type (more information than the
63 * {@link BasicSupport#getSourceName()}).
65 * @return the description
67 public String
getDesc() {
68 String desc
= Instance
.getTrans().getStringX(StringId
.INPUT_DESC
,
72 desc
= Instance
.getTrans().getString(StringId
.INPUT_DESC
, this);
79 * The name of this support type (a short version).
83 public String
getSourceName() {
84 BasicSupport support
= BasicSupport
.getSupport(this);
85 if (support
!= null) {
86 return support
.getSourceName();
93 public String
toString() {
94 return super.toString().toLowerCase();
98 * Call {@link SupportType#valueOf(String.toUpperCase())}.
101 * the possible type name
103 * @return NULL or the type
105 public static SupportType
valueOfUC(String typeName
) {
106 return SupportType
.valueOf(typeName
== null ?
null : typeName
111 * Call {@link SupportType#valueOf(String.toUpperCase())} but return
112 * NULL for NULL instead of raising exception.
115 * the possible type name
117 * @return NULL or the type
119 public static SupportType
valueOfNullOkUC(String typeName
) {
120 if (typeName
== null) {
124 return SupportType
.valueOfUC(typeName
);
128 * Call {@link SupportType#valueOf(String.toUpperCase())} but return
129 * NULL in case of error instead of raising an exception.
132 * the possible type name
134 * @return NULL or the type
136 public static SupportType
valueOfAllOkUC(String typeName
) {
138 return SupportType
.valueOfUC(typeName
);
139 } catch (Exception e
) {
145 /** Only used by {@link BasicSupport#getInput()} just so it is always reset. */
146 private InputStream in
;
147 private SupportType type
;
148 private URL currentReferer
; // with on 'r', as in 'HTTP'...
151 private char openQuote
= Instance
.getTrans().getChar(
152 StringId
.OPEN_SINGLE_QUOTE
);
153 private char closeQuote
= Instance
.getTrans().getChar(
154 StringId
.CLOSE_SINGLE_QUOTE
);
155 private char openDoubleQuote
= Instance
.getTrans().getChar(
156 StringId
.OPEN_DOUBLE_QUOTE
);
157 private char closeDoubleQuote
= Instance
.getTrans().getChar(
158 StringId
.CLOSE_DOUBLE_QUOTE
);
161 * The name of this support class.
165 protected abstract String
getSourceName();
168 * Check if the given resource is supported by this {@link BasicSupport}.
171 * the resource to check for
173 * @return TRUE if it is
175 protected abstract boolean supports(URL url
);
178 * Return TRUE if the support will return HTML encoded content values for
179 * the chapters content.
181 * @return TRUE for HTML
183 protected abstract boolean isHtml();
186 * Return the story title.
189 * the source of the story
191 * the input (the main resource)
195 * @throws IOException
196 * in case of I/O error
198 protected abstract String
getTitle(URL source
, InputStream in
)
202 * Return the story author.
205 * the source of the story
207 * the input (the main resource)
211 * @throws IOException
212 * in case of I/O error
214 protected abstract String
getAuthor(URL source
, InputStream in
)
218 * Return the story publication date.
221 * the source of the story
223 * the input (the main resource)
227 * @throws IOException
228 * in case of I/O error
230 protected abstract String
getDate(URL source
, InputStream in
)
234 * Return the subject of the story (for instance, if it is a fanfiction,
235 * what is the original work; if it is a technical text, what is the
236 * technical subject...).
239 * the source of the story
241 * the input (the main resource)
243 * @return the subject
245 * @throws IOException
246 * in case of I/O error
248 protected abstract String
getSubject(URL source
, InputStream in
)
252 * Return the story description.
255 * the source of the story
257 * the input (the main resource)
259 * @return the description
261 * @throws IOException
262 * in case of I/O error
264 protected abstract String
getDesc(URL source
, InputStream in
)
268 * Return the story cover resource if any, or NULL if none.
270 * The default cover should not be checked for here.
273 * the source of the story
275 * the input (the main resource)
277 * @return the cover or NULL
279 * @throws IOException
280 * in case of I/O error
282 protected abstract URL
getCover(URL source
, InputStream in
)
286 * Return the list of chapters (name and resource).
289 * the source of the story
291 * the input (the main resource)
293 * @return the chapters
295 * @throws IOException
296 * in case of I/O error
298 protected abstract List
<Entry
<String
, URL
>> getChapters(URL source
,
299 InputStream in
) throws IOException
;
302 * Return the content of the chapter (possibly HTML encoded, if
303 * {@link BasicSupport#isHtml()} is TRUE).
306 * the source of the story
308 * the input (the main resource)
312 * @return the content
314 * @throws IOException
315 * in case of I/O error
317 protected abstract String
getChapterContent(URL source
, InputStream in
,
318 int number
) throws IOException
;
321 * Check if this {@link BasicSupport} is mainly catered to image files.
323 * @return TRUE if it is
325 public boolean isImageDocument(URL source
, InputStream in
)
331 * Return the list of cookies (values included) that must be used to
332 * correctly fetch the resources.
334 * You are expected to call the super method implementation if you override
337 * @return the cookies
339 public Map
<String
, String
> getCookies() {
340 return new HashMap
<String
, String
>();
344 * Process the given story resource into a partially filled {@link Story}
345 * object containing the name and metadata, except for the description.
350 * @return the {@link Story}
352 * @throws IOException
353 * in case of I/O error
355 public Story
processMeta(URL url
) throws IOException
{
356 return processMeta(url
, true, false);
360 * Process the given story resource into a partially filled {@link Story}
361 * object containing the name and metadata.
367 * close "this" and "in" when done
369 * @return the {@link Story}
371 * @throws IOException
372 * in case of I/O error
374 protected Story
processMeta(URL url
, boolean close
, boolean getDesc
)
376 in
= Instance
.getCache().open(url
, this, false);
382 preprocess(getInput());
384 Story story
= new Story();
385 story
.setMeta(new MetaData());
386 story
.getMeta().setTitle(ifUnhtml(getTitle(url
, getInput())));
387 story
.getMeta().setAuthor(
388 fixAuthor(ifUnhtml(getAuthor(url
, getInput()))));
389 story
.getMeta().setDate(ifUnhtml(getDate(url
, getInput())));
390 story
.getMeta().setTags(getTags(url
, getInput()));
391 story
.getMeta().setSource(getSourceName());
392 story
.getMeta().setPublisher(
393 ifUnhtml(getPublisher(url
, getInput())));
394 story
.getMeta().setUuid(getUuid(url
, getInput()));
395 story
.getMeta().setLuid(getLuid(url
, getInput()));
396 story
.getMeta().setLang(getLang(url
, getInput()));
397 story
.getMeta().setSubject(ifUnhtml(getSubject(url
, getInput())));
398 story
.getMeta().setImageDocument(isImageDocument(url
, getInput()));
401 String descChapterName
= Instance
.getTrans().getString(
402 StringId
.DESCRIPTION
);
403 story
.getMeta().setResume(
404 makeChapter(url
, 0, descChapterName
,
405 getDesc(url
, getInput())));
413 } catch (IOException e
) {
425 * Process the given story resource into a fully filled {@link Story}
431 * @return the {@link Story}
433 * @throws IOException
434 * in case of I/O error
436 public Story
process(URL url
) throws IOException
{
437 setCurrentReferer(url
);
440 Story story
= processMeta(url
, false, true);
445 story
.setChapters(new ArrayList
<Chapter
>());
447 URL cover
= getCover(url
, getInput());
449 String subject
= story
.getMeta() == null ?
null : story
450 .getMeta().getSubject();
451 if (subject
!= null && !subject
.isEmpty()
452 && Instance
.getCoverDir() != null) {
453 File fileCover
= new File(Instance
.getCoverDir(), subject
);
454 cover
= getImage(fileCover
.toURI().toURL(), subject
);
459 InputStream coverIn
= null;
461 coverIn
= Instance
.getCache().open(cover
, this, true);
462 story
.getMeta().setCover(StringUtils
.toImage(coverIn
));
463 } catch (IOException e
) {
464 Instance
.syserr(new IOException(Instance
.getTrans()
465 .getString(StringId
.ERR_BS_NO_COVER
, cover
), e
));
472 List
<Entry
<String
, URL
>> chapters
= getChapters(url
, getInput());
474 if (chapters
!= null) {
475 for (Entry
<String
, URL
> chap
: chapters
) {
476 setCurrentReferer(chap
.getValue());
477 InputStream chapIn
= Instance
.getCache().open(
478 chap
.getValue(), this, true);
480 story
.getChapters().add(
481 makeChapter(url
, i
, chap
.getKey(),
482 getChapterContent(url
, chapIn
, i
)));
495 } catch (IOException e
) {
503 currentReferer
= null;
512 public SupportType
getType() {
517 * The current referer {@link URL} (only one 'r', as in 'HTML'...), i.e.,
518 * the current {@link URL} we work on.
520 * @return the referer
522 public URL
getCurrentReferer() {
523 return currentReferer
;
527 * The current referer {@link URL} (only one 'r', as in 'HTML'...), i.e.,
528 * the current {@link URL} we work on.
530 * @param currentReferer
533 protected void setCurrentReferer(URL currentReferer
) {
534 this.currentReferer
= currentReferer
;
545 protected BasicSupport
setType(SupportType type
) {
551 * Return the story publisher (by default,
552 * {@link BasicSupport#getSourceName()}).
555 * the source of the story
557 * the input (the main resource)
559 * @return the publisher
561 * @throws IOException
562 * in case of I/O error
564 protected String
getPublisher(URL source
, InputStream in
)
566 return getSourceName();
570 * Return the story UUID, a unique value representing the story (it is often
573 * By default, this is the {@link URL} of the resource.
576 * the source of the story
578 * the input (the main resource)
582 * @throws IOException
583 * in case of I/O error
585 protected String
getUuid(URL source
, InputStream in
) throws IOException
{
586 return source
.toString();
590 * Return the story Library UID, a unique value representing the story (it
591 * is often a number) in the local library.
593 * By default, this is empty.
596 * the source of the story
598 * the input (the main resource)
602 * @throws IOException
603 * in case of I/O error
605 protected String
getLuid(URL source
, InputStream in
) throws IOException
{
610 * Return the 2-letter language code of this story.
612 * By default, this is 'EN'.
615 * the source of the story
617 * the input (the main resource)
619 * @return the language
621 * @throws IOException
622 * in case of I/O error
624 protected String
getLang(URL source
, InputStream in
) throws IOException
{
629 * Return the list of tags for this story.
632 * the source of the story
634 * the input (the main resource)
638 * @throws IOException
639 * in case of I/O error
641 protected List
<String
> getTags(URL source
, InputStream in
)
643 return new ArrayList
<String
>();
647 * Return the first line from the given input which correspond to the given
650 * Do not reset the input, which will be pointing at the line just after the
651 * result (input will be spent if no result is found).
656 * a string that must be found inside the target line (also
657 * supports "^" at start to say "only if it starts with" the
659 * @param relativeLine
660 * the line to return based upon the target line position (-1 =
661 * the line before, 0 = the target line...)
665 protected String
getLine(InputStream in
, String needle
, int relativeLine
) {
666 return getLine(in
, needle
, relativeLine
, true);
670 * Return a line from the given input which correspond to the given
673 * Do not reset the input, which will be pointing at the line just after the
674 * result (input will be spent if no result is found) when first is TRUE,
675 * and will always be spent if first is FALSE.
680 * a string that must be found inside the target line (also
681 * supports "^" at start to say "only if it starts with" the
683 * @param relativeLine
684 * the line to return based upon the target line position (-1 =
685 * the line before, 0 = the target line...)
687 * takes the first result (as opposed to the last one, which will
688 * also always spend the input)
692 protected String
getLine(InputStream in
, String needle
, int relativeLine
,
696 List
<String
> lines
= new ArrayList
<String
>();
697 @SuppressWarnings("resource")
698 Scanner scan
= new Scanner(in
, "UTF-8");
700 scan
.useDelimiter("\\n");
701 while (scan
.hasNext()) {
702 lines
.add(scan
.next());
705 if (needle
.startsWith("^")) {
706 if (lines
.get(lines
.size() - 1).startsWith(
707 needle
.substring(1))) {
708 index
= lines
.size() - 1;
712 if (lines
.get(lines
.size() - 1).contains(needle
)) {
713 index
= lines
.size() - 1;
718 if (index
>= 0 && index
+ relativeLine
< lines
.size()) {
719 rep
= lines
.get(index
+ relativeLine
);
730 * Prepare the support if needed before processing.
732 * @throws IOException
735 protected void preprocess(InputStream in
) throws IOException
{
739 * Now that we have processed the {@link Story}, close the resources if any.
741 * @throws IOException
744 protected void close() throws IOException
{
748 * Create a {@link Chapter} object from the given information, formatting
749 * the content as it should be.
756 * the chapter content
758 * @return the {@link Chapter}
760 * @throws IOException
761 * in case of I/O error
763 protected Chapter
makeChapter(URL source
, int number
, String name
,
764 String content
) throws IOException
{
766 // Chapter name: process it correctly, then remove the possible
767 // redundant "Chapter x: " in front of it
768 String chapterName
= processPara(name
).getContent().trim();
769 for (String lang
: Instance
.getConfig().getString(Config
.CHAPTER
)
771 String chapterWord
= Instance
.getConfig().getStringX(
772 Config
.CHAPTER
, lang
);
773 if (chapterName
.startsWith(chapterWord
)) {
774 chapterName
= chapterName
.substring(chapterWord
.length())
780 if (chapterName
.startsWith(Integer
.toString(number
))) {
781 chapterName
= chapterName
.substring(
782 Integer
.toString(number
).length()).trim();
785 if (chapterName
.startsWith(":")) {
786 chapterName
= chapterName
.substring(1).trim();
790 Chapter chap
= new Chapter(number
, chapterName
);
792 if (content
== null) {
797 // Special <HR> processing:
798 content
= content
.replaceAll("(<hr [^>]*>)|(<hr/>)|(<hr>)",
802 InputStream in
= new ByteArrayInputStream(
803 content
.getBytes(StandardCharsets
.UTF_8
));
805 @SuppressWarnings("resource")
806 Scanner scan
= new Scanner(in
, "UTF-8");
807 scan
.useDelimiter("(\\n|</p>)"); // \n for test, </p> for html
809 List
<Paragraph
> paras
= new ArrayList
<Paragraph
>();
810 while (scan
.hasNext()) {
811 String line
= scan
.next().trim();
812 boolean image
= false;
813 if (line
.startsWith("[") && line
.endsWith("]")) {
814 URL url
= getImage(source
,
815 line
.substring(1, line
.length() - 1).trim());
817 paras
.add(new Paragraph(url
));
823 paras
.add(processPara(line
));
827 // Check quotes for "bad" format
828 List
<Paragraph
> newParas
= new ArrayList
<Paragraph
>();
829 for (Paragraph para
: paras
) {
830 newParas
.addAll(requotify(para
));
834 // Remove double blanks/brks
835 boolean space
= false;
837 for (int i
= 0; i
< paras
.size(); i
++) {
838 Paragraph para
= paras
.get(i
);
839 boolean thisSpace
= para
.getType() == ParagraphType
.BLANK
;
840 boolean thisBrk
= para
.getType() == ParagraphType
.BREAK
;
842 if (space
&& thisBrk
) {
845 } else if ((space
|| brk
) && (thisSpace
|| thisBrk
)) {
854 // Remove blank/brk at start
856 && (paras
.get(0).getType() == ParagraphType
.BLANK
|| paras
857 .get(0).getType() == ParagraphType
.BREAK
)) {
861 // Remove blank/brk at end
862 int last
= paras
.size() - 1;
864 && (paras
.get(last
).getType() == ParagraphType
.BLANK
|| paras
865 .get(last
).getType() == ParagraphType
.BREAK
)) {
869 chap
.setParagraphs(paras
);
878 * Return the list of supported image extensions.
880 * @return the extensions
882 protected String
[] getImageExt(boolean emptyAllowed
) {
884 return new String
[] { "", ".png", ".jpg", ".jpeg", ".gif", ".bmp" };
886 return new String
[] { ".png", ".jpg", ".jpeg", ".gif", ".bmp" };
891 * Check if the given resource can be a local image or a remote image, then
892 * refresh the cache with it if it is.
897 * the resource to check
899 * @return the image URL if found, or NULL
902 protected URL
getImage(URL source
, String line
) {
903 String path
= new File(source
.getFile()).getParent();
908 String urlBase
= new File(new File(path
), line
.trim()).toURI()
910 for (String ext
: getImageExt(true)) {
911 if (new File(urlBase
+ ext
).exists()) {
912 url
= new File(urlBase
+ ext
).toURI().toURL();
915 } catch (Exception e
) {
916 // Nothing to do here
922 for (String ext
: getImageExt(true)) {
923 if (Instance
.getCache().check(new URL(line
+ ext
))) {
924 url
= new URL(line
+ ext
);
930 for (String ext
: getImageExt(true)) {
932 url
= new URL(line
+ ext
);
933 Instance
.getCache().refresh(url
, this, true);
935 } catch (IOException e
) {
936 // no image with this ext
941 } catch (MalformedURLException e
) {
946 // refresh the cached file
949 Instance
.getCache().refresh(url
, this, true);
950 } catch (IOException e
) {
951 // woops, broken image
960 * Reset then return {@link BasicSupport#in}.
962 * @return {@link BasicSupport#in}
964 * @throws IOException
965 * in case of I/O error
967 protected InputStream
getInput() throws IOException
{
973 * Fix the author name if it is prefixed with some "by" {@link String}.
976 * the author with a possible prefix
978 * @return the author without prefixes
980 private String
fixAuthor(String author
) {
981 if (author
!= null) {
982 for (String suffix
: new String
[] { " ", ":" }) {
983 for (String byString
: Instance
.getConfig()
984 .getString(Config
.BYS
).split(",")) {
986 if (author
.toUpperCase().startsWith(byString
.toUpperCase())) {
987 author
= author
.substring(byString
.length()).trim();
992 // Special case (without suffix):
993 if (author
.startsWith("©")) {
994 author
= author
.substring(1);
1002 * Check quotes for bad format (i.e., quotes with normal paragraphs inside)
1003 * and requotify them (i.e., separate them into QUOTE paragraphs and other
1004 * paragraphs (quotes or not)).
1007 * the paragraph to requotify (not necessaraly a quote)
1009 * @return the correctly (or so we hope) quotified paragraphs
1011 private List
<Paragraph
> requotify(Paragraph para
) {
1012 List
<Paragraph
> newParas
= new ArrayList
<Paragraph
>();
1014 if (para
.getType() == ParagraphType
.QUOTE
) {
1015 String line
= para
.getContent();
1016 boolean singleQ
= line
.startsWith("" + openQuote
);
1017 boolean doubleQ
= line
.startsWith("" + openDoubleQuote
);
1019 if (!singleQ
&& !doubleQ
) {
1020 line
= openDoubleQuote
+ line
+ closeDoubleQuote
;
1021 newParas
.add(new Paragraph(ParagraphType
.QUOTE
, line
));
1023 char close
= singleQ ? closeQuote
: closeDoubleQuote
;
1024 int posClose
= line
.indexOf(close
);
1025 int posDot
= line
.indexOf(".");
1026 while (posDot
>= 0 && posDot
< posClose
) {
1027 posDot
= line
.indexOf(".", posDot
+ 1);
1031 String rest
= line
.substring(posDot
+ 1).trim();
1032 line
= line
.substring(0, posDot
+ 1).trim();
1033 newParas
.add(new Paragraph(ParagraphType
.QUOTE
, line
));
1034 newParas
.addAll(requotify(processPara(rest
)));
1047 * Process a {@link Paragraph} from a raw line of text.
1049 * Will also fix quotes and HTML encoding if needed.
1054 * @return the processed {@link Paragraph}
1056 private Paragraph
processPara(String line
) {
1057 line
= ifUnhtml(line
).trim();
1059 boolean space
= true;
1061 boolean quote
= false;
1062 boolean tentativeCloseQuote
= false;
1066 StringBuilder builder
= new StringBuilder();
1067 for (char car
: line
.toCharArray()) {
1069 if (dashCount
> 0) {
1070 // dash, ndash and mdash: - – —
1071 // currently: always use mdash
1072 builder
.append(dashCount
== 1 ?
'-' : '—');
1077 if (tentativeCloseQuote
) {
1078 tentativeCloseQuote
= false;
1079 if ((car
>= 'a' && car
<= 'z') || (car
>= 'A' && car
<= 'Z')
1080 || (car
>= '0' && car
<= '9')) {
1081 builder
.append("'");
1083 builder
.append(closeQuote
);
1088 case ' ': // note: unbreakable space
1091 case '\n': // just in case
1092 case '\r': // just in case
1093 builder
.append(' ');
1097 if (space
|| (brk
&& quote
)) {
1099 builder
.append(openQuote
);
1100 } else if (prev
== ' ') {
1101 builder
.append(openQuote
);
1103 // it is a quote ("I'm off") or a 'quote' ("This
1104 // 'good' restaurant"...)
1105 tentativeCloseQuote
= true;
1110 if (space
|| (brk
&& quote
)) {
1112 builder
.append(openDoubleQuote
);
1113 } else if (prev
== ' ') {
1114 builder
.append(openDoubleQuote
);
1116 builder
.append(closeDoubleQuote
);
1141 builder
.append(car
);
1150 if (space
|| (brk
&& quote
)) {
1152 builder
.append(openQuote
);
1154 builder
.append(openQuote
);
1167 builder
.append(closeQuote
);
1175 if (space
|| (brk
&& quote
)) {
1177 builder
.append(openDoubleQuote
);
1179 builder
.append(openDoubleQuote
);
1192 builder
.append(closeDoubleQuote
);
1198 builder
.append(car
);
1205 if (tentativeCloseQuote
) {
1206 tentativeCloseQuote
= false;
1207 builder
.append(closeQuote
);
1210 line
= builder
.toString().trim();
1212 ParagraphType type
= ParagraphType
.NORMAL
;
1214 type
= ParagraphType
.BLANK
;
1216 type
= ParagraphType
.BREAK
;
1218 type
= ParagraphType
.QUOTE
;
1221 return new Paragraph(type
, line
);
1225 * Remove the HTML from the inpit <b>if</b> {@link BasicSupport#isHtml()} is
1231 * @return the no html version if needed
1233 private String
ifUnhtml(String input
) {
1234 if (isHtml() && input
!= null) {
1235 return StringUtils
.unhtml(input
);
1242 * Return a {@link BasicSupport} implementation supporting the given
1243 * resource if possible.
1246 * the story resource
1248 * @return an implementation that supports it, or NULL
1250 public static BasicSupport
getSupport(URL url
) {
1255 // TEXT and INFO_TEXT always support files (not URLs though)
1256 for (SupportType type
: SupportType
.values()) {
1257 if (type
!= SupportType
.TEXT
&& type
!= SupportType
.INFO_TEXT
) {
1258 BasicSupport support
= getSupport(type
);
1259 if (support
!= null && support
.supports(url
)) {
1265 for (SupportType type
: new SupportType
[] { SupportType
.TEXT
,
1266 SupportType
.INFO_TEXT
}) {
1267 BasicSupport support
= getSupport(type
);
1268 if (support
!= null && support
.supports(url
)) {
1277 * Return a {@link BasicSupport} implementation supporting the given type.
1282 * @return an implementation that supports it, or NULL
1284 public static BasicSupport
getSupport(SupportType type
) {
1287 return new Epub().setType(type
);
1289 return new InfoText().setType(type
);
1291 return new Fimfiction().setType(type
);
1293 return new Fanfiction().setType(type
);
1295 return new Text().setType(type
);
1297 return new MangaFox().setType(type
);
1299 return new E621().setType(type
);
1301 return new Cbz().setType(type
);