b9517284318e30169f571109fb4611d789fa006d
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
.util
.ArrayList
;
10 import java
.util
.HashMap
;
11 import java
.util
.List
;
13 import java
.util
.Map
.Entry
;
14 import java
.util
.Scanner
;
16 import be
.nikiroo
.fanfix
.Instance
;
17 import be
.nikiroo
.fanfix
.bundles
.Config
;
18 import be
.nikiroo
.fanfix
.bundles
.StringId
;
19 import be
.nikiroo
.fanfix
.data
.Chapter
;
20 import be
.nikiroo
.fanfix
.data
.MetaData
;
21 import be
.nikiroo
.fanfix
.data
.Paragraph
;
22 import be
.nikiroo
.fanfix
.data
.Paragraph
.ParagraphType
;
23 import be
.nikiroo
.fanfix
.data
.Story
;
24 import be
.nikiroo
.utils
.StringUtils
;
27 * This class is the base class used by the other support classes. It can be
28 * used outside of this package, and have static method that you can use to get
29 * access to the correct support class.
31 * It will be used with 'resources' (usually web pages or files).
35 public abstract class BasicSupport
{
37 * The supported input types for which we can get a {@link BasicSupport}
42 public enum SupportType
{
43 /** EPUB files created with this program */
45 /** Pure text file with some rules */
47 /** TEXT but with associated .info file */
49 /** My Little Pony fanfictions */
51 /** Fanfictions from a lot of different universes */
53 /** Website with lots of Mangas */
55 /** Furry website with comics support */
61 * A description of this support type (more information than the
62 * {@link BasicSupport#getSourceName()}).
64 * @return the description
66 public String
getDesc() {
67 String desc
= Instance
.getTrans().getStringX(StringId
.INPUT_DESC
,
71 desc
= Instance
.getTrans().getString(StringId
.INPUT_DESC
, this);
78 * The name of this support type (a short version).
82 public String
getSourceName() {
83 BasicSupport support
= BasicSupport
.getSupport(this);
84 if (support
!= null) {
85 return support
.getSourceName();
92 public String
toString() {
93 return super.toString().toLowerCase();
97 * Call {@link SupportType#valueOf(String.toUpperCase())}.
100 * the possible type name
102 * @return NULL or the type
104 public static SupportType
valueOfUC(String typeName
) {
105 return SupportType
.valueOf(typeName
== null ?
null : typeName
110 * Call {@link SupportType#valueOf(String.toUpperCase())} but return
111 * NULL for NULL instead of raising exception.
114 * the possible type name
116 * @return NULL or the type
118 public static SupportType
valueOfNullOkUC(String typeName
) {
119 if (typeName
== null) {
123 return SupportType
.valueOfUC(typeName
);
127 * Call {@link SupportType#valueOf(String.toUpperCase())} but return
128 * NULL in case of error instead of raising an exception.
131 * the possible type name
133 * @return NULL or the type
135 public static SupportType
valueOfAllOkUC(String typeName
) {
137 return SupportType
.valueOfUC(typeName
);
138 } catch (Exception e
) {
144 /** Only used by {@link BasicSupport#getInput()} just so it is always reset. */
145 private InputStream in
;
146 private SupportType type
;
147 private URL currentReferer
; // with on 'r', as in 'HTTP'...
150 private char openQuote
= Instance
.getTrans().getChar(
151 StringId
.OPEN_SINGLE_QUOTE
);
152 private char closeQuote
= Instance
.getTrans().getChar(
153 StringId
.CLOSE_SINGLE_QUOTE
);
154 private char openDoubleQuote
= Instance
.getTrans().getChar(
155 StringId
.OPEN_DOUBLE_QUOTE
);
156 private char closeDoubleQuote
= Instance
.getTrans().getChar(
157 StringId
.CLOSE_DOUBLE_QUOTE
);
160 * The name of this support class.
164 protected abstract String
getSourceName();
167 * Check if the given resource is supported by this {@link BasicSupport}.
170 * the resource to check for
172 * @return TRUE if it is
174 protected abstract boolean supports(URL url
);
177 * Return TRUE if the support will return HTML encoded content values for
178 * the chapters content.
180 * @return TRUE for HTML
182 protected abstract boolean isHtml();
185 * Return the story title.
188 * the source of the story
190 * the input (the main resource)
194 * @throws IOException
195 * in case of I/O error
197 protected abstract String
getTitle(URL source
, InputStream in
)
201 * Return the story author.
204 * the source of the story
206 * the input (the main resource)
210 * @throws IOException
211 * in case of I/O error
213 protected abstract String
getAuthor(URL source
, InputStream in
)
217 * Return the story publication date.
220 * the source of the story
222 * the input (the main resource)
226 * @throws IOException
227 * in case of I/O error
229 protected abstract String
getDate(URL source
, InputStream in
)
233 * Return the subject of the story (for instance, if it is a fanfiction,
234 * what is the original work; if it is a technical text, what is the
235 * technical subject...).
238 * the source of the story
240 * the input (the main resource)
242 * @return the subject
244 * @throws IOException
245 * in case of I/O error
247 protected abstract String
getSubject(URL source
, InputStream in
)
251 * Return the story description.
254 * the source of the story
256 * the input (the main resource)
258 * @return the description
260 * @throws IOException
261 * in case of I/O error
263 protected abstract String
getDesc(URL source
, InputStream in
)
267 * Return the story cover resource if any, or NULL if none.
269 * The default cover should not be checked for here.
272 * the source of the story
274 * the input (the main resource)
276 * @return the cover or NULL
278 * @throws IOException
279 * in case of I/O error
281 protected abstract URL
getCover(URL source
, InputStream in
)
285 * Return the list of chapters (name and resource).
288 * the source of the story
290 * the input (the main resource)
292 * @return the chapters
294 * @throws IOException
295 * in case of I/O error
297 protected abstract List
<Entry
<String
, URL
>> getChapters(URL source
,
298 InputStream in
) throws IOException
;
301 * Return the content of the chapter (possibly HTML encoded, if
302 * {@link BasicSupport#isHtml()} is TRUE).
305 * the source of the story
307 * the input (the main resource)
311 * @return the content
313 * @throws IOException
314 * in case of I/O error
316 protected abstract String
getChapterContent(URL source
, InputStream in
,
317 int number
) throws IOException
;
320 * Check if this {@link BasicSupport} is mainly catered to image files.
322 * @return TRUE if it is
324 public boolean isImageDocument(URL source
, InputStream in
)
330 * Return the list of cookies (values included) that must be used to
331 * correctly fetch the resources.
333 * You are expected to call the super method implementation if you override
336 * @return the cookies
338 public Map
<String
, String
> getCookies() {
339 return new HashMap
<String
, String
>();
343 * Process the given story resource into a partially filled {@link Story}
344 * object containing the name and metadata, except for the description.
349 * @return the {@link Story}
351 * @throws IOException
352 * in case of I/O error
354 public Story
processMeta(URL url
) throws IOException
{
355 return processMeta(url
, true, false);
359 * Process the given story resource into a partially filled {@link Story}
360 * object containing the name and metadata.
366 * close "this" and "in" when done
368 * @return the {@link Story}
370 * @throws IOException
371 * in case of I/O error
373 protected Story
processMeta(URL url
, boolean close
, boolean getDesc
)
375 in
= Instance
.getCache().open(url
, this, false);
381 preprocess(getInput());
383 Story story
= new Story();
384 story
.setMeta(new MetaData());
385 story
.getMeta().setTitle(ifUnhtml(getTitle(url
, getInput())));
386 story
.getMeta().setAuthor(
387 fixAuthor(ifUnhtml(getAuthor(url
, getInput()))));
388 story
.getMeta().setDate(ifUnhtml(getDate(url
, getInput())));
389 story
.getMeta().setTags(getTags(url
, getInput()));
390 story
.getMeta().setSource(getSourceName());
391 story
.getMeta().setPublisher(
392 ifUnhtml(getPublisher(url
, getInput())));
393 story
.getMeta().setUuid(getUuid(url
, getInput()));
394 story
.getMeta().setLuid(getLuid(url
, getInput()));
395 story
.getMeta().setLang(getLang(url
, getInput()));
396 story
.getMeta().setSubject(ifUnhtml(getSubject(url
, getInput())));
397 story
.getMeta().setImageDocument(isImageDocument(url
, getInput()));
400 String descChapterName
= Instance
.getTrans().getString(
401 StringId
.DESCRIPTION
);
402 story
.getMeta().setResume(
403 makeChapter(url
, 0, descChapterName
,
404 getDesc(url
, getInput())));
412 } catch (IOException e
) {
424 * Process the given story resource into a fully filled {@link Story}
430 * @return the {@link Story}
432 * @throws IOException
433 * in case of I/O error
435 public Story
process(URL url
) throws IOException
{
436 setCurrentReferer(url
);
439 Story story
= processMeta(url
, false, true);
444 story
.setChapters(new ArrayList
<Chapter
>());
446 URL cover
= getCover(url
, getInput());
448 String subject
= story
.getMeta() == null ?
null : story
449 .getMeta().getSubject();
450 if (subject
!= null && !subject
.isEmpty()
451 && Instance
.getCoverDir() != null) {
452 File fileCover
= new File(Instance
.getCoverDir(), subject
);
453 cover
= getImage(fileCover
.toURI().toURL(), subject
);
458 InputStream coverIn
= null;
460 coverIn
= Instance
.getCache().open(cover
, this, true);
461 story
.getMeta().setCover(StringUtils
.toImage(coverIn
));
462 } catch (IOException e
) {
463 Instance
.syserr(new IOException(Instance
.getTrans()
464 .getString(StringId
.ERR_BS_NO_COVER
, cover
), e
));
471 List
<Entry
<String
, URL
>> chapters
= getChapters(url
, getInput());
473 if (chapters
!= null) {
474 for (Entry
<String
, URL
> chap
: chapters
) {
475 setCurrentReferer(chap
.getValue());
476 InputStream chapIn
= Instance
.getCache().open(
477 chap
.getValue(), this, true);
479 story
.getChapters().add(
480 makeChapter(url
, i
, chap
.getKey(),
481 getChapterContent(url
, chapIn
, i
)));
494 } catch (IOException e
) {
502 currentReferer
= null;
511 public SupportType
getType() {
516 * The current referer {@link URL} (only one 'r', as in 'HTML'...), i.e.,
517 * the current {@link URL} we work on.
519 * @return the referer
521 public URL
getCurrentReferer() {
522 return currentReferer
;
526 * The current referer {@link URL} (only one 'r', as in 'HTML'...), i.e.,
527 * the current {@link URL} we work on.
529 * @param currentReferer
532 protected void setCurrentReferer(URL currentReferer
) {
533 this.currentReferer
= currentReferer
;
544 protected BasicSupport
setType(SupportType type
) {
550 * Return the story publisher (by default,
551 * {@link BasicSupport#getSourceName()}).
554 * the source of the story
556 * the input (the main resource)
558 * @return the publisher
560 * @throws IOException
561 * in case of I/O error
563 protected String
getPublisher(URL source
, InputStream in
)
565 return getSourceName();
569 * Return the story UUID, a unique value representing the story (it is often
572 * By default, this is the {@link URL} of the resource.
575 * the source of the story
577 * the input (the main resource)
581 * @throws IOException
582 * in case of I/O error
584 protected String
getUuid(URL source
, InputStream in
) throws IOException
{
585 return source
.toString();
589 * Return the story Library UID, a unique value representing the story (it
590 * is often a number) in the local library.
592 * By default, this is empty.
595 * the source of the story
597 * the input (the main resource)
601 * @throws IOException
602 * in case of I/O error
604 protected String
getLuid(URL source
, InputStream in
) throws IOException
{
609 * Return the 2-letter language code of this story.
611 * By default, this is 'EN'.
614 * the source of the story
616 * the input (the main resource)
618 * @return the language
620 * @throws IOException
621 * in case of I/O error
623 protected String
getLang(URL source
, InputStream in
) throws IOException
{
628 * Return the list of tags for this story.
631 * the source of the story
633 * the input (the main resource)
637 * @throws IOException
638 * in case of I/O error
640 protected List
<String
> getTags(URL source
, InputStream in
)
642 return new ArrayList
<String
>();
646 * Return the first line from the given input which correspond to the given
649 * Do not reset the input, which will be pointing at the line just after the
650 * result (input will be spent if no result is found).
655 * a string that must be found inside the target line (also
656 * supports "^" at start to say "only if it starts with" the
658 * @param relativeLine
659 * the line to return based upon the target line position (-1 =
660 * the line before, 0 = the target line...)
664 protected String
getLine(InputStream in
, String needle
, int relativeLine
) {
665 return getLine(in
, needle
, relativeLine
, true);
669 * Return a line from the given input which correspond to the given
672 * Do not reset the input, which will be pointing at the line just after the
673 * result (input will be spent if no result is found) when first is TRUE,
674 * and will always be spent if first is FALSE.
679 * a string that must be found inside the target line (also
680 * supports "^" at start to say "only if it starts with" the
682 * @param relativeLine
683 * the line to return based upon the target line position (-1 =
684 * the line before, 0 = the target line...)
686 * takes the first result (as opposed to the last one, which will
687 * also always spend the input)
691 protected String
getLine(InputStream in
, String needle
, int relativeLine
,
695 List
<String
> lines
= new ArrayList
<String
>();
696 @SuppressWarnings("resource")
697 Scanner scan
= new Scanner(in
, "UTF-8");
699 scan
.useDelimiter("\\n");
700 while (scan
.hasNext()) {
701 lines
.add(scan
.next());
704 if (needle
.startsWith("^")) {
705 if (lines
.get(lines
.size() - 1).startsWith(
706 needle
.substring(1))) {
707 index
= lines
.size() - 1;
711 if (lines
.get(lines
.size() - 1).contains(needle
)) {
712 index
= lines
.size() - 1;
717 if (index
>= 0 && index
+ relativeLine
< lines
.size()) {
718 rep
= lines
.get(index
+ relativeLine
);
729 * Prepare the support if needed before processing.
731 * @throws IOException
734 protected void preprocess(InputStream in
) throws IOException
{
738 * Now that we have processed the {@link Story}, close the resources if any.
740 * @throws IOException
743 protected void close() throws IOException
{
747 * Create a {@link Chapter} object from the given information, formatting
748 * the content as it should be.
755 * the chapter content
757 * @return the {@link Chapter}
759 * @throws IOException
760 * in case of I/O error
762 protected Chapter
makeChapter(URL source
, int number
, String name
,
763 String content
) throws IOException
{
765 // Chapter name: process it correctly, then remove the possible
766 // redundant "Chapter x: " in front of it
767 String chapterName
= processPara(name
).getContent().trim();
768 for (String lang
: Instance
.getConfig().getString(Config
.CHAPTER
)
770 String chapterWord
= Instance
.getConfig().getStringX(
771 Config
.CHAPTER
, lang
);
772 if (chapterName
.startsWith(chapterWord
)) {
773 chapterName
= chapterName
.substring(chapterWord
.length())
779 if (chapterName
.startsWith(Integer
.toString(number
))) {
780 chapterName
= chapterName
.substring(
781 Integer
.toString(number
).length()).trim();
784 if (chapterName
.startsWith(":")) {
785 chapterName
= chapterName
.substring(1).trim();
789 Chapter chap
= new Chapter(number
, chapterName
);
791 if (content
== null) {
796 // Special <HR> processing:
797 content
= content
.replaceAll("(<hr [^>]*>)|(<hr/>)|(<hr>)",
801 InputStream in
= new ByteArrayInputStream(content
.getBytes("UTF-8"));
803 @SuppressWarnings("resource")
804 Scanner scan
= new Scanner(in
, "UTF-8");
805 scan
.useDelimiter("(\\n|</p>)"); // \n for test, </p> for html
807 List
<Paragraph
> paras
= new ArrayList
<Paragraph
>();
808 while (scan
.hasNext()) {
809 String line
= scan
.next().trim();
810 boolean image
= false;
811 if (line
.startsWith("[") && line
.endsWith("]")) {
812 URL url
= getImage(source
,
813 line
.substring(1, line
.length() - 1).trim());
815 paras
.add(new Paragraph(url
));
821 paras
.add(processPara(line
));
825 // Check quotes for "bad" format
826 List
<Paragraph
> newParas
= new ArrayList
<Paragraph
>();
827 for (Paragraph para
: paras
) {
828 newParas
.addAll(requotify(para
));
832 // Remove double blanks/brks
833 boolean space
= false;
835 for (int i
= 0; i
< paras
.size(); i
++) {
836 Paragraph para
= paras
.get(i
);
837 boolean thisSpace
= para
.getType() == ParagraphType
.BLANK
;
838 boolean thisBrk
= para
.getType() == ParagraphType
.BREAK
;
840 if (space
&& thisBrk
) {
843 } else if ((space
|| brk
) && (thisSpace
|| thisBrk
)) {
852 // Remove blank/brk at start
854 && (paras
.get(0).getType() == ParagraphType
.BLANK
|| paras
855 .get(0).getType() == ParagraphType
.BREAK
)) {
859 // Remove blank/brk at end
860 int last
= paras
.size() - 1;
862 && (paras
.get(last
).getType() == ParagraphType
.BLANK
|| paras
863 .get(last
).getType() == ParagraphType
.BREAK
)) {
867 chap
.setParagraphs(paras
);
876 * Return the list of supported image extensions.
878 * @return the extensions
880 protected String
[] getImageExt(boolean emptyAllowed
) {
882 return new String
[] { "", ".png", ".jpg", ".jpeg", ".gif", ".bmp" };
884 return new String
[] { ".png", ".jpg", ".jpeg", ".gif", ".bmp" };
889 * Check if the given resource can be a local image or a remote image, then
890 * refresh the cache with it if it is.
895 * the resource to check
897 * @return the image URL if found, or NULL
900 protected URL
getImage(URL source
, String line
) {
901 String path
= new File(source
.getFile()).getParent();
906 String urlBase
= new File(new File(path
), line
.trim()).toURI()
908 for (String ext
: getImageExt(true)) {
909 if (new File(urlBase
+ ext
).exists()) {
910 url
= new File(urlBase
+ ext
).toURI().toURL();
913 } catch (Exception e
) {
914 // Nothing to do here
920 for (String ext
: getImageExt(true)) {
921 if (Instance
.getCache().check(new URL(line
+ ext
))) {
922 url
= new URL(line
+ ext
);
928 for (String ext
: getImageExt(true)) {
930 url
= new URL(line
+ ext
);
931 Instance
.getCache().refresh(url
, this, true);
933 } catch (IOException e
) {
934 // no image with this ext
939 } catch (MalformedURLException e
) {
944 // refresh the cached file
947 Instance
.getCache().refresh(url
, this, true);
948 } catch (IOException e
) {
949 // woops, broken image
958 * Reset then return {@link BasicSupport#in}.
960 * @return {@link BasicSupport#in}
962 * @throws IOException
963 * in case of I/O error
965 protected InputStream
getInput() throws IOException
{
971 * Fix the author name if it is prefixed with some "by" {@link String}.
974 * the author with a possible prefix
976 * @return the author without prefixes
978 private String
fixAuthor(String author
) {
979 if (author
!= null) {
980 for (String suffix
: new String
[] { " ", ":" }) {
981 for (String byString
: Instance
.getConfig()
982 .getString(Config
.BYS
).split(",")) {
984 if (author
.toUpperCase().startsWith(byString
.toUpperCase())) {
985 author
= author
.substring(byString
.length()).trim();
990 // Special case (without suffix):
991 if (author
.startsWith("©")) {
992 author
= author
.substring(1);
1000 * Check quotes for bad format (i.e., quotes with normal paragraphs inside)
1001 * and requotify them (i.e., separate them into QUOTE paragraphs and other
1002 * paragraphs (quotes or not)).
1005 * the paragraph to requotify (not necessaraly a quote)
1007 * @return the correctly (or so we hope) quotified paragraphs
1009 private List
<Paragraph
> requotify(Paragraph para
) {
1010 List
<Paragraph
> newParas
= new ArrayList
<Paragraph
>();
1012 if (para
.getType() == ParagraphType
.QUOTE
) {
1013 String line
= para
.getContent();
1014 boolean singleQ
= line
.startsWith("" + openQuote
);
1015 boolean doubleQ
= line
.startsWith("" + openDoubleQuote
);
1017 if (!singleQ
&& !doubleQ
) {
1018 line
= openDoubleQuote
+ line
+ closeDoubleQuote
;
1019 newParas
.add(new Paragraph(ParagraphType
.QUOTE
, line
));
1021 char close
= singleQ ? closeQuote
: closeDoubleQuote
;
1022 int posClose
= line
.indexOf(close
);
1023 int posDot
= line
.indexOf(".");
1024 while (posDot
>= 0 && posDot
< posClose
) {
1025 posDot
= line
.indexOf(".", posDot
+ 1);
1029 String rest
= line
.substring(posDot
+ 1).trim();
1030 line
= line
.substring(0, posDot
+ 1).trim();
1031 newParas
.add(new Paragraph(ParagraphType
.QUOTE
, line
));
1032 newParas
.addAll(requotify(processPara(rest
)));
1045 * Process a {@link Paragraph} from a raw line of text.
1047 * Will also fix quotes and HTML encoding if needed.
1052 * @return the processed {@link Paragraph}
1054 private Paragraph
processPara(String line
) {
1055 line
= ifUnhtml(line
).trim();
1057 boolean space
= true;
1059 boolean quote
= false;
1060 boolean tentativeCloseQuote
= false;
1064 StringBuilder builder
= new StringBuilder();
1065 for (char car
: line
.toCharArray()) {
1067 if (dashCount
> 0) {
1068 // dash, ndash and mdash: - – —
1069 // currently: always use mdash
1070 builder
.append(dashCount
== 1 ?
'-' : '—');
1075 if (tentativeCloseQuote
) {
1076 tentativeCloseQuote
= false;
1077 if ((car
>= 'a' && car
<= 'z') || (car
>= 'A' && car
<= 'Z')
1078 || (car
>= '0' && car
<= '9')) {
1079 builder
.append("'");
1081 builder
.append(closeQuote
);
1086 case ' ': // note: unbreakable space
1089 case '\n': // just in case
1090 case '\r': // just in case
1091 builder
.append(' ');
1095 if (space
|| (brk
&& quote
)) {
1097 builder
.append(openQuote
);
1098 } else if (prev
== ' ') {
1099 builder
.append(openQuote
);
1101 // it is a quote ("I'm off") or a 'quote' ("This
1102 // 'good' restaurant"...)
1103 tentativeCloseQuote
= true;
1108 if (space
|| (brk
&& quote
)) {
1110 builder
.append(openDoubleQuote
);
1111 } else if (prev
== ' ') {
1112 builder
.append(openDoubleQuote
);
1114 builder
.append(closeDoubleQuote
);
1139 builder
.append(car
);
1148 if (space
|| (brk
&& quote
)) {
1150 builder
.append(openQuote
);
1152 builder
.append(openQuote
);
1165 builder
.append(closeQuote
);
1173 if (space
|| (brk
&& quote
)) {
1175 builder
.append(openDoubleQuote
);
1177 builder
.append(openDoubleQuote
);
1190 builder
.append(closeDoubleQuote
);
1196 builder
.append(car
);
1203 if (tentativeCloseQuote
) {
1204 tentativeCloseQuote
= false;
1205 builder
.append(closeQuote
);
1208 line
= builder
.toString().trim();
1210 ParagraphType type
= ParagraphType
.NORMAL
;
1212 type
= ParagraphType
.BLANK
;
1214 type
= ParagraphType
.BREAK
;
1216 type
= ParagraphType
.QUOTE
;
1219 return new Paragraph(type
, line
);
1223 * Remove the HTML from the inpit <b>if</b> {@link BasicSupport#isHtml()} is
1229 * @return the no html version if needed
1231 private String
ifUnhtml(String input
) {
1232 if (isHtml() && input
!= null) {
1233 return StringUtils
.unhtml(input
);
1240 * Return a {@link BasicSupport} implementation supporting the given
1241 * resource if possible.
1244 * the story resource
1246 * @return an implementation that supports it, or NULL
1248 public static BasicSupport
getSupport(URL url
) {
1253 // TEXT and INFO_TEXT always support files (not URLs though)
1254 for (SupportType type
: SupportType
.values()) {
1255 if (type
!= SupportType
.TEXT
&& type
!= SupportType
.INFO_TEXT
) {
1256 BasicSupport support
= getSupport(type
);
1257 if (support
!= null && support
.supports(url
)) {
1263 for (SupportType type
: new SupportType
[] { SupportType
.TEXT
,
1264 SupportType
.INFO_TEXT
}) {
1265 BasicSupport support
= getSupport(type
);
1266 if (support
!= null && support
.supports(url
)) {
1275 * Return a {@link BasicSupport} implementation supporting the given type.
1280 * @return an implementation that supports it, or NULL
1282 public static BasicSupport
getSupport(SupportType type
) {
1285 return new Epub().setType(type
);
1287 return new InfoText().setType(type
);
1289 return new Fimfiction().setType(type
);
1291 return new Fanfiction().setType(type
);
1293 return new Text().setType(type
);
1295 return new MangaFox().setType(type
);
1297 return new E621().setType(type
);
1299 return new Cbz().setType(type
);