Home
Softono
xml2epub

xml2epub

Open source MIT Python
21
Stars
3
Forks
4
Issues
3
Watchers
4 months
Last Commit

About xml2epub

Batch convert multiple web pages, html files or images into one e-book.

Platforms

Web Self-hosted

Languages

Python

Links

xml2epub

GitHub Repo stars GitHub Workflow Status python pypi wheel license PyPI - Downloads

Batch convert web pages, HTML files or images to a single e-book.

Features:

  • Auto-generate cover: Uses matching <title> text (per COVER_TITLE_LIST) or a random generated cover default.
  • Auto-extract core content: Filters HTML to retain key elements (see SUPPORTED_TAGS).

ToC

How to install

xml2epub is available on pypi: https://pypi.org/project/xml2epub/

pip3 install xml2epub

Basic Usage

import xml2epub

## create an empty eBook, with toc located after cover
book = xml2epub.Epub("My New E-book Name", toc_location="afterFirstChapter")
## create chapters by url
#### custom your own cover image
chapter0 = xml2epub.create_chapter_from_string("https://cdn.jsdelivr.net/gh/dfface/img0@master/2022/02-10-0R7kll.png", title='cover', strict=False)
#### create chapter objects
chapter1 = xml2epub.create_chapter_from_url("https://dev.to/devteam/top-7-featured-dev-posts-from-the-past-week-h6h")
chapter2 = xml2epub.create_chapter_from_url("https://dev.to/ks1912/getting-started-with-docker-34g6")
## add chapters to your eBook
book.add_chapter(chapter0)
book.add_chapter(chapter1)
book.add_chapter(chapter2)
## generate epub file
book.create_epub("Your Output Directory")

After a short wait (no errors), "My New E-book Name.epub" will be generated in "Your Output Directory":

The generated epub file

For more examples, check the examples directory.

If no cover is inferred from the HTML, a random cover is generated.

The generated cover image

API

Epub object

Epub(title)

Epub(title, creator='dfface', language='en', rights='', publisher='dfface/xml2epub', epub_dir=None, toc_location='end')

Creates Epub object (adds book info/chapters, generates EPUB file).

  • title (str): EPUB title (per spec).
  • creator (Optional[str]): EPUB author (per spec).
  • owner (Optional[str]): The owner of this file—yes, that's you! This affects the text in the top banner if you use our generated cover.
  • language (Optional[str]): EPUB language (per spec).
  • rights (Optional[str]): EPUB copyright (per spec).
  • publisher (Optional[str]): EPUB publisher (per spec).
  • epub_dir (Optional[str]): Intermediate file path (default: system temp path).
  • toc_location (Optional[str]): ToC position (default: end; options: beginning/afterFirstChapter/end):
    • beginning: ToC → chapters
    • afterFirstChapter: Chapter1 (cover) → ToC → chapters
    • end: Chapters → ToC

Epub.add_chapter(chapter_object)

Add Chapter object (Created via 3 chapter creation methods) to EPUB.

Epub.create_epub(output_directory)

Epub.create_epub(output_directory, epub_name=None, absolute_location=None)

Generate EPUB file.

  • output_directory (str): Output directory for EPUB.
  • epub_name (Optional[str]): EPUB filename (no .epub suffix; printable chars only, defaults to title).
  • absolute_location (Optional[str]): Absolute path/name (no .epub suffix; overrides default ${cwd}/${output_directory}/${epub_name}.epub; requires write permissions).

create_chapter_from_file(path_to_file)

create_chapter_from_file(file_name, url=None, title=None, strict=True, local=False)

Create Chapter from HTML/XHTML file.

  • file_name (string): HTML/XHTML file path.
  • url (Optional[string]): Infers title; recommended for relative links.
  • title (Optional[string]): Chapter name (uses HTML <title> if None).
  • strict (Optional[boolean]): Strict cleaning (removes inline styles, trivial attrs); default True.
  • local (Optional[boolean]): Use local resources (copy images/CSS via paths, no online fetch).

create_chapter_from_url(url)

create_chapter_from_url(url, title=None, strict=True, local=False)

Create Chapter by extracting webpage from URL.

  • url (string): Website link (recommended for resolving relative links).
  • title (Optional[string]): Chapter name (uses HTML <title> if None).
  • strict (Optional[boolean]): Strict page cleaning (removes inline styles/attrs; default True).False allows image links for custom covers.
  • local (Optional[boolean]): Use local resources (copy images/CSS via paths, no online fetch).

create_chapter_from_string(html_string)

create_chapter_from_string(html_string, url=None, title=None, strict=True, local=False)

Create Chapter from string (base method for URL/file variants).

  • html_string (string): HTML/XHTML string; or image URL (strict=False) / image path (strict=False + local=True). Image as cover if title is None/ in [COVER_TITLE_LIST] (e.g., cover).
  • url (Optional[string]): Infers title; recommended for relative links.
  • title (Optional[string]): Chapter name (uses HTML if None).</li> <li><code>strict</code> (Optional[boolean]): Strict page cleaning (removes inline styles/attrs; default True).</li> <li><code>local</code> (Optional[boolean]): Use local resources (copy images/CSS via paths, no online fetch).</li> </ul> <h3><code>html_clean(input_string)</code></h3> <p><code>html_clean(input_string, help_url=None, tag_clean_list=constants.TAG_DELETE_LIST, class_list=constants.CLASS_INCLUDE_LIST, tag_dictionary=constants.SUPPORTED_TAGS)</code></p> <p>Exposed internal default clean method for easy customization.</p> <ul> <li><code>input_string</code> (str): HTML/XML string.</li> <li><code>help_url</code> (Optional[str]): Current chapter URL (resolves relative links).</li> <li><code>tag_dictionary</code> (Optional[dict]): Tags/classes to retain (default: <a href="./xml2epub/constants.py">SUPPORTED_TAGS</a>, can be <code>None</code>: <strong>retain all tags</strong> except those specified in <code>tag_clean_list</code>).</li> <li><code>tag_clean_list</code> (Optional[list]): Tags to delete (full tag + subtags; default: <a href="./xml2epub/constants.py">TAG_DELETE_LIST</a>). Preferably set <code>tag_dictionary</code> to <code>None</code>.</li> <li><code>class_clean_list</code> (Optional[list]): Tags to delete (class matches list; full tag + subtags; default: <a href="./xml2epub/constants.py">CLASS_DELETE_LIST</a>).</li> </ul> <h2>Tips</h2> <ul> <li>Custom cover: Use <code>create_chapter_from_string</code> – set <code>html_string</code> to image URL (with <code>strict=False</code>) or local path (with <code>local=True</code> + <code>strict=False</code>). Recommend adding <code>title='Cover'</code>.</li> <li>Custom web content cleaning: Fetch HTML via crawler → use exposed <code>html_clean</code> (recommend <code>tag_clean_list</code>, <code>class_clean_list</code>, url) → pass output to <code>create_chapter_from_string</code>'s <code>html_string</code> (keep <code>strict=False</code>).</li> <li>For <code>create_chapter_*</code> + <code>strict=False</code>: Recommend <code>url</code> (resolves relative links).</li> <li>For <code>html_clean</code>: Recommend <code>help_url</code> (resolves relative links).</li> <li>Post-EPUB generation: Use <a href="https://calibre-ebook.com/">Calibre</a> to convert to standard EPUB/mobi/azw3 (fix compatibility) or edit/adjust styles.</li> <li>If the reading effect of the generated EPUB e-books is unsatisfactory on traditional readers such as Calibre, you can consider using <a href="https://github.com/dfface/epub-browser">epub-browser</a> to read the generated EPUB e-books in your browser.</li> <li>Local images/CSS/resources: Set <code>local=True</code> in <code>create_chapter_*</code> – program copies local resources instead of fetching online.</li> </ul> <h2>FAQ</h2> <ol> <li>Generated EPUB has no content?</li> </ol> <p>Ensure the target URL is a static page accessible without login. If empty, fetch the HTML string (via crawler) and use <code>create_chapter_from_string</code> to generate EPUB.</p> <ol start="2"> <li>Generated EPUB has unwanted content?</li> </ol> <p>Our default HTML filtering may not cover all cases. Filter the HTML string yourself before using <code>create_chapter_from_string</code>.</p> <ol start="3"> <li>Generate EPUB from HTML string without content sanitization?</li> </ol> <p>Set <code>strict=False</code> in <code>create_chapter_from_string</code> to skip internal cleaning.</p> <ol start="4"> <li>Self-fetch & clean HTML string (steps):<ol> <li>Get HTML string via crawler (e.g., <code>requests.get(url).text</code>).</li> <li>Clean it with exposed <code>html_clean</code> (e.g., <code>html_clean(html_string, tag_clean_list=['sidebar'])</code>) or custom methods.</li> <li>Generate Chapter via <code>create_chapter_from_string(html_string, strict=False)</code> (set <code>strict=False</code> to skip internal cleaning).</li> <li>Generate EPUB per basic usage (see example: <a href="examples/hugo2epub/hugo2epub.py">hugo2epub.py</a>).</li> </ol> </li> </ol> </article> </div> </div> </div> </div> </section> </div> </div> </main> <!-- ========================= Footer v3 ===========================--> <footer class="footer footer-three dark:bg-background-8 {=$class} relative overflow-hidden bg-white"> <div class="main-container"> <div class="grid grid-cols-12 justify-between gap-x-0 gap-y-16 pt-16 pb-16 lg:gap-x-8 xl:gap-x-0 xl:pt-[100px]"> <div class="col-span-12 lg:col-span-4"> <div data-ns-animate data-delay="0.3" class="xl:max-w-[306px]"> <figure> <img src="https://img.softono.com/qoj701ib3Ld4bDgb-icIWTSfvTWeTYajWDUdTPwHgQ0/aHR0cHM6Ly9zb2Z0b25vLmNvbS91cGxvYWQvbG9nby9sb2dvLnBuZw" class="dark:hidden" alt="Nexsass" /> <img src="https://img.softono.com/qoj701ib3Ld4bDgb-icIWTSfvTWeTYajWDUdTPwHgQ0/aHR0cHM6Ly9zb2Z0b25vLmNvbS91cGxvYWQvbG9nby9sb2dvLnBuZw" class="hidden dark:block" alt="Nexsass" /> </figure> <p class="text-secondary dark:text-accent mt-4 mb-7"> Turpis tortor nunc sed amet et faucibus vitae morbi congue sed id mauris. </p> <div class="flex items-center gap-3"> <a href="#" class="footer-social-link"> <span class="sr-only">Facebook</span> <svg xmlns="http://www.w3.org/2000/svg" width="7" height="16" viewBox="0 0 7 16" fill="none"> <path d="M2.25 15C2.25 15.4142 2.58579 15.75 3 15.75C3.41421 15.75 3.75 15.4142 3.75 15H2.25ZM3.75 7C3.75 6.58579 3.41421 6.25 3 6.25C2.58579 6.25 2.25 6.58579 2.25 7H3.75ZM6 1.75C6.41421 1.75 6.75 1.41421 6.75 1C6.75 0.585786 6.41421 0.25 6 0.25V1.75ZM3 4H2.25H3ZM2.25 7C2.25 7.41421 2.58579 7.75 3 7.75C3.41421 7.75 3.75 7.41421 3.75 7H2.25ZM3 6.25C2.58579 6.25 2.25 6.58579 2.25 7C2.25 7.41421 2.58579 7.75 3 7.75V6.25ZM5 7.75C5.41421 7.75 5.75 7.41421 5.75 7C5.75 6.58579 5.41421 6.25 5 6.25V7.75ZM3 7.75C3.41421 7.75 3.75 7.41421 3.75 7C3.75 6.58579 3.41421 6.25 3 6.25V7.75ZM1 6.25C0.585786 6.25 0.25 6.58579 0.25 7C0.25 7.41421 0.585786 7.75 1 7.75V6.25ZM3 15H3.75V7H3H2.25V15H3ZM6 1V0.25C3.92893 0.25 2.25 1.92893 2.25 4H3H3.75C3.75 2.75736 4.75736 1.75 6 1.75V1ZM3 4H2.25V7H3H3.75V4H3ZM3 7V7.75H5V7V6.25H3V7ZM3 7V6.25H1V7V7.75H3V7Z" class="fill-secondary dark:fill-accent" /> </svg> </a> <div class="bg-stroke-1 dark:bg-stroke-8 h-5 w-px"></div> <a href="#" class="footer-social-link"> <span class="sr-only">Instagram</span> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path fill-rule="evenodd" clip-rule="evenodd" d="M11 1H5C2.79086 1 1 2.79086 1 5V11C1 13.2091 2.79086 15 5 15H11C13.2091 15 15 13.2091 15 11V5C15 2.79086 13.2091 1 11 1Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M8 11C6.34315 11 5 9.65685 5 8C5 6.34315 6.34315 5 8 5C9.65685 5 11 6.34315 11 8C11 8.79565 10.6839 9.55871 10.1213 10.1213C9.55871 10.6839 8.79565 11 8 11Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <rect x="11" y="5" width="2" height="2" rx="1" transform="rotate(-90 11 5)" class="fill-secondary dark:fill-accent" /> <rect x="11.5" y="4.5" width="1" height="1" rx="0.5" transform="rotate(-90 11.5 4.5)" class="stroke-secondary dark:stroke-accent" stroke-linecap="round" /> </svg> </a> <div class="bg-stroke-1 dark:bg-stroke-8 h-5 w-px"></div> <a href="#" class="footer-social-link"> <span class="sr-only">Youtube</span> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="16" viewBox="0 0 22 16" fill="none"> <path fill-rule="evenodd" clip-rule="evenodd" d="M16.668 15.0028C18.9724 15.0867 20.91 13.29 21 10.9858V5.01982C20.91 2.71569 18.9724 0.918929 16.668 1.00282H5.332C3.02763 0.918929 1.08998 2.71569 1 5.01982V10.9858C1.08998 13.29 3.02763 15.0867 5.332 15.0028H16.668Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M10.508 5.17711L13.669 7.32511C13.8738 7.44468 13.9997 7.66398 13.9997 7.90111C13.9997 8.13824 13.8738 8.35754 13.669 8.47711L10.508 10.8271C9.908 11.2341 9 10.8871 9 10.2511V5.75111C9 5.11811 9.909 4.77011 10.508 5.17711Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </a> <div class="bg-stroke-1 dark:bg-stroke-8 h-5 w-px"></div> <a href="#" class="footer-social-link"> <span class="sr-only">LinkedIn</span> <svg xmlns="http://www.w3.org/2000/svg" width="13" height="11" viewBox="0 0 13 11" fill="none"> <path d="M2.25 4C2.25 3.58579 1.91421 3.25 1.5 3.25C1.08579 3.25 0.75 3.58579 0.75 4H2.25ZM0.75 10C0.75 10.4142 1.08579 10.75 1.5 10.75C1.91421 10.75 2.25 10.4142 2.25 10H0.75ZM10.75 10C10.75 10.4142 11.0858 10.75 11.5 10.75C11.9142 10.75 12.25 10.4142 12.25 10H10.75ZM5.5 7H4.75H5.5ZM4.75 10C4.75 10.4142 5.08579 10.75 5.5 10.75C5.91421 10.75 6.25 10.4142 6.25 10H4.75ZM2.25 1C2.25 0.585786 1.91421 0.25 1.5 0.25C1.08579 0.25 0.75 0.585786 0.75 1H2.25ZM0.75 2C0.75 2.41421 1.08579 2.75 1.5 2.75C1.91421 2.75 2.25 2.41421 2.25 2H0.75ZM1.5 4H0.75V10H1.5H2.25V4H1.5ZM11.5 10H12.25V7H11.5H10.75V10H11.5ZM11.5 7H12.25C12.25 4.92893 10.5711 3.25 8.5 3.25V4V4.75C9.74264 4.75 10.75 5.75736 10.75 7H11.5ZM8.5 4V3.25C6.42893 3.25 4.75 4.92893 4.75 7H5.5H6.25C6.25 5.75736 7.25736 4.75 8.5 4.75V4ZM5.5 7H4.75V10H5.5H6.25V7H5.5ZM1.5 1H0.75V2H1.5H2.25V1H1.5Z" class="fill-secondary dark:fill-accent" /> </svg> </a> <div class="bg-stroke-1 dark:bg-stroke-8 h-5 w-px"></div> <a href="#" class="footer-social-link"> <span class="sr-only">Dribbble</span> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path fill-rule="evenodd" clip-rule="evenodd" d="M9.81146 14.7617C6.69789 15.5957 3.41731 14.1957 1.86521 11.3707C0.313116 8.54567 0.890795 5.02595 3.26447 2.84524C5.63814 0.66452 9.19411 0.386619 11.8777 2.1721C14.5614 3.95759 15.6788 7.34483 14.5845 10.3767C13.8079 12.532 12.0248 14.1702 9.81146 14.7617Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path d="M9.06142 14.7162C9.03653 15.1297 9.35153 15.485 9.765 15.5099C10.1785 15.5348 10.5338 15.2198 10.5587 14.8063L9.06142 14.7162ZM6.84286 0.874373C6.64188 0.512186 6.18534 0.381502 5.82315 0.582483C5.46097 0.783464 5.33028 1.24 5.53126 1.60219L6.84286 0.874373ZM13.2187 2.9035C13.3591 2.5138 13.157 2.08408 12.7673 1.94368C12.3776 1.80328 11.9479 2.00537 11.8075 2.39506L13.2187 2.9035ZM7.74006 7.03428L7.54644 6.30971L7.54546 6.30997L7.74006 7.03428ZM1.89802 5.05032C1.58158 4.78304 1.10838 4.82289 0.841101 5.13932C0.573819 5.45576 0.613667 5.92896 0.930105 6.19624L1.89802 5.05032ZM2.77955 13.0958C2.63901 13.4855 2.84095 13.9153 3.23059 14.0558C3.62023 14.1963 4.05003 13.9944 4.19057 13.6048L2.77955 13.0958ZM8.25822 8.96384L8.06412 8.23939L8.25822 8.96384ZM14.1013 10.9494C14.4178 11.2166 14.891 11.1766 15.1582 10.8601C15.4254 10.5435 15.3854 10.0703 15.0688 9.80317L14.1013 10.9494ZM9.81006 14.7613L10.5587 14.8063C10.7186 12.1509 10.1178 9.27114 9.32769 6.78072C8.53534 4.28333 7.53363 2.11922 6.84286 0.874373L6.18706 1.23828L5.53126 1.60219C6.17449 2.76135 7.13628 4.83373 7.89793 7.23434C8.66179 9.64192 9.20557 12.3216 9.06142 14.7162L9.81006 14.7613ZM12.5131 2.64928L11.8075 2.39506C11.1142 4.31922 9.52233 5.7817 7.54644 6.30971L7.74006 7.03428L7.93369 7.75886C10.3844 7.10397 12.3588 5.29004 13.2187 2.9035L12.5131 2.64928ZM7.74006 7.03428L7.54546 6.30997C5.57029 6.84064 3.46046 6.37005 1.89802 5.05032L1.41406 5.62328L0.930105 6.19624C2.86801 7.83311 5.48485 8.41679 7.93467 7.75859L7.74006 7.03428ZM3.48506 13.3503L4.19057 13.6048C4.88464 11.6805 6.47642 10.2177 8.45232 9.68829L8.25822 8.96384L8.06412 8.23939C5.614 8.89585 3.64019 10.7097 2.77955 13.0958L3.48506 13.3503ZM8.25822 8.96384L8.45232 9.68829C10.4282 9.15889 12.5381 9.62992 14.1013 10.9494L14.5851 10.3763L15.0688 9.80317C13.1305 8.16701 10.5142 7.58293 8.06412 8.23939L8.25822 8.96384Z" class="fill-secondary dark:fill-accent" /> </svg> </a> <div class="bg-stroke-1 dark:bg-stroke-8 h-5 w-px"></div> <a href="#" class="footer-social-link"> <span class="sr-only">Behance</span> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="14" viewBox="0 0 16 14" fill="none"> <path fill-rule="evenodd" clip-rule="evenodd" d="M1 7V1H4C5.65685 1 7 2.34315 7 4C7 5.65685 5.65685 7 4 7C5.65685 7 7 8.34315 7 10C7 11.6569 5.65685 13 4 13H1V7Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M15 10C15 8.34315 13.6569 7 12 7C10.3431 7 9 8.34315 9 10H15Z" class="stroke-secondary dark:stroke-accent" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path d="M1 6.25C0.585786 6.25 0.25 6.58579 0.25 7C0.25 7.41421 0.585786 7.75 1 7.75V6.25ZM4 7.75C4.41421 7.75 4.75 7.41421 4.75 7C4.75 6.58579 4.41421 6.25 4 6.25V7.75ZM9.75 9.99998C9.74999 9.58577 9.41419 9.24999 8.99998 9.25C8.58577 9.25001 8.24999 9.58581 8.25 10L9.75 9.99998ZM10.9295 12.8024L10.6619 13.5031L10.9295 12.8024ZM14.795 12.5C15.0712 12.1913 15.0447 11.7172 14.736 11.441C14.4273 11.1648 13.9532 11.1913 13.677 11.5L14.795 12.5ZM14 5.75C14.4142 5.75 14.75 5.41421 14.75 5C14.75 4.58579 14.4142 4.25 14 4.25V5.75ZM10 4.25C9.58579 4.25 9.25 4.58579 9.25 5C9.25 5.41421 9.58579 5.75 10 5.75V4.25ZM1 7V7.75H4V7V6.25H1V7ZM9 10L8.25 10C8.25004 11.5548 9.20948 12.9483 10.6619 13.5031L10.9295 12.8024L11.1971 12.1018C10.3257 11.7689 9.75002 10.9328 9.75 9.99998L9 10ZM10.9295 12.8024L10.6619 13.5031C12.1143 14.0578 13.7584 13.6588 14.795 12.5L14.236 12L13.677 11.5C13.0551 12.1953 12.0686 12.4347 11.1971 12.1018L10.9295 12.8024ZM14 5V4.25H10V5V5.75H14V5Z" class="fill-secondary dark:fill-accent" /> </svg> </a> </div> </div> </div> <div class="col-span-12 grid grid-cols-12 gap-x-0 gap-y-8 lg:col-span-8"> <div class="col-span-12 md:col-span-4"> <div data-ns-animate data-delay="0.4" class="space-y-8"> <p class="sm:text-heading-6 text-tagline-1 text-secondary dark:text-accent font-normal"> Company </p> <ul class="space-y-3"> <li> <a href="page/about-us" class="footer-link-v2 router pjax"> About Us </a> </li> <li> <a href="himanshu" class="footer-link-v2 router pjax"> About Founder </a> </li> <li> <a href="services" class="footer-link-v2 router pjax"> Our Services </a> </li> <li> <a href="testimonials" class="footer-link-v2 router pjax"> Testimonials </a> </li> <li> <a href="contact" class="footer-link-v2 router pjax"> Contact Us </a> </li> </ul> </div> </div> <div class="col-span-12 md:col-span-4"> <div data-ns-animate data-delay="0.5" class="space-y-8"> <p class="sm:text-heading-6 text-tagline-1 text-secondary dark:text-accent font-normal"> Explore </p> <ul class="space-y-3"> <li> <a href="softwares" class="footer-link-v2 router pjax"> Softwares </a> </li> <li> <a href="vendors" class="footer-link-v2 router pjax"> Software Vendors </a> </li> <li> <a href="softwares/categories" class="footer-link-v2 router pjax"> Software Categories </a> </li> <li> <a href="blog" class="footer-link-v2 router pjax"> Tech Blog </a> </li> </ul> </div> </div> <div class="col-span-12 md:col-span-4"> <div data-ns-animate data-delay="0.6" class="space-y-8"> <p class="sm:text-heading-6 text-tagline-1 text-secondary dark:text-accent font-normal"> Legal Policies </p> <ul class="space-y-3"> <li> <a href="page/terms-condition" class="footer-link-v2 router pjax"> Terms & Conditions </a> </li> <li> <a href="page/privacy-policy" class="footer-link-v2 router pjax"> Privacy Policy </a> </li> </ul> </div> </div> </div> </div> <div class="relative overflow-hidden pt-6 pb-[60px] text-center"> <div class="footer-divider bg-stroke-2 dark:bg-accent/5 absolute top-0 right-0 left-0 mx-auto h-px w-0 origin-center"></div> <p data-ns-animate data-delay="0.7" data-offset="10" data-start="top 105%" class="text-secondary dark:text-accent/60"> ©2026 , made by <a href="http://softono.com" target="_blank" class="text-secondary dark:text-accent">Softono</a> </p> </div> </div> <div class="{=$hide-theme-toggle}"> <!-- ========================= Theme Toggle Button ===========================--> <button id="theme-toggle" data-default-theme="{=$default-theme}" aria-label="Theme toggle button" class="size-12 bg-background-8 !z-[9999] dark:bg-white rounded-l-2xl cursor-pointer flex items-center justify-center fixed right-0 bottom-5"> <span id="dark-theme-icon"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 stroke-black"> <path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" /> </svg> </span> <span id="light-theme-icon"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" class="size-6 stroke-white"> <path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" /> </svg> </span> </button> </div> <div id="common-modal" class="modal fade"> <div class="modal-dialog"> <div class="modal-content" id="common-modal-content"> </div> </div> </div> </footer> <script src="https://code.jquery.com/jquery-4.0.0.min.js" integrity="sha256-OaVG6prZf4v69dPg6PhVattBXkcOWQB62pdZ3ORyrao=" crossorigin="anonymous"></script> <script src="theme/vendor/swiper.min.js"></script> <script src="theme/vendor/leaflet.min.js"></script> <script src="theme/vendor/vanilla-infinite-marquee.min.js"></script> <script src="theme/vendor/split-text.min.js"></script> <script src="theme/vendor/gsap.min.js"></script> <script src="theme/vendor/scroll-trigger.min.js"></script> <script src="theme/vendor/draw-svg.min.js"></script> <script src="theme/vendor/scroll-trigger.min.js"></script> <script src="theme/vendor/motionpathplugin.min.js"></script> <script src="theme/vendor/lenis.min.js"></script> <script src="theme/vendor/springer.min.js"></script> <script src="theme/vendor/number-counter.js"></script> <script src="theme/vendor/stack-card.min.js"></script> <script src="theme/assets/main.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.21.0/jquery.validate.min.js" integrity="sha512-KFHXdr2oObHKI9w4Hv1XPKc898mE4kgYx58oqsc/JqqdLMDI4YjOLzom+EMlW8HFUd0QfjfAvxSL6sEq/a42fQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="assets/js/app.js?v=4"></script> <script src="assets/js/pjax.js?v=8"></script> <script src="assets/js/common.js"></script> <script> $(document).ready(function() { pjax.onLinkClick = function(target){ updateActiveMenu(target); }; pjax.onPageLoaded = function(url){ initRevealElements(); }; pjax.init(); }); </script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@3.0.1/dist/cookieconsent.css"> <script type="module"> import 'https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@3.0.1/dist/cookieconsent.umd.js'; window.addEventListener('load', function() { if (window.templateCustomizer?.settings?.style === 'dark') { document.documentElement.classList.add('cc--darkmode'); } CookieConsent.run({ // root: 'body', // autoShow: true, //disablePageInteraction: true, // hideFromBots: true, // mode: 'opt-in', //revision: 100, cookie: { name: 'cc_cookie', // domain: location.hostname, // path: '/', // sameSite: "Lax", expiresAfterDays: 365, }, // https://cookieconsent.orestbida.com/reference/configuration-reference.html#guioptions guiOptions: { consentModal: { layout: 'cloud inline', position: 'bottom right', equalWeightButtons: true, flipButtons: false }, preferencesModal: { layout: 'box', equalWeightButtons: true, flipButtons: false } }, onChange: ({changedCategories, changedServices}) => { alert(changedCategories); alert(changedServices,changedServices); if(CookieConsent.getUserPreferences().rejectedCategories.indexOf('necessary')<0){ app.setCookie('cookie_consent',1); } }, onModalHide: ({modalName}) => { if(CookieConsent.getUserPreferences().rejectedCategories.indexOf('necessary')<0){ app.setCookie('cookie_consent',1); } }, categories: { necessary: { enabled: true, // this category is enabled by default readOnly: true // this category cannot be disabled }, analytics: { autoClear: { cookies: [ { name: /^_ga/, // regex: match all cookies starting with '_ga' }, { name: '_gid', // string: exact cookie name } ] }, // https://cookieconsent.orestbida.com/reference/configuration-reference.html#category-services services: { ga: { label: 'Google Analytics', onAccept: () => {}, onReject: () => {} }, youtube: { label: 'Youtube Embed', onAccept: () => {}, onReject: () => {} }, } }, //ads: {} }, language: { default: 'en', translations: { en: { consentModal: { title: 'We use cookies', description: 'We use cookies to provide our services and for analytics and marketing. To find out more about our use of cookies, please see our Privacy Policy. By continuing to browse our website, you agree to our use of cookies. <a href="page/cookie-policy">Cookie policy</a>', acceptAllBtn: 'Accept all', acceptNecessaryBtn: 'Accept Necessary', showPreferencesBtn: 'Manage Individual preferences', // closeIconLabel: 'Reject all and close modal', footer: ``, }, preferencesModal: { title: 'Manage cookie preferences', acceptAllBtn: 'Accept all', acceptNecessaryBtn: 'Accept Necessary', savePreferencesBtn: 'Accept current selection', closeIconLabel: 'Close modal', serviceCounterLabel: 'Service|Services', sections: [ { title: 'Your Privacy Choices', description: `In this panel you can express some preferences related to the processing of your personal information. You may review and change expressed choices at any time by resurfacing this panel via the provided link. To deny your consent to the specific processing activities described below, switch the toggles to off or use the “Reject all” button and confirm you want to save your choices.`, }, { title: 'Strictly Necessary', description: 'These cookies are essential for the proper functioning of the website and cannot be disabled.', //this field will generate a toggle linked to the 'necessary' category linkedCategory: 'necessary', cookieTable: { caption: 'Cookie table', headers: { name: 'Cookie', domain: 'Domain', desc: 'Description' }, body: [ { name: 'XSRF-TOKEN', domain: location.hostname, desc: 'csrf security', }, { name: APP_UID+'_session', domain: location.hostname, desc: 'user session', }, { name: APP_UID+'_token', domain: location.hostname, desc: 'user remember', }, { name: APP_UID+'_tz', domain: location.hostname, desc: 'timezone', } ] } }, { title: 'Performance and Analytics', description: 'These cookies collect information about how you use our website. All of the data is anonymized and cannot be used to identify you.', linkedCategory: 'analytics', cookieTable: { caption: 'Cookie table', headers: { name: 'Cookie', domain: 'Domain', desc: 'Description' }, body: [ { name: '_ga', domain: location.hostname, desc: 'Description 1', }, { name: '_gid', domain: location.hostname, desc: 'Description 2', } ] } }, { title: 'More information', description: 'For any queries in relation to my policy on cookies and your choices, please <a href="contact">contact us</a>' } ] } } } } }); }); </script> </body> </html>