Sync to trunk

This commit is contained in:
Tomasz Długosz 2012-08-19 21:41:59 +02:00
commit bfa9db9809
509 changed files with 229706 additions and 113151 deletions

View File

@ -41,6 +41,12 @@ License: Apache 2.0
The full text of the Apache 2.0 license is available at: The full text of the Apache 2.0 license is available at:
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Files: resources/viewer/mathjax/*
Copyright: Unknown
License: Apache 2.0
The full text of the Apache 2.0 license is available at:
http://www.apache.org/licenses/LICENSE-2.0
Files: /src/cherrypy/* Files: /src/cherrypy/*
Copyright: Copyright (c) 2004-2007, CherryPy Team (team@cherrypy.org) Copyright: Copyright (c) 2004-2007, CherryPy Team (team@cherrypy.org)
Copyright: Copyright (C) 2005, Tiago Cogumbreiro <cogumbreiro@users.sf.net> Copyright: Copyright (C) 2005, Tiago Cogumbreiro <cogumbreiro@users.sf.net>
@ -396,8 +402,9 @@ License: other
Liberation Fonts Liberation Fonts
----------------- -----------------
calibre includes a copy of the liberation fonts, available from calibre includes a copy of the liberation fonts in TTF format, licensed under
https://calibre-ebook.com/downloads/liberation-fonts the SIL Open Font License, Version 1.1, and available from
https://fedorahosted.org/liberation-fonts/
BSD License (for all the BSD licensed code indicated above) BSD License (for all the BSD licensed code indicated above)
----------------------------------------------------------- -----------------------------------------------------------

View File

@ -19,6 +19,249 @@
# new recipes: # new recipes:
# - title: # - title:
- version: 0.8.65
date: 2012-08-17
new features:
- title: "A new wireless device driver. This allows connecting wirelessly to a device running a 'smart' calibre client"
description: "The wireless connection functions just as if the device was plugged into the computer by USB cable. Currently, Android devices are supported. See https://play.google.com/store/apps/details?id=com.multipie.calibreandroid"
type: major
- title: "MOBI Output: Add an option to control the type of MOBI file produced, to the MOBI Output conversion options. You can now generate an old MOBI6, a new KF8 or a joint MOBI6/KF8 file. By default, MOBI6 files are generated. This replaces the previous use of a tweak."
- title: "E-book viewer: Make paged mode the default. You can go back to the old flow mode by clicking the button with the yellow scroll in the top right corner of the viewer."
- title: "Driver for COBY kYROS MID7042 and Samsung Galaxy Ace S5839i"
bug fixes:
- title: "Update version of poppler bundled with calibre to fix reading covers from some PDF files"
- title: "Get Books: Fix clicking of results from Diesel books when there is only a single result not working"
- title: "Improve detection of system language on first run of calibre"
tickets: [1036354]
- title: "When finding the next series index and the last series index is a fractional number, use the next largest integer, instead of just adding 1"
- title: "Fix exception when saving a search/replace when no saved search/replace had been opened previously in the bulk search/replace dialog"
tickets: [1036464]
- title: "Fix restore database not restoring entries for the original_* formats"
- title: "Fix first run wizard not allowing empty email sending settings"
tickets: [1036358]
- title: "Do not error out when setting the cover for a book that has no folders in the library."
tickets: [1035935]
- title: "Conversion pipeline: Ignore unparseable values in the color attribute of font tags, instead of erroring out on them."
tickets: [1035633]
- title: "Catalogs: Fix regression that broke creation of catalogs while a device is connected"
- title: "Fix --with-library=/whatever not working for calibredb list"
improved recipes:
- Slashdot
- Various Canadian newspapers
- Business Spectator
- version: 0.8.64
date: 2012-08-09
new features:
- title: "E-book viewer: Allow viewing images in the book in a separate pop-up window by right clicking on the image. Useful if you want to keep some image, like a map to the side while reading the book."
- title: "Catalogs: Allow generation of catalogs in AZW3 format. Also add more powerful configuration options to exclude books and set prefixes. See http://www.mobileread.com/forums/showthread.php?t=187298 for details."
- title: "Generate a PDF version of the User Manual"
bug fixes:
- title: "News download: Fix broken handling of nesting for HTML 5 tags when parsing with BeautifulSoup"
- title: "EPUB: Handle files in the EPUB that have semi-colons in their file names. This means in particular using URL escaping when creating the NCX as ADE cannot handle unescaped semi-colons in the NCX."
tickets: [1033665]
- title: "Conversion pipeline: Ignore unparseable CSS instead of erroring out on it."
tickets: [1034074]
- title: "When setting up a column coloring rule based on the languages column, allow entry of localized language names instead of only ISO codes"
- title: "Catalogs: Generate cover for mobi/azw3 catalogs"
- title: "Update the last modified column record of a book, whenever a format is added to the book."
- title: "E-book viewer: Fix line scrolling stops at breaks option not working in paged mode"
tickets: [1033430]
- title: "MOBI Output: Fix ToC at start option having no effect when converting some input documents that have an out-of-spine ToC."
tickets: [1033656]
- title: "Catalog Generation: When generating EPUB/MOBI catalogs add more flexible rules for excluding books. Also add rules to customize the prefix characters used."
- title: "Make setting published date using metadata search/replace more robust."
- title: "Tag Browser: Flatten the display of sub-groups when sort by is not set to 'name'."
tickets: [1032746]
- title: "Fix isbn:false not matching if other identifiers are attached to the book."
improved recipes:
- The New Republic
- ZDNet
- Metro UK
- FHM UK
new recipes:
- title: eKundelek.pl
author: Artur Stachecki
- title: Sueddeutsche Mobil
author: Andreas Zeiser
- version: 0.8.63
date: 2012-08-02
new features:
- title: "E-book viewer: Allow quick saving and loading of viewer settings as 'themes'."
tickets: [1024611]
- title: "Ebook-viewer: Add a restore defaults button to the viewer preferences dialog"
- title: "E-book viewer: Add simple settings for text and background colors"
- title: "Add an entry to save to disk when right clicking a format in the book details panel"
- title: "ODT metadata: Read first image as the metadata cover from ODT files. Also allow ODT authors to set custom properties for extended metadata."
- title: "E-book viewer and PDF Output: Resize images that are longer than the page to fit onto a single page"
bug fixes:
- title: "KF8 Output: Fix bug where some calibre generated KF8 files would cause the Amazon KF8 viewer on the Touch to go to into an infinite loop when using the next page function"
tickets: [1026421]
- title: "News download: Add support for <img> tags that link to SVG images."
tickets: [1031553]
- title: "Update podofo to 0.9.1 in all binary builds, to fix corruption of some PDFs when updating metadata."
tickets: [1031086]
- title: "Catalog generation: Handle authors whose last name is a number."
- title: "KF8 Input: Handle html entities in the NCX toc entries correctly"
- title: "Fix a calibre crash that affected some windows installs"
tickets: [1030234]
- title: "MOBI Output: Normalize unicode strings before writing to file, to workaround lack of support for non-normal unicode in Amazon's MOBI renderer."
tickets: [1029825]
- title: "EPUB Input: Handle files that have duplicate entries in the spine"
- title: "Fix regression in Kobo driver that caused the on device column to not be updated after deleting books"
new recipes:
- title: Dziennik Polski
author: Gregorz Maj
- title: High Country Blogs
author: Armin Geller
- title: Philosophy Now
author: Rick Shang
- version: 0.8.62
date: 2012-07-27
new features:
- title: "Book details panel: Allow right clicking on a format to delete it."
- title: "When errors occur in lots of background jobs, add an option to the error message to temporarily suppress subsequent error messages."
tickets: [886904]
- title: "E-book viewer full screen mode: Allow clicking in the left and right page margins to turn pages."
tickets: [1024819]
- title: "Drivers for various Android devices"
tickets: [1028690,1027431]
- title: "Advanced search dialog: When starting on the title/author/etc. tab, restore the previously used search kind as well."
tickets: [1029745]
- title: "When presenting the calibre must be restarted warning after installing a new plugin, add a restart now button so that the user can conveniently restart calibre. Currently only works when going vie Preferences->Plugins->Get new plugins"
bug fixes:
- title: "Fix main window layout state being saved incorrectly if calibre is killed without a proper shutdown"
- title: "Fix boolean and date searching in non english calibre installs."
- title: "Conversion: Ignore invalid chapter detection and level n ToC expressions instead of erroring out"
improved recipes:
- Psychology Today
- The Smithsonian
- The New Republic
- Various updated Polish news sources
- The Sun
- San Francisco Bay Guardian
- AnandTech
- Smashing Magazine
new recipes:
- title: Linux Journal and Conowego.pl
author: fenuks
- title: A list apart and .net magazine
author: Marc Busque
- version: 0.8.61
date: 2012-07-20
new features:
- title: "E-book viewer: Add a paged mode that splits up the text into pages, like in a paper book instead of presenting it as a single column. To activate click the button with the yellow scroll icon in the top right corner."
type: major
description: "In paged mode, the ebook viewer no longer cuts off the last line of text at the bottom of the screen, and it respects CSS page-break directives. You can also set page margins and control the number of pages displayed on screen by clicking the Preferences button in the viewer and going to 'Text layout in paged mode'."
- title: "Digitally sign the calibre OS X and windows builds"
- title: "Get Books: Add Mills and Boon UK"
- title: "Various minor improvements to the Bulk metadata edit dialog"
tickets: [1025825, 1025838, 1025628]
- title: "Fix various regression in the auto-complete functionality for authors/series/tags etc introduced in 0.8.60"
- title: "Drivers for various new Android devices"
tickets: [1024934]
- title: "MOBI: Add support for the new language EXTH header field in MOBI files generated by kindlegen 2.5"
bug fixes:
- title: "KF8 Output: Fix calibre produced KF8 files not showing the 'Use publisher font' option on the Kindle Touch when they have embedded fonts"
- title: "Txt/fb2/rtf/pml/rb output: Fix non-visibile element's tail text (which should be visible) is being ignored when it shouldn't."
tickets: [1026541]
- title: "Book details panel: When displaying a link to amazon, use a country specific name like amazon.fr instead of using amazon.com for all countries"
- title: "Conversion: When splitting on page breaks, ignore page-breaks with values of auto and inherit. "
tickets: [1018875]
- title: "Metadata jacket: Specify foreground in addition to the background color for the title banner so that it remain readable if the user tries to monkey with the CSS in the viewer."
- title: "PDF Output: Fix rendering of cover as first age of PDF (ignore margins so that the image covers the entire page)"
- title: "Linux binaries: Bundle libglib to avoid incompatibilities with glib on various distros."
tickets: [1022019]
- title: "Fix find_identical_books() choking on books with too many authors"
improved recipes:
- Toronto Star
- American Prospect
- faz.net
- version: 0.8.60 - version: 0.8.60
date: 2012-07-13 date: 2012-07-13

View File

@ -60,7 +60,7 @@ htmlhelp:
latex: latex:
mkdir -p .build/latex .build/doctrees mkdir -p .build/latex .build/doctrees
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex $(SPHINXBUILD) -b mylatex $(ALLSPHINXOPTS) .build/latex
@echo @echo
@echo "Build finished; the LaTeX files are in .build/latex." @echo "Build finished; the LaTeX files are in .build/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \

View File

@ -14,10 +14,10 @@
import sys, os import sys, os
# If your extensions are in another directory, add it here. # If your extensions are in another directory, add it here.
sys.path.append(os.path.abspath('../src'))
sys.path.append(os.path.abspath('.')) sys.path.append(os.path.abspath('.'))
__appname__ = os.environ.get('__appname__', 'calibre') import init_calibre
__version__ = os.environ.get('__version__', '0.0.0') init_calibre
from calibre.constants import __appname__, __version__
import custom import custom
custom custom
# General configuration # General configuration
@ -154,7 +154,8 @@ latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, document class [howto/manual]). # (source start file, target name, title, author, document class [howto/manual]).
#latex_documents = [] latex_documents = [('index', 'calibre.tex', 'calibre User Manual',
'Kovid Goyal', 'manual', False)]
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
#latex_preamble = '' #latex_preamble = ''
@ -164,3 +165,11 @@ latex_font_size = '10pt'
# If false, no module index is generated. # If false, no module index is generated.
#latex_use_modindex = True #latex_use_modindex = True
latex_logo = 'resources/logo.png'
latex_show_pagerefs = True
latex_show_urls = 'footnote'
latex_elements = {
'papersize':'letterpaper',
'fontenc':r'\usepackage[T2A,T1]{fontenc}'
}

View File

@ -710,3 +710,35 @@ EPUB from the ZIP file are::
Note that because this file explores the potential of EPUB, most of the advanced formatting is not going to work on readers less capable than |app|'s built-in EPUB viewer. Note that because this file explores the potential of EPUB, most of the advanced formatting is not going to work on readers less capable than |app|'s built-in EPUB viewer.
Convert ODT documents
~~~~~~~~~~~~~~~~~~~~~
|app| can directly convert ODT (OpenDocument Text) files. You should use styles to format your document and minimize the use of direct formatting.
When inserting images into your document you need to anchor them to the paragraph, images anchored to a page will all end up in the front of the conversion.
To enable automatic detection of chapters, you need to mark them with the build-in styles called 'Heading 1', 'Heading 2', ..., 'Heading 6' ('Heading 1' equates to the HTML tag <h1>, 'Heading 2' to <h2> etc). When you convert in |app| you can enter which style you used into the 'Detect chapters at' box. Example:
* If you mark Chapters with style 'Heading 2', you have to set the 'Detect chapters at' box to ``//h:h2``
* For a nested TOC with Sections marked with 'Heading 2' and the Chapters marked with 'Heading 3' you need to enter ``//h:h2|//h:h3``. On the Convert - TOC page set the 'Level 1 TOC' box to ``//h:h2`` and the 'Level 2 TOC' box to ``//h:h3``.
Well-known document properties (Title, Keywords, Description, Creator) are recognized and |app| will use the first image (not to small, and with good aspect-ratio) as the cover image.
There is also an advanced property conversion mode, which is activated by setting the custom property ``opf.metadata`` ('Yes or No' type) to Yes in your ODT document (File->Properties->Custom Properties).
If this property is detected by |app|, the following custom properties are recognized (``opf.authors`` overrides document creator)::
opf.titlesort
opf.authors
opf.authorsort
opf.publisher
opf.pubdate
opf.isbn
opf.language
opf.series
opf.seriesindex
In addition to this, you can specify the picture to use as the cover by naming it ``opf.cover`` (right click, Picture->Options->Name) in the ODT. If no picture with this name is found, the 'smart' method is used.
As the cover detection might result in double covers in certain output formats, the process will remove the paragraph (only if the only content is the cover!) from the document. But this works only with the named picture!
To disable cover detection you can set the custom property ``opf.nocover`` ('Yes or No' type) to Yes in advanced mode.

View File

@ -14,6 +14,7 @@ from sphinx.util.console import bold
sys.path.append(os.path.abspath('../../../')) sys.path.append(os.path.abspath('../../../'))
from calibre.linux import entry_points from calibre.linux import entry_points
from epub import EPUBHelpBuilder from epub import EPUBHelpBuilder
from latex import LaTeXHelpBuilder
def substitute(app, doctree): def substitute(app, doctree):
pass pass
@ -251,6 +252,7 @@ def template_docs(app):
def setup(app): def setup(app):
app.add_config_value('kovid_epub_cover', None, False) app.add_config_value('kovid_epub_cover', None, False)
app.add_builder(EPUBHelpBuilder) app.add_builder(EPUBHelpBuilder)
app.add_builder(LaTeXHelpBuilder)
app.connect('doctree-read', substitute) app.connect('doctree-read', substitute)
app.connect('builder-inited', generate_docs) app.connect('builder-inited', generate_docs)
app.connect('build-finished', finished) app.connect('build-finished', finished)

61
manual/develop.rst Executable file → Normal file
View File

@ -6,9 +6,9 @@ Setting up a |app| development environment
=========================================== ===========================================
|app| is completely open source, licensed under the `GNU GPL v3 <http://www.gnu.org/copyleft/gpl.html>`_. |app| is completely open source, licensed under the `GNU GPL v3 <http://www.gnu.org/copyleft/gpl.html>`_.
This means that you are free to download and modify the program to your heart's content. In this section, This means that you are free to download and modify the program to your heart's content. In this section,
you will learn how to get a |app| development environment set up on the operating system of your choice. you will learn how to get a |app| development environment set up on the operating system of your choice.
|app| is written primarily in `Python <http://www.python.org>`_ with some C/C++ code for speed and system interfacing. |app| is written primarily in `Python <http://www.python.org>`_ with some C/C++ code for speed and system interfacing.
Note that |app| is not compatible with Python 3 and requires at least Python 2.7. Note that |app| is not compatible with Python 3 and requires at least Python 2.7.
.. contents:: Contents .. contents:: Contents
@ -20,14 +20,14 @@ Design philosophy
|app| has its roots in the Unix world, which means that its design is highly modular. |app| has its roots in the Unix world, which means that its design is highly modular.
The modules interact with each other via well defined interfaces. This makes adding new features and fixing The modules interact with each other via well defined interfaces. This makes adding new features and fixing
bugs in |app| very easy, resulting in a frenetic pace of development. Because of its roots, |app| has a bugs in |app| very easy, resulting in a frenetic pace of development. Because of its roots, |app| has a
comprehensive command line interface for all its functions, documented in :ref:`cli`. comprehensive command line interface for all its functions, documented in :ref:`cli`.
The modular design of |app| is expressed via ``Plugins``. There is a :ref:`tutorial <customize>` on writing |app| plugins. The modular design of |app| is expressed via ``Plugins``. There is a :ref:`tutorial <customize>` on writing |app| plugins.
For example, adding support for a new device to |app| typically involves writing less than a 100 lines of code in the form of For example, adding support for a new device to |app| typically involves writing less than a 100 lines of code in the form of
a device driver plugin. You can browse the a device driver plugin. You can browse the
`built-in drivers <http://bazaar.launchpad.net/%7Ekovid/calibre/trunk/files/head%3A/src/calibre/devices/>`_. Similarly, adding support `built-in drivers <http://bazaar.launchpad.net/%7Ekovid/calibre/trunk/files/head%3A/src/calibre/devices/>`_. Similarly, adding support
for new conversion formats involves writing input/output format plugins. Another example of the modular design is the :ref:`recipe system <news>` for for new conversion formats involves writing input/output format plugins. Another example of the modular design is the :ref:`recipe system <news>` for
fetching news. For more examples of plugins designed to add features to |app|, see the `plugin index <http://www.mobileread.com/forums/showthread.php?p=1362767#post1362767>`_. fetching news. For more examples of plugins designed to add features to |app|, see the `plugin index <http://www.mobileread.com/forums/showthread.php?p=1362767#post1362767>`_.
Code layout Code layout
@ -91,15 +91,15 @@ this, make your changes, then run::
This will create a :file:`my-changes` file in the current directory, This will create a :file:`my-changes` file in the current directory,
simply attach that to a ticket on the |app| `bug tracker <https://bugs.launchpad.net/calibre>`_. simply attach that to a ticket on the |app| `bug tracker <https://bugs.launchpad.net/calibre>`_.
If you plan to do a lot of development on |app|, then the best method is to create a If you plan to do a lot of development on |app|, then the best method is to create a
`Launchpad <http://launchpad.net>`_ account. Once you have an account, you can use it to register `Launchpad <http://launchpad.net>`_ account. Once you have an account, you can use it to register
your bzr branch created by the `bzr branch` command above. First run the your bzr branch created by the `bzr branch` command above. First run the
following command to tell bzr about your launchpad account:: following command to tell bzr about your launchpad account::
bzr launchpad-login your_launchpad_username bzr launchpad-login your_launchpad_username
Now, you have to setup SSH access to Launchpad. First create an SSH public/private keypair. Then upload Now, you have to setup SSH access to Launchpad. First create an SSH public/private keypair. Then upload
the public key to Launchpad by going to your Launchpad account page. Instructions for setting up the the public key to Launchpad by going to your Launchpad account page. Instructions for setting up the
private key in bzr are at http://bazaar-vcs.org/Bzr_and_SSH. Now you can upload your branch to the |app| private key in bzr are at http://bazaar-vcs.org/Bzr_and_SSH. Now you can upload your branch to the |app|
project in Launchpad by following the instructions at https://help.launchpad.net/Code/UploadingABranch. project in Launchpad by following the instructions at https://help.launchpad.net/Code/UploadingABranch.
Whenever you commit changes to your branch with the command:: Whenever you commit changes to your branch with the command::
@ -108,7 +108,7 @@ Whenever you commit changes to your branch with the command::
Kovid can merge it directly from your branch into the main |app| source tree. You should also keep an eye on the |app| Kovid can merge it directly from your branch into the main |app| source tree. You should also keep an eye on the |app|
`development forum <http://www.mobileread.com/forums/forumdisplay.php?f=240>`. Before making major changes, you should `development forum <http://www.mobileread.com/forums/forumdisplay.php?f=240>`. Before making major changes, you should
discuss them in the forum or contact Kovid directly (his email address is all over the source code). discuss them in the forum or contact Kovid directly (his email address is all over the source code).
Windows development environment Windows development environment
--------------------------------- ---------------------------------
@ -118,12 +118,12 @@ the previously checked out |app| code directory. For example::
cd C:\Users\kovid\work\calibre cd C:\Users\kovid\work\calibre
calibre is the directory that contains the src and resources sub-directories. calibre is the directory that contains the src and resources sub-directories.
The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory. The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory.
So, following the example above, it would be ``C:\Users\kovid\work\calibre\src``. `Here is a short So, following the example above, it would be ``C:\Users\kovid\work\calibre\src``. `Here is a short
guide <http://docs.python.org/using/windows.html#excursus-setting-environment-variables>`_ to setting environment guide <http://docs.python.org/using/windows.html#excursus-setting-environment-variables>`_ to setting environment
variables on Windows. variables on Windows.
Once you have set the environment variable, open a new command prompt and check that it was correctly set by using Once you have set the environment variable, open a new command prompt and check that it was correctly set by using
the command:: the command::
@ -134,7 +134,7 @@ Setting this environment variable means that |app| will now load all its Python
That's it! You are now ready to start hacking on the |app| code. For example, open the file :file:`src\\calibre\\__init__.py` That's it! You are now ready to start hacking on the |app| code. For example, open the file :file:`src\\calibre\\__init__.py`
in your favorite editor and add the line:: in your favorite editor and add the line::
print ("Hello, world!") print ("Hello, world!")
near the top of the file. Now run the command :command:`calibredb`. The very first line of output should be ``Hello, world!``. near the top of the file. Now run the command :command:`calibredb`. The very first line of output should be ``Hello, world!``.
@ -149,24 +149,25 @@ the previously checked out |app| code directory, for example::
calibre is the directory that contains the src and resources sub-directories. Ensure you have installed the |app| commandline tools via :guilabel:`Preferences->Advanced->Miscellaneous` in the |app| GUI. calibre is the directory that contains the src and resources sub-directories. Ensure you have installed the |app| commandline tools via :guilabel:`Preferences->Advanced->Miscellaneous` in the |app| GUI.
The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory. The next step is to create a bash script that will set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory when running calibre in debug mode.
So, following the example above, it would be ``/Users/kovid/work/calibre/src``. Apple
`documentation <http://developer.apple.com/mac/library/documentation/MacOSX/Conceptual/BPRuntimeConfig/Articles/EnvironmentVars.html#//apple_ref/doc/uid/20002093-BCIJIJBH>`_
on how to set environment variables.
Once you have set the environment variable, open a new Terminal and check that it was correctly set by using Create a plain text file::
the command::
echo $CALIBRE_DEVELOP_FROM #!/bin/sh
export CALIBRE_DEVELOP_FROM="/Users/kovid/work/calibre/src"
calibre-debug -g
Setting this environment variable means that |app| will now load all its Python code from the specified location. Save this file as ``/usr/bin/calibre-develop``, then set its permissions so that it can be executed::
That's it! You are now ready to start hacking on the |app| code. For example, open the file :file:`src/calibre/__init__.py` chmod +x /usr/bin/calibre-develop
in your favorite editor and add the line::
print ("Hello, world!")
near the top of the file. Now run the command :command:`calibredb`. The very first line of output should be ``Hello, world!``. Once you have done this, run::
calibre-develop
You should see some diagnostic information in the Terminal window as calibre
starts up, and you should see an asterisk after the version number in the GUI
window, indicating that you are running from source.
Linux development environment Linux development environment
------------------------------ ------------------------------
@ -181,11 +182,11 @@ Install the |app| using the binary installer. Then open a terminal and change to
cd /home/kovid/work/calibre cd /home/kovid/work/calibre
calibre is the directory that contains the src and resources sub-directories. calibre is the directory that contains the src and resources sub-directories.
The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory. The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory.
So, following the example above, it would be ``/home/kovid/work/calibre/src``. How to set environment variables depends on So, following the example above, it would be ``/home/kovid/work/calibre/src``. How to set environment variables depends on
your Linux distribution and what shell you are using. your Linux distribution and what shell you are using.
Once you have set the environment variable, open a new terminal and check that it was correctly set by using Once you have set the environment variable, open a new terminal and check that it was correctly set by using
the command:: the command::
@ -196,7 +197,7 @@ Setting this environment variable means that |app| will now load all its Python
That's it! You are now ready to start hacking on the |app| code. For example, open the file :file:`src/calibre/__init__.py` That's it! You are now ready to start hacking on the |app| code. For example, open the file :file:`src/calibre/__init__.py`
in your favorite editor and add the line:: in your favorite editor and add the line::
print ("Hello, world!") print ("Hello, world!")
near the top of the file. Now run the command :command:`calibredb`. The very first line of output should be ``Hello, world!``. near the top of the file. Now run the command :command:`calibredb`. The very first line of output should be ``Hello, world!``.

View File

@ -548,7 +548,7 @@ Calibre has several keyboard shortcuts to save you time and mouse movement. Thes
- Toggle jobs list - Toggle jobs list
* - :kbd:`Alt+Shift+B` * - :kbd:`Alt+Shift+B`
- Toggle Cover Browser - Toggle Cover Browser
* - :kbd:`Alt+Shift+B` * - :kbd:`Alt+Shift+D`
- Toggle Book Details panel - Toggle Book Details panel
* - :kbd:`Alt+Shift+T` * - :kbd:`Alt+Shift+T`
- Toggle Tag Browser - Toggle Tag Browser

BIN
manual/images/lorentz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -17,7 +17,7 @@ To get started with more advanced usage, you should read about the :ref:`Graphic
.. only:: online .. only:: online
**An ebook version of this user manual is available in** `EPUB format <calibre.epub>`_ and `AZW3 (Kindle Fire) format <calibre.azw3>`_. **An ebook version of this user manual is available in** `EPUB format <calibre.epub>`_, `AZW3 (Kindle Fire) format <calibre.azw3>`_ and `PDF format <calibre.pdf>`_.
Sections Sections
------------ ------------

25
manual/latex.py Normal file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os
from sphinx.builders.latex import LaTeXBuilder
class LaTeXHelpBuilder(LaTeXBuilder):
name = 'mylatex'
def finish(self):
LaTeXBuilder.finish(self)
self.info('Fixing Cyrillic characters...')
tex = os.path.join(self.outdir, 'calibre.tex')
with open(tex, 'r+b') as f:
raw = f.read().replace(b'Михаил Горбачёв',
br'{\fontencoding{T2A}\selectfont Михаил Горбачёв}')
f.seek(0)
f.write(raw)

102
manual/mathjax.html Normal file
View File

@ -0,0 +1,102 @@
<!DOCTYPE html>
<html>
<!-- Copyright (c) 2012 Design Science, Inc. -->
<head>
<title>Math Test Page</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<!-- This script tag is needed to make calibre's ebook-viewer recpgnize that this file needs math typesetting -->
<script type="text/x-mathjax-config">
// This line adds numbers to all equations automatically, unless explicitly suppressed.
MathJax.Hub.Config({ TeX: { equationNumbers: {autoNumber: "all"} } });
</script>
<style>
h1 {text-align:center}
h2 {
font-weight: bold;
background-color: #DDDDDD;
padding: .2em .5em;
margin-top: 1.5em;
border-top: 3px solid #666666;
border-bottom: 2px solid #999999;
}
</style>
</head>
<body>
<h1>Sample Equations</h1>
<h2>The Lorenz Equations</h2>
<p>
\begin{align}
\dot{x} &amp; = \sigma(y-x) \label{lorenz}\\
\dot{y} &amp; = \rho x - y - xz \\
\dot{z} &amp; = -\beta z + xy
\end{align}
</p>
<h2>The Cauchy-Schwarz Inequality</h2>
<p>\[
\left( \sum_{k=1}^n a_k b_k \right)^{\!\!2} \leq
\left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)
\]</p>
<h2>A Cross Product Formula</h2>
<p>\[
\mathbf{V}_1 \times \mathbf{V}_2 =
\begin{vmatrix}
\mathbf{i} &amp; \mathbf{j} &amp; \mathbf{k} \\
\frac{\partial X}{\partial u} &amp; \frac{\partial Y}{\partial u} &amp; 0 \\
\frac{\partial X}{\partial v} &amp; \frac{\partial Y}{\partial v} &amp; 0 \\
\end{vmatrix}
\]</p>
<h2>The probability of getting \(k\) heads when flipping \(n\) coins is:</h2>
<p>\[P(E) = {n \choose k} p^k (1-p)^{ n-k} \]</p>
<h2>An Identity of Ramanujan</h2>
<p>\[
\frac{1}{(\sqrt{\phi \sqrt{5}}-\phi) e^{\frac25 \pi}} =
1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}}
{1+\frac{e^{-8\pi}} {1+\ldots} } } }
\]</p>
<h2>A Rogers-Ramanujan Identity</h2>
<p>\[
1 + \frac{q^2}{(1-q)}+\frac{q^6}{(1-q)(1-q^2)}+\cdots =
\prod_{j=0}^{\infty}\frac{1}{(1-q^{5j+2})(1-q^{5j+3})},
\quad\quad \text{for $|q|&lt;1$}.
\]</p>
<h2>Maxwell's Equations</h2>
<p>
\begin{align}
\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} &amp; = \frac{4\pi}{c}\vec{\mathbf{j}} \\
\nabla \cdot \vec{\mathbf{E}} &amp; = 4 \pi \rho \\
\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} &amp; = \vec{\mathbf{0}} \\
\nabla \cdot \vec{\mathbf{B}} &amp; = 0
\end{align}
</p>
<h2>In-line Mathematics</h2>
<p>While display equations look good for a page of samples, the
ability to mix math and text in a paragraph is also important. This
expression \(\sqrt{3x-1}+(1+x)^2\) is an example of an inline equation. As
you see, equations can be used this way as well, without unduly
disturbing the spacing between lines.</p>
<h2>References to equations</h2>
<p>Here is a reference to the Lorenz Equations (\ref{lorenz}). Clicking on the equation number will take you back to the equation.</p>
</body>
</html>

View File

@ -30,7 +30,7 @@ Lets pick a couple of feeds that look interesting:
#. Business Travel: http://feeds.portfolio.com/portfolio/businesstravel #. Business Travel: http://feeds.portfolio.com/portfolio/businesstravel
#. Tech Observer: http://feeds.portfolio.com/portfolio/thetechobserver #. Tech Observer: http://feeds.portfolio.com/portfolio/thetechobserver
I got the URLs by clicking the little orange RSS icon next to each feed name. To make |app| download the feeds and convert them into an ebook, you should click the :guilabel:`Fetch news` button and then the :guilabel:`Add a custom news source` menu item. A dialog similar to that shown below should open up. I got the URLs by clicking the little orange RSS icon next to each feed name. To make |app| download the feeds and convert them into an ebook, you should right click the :guilabel:`Fetch news` button and then the :guilabel:`Add a custom news source` menu item. A dialog similar to that shown below should open up.
.. image:: images/custom_news.png .. image:: images/custom_news.png
:align: center :align: center

Binary file not shown.

View File

@ -18,4 +18,5 @@ Here you will find tutorials to get you started using |app|'s more advanced feat
regexp regexp
server server
creating_plugins creating_plugins
typesetting_math

View File

@ -0,0 +1,70 @@
.. include:: global.rst
.. _typesetting_math:
Typesetting Math in ebooks
============================
The |app| ebook viewer has the ability to display math embedded in ebooks (ePub
and HTML files). You can typeset the math directly with TeX or MathML or
AsciiMath. The |app| viewer uses the excellent `MathJax
<http://www.mathjax.org>`_ library to do this. This is a brief tutorial on
creating ebooks with math in them that work well with the |app| viewer.
.. note::
This only applies to calibre version 0.8.66 and newer
A simple HTML file with mathematics
-------------------------------------
You can write mathematics inline inside a simple HTML file and the |app| viewer
will render it into properly typeset mathematics. In the example below, we use
TeX notation for mathematics. You will see that you can use normal TeX
commands, with the small caveat that ampersands and less than and greater than
signs have to be written as &amp; &lt; and &gt; respectively.
The first step is to tell |app| that this will contains maths. You do this by
adding the following snippet of code to the <head> section of the HTML file::
<script type="text/x-mathjax-config"></script>
That's it, now you can type mathematics just as you would in a .tex file. For
example, here are Lorentz's equations::
<h2>The Lorenz Equations</h2>
<p>
\begin{align}
\dot{x} &amp; = \sigma(y-x) \\
\dot{y} &amp; = \rho x - y - xz \\
\dot{z} &amp; = -\beta z + xy
\end{align}
</p>
This snippet looks like the following screen shot in the |app| viewer.
.. figure:: images/lorentz.png
:align: center
:guilabel:`The Lorentz Equations`
The complete HTML file, with more equations and inline mathematics is
reproduced below. You can convert this HTML file to EPUB in |app| to end up
with an ebook you can distribute easily to other people.
.. only:: online
Here is the generated EPUB file: `mathjax.epub <_static/mathjax.epub>`_.
.. literalinclude:: mathjax.html
:language: html
More information
-----------------
Since the |app| viewer uses the MathJax library to render mathematics, the best
place to find out more about math in ebooks and get help is the `MathJax
website <http://www.mathjax.org>`_.

View File

@ -21,8 +21,12 @@ class anan(BasicNewsRecipe):
remove_javascript = True remove_javascript = True
encoding = 'utf-8' encoding = 'utf-8'
remove_tags=[dict(name='a', attrs={'style':'width:110px; margin-top:0px;text-align:center;'}), remove_tags=[
dict(name='a', attrs={'style':'width:110px; margin-top:0px; margin-right:20px;text-align:center;'})] dict(name='a', attrs={'style':'width:110px; margin-top:0px;text-align:center;'}),
dict(name='a', attrs={'style':'width:110px; margin-top:0px; margin-right:20px;text-align:center;'}),
{'attrs':{'class':['article_links', 'header', 'body_right']}},
{'id':['crumbs']},
]
feeds = [ ('Anandtech', 'http://www.anandtech.com/rss/')] feeds = [ ('Anandtech', 'http://www.anandtech.com/rss/')]

View File

@ -1,6 +1,6 @@
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
import re import re
class Benchmark_pl(BasicNewsRecipe): class BenchmarkPl(BasicNewsRecipe):
title = u'Benchmark.pl' title = u'Benchmark.pl'
__author__ = 'fenuks' __author__ = 'fenuks'
description = u'benchmark.pl -IT site' description = u'benchmark.pl -IT site'
@ -14,7 +14,7 @@ class Benchmark_pl(BasicNewsRecipe):
preprocess_regexps = [(re.compile(ur'<h3><span style="font-size: small;">&nbsp;Zobacz poprzednie <a href="http://www.benchmark.pl/news/zestawienie/grupa_id/135">Opinie dnia:</a></span>.*</body>', re.DOTALL|re.IGNORECASE), lambda match: '</body>'), (re.compile(ur'Więcej o .*?</ul>', re.DOTALL|re.IGNORECASE), lambda match: '')] preprocess_regexps = [(re.compile(ur'<h3><span style="font-size: small;">&nbsp;Zobacz poprzednie <a href="http://www.benchmark.pl/news/zestawienie/grupa_id/135">Opinie dnia:</a></span>.*</body>', re.DOTALL|re.IGNORECASE), lambda match: '</body>'), (re.compile(ur'Więcej o .*?</ul>', re.DOTALL|re.IGNORECASE), lambda match: '')]
keep_only_tags=[dict(name='div', attrs={'class':['m_zwykly', 'gallery']})] keep_only_tags=[dict(name='div', attrs={'class':['m_zwykly', 'gallery']})]
remove_tags_after=dict(name='div', attrs={'class':'body'}) remove_tags_after=dict(name='div', attrs={'class':'body'})
remove_tags=[dict(name='div', attrs={'class':['kategoria', 'socialize', 'thumb', 'panelOcenaObserwowane', 'categoryNextToSocializeGallery']}), dict(name='table', attrs={'background':'http://www.benchmark.pl/uploads/backend_img/a/fotki_newsy/opinie_dnia/bg.png'}), dict(name='table', attrs={'width':'210', 'cellspacing':'1', 'cellpadding':'4', 'border':'0', 'align':'right'})] remove_tags=[dict(name='div', attrs={'class':['kategoria', 'socialize', 'thumb', 'panelOcenaObserwowane', 'categoryNextToSocializeGallery', 'breadcrumb']}), dict(name='table', attrs={'background':'http://www.benchmark.pl/uploads/backend_img/a/fotki_newsy/opinie_dnia/bg.png'}), dict(name='table', attrs={'width':'210', 'cellspacing':'1', 'cellpadding':'4', 'border':'0', 'align':'right'})]
INDEX= 'http://www.benchmark.pl' INDEX= 'http://www.benchmark.pl'
feeds = [(u'Aktualności', u'http://www.benchmark.pl/rss/aktualnosci-pliki.xml'), feeds = [(u'Aktualności', u'http://www.benchmark.pl/rss/aktualnosci-pliki.xml'),
(u'Testy i recenzje', u'http://www.benchmark.pl/rss/testy-recenzje-minirecenzje.xml')] (u'Testy i recenzje', u'http://www.benchmark.pl/rss/testy-recenzje-minirecenzje.xml')]

82
recipes/brecha.recipe Normal file
View File

@ -0,0 +1,82 @@
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, Darko Miletic <darko.miletic at gmail.com>'
'''
www.brecha.com.uy
'''
import urllib
from calibre.web.feeds.news import BasicNewsRecipe
class Brecha(BasicNewsRecipe):
title = 'Brecha Digital'
__author__ = 'Darko Miletic'
description = 'Brecha , Cultura ,Sociales , Separatas, Lupas, Vueltas de Montevideo y toda la infomacion que caracteriza a este semanario'
publisher = 'Brecha'
category = 'brecha, digital, prensa, uruguay, semanario, sociedad, politica, cultura'
oldest_article = 7
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
language = 'es_UY'
remove_empty_feeds = True
publication_type = 'magazine'
auto_cleanup = True
needs_subscription = 'optional'
masthead_url = 'http://www.brecha.com.uy/templates/ja_nex/themes/orange/images/logo.png'
extra_css = """
body{font-family: Arial,Helvetica,sans-serif }
img{margin-bottom: 0.4em; display:block}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
def get_browser(self):
br = BasicNewsRecipe.get_browser()
br.open('http://www.brecha.com.uy/index.php/acceder-miembros')
if self.username is not None and self.password is not None:
data = urllib.urlencode({ 'task':'login'
,'view':'register'
,'username':self.username
,'password':self.password
})
br.open('http://www.brecha.com.uy/index.php/index.php?option=com_osemsc&controller=register',data)
return br
remove_tags = [
dict(name=['meta','link']),
dict(name='div', attrs={'id':'js_ja'}),
dict(name='ul', attrs={'class':'actions'})
]
remove_attributes=['lang', 'border']
feeds = [
(u'Politica' , u'http://www.brecha.com.uy/index.php/politica-uruguaya?format=feed&type=rss'),
(u'Mundo' , u'http://www.brecha.com.uy/index.php/mundo?format=feed&type=rss'),
(u'Mapamundi', u'http://www.brecha.com.uy/index.php/mundo/mapamundi?format=feed&type=rss'),
(u'Cultura' , u'http://www.brecha.com.uy/index.php/cultura?format=feed&type=rss'),
(u'Vueltas de Montevideo', u'http://www.brecha.com.uy/index.php/cultura/vueltas-de-montevideo?format=feed&type=rss'),
(u'Secos y Mojados', u'http://www.brecha.com.uy/index.php/cultura/secos-y-mojados?format=feed&type=rss'),
(u'Literarias', u'http://www.brecha.com.uy/index.php/cultura/literarias?format=feed&type=rss'),
(u'Sociedad', u'http://www.brecha.com.uy/index.php/sociedad?format=feed&type=rss'),
(u'Especiales', u'http://www.brecha.com.uy/index.php/especiales?format=feed&type=rss'),
(u'Contratapa', u'http://www.brecha.com.uy/index.php/contratapa?format=feed&type=rss')
]
def print_version(self, url):
return url + '?tmpl=component&print=1&layout=default&page='
def get_cover_url(self):
soup = self.index_to_soup('http://www.brecha.com.uy/index.php')
for image in soup.findAll('img', alt=True):
if image['alt'].startswith('Tapa '):
return 'http://www.brecha.com.uy' + urllib.quote(image['src'])
return None

View File

@ -16,6 +16,7 @@ class BusinessSpectator(BasicNewsRecipe):
oldest_article = 2 oldest_article = 2
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
auto_cleanup = True
#delay = 1 #delay = 1
use_embedded_content = False use_embedded_content = False
encoding = 'utf8' encoding = 'utf8'
@ -32,11 +33,11 @@ class BusinessSpectator(BasicNewsRecipe):
,'linearize_tables': False ,'linearize_tables': False
} }
keep_only_tags = [dict(id='storyHeader'), dict(id='body-html')] #keep_only_tags = [dict(id='storyHeader'), dict(id='body-html')]
remove_tags = [dict(attrs={'class':'hql'})] #remove_tags = [dict(attrs={'class':'hql'})]
remove_attributes = ['width','height','style'] #remove_attributes = ['width','height','style']
feeds = [ feeds = [
('Top Stories', 'http://www.businessspectator.com.au/top-stories.rss'), ('Top Stories', 'http://www.businessspectator.com.au/top-stories.rss'),
@ -46,3 +47,4 @@ class BusinessSpectator(BasicNewsRecipe):
('Daily Dossier', 'http://www.businessspectator.com.au/bs.nsf/RSS?readform&type=kgb&cat=dossier'), ('Daily Dossier', 'http://www.businessspectator.com.au/bs.nsf/RSS?readform&type=kgb&cat=dossier'),
('Australia', 'http://www.businessspectator.com.au/bs.nsf/RSS?readform&type=region&cat=australia'), ('Australia', 'http://www.businessspectator.com.au/bs.nsf/RSS?readform&type=region&cat=australia'),
] ]

View File

@ -1,35 +1,314 @@
from calibre.web.feeds.news import BasicNewsRecipe #!/usr/bin/env python
# -*- coding: utf-8 -*-
class CalgaryHerald(BasicNewsRecipe): __license__ = 'GPL v3'
title = u'Calgary Herald'
oldest_article = 3 '''
max_articles_per_feed = 100 www.canada.com
'''
feeds = [ import re
(u'News', u'http://rss.canada.com/get/?F233'), from calibre.web.feeds.recipes import BasicNewsRecipe
(u'Calgary', u'http://www.calgaryherald.com/scripts/sp6query.aspx?catalog=cahr&tags=keyword|calgary&output=rss?link=http%3a%2f%2fwww.calgaryherald'), from calibre.ebooks.BeautifulSoup import Tag, BeautifulStoneSoup
(u'Alberta', u'http://www.calgaryherald.com/scripts/Sp6Query.aspx?catalog=CAHR&tags=Keyword|Alberta&output=rss?link=http%3A%2F%2Fwww.calgaryherald.com%2Fnews%2Falberta%2Findex.html'),
(u'Politics', u'http://rss.canada.com/get/?F7551'),
(u'National', u'http://rss.canada.com/get/?F7552'), class CanWestPaper(BasicNewsRecipe):
(u'World', u'http://rss.canada.com/get/?F7553'),
] postmedia_index_pages = [
__author__ = 'rty' (u'Headlines',u'/index.html'),
pubisher = 'Calgary Herald' (u'Ottawa & Area',u'/news/ottawa/index.html'),
description = 'Calgary, Alberta, Canada' (u'Vancouver',u'/news/vancouver/index.html'),
category = 'News, Calgary, Alberta, Canada' (u'Calgary',u'/news/calgary/index.html'),
(u'Edmonton',u'/news/edmonton/index.html'),
(u'Montreal',u'/news/montreal/index.html'),
remove_javascript = True (u'Fraser Valley',u'/news/fraser-valley/index.html'),
use_embedded_content = False (u'British Columbia',u'/news/bc/index.html'),
no_stylesheets = True (u'Alberta',u'/news/alberta/index.html'),
language = 'en_CA' (u'Canada',u'/news/canada/index.html'),
encoding = 'utf-8' (u'National',u'/news/national/index.html'),
conversion_options = {'linearize_tables':True} (u'Politics',u'/news/politics/index.html'),
##masthead_url = 'http://www.calgaryherald.com/index.html' (u'Insight',u'/news/insight/index.html'),
keep_only_tags = [ (u'Special Reports',u'/news/specialreports/index.html'),
dict(name='div', attrs={'id':'storyheader'}), (u'Gangs',u'/news/gangs/index.html'),
dict(name='div', attrs={'id':'storycontent'}) (u'Education',u'/news/education/index.html'),
(u'Health',u'/news/health/index.html'),
] (u'Environment',u'/news/environment/index.html'),
remove_tags_after = {'class':"story_tool_hr"} (u'World',u'/news/world/index.html'),
(u'Police Blotter',u'/news/crime-and-justice/index.html'),
(u'Crime',u'/news/blotter/index.html'),
(u'Around Town',u'/news/topic.html?t=keyword&q=Around+Town'),
(u'Diplomatica',u'/news/diplomatica/index.html'),
(u'Opinion',u'/opinion/index.html'),
(u'Columnists',u'/columnists/index.html'),
(u'Editorials',u'/opinion/editorials/index.html'),
(u'Letters',u'/opinion/letters/index.html'),
(u'Business',u'/business/index.html'),
(u'Sports',u'/sports/index.html'),
(u'Arts',u'/entertainment/index.html'),
(u'Life',u'/life/index.html'),
(u'Technology',u'/technology/index.html'),
(u'Travel',u'/travel/index.html'),
(u'Health',u'/health/index.html')
]
# un-comment the following six lines for the Vancouver Province
## title = u'Vancouver Province'
## url_prefix = 'http://www.theprovince.com'
## description = u'News from Vancouver, BC'
## std_logo_url = 'http://www.theprovince.com/images/logo_theprovince.jpg'
## logo_url = 'vplogo.jpg'
## fp_tag = 'CAN_TP'
# un-comment the following six lines for the Vancouver Sun
## title = u'Vancouver Sun'
## url_prefix = 'http://www.vancouversun.com'
## description = u'News from Vancouver, BC'
## std_logo_url = 'http://www.vancouversun.com/images/logo_vancouversun.jpg'
## logo_url = 'vslogo.jpg'
## fp_tag = 'CAN_VS'
# un-comment the following six lines for the Calgary Herald
title = u'Calgary Herald'
url_prefix = 'http://www.calgaryherald.com'
description = u'News from Calgary, AB'
std_logo_url = 'http://www.calgaryherald.com/images/logo_calgaryherald.jpg'
logo_url = 'chlogo.jpg'
fp_tag = 'CAN_CH'
# un-comment the following six lines for the Edmonton Journal
## title = u'Edmonton Journal'
## url_prefix = 'http://www.edmontonjournal.com'
## description = u'News from Edmonton, AB'
## std_logo_url = 'http://www.edmontonjournal.com/images/logo_edmontonjournal.jpg'
## logo_url = 'ejlogo.jpg'
## fp_tag = 'CAN_EJ'
# un-comment the following six lines for the Ottawa Citizen
## title = u'Ottawa Citizen'
## url_prefix = 'http://www.ottawacitizen.com'
## description = u'News from Ottawa, ON'
## std_logo_url = 'http://www.ottawacitizen.com/images/logo_ottawacitizen.jpg'
## logo_url = 'oclogo.jpg'
## fp_tag = 'CAN_OC'
# un-comment the following six lines for the Montreal Gazette
## title = u'Montreal Gazette'
## url_prefix = 'http://www.montrealgazette.com'
## description = u'News from Montreal, QC'
## std_logo_url = 'http://www.montrealgazette.com/images/logo_montrealgazette.jpg'
## logo_url = 'mglogo.jpg'
## fp_tag = 'CAN_MG'
Kindle_Fire=False
masthead_url = std_logo_url
url_list = []
language = 'en_CA'
__author__ = 'Nick Redding'
no_stylesheets = True
timefmt = ' [%b %d]'
encoding = 'utf-8'
extra_css = '''
.timestamp { font-size:xx-small; display: block; }
#storyheader { font-size: medium; }
#storyheader h1 { font-size: x-large; }
#storyheader h2 { font-size: small; font-style: italic; }
.byline { font-size:xx-small; }
#photocaption { font-size: xx-small; font-style: italic; font-weight: normal; }
.photocaption { font-size: xx-small; font-style: italic; font-weight: normal; }
#photocredit { font-size: xx-small; font-weight: normal; }'''
keep_only_tags = [dict(name='div', attrs={'id':'storyheader'}),dict(name='div', attrs={'id':'storycontent'}),dict(name='div', attrs={'id':'storyphoto'})]
remove_tags = [{'class':'comments'},
dict(name='div', attrs={'class':'navbar'}),dict(name='div', attrs={'class':'morelinks'}),
dict(name='h2', attrs={'id':'photocredit'}),
dict(name='div', attrs={'class':'viewmore'}),dict(name='li', attrs={'class':'email'}),
dict(name='div', attrs={'class':'story_tool_hr'}),dict(name='div', attrs={'class':'clear'}),
dict(name='div', attrs={'class':'story_tool'}),dict(name='div', attrs={'class':'copyright'}),
dict(name='div', attrs={'class':'rule_grey_solid'}),
dict(name='li', attrs={'class':'print'}),dict(name='li', attrs={'class':'share'}),dict(name='ul', attrs={'class':'bullet'})]
def get_cover_url(self):
from datetime import timedelta, date
cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str(date.today().day)+'/lg/'+self.fp_tag+'.jpg'
br = BasicNewsRecipe.get_browser()
daysback=1
try:
br.open(cover)
except:
while daysback<7:
cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str((date.today() - timedelta(days=daysback)).day)+'/lg/'+self.fp_tag+'.jpg'
br = BasicNewsRecipe.get_browser()
try:
br.open(cover)
except:
daysback = daysback+1
continue
break
if daysback==7:
self.log("\nCover unavailable")
cover = None
return cover
def prepare_masthead_image(self, path_to_image, out_path):
if self.Kindle_Fire:
from calibre.utils.magick import Image, create_canvas
img = Image()
img.open(path_to_image)
width, height = img.size
img2 = create_canvas(width, height)
img2.compose(img)
img2.save(out_path)
else:
BasicNewsRecipe.prepare_masthead_image(self, path_to_image, out_path)
def fixChars(self,string):
# Replace lsquo (\x91)
fixed = re.sub("\x91","",string)
# Replace rsquo (\x92)
fixed = re.sub("\x92","",fixed)
# Replace ldquo (\x93)
fixed = re.sub("\x93","“",fixed)
# Replace rdquo (\x94)
fixed = re.sub("\x94","”",fixed)
# Replace ndash (\x96)
fixed = re.sub("\x96","",fixed)
# Replace mdash (\x97)
fixed = re.sub("\x97","—",fixed)
fixed = re.sub("&#x2019;","",fixed)
return fixed
def massageNCXText(self, description):
# Kindle TOC descriptions won't render certain characters
if description:
massaged = unicode(BeautifulStoneSoup(description, convertEntities=BeautifulStoneSoup.HTML_ENTITIES))
# Replace '&' with '&'
massaged = re.sub("&","&", massaged)
return self.fixChars(massaged)
else:
return description
def populate_article_metadata(self, article, soup, first):
if first:
picdiv = soup.find('body').find('img')
if picdiv is not None:
self.add_toc_thumbnail(article,re.sub(r'links\\link\d+\\','',picdiv['src']))
xtitle = article.text_summary.strip()
if len(xtitle) == 0:
desc = soup.find('meta',attrs={'property':'og:description'})
if desc is not None:
article.summary = article.text_summary = desc['content']
def strip_anchors(self,soup):
paras = soup.findAll(True)
for para in paras:
aTags = para.findAll('a')
for a in aTags:
if a.img is None:
a.replaceWith(a.renderContents().decode('cp1252','replace'))
return soup
def preprocess_html(self,soup):
#delete empty id attributes--they screw up the TOC for unknown reasons
divtags = soup.findAll('div',attrs={'id':''})
if divtags:
for div in divtags:
del(div['id'])
pgall = soup.find('div',attrs={'id':'storyphoto'})
if pgall is not None: # photo gallery perhaps
if (soup.find('div',attrs={'id':'storycontent'}) is None):
allpics = Tag(soup,'div')
first_img = pgall.find('div','storyimage')
if first_img is not None:
first_img.extract()
tlist = pgall.find('div',attrs={'id':'relatedthumbs'})
if tlist is not None:
for atag in tlist.findAll('a'):
img = Tag(soup,'img')
srcpre, sep, srcpost = atag.img['src'].partition('?')
img['src'] = srcpre
pdesc = Tag(soup,'p')
pdesc.insert(0,atag.img['alt'])
pdesc['class']='photocaption'
div = Tag(soup,'div')
div.insert(0,pdesc)
div.insert(0,img)
allpics.append(div)
pgall.replaceWith(allpics)
for pg in soup.findAll('div',attrs={'id':'storyphoto'}):
pg.extract()
return self.strip_anchors(soup)
def parse_index(self):
articles = {}
ans = []
def handle_article(adiv,key):
h1tag = adiv.h1
if h1tag is not None:
atag = h1tag.a
if atag is not None:
url = atag['href']
if atag['href'].startswith('http'):
return
elif atag['href'].startswith('/'):
url = self.url_prefix+atag['href']
else:
url = self.url_prefix+'/'+atag['href']
if url in self.url_list:
return
self.url_list.append(url)
title = self.tag_to_string(atag,False)
if 'VIDEO' in title.upper():
return
if 'GALLERY' in title.upper():
return
if 'PHOTOS' in title.upper():
return
dtag = adiv.find('div','content')
description=''
print("URL "+url)
print("TITLE "+title)
if dtag is not None:
stag = dtag.span
if stag is not None:
if stag['class'] != 'timestamp':
description = self.tag_to_string(stag,False)
else:
description = self.tag_to_string(dtag,False)
print("DESCRIPTION: "+description)
if not articles.has_key(key):
articles[key] = []
articles[key].append(dict(title=title,url=url,date='',description=description,author='',content=''))
def parse_web_index(key, keyurl):
try:
soup = self.index_to_soup(self.url_prefix+keyurl)
except:
return
ans.append(key)
mainsoup = soup.find('div','bodywrapper')
footer = mainsoup.find(attrs={'id':'footerfeature'})
if footer is not None:
footer.extract()
print("Section: "+key)
for wdiv in mainsoup.findAll('div',attrs={'id':re.compile('^HorizontalFeatureSlider_1_Story')}):
handle_article(wdiv,key)
wdiv.extract()
for wdiv in mainsoup.findAll(attrs={'id':['featurewidget','textfeature','textlinks_timestamp']}):
for adiv in wdiv.findAll('div','featurecontent'):
handle_article(adiv,key)
for (k,url) in self.postmedia_index_pages:
parse_web_index(k,url)
ans = [(key, articles[key]) for key in ans if articles.has_key(key)]
return ans

38
recipes/conowego_pl.recipe Executable file
View File

@ -0,0 +1,38 @@
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup
class CoNowegoPl(BasicNewsRecipe):
title = u'conowego.pl'
__author__ = 'fenuks'
description = u'Nowy wortal technologiczny oraz gazeta internetowa. Testy najnowszych produktów, fachowe porady i recenzje. U nas znajdziesz wszystko o elektronice użytkowej !'
cover_url = 'http://www.conowego.pl/fileadmin/templates/main/images/logo_top.png'
category = 'IT, news'
language = 'pl'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
remove_empty_feeds = True
use_embedded_content = False
keep_only_tags = [dict(name='div', attrs={'class':'news_list single_view'})]
remove_tags = [dict(name='div', attrs={'class':['ni_bottom', 'ni_rank', 'ni_date']})]
feeds = [(u'Aktualno\u015bci', u'http://www.conowego.pl/rss/aktualnosci-5/?type=100'), (u'Gaming', u'http://www.conowego.pl/rss/gaming-6/?type=100'), (u'Porady', u'http://www.conowego.pl/rss/porady-3/?type=100'), (u'Testy', u'http://www.conowego.pl/rss/testy-2/?type=100')]
def preprocess_html(self, soup):
for i in soup.findAll('img'):
i.parent.insert(0, BeautifulSoup('<br />'))
i.insert(len(i), BeautifulSoup('<br />'))
self.append_page(soup, soup.body)
return soup
def append_page(self, soup, appendtag):
tag = appendtag.find('div', attrs={'class':'pages'})
if tag:
nexturls=tag.findAll('a')
for nexturl in nexturls[:-1]:
soup2 = self.index_to_soup('http://www.conowego.pl/' + nexturl['href'])
pagetext = soup2.find(attrs={'class':'ni_content'})
pos = len(appendtag.contents)
appendtag.insert(pos, pagetext)
for r in appendtag.findAll(attrs={'class':['pages', 'paginationWrap']}):
r.extract()

32
recipes/dot_net.recipe Normal file
View File

@ -0,0 +1,32 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from calibre.web.feeds.news import BasicNewsRecipe
import re
class NetMagazineRecipe (BasicNewsRecipe):
__author__ = u'Marc Busqué <marc@lamarciana.com>'
__url__ = 'http://www.lamarciana.com'
__version__ = '1.0'
__license__ = 'GPL v3'
__copyright__ = u'2012, Marc Busqué <marc@lamarciana.com>'
title = u'.net magazine'
description = u'net is the worlds best-selling magazine for web designers and developers, featuring tutorials from leading agencies, interviews with the webs biggest names, and agenda-setting features on the hottest issues affecting the internet today.'
language = 'en'
tags = 'web development, software'
oldest_article = 7
remove_empty_feeds = True
no_stylesheets = True
cover_url = u'http://media.netmagazine.futurecdn.net/sites/all/themes/netmag/logo.png'
keep_only_tags = [
dict(name='article', attrs={'class': re.compile('^node.*$', re.IGNORECASE)})
]
remove_tags = [
dict(name='span', attrs={'class': 'comment-count'}),
dict(name='div', attrs={'class': 'item-list share-links'}),
dict(name='footer'),
]
remove_attributes = ['border', 'cellspacing', 'align', 'cellpadding', 'colspan', 'valign', 'vspace', 'hspace', 'alt', 'width', 'height', 'style']
extra_css = 'img {max-width: 100%; display: block; margin: auto;} .captioned-image div {text-align: center; font-style: italic;}'
feeds = [
(u'.net', u'http://feeds.feedburner.com/net/topstories'),
]

View File

@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
__license__='GPL v3'
__author__='grzegorz.maj@dziennik.krakow.pl>'
'''
http://dziennikpolski24.pl
Author: grzegorz.maj@dziennik.krakow.pl
'''
from calibre.web.feeds.news import BasicNewsRecipe
class DziennikPolski24(BasicNewsRecipe):
title=u'Dziennik Polski'
publisher=u'Grupa Polskapresse'
__author__='grzegorz.maj'
description=u'Wiadomości z wydania Dziennika Polskiego'
oldest_article=1
max_articles_per_feed=50
needs_subscription=True
remove_javascript=True
no_stylesheets=True
use_embedded_content=False
remove_empty_feeds=True
extra_css='.date{margin-top: 4em;} .logo_author{margin-left:0.5em;}'
publication_type='newspaper'
cover_url='http://www.dziennikpolski24.pl/_p/images/logoDP24-b.gif'
INDEX='http://dziennikpolski24.pl/'
encoding='utf-8'
language='pl'
keep_only_tags=[
dict(name = 'div', attrs = {'class':['toolbar']})
, dict(name = 'h1')
, dict(name = 'h2', attrs = {'class':['teaser']})
, dict(name = 'div', attrs = {'class':['picture']})
, dict(name = 'div', attrs = {'id':['showContent']})
, dict(name = 'div', attrs = {'class':['paging']})
, dict(name = 'div', attrs = {'class':['wykupTresc']})
]
remove_tags=[
]
feeds=[
(u'Kraj', u'http://www.dziennikpolski24.pl/rss/feed/1151')
, (u'Świat', u'http://www.dziennikpolski24.pl/rss/feed/1153')
, (u'Gospodarka', u'http://www.dziennikpolski24.pl/rss/feed/1154')
, (u'Małopolska', u'http://www.dziennikpolski24.pl/rss/feed/1155')
, (u'Kultura', u'http://www.dziennikpolski24.pl/rss/feed/1156')
, (u'Opinie', u'http://www.dziennikpolski24.pl/rss/feed/1158')
, (u'Kronika Nowohucka', u'http://www.dziennikpolski24.pl/rss/feed/1656')
, (u'Na bieżąco', u'http://www.dziennikpolski24.pl/rss/feed/1543')
, (u'Londyn 2012', u'http://www.dziennikpolski24.pl/rss/feed/2545')
, (u'Piłka nożna', u'http://www.dziennikpolski24.pl/rss/feed/2196')
, (u'Siatkówka', u'http://www.dziennikpolski24.pl/rss/feed/2197')
, (u'Koszykówka', u'http://www.dziennikpolski24.pl/rss/feed/2198')
, (u'Tenis', u'http://www.dziennikpolski24.pl/rss/feed/2199')
, (u'Formuła 1', u'http://www.dziennikpolski24.pl/rss/feed/2203')
, (u'Lekkoatletyka', u'http://www.dziennikpolski24.pl/rss/feed/2204')
, (u'Żużel', u'http://www.dziennikpolski24.pl/rss/feed/2200')
, (u'Sporty motorowe', u'http://www.dziennikpolski24.pl/rss/feed/2206')
, (u'Publicystyka sportowa', u'http://www.dziennikpolski24.pl/rss/feed/2201')
, (u'Kolarstwo', u'http://www.dziennikpolski24.pl/rss/feed/2205')
, (u'Inne', u'http://www.dziennikpolski24.pl/rss/feed/2202')
, (u'Miasto Kraków', u'http://www.dziennikpolski24.pl/rss/feed/1784')
, (u'Region nowosądecki', u'http://www.dziennikpolski24.pl/rss/feed/1795')
, (u'Region Małopolski Zachodniej', u'http://www.dziennikpolski24.pl/rss/feed/1793')
, (u'Region tarnowski', u'http://www.dziennikpolski24.pl/rss/feed/1797')
, (u'Region podhalański', u'http://www.dziennikpolski24.pl/rss/feed/1789')
, (u'Region olkuski', u'http://www.dziennikpolski24.pl/rss/feed/1670')
, (u'Region miechowski', u'http://www.dziennikpolski24.pl/rss/feed/1806')
, (u'Region podkrakowski', u'http://www.dziennikpolski24.pl/rss/feed/1787')
, (u'Region proszowicki', u'http://www.dziennikpolski24.pl/rss/feed/1804')
, (u'Region wielicki', u'http://www.dziennikpolski24.pl/rss/feed/1802')
, (u'Region podbeskidzki', u'http://www.dziennikpolski24.pl/rss/feed/1791')
, (u'Region myślenicki', u'http://www.dziennikpolski24.pl/rss/feed/1800')
, (u'Autosalon', u'http://www.dziennikpolski24.pl/rss/feed/1294')
, (u'Kariera', u'http://www.dziennikpolski24.pl/rss/feed/1289')
, (u'Przegląd nieruchomości', u'http://www.dziennikpolski24.pl/rss/feed/1281')
, (u'Magnes', u'http://www.dziennikpolski24.pl/rss/feed/1283')
, (u'Magazyn Piątek', u'http://www.dziennikpolski24.pl/rss/feed/1293')
, (u'Pejzaż rodzinny', u'http://www.dziennikpolski24.pl/rss/feed/1274')
, (u'Podróże', u'http://www.dziennikpolski24.pl/rss/feed/1275')
, (u'Konsument', u'http://www.dziennikpolski24.pl/rss/feed/1288')
]
def append_page(self, soup, appendtag):
loop=False
tag=soup.find('div', attrs = {'class':'paging'})
if tag:
loop=True
li_nks=tag.findAll('li')
appendtag.find('div', attrs = {'class':'paging'}).extract()
if appendtag.find('ul', attrs = {'class':'menuf'}):
appendtag.find('ul', attrs = {'class':'menuf'}).extract()
while loop:
loop=False
for li_nk in li_nks:
link_tag=li_nk.contents[0].contents[0].string
if u'następna' in link_tag:
soup2=self.index_to_soup(self.INDEX+li_nk.contents[0]['href'])
if soup2.find('div', attrs = {'id':'showContent'}):
pagetext=soup2.find('div', attrs = {'id':'showContent'})
pos=len(appendtag.contents)
appendtag.insert(pos, pagetext)
if soup2.find('div', attrs = {'class':'rightbar'}):
pagecont=soup2.find('div', attrs = {'class':'rightbar'})
tag=pagecont.find('div', attrs = {'class':'paging'})
li_nks=tag.findAll('li')
loop=True
def get_browser(self):
br=BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
br.open('http://www.dziennikpolski24.pl/pl/moje-konto/950606-loguj.html')
br.select_form(nr = 1)
br["user_login[login]"]=self.username
br['user_login[pass]']=self.password
br.submit()
return br
def preprocess_html(self, soup):
self.append_page(soup, soup.body)
return soup

View File

@ -1,105 +1,136 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__license__ = 'GPL v3' __license__ = 'GPL v3'
''' '''
www.canada.com www.canada.com
''' '''
import re import re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.recipes import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, Tag
class CanWestPaper(BasicNewsRecipe): class CanWestPaper(BasicNewsRecipe):
# un-comment the following four lines for the Victoria Times Colonist postmedia_index_pages = [
## title = u'Victoria Times Colonist' (u'Headlines',u'/index.html'),
## url_prefix = 'http://www.timescolonist.com' (u'Ottawa & Area',u'/news/ottawa/index.html'),
## description = u'News from Victoria, BC' (u'Vancouver',u'/news/vancouver/index.html'),
## fp_tag = 'CAN_TC' (u'Calgary',u'/news/calgary/index.html'),
(u'Edmonton',u'/news/edmonton/index.html'),
(u'Montreal',u'/news/montreal/index.html'),
(u'Fraser Valley',u'/news/fraser-valley/index.html'),
(u'British Columbia',u'/news/bc/index.html'),
(u'Alberta',u'/news/alberta/index.html'),
(u'Canada',u'/news/canada/index.html'),
(u'National',u'/news/national/index.html'),
(u'Politics',u'/news/politics/index.html'),
(u'Insight',u'/news/insight/index.html'),
(u'Special Reports',u'/news/specialreports/index.html'),
(u'Gangs',u'/news/gangs/index.html'),
(u'Education',u'/news/education/index.html'),
(u'Health',u'/news/health/index.html'),
(u'Environment',u'/news/environment/index.html'),
(u'World',u'/news/world/index.html'),
(u'Police Blotter',u'/news/crime-and-justice/index.html'),
(u'Crime',u'/news/blotter/index.html'),
(u'Around Town',u'/news/topic.html?t=keyword&q=Around+Town'),
(u'Diplomatica',u'/news/diplomatica/index.html'),
(u'Opinion',u'/opinion/index.html'),
(u'Columnists',u'/columnists/index.html'),
(u'Editorials',u'/opinion/editorials/index.html'),
(u'Letters',u'/opinion/letters/index.html'),
(u'Business',u'/business/index.html'),
(u'Sports',u'/sports/index.html'),
(u'Arts',u'/entertainment/index.html'),
(u'Life',u'/life/index.html'),
(u'Technology',u'/technology/index.html'),
(u'Travel',u'/travel/index.html'),
(u'Health',u'/health/index.html')
]
# un-comment the following four lines for the Vancouver Province
# un-comment the following six lines for the Vancouver Province
## title = u'Vancouver Province' ## title = u'Vancouver Province'
## url_prefix = 'http://www.theprovince.com' ## url_prefix = 'http://www.theprovince.com'
## description = u'News from Vancouver, BC' ## description = u'News from Vancouver, BC'
## fp_tag = 'CAN_VP' ## std_logo_url = 'http://www.theprovince.com/images/logo_theprovince.jpg'
## logo_url = 'vplogo.jpg'
## fp_tag = 'CAN_TP'
# un-comment the following four lines for the Vancouver Sun # un-comment the following six lines for the Vancouver Sun
## title = u'Vancouver Sun' ## title = u'Vancouver Sun'
## url_prefix = 'http://www.vancouversun.com' ## url_prefix = 'http://www.vancouversun.com'
## description = u'News from Vancouver, BC' ## description = u'News from Vancouver, BC'
## std_logo_url = 'http://www.vancouversun.com/images/logo_vancouversun.jpg'
## logo_url = 'vslogo.jpg'
## fp_tag = 'CAN_VS' ## fp_tag = 'CAN_VS'
# un-comment the following four lines for the Edmonton Journal # un-comment the following six lines for the Calgary Herald
title = u'Edmonton Journal'
url_prefix = 'http://www.edmontonjournal.com'
description = u'News from Edmonton, AB'
fp_tag = 'CAN_EJ'
# un-comment the following four lines for the Calgary Herald
## title = u'Calgary Herald' ## title = u'Calgary Herald'
## url_prefix = 'http://www.calgaryherald.com' ## url_prefix = 'http://www.calgaryherald.com'
## description = u'News from Calgary, AB' ## description = u'News from Calgary, AB'
## std_logo_url = 'http://www.calgaryherald.com/images/logo_calgaryherald.jpg'
## logo_url = 'chlogo.jpg'
## fp_tag = 'CAN_CH' ## fp_tag = 'CAN_CH'
# un-comment the following four lines for the Regina Leader-Post # un-comment the following six lines for the Edmonton Journal
## title = u'Regina Leader-Post' title = u'Edmonton Journal'
## url_prefix = 'http://www.leaderpost.com' url_prefix = 'http://www.edmontonjournal.com'
## description = u'News from Regina, SK' description = u'News from Edmonton, AB'
## fp_tag = '' std_logo_url = 'http://www.edmontonjournal.com/images/logo_edmontonjournal.jpg'
logo_url = 'ejlogo.jpg'
fp_tag = 'CAN_EJ'
# un-comment the following four lines for the Saskatoon Star-Phoenix # un-comment the following six lines for the Ottawa Citizen
## title = u'Saskatoon Star-Phoenix'
## url_prefix = 'http://www.thestarphoenix.com'
## description = u'News from Saskatoon, SK'
## fp_tag = ''
# un-comment the following four lines for the Windsor Star
## title = u'Windsor Star'
## url_prefix = 'http://www.windsorstar.com'
## description = u'News from Windsor, ON'
## fp_tag = 'CAN_'
# un-comment the following four lines for the Ottawa Citizen
## title = u'Ottawa Citizen' ## title = u'Ottawa Citizen'
## url_prefix = 'http://www.ottawacitizen.com' ## url_prefix = 'http://www.ottawacitizen.com'
## description = u'News from Ottawa, ON' ## description = u'News from Ottawa, ON'
## std_logo_url = 'http://www.ottawacitizen.com/images/logo_ottawacitizen.jpg'
## logo_url = 'oclogo.jpg'
## fp_tag = 'CAN_OC' ## fp_tag = 'CAN_OC'
# un-comment the following four lines for the Montreal Gazette # un-comment the following six lines for the Montreal Gazette
## title = u'Montreal Gazette' ## title = u'Montreal Gazette'
## url_prefix = 'http://www.montrealgazette.com' ## url_prefix = 'http://www.montrealgazette.com'
## description = u'News from Montreal, QC' ## description = u'News from Montreal, QC'
## std_logo_url = 'http://www.montrealgazette.com/images/logo_montrealgazette.jpg'
## logo_url = 'mglogo.jpg'
## fp_tag = 'CAN_MG' ## fp_tag = 'CAN_MG'
Kindle_Fire=False
masthead_url = std_logo_url
url_list = []
language = 'en_CA' language = 'en_CA'
__author__ = 'Nick Redding' __author__ = 'Nick Redding'
no_stylesheets = True no_stylesheets = True
timefmt = ' [%b %d]' timefmt = ' [%b %d]'
encoding = 'utf-8'
extra_css = ''' extra_css = '''
.timestamp { font-size:xx-small; display: block; } .timestamp { font-size:xx-small; display: block; }
#storyheader { font-size: medium; } #storyheader { font-size: medium; }
#storyheader h1 { font-size: x-large; } #storyheader h1 { font-size: x-large; }
#storyheader h2 { font-size: large; font-style: italic; } #storyheader h2 { font-size: small; font-style: italic; }
.byline { font-size:xx-small; } .byline { font-size:xx-small; }
#photocaption { font-size: small; font-style: italic } #photocaption { font-size: xx-small; font-style: italic; font-weight: normal; }
#photocredit { font-size: xx-small; }''' .photocaption { font-size: xx-small; font-style: italic; font-weight: normal; }
keep_only_tags = [dict(name='div', attrs={'id':'storyheader'}),dict(name='div', attrs={'id':'storycontent'})] #photocredit { font-size: xx-small; font-weight: normal; }'''
keep_only_tags = [dict(name='div', attrs={'id':'storyheader'}),dict(name='div', attrs={'id':'storycontent'}),dict(name='div', attrs={'id':'storyphoto'})]
remove_tags = [{'class':'comments'}, remove_tags = [{'class':'comments'},
dict(name='div', attrs={'class':'navbar'}),dict(name='div', attrs={'class':'morelinks'}), dict(name='div', attrs={'class':'navbar'}),dict(name='div', attrs={'class':'morelinks'}),
dict(name='h2', attrs={'id':'photocredit'}),
dict(name='div', attrs={'class':'viewmore'}),dict(name='li', attrs={'class':'email'}), dict(name='div', attrs={'class':'viewmore'}),dict(name='li', attrs={'class':'email'}),
dict(name='div', attrs={'class':'story_tool_hr'}),dict(name='div', attrs={'class':'clear'}), dict(name='div', attrs={'class':'story_tool_hr'}),dict(name='div', attrs={'class':'clear'}),
dict(name='div', attrs={'class':'story_tool'}),dict(name='div', attrs={'class':'copyright'}), dict(name='div', attrs={'class':'story_tool'}),dict(name='div', attrs={'class':'copyright'}),
dict(name='div', attrs={'class':'rule_grey_solid'}), dict(name='div', attrs={'class':'rule_grey_solid'}),
dict(name='li', attrs={'class':'print'}),dict(name='li', attrs={'class':'share'}),dict(name='ul', attrs={'class':'bullet'})] dict(name='li', attrs={'class':'print'}),dict(name='li', attrs={'class':'share'}),dict(name='ul', attrs={'class':'bullet'})]
def get_cover_url(self): def get_cover_url(self):
from datetime import timedelta, date from datetime import timedelta, date
if self.fp_tag=='':
return None
cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str(date.today().day)+'/lg/'+self.fp_tag+'.jpg' cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str(date.today().day)+'/lg/'+self.fp_tag+'.jpg'
br = BasicNewsRecipe.get_browser() br = BasicNewsRecipe.get_browser()
daysback=1 daysback=1
@ -120,6 +151,18 @@ class CanWestPaper(BasicNewsRecipe):
cover = None cover = None
return cover return cover
def prepare_masthead_image(self, path_to_image, out_path):
if self.Kindle_Fire:
from calibre.utils.magick import Image, create_canvas
img = Image()
img.open(path_to_image)
width, height = img.size
img2 = create_canvas(width, height)
img2.compose(img)
img2.save(out_path)
else:
BasicNewsRecipe.prepare_masthead_image(self, path_to_image, out_path)
def fixChars(self,string): def fixChars(self,string):
# Replace lsquo (\x91) # Replace lsquo (\x91)
fixed = re.sub("\x91","",string) fixed = re.sub("\x91","",string)
@ -166,55 +209,106 @@ class CanWestPaper(BasicNewsRecipe):
a.replaceWith(a.renderContents().decode('cp1252','replace')) a.replaceWith(a.renderContents().decode('cp1252','replace'))
return soup return soup
def preprocess_html(self, soup):
def preprocess_html(self,soup):
#delete empty id attributes--they screw up the TOC for unknown reasons
divtags = soup.findAll('div',attrs={'id':''})
if divtags:
for div in divtags:
del(div['id'])
pgall = soup.find('div',attrs={'id':'storyphoto'})
if pgall is not None: # photo gallery perhaps
if (soup.find('div',attrs={'id':'storycontent'}) is None):
allpics = Tag(soup,'div')
first_img = pgall.find('div','storyimage')
if first_img is not None:
first_img.extract()
tlist = pgall.find('div',attrs={'id':'relatedthumbs'})
if tlist is not None:
for atag in tlist.findAll('a'):
img = Tag(soup,'img')
srcpre, sep, srcpost = atag.img['src'].partition('?')
img['src'] = srcpre
pdesc = Tag(soup,'p')
pdesc.insert(0,atag.img['alt'])
pdesc['class']='photocaption'
div = Tag(soup,'div')
div.insert(0,pdesc)
div.insert(0,img)
allpics.append(div)
pgall.replaceWith(allpics)
for pg in soup.findAll('div',attrs={'id':'storyphoto'}):
pg.extract()
return self.strip_anchors(soup) return self.strip_anchors(soup)
def parse_index(self): def parse_index(self):
soup = self.index_to_soup(self.url_prefix+'/news/todays-paper/index.html')
articles = {} articles = {}
key = 'News' ans = []
ans = ['News']
# Find each instance of class="sectiontitle", class="featurecontent"
for divtag in soup.findAll('div',attrs={'class' : ["section_title02","featurecontent"]}):
#self.log(" div class = %s" % divtag['class'])
if divtag['class'].startswith('section_title'):
# div contains section title
if not divtag.h3:
continue
key = self.tag_to_string(divtag.h3,False)
ans.append(key)
self.log("Section name %s" % key)
continue
# div contains article data
h1tag = divtag.find('h1')
if not h1tag:
continue
atag = h1tag.find('a',href=True)
if not atag:
continue
url = self.url_prefix+'/news/todays-paper/'+atag['href']
#self.log("Section %s" % key)
#self.log("url %s" % url)
title = self.tag_to_string(atag,False)
#self.log("title %s" % title)
pubdate = ''
description = ''
ptag = divtag.find('p');
if ptag:
description = self.tag_to_string(ptag,False)
#self.log("description %s" % description)
author = ''
autag = divtag.find('h4')
if autag:
author = self.tag_to_string(autag,False)
#self.log("author %s" % author)
if not articles.has_key(key):
articles[key] = []
articles[key].append(dict(title=title,url=url,date=pubdate,description=description,author=author,content=''))
def handle_article(adiv,key):
h1tag = adiv.h1
if h1tag is not None:
atag = h1tag.a
if atag is not None:
url = atag['href']
if atag['href'].startswith('http'):
return
elif atag['href'].startswith('/'):
url = self.url_prefix+atag['href']
else:
url = self.url_prefix+'/'+atag['href']
if url in self.url_list:
return
self.url_list.append(url)
title = self.tag_to_string(atag,False)
if 'VIDEO' in title.upper():
return
if 'GALLERY' in title.upper():
return
if 'PHOTOS' in title.upper():
return
dtag = adiv.find('div','content')
description=''
print("URL "+url)
print("TITLE "+title)
if dtag is not None:
stag = dtag.span
if stag is not None:
if stag['class'] != 'timestamp':
description = self.tag_to_string(stag,False)
else:
description = self.tag_to_string(dtag,False)
print("DESCRIPTION: "+description)
if not articles.has_key(key):
articles[key] = []
articles[key].append(dict(title=title,url=url,date='',description=description,author='',content=''))
def parse_web_index(key, keyurl):
try:
soup = self.index_to_soup(self.url_prefix+keyurl)
except:
return
ans.append(key)
mainsoup = soup.find('div','bodywrapper')
footer = mainsoup.find(attrs={'id':'footerfeature'})
if footer is not None:
footer.extract()
print("Section: "+key)
for wdiv in mainsoup.findAll('div',attrs={'id':re.compile('^HorizontalFeatureSlider_1_Story')}):
handle_article(wdiv,key)
wdiv.extract()
for wdiv in mainsoup.findAll(attrs={'id':['featurewidget','textfeature','textlinks_timestamp']}):
for adiv in wdiv.findAll('div','featurecontent'):
handle_article(adiv,key)
for (k,url) in self.postmedia_index_pages:
parse_web_index(k,url)
ans = [(key, articles[key]) for key in ans if articles.has_key(key)] ans = [(key, articles[key]) for key in ans if articles.has_key(key)]
return ans return ans

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = u'2012, Artur Stachecki <artur.stachecki@gmail.com>'
from calibre.web.feeds.news import BasicNewsRecipe
class swiatczytnikow(BasicNewsRecipe):
title = u'eKundelek'
description = u'Najsympatyczniejszy blog o e-czytnikach Kindle'
language = 'pl'
__author__ = u'Artur Stachecki'
oldest_article = 7
max_articles_per_feed = 100
remove_tags = [dict(name = 'div', attrs = {'class' : 'feedflare'})]
feeds = [(u'Wpisy', u'http://feeds.feedburner.com/Ekundelekpl?format=xml')]

View File

@ -18,15 +18,15 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
keep_only_tags = [ keep_only_tags = [
dict(name='h1'), dict(name='h1'),
dict(name='img',attrs={'id' : 'ctl00_Body_imgMainImage'}), dict(name='img',attrs={'id' : 'ctl00_Body_imgMainImage'}),
dict(name='div',attrs={'id' : ['articleLeft']}), dict(name='div',attrs={'id' : ['profileLeft','articleLeft','profileRight','profileBody']}),
dict(name='div',attrs={'class' : ['imagesCenterArticle','containerCenterArticle','articleBody']}), dict(name='div',attrs={'class' : ['imagesCenterArticle','containerCenterArticle','articleBody',]}),
] ]
#remove_tags = [ remove_tags = [
#dict(attrs={'class' : ['player']}), dict(attrs={'id' : ['ctl00_Body_divSlideShow' ]}),
#] ]
feeds = [ feeds = [
(u'Homepage 1',u'http://feed43.com/6655867614547036.xml'), (u'Homepage 1',u'http://feed43.com/6655867614547036.xml'),
(u'Homepage 2',u'http://feed43.com/4167731873103110.xml'), (u'Homepage 2',u'http://feed43.com/4167731873103110.xml'),
@ -34,7 +34,7 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
(u'Homepage 4',u'http://feed43.com/6550421522527341.xml'), (u'Homepage 4',u'http://feed43.com/6550421522527341.xml'),
(u'Funny - The Very Best Of The Internet',u'http://feed43.com/4538510106331565.xml'), (u'Funny - The Very Best Of The Internet',u'http://feed43.com/4538510106331565.xml'),
(u'Gaming',u'http://feed43.com/6537162612465672.xml'), (u'Gaming',u'http://feed43.com/6537162612465672.xml'),
(u'Girls',u'http://feed43.com/3674777224513254.xml'), (u'Girls',u'http://feed43.com/4574262733341068.xml'),# edit link http://feed43.com/feed.html?name=4574262733341068
] ]
extra_css = ''' extra_css = '''

View File

@ -1,6 +1,7 @@
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
import re
class Filmweb_pl(BasicNewsRecipe): from calibre.ebooks.BeautifulSoup import BeautifulSoup
class FilmWebPl(BasicNewsRecipe):
title = u'FilmWeb' title = u'FilmWeb'
__author__ = 'fenuks' __author__ = 'fenuks'
description = 'FilmWeb - biggest polish movie site' description = 'FilmWeb - biggest polish movie site'
@ -12,8 +13,9 @@ class Filmweb_pl(BasicNewsRecipe):
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets= True no_stylesheets= True
remove_empty_feeds=True remove_empty_feeds=True
preprocess_regexps = [(re.compile(u'\(kliknij\,\ aby powiększyć\)', re.IGNORECASE), lambda m: ''), ]#(re.compile(ur' | ', re.IGNORECASE), lambda m: '')]
extra_css = '.hdrBig {font-size:22px;} ul {list-style-type:none; padding: 0; margin: 0;}' extra_css = '.hdrBig {font-size:22px;} ul {list-style-type:none; padding: 0; margin: 0;}'
remove_tags= [dict(name='div', attrs={'class':['recommendOthers']}), dict(name='ul', attrs={'class':'fontSizeSet'})] remove_tags= [dict(name='div', attrs={'class':['recommendOthers']}), dict(name='ul', attrs={'class':'fontSizeSet'}), dict(attrs={'class':'userSurname anno'})]
keep_only_tags= [dict(name='h1', attrs={'class':['hdrBig', 'hdrEntity']}), dict(name='div', attrs={'class':['newsInfo', 'newsInfoSmall', 'reviewContent description']})] keep_only_tags= [dict(name='h1', attrs={'class':['hdrBig', 'hdrEntity']}), dict(name='div', attrs={'class':['newsInfo', 'newsInfoSmall', 'reviewContent description']})]
feeds = [(u'Wszystkie newsy', u'http://www.filmweb.pl/feed/news/latest'), feeds = [(u'Wszystkie newsy', u'http://www.filmweb.pl/feed/news/latest'),
(u'News / Filmy w produkcji', 'http://www.filmweb.pl/feed/news/category/filminproduction'), (u'News / Filmy w produkcji', 'http://www.filmweb.pl/feed/news/category/filminproduction'),
@ -31,18 +33,22 @@ class Filmweb_pl(BasicNewsRecipe):
(u'News / Kino polskie', u'http://www.filmweb.pl/feed/news/category/polish.cinema'), (u'News / Kino polskie', u'http://www.filmweb.pl/feed/news/category/polish.cinema'),
(u'News / Telewizja', u'http://www.filmweb.pl/feed/news/category/tv'), (u'News / Telewizja', u'http://www.filmweb.pl/feed/news/category/tv'),
(u'Recenzje redakcji', u'http://www.filmweb.pl/feed/reviews/latest'), (u'Recenzje redakcji', u'http://www.filmweb.pl/feed/reviews/latest'),
(u'Recenzje użytkowników', u'http://www.filmweb.pl/feed/user-reviews/latest')] (u'Recenzje użytkowników', u'http://www.filmweb.pl/feed/user-reviews/latest')
]
def skip_ad_pages(self, soup): def skip_ad_pages(self, soup):
skip_tag = soup.find('a', attrs={'class':'welcomeScreenButton'}) skip_tag = soup.find('a', attrs={'class':'welcomeScreenButton'})
if skip_tag is not None: if skip_tag is not None:
self.log.warn('skip_tag')
self.log.warn(skip_tag)
return self.index_to_soup(skip_tag['href'], raw=True) return self.index_to_soup(skip_tag['href'], raw=True)
def preprocess_html(self, soup): def preprocess_html(self, soup):
for a in soup('a'): for a in soup('a'):
if a.has_key('href') and 'http://' not in a['href'] and 'https://' not in a['href']: if a.has_key('href') and 'http://' not in a['href'] and 'https://' not in a['href']:
a['href']=self.index + a['href'] a['href']=self.index + a['href']
return soup for i in soup.findAll('a', attrs={'class':'fn'}):
i.insert(len(i), BeautifulSoup('<br />'))
for i in soup.findAll('sup'):
if not i.string or i.string.startswith('(kliknij'):
i.extract()
return soup

View File

@ -1,6 +1,6 @@
from calibre.web.feeds.recipes import BasicNewsRecipe from calibre.web.feeds.recipes import BasicNewsRecipe
class Gry_online_pl(BasicNewsRecipe): class GryOnlinePl(BasicNewsRecipe):
title = u'Gry-Online.pl' title = u'Gry-Online.pl'
__author__ = 'fenuks' __author__ = 'fenuks'
description = 'Gry-Online.pl - computer games' description = 'Gry-Online.pl - computer games'
@ -21,17 +21,18 @@ class Gry_online_pl(BasicNewsRecipe):
tag = appendtag.find('div', attrs={'class':'n5p'}) tag = appendtag.find('div', attrs={'class':'n5p'})
if tag: if tag:
nexturls=tag.findAll('a') nexturls=tag.findAll('a')
for nexturl in nexturls[1:]: url_part = soup.find('link', attrs={'rel':'canonical'})['href']
try: url_part = url_part[25:].rpartition('?')[0]
soup2 = self.index_to_soup('http://www.gry-online.pl/S020.asp'+ nexturl['href']) for nexturl in nexturls[1:-1]:
except: soup2 = self.index_to_soup('http://www.gry-online.pl/' + url_part + nexturl['href'])
soup2 = self.index_to_soup('http://www.gry-online.pl/S022.asp'+ nexturl['href'])
pagetext = soup2.find(attrs={'class':'gc660'}) pagetext = soup2.find(attrs={'class':'gc660'})
for r in pagetext.findAll(name='header'): for r in pagetext.findAll(name='header'):
r.extract() r.extract()
for r in pagetext.findAll(attrs={'itemprop':'description'}):
r.extract()
pos = len(appendtag.contents) pos = len(appendtag.contents)
appendtag.insert(pos, pagetext) appendtag.insert(pos, pagetext)
for r in appendtag.findAll(attrs={'class':['n5p', 'add-info', 'twitter-share-button']}): for r in appendtag.findAll(attrs={'class':['n5p', 'add-info', 'twitter-share-button', 'lista lista3 lista-gry']}):
r.extract() r.extract()

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>, Armin Geller'
'''
Fetch High Country News - Blogs
'''
from calibre.web.feeds.news import BasicNewsRecipe
class HighCountryNewsBlogs(BasicNewsRecipe):
title = u'High Country News - Blogs'
description = u'High Country News - Blogs (RSS Version)'
__author__ = 'Armin Geller' # 2012-08-01
publisher = 'High Country News'
category = 'news, politics, Germany'
timefmt = ' [%a, %d %b %Y]'
language = 'en'
encoding = 'UTF-8'
publication_type = 'newspaper'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
auto_cleanup = True
remove_javascript = True
use_embedded_content = False
masthead_url = 'http://www.hcn.org/logo.jpg'
cover_source = 'http://www.hcn.org'
def get_cover_url(self):
cover_source_soup = self.index_to_soup(self.cover_source)
preview_image_div = cover_source_soup.find(attrs={'class':' portaltype-Plone Site content--hcn template-homepage_view'})
return preview_image_div.div.img['src']
feeds = [
(u'From the Blogs', u'http://feeds.feedburner.com/hcn/FromTheBlogs?format=xml'),
(u'Heard around the West', u'http://feeds.feedburner.com/hcn/heard?format=xml'),
(u'The GOAT Blog', u'http://feeds.feedburner.com/hcn/goat?format=xml'),
(u'The Range', u'http://feeds.feedburner.com/hcn/range?format=xml'),
]
def print_version(self, url):
return url

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

36
recipes/linux_journal.recipe Executable file
View File

@ -0,0 +1,36 @@
from calibre.web.feeds.news import BasicNewsRecipe
class LinuxJournal(BasicNewsRecipe):
title = u'Linux Journal'
__author__ = 'fenuks'
description = u'The monthly magazine of the Linux community, promoting the use of Linux worldwide.'
cover_url = 'http://www.linuxjournal.com/files/linuxjournal.com/ufiles/logo-lj.jpg'
category = 'IT, Linux'
language = 'en'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
remove_empty_feeds = True
keep_only_tags=[dict(id='content-inner')]
remove_tags_after= dict(attrs={'class':'user-signature clear-block'})
remove_tags=[dict(attrs={'class':['user-signature clear-block', 'breadcrumb', 'terms terms-inline']})]
feeds = [(u'Front Page', u'http://feeds.feedburner.com/linuxjournalcom'), (u'News', u'http://feeds.feedburner.com/LinuxJournal-BreakingNews'), (u'Blogs', u'http://www.linuxjournal.com/blog/feed'), (u'Audio/Video', u'http://www.linuxjournal.com/taxonomy/term/28/0/feed'), (u'Community', u'http://www.linuxjournal.com/taxonomy/term/18/0/feed'), (u'Education', u'http://www.linuxjournal.com/taxonomy/term/25/0/feed'), (u'Embedded', u'http://www.linuxjournal.com/taxonomy/term/27/0/feed'), (u'Hardware', u'http://www.linuxjournal.com/taxonomy/term/23/0/feed'), (u'HOWTOs', u'http://www.linuxjournal.com/taxonomy/term/19/0/feed'), (u'International', u'http://www.linuxjournal.com/taxonomy/term/30/0/feed'), (u'Security', u'http://www.linuxjournal.com/taxonomy/term/31/0/feed'), (u'Software', u'http://www.linuxjournal.com/taxonomy/term/17/0/feed'), (u'Sysadmin', u'http://www.linuxjournal.com/taxonomy/term/21/0/feed'), (u'Webmaster', u'http://www.linuxjournal.com/taxonomy/term/24/0/feed')]
def append_page(self, soup, appendtag):
next = appendtag.find('li', attrs={'class':'pager-next'})
while next:
nexturl = next.a['href']
appendtag.find('div', attrs={'class':'links'}).extract()
soup2 = self.index_to_soup('http://www.linuxjournal.com'+ nexturl)
pagetext = soup2.find(attrs={'class':'node-inner'}).find(attrs={'class':'content'})
next = appendtag.find('li', attrs={'class':'pager-next'})
pos = len(appendtag.contents)
appendtag.insert(pos, pagetext)
tag = appendtag.find('div', attrs={'class':'links'})
if tag:
tag.extract()
def preprocess_html(self, soup):
self.append_page(soup, soup.body)
return soup

33
recipes/list_apart.recipe Normal file
View File

@ -0,0 +1,33 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from calibre.web.feeds.news import BasicNewsRecipe
class AListApart (BasicNewsRecipe):
__author__ = u'Marc Busqué <marc@lamarciana.com>'
__url__ = 'http://www.lamarciana.com'
__version__ = '1.0'
__license__ = 'GPL v3'
__copyright__ = u'2012, Marc Busqué <marc@lamarciana.com>'
title = u'A List Apart'
description = u'A List Apart Magazine (ISSN: 1534-0295) explores the design, development, and meaning of web content, with a special focus on web standards and best practices.'
language = 'en'
tags = 'web development, software'
oldest_article = 120
remove_empty_feeds = True
no_stylesheets = True
encoding = 'utf8'
cover_url = u'http://alistapart.com/pix/alalogo.gif'
keep_only_tags = [
dict(name='div', attrs={'id': 'content'})
]
remove_tags = [
dict(name='ul', attrs={'id': 'metastuff'}),
dict(name='div', attrs={'class': 'discuss'}),
dict(name='div', attrs={'class': 'discuss'}),
dict(name='div', attrs={'id': 'learnmore'}),
]
remove_attributes = ['border', 'cellspacing', 'align', 'cellpadding', 'colspan', 'valign', 'vspace', 'hspace', 'alt', 'width', 'height']
extra_css = u'img {max-width: 100%; display: block; margin: auto;} #authorbio img {float: left; margin-right: 2%;}'
feeds = [
(u'A List Apart', u'http://www.alistapart.com/site/rss'),
]

View File

@ -1,31 +1,42 @@
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1306097511(BasicNewsRecipe): class AdvancedUserRecipe1306097511(BasicNewsRecipe):
title = u'Metro UK' title = u'Metro UK'
description = 'News as provide by The Metro -UK' description = 'Author Dave Asbury : News as provide by The Metro -UK'
#timefmt = '' #timefmt = ''
__author__ = 'Dave Asbury' __author__ = 'Dave Asbury'
#last update 9/6/12 #last update 4/8/12
cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/276636_117118184990145_2132092232_n.jpg' cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/276636_117118184990145_2132092232_n.jpg'
#no_stylesheets = True no_stylesheets = True
oldest_article = 1 oldest_article = 1
max_articles_per_feed = 10 max_articles_per_feed = 12
remove_empty_feeds = True remove_empty_feeds = True
remove_javascript = True remove_javascript = True
auto_cleanup = True #auto_cleanup = True
encoding = 'UTF-8' encoding = 'UTF-8'
cover_url ='http://profile.ak.fbcdn.net/hprofile-ak-snc4/157897_117118184990145_840702264_n.jpg'
language = 'en_GB' language = 'en_GB'
masthead_url = 'http://e-edition.metro.co.uk/images/metro_logo.gif' masthead_url = 'http://e-edition.metro.co.uk/images/metro_logo.gif'
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:1.6em;}
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:1.2em;}
p{font-family:Arial,Helvetica,sans-serif;font-size:1.0em;}
body{font-family:Helvetica,Arial,sans-serif;font-size:1.0em;}
'''
keep_only_tags = [ keep_only_tags = [
#dict(name='h1'),
] #dict(name='h2'),
#dict(name='div', attrs={'class' : ['row','article','img-cnt figure','clrd']})
#dict(name='h3'),
#dict(attrs={'class' : 'BText'}),
]
remove_tags = [ remove_tags = [
dict(name='span',attrs={'class' : 'share'}),
dict(name='li'),
dict(attrs={'class' : ['twitter-share-button','header-forms','hdr-lnks','close','art-rgt','fd-gr1-b clrd google-article','news m12 clrd clr-b p5t shareBtm','item-ds csl-3-img news','c-1of3 c-last','c-1of1','pd','item-ds csl-3-img sport']}),
dict(attrs={'id' : ['','sky-left','sky-right','ftr-nav','and-ftr','notificationList','logo','miniLogo','comments-news','metro_extras']})
] ]
remove_tags_before = dict(name='h1')
#remove_tags_after = dict(attrs={'id':['topic-buttons']})
feeds = [ feeds = [
(u'News', u'http://www.metro.co.uk/rss/news/'), (u'Money', u'http://www.metro.co.uk/rss/money/'), (u'Sport', u'http://www.metro.co.uk/rss/sport/'), (u'Film', u'http://www.metro.co.uk/rss/metrolife/film/'), (u'Music', u'http://www.metro.co.uk/rss/metrolife/music/'), (u'TV', u'http://www.metro.co.uk/rss/tv/'), (u'Showbiz', u'http://www.metro.co.uk/rss/showbiz/'), (u'Weird News', u'http://www.metro.co.uk/rss/weird/'), (u'Travel', u'http://www.metro.co.uk/rss/travel/'), (u'Lifestyle', u'http://www.metro.co.uk/rss/lifestyle/'), (u'Books', u'http://www.metro.co.uk/rss/lifestyle/books/'), (u'Food', u'http://www.metro.co.uk/rss/lifestyle/restaurants/')] (u'News', u'http://www.metro.co.uk/rss/news/'), (u'Money', u'http://www.metro.co.uk/rss/money/'), (u'Sport', u'http://www.metro.co.uk/rss/sport/'), (u'Film', u'http://www.metro.co.uk/rss/metrolife/film/'), (u'Music', u'http://www.metro.co.uk/rss/metrolife/music/'), (u'TV', u'http://www.metro.co.uk/rss/tv/'), (u'Showbiz', u'http://www.metro.co.uk/rss/showbiz/'), (u'Weird News', u'http://www.metro.co.uk/rss/weird/'), (u'Travel', u'http://www.metro.co.uk/rss/travel/'), (u'Lifestyle', u'http://www.metro.co.uk/rss/lifestyle/'), (u'Books', u'http://www.metro.co.uk/rss/lifestyle/books/'), (u'Food', u'http://www.metro.co.uk/rss/lifestyle/restaurants/')]
extra_css = '''
body{ text-align: justify; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:normal;}
'''

View File

@ -1,48 +1,314 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
__license__ = 'GPL v3' __license__ = 'GPL v3'
''' '''
www.canada.com www.canada.com
''' '''
import re
from calibre.web.feeds.recipes import BasicNewsRecipe from calibre.web.feeds.recipes import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, Tag
class CanWestPaper(BasicNewsRecipe): class CanWestPaper(BasicNewsRecipe):
# un-comment the following three lines for the Montreal Gazette postmedia_index_pages = [
(u'Headlines',u'/index.html'),
(u'Ottawa & Area',u'/news/ottawa/index.html'),
(u'Vancouver',u'/news/vancouver/index.html'),
(u'Calgary',u'/news/calgary/index.html'),
(u'Edmonton',u'/news/edmonton/index.html'),
(u'Montreal',u'/news/montreal/index.html'),
(u'Fraser Valley',u'/news/fraser-valley/index.html'),
(u'British Columbia',u'/news/bc/index.html'),
(u'Alberta',u'/news/alberta/index.html'),
(u'Canada',u'/news/canada/index.html'),
(u'National',u'/news/national/index.html'),
(u'Politics',u'/news/politics/index.html'),
(u'Insight',u'/news/insight/index.html'),
(u'Special Reports',u'/news/specialreports/index.html'),
(u'Gangs',u'/news/gangs/index.html'),
(u'Education',u'/news/education/index.html'),
(u'Health',u'/news/health/index.html'),
(u'Environment',u'/news/environment/index.html'),
(u'World',u'/news/world/index.html'),
(u'Police Blotter',u'/news/crime-and-justice/index.html'),
(u'Crime',u'/news/blotter/index.html'),
(u'Around Town',u'/news/topic.html?t=keyword&q=Around+Town'),
(u'Diplomatica',u'/news/diplomatica/index.html'),
(u'Opinion',u'/opinion/index.html'),
(u'Columnists',u'/columnists/index.html'),
(u'Editorials',u'/opinion/editorials/index.html'),
(u'Letters',u'/opinion/letters/index.html'),
(u'Business',u'/business/index.html'),
(u'Sports',u'/sports/index.html'),
(u'Arts',u'/entertainment/index.html'),
(u'Life',u'/life/index.html'),
(u'Technology',u'/technology/index.html'),
(u'Travel',u'/travel/index.html'),
(u'Health',u'/health/index.html')
]
# un-comment the following six lines for the Vancouver Province
## title = u'Vancouver Province'
## url_prefix = 'http://www.theprovince.com'
## description = u'News from Vancouver, BC'
## std_logo_url = 'http://www.theprovince.com/images/logo_theprovince.jpg'
## logo_url = 'vplogo.jpg'
## fp_tag = 'CAN_TP'
# un-comment the following six lines for the Vancouver Sun
## title = u'Vancouver Sun'
## url_prefix = 'http://www.vancouversun.com'
## description = u'News from Vancouver, BC'
## std_logo_url = 'http://www.vancouversun.com/images/logo_vancouversun.jpg'
## logo_url = 'vslogo.jpg'
## fp_tag = 'CAN_VS'
# un-comment the following six lines for the Calgary Herald
## title = u'Calgary Herald'
## url_prefix = 'http://www.calgaryherald.com'
## description = u'News from Calgary, AB'
## std_logo_url = 'http://www.calgaryherald.com/images/logo_calgaryherald.jpg'
## logo_url = 'chlogo.jpg'
## fp_tag = 'CAN_CH'
# un-comment the following six lines for the Edmonton Journal
## title = u'Edmonton Journal'
## url_prefix = 'http://www.edmontonjournal.com'
## description = u'News from Edmonton, AB'
## std_logo_url = 'http://www.edmontonjournal.com/images/logo_edmontonjournal.jpg'
## logo_url = 'ejlogo.jpg'
## fp_tag = 'CAN_EJ'
# un-comment the following six lines for the Ottawa Citizen
## title = u'Ottawa Citizen'
## url_prefix = 'http://www.ottawacitizen.com'
## description = u'News from Ottawa, ON'
## std_logo_url = 'http://www.ottawacitizen.com/images/logo_ottawacitizen.jpg'
## logo_url = 'oclogo.jpg'
## fp_tag = 'CAN_OC'
# un-comment the following six lines for the Montreal Gazette
title = u'Montreal Gazette' title = u'Montreal Gazette'
url_prefix = 'http://www.montrealgazette.com'
description = u'News from Montreal, QC' description = u'News from Montreal, QC'
std_logo_url = 'http://www.montrealgazette.com/images/logo_montrealgazette.jpg'
logo_url = 'mglogo.jpg'
fp_tag = 'CAN_MG'
Kindle_Fire=False
masthead_url = std_logo_url
url_list = []
language = 'en_CA' language = 'en_CA'
__author__ = 'Nick Redding' __author__ = 'Nick Redding'
no_stylesheets = True no_stylesheets = True
auto_cleanup = True timefmt = ' [%b %d]'
auto_cleanup_keep = '//*[@id="imageBox"]' encoding = 'utf-8'
timefmt = ' [%b %d]'
extra_css = ''' extra_css = '''
.timestamp { font-size:xx-small; display: block; } .timestamp { font-size:xx-small; display: block; }
#storyheader { font-size: medium; } #storyheader { font-size: medium; }
#storyheader h1 { font-size: x-large; } #storyheader h1 { font-size: x-large; }
#storyheader h2 { font-size: large; font-style: italic; } #storyheader h2 { font-size: small; font-style: italic; }
.byline { font-size:xx-small; } .byline { font-size:xx-small; }
#photocaption { font-size: small; font-style: italic } #photocaption { font-size: xx-small; font-style: italic; font-weight: normal; }
#photocredit { font-size: xx-small; }''' .photocaption { font-size: xx-small; font-style: italic; font-weight: normal; }
#photocredit { font-size: xx-small; font-weight: normal; }'''
keep_only_tags = [dict(name='div', attrs={'id':'storyheader'}),dict(name='div', attrs={'id':'storycontent'}),dict(name='div', attrs={'id':'storyphoto'})]
remove_tags = [{'class':'comments'},
dict(name='div', attrs={'class':'navbar'}),dict(name='div', attrs={'class':'morelinks'}),
dict(name='h2', attrs={'id':'photocredit'}),
dict(name='div', attrs={'class':'viewmore'}),dict(name='li', attrs={'class':'email'}),
dict(name='div', attrs={'class':'story_tool_hr'}),dict(name='div', attrs={'class':'clear'}),
dict(name='div', attrs={'class':'story_tool'}),dict(name='div', attrs={'class':'copyright'}),
dict(name='div', attrs={'class':'rule_grey_solid'}),
dict(name='li', attrs={'class':'print'}),dict(name='li', attrs={'class':'share'}),dict(name='ul', attrs={'class':'bullet'})]
feeds = [ def get_cover_url(self):
('News', from datetime import timedelta, date
'http://rss.canada.com/get/?F297'), cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str(date.today().day)+'/lg/'+self.fp_tag+'.jpg'
('Sports', br = BasicNewsRecipe.get_browser()
'http://rss.canada.com/get/?F299'), daysback=1
('Entertainment', try:
'http://rss.canada.com/get/?F7366'), br.open(cover)
('Business', except:
'http://rss.canada.com/get/?F6939'), while daysback<7:
] cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str((date.today() - timedelta(days=daysback)).day)+'/lg/'+self.fp_tag+'.jpg'
br = BasicNewsRecipe.get_browser()
try:
br.open(cover)
except:
daysback = daysback+1
continue
break
if daysback==7:
self.log("\nCover unavailable")
cover = None
return cover
def prepare_masthead_image(self, path_to_image, out_path):
if self.Kindle_Fire:
from calibre.utils.magick import Image, create_canvas
img = Image()
img.open(path_to_image)
width, height = img.size
img2 = create_canvas(width, height)
img2.compose(img)
img2.save(out_path)
else:
BasicNewsRecipe.prepare_masthead_image(self, path_to_image, out_path)
def fixChars(self,string):
# Replace lsquo (\x91)
fixed = re.sub("\x91","",string)
# Replace rsquo (\x92)
fixed = re.sub("\x92","",fixed)
# Replace ldquo (\x93)
fixed = re.sub("\x93","“",fixed)
# Replace rdquo (\x94)
fixed = re.sub("\x94","”",fixed)
# Replace ndash (\x96)
fixed = re.sub("\x96","",fixed)
# Replace mdash (\x97)
fixed = re.sub("\x97","—",fixed)
fixed = re.sub("&#x2019;","",fixed)
return fixed
def massageNCXText(self, description):
# Kindle TOC descriptions won't render certain characters
if description:
massaged = unicode(BeautifulStoneSoup(description, convertEntities=BeautifulStoneSoup.HTML_ENTITIES))
# Replace '&' with '&'
massaged = re.sub("&","&", massaged)
return self.fixChars(massaged)
else:
return description
def populate_article_metadata(self, article, soup, first):
if first:
picdiv = soup.find('body').find('img')
if picdiv is not None:
self.add_toc_thumbnail(article,re.sub(r'links\\link\d+\\','',picdiv['src']))
xtitle = article.text_summary.strip()
if len(xtitle) == 0:
desc = soup.find('meta',attrs={'property':'og:description'})
if desc is not None:
article.summary = article.text_summary = desc['content']
def strip_anchors(self,soup):
paras = soup.findAll(True)
for para in paras:
aTags = para.findAll('a')
for a in aTags:
if a.img is None:
a.replaceWith(a.renderContents().decode('cp1252','replace'))
return soup
def preprocess_html(self,soup):
#delete empty id attributes--they screw up the TOC for unknown reasons
divtags = soup.findAll('div',attrs={'id':''})
if divtags:
for div in divtags:
del(div['id'])
pgall = soup.find('div',attrs={'id':'storyphoto'})
if pgall is not None: # photo gallery perhaps
if (soup.find('div',attrs={'id':'storycontent'}) is None):
allpics = Tag(soup,'div')
first_img = pgall.find('div','storyimage')
if first_img is not None:
first_img.extract()
tlist = pgall.find('div',attrs={'id':'relatedthumbs'})
if tlist is not None:
for atag in tlist.findAll('a'):
img = Tag(soup,'img')
srcpre, sep, srcpost = atag.img['src'].partition('?')
img['src'] = srcpre
pdesc = Tag(soup,'p')
pdesc.insert(0,atag.img['alt'])
pdesc['class']='photocaption'
div = Tag(soup,'div')
div.insert(0,pdesc)
div.insert(0,img)
allpics.append(div)
pgall.replaceWith(allpics)
for pg in soup.findAll('div',attrs={'id':'storyphoto'}):
pg.extract()
return self.strip_anchors(soup)
def parse_index(self):
articles = {}
ans = []
def handle_article(adiv,key):
h1tag = adiv.h1
if h1tag is not None:
atag = h1tag.a
if atag is not None:
url = atag['href']
if atag['href'].startswith('http'):
return
elif atag['href'].startswith('/'):
url = self.url_prefix+atag['href']
else:
url = self.url_prefix+'/'+atag['href']
if url in self.url_list:
return
self.url_list.append(url)
title = self.tag_to_string(atag,False)
if 'VIDEO' in title.upper():
return
if 'GALLERY' in title.upper():
return
if 'PHOTOS' in title.upper():
return
dtag = adiv.find('div','content')
description=''
print("URL "+url)
print("TITLE "+title)
if dtag is not None:
stag = dtag.span
if stag is not None:
if stag['class'] != 'timestamp':
description = self.tag_to_string(stag,False)
else:
description = self.tag_to_string(dtag,False)
print("DESCRIPTION: "+description)
if not articles.has_key(key):
articles[key] = []
articles[key].append(dict(title=title,url=url,date='',description=description,author='',content=''))
def parse_web_index(key, keyurl):
try:
soup = self.index_to_soup(self.url_prefix+keyurl)
except:
return
ans.append(key)
mainsoup = soup.find('div','bodywrapper')
footer = mainsoup.find(attrs={'id':'footerfeature'})
if footer is not None:
footer.extract()
print("Section: "+key)
for wdiv in mainsoup.findAll('div',attrs={'id':re.compile('^HorizontalFeatureSlider_1_Story')}):
handle_article(wdiv,key)
wdiv.extract()
for wdiv in mainsoup.findAll(attrs={'id':['featurewidget','textfeature','textlinks_timestamp']}):
for adiv in wdiv.findAll('div','featurecontent'):
handle_article(adiv,key)
for (k,url) in self.postmedia_index_pages:
parse_web_index(k,url)
ans = [(key, articles[key]) for key in ans if articles.has_key(key)]
return ans

View File

@ -1,3 +1,4 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class NaTemat(BasicNewsRecipe): class NaTemat(BasicNewsRecipe):
@ -8,8 +9,9 @@ class NaTemat(BasicNewsRecipe):
description = u'informacje, komentarze, opinie' description = u'informacje, komentarze, opinie'
category = 'news' category = 'news'
language = 'pl' language = 'pl'
preprocess_regexps = [(re.compile(ur'Czytaj też\:.*?</a>', re.IGNORECASE), lambda m: ''), (re.compile(ur'Zobacz też\:.*?</a>', re.IGNORECASE), lambda m: ''), (re.compile(ur'Czytaj więcej\:.*?</a>', re.IGNORECASE), lambda m: ''), (re.compile(ur'Czytaj również\:.*?</a>', re.IGNORECASE), lambda m: '')]
cover_url= 'http://blog.plona.pl/wp-content/uploads/2012/05/natemat.png' cover_url= 'http://blog.plona.pl/wp-content/uploads/2012/05/natemat.png'
no_stylesheets = True no_stylesheets = True
keep_only_tags= [dict(id='main')] keep_only_tags= [dict(id='main')]
remove_tags= [dict(attrs={'class':['button', 'block-inside style_default', 'article-related']})] remove_tags= [dict(attrs={'class':['button', 'block-inside style_default', 'article-related', 'user-header', 'links']}), dict(name='img', attrs={'class':'indent'})]
feeds = [(u'Artyku\u0142y', u'http://natemat.pl/rss/wszystkie')] feeds = [(u'Artyku\u0142y', u'http://natemat.pl/rss/wszystkie')]

View File

@ -1,105 +1,136 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__license__ = 'GPL v3' __license__ = 'GPL v3'
''' '''
www.canada.com www.canada.com
''' '''
import re import re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.recipes import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, Tag
class CanWestPaper(BasicNewsRecipe): class CanWestPaper(BasicNewsRecipe):
# un-comment the following four lines for the Victoria Times Colonist postmedia_index_pages = [
## title = u'Victoria Times Colonist' (u'Headlines',u'/index.html'),
## url_prefix = 'http://www.timescolonist.com' (u'Ottawa & Area',u'/news/ottawa/index.html'),
## description = u'News from Victoria, BC' (u'Vancouver',u'/news/vancouver/index.html'),
## fp_tag = 'CAN_TC' (u'Calgary',u'/news/calgary/index.html'),
(u'Edmonton',u'/news/edmonton/index.html'),
(u'Montreal',u'/news/montreal/index.html'),
(u'Fraser Valley',u'/news/fraser-valley/index.html'),
(u'British Columbia',u'/news/bc/index.html'),
(u'Alberta',u'/news/alberta/index.html'),
(u'Canada',u'/news/canada/index.html'),
(u'National',u'/news/national/index.html'),
(u'Politics',u'/news/politics/index.html'),
(u'Insight',u'/news/insight/index.html'),
(u'Special Reports',u'/news/specialreports/index.html'),
(u'Gangs',u'/news/gangs/index.html'),
(u'Education',u'/news/education/index.html'),
(u'Health',u'/news/health/index.html'),
(u'Environment',u'/news/environment/index.html'),
(u'World',u'/news/world/index.html'),
(u'Police Blotter',u'/news/crime-and-justice/index.html'),
(u'Crime',u'/news/blotter/index.html'),
(u'Around Town',u'/news/topic.html?t=keyword&q=Around+Town'),
(u'Diplomatica',u'/news/diplomatica/index.html'),
(u'Opinion',u'/opinion/index.html'),
(u'Columnists',u'/columnists/index.html'),
(u'Editorials',u'/opinion/editorials/index.html'),
(u'Letters',u'/opinion/letters/index.html'),
(u'Business',u'/business/index.html'),
(u'Sports',u'/sports/index.html'),
(u'Arts',u'/entertainment/index.html'),
(u'Life',u'/life/index.html'),
(u'Technology',u'/technology/index.html'),
(u'Travel',u'/travel/index.html'),
(u'Health',u'/health/index.html')
]
# un-comment the following four lines for the Vancouver Province
# un-comment the following six lines for the Vancouver Province
## title = u'Vancouver Province' ## title = u'Vancouver Province'
## url_prefix = 'http://www.theprovince.com' ## url_prefix = 'http://www.theprovince.com'
## description = u'News from Vancouver, BC' ## description = u'News from Vancouver, BC'
## fp_tag = 'CAN_VP' ## std_logo_url = 'http://www.theprovince.com/images/logo_theprovince.jpg'
## logo_url = 'vplogo.jpg'
## fp_tag = 'CAN_TP'
# un-comment the following four lines for the Vancouver Sun # un-comment the following six lines for the Vancouver Sun
## title = u'Vancouver Sun' ## title = u'Vancouver Sun'
## url_prefix = 'http://www.vancouversun.com' ## url_prefix = 'http://www.vancouversun.com'
## description = u'News from Vancouver, BC' ## description = u'News from Vancouver, BC'
## std_logo_url = 'http://www.vancouversun.com/images/logo_vancouversun.jpg'
## logo_url = 'vslogo.jpg'
## fp_tag = 'CAN_VS' ## fp_tag = 'CAN_VS'
# un-comment the following four lines for the Edmonton Journal # un-comment the following six lines for the Calgary Herald
## title = u'Edmonton Journal'
## url_prefix = 'http://www.edmontonjournal.com'
## description = u'News from Edmonton, AB'
## fp_tag = 'CAN_EJ'
# un-comment the following four lines for the Calgary Herald
## title = u'Calgary Herald' ## title = u'Calgary Herald'
## url_prefix = 'http://www.calgaryherald.com' ## url_prefix = 'http://www.calgaryherald.com'
## description = u'News from Calgary, AB' ## description = u'News from Calgary, AB'
## std_logo_url = 'http://www.calgaryherald.com/images/logo_calgaryherald.jpg'
## logo_url = 'chlogo.jpg'
## fp_tag = 'CAN_CH' ## fp_tag = 'CAN_CH'
# un-comment the following four lines for the Regina Leader-Post # un-comment the following six lines for the Edmonton Journal
## title = u'Regina Leader-Post' ## title = u'Edmonton Journal'
## url_prefix = 'http://www.leaderpost.com' ## url_prefix = 'http://www.edmontonjournal.com'
## description = u'News from Regina, SK' ## description = u'News from Edmonton, AB'
## fp_tag = '' ## std_logo_url = 'http://www.edmontonjournal.com/images/logo_edmontonjournal.jpg'
## logo_url = 'ejlogo.jpg'
## fp_tag = 'CAN_EJ'
# un-comment the following four lines for the Saskatoon Star-Phoenix # un-comment the following six lines for the Ottawa Citizen
## title = u'Saskatoon Star-Phoenix'
## url_prefix = 'http://www.thestarphoenix.com'
## description = u'News from Saskatoon, SK'
## fp_tag = ''
# un-comment the following four lines for the Windsor Star
## title = u'Windsor Star'
## url_prefix = 'http://www.windsorstar.com'
## description = u'News from Windsor, ON'
## fp_tag = 'CAN_'
# un-comment the following four lines for the Ottawa Citizen
title = u'Ottawa Citizen' title = u'Ottawa Citizen'
url_prefix = 'http://www.ottawacitizen.com' url_prefix = 'http://www.ottawacitizen.com'
description = u'News from Ottawa, ON' description = u'News from Ottawa, ON'
std_logo_url = 'http://www.ottawacitizen.com/images/logo_ottawacitizen.jpg'
logo_url = 'oclogo.jpg'
fp_tag = 'CAN_OC' fp_tag = 'CAN_OC'
# un-comment the following four lines for the Montreal Gazette # un-comment the following six lines for the Montreal Gazette
## title = u'Montreal Gazette' ## title = u'Montreal Gazette'
## url_prefix = 'http://www.montrealgazette.com' ## url_prefix = 'http://www.montrealgazette.com'
## description = u'News from Montreal, QC' ## description = u'News from Montreal, QC'
## std_logo_url = 'http://www.montrealgazette.com/images/logo_montrealgazette.jpg'
## logo_url = 'mglogo.jpg'
## fp_tag = 'CAN_MG' ## fp_tag = 'CAN_MG'
Kindle_Fire=False
masthead_url = std_logo_url
url_list = []
language = 'en_CA' language = 'en_CA'
__author__ = 'Nick Redding' __author__ = 'Nick Redding'
no_stylesheets = True no_stylesheets = True
timefmt = ' [%b %d]' timefmt = ' [%b %d]'
encoding = 'utf-8'
extra_css = ''' extra_css = '''
.timestamp { font-size:xx-small; display: block; } .timestamp { font-size:xx-small; display: block; }
#storyheader { font-size: medium; } #storyheader { font-size: medium; }
#storyheader h1 { font-size: x-large; } #storyheader h1 { font-size: x-large; }
#storyheader h2 { font-size: large; font-style: italic; } #storyheader h2 { font-size: small; font-style: italic; }
.byline { font-size:xx-small; } .byline { font-size:xx-small; }
#photocaption { font-size: small; font-style: italic } #photocaption { font-size: xx-small; font-style: italic; font-weight: normal; }
#photocredit { font-size: xx-small; }''' .photocaption { font-size: xx-small; font-style: italic; font-weight: normal; }
keep_only_tags = [dict(name='div', attrs={'id':'storyheader'}),dict(name='div', attrs={'id':'storycontent'})] #photocredit { font-size: xx-small; font-weight: normal; }'''
keep_only_tags = [dict(name='div', attrs={'id':'storyheader'}),dict(name='div', attrs={'id':'storycontent'}),dict(name='div', attrs={'id':'storyphoto'})]
remove_tags = [{'class':'comments'}, remove_tags = [{'class':'comments'},
dict(name='div', attrs={'class':'navbar'}),dict(name='div', attrs={'class':'morelinks'}), dict(name='div', attrs={'class':'navbar'}),dict(name='div', attrs={'class':'morelinks'}),
dict(name='h2', attrs={'id':'photocredit'}),
dict(name='div', attrs={'class':'viewmore'}),dict(name='li', attrs={'class':'email'}), dict(name='div', attrs={'class':'viewmore'}),dict(name='li', attrs={'class':'email'}),
dict(name='div', attrs={'class':'story_tool_hr'}),dict(name='div', attrs={'class':'clear'}), dict(name='div', attrs={'class':'story_tool_hr'}),dict(name='div', attrs={'class':'clear'}),
dict(name='div', attrs={'class':'story_tool'}),dict(name='div', attrs={'class':'copyright'}), dict(name='div', attrs={'class':'story_tool'}),dict(name='div', attrs={'class':'copyright'}),
dict(name='div', attrs={'class':'rule_grey_solid'}), dict(name='div', attrs={'class':'rule_grey_solid'}),
dict(name='li', attrs={'class':'print'}),dict(name='li', attrs={'class':'share'}),dict(name='ul', attrs={'class':'bullet'})] dict(name='li', attrs={'class':'print'}),dict(name='li', attrs={'class':'share'}),dict(name='ul', attrs={'class':'bullet'})]
def get_cover_url(self): def get_cover_url(self):
from datetime import timedelta, date from datetime import timedelta, date
if self.fp_tag=='':
return None
cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str(date.today().day)+'/lg/'+self.fp_tag+'.jpg' cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str(date.today().day)+'/lg/'+self.fp_tag+'.jpg'
br = BasicNewsRecipe.get_browser() br = BasicNewsRecipe.get_browser()
daysback=1 daysback=1
@ -120,6 +151,18 @@ class CanWestPaper(BasicNewsRecipe):
cover = None cover = None
return cover return cover
def prepare_masthead_image(self, path_to_image, out_path):
if self.Kindle_Fire:
from calibre.utils.magick import Image, create_canvas
img = Image()
img.open(path_to_image)
width, height = img.size
img2 = create_canvas(width, height)
img2.compose(img)
img2.save(out_path)
else:
BasicNewsRecipe.prepare_masthead_image(self, path_to_image, out_path)
def fixChars(self,string): def fixChars(self,string):
# Replace lsquo (\x91) # Replace lsquo (\x91)
fixed = re.sub("\x91","",string) fixed = re.sub("\x91","",string)
@ -166,55 +209,106 @@ class CanWestPaper(BasicNewsRecipe):
a.replaceWith(a.renderContents().decode('cp1252','replace')) a.replaceWith(a.renderContents().decode('cp1252','replace'))
return soup return soup
def preprocess_html(self, soup):
def preprocess_html(self,soup):
#delete empty id attributes--they screw up the TOC for unknown reasons
divtags = soup.findAll('div',attrs={'id':''})
if divtags:
for div in divtags:
del(div['id'])
pgall = soup.find('div',attrs={'id':'storyphoto'})
if pgall is not None: # photo gallery perhaps
if (soup.find('div',attrs={'id':'storycontent'}) is None):
allpics = Tag(soup,'div')
first_img = pgall.find('div','storyimage')
if first_img is not None:
first_img.extract()
tlist = pgall.find('div',attrs={'id':'relatedthumbs'})
if tlist is not None:
for atag in tlist.findAll('a'):
img = Tag(soup,'img')
srcpre, sep, srcpost = atag.img['src'].partition('?')
img['src'] = srcpre
pdesc = Tag(soup,'p')
pdesc.insert(0,atag.img['alt'])
pdesc['class']='photocaption'
div = Tag(soup,'div')
div.insert(0,pdesc)
div.insert(0,img)
allpics.append(div)
pgall.replaceWith(allpics)
for pg in soup.findAll('div',attrs={'id':'storyphoto'}):
pg.extract()
return self.strip_anchors(soup) return self.strip_anchors(soup)
def parse_index(self): def parse_index(self):
soup = self.index_to_soup(self.url_prefix+'/news/todays-paper/index.html')
articles = {} articles = {}
key = 'News' ans = []
ans = ['News']
# Find each instance of class="sectiontitle", class="featurecontent"
for divtag in soup.findAll('div',attrs={'class' : ["section_title02","featurecontent"]}):
#self.log(" div class = %s" % divtag['class'])
if divtag['class'].startswith('section_title'):
# div contains section title
if not divtag.h3:
continue
key = self.tag_to_string(divtag.h3,False)
ans.append(key)
self.log("Section name %s" % key)
continue
# div contains article data
h1tag = divtag.find('h1')
if not h1tag:
continue
atag = h1tag.find('a',href=True)
if not atag:
continue
url = self.url_prefix+'/news/todays-paper/'+atag['href']
#self.log("Section %s" % key)
#self.log("url %s" % url)
title = self.tag_to_string(atag,False)
#self.log("title %s" % title)
pubdate = ''
description = ''
ptag = divtag.find('p');
if ptag:
description = self.tag_to_string(ptag,False)
#self.log("description %s" % description)
author = ''
autag = divtag.find('h4')
if autag:
author = self.tag_to_string(autag,False)
#self.log("author %s" % author)
if not articles.has_key(key):
articles[key] = []
articles[key].append(dict(title=title,url=url,date=pubdate,description=description,author=author,content=''))
def handle_article(adiv,key):
h1tag = adiv.h1
if h1tag is not None:
atag = h1tag.a
if atag is not None:
url = atag['href']
if atag['href'].startswith('http'):
return
elif atag['href'].startswith('/'):
url = self.url_prefix+atag['href']
else:
url = self.url_prefix+'/'+atag['href']
if url in self.url_list:
return
self.url_list.append(url)
title = self.tag_to_string(atag,False)
if 'VIDEO' in title.upper():
return
if 'GALLERY' in title.upper():
return
if 'PHOTOS' in title.upper():
return
dtag = adiv.find('div','content')
description=''
print("URL "+url)
print("TITLE "+title)
if dtag is not None:
stag = dtag.span
if stag is not None:
if stag['class'] != 'timestamp':
description = self.tag_to_string(stag,False)
else:
description = self.tag_to_string(dtag,False)
print("DESCRIPTION: "+description)
if not articles.has_key(key):
articles[key] = []
articles[key].append(dict(title=title,url=url,date='',description=description,author='',content=''))
def parse_web_index(key, keyurl):
try:
soup = self.index_to_soup(self.url_prefix+keyurl)
except:
return
ans.append(key)
mainsoup = soup.find('div','bodywrapper')
footer = mainsoup.find(attrs={'id':'footerfeature'})
if footer is not None:
footer.extract()
print("Section: "+key)
for wdiv in mainsoup.findAll('div',attrs={'id':re.compile('^HorizontalFeatureSlider_1_Story')}):
handle_article(wdiv,key)
wdiv.extract()
for wdiv in mainsoup.findAll(attrs={'id':['featurewidget','textfeature','textlinks_timestamp']}):
for adiv in wdiv.findAll('div','featurecontent'):
handle_article(adiv,key)
for (k,url) in self.postmedia_index_pages:
parse_web_index(k,url)
ans = [(key, articles[key]) for key in ans if articles.has_key(key)] ans = [(key, articles[key]) for key in ans if articles.has_key(key)]
return ans return ans

View File

@ -0,0 +1,75 @@
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
from collections import OrderedDict
class PhilosophyNow(BasicNewsRecipe):
title = 'Philosophy Now'
__author__ = 'Rick Shang'
description = '''Philosophy Now is a lively magazine for everyone
interested in ideas. It isn't afraid to tackle all the major questions of
life, the universe and everything. Published every two months, it tries to
corrupt innocent citizens by convincing them that philosophy can be
exciting, worthwhile and comprehensible, and also to provide some enjoyable
reading matter for those already ensnared by the muse, such as philosophy
students and academics.'''
language = 'en'
category = 'news'
encoding = 'UTF-8'
keep_only_tags = [dict(attrs={'id':'fullMainColumn'})]
remove_tags = [dict(attrs={'class':'articleTools'})]
no_javascript = True
no_stylesheets = True
needs_subscription = True
def get_browser(self):
br = BasicNewsRecipe.get_browser()
br.open('https://philosophynow.org/auth/login')
br.select_form(nr = 1)
br['username'] = self.username
br['password'] = self.password
br.submit()
return br
def parse_index(self):
#Go to the issue
soup0 = self.index_to_soup('http://philosophynow.org/')
issue = soup0.find('div',attrs={'id':'navColumn'})
#Find date & cover
cover = issue.find('div', attrs={'id':'cover'})
date = self.tag_to_string(cover.find('h3')).strip()
self.timefmt = u' [%s]'%date
img=cover.find('img',src=True)['src']
self.cover_url = 'http://philosophynow.org' + re.sub('medium','large',img)
issuenum = re.sub('/media/images/covers/medium/issue','',img)
issuenum = re.sub('.jpg','',issuenum)
#Go to the main body
current_issue_url = 'http://philosophynow.org/issues/' + issuenum
soup = self.index_to_soup(current_issue_url)
div = soup.find ('div', attrs={'class':'articlesColumn'})
feeds = OrderedDict()
for post in div.findAll('h3'):
articles = []
a=post.find('a',href=True)
if a is not None:
url="http://philosophynow.org" + a['href']
title=self.tag_to_string(a).strip()
s=post.findPrevious('h4')
section_title = self.tag_to_string(s).strip()
d=post.findNext('p')
desc = self.tag_to_string(d).strip()
articles.append({'title':title, 'url':url, 'description':desc, 'date':''})
if articles:
if section_title not in feeds:
feeds[section_title] = []
feeds[section_title] += articles
ans = [(key, val) for key, val in feeds.iteritems()]
return ans

View File

@ -1,44 +1,79 @@
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
from calibre.ptempfile import PersistentTemporaryFile
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1275708473(BasicNewsRecipe): class PsychologyToday(BasicNewsRecipe):
title = u'Psychology Today'
_author__ = 'rty' title = 'Psychology Today'
publisher = u'www.psychologytoday.com' __author__ = 'Rick Shang'
category = u'Psychology'
max_articles_per_feed = 100 description = 'This magazine takes information from the latest research in the field of psychology and makes it useful to people in their everyday lives. Its coverage encompasses self-improvement, relationships, the mind-body connection, health, family, the workplace and culture.'
remove_javascript = True
use_embedded_content = False
no_stylesheets = True
language = 'en' language = 'en'
temp_files = [] category = 'news'
articles_are_obfuscated = True encoding = 'UTF-8'
remove_tags = [ keep_only_tags = [dict(attrs={'class':['print-title', 'print-submitted', 'print-content', 'print-footer', 'print-source_url', 'print-links']})]
dict(name='div', attrs={'class':['print-source_url','field-items','print-footer']}), no_javascript = True
dict(name='span', attrs={'class':'print-footnote'}), no_stylesheets = True
]
remove_tags_before = dict(name='h1', attrs={'class':'print-title'})
remove_tags_after = dict(name='div', attrs={'class':['field-items','print-footer']})
feeds = [(u'Contents', u'http://www.psychologytoday.com/articles/index.rss')]
def get_article_url(self, article): def parse_index(self):
return article.get('link', None) articles = []
soup = self.index_to_soup('http://www.psychologytoday.com/magazine')
#Go to the main body
div = soup.find('div',attrs={'id':'content-content'})
#Find cover & date
cover_item = div.find('div', attrs={'class':'collections-header-image'})
cover = cover_item.find('img',src=True)
self.cover_url = cover['src']
date = self.tag_to_string(cover['title'])
self.timefmt = u' [%s]'%date
articles = []
for post in div.findAll('div', attrs={'class':'collections-node-feature-info'}):
title = self.tag_to_string(post.find('h2'))
author_item=post.find('div', attrs={'class':'collection-node-byline'})
author = re.sub(r'.*by\s',"",self.tag_to_string(author_item).strip())
title = title + u' (%s)'%author
article_page= self.index_to_soup('http://www.psychologytoday.com'+post.find('a', href=True)['href'])
print_page=article_page.find('li', attrs={'class':'print_html first'})
url='http://www.psychologytoday.com'+print_page.find('a',href=True)['href']
desc = self.tag_to_string(post.find('div', attrs={'class':'collection-node-description'})).strip()
self.log('Found article:', title)
self.log('\t', url)
self.log('\t', desc)
articles.append({'title':title, 'url':url, 'date':'','description':desc})
for post in div.findAll('div', attrs={'class':'collections-node-thumbnail-info'}):
title = self.tag_to_string(post.find('h2'))
author_item=post.find('div', attrs={'class':'collection-node-byline'})
article_page= self.index_to_soup('http://www.psychologytoday.com'+post.find('a', href=True)['href'])
print_page=article_page.find('li', attrs={'class':'print_html first'})
description = post.find('div', attrs={'class':'collection-node-description'})
author = re.sub(r'.*by\s',"",self.tag_to_string(description.nextSibling).strip())
desc = self.tag_to_string(description).strip()
url='http://www.psychologytoday.com'+print_page.find('a',href=True)['href']
title = title + u' (%s)'%author
self.log('Found article:', title)
self.log('\t', url)
self.log('\t', desc)
articles.append({'title':title, 'url':url, 'date':'','description':desc})
for post in div.findAll('li', attrs={'class':['collection-item-list-odd','collection-item-list-even']}):
title = self.tag_to_string(post.find('h2'))
author_item=post.find('div', attrs={'class':'collection-node-byline'})
author = re.sub(r'.*by\s',"",self.tag_to_string(author_item).strip())
title = title + u' (%s)'%author
article_page= self.index_to_soup('http://www.psychologytoday.com'+post.find('a', href=True)['href'])
print_page=article_page.find('li', attrs={'class':'print_html first'})
url='http://www.psychologytoday.com'+print_page.find('a',href=True)['href']
desc = self.tag_to_string(post.find('div', attrs={'class':'collection-node-description'})).strip()
self.log('Found article:', title)
self.log('\t', url)
self.log('\t', desc)
articles.append({'title':title, 'url':url, 'date':'','description':desc})
return [('Current Issue', articles)]
def get_obfuscated_article(self, url):
br = self.get_browser()
br.open(url)
response = br.follow_link(url_regex = r'/print/[0-9]+', nr = 0)
html = response.read()
self.temp_files.append(PersistentTemporaryFile('_fa.html'))
self.temp_files[-1].write(html)
self.temp_files[-1].close()
return self.temp_files[-1].name
def get_cover_url(self):
index = 'http://www.psychologytoday.com/magazine/'
soup = self.index_to_soup(index)
for image in soup.findAll('img',{ "class" : "imagefield imagefield-field_magazine_cover" }):
return image['src'] + '.jpg'
return None

View File

@ -1,25 +1,35 @@
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class SanFranciscoBayGuardian(BasicNewsRecipe): class SanFranciscoBayGuardian(BasicNewsRecipe):
title = u'San Francisco Bay Guardian' title = u'San Francisco Bay Guardian'
language = 'en' language = 'en'
__author__ = 'Krittika Goyal' __author__ = 'Krittika Goyal'
oldest_article = 31 #days oldest_article = 31 #days
max_articles_per_feed = 25 max_articles_per_feed = 25
#encoding = 'latin1'
no_stylesheets = True no_stylesheets = True
#remove_tags_before = dict(name='div', attrs={'id':'story_header'})
#remove_tags_after = dict(name='div', attrs={'id':'shirttail'})
remove_tags = [ remove_tags = [
dict(name='iframe'), dict(name='iframe'),
#dict(name='div', attrs={'class':'related-articles'}),
#dict(name='div', attrs={'id':['story_tools', 'toolbox', 'shirttail', 'comment_widget']}),
#dict(name='ul', attrs={'class':'article-tools'}),
#dict(name='ul', attrs={'id':'story_tabs'}),
] ]
feeds = [ feeds = [
('sfbg', 'http://www.sfbg.com/rss.xml'), ('sfbg', 'http://www.sfbg.com/rss.xml'),
('politics', 'http://www.sfbg.com/politics/rss.xml'),
('blogs', 'http://www.sfbg.com/blog/rss.xml'),
('pixel_vision', 'http://www.sfbg.com/pixel_vision/rss.xml'),
('bruce', 'http://www.sfbg.com/bruce/rss.xml'),
] ]
#def preprocess_html(self, soup):
#story = soup.find(name='div', attrs={'id':'story_body'})
#td = heading.findParent(name='td')
#td.extract()
#soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
#body = soup.find(name='body')
#body.insert(0, story)
#return soup

View File

@ -19,23 +19,12 @@ class Slashdot(BasicNewsRecipe):
__author__ = 'floweros edited by Huan T' __author__ = 'floweros edited by Huan T'
no_stylesheets = True no_stylesheets = True
use_embedded_content = False
keep_only_tags = [ keep_only_tags = [
dict(name='div',attrs={'id':'article'}), dict(name='div',attrs={'class':'story'}),
dict(name='div',attrs={'class':['postBody' 'details']}), dict(name='div',attrs={'class':'body'}),
dict(name='footer',attrs={'class':['clearfix meta article-foot']}), dict(name='ul',attrs={'id':'commentlisting'}),
dict(name='article',attrs={'class':['fhitem fhitem-story article usermode thumbs grid_24']}), ]
dict(name='dl',attrs={'class':'relatedPosts'}),
dict(name='h2',attrs={'class':'story'}),
dict(name='span',attrs={'class':'comments'}),
]
remove_tags = [
dict(name='aside',attrs={'id':'slashboxes'}),
dict(name='div',attrs={'class':'paginate'}),
dict(name='section',attrs={'id':'comments'}),
dict(name='span',attrs={'class':'topic'}),
]
feeds = [ feeds = [
(u'Slashdot', (u'Slashdot',

View File

@ -1,50 +1,24 @@
#!/usr/bin/env python # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
www.smashingmagazine.com
'''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class SmashingMagazine(BasicNewsRecipe): class SmashingMagazine (BasicNewsRecipe):
title = 'Smashing Magazine' __author__ = u'Marc Busqué <marc@lamarciana.com>'
__author__ = 'Darko Miletic' __url__ = 'http://www.lamarciana.com'
description = 'We smash you with the information that will make your life easier, really' __version__ = '1.0.1'
oldest_article = 20 __license__ = 'GPL v3'
language = 'en' __copyright__ = u'2012, Marc Busqué <marc@lamarciana.com>'
max_articles_per_feed = 100 title = u'Smashing Magazine'
no_stylesheets = True description = u'Founded in September 2006, Smashing Magazine delivers useful and innovative information to Web designers and developers. Our aim is to inform our readers about the latest trends and techniques in Web development. We try to persuade you not with the quantity but with the quality of the information we present. Smashing Magazine is and always has been independent.'
use_embedded_content = False language = 'en'
publisher = 'Smashing Magazine' tags = 'web development, software'
category = 'news, web, IT, css, javascript, html' oldest_article = 7
encoding = 'utf-8' remove_empty_feeds = True
no_stylesheets = True
encoding = 'utf8'
cover_url = u'http://media.smashingmagazine.com/themes/smashingv4/images/logo.png'
remove_attributes = ['border', 'cellspacing', 'align', 'cellpadding', 'colspan', 'valign', 'vspace', 'hspace', 'alt', 'width', 'height', 'style']
extra_css = u'body div table:first-child {display: none;} img {max-width: 100%; display: block; margin: auto;}'
conversion_options = { feeds = [
'comments' : description (u'Smashing Magazine', u'http://rss1.smashingmagazine.com/feed/'),
,'tags' : category ]
,'publisher' : publisher
}
keep_only_tags = [dict(name='div', attrs={'id':'leftcolumn'})]
remove_tags_after = dict(name='ul',attrs={'class':'social'})
remove_tags = [
dict(name=['link','object'])
,dict(name='h1',attrs={'class':'logo'})
,dict(name='div',attrs={'id':'booklogosec'})
,dict(attrs={'src':'http://media2.smashingmagazine.com/wp-content/uploads/images/the-smashing-book/smbook6.gif'})
]
feeds = [(u'Articles', u'http://rss1.smashingmagazine.com/feed/')]
def preprocess_html(self, soup):
for iter in soup.findAll('div',attrs={'class':'leftframe'}):
it = iter.find('h1')
if it == None:
iter.extract()
for item in soup.findAll('img'):
oldParent = item.parent
if oldParent.name == 'a':
oldParent.name = 'div'
return soup

View File

@ -1,61 +1,67 @@
import re import re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.recipes import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup from collections import OrderedDict
class SmithsonianMagazine(BasicNewsRecipe): class Smithsonian(BasicNewsRecipe):
title = u'Smithsonian Magazine'
language = 'en'
__author__ = 'Krittika Goyal and TerminalVeracity'
oldest_article = 31#days
max_articles_per_feed = 50
use_embedded_content = False
recursions = 1
cover_url = 'http://sphotos.xx.fbcdn.net/hphotos-snc7/431147_10150602715983253_764313347_n.jpg'
match_regexps = ['&page=[2-9]$']
preprocess_regexps = [
(re.compile(r'for more of Smithsonian\'s coverage on history, science and nature.', re.DOTALL), lambda m: '')
]
extra_css = """
h1{font-size: large; margin: .2em 0}
h2{font-size: medium; margin: .2em 0}
h3{font-size: medium; margin: .2em 0}
#byLine{margin: .2em 0}
.articleImageCaptionwide{font-style: italic}
.wp-caption-text{font-style: italic}
img{display: block}
"""
title = 'Smithsonian Magazine'
__author__ = 'Rick Shang'
remove_stylesheets = True description = 'This magazine chronicles the arts, environment, sciences and popular culture of the times. It is edited for modern, well-rounded individuals with diverse, general interests. With your order, you become a National Associate Member of the Smithsonian. Membership benefits include your subscription to Smithsonian magazine, a personalized membership card, discounts from the Smithsonian catalog, and more.'
remove_tags_after = dict(name='div', attrs={'class':['post','articlePaginationWrapper']}) language = 'en'
remove_tags = [ category = 'news'
dict(name='iframe'), encoding = 'UTF-8'
dict(name='div', attrs={'class':['article_sidebar_border','viewMorePhotos','addtoany_share_save_container','meta','social','OUTBRAIN','related-articles-inpage']}), keep_only_tags = [dict(attrs={'id':['articleTitle', 'subHead', 'byLine', 'articleImage', 'article-text']})]
dict(name='div', attrs={'id':['article_sidebar_border', 'most-popular_large', 'most-popular-body_large','comment_section','article-related']}), remove_tags = [dict(attrs={'class':['related-articles-inpage', 'viewMorePhotos']})]
dict(name='ul', attrs={'class':'cat-breadcrumb col three last'}), no_javascript = True
dict(name='h4', attrs={'id':'related-topics'}), no_stylesheets = True
dict(name='table'),
dict(name='a', attrs={'href':['/subArticleBottomWeb','/subArticleTopWeb','/subArticleTopMag','/subArticleBottomMag']}),
dict(name='a', attrs={'name':'comments_shaded'}),
]
def parse_index(self):
#Go to the issue
soup0 = self.index_to_soup('http://www.smithsonianmag.com/issue/archive/')
div = soup0.find('div',attrs={'id':'archives'})
issue = div.find('ul',attrs={'class':'clear-both'})
current_issue_url = issue.find('a', href=True)['href']
soup = self.index_to_soup(current_issue_url)
feeds = [ #Go to the main body
('History and Archeology', div = soup.find ('div', attrs={'id':'content-inset'})
'http://feeds.feedburner.com/smithsonianmag/history-archaeology'),
('People and Places', #Find date
'http://feeds.feedburner.com/smithsonianmag/people-places'), date = re.sub('.*\:\W*', "", self.tag_to_string(div.find('h2')).strip())
('Science and Nature', self.timefmt = u' [%s]'%date
'http://feeds.feedburner.com/smithsonianmag/science-nature'),
('Arts and Culture', #Find cover
'http://feeds.feedburner.com/smithsonianmag/arts-culture'), self.cover_url = div.find('img',src=True)['src']
('Travel',
'http://feeds.feedburner.com/smithsonianmag/travel'), feeds = OrderedDict()
] section_title = ''
subsection_title = ''
for post in div.findAll('div', attrs={'class':['plainModule', 'departments plainModule']}):
articles = []
prefix = ''
h3=post.find('h3')
if h3 is not None:
section_title = self.tag_to_string(h3)
else:
subsection=post.find('p',attrs={'class':'article-cat'})
link=post.find('a',href=True)
url=link['href']+'?c=y&story=fullstory'
if subsection is not None:
subsection_title = self.tag_to_string(subsection)
prefix = (subsection_title+': ')
description=self.tag_to_string(post('p', limit=2)[1]).strip()
else:
description=self.tag_to_string(post.find('p')).strip()
desc=re.sub('\sBy\s.*', '', description, re.DOTALL)
author=re.sub('.*By\s', '', description, re.DOTALL)
title=prefix + self.tag_to_string(link).strip()+ u' (%s)'%author
articles.append({'title':title, 'url':url, 'description':desc, 'date':''})
if articles:
if section_title not in feeds:
feeds[section_title] = []
feeds[section_title] += articles
ans = [(key, val) for key, val in feeds.iteritems()]
return ans
def preprocess_html(self, soup):
story = soup.find(name='div', attrs={'id':'article-body'})
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
body = soup.find(name='body')
body.insert(0, story)
return soup

View File

@ -0,0 +1,117 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2012, Andreas Zeiser <andreas.zeiser@web.de>'
'''
szmobil.sueddeutsche.de/
'''
from calibre import strftime
from calibre.web.feeds.recipes import BasicNewsRecipe
import re
class SZmobil(BasicNewsRecipe):
title = u'Süddeutsche Zeitung mobil'
__author__ = u'Andreas Zeiser'
description = u'Nachrichten aus Deutschland. Zugriff auf kostenpflichtiges Abo SZ mobil.'
publisher = u'Sueddeutsche Zeitung'
language = u'de'
publication_type = u'newspaper'
category = u'news, politics, Germany'
no_stylesheets = True
oldest_article = 2
encoding = 'iso-8859-1'
needs_subscription = True
remove_empty_feeds = True
delay = 1
cover_source = 'http://www.sueddeutsche.de/verlag'
timefmt = ' [%a, %d %b, %Y]'
root_url ='http://szmobil.sueddeutsche.de/'
keep_only_tags = [dict(name='div', attrs={'class':'article'})]
def get_cover_url(self):
src = self.index_to_soup(self.cover_source)
image_url = src.find(attrs={'class':'preview-image'})
return image_url.div.img['src']
def get_browser(self):
browser = BasicNewsRecipe.get_browser(self)
# Login via fetching of Streiflicht -> Fill out login request
url = self.root_url + 'show.php?id=streif'
browser.open(url)
browser.select_form(nr=0) # to select the first form
browser['username'] = self.username
browser['password'] = self.password
browser.submit()
return browser
def parse_index(self):
# find all sections
src = self.index_to_soup('http://szmobil.sueddeutsche.de')
feeds = []
for itt in src.findAll('a',href=True):
if itt['href'].startswith('show.php?section'):
feeds.append( (itt.string[0:-2],itt['href']) )
all_articles = []
for feed in feeds:
feed_url = self.root_url + feed[1]
feed_title = feed[0]
self.report_progress(0, ('Fetching feed')+' %s...'%(feed_title if feed_title else feed_url))
src = self.index_to_soup(feed_url)
articles = []
shorttitles = dict()
for itt in src.findAll('a', href=True):
if itt['href'].startswith('show.php?id='):
article_url = itt['href']
article_id = int(re.search("id=(\d*)&etag=", itt['href']).group(1))
# first check if link is a special article in section "Meinungsseite"
if itt.find('strong')!= None:
article_name = itt.strong.string
article_shorttitle = itt.contents[1]
articles.append( (article_name, article_url, article_id) )
shorttitles[article_id] = article_shorttitle
continue
# candidate for a general article
if itt.string == None:
article_name = ''
else:
article_name = itt.string
if (article_name[0:10] == "&nbsp;mehr"):
# just another link ("mehr") to an article
continue
if itt.has_key('id'):
shorttitles[article_id] = article_name
else:
articles.append( (article_name, article_url, article_id) )
feed_articles = []
for article_name, article_url, article_id in articles:
url = self.root_url + article_url
title = article_name
pubdate = strftime('%a, %d %b')
description = ''
if shorttitles.has_key(article_id):
description = shorttitles[article_id]
# we do not want the flag ("Impressum")
if "HERAUSGEGEBEN VOM" in description:
continue
d = dict(title=title, url=url, date=pubdate, description=description, content='')
feed_articles.append(d)
all_articles.append( (feed_title, feed_articles) )
return all_articles

View File

@ -1,45 +1,68 @@
from calibre.web.feeds.news import BasicNewsRecipe import re
from calibre.web.feeds.recipes import BasicNewsRecipe
from collections import OrderedDict
class The_New_Republic(BasicNewsRecipe): class TNR(BasicNewsRecipe):
title = 'The New Republic'
__author__ = 'cix3' title = 'The New Republic'
__author__ = 'Rick Shang'
description = 'The New Republic is a journal of opinion with an emphasis on politics and domestic and international affairs. It carries feature articles by staff and contributing editors. The second half of each issue is devoted to book and the arts, theater, motion pictures, music and art.'
language = 'en' language = 'en'
description = 'Intelligent, stimulating and rigorous examination of American politics, foreign policy and culture' category = 'news'
timefmt = ' [%b %d, %Y]' encoding = 'UTF-8'
remove_tags = [dict(attrs={'class':['print-logo','print-site_name','print-hr']})]
oldest_article = 7 no_javascript = True
max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
remove_tags = [
dict(name='div', attrs={'class':['print-logo', 'print-site_name', 'img-left', 'print-source_url']}),
dict(name='hr', attrs={'class':'print-hr'}), dict(name='img')
]
feeds = [ def parse_index(self):
('Politics', 'http://www.tnr.com/rss/articles/Politics'),
('Books and Arts', 'http://www.tnr.com/rss/articles/Books-and-Arts'),
('Economy', 'http://www.tnr.com/rss/articles/Economy'),
('Environment and Energy', 'http://www.tnr.com/rss/articles/Environment-%2526-Energy'),
('Health Care', 'http://www.tnr.com/rss/articles/Health-Care'),
('Metro Policy', 'http://www.tnr.com/rss/articles/Metro-Policy'),
('World', 'http://www.tnr.com/rss/articles/World'),
('Film', 'http://www.tnr.com/rss/articles/Film'),
('Books', 'http://www.tnr.com/rss/articles/books'),
('The Book', 'http://www.tnr.com/rss/book'),
('Jonathan Chait', 'http://www.tnr.com/rss/blogs/Jonathan-Chait'),
('The Plank', 'http://www.tnr.com/rss/blogs/The-Plank'),
('The Treatment', 'http://www.tnr.com/rss/blogs/The-Treatment'),
('The Spine', 'http://www.tnr.com/rss/blogs/The-Spine'),
('The Vine', 'http://www.tnr.com/rss/blogs/The-Vine'),
('The Avenue', 'http://www.tnr.com/rss/blogs/The-Avenue'),
('William Galston', 'http://www.tnr.com/rss/blogs/William-Galston'),
('Simon Johnson', 'http://www.tnr.com/rss/blogs/Simon-Johnson'),
('Ed Kilgore', 'http://www.tnr.com/rss/blogs/Ed-Kilgore'),
('Damon Linker', 'http://www.tnr.com/rss/blogs/Damon-Linker'),
('John McWhorter', 'http://www.tnr.com/rss/blogs/John-McWhorter')
]
def print_version(self, url): #Go to the issue
return url.replace('http://www.tnr.com/', 'http://www.tnr.com/print/') soup0 = self.index_to_soup('http://www.tnr.com/magazine-issues')
issue = soup0.find('div',attrs={'id':'current_issue'})
#Find date
date = self.tag_to_string(issue.find('div',attrs={'class':'date'})).strip()
self.timefmt = u' [%s]'%date
#Go to the main body
current_issue_url = 'http://www.tnr.com' + issue.find('a', href=True)['href']
soup = self.index_to_soup(current_issue_url)
div = soup.find ('div', attrs={'class':'article_detail_body'})
#Find cover
self.cover_url = div.find('img',src=True)['src']
feeds = OrderedDict()
section_title = ''
subsection_title = ''
for post in div.findAll('p'):
articles = []
em=post.find('em')
b=post.find('b')
a=post.find('a',href=True)
p=post.find('img', src=True)
#Find cover
if p is not None:
self.cover_url = p['src'].strip()
if em is not None:
section_title = self.tag_to_string(em).strip()
subsection_title = ''
elif b is not None:
subsection_title=self.tag_to_string(b).strip()
elif a is not None:
prefix = (subsection_title+': ') if subsection_title else ''
url=re.sub('www.tnr.com','www.tnr.com/print', a['href'])
author=re.sub('.*by\s', '', self.tag_to_string(post), re.DOTALL)
title=prefix + self.tag_to_string(a).strip()+ u' (%s)'%author
articles.append({'title':title, 'url':url, 'description':'', 'date':''})
if articles:
if section_title not in feeds:
feeds[section_title] = []
feeds[section_title] += articles
ans = [(key, val) for key, val in feeds.iteritems()]
return ans

View File

@ -1,4 +1,4 @@
import re, random import random
from calibre import browser from calibre import browser
from calibre.web.feeds.recipes import BasicNewsRecipe from calibre.web.feeds.recipes import BasicNewsRecipe
@ -6,48 +6,45 @@ from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1325006965(BasicNewsRecipe): class AdvancedUserRecipe1325006965(BasicNewsRecipe):
title = u'The Sun UK' title = u'The Sun UK'
description = 'A Recipe for The Sun tabloid UK' description = 'Articles from The Sun tabloid UK'
__author__ = 'Dave Asbury' __author__ = 'Dave Asbury'
# last updated 29/4/12 # last updated 25/7/12
language = 'en_GB' language = 'en_GB'
oldest_article = 1 oldest_article = 1
max_articles_per_feed = 15 max_articles_per_feed = 12
remove_empty_feeds = True remove_empty_feeds = True
no_stylesheets = True no_stylesheets = True
#auto_cleanup = True
#articles_are_obfuscated = True
masthead_url = 'http://www.thesun.co.uk/sol/img/global/Sun-logo.gif' masthead_url = 'http://www.thesun.co.uk/sol/img/global/Sun-logo.gif'
encoding = 'UTF-8' encoding = 'UTF-8'
remove_empty_feeds = True
remove_javascript = True remove_javascript = True
no_stylesheets = True no_stylesheets = True
#preprocess_regexps = [
# (re.compile(r'<div class="foot-copyright".*?</div>', re.IGNORECASE | re.DOTALL), lambda match: '')]
extra_css = ''' extra_css = '''
body{ text-align: justify; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:normal;} body{ text-align: justify; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:normal;}
''' '''
preprocess_regexps = [
(re.compile(r'<div class="foot-copyright".*?</div>', re.IGNORECASE | re.DOTALL), lambda match: '')]
keep_only_tags = [ keep_only_tags = [
dict(name='h1'),dict(name='h2',attrs={'class' : 'medium centered'}), dict(name='div',attrs={'class' : 'intro'}),
dict(name='div',attrs={'class' : 'text-center'}), dict(name='h3'),
dict(name='div',attrs={'id' : 'bodyText'}) dict(name='div',attrs={'id' : 'articlebody'}),
# dict(name='p') #dict(attrs={'class' : ['right_col_branding','related-stories','mystery-meat-link','ltbx-container','ltbx-var ltbx-hbxpn','ltbx-var ltbx-nav-loop','ltbx-var ltbx-url']}),
] # dict(name='div',attrs={'class' : 'cf'}),
remove_tags=[ # dict(attrs={'title' : 'download flash'}),
#dict(name='head'), # dict(attrs={'style' : 'padding: 5px'})
dict(attrs={'class' : ['mystery-meat-link','ltbx-container','ltbx-var ltbx-hbxpn','ltbx-var ltbx-nav-loop','ltbx-var ltbx-url']}),
dict(name='div',attrs={'class' : 'cf'}),
dict(attrs={'title' : 'download flash'}),
dict(attrs={'style' : 'padding: 5px'})
] ]
remove_tags_after = [dict(id='bodyText')]
remove_tags=[
dict(name='li'),
dict(attrs={'class' : 'grid-4 right-hand-column'}),
]
feeds = [ feeds = [
(u'News', u'http://www.thesun.co.uk/sol/homepage/news/rss'), (u'News', u'http://www.thesun.co.uk/sol/homepage/news/rss'),
@ -72,22 +69,18 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
cov2 = str(cov) cov2 = str(cov)
cov2=cov2[27:-18] cov2=cov2[27:-18]
#cov2 now is pic url, now go back to original function #cov2 now is pic url, now go back to original function
br = browser() br = browser()
br.set_handle_redirect(False) br.set_handle_redirect(False)
try: try:
br.open_novisit(cov2) br.open_novisit(cov2)
cover_url = cov2 cover_url = cov2
except: except:
cover_url = random.choice(( cover_url = random.choice([
'http://img.thesun.co.uk/multimedia/archive/00905/errorpage6_677961a_905507a.jpg' 'http://img.thesun.co.uk/multimedia/archive/00905/errorpage6_677961a_905507a.jpg'
,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage7_677962a_905505a.jpg' ,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage7_677962a_905505a.jpg'
,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage5_677960a_905512a.jpg' ,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage5_677960a_905512a.jpg'
,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage2_677957a_905502a.jpg' ,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage2_677957a_905502a.jpg'
,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage3_677958a_905503a.jpg' ,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage3_677958a_905503a.jpg'
)) ])
return cover_url return cover_url

View File

@ -16,6 +16,8 @@ class TheTorontoStar(BasicNewsRecipe):
language = 'en_CA' language = 'en_CA'
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
#auto_cleanup = True
#auto_cleanup_keep = '//div[@class="topsContent topsContentActive"]'
use_embedded_content = False use_embedded_content = False
delay = 2 delay = 2
publisher = 'The Toronto Star' publisher = 'The Toronto Star'
@ -28,18 +30,18 @@ class TheTorontoStar(BasicNewsRecipe):
,'publisher' : publisher ,'publisher' : publisher
} }
keep_only_tags = [dict(name='div', attrs={'class':'ts-article'})] #keep_only_tags = [dict(name='div', attrs={'class':'ts-article'})]
remove_tags_before = dict(name='div',attrs={'id':'ts-article_header'}) #remove_tags_before = dict(name='div',attrs={'id':'ts-article_header'})
feeds = [ feeds = [
(u'News' , u'http://www.thestar.com/rss/82672?' ) (u'News' , u'http://www.thestar.com/rss/?categories=293' )
,(u'Opinion' , u'http://www.thestar.com/rss/82863?' ) ,(u'Opinion' , u'http://www.thestar.com/rss/?categories=303' )
,(u'Business' , u'http://www.thestar.com/rss/82796?' ) ,(u'Business' , u'http://www.thestar.com/rss/?categories=294' )
,(u'Sports' , u'http://www.thestar.com/rss/82758?' ) ,(u'Sports' , u'http://www.thestar.com/rss/?categories=295' )
,(u'Entertainment', u'http://www.thestar.com/rss/117741?' ) ,(u'Entertainment', u'http://www.toronto.com/rss?categories=6298' )
,(u'Living' , u'http://www.thestar.com/rss/82839?' ) ,(u'Living' , u'http://www.thestar.com/rss/?categories=297' )
,(u'Travel' , u'http://www.thestar.com/rss/82858?' ) ,(u'Travel' , u'http://www.thestar.com/rss/list/1042246?' )
,(u'Science' , u'http://www.thestar.com/rss/82848?') ,(u'Science' , u'http://www.thestar.com/rss?categories=6481')
] ]
def print_version(self, url): def print_version(self, url):
@ -47,3 +49,4 @@ class TheTorontoStar(BasicNewsRecipe):
artid = artl.rpartition('/')[2] artid = artl.rpartition('/')[2]
return 'http://www.thestar.com/printarticle/' + artid return 'http://www.thestar.com/printarticle/' + artid

View File

@ -1,136 +1,314 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
__license__ = 'GPL v3' __license__ = 'GPL v3'
''' '''
www.canada.com www.canada.com
''' '''
import re
from calibre.web.feeds.recipes import BasicNewsRecipe from calibre.web.feeds.recipes import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, Tag
class CanWestPaper(BasicNewsRecipe): class CanWestPaper(BasicNewsRecipe):
# un-comment the following three lines for the Vancouver Province postmedia_index_pages = [
(u'Headlines',u'/index.html'),
(u'Ottawa & Area',u'/news/ottawa/index.html'),
(u'Vancouver',u'/news/vancouver/index.html'),
(u'Calgary',u'/news/calgary/index.html'),
(u'Edmonton',u'/news/edmonton/index.html'),
(u'Montreal',u'/news/montreal/index.html'),
(u'Fraser Valley',u'/news/fraser-valley/index.html'),
(u'British Columbia',u'/news/bc/index.html'),
(u'Alberta',u'/news/alberta/index.html'),
(u'Canada',u'/news/canada/index.html'),
(u'National',u'/news/national/index.html'),
(u'Politics',u'/news/politics/index.html'),
(u'Insight',u'/news/insight/index.html'),
(u'Special Reports',u'/news/specialreports/index.html'),
(u'Gangs',u'/news/gangs/index.html'),
(u'Education',u'/news/education/index.html'),
(u'Health',u'/news/health/index.html'),
(u'Environment',u'/news/environment/index.html'),
(u'World',u'/news/world/index.html'),
(u'Police Blotter',u'/news/crime-and-justice/index.html'),
(u'Crime',u'/news/blotter/index.html'),
(u'Around Town',u'/news/topic.html?t=keyword&q=Around+Town'),
(u'Diplomatica',u'/news/diplomatica/index.html'),
(u'Opinion',u'/opinion/index.html'),
(u'Columnists',u'/columnists/index.html'),
(u'Editorials',u'/opinion/editorials/index.html'),
(u'Letters',u'/opinion/letters/index.html'),
(u'Business',u'/business/index.html'),
(u'Sports',u'/sports/index.html'),
(u'Arts',u'/entertainment/index.html'),
(u'Life',u'/life/index.html'),
(u'Technology',u'/technology/index.html'),
(u'Travel',u'/travel/index.html'),
(u'Health',u'/health/index.html')
]
# un-comment the following six lines for the Vancouver Province
title = u'Vancouver Province' title = u'Vancouver Province'
url_prefix = 'http://www.theprovince.com' url_prefix = 'http://www.theprovince.com'
description = u'News from Vancouver, BC' description = u'News from Vancouver, BC'
std_logo_url = 'http://www.theprovince.com/images/logo_theprovince.jpg'
logo_url = 'vplogo.jpg'
fp_tag = 'CAN_TP'
# un-comment the following three lines for the Vancouver Sun # un-comment the following six lines for the Vancouver Sun
#title = u'Vancouver Sun' ## title = u'Vancouver Sun'
#url_prefix = 'http://www.vancouversun.com' ## url_prefix = 'http://www.vancouversun.com'
#description = u'News from Vancouver, BC' ## description = u'News from Vancouver, BC'
## std_logo_url = 'http://www.vancouversun.com/images/logo_vancouversun.jpg'
## logo_url = 'vslogo.jpg'
## fp_tag = 'CAN_VS'
# un-comment the following three lines for the Edmonton Journal # un-comment the following six lines for the Calgary Herald
#title = u'Edmonton Journal' ## title = u'Calgary Herald'
#url_prefix = 'http://www.edmontonjournal.com' ## url_prefix = 'http://www.calgaryherald.com'
#description = u'News from Edmonton, AB' ## description = u'News from Calgary, AB'
## std_logo_url = 'http://www.calgaryherald.com/images/logo_calgaryherald.jpg'
## logo_url = 'chlogo.jpg'
## fp_tag = 'CAN_CH'
# un-comment the following three lines for the Calgary Herald # un-comment the following six lines for the Edmonton Journal
#title = u'Calgary Herald' ## title = u'Edmonton Journal'
#url_prefix = 'http://www.calgaryherald.com' ## url_prefix = 'http://www.edmontonjournal.com'
#description = u'News from Calgary, AB' ## description = u'News from Edmonton, AB'
## std_logo_url = 'http://www.edmontonjournal.com/images/logo_edmontonjournal.jpg'
## logo_url = 'ejlogo.jpg'
## fp_tag = 'CAN_EJ'
# un-comment the following three lines for the Regina Leader-Post # un-comment the following six lines for the Ottawa Citizen
#title = u'Regina Leader-Post' ## title = u'Ottawa Citizen'
#url_prefix = 'http://www.leaderpost.com' ## url_prefix = 'http://www.ottawacitizen.com'
#description = u'News from Regina, SK' ## description = u'News from Ottawa, ON'
## std_logo_url = 'http://www.ottawacitizen.com/images/logo_ottawacitizen.jpg'
## logo_url = 'oclogo.jpg'
## fp_tag = 'CAN_OC'
# un-comment the following three lines for the Saskatoon Star-Phoenix # un-comment the following six lines for the Montreal Gazette
#title = u'Saskatoon Star-Phoenix' ## title = u'Montreal Gazette'
#url_prefix = 'http://www.thestarphoenix.com' ## url_prefix = 'http://www.montrealgazette.com'
#description = u'News from Saskatoon, SK' ## description = u'News from Montreal, QC'
## std_logo_url = 'http://www.montrealgazette.com/images/logo_montrealgazette.jpg'
# un-comment the following three lines for the Windsor Star ## logo_url = 'mglogo.jpg'
#title = u'Windsor Star' ## fp_tag = 'CAN_MG'
#url_prefix = 'http://www.windsorstar.com'
#description = u'News from Windsor, ON'
# un-comment the following three lines for the Ottawa Citizen
#title = u'Ottawa Citizen'
#url_prefix = 'http://www.ottawacitizen.com'
#description = u'News from Ottawa, ON'
# un-comment the following three lines for the Montreal Gazette
#title = u'Montreal Gazette'
#url_prefix = 'http://www.montrealgazette.com'
#description = u'News from Montreal, QC'
Kindle_Fire=False
masthead_url = std_logo_url
url_list = []
language = 'en_CA' language = 'en_CA'
__author__ = 'Nick Redding' __author__ = 'Nick Redding'
no_stylesheets = True no_stylesheets = True
timefmt = ' [%b %d]' timefmt = ' [%b %d]'
encoding = 'utf-8'
extra_css = ''' extra_css = '''
.timestamp { font-size:xx-small; display: block; } .timestamp { font-size:xx-small; display: block; }
#storyheader { font-size: medium; } #storyheader { font-size: medium; }
#storyheader h1 { font-size: x-large; } #storyheader h1 { font-size: x-large; }
#storyheader h2 { font-size: large; font-style: italic; } #storyheader h2 { font-size: small; font-style: italic; }
.byline { font-size:xx-small; } .byline { font-size:xx-small; }
#photocaption { font-size: small; font-style: italic } #photocaption { font-size: xx-small; font-style: italic; font-weight: normal; }
#photocredit { font-size: xx-small; }''' .photocaption { font-size: xx-small; font-style: italic; font-weight: normal; }
keep_only_tags = [dict(name='div', attrs={'id':'storyheader'}),dict(name='div', attrs={'id':'storycontent'})] #photocredit { font-size: xx-small; font-weight: normal; }'''
keep_only_tags = [dict(name='div', attrs={'id':'storyheader'}),dict(name='div', attrs={'id':'storycontent'}),dict(name='div', attrs={'id':'storyphoto'})]
remove_tags = [{'class':'comments'}, remove_tags = [{'class':'comments'},
dict(name='div', attrs={'class':'navbar'}),dict(name='div', attrs={'class':'morelinks'}), dict(name='div', attrs={'class':'navbar'}),dict(name='div', attrs={'class':'morelinks'}),
dict(name='h2', attrs={'id':'photocredit'}),
dict(name='div', attrs={'class':'viewmore'}),dict(name='li', attrs={'class':'email'}), dict(name='div', attrs={'class':'viewmore'}),dict(name='li', attrs={'class':'email'}),
dict(name='div', attrs={'class':'story_tool_hr'}),dict(name='div', attrs={'class':'clear'}), dict(name='div', attrs={'class':'story_tool_hr'}),dict(name='div', attrs={'class':'clear'}),
dict(name='div', attrs={'class':'story_tool'}),dict(name='div', attrs={'class':'copyright'}), dict(name='div', attrs={'class':'story_tool'}),dict(name='div', attrs={'class':'copyright'}),
dict(name='div', attrs={'class':'rule_grey_solid'}), dict(name='div', attrs={'class':'rule_grey_solid'}),
dict(name='li', attrs={'class':'print'}),dict(name='li', attrs={'class':'share'}),dict(name='ul', attrs={'class':'bullet'})] dict(name='li', attrs={'class':'print'}),dict(name='li', attrs={'class':'share'}),dict(name='ul', attrs={'class':'bullet'})]
def get_cover_url(self):
from datetime import timedelta, date
cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str(date.today().day)+'/lg/'+self.fp_tag+'.jpg'
br = BasicNewsRecipe.get_browser()
daysback=1
try:
br.open(cover)
except:
while daysback<7:
cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str((date.today() - timedelta(days=daysback)).day)+'/lg/'+self.fp_tag+'.jpg'
br = BasicNewsRecipe.get_browser()
try:
br.open(cover)
except:
daysback = daysback+1
continue
break
if daysback==7:
self.log("\nCover unavailable")
cover = None
return cover
def prepare_masthead_image(self, path_to_image, out_path):
if self.Kindle_Fire:
from calibre.utils.magick import Image, create_canvas
img = Image()
img.open(path_to_image)
width, height = img.size
img2 = create_canvas(width, height)
img2.compose(img)
img2.save(out_path)
else:
BasicNewsRecipe.prepare_masthead_image(self, path_to_image, out_path)
def fixChars(self,string):
# Replace lsquo (\x91)
fixed = re.sub("\x91","",string)
# Replace rsquo (\x92)
fixed = re.sub("\x92","",fixed)
# Replace ldquo (\x93)
fixed = re.sub("\x93","“",fixed)
# Replace rdquo (\x94)
fixed = re.sub("\x94","”",fixed)
# Replace ndash (\x96)
fixed = re.sub("\x96","",fixed)
# Replace mdash (\x97)
fixed = re.sub("\x97","—",fixed)
fixed = re.sub("&#x2019;","",fixed)
return fixed
def massageNCXText(self, description):
# Kindle TOC descriptions won't render certain characters
if description:
massaged = unicode(BeautifulStoneSoup(description, convertEntities=BeautifulStoneSoup.HTML_ENTITIES))
# Replace '&' with '&'
massaged = re.sub("&","&", massaged)
return self.fixChars(massaged)
else:
return description
def populate_article_metadata(self, article, soup, first):
if first:
picdiv = soup.find('body').find('img')
if picdiv is not None:
self.add_toc_thumbnail(article,re.sub(r'links\\link\d+\\','',picdiv['src']))
xtitle = article.text_summary.strip()
if len(xtitle) == 0:
desc = soup.find('meta',attrs={'property':'og:description'})
if desc is not None:
article.summary = article.text_summary = desc['content']
def strip_anchors(self,soup):
paras = soup.findAll(True)
for para in paras:
aTags = para.findAll('a')
for a in aTags:
if a.img is None:
a.replaceWith(a.renderContents().decode('cp1252','replace'))
return soup
def preprocess_html(self,soup): def preprocess_html(self,soup):
#delete iempty id attributes--they screw up the TOC for unknow reasons #delete empty id attributes--they screw up the TOC for unknown reasons
divtags = soup.findAll('div',attrs={'id':''}) divtags = soup.findAll('div',attrs={'id':''})
if divtags: if divtags:
for div in divtags: for div in divtags:
del(div['id']) del(div['id'])
return soup
pgall = soup.find('div',attrs={'id':'storyphoto'})
if pgall is not None: # photo gallery perhaps
if (soup.find('div',attrs={'id':'storycontent'}) is None):
allpics = Tag(soup,'div')
first_img = pgall.find('div','storyimage')
if first_img is not None:
first_img.extract()
tlist = pgall.find('div',attrs={'id':'relatedthumbs'})
if tlist is not None:
for atag in tlist.findAll('a'):
img = Tag(soup,'img')
srcpre, sep, srcpost = atag.img['src'].partition('?')
img['src'] = srcpre
pdesc = Tag(soup,'p')
pdesc.insert(0,atag.img['alt'])
pdesc['class']='photocaption'
div = Tag(soup,'div')
div.insert(0,pdesc)
div.insert(0,img)
allpics.append(div)
pgall.replaceWith(allpics)
for pg in soup.findAll('div',attrs={'id':'storyphoto'}):
pg.extract()
return self.strip_anchors(soup)
def parse_index(self): def parse_index(self):
soup = self.index_to_soup(self.url_prefix+'/news/todays-paper/index.html')
articles = {} articles = {}
key = 'News' ans = []
ans = ['News']
# Find each instance of class="sectiontitle", class="featurecontent"
for divtag in soup.findAll('div',attrs={'class' : ["section_title02","featurecontent"]}):
#self.log(" div class = %s" % divtag['class'])
if divtag['class'].startswith('section_title'):
# div contains section title
if not divtag.h3:
continue
key = self.tag_to_string(divtag.h3,False)
ans.append(key)
self.log("Section name %s" % key)
continue
# div contains article data
h1tag = divtag.find('h1')
if not h1tag:
continue
atag = h1tag.find('a',href=True)
if not atag:
continue
url = self.url_prefix+'/news/todays-paper/'+atag['href']
#self.log("Section %s" % key)
#self.log("url %s" % url)
title = self.tag_to_string(atag,False)
#self.log("title %s" % title)
pubdate = ''
description = ''
ptag = divtag.find('p');
if ptag:
description = self.tag_to_string(ptag,False)
#self.log("description %s" % description)
author = ''
autag = divtag.find('h4')
if autag:
author = self.tag_to_string(autag,False)
#self.log("author %s" % author)
if not articles.has_key(key):
articles[key] = []
articles[key].append(dict(title=title,url=url,date=pubdate,description=description,author=author,content=''))
def handle_article(adiv,key):
h1tag = adiv.h1
if h1tag is not None:
atag = h1tag.a
if atag is not None:
url = atag['href']
if atag['href'].startswith('http'):
return
elif atag['href'].startswith('/'):
url = self.url_prefix+atag['href']
else:
url = self.url_prefix+'/'+atag['href']
if url in self.url_list:
return
self.url_list.append(url)
title = self.tag_to_string(atag,False)
if 'VIDEO' in title.upper():
return
if 'GALLERY' in title.upper():
return
if 'PHOTOS' in title.upper():
return
dtag = adiv.find('div','content')
description=''
print("URL "+url)
print("TITLE "+title)
if dtag is not None:
stag = dtag.span
if stag is not None:
if stag['class'] != 'timestamp':
description = self.tag_to_string(stag,False)
else:
description = self.tag_to_string(dtag,False)
print("DESCRIPTION: "+description)
if not articles.has_key(key):
articles[key] = []
articles[key].append(dict(title=title,url=url,date='',description=description,author='',content=''))
def parse_web_index(key, keyurl):
try:
soup = self.index_to_soup(self.url_prefix+keyurl)
except:
return
ans.append(key)
mainsoup = soup.find('div','bodywrapper')
footer = mainsoup.find(attrs={'id':'footerfeature'})
if footer is not None:
footer.extract()
print("Section: "+key)
for wdiv in mainsoup.findAll('div',attrs={'id':re.compile('^HorizontalFeatureSlider_1_Story')}):
handle_article(wdiv,key)
wdiv.extract()
for wdiv in mainsoup.findAll(attrs={'id':['featurewidget','textfeature','textlinks_timestamp']}):
for adiv in wdiv.findAll('div','featurecontent'):
handle_article(adiv,key)
for (k,url) in self.postmedia_index_pages:
parse_web_index(k,url)
ans = [(key, articles[key]) for key in ans if articles.has_key(key)] ans = [(key, articles[key]) for key in ans if articles.has_key(key)]
return ans return ans

View File

@ -1,105 +1,136 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__license__ = 'GPL v3' __license__ = 'GPL v3'
''' '''
www.canada.com www.canada.com
''' '''
import re import re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.recipes import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, Tag
class CanWestPaper(BasicNewsRecipe): class CanWestPaper(BasicNewsRecipe):
# un-comment the following four lines for the Victoria Times Colonist postmedia_index_pages = [
## title = u'Victoria Times Colonist' (u'Headlines',u'/index.html'),
## url_prefix = 'http://www.timescolonist.com' (u'Ottawa & Area',u'/news/ottawa/index.html'),
## description = u'News from Victoria, BC' (u'Vancouver',u'/news/vancouver/index.html'),
## fp_tag = 'CAN_TC' (u'Calgary',u'/news/calgary/index.html'),
(u'Edmonton',u'/news/edmonton/index.html'),
(u'Montreal',u'/news/montreal/index.html'),
(u'Fraser Valley',u'/news/fraser-valley/index.html'),
(u'British Columbia',u'/news/bc/index.html'),
(u'Alberta',u'/news/alberta/index.html'),
(u'Canada',u'/news/canada/index.html'),
(u'National',u'/news/national/index.html'),
(u'Politics',u'/news/politics/index.html'),
(u'Insight',u'/news/insight/index.html'),
(u'Special Reports',u'/news/specialreports/index.html'),
(u'Gangs',u'/news/gangs/index.html'),
(u'Education',u'/news/education/index.html'),
(u'Health',u'/news/health/index.html'),
(u'Environment',u'/news/environment/index.html'),
(u'World',u'/news/world/index.html'),
(u'Police Blotter',u'/news/crime-and-justice/index.html'),
(u'Crime',u'/news/blotter/index.html'),
(u'Around Town',u'/news/topic.html?t=keyword&q=Around+Town'),
(u'Diplomatica',u'/news/diplomatica/index.html'),
(u'Opinion',u'/opinion/index.html'),
(u'Columnists',u'/columnists/index.html'),
(u'Editorials',u'/opinion/editorials/index.html'),
(u'Letters',u'/opinion/letters/index.html'),
(u'Business',u'/business/index.html'),
(u'Sports',u'/sports/index.html'),
(u'Arts',u'/entertainment/index.html'),
(u'Life',u'/life/index.html'),
(u'Technology',u'/technology/index.html'),
(u'Travel',u'/travel/index.html'),
(u'Health',u'/health/index.html')
]
# un-comment the following four lines for the Vancouver Province
# un-comment the following six lines for the Vancouver Province
## title = u'Vancouver Province' ## title = u'Vancouver Province'
## url_prefix = 'http://www.theprovince.com' ## url_prefix = 'http://www.theprovince.com'
## description = u'News from Vancouver, BC' ## description = u'News from Vancouver, BC'
## fp_tag = 'CAN_VP' ## std_logo_url = 'http://www.theprovince.com/images/logo_theprovince.jpg'
## logo_url = 'vplogo.jpg'
## fp_tag = 'CAN_TP'
# un-comment the following four lines for the Vancouver Sun # un-comment the following six lines for the Vancouver Sun
title = u'Vancouver Sun' title = u'Vancouver Sun'
url_prefix = 'http://www.vancouversun.com' url_prefix = 'http://www.vancouversun.com'
description = u'News from Vancouver, BC' description = u'News from Vancouver, BC'
std_logo_url = 'http://www.vancouversun.com/images/logo_vancouversun.jpg'
logo_url = 'vslogo.jpg'
fp_tag = 'CAN_VS' fp_tag = 'CAN_VS'
# un-comment the following four lines for the Edmonton Journal # un-comment the following six lines for the Calgary Herald
## title = u'Edmonton Journal'
## url_prefix = 'http://www.edmontonjournal.com'
## description = u'News from Edmonton, AB'
## fp_tag = 'CAN_EJ'
# un-comment the following four lines for the Calgary Herald
## title = u'Calgary Herald' ## title = u'Calgary Herald'
## url_prefix = 'http://www.calgaryherald.com' ## url_prefix = 'http://www.calgaryherald.com'
## description = u'News from Calgary, AB' ## description = u'News from Calgary, AB'
## std_logo_url = 'http://www.calgaryherald.com/images/logo_calgaryherald.jpg'
## logo_url = 'chlogo.jpg'
## fp_tag = 'CAN_CH' ## fp_tag = 'CAN_CH'
# un-comment the following four lines for the Regina Leader-Post # un-comment the following six lines for the Edmonton Journal
## title = u'Regina Leader-Post' ## title = u'Edmonton Journal'
## url_prefix = 'http://www.leaderpost.com' ## url_prefix = 'http://www.edmontonjournal.com'
## description = u'News from Regina, SK' ## description = u'News from Edmonton, AB'
## fp_tag = '' ## std_logo_url = 'http://www.edmontonjournal.com/images/logo_edmontonjournal.jpg'
## logo_url = 'ejlogo.jpg'
## fp_tag = 'CAN_EJ'
# un-comment the following four lines for the Saskatoon Star-Phoenix # un-comment the following six lines for the Ottawa Citizen
## title = u'Saskatoon Star-Phoenix'
## url_prefix = 'http://www.thestarphoenix.com'
## description = u'News from Saskatoon, SK'
## fp_tag = ''
# un-comment the following four lines for the Windsor Star
## title = u'Windsor Star'
## url_prefix = 'http://www.windsorstar.com'
## description = u'News from Windsor, ON'
## fp_tag = 'CAN_'
# un-comment the following four lines for the Ottawa Citizen
## title = u'Ottawa Citizen' ## title = u'Ottawa Citizen'
## url_prefix = 'http://www.ottawacitizen.com' ## url_prefix = 'http://www.ottawacitizen.com'
## description = u'News from Ottawa, ON' ## description = u'News from Ottawa, ON'
## std_logo_url = 'http://www.ottawacitizen.com/images/logo_ottawacitizen.jpg'
## logo_url = 'oclogo.jpg'
## fp_tag = 'CAN_OC' ## fp_tag = 'CAN_OC'
# un-comment the following four lines for the Montreal Gazette # un-comment the following six lines for the Montreal Gazette
## title = u'Montreal Gazette' ## title = u'Montreal Gazette'
## url_prefix = 'http://www.montrealgazette.com' ## url_prefix = 'http://www.montrealgazette.com'
## description = u'News from Montreal, QC' ## description = u'News from Montreal, QC'
## std_logo_url = 'http://www.montrealgazette.com/images/logo_montrealgazette.jpg'
## logo_url = 'mglogo.jpg'
## fp_tag = 'CAN_MG' ## fp_tag = 'CAN_MG'
Kindle_Fire=False
masthead_url = std_logo_url
url_list = []
language = 'en_CA' language = 'en_CA'
__author__ = 'Nick Redding' __author__ = 'Nick Redding'
no_stylesheets = True no_stylesheets = True
timefmt = ' [%b %d]' timefmt = ' [%b %d]'
encoding = 'utf-8'
extra_css = ''' extra_css = '''
.timestamp { font-size:xx-small; display: block; } .timestamp { font-size:xx-small; display: block; }
#storyheader { font-size: medium; } #storyheader { font-size: medium; }
#storyheader h1 { font-size: x-large; } #storyheader h1 { font-size: x-large; }
#storyheader h2 { font-size: large; font-style: italic; } #storyheader h2 { font-size: small; font-style: italic; }
.byline { font-size:xx-small; } .byline { font-size:xx-small; }
#photocaption { font-size: small; font-style: italic } #photocaption { font-size: xx-small; font-style: italic; font-weight: normal; }
#photocredit { font-size: xx-small; }''' .photocaption { font-size: xx-small; font-style: italic; font-weight: normal; }
keep_only_tags = [dict(name='div', attrs={'id':'storyheader'}),dict(name='div', attrs={'id':'storycontent'})] #photocredit { font-size: xx-small; font-weight: normal; }'''
keep_only_tags = [dict(name='div', attrs={'id':'storyheader'}),dict(name='div', attrs={'id':'storycontent'}),dict(name='div', attrs={'id':'storyphoto'})]
remove_tags = [{'class':'comments'}, remove_tags = [{'class':'comments'},
dict(name='div', attrs={'class':'navbar'}),dict(name='div', attrs={'class':'morelinks'}), dict(name='div', attrs={'class':'navbar'}),dict(name='div', attrs={'class':'morelinks'}),
dict(name='h2', attrs={'id':'photocredit'}),
dict(name='div', attrs={'class':'viewmore'}),dict(name='li', attrs={'class':'email'}), dict(name='div', attrs={'class':'viewmore'}),dict(name='li', attrs={'class':'email'}),
dict(name='div', attrs={'class':'story_tool_hr'}),dict(name='div', attrs={'class':'clear'}), dict(name='div', attrs={'class':'story_tool_hr'}),dict(name='div', attrs={'class':'clear'}),
dict(name='div', attrs={'class':'story_tool'}),dict(name='div', attrs={'class':'copyright'}), dict(name='div', attrs={'class':'story_tool'}),dict(name='div', attrs={'class':'copyright'}),
dict(name='div', attrs={'class':'rule_grey_solid'}), dict(name='div', attrs={'class':'rule_grey_solid'}),
dict(name='li', attrs={'class':'print'}),dict(name='li', attrs={'class':'share'}),dict(name='ul', attrs={'class':'bullet'})] dict(name='li', attrs={'class':'print'}),dict(name='li', attrs={'class':'share'}),dict(name='ul', attrs={'class':'bullet'})]
def get_cover_url(self): def get_cover_url(self):
from datetime import timedelta, date from datetime import timedelta, date
if self.fp_tag=='':
return None
cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str(date.today().day)+'/lg/'+self.fp_tag+'.jpg' cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str(date.today().day)+'/lg/'+self.fp_tag+'.jpg'
br = BasicNewsRecipe.get_browser() br = BasicNewsRecipe.get_browser()
daysback=1 daysback=1
@ -120,6 +151,18 @@ class CanWestPaper(BasicNewsRecipe):
cover = None cover = None
return cover return cover
def prepare_masthead_image(self, path_to_image, out_path):
if self.Kindle_Fire:
from calibre.utils.magick import Image, create_canvas
img = Image()
img.open(path_to_image)
width, height = img.size
img2 = create_canvas(width, height)
img2.compose(img)
img2.save(out_path)
else:
BasicNewsRecipe.prepare_masthead_image(self, path_to_image, out_path)
def fixChars(self,string): def fixChars(self,string):
# Replace lsquo (\x91) # Replace lsquo (\x91)
fixed = re.sub("\x91","",string) fixed = re.sub("\x91","",string)
@ -166,55 +209,106 @@ class CanWestPaper(BasicNewsRecipe):
a.replaceWith(a.renderContents().decode('cp1252','replace')) a.replaceWith(a.renderContents().decode('cp1252','replace'))
return soup return soup
def preprocess_html(self, soup):
def preprocess_html(self,soup):
#delete empty id attributes--they screw up the TOC for unknown reasons
divtags = soup.findAll('div',attrs={'id':''})
if divtags:
for div in divtags:
del(div['id'])
pgall = soup.find('div',attrs={'id':'storyphoto'})
if pgall is not None: # photo gallery perhaps
if (soup.find('div',attrs={'id':'storycontent'}) is None):
allpics = Tag(soup,'div')
first_img = pgall.find('div','storyimage')
if first_img is not None:
first_img.extract()
tlist = pgall.find('div',attrs={'id':'relatedthumbs'})
if tlist is not None:
for atag in tlist.findAll('a'):
img = Tag(soup,'img')
srcpre, sep, srcpost = atag.img['src'].partition('?')
img['src'] = srcpre
pdesc = Tag(soup,'p')
pdesc.insert(0,atag.img['alt'])
pdesc['class']='photocaption'
div = Tag(soup,'div')
div.insert(0,pdesc)
div.insert(0,img)
allpics.append(div)
pgall.replaceWith(allpics)
for pg in soup.findAll('div',attrs={'id':'storyphoto'}):
pg.extract()
return self.strip_anchors(soup) return self.strip_anchors(soup)
def parse_index(self): def parse_index(self):
soup = self.index_to_soup(self.url_prefix+'/news/todays-paper/index.html')
articles = {} articles = {}
key = 'News' ans = []
ans = ['News']
# Find each instance of class="sectiontitle", class="featurecontent"
for divtag in soup.findAll('div',attrs={'class' : ["section_title02","featurecontent"]}):
#self.log(" div class = %s" % divtag['class'])
if divtag['class'].startswith('section_title'):
# div contains section title
if not divtag.h3:
continue
key = self.tag_to_string(divtag.h3,False)
ans.append(key)
self.log("Section name %s" % key)
continue
# div contains article data
h1tag = divtag.find('h1')
if not h1tag:
continue
atag = h1tag.find('a',href=True)
if not atag:
continue
url = self.url_prefix+'/news/todays-paper/'+atag['href']
#self.log("Section %s" % key)
#self.log("url %s" % url)
title = self.tag_to_string(atag,False)
#self.log("title %s" % title)
pubdate = ''
description = ''
ptag = divtag.find('p');
if ptag:
description = self.tag_to_string(ptag,False)
#self.log("description %s" % description)
author = ''
autag = divtag.find('h4')
if autag:
author = self.tag_to_string(autag,False)
#self.log("author %s" % author)
if not articles.has_key(key):
articles[key] = []
articles[key].append(dict(title=title,url=url,date=pubdate,description=description,author=author,content=''))
def handle_article(adiv,key):
h1tag = adiv.h1
if h1tag is not None:
atag = h1tag.a
if atag is not None:
url = atag['href']
if atag['href'].startswith('http'):
return
elif atag['href'].startswith('/'):
url = self.url_prefix+atag['href']
else:
url = self.url_prefix+'/'+atag['href']
if url in self.url_list:
return
self.url_list.append(url)
title = self.tag_to_string(atag,False)
if 'VIDEO' in title.upper():
return
if 'GALLERY' in title.upper():
return
if 'PHOTOS' in title.upper():
return
dtag = adiv.find('div','content')
description=''
print("URL "+url)
print("TITLE "+title)
if dtag is not None:
stag = dtag.span
if stag is not None:
if stag['class'] != 'timestamp':
description = self.tag_to_string(stag,False)
else:
description = self.tag_to_string(dtag,False)
print("DESCRIPTION: "+description)
if not articles.has_key(key):
articles[key] = []
articles[key].append(dict(title=title,url=url,date='',description=description,author='',content=''))
def parse_web_index(key, keyurl):
try:
soup = self.index_to_soup(self.url_prefix+keyurl)
except:
return
ans.append(key)
mainsoup = soup.find('div','bodywrapper')
footer = mainsoup.find(attrs={'id':'footerfeature'})
if footer is not None:
footer.extract()
print("Section: "+key)
for wdiv in mainsoup.findAll('div',attrs={'id':re.compile('^HorizontalFeatureSlider_1_Story')}):
handle_article(wdiv,key)
wdiv.extract()
for wdiv in mainsoup.findAll(attrs={'id':['featurewidget','textfeature','textlinks_timestamp']}):
for adiv in wdiv.findAll('div','featurecontent'):
handle_article(adiv,key)
for (k,url) in self.postmedia_index_pages:
parse_web_index(k,url)
ans = [(key, articles[key]) for key in ans if articles.has_key(key)] ans = [(key, articles[key]) for key in ans if articles.has_key(key)]
return ans return ans

View File

@ -1,7 +1,7 @@
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
import re import re
class AdvancedUserRecipe1312886443(BasicNewsRecipe): class WNP(BasicNewsRecipe):
title = u'WNP' title = u'WNP'
cover_url= 'http://k.wnp.pl/images/wnpLogo.gif' cover_url= 'http://k.wnp.pl/images/wnpLogo.gif'
__author__ = 'fenuks' __author__ = 'fenuks'
@ -12,7 +12,7 @@ class AdvancedUserRecipe1312886443(BasicNewsRecipe):
oldest_article = 8 oldest_article = 8
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets= True no_stylesheets= True
remove_tags=[dict(attrs={'class':'printF'})] remove_tags=[dict(attrs={'class':['printF', 'border3B2 clearfix', 'articleMenu clearfix']})]
feeds = [(u'Wiadomości gospodarcze', u'http://www.wnp.pl/rss/serwis_rss.xml'), feeds = [(u'Wiadomości gospodarcze', u'http://www.wnp.pl/rss/serwis_rss.xml'),
(u'Serwis Energetyka - Gaz', u'http://www.wnp.pl/rss/serwis_rss_1.xml'), (u'Serwis Energetyka - Gaz', u'http://www.wnp.pl/rss/serwis_rss_1.xml'),
(u'Serwis Nafta - Chemia', u'http://www.wnp.pl/rss/serwis_rss_2.xml'), (u'Serwis Nafta - Chemia', u'http://www.wnp.pl/rss/serwis_rss_2.xml'),

View File

@ -12,7 +12,7 @@ class cdnet(BasicNewsRecipe):
title = 'zdnet' title = 'zdnet'
description = 'zdnet security' description = 'zdnet security'
__author__ = 'Oliver Niesner' __author__ = 'Oliver Niesner, Krittika Goyal'
language = 'en' language = 'en'
use_embedded_content = False use_embedded_content = False
@ -20,41 +20,42 @@ class cdnet(BasicNewsRecipe):
max_articles_per_feed = 40 max_articles_per_feed = 40
no_stylesheets = True no_stylesheets = True
encoding = 'latin1' encoding = 'latin1'
auto_cleanup = True
remove_tags = [dict(id='eyebrows'), #remove_tags = [dict(id='eyebrows'),
dict(id='header'), #dict(id='header'),
dict(id='search'), #dict(id='search'),
dict(id='nav'), #dict(id='nav'),
dict(id='blog-author-info'), #dict(id='blog-author-info'),
dict(id='post-tags'), #dict(id='post-tags'),
dict(id='bio-naraine'), #dict(id='bio-naraine'),
dict(id='bio-kennedy'), #dict(id='bio-kennedy'),
dict(id='author-short-disclosure-kennedy'), #dict(id='author-short-disclosure-kennedy'),
dict(id=''), #dict(id=''),
dict(name='div', attrs={'class':'banner'}), #dict(name='div', attrs={'class':'banner'}),
dict(name='div', attrs={'class':'int'}), #dict(name='div', attrs={'class':'int'}),
dict(name='div', attrs={'class':'talkback clear space-2'}), #dict(name='div', attrs={'class':'talkback clear space-2'}),
dict(name='div', attrs={'class':'content-1 clear'}), #dict(name='div', attrs={'class':'content-1 clear'}),
dict(name='div', attrs={'class':'space-2'}), #dict(name='div', attrs={'class':'space-2'}),
dict(name='div', attrs={'class':'space-3'}), #dict(name='div', attrs={'class':'space-3'}),
dict(name='div', attrs={'class':'thumb-2 left'}), #dict(name='div', attrs={'class':'thumb-2 left'}),
dict(name='div', attrs={'class':'hotspot'}), #dict(name='div', attrs={'class':'hotspot'}),
dict(name='div', attrs={'class':'hed hed-1 space-1'}), #dict(name='div', attrs={'class':'hed hed-1 space-1'}),
dict(name='div', attrs={'class':'view-1 clear content-3 space-2'}), #dict(name='div', attrs={'class':'view-1 clear content-3 space-2'}),
dict(name='div', attrs={'class':'hed hed-1 space-1'}), #dict(name='div', attrs={'class':'hed hed-1 space-1'}),
dict(name='div', attrs={'class':'hed hed-1'}), #dict(name='div', attrs={'class':'hed hed-1'}),
dict(name='div', attrs={'class':'post-header'}), #dict(name='div', attrs={'class':'post-header'}),
dict(name='div', attrs={'class':'lvl-nav clear'}), #dict(name='div', attrs={'class':'lvl-nav clear'}),
dict(name='div', attrs={'class':'t-share-overlay overlay-pop contain-overlay-4'}), #dict(name='div', attrs={'class':'t-share-overlay overlay-pop contain-overlay-4'}),
dict(name='p', attrs={'class':'tags'}), #dict(name='p', attrs={'class':'tags'}),
dict(name='span', attrs={'class':'follow'}), #dict(name='span', attrs={'class':'follow'}),
dict(name='span', attrs={'class':'int'}), #dict(name='span', attrs={'class':'int'}),
dict(name='h4', attrs={'class':'h s-4'}), #dict(name='h4', attrs={'class':'h s-4'}),
dict(name='a', attrs={'href':'http://www.twitter.com/ryanaraine'}), #dict(name='a', attrs={'href':'http://www.twitter.com/ryanaraine'}),
dict(name='div', attrs={'class':'special1'})] #dict(name='div', attrs={'class':'special1'})]
remove_tags_after = [dict(name='div', attrs={'class':'clear'})] #remove_tags_after = [dict(name='div', attrs={'class':'clear'})]
feeds = [ ('zdnet', 'http://feeds.feedburner.com/zdnet/security') ] feeds = [ ('zdnet', 'http://feeds.feedburner.com/zdnet/security') ]
@ -63,6 +64,3 @@ class cdnet(BasicNewsRecipe):
for item in soup.findAll(style=True): for item in soup.findAll(style=True):
del item['style'] del item['style']
return soup return soup

View File

@ -1,35 +1,229 @@
body { background-color: white; } body { background-color: white; }
/*
** The following rules apply principally to the line items shown in the
** Authors, Titles, Genres, Series, and Recently Added sections. Rules for the
** Descriptions section are grouped together later in the file.
** ------------------------------------------------------------------------
*/
/*
** <div> grouping an author's works together
** Used in Sections:
** Authors
**
** Minimize widows and orphans by logically grouping chunks
** Some reports of problems with Sony (ADE) ereaders
** ADE: page-break-inside:avoid;
** iBooks: display:inline-block;
** width:100%;
*/
div.author_logical_group {
page-break-inside:avoid;
}
/*
** Force page break when starting new initial letter
** Used in Sections:
** Authors
** Titles
*/
div.initial_letter {
page-break-before:always;
}
/*
** Author name
** Used in Sections:
** Authors
** Genres
** Recently Added
*/
p.author_index {
clear:both;
font-size:large;
font-weight:bold;
text-align:left;
margin-top:0.25px;
margin-bottom:-2px;
text-indent: 0em;
}
/*
** Index letter
** Used in Sections:
** Authors
** Titles
*/
p.author_title_letter_index {
clear:both;
font-size:x-large;
text-align:center;
font-weight:bold;
margin-top:0px;
margin-bottom:0px;
}
/*
** Index letter
** Used in Sections:
** Series
*/
p.series_letter_index {
font-size:x-large;
text-align:center;
font-weight:bold;
margin-top:1em;
margin-bottom:0px;
}
/*
** Month-Year
** Used in Sections:
** Recently Added
*/
p.date_index {
clear:both;
font-size:x-large;
text-align:center;
font-weight:bold;
margin-top:1em;
margin-bottom:0px;
}
p.date_read {
clear:both;
text-align:left;
margin-top:0px;
margin-bottom:0px;
margin-left:6em;
text-indent:-6em;
}
/*
** Series name
** Used in Sections:
** Authors
** Series
** Genres
** Recently Added
** Optimized for ePub
*/
p.series {
clear:both;
font-style:italic;
margin-top:0.10em;
margin-bottom:0em;
margin-left:1.5em;
text-align:left;
text-indent:-1.25em;
}
/*
** Series name
** Used in Sections:
** Authors
** Series
** Genres
** Recently Added
** Optimized for mobi
*/
p.series_mobi {
clear:both;
font-style:italic;
margin-top:0em;
margin-bottom:0em;
margin-left:0em;
text-align:left;
text-indent:-30px;
}
/*
** Section title
** Used in Sections:
** Authors
** Titles
** Series
** Genres
** Recently Added
** Descriptions
*/
p.title {
margin-top:0em;
margin-bottom:0em;
text-align:center;
font-style:italic;
font-size:xx-large;
}
/*
** Line item book listing
** Used in Sections:
** Authors
** Titles
** Series
** Genres
** Recently Added
*/
p.line_item {
clear: both;
font-family:monospace;
margin-top:0px;
margin-bottom:0px;
margin-left:2em;
text-align:left;
text-indent:-2em;
}
/*
** Prefix
** Used in Sections:
** Authors
** Titles
** Series
** Genres
** Recently Added
*/
span.prefix {
float:left;
margin-left: 0.25em;
text-align: left;
vertical-align: middle;
width: 1.5em;
}
/*
** Book details entry
** Used in Sections:
** Authors
** Titles
** Series
** Genres
** Recently Added
*/
span.entry {
font-family: serif;
vertical-align:middle;
}
/*
** The following rules apply to Descriptions
** -----------------------------------------
*/
/*
** Link to Series
*/
a.series_id { a.series_id {
font-style:normal; font-style:normal;
font-size:large; font-size:large;
} }
/* /*
* Minimize widows and orphans by logically grouping chunks ** Various dividers
* Some reports of problems with Sony (ADE) ereaders
* ADE: page-break-inside:avoid;
* iBooks: display:inline-block;
* width:100%;
*/ */
div.author_logical_group {
page-break-inside:avoid;
}
div.description > p:first-child {
margin: 0 0 0 0;
text-indent: 0em;
}
div.description {
margin: 0 0 0 0;
text-indent: 1em;
}
div.initial_letter {
page-break-before:always;
}
hr.annotations_divider { hr.annotations_divider {
width:50%; width:50%;
margin-left:1em; margin-left:1em;
@ -63,47 +257,21 @@ hr.merged_comments_divider {
border-left: solid white 0px; border-left: solid white 0px;
} }
p.date_read { /*
text-align:left; ** Author name
margin-top:0px; */
margin-bottom:0px;
margin-left:6em;
text-indent:-6em;
}
p.author { p.author {
clear:both;
font-size:large; font-size:large;
margin-top:0em; margin-top:0em;
margin-bottom:0em; margin-bottom:0.1em;
text-align: center; text-align: center;
text-indent: 0em; text-indent: 0em;
} }
p.author_index { /*
font-size:large; ** Formats
font-weight:bold; */
text-align:left;
margin-top:0px;
margin-bottom:-2px;
text-indent: 0em;
}
p.author_title_letter_index {
font-size:x-large;
text-align:center;
font-weight:bold;
margin-top:0px;
margin-bottom:0px;
}
p.date_index {
font-size:x-large;
text-align:center;
font-weight:bold;
margin-top:1em;
margin-bottom:0px;
}
p.formats { p.formats {
font-size:90%; font-size:90%;
margin-top:0em; margin-top:0em;
@ -112,6 +280,9 @@ p.formats {
text-indent: 0.0in; text-indent: 0.0in;
} }
/*
** Genres
*/
p.genres { p.genres {
font-style:normal; font-style:normal;
margin-top:0.5em; margin-top:0.5em;
@ -120,59 +291,55 @@ p.genres {
text-indent: 0.0in; text-indent: 0.0in;
} }
p.series {
font-style:italic;
margin-top:0.25em;
margin-bottom:0em;
margin-left:2em;
text-align:left;
text-indent:-2em;
}
/*
** Series name
*/
p.series_id { p.series_id {
margin-top:0em; margin-top:0em;
margin-bottom:0em; margin-bottom:0em;
text-align:center; text-align:center;
} }
p.series_letter_index { /*
font-size:x-large; ** Publisher, Publication Date
text-align:center; */
font-weight:bold;
margin-top:1em;
margin-bottom:0px;
}
p.title {
margin-top:0em;
margin-bottom:0em;
text-align:center;
font-style:italic;
font-size:xx-large;
}
p.wishlist_item, p.unread_book, p.read_book {
text-align:left;
margin-top:0px;
margin-bottom:0px;
margin-left:2em;
text-indent:-2em;
}
td.publisher, td.date { td.publisher, td.date {
font-weight:bold; font-weight:bold;
text-align:center; text-align:center;
} }
/*
** Rating
*/
td.rating{ td.rating{
text-align:center; text-align:center;
} }
/*
** Additional notes
*/
td.notes { td.notes {
font-size: 100%; font-size: 100%;
text-align:center; text-align:center;
} }
/*
** Thumbnail
*/
td.thumbnail img { td.thumbnail img {
-webkit-box-shadow: 4px 4px 12px #999; -webkit-box-shadow: 4px 4px 12px #999;
} }
/*
** Comments
*/
div.description {
margin: 0 0 0 0;
text-indent: 1em;
}
div.description > p:first-child {
margin: 0 0 0 0;
text-indent: 0em;
}

Binary file not shown.

View File

@ -506,13 +506,11 @@ compile_gpm_templates = True
# default_tweak_format = 'remember' # default_tweak_format = 'remember'
default_tweak_format = None default_tweak_format = None
#: Enable multi-character first-letters in the tag browser #: Do not preselect a completion when editing authors/tags/series/etc.
# Some languages have letters that can be represented by multiple characters. # This means that you can make changes and press Enter and your changes will
# For example, Czech has a 'character' "ch" that sorts between "h" and "i". # not be overwritten by a matching completion. However, if you wish to use the
# If this tweak is True, then the tag browser will take these characters into # completions you will now have to press Tab to select one before pressing
# consideration when partitioning by first letter. # Enter. Which technique you prefer will depend on the state of metadata in
# Examples: # your library and your personal editing style.
# enable_multicharacters_in_tag_browser = True preselect_first_completion = False
# enable_multicharacters_in_tag_browser = False
enable_multicharacters_in_tag_browser = True

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
resources/images/scroll.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -27,6 +27,7 @@
*/ */
.cbj_banner { .cbj_banner {
background: #eee; background: #eee;
color: black;
border: thin solid black; border: thin solid black;
margin: 1em; margin: 1em;
padding: 1em; padding: 1em;

View File

@ -822,7 +822,6 @@ application/x-lzh lzh
application/x-lzx lzx application/x-lzx lzx
application/x-maker book fb fbdoc fm frame frm maker application/x-maker book fb fbdoc fm frame frm maker
application/x-mif mif application/x-mif mif
application/x-mobipocket-ebook mobi prc
application/x-ms-application application application/x-ms-application application
application/x-ms-wmd wmd application/x-ms-wmd wmd
application/x-ms-wmz wmz application/x-ms-wmz wmz
@ -1371,11 +1370,11 @@ application/x-sony-bbeb lrf lrx
application/adobe-page-template+xml xpgt application/adobe-page-template+xml xpgt
application/x-font-opentype otf application/x-font-opentype otf
application/x-font-truetype ttf application/x-font-truetype ttf
application/x-mobipocket-ebook mobi prc azw application/x-mobipocket-ebook mobi prc
application/vnd.amazon.ebook azw3 azw azw2 azw4
application/x-cbz cbz application/x-cbz cbz
application/x-cbr cbr application/x-cbr cbr
application/x-cb7 cb7 application/x-cb7 cb7
application/x-koboreader-ebook kobo application/x-koboreader-ebook kobo
image/wmf wmf image/wmf wmf
application/ereader pdb application/ereader pdb

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,286 @@
/*************************************************************
*
* MathJax/extensions/FontWarnings.js
*
* Implements a font warning message window that appears when
* the image fonts, no fonts, or web fonts are used, informing
* the user where to download the fonts, or to update to a more
* modern browser. The window will fade out automatically after
* a time, and the user can dismiss it by a close box.
*
* To include font warning messages, add "FontWarnings.js" to the
* extensions array in your MathJax configuration.
*
* You can customize the warning messages in a number of ways. Use the
* FontWarnings section of the configuration to specify any of the items
* shown in the CONFIG variable below. These include
*
* messageStyle the style to apply to the warning box that is
* displayed when MathJax uses one of its fallback
* methods.
*
* removeAfter the amount of time to show the warning message (in ms)
* fadeoutTime how long the message should take to fade out
* fadeoutSteps how many separate steps to use during the fade out
* (set to 0 to use no fadeout and simply remove the window)
*
* Messages stores the descriptions of the messages to use for the
* various warnings (webFonts, imageFonts, and noFonts).
* These are arrays of strings to be inserted into the window,
* or identifiers within brackets, which refer to the HTML
* snippets in the HTML section described below. To disable a
* specific message, set its value to null (see example below).
*
* HTML stores snippets of HTML descriptions for various
* common parts of the error messages. These include
* the closeBox, the message about web fonts being available
* in modern browser, and messages about downloadable fonts.
* The STIX and TeX font messages are used when only one
* of these is in the availableFonts list. The data for these
* are arrays of either strings to include or a description of
* an HTML item enclosed in square brackets. That description
* has (up to) three parts: the name of the tag to be included,
* a list (enclosed in braces) of attributes and their values
* to be set on the tag (optional), and an array of the contents
* of the tag (optional). See the definitions below for examples.
*
* For example,
*
* MathJax.Hub.Config({
* ...
* extensions: ["FontWarnings.js"],
* FontWarnings: {
* removeAfter: 20*1000, // 20 seconds
* messageStyle: {
* border: "2px solid black",
* padding: "2em"
* },
* Message: {
* webFont: null // no webfont messages (only image and no fonts)
* }
* }
* });
*
* would extend the time the message is displayed from 12 seconds to 20,
* and changes the border to a solid black one, with 2em of padding
* rather than the default of 1em.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2010-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (HUB,HTML) {
var VERSION = "2.0";
var CONFIG = HUB.CombineConfig("FontWarnings",{
//
// The CSS for the message window
//
messageStyle: {
position:"fixed", bottom:"4em", left:"3em", width:"40em",
border: "3px solid #880000", "background-color": "#E0E0E0", color: "black",
padding: "1em", "font-size":"small", "white-space":"normal",
"border-radius": ".75em", // Opera 10.5 and IE9
"-webkit-border-radius": ".75em", // Safari and Chrome
"-moz-border-radius": ".75em", // Firefox
"-khtml-border-radius": ".75em", // Konqueror
"box-shadow": "4px 4px 10px #AAAAAA", // Opera 10.5 and IE9
"-webkit-box-shadow": "4px 4px 10px #AAAAAA", // Safari 3 and Chrome
"-moz-box-shadow": "4px 4px 10px #AAAAAA", // Forefox 3.5
"-khtml-box-shadow": "4px 4px 10px #AAAAAA", // Konqueror
filter: "progid:DXImageTransform.Microsoft.dropshadow(OffX=3, OffY=3, Color='gray', Positive='true')" // IE
},
//
// The messages for the various situations
//
Message: {
webFont: [
["closeBox"],
"MathJax is using web-based fonts to display the mathematics ",
"on this page. These take time to download, so the page would ",
"render faster if you installed math fonts directly in your ",
"system's font folder.",
["fonts"]
],
imageFonts: [
["closeBox"],
"MathJax is using its image fonts rather than local or web-based fonts. ",
"This will render slower than usual, and the mathematics may not print ",
"at the full resolution of your printer.",
["fonts"],
["webfonts"]
],
noFonts: [
["closeBox"],
"MathJax is unable to locate a font to use to display ",
"its mathematics, and image fonts are not available, so it ",
"is falling back on generic unicode characters in hopes that ",
"your browser will be able to display them. Some characters ",
"may not show up properly, or possibly not at all.",
["fonts"],
["webfonts"]
]
},
//
// HTML objects that can be referred to in the message definitions
//
HTML: {
//
// The definition of the close box
//
closeBox: [[
"div",{
style: {
position:"absolute", overflow:"hidden", top:".1em", right:".1em",
border: "1px outset", width:"1em", height:"1em",
"text-align": "center", cursor: "pointer",
"background-color": "#EEEEEE", color:"#606060",
"border-radius": ".5em", // Opera 10.5
"-webkit-border-radius": ".5em", // Safari and Chrome
"-moz-border-radius": ".5em", // Firefox
"-khtml-border-radius": ".5em" // Konqueror
},
onclick: function () {
if (DATA.div && DATA.fade === 0)
{if (DATA.timer) {clearTimeout(DATA.timer)}; DATA.div.style.display = "none"}
}
},
[["span",{style:{position:"relative", bottom:".2em"}},["x"]]]
]],
webfonts: [
["p"],
"Most modern browsers allow for fonts to be downloaded over the web. ",
"Updating to a more recent version of your browser (or changing browsers) ",
"could improve the quality of the mathematics on this page."
],
fonts: [
["p"],
"MathJax can use either the ",
["a",{href:"http://www.stixfonts.org/",target:"_blank"},"STIX fonts"],
" or the ",
["a",{href:"http://www.mathjax.org/help-v2/fonts/",target:"_blank"},["MathJax TeX fonts"]],
". Download and install either one to improve your MathJax experience."
],
STIXfonts: [
["p"],
"This page is designed to use the ",
["a",{href:"http://www.stixfonts.org/",target:"_blank"},"STIX fonts"],
". Download and install those fonts to improve your MathJax experience."
],
TeXfonts: [
["p"],
"This page is designed to use the ",
["a",{href:"http://www.mathjax.org/help-v2/fonts/",target:"_blank"},["MathJax TeX fonts"]],
". Download and install those fonts to improve your MathJax experience."
]
},
removeAfter: 12*1000, // time to show message (in ms)
fadeoutSteps: 10, // fade-out steps
fadeoutTime: 1.5*1000 // fadeout over this amount of time (in ms)
});
if (MathJax.Hub.Browser.isIE9 && document.documentMode >= 9)
{delete CONFIG.messageStyle.filter}
//
// Data for the window
//
var DATA = {
div: null, // the message window, when displayed
fade: 0 // number of fade-out steps so far
};
//
// Create the message window and start the fade-out timer
//
var CREATEMESSAGE = function (data) {
if (DATA.div) return;
var HTMLCSS = MathJax.OutputJax["HTML-CSS"], frame = document.body;
if (HUB.Browser.isMSIE) {
if (CONFIG.messageStyle.position === "fixed") {
MathJax.Message.Init(); // make sure MathJax_MSIE_frame exists
frame = document.getElementById("MathJax_MSIE_Frame");
CONFIG.messageStyle.position = "absolute";
}
} else {delete CONFIG.messageStyle.filter}
CONFIG.messageStyle.maxWidth = (document.body.clientWidth-75) + "px";
var i = 0; while (i < data.length) {
if (data[i] instanceof Array && CONFIG.HTML[data[i][0]])
{data.splice.apply(data,[i,1].concat(CONFIG.HTML[data[i][0]]))} else {i++}
}
DATA.div = HTMLCSS.addElement(frame,"div",{id:"MathJax_FontWarning",style:CONFIG.messageStyle},data);
if (CONFIG.removeAfter) {
HUB.Register.StartupHook("End",function ()
{DATA.timer = setTimeout(FADEOUT,CONFIG.removeAfter)});
}
HTML.Cookie.Set("fontWarn",{warned:true});
};
//
// Set the opacity based on the number of steps taken so far
// and remove the window when it gets to 0
//
var FADEOUT = function () {
DATA.fade++; if (DATA.timer) {delete DATA.timer}
if (DATA.fade < CONFIG.fadeoutSteps) {
var opacity = 1 - DATA.fade/CONFIG.fadeoutSteps;
DATA.div.style.opacity = opacity;
DATA.div.style.filter = "alpha(opacity="+Math.floor(100*opacity)+")";
setTimeout(FADEOUT,CONFIG.fadeoutTime/CONFIG.fadeoutSteps);
} else {
DATA.div.style.display = "none";
}
};
//
// Check that we haven't already issued a warning
//
if (!HTML.Cookie.Get("fontWarn").warned) {
//
// Hook into the Startup signal and look for font warning messages.
// When one comes, issue the correct message.
//
HUB.Startup.signal.Interest(function (message) {
if (message.match(/HTML-CSS Jax - /) && !DATA.div) {
var HTMLCSS = MathJax.OutputJax["HTML-CSS"], FONTS = HTMLCSS.config.availableFonts, MSG;
var localFonts = (FONTS && FONTS.length);
if (!localFonts) {CONFIG.HTML.fonts = [""]}
else if (FONTS.length === 1) {CONFIG.HTML.fonts = CONFIG.HTML[FONTS[0]+"fonts"]}
if (HTMLCSS.allowWebFonts) {CONFIG.HTML.webfonts = [""]}
if (message.match(/- Web-Font/)) {if (localFonts) {MSG = "webFont"}}
else if (message.match(/- using image fonts/)) {MSG = "imageFonts"}
else if (message.match(/- no valid font/)) {MSG = "noFonts"}
if (MSG && CONFIG.Message[MSG]) {CREATEMESSAGE(CONFIG.Message[MSG])}
}
});
}
})(MathJax.Hub,MathJax.HTML);
MathJax.Ajax.loadComplete("[MathJax]/extensions/FontWarnings.js");

View File

@ -0,0 +1,531 @@
/*************************************************************
*
* MathJax/extensions/MathEvents.js
*
* Implements the event handlers needed by the output jax to perform
* menu, hover, and other events.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2011-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (HUB,HTML,AJAX,CALLBACK,OUTPUT,INPUT) {
var VERSION = "2.0";
var EXTENSION = MathJax.Extension;
var ME = EXTENSION.MathEvents = {version: VERSION};
var SETTINGS = HUB.config.menuSettings;
var CONFIG = {
hover: 500, // time required to be considered a hover
frame: {
x: 3.5, y: 5, // frame padding and
bwidth: 1, // frame border width (in pixels)
bcolor: "#A6D", // frame border color
hwidth: "15px", // haze width
hcolor: "#83A" // haze color
},
button: {
x: -4, y: -3, // menu button offsets
wx: -2, // button offset for full-width equations
src: AJAX.fileURL(OUTPUT.imageDir+"/MenuArrow-15.png") // button image
},
fadeinInc: .2, // increment for fade-in
fadeoutInc: .05, // increment for fade-out
fadeDelay: 50, // delay between fade-in or fade-out steps
fadeoutStart: 400, // delay before fade-out after mouseout
fadeoutDelay: 15*1000, // delay before automatic fade-out
styles: {
".MathJax_Hover_Frame": {
"border-radius": ".25em", // Opera 10.5 and IE9
"-webkit-border-radius": ".25em", // Safari and Chrome
"-moz-border-radius": ".25em", // Firefox
"-khtml-border-radius": ".25em", // Konqueror
"box-shadow": "0px 0px 15px #83A", // Opera 10.5 and IE9
"-webkit-box-shadow": "0px 0px 15px #83A", // Safari and Chrome
"-moz-box-shadow": "0px 0px 15px #83A", // Forefox
"-khtml-box-shadow": "0px 0px 15px #83A", // Konqueror
border: "1px solid #A6D ! important",
display: "inline-block", position:"absolute"
},
".MathJax_Hover_Arrow": {
position:"absolute",
width:"15px", height:"11px",
cursor:"pointer"
}
}
};
//
// Common event-handling code
//
var EVENT = ME.Event = {
LEFTBUTTON: 0, // the event.button value for left button
RIGHTBUTTON: 2, // the event.button value for right button
MENUKEY: "altKey", // the event value for alternate context menu
Mousedown: function (event) {return EVENT.Handler(event,"Mousedown",this)},
Mouseup: function (event) {return EVENT.Handler(event,"Mouseup",this)},
Mousemove: function (event) {return EVENT.Handler(event,"Mousemove",this)},
Mouseover: function (event) {return EVENT.Handler(event,"Mouseover",this)},
Mouseout: function (event) {return EVENT.Handler(event,"Mouseout",this)},
Click: function (event) {return EVENT.Handler(event,"Click",this)},
DblClick: function (event) {return EVENT.Handler(event,"DblClick",this)},
Menu: function (event) {return EVENT.Handler(event,"ContextMenu",this)},
//
// Call the output jax's event handler or the zoom handler
//
Handler: function (event,type,math) {
if (AJAX.loadingMathMenu) {return EVENT.False(event)}
var jax = OUTPUT[math.jaxID];
if (!event) {event = window.event}
event.isContextMenu = (type === "ContextMenu");
if (jax[type]) {return jax[type](event,math)}
if (EXTENSION.MathZoom) {return EXTENSION.MathZoom.HandleEvent(event,type,math)}
},
//
// Try to cancel the event in every way we can
//
False: function (event) {
if (!event) {event = window.event}
if (event) {
if (event.preventDefault) {event.preventDefault()}
if (event.stopPropagation) {event.stopPropagation()}
event.cancelBubble = true;
event.returnValue = false;
}
return false;
},
//
// Load the contextual menu code, if needed, and post the menu
//
ContextMenu: function (event,math,force) {
//
// Check if we are showing menus
//
var JAX = OUTPUT[math.jaxID], jax = JAX.getJaxFromMath(math);
var show = (JAX.config.showMathMenu != null ? JAX : HUB).config.showMathMenu;
if (!show || (SETTINGS.context !== "MathJax" && !force)) return;
//
// Remove selections, remove hover fades
//
if (ME.msieEventBug) {event = window.event || event}
EVENT.ClearSelection(); HOVER.ClearHoverTimer();
if (jax.hover) {
if (jax.hover.remove) {clearTimeout(jax.hover.remove); delete jax.hover.remove}
jax.hover.nofade = true;
}
//
// If the menu code is loaded, post the menu
// Otherwse lad the menu code and try again
//
var MENU = MathJax.Menu;
if (MENU) {
MENU.jax = jax;
var source = MENU.menu.Find("Show Math As").menu;
source.items[1].name = (INPUT[jax.inputJax].sourceMenuTitle||"Original Form");
source.items[0].hidden = (jax.inputJax === "Error"); // hide MathML choice for error messages
var MathPlayer = MENU.menu.Find("Math Settings","MathPlayer");
MathPlayer.hidden = !(jax.outputJax === "NativeMML" && HUB.Browser.hasMathPlayer);
return MENU.menu.Post(event);
} else {
if (!AJAX.loadingMathMenu) {
AJAX.loadingMathMenu = true;
var ev = {
pageX:event.pageX, pageY:event.pageY,
clientX:event.clientX, clientY:event.clientY
};
CALLBACK.Queue(
AJAX.Require("[MathJax]/extensions/MathMenu.js"),
function () {delete AJAX.loadingMathMenu; if (!MathJax.Menu) {MathJax.Menu = {}}},
["ContextMenu",this,ev,math,force] // call this function again
);
}
return EVENT.False(event);
}
},
//
// Mousedown handler for alternate means of accessing menu
//
AltContextMenu: function (event,math) {
var JAX = OUTPUT[math.jaxID];
var show = (JAX.config.showMathMenu != null ? JAX : HUB).config.showMathMenu;
if (show) {
show = (JAX.config.showMathMenuMSIE != null ? JAX : HUB).config.showMathMenuMSIE;
if (SETTINGS.context === "MathJax" && !SETTINGS.mpContext && show) {
if (!ME.noContextMenuBug || event.button !== EVENT.RIGHTBUTTON) return;
} else {
if (!event[EVENT.MENUKEY] || event.button !== EVENT.LEFTBUTTON) return;
}
return JAX.ContextMenu(event,math,true);
}
},
ClearSelection: function () {
if (ME.safariContextMenuBug) {setTimeout("window.getSelection().empty()",0)}
if (document.selection) {setTimeout("document.selection.empty()",0)}
},
getBBox: function (span) {
span.appendChild(ME.topImg);
var h = ME.topImg.offsetTop, d = span.offsetHeight-h, w = span.offsetWidth;
span.removeChild(ME.topImg);
return {w:w, h:h, d:d};
}
};
//
// Handle hover "discoverability"
//
var HOVER = ME.Hover = {
//
// Check if we are moving from a non-MathJax element to a MathJax one
// and either start fading in again (if it is fading out) or start the
// timer for the hover
//
Mouseover: function (event,math) {
if (SETTINGS.discoverable || SETTINGS.zoom === "Hover") {
var from = event.fromElement || event.relatedTarget,
to = event.toElement || event.target;
if (from && to && (from.isMathJax != to.isMathJax ||
HUB.getJaxFor(from) !== HUB.getJaxFor(to))) {
var jax = this.getJaxFromMath(math);
if (jax.hover) {HOVER.ReHover(jax)} else {HOVER.HoverTimer(jax,math)}
return EVENT.False(event);
}
}
},
//
// Check if we are moving from a MathJax element to a non-MathJax one
// and either start fading out, or clear the timer if we haven't
// hovered yet
//
Mouseout: function (event,math) {
if (SETTINGS.discoverable || SETTINGS.zoom === "Hover") {
var from = event.fromElement || event.relatedTarget,
to = event.toElement || event.target;
if (from && to && (from.isMathJax != to.isMathJax ||
HUB.getJaxFor(from) !== HUB.getJaxFor(to))) {
var jax = this.getJaxFromMath(math);
if (jax.hover) {HOVER.UnHover(jax)} else {HOVER.ClearHoverTimer()}
return EVENT.False(event);
}
}
},
//
// Restart hover timer if the mouse moves
//
Mousemove: function (event,math) {
if (SETTINGS.discoverable || SETTINGS.zoom === "Hover") {
var jax = this.getJaxFromMath(math); if (jax.hover) return;
if (HOVER.lastX == event.clientX && HOVER.lastY == event.clientY) return;
HOVER.lastX = event.clientX; HOVER.lastY = event.clientY;
HOVER.HoverTimer(jax,math);
return EVENT.False(event);
}
},
//
// Clear the old timer and start a new one
//
HoverTimer: function (jax,math) {
this.ClearHoverTimer();
this.hoverTimer = setTimeout(CALLBACK(["Hover",this,jax,math]),CONFIG.hover);
},
ClearHoverTimer: function () {
if (this.hoverTimer) {clearTimeout(this.hoverTimer); delete this.hoverTimer}
},
//
// Handle putting up the hover frame
//
Hover: function (jax,math) {
//
// Check if Zoom handles the hover event
//
if (EXTENSION.MathZoom && EXTENSION.MathZoom.Hover({},math)) return;
//
// Get the hover data
//
var JAX = OUTPUT[jax.outputJax],
span = JAX.getHoverSpan(jax,math),
bbox = JAX.getHoverBBox(jax,span,math),
show = (JAX.config.showMathMenu != null ? JAX : HUB).config.showMathMenu;
var dx = CONFIG.frame.x, dy = CONFIG.frame.y, dd = CONFIG.frame.bwidth; // frame size
if (ME.msieBorderWidthBug) {dd = 0}
jax.hover = {opacity:0, id:jax.inputID+"-Hover"};
//
// The frame and menu button
//
var frame = HTML.Element("span",{
id:jax.hover.id, isMathJax: true,
style:{display:"inline-block", width:0, height:0, position:"relative"}
},[["span",{
className:"MathJax_Hover_Frame", isMathJax: true,
style:{
display:"inline-block", position:"absolute",
top:this.Px(-bbox.h-dy-dd-(bbox.y||0)), left:this.Px(-dx-dd+(bbox.x||0)),
width:this.Px(bbox.w+2*dx), height:this.Px(bbox.h+bbox.d+2*dy),
opacity:0, filter:"alpha(opacity=0)"
}}
]]
);
var button = HTML.Element("span",{
isMathJax: true, id:jax.hover.id+"Menu",
style:{display:"inline-block", "z-index": 1, width:0, height:0, position:"relative"}
},[["img",{
className: "MathJax_Hover_Arrow", isMathJax: true, math: math,
src: CONFIG.button.src, onclick: this.HoverMenu, jax:JAX.id,
style: {
left:this.Px(bbox.w+dx+dd+(bbox.x||0)+CONFIG.button.x),
top:this.Px(-bbox.h-dy-dd-(bbox.y||0)-CONFIG.button.y),
opacity:0, filter:"alpha(opacity=0)"
}
}]]
);
if (bbox.width) {
frame.style.width = button.style.width = bbox.width;
frame.style.marginRight = button.style.marginRight = "-"+bbox.width;
frame.firstChild.style.width = bbox.width;
button.firstChild.style.left = "";
button.firstChild.style.right = this.Px(CONFIG.button.wx);
}
//
// Add the frame and button
//
span.parentNode.insertBefore(frame,span);
if (show) {span.parentNode.insertBefore(button,span)}
if (span.style) {span.style.position = "relative"} // so math is on top of hover frame
//
// Start the hover fade-in
//
this.ReHover(jax);
},
//
// Restart the hover fade in and fade-out timers
//
ReHover: function (jax) {
if (jax.hover.remove) {clearTimeout(jax.hover.remove)}
jax.hover.remove = setTimeout(CALLBACK(["UnHover",this,jax]),CONFIG.fadeoutDelay);
this.HoverFadeTimer(jax,CONFIG.fadeinInc);
},
//
// Start the fade-out
//
UnHover: function (jax) {
if (!jax.hover.nofade) {this.HoverFadeTimer(jax,-CONFIG.fadeoutInc,CONFIG.fadeoutStart)}
},
//
// Handle the fade-in and fade-out
//
HoverFade: function (jax) {
delete jax.hover.timer;
jax.hover.opacity = Math.max(0,Math.min(1,jax.hover.opacity + jax.hover.inc));
jax.hover.opacity = Math.floor(1000*jax.hover.opacity)/1000;
var frame = document.getElementById(jax.hover.id),
button = document.getElementById(jax.hover.id+"Menu");
frame.firstChild.style.opacity = jax.hover.opacity;
frame.firstChild.style.filter = "alpha(opacity="+Math.floor(100*jax.hover.opacity)+")";
if (button) {
button.firstChild.style.opacity = jax.hover.opacity;
button.firstChild.style.filter = frame.style.filter;
}
if (jax.hover.opacity === 1) {return}
if (jax.hover.opacity > 0) {this.HoverFadeTimer(jax,jax.hover.inc); return}
frame.parentNode.removeChild(frame);
if (button) {button.parentNode.removeChild(button)}
if (jax.hover.remove) {clearTimeout(jax.hover.remove)}
delete jax.hover;
},
//
// Set the fade to in or out (via inc) and start the timer, if needed
//
HoverFadeTimer: function (jax,inc,delay) {
jax.hover.inc = inc;
if (!jax.hover.timer) {
jax.hover.timer = setTimeout(CALLBACK(["HoverFade",this,jax]),(delay||CONFIG.fadeDelay));
}
},
//
// Handle a click on the menu button
//
HoverMenu: function (event) {
if (!event) {event = window.event}
return OUTPUT[this.jax].ContextMenu(event,this.math,true);
},
//
// Clear all hover timers
//
ClearHover: function (jax) {
if (jax.hover.remove) {clearTimeout(jax.hover.remove)}
if (jax.hover.timer) {clearTimeout(jax.hover.timer)}
HOVER.ClearHoverTimer();
delete jax.hover;
},
//
// Make a measurement in pixels
//
Px: function (m) {
if (Math.abs(m) < .006) {return "0px"}
return m.toFixed(2).replace(/\.?0+$/,"") + "px";
},
//
// Preload images so they show up with the menu
//
getImages: function () {
var menu = new Image();
menu.src = CONFIG.button.src;
}
};
//
// Handle touch events.
//
// Use double-tap-and-hold as a replacement for context menu event.
// Use double-tap as a replacement for double click.
//
var TOUCH = ME.Touch = {
last: 0, // time of last tap event
delay: 500, // delay time for double-click
//
// Check if this is a double-tap, and if so, start the timer
// for the double-tap and hold (to trigger the contextual menu)
//
start: function (event) {
var now = new Date().getTime();
var dblTap = (now - TOUCH.last < TOUCH.delay);
TOUCH.last = now;
if (dblTap) {
TOUCH.timeout = setTimeout(TOUCH.menu,TOUCH.delay,event,this);
event.preventDefault();
}
},
//
// Check if there is a timeout pending, i.e., we have a
// double-tap and were waiting to see if it is held long
// enough for the menu. Since we got the end before the
// timeout, it is a double-click, not a double-tap-and-hold.
// Prevent the default action and issue a double click.
//
end: function (event) {
if (TOUCH.timeout) {
clearTimeout(TOUCH.timeout);
delete TOUCH.timeout; TOUCH.last = 0;
event.preventDefault();
return EVENT.Handler((event.touches[0]||event.touch),"DblClick",this);
}
},
//
// If the timeout passes without an end event, we issue
// the contextual menu event.
//
menu: function (event,math) {
delete TOUCH.timeout; TOUCH.last = 0;
return EVENT.Handler((event.touches[0]||event.touch),"ContextMenu",math);
}
};
//
// Mobile screens are small, so use larger version of arrow
//
if (HUB.Browser.isMobile) {
var arrow = CONFIG.styles[".MathJax_Hover_Arrow"];
arrow.width = "25px"; arrow.height = "18px";
CONFIG.button.x = -6;
}
//
// Set up browser-specific values
//
HUB.Browser.Select({
MSIE: function (browser) {
var mode = (document.documentMode || 0);
var isIE8 = browser.versionAtLeast("8.0");
ME.msieBorderWidthBug = (document.compatMode === "BackCompat"); // borders are inside offsetWidth/Height
ME.msieEventBug = browser.isIE9; // must get event from window even though event is passed
ME.msieAlignBug = (!isIE8 || mode < 8); // inline-block spans don't rest on baseline
if (mode < 9) {EVENT.LEFTBUTTON = 1} // IE < 9 has wrong event.button values
},
Safari: function (browser) {
ME.safariContextMenuBug = true; // selection can be started by contextmenu event
},
Opera: function (browser) {
ME.operaPositionBug = true; // position is wrong unless border is used
},
Konqueror: function (browser) {
ME.noContextMenuBug = true; // doesn't produce contextmenu event
}
});
//
// Used in measuring zoom and hover positions
//
ME.topImg = (ME.msieAlignBug ?
HTML.Element("img",{style:{width:0,height:0,position:"relative"},src:"about:blank"}) :
HTML.Element("span",{style:{width:0,height:0,display:"inline-block"}})
);
if (ME.operaPositionBug) {ME.topImg.style.border="1px solid"}
//
// Get configuration from user
//
ME.config = CONFIG = HUB.CombineConfig("MathEvents",CONFIG);
var SETFRAME = function () {
var haze = CONFIG.styles[".MathJax_Hover_Frame"];
haze.border = CONFIG.frame.bwidth+"px solid "+CONFIG.frame.bcolor+" ! important";
haze["box-shadow"] = haze["-webkit-box-shadow"] =
haze["-moz-box-shadow"] = haze["-khtml-box-shadow"] =
"0px 0px "+CONFIG.frame.hwidth+" "+CONFIG.frame.hcolor;
};
//
// Queue the events needed for startup
//
CALLBACK.Queue(
HUB.Register.StartupHook("End Config",{}), // wait until config is complete
[SETFRAME],
["getImages",HOVER],
["Styles",AJAX,CONFIG.styles],
["Post",HUB.Startup.signal,"MathEvents Ready"],
["loadComplete",AJAX,"[MathJax]/extensions/MathEvents.js"]
);
})(MathJax.Hub,MathJax.HTML,MathJax.Ajax,MathJax.Callback,MathJax.OutputJax,MathJax.InputJax);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,316 @@
/*************************************************************
*
* MathJax/extensions/MathZoom.js
*
* Implements the zoom feature for enlarging math expressions. It is
* loaded automatically when the Zoom menu selection changes from "None".
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2010-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (HUB,HTML,AJAX,HTMLCSS,nMML) {
var VERSION = "2.0";
var CONFIG = HUB.CombineConfig("MathZoom",{
styles: {
//
// The styles for the MathZoom display box
//
"#MathJax_Zoom": {
position:"absolute", "background-color":"#F0F0F0", overflow:"auto",
display:"block", "z-index":301, padding:".5em", border:"1px solid black", margin:0,
"font-weight":"normal", "font-style":"normal",
"text-align":"left", "text-indent":0, "text-transform":"none",
"line-height":"normal", "letter-spacing":"normal", "word-spacing":"normal",
"word-wrap":"normal", "white-space":"nowrap", "float":"none",
"box-shadow":"5px 5px 15px #AAAAAA", // Opera 10.5 and IE9
"-webkit-box-shadow":"5px 5px 15px #AAAAAA", // Safari 3 and Chrome
"-moz-box-shadow":"5px 5px 15px #AAAAAA", // Forefox 3.5
"-khtml-box-shadow":"5px 5px 15px #AAAAAA", // Konqueror
filter: "progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color='gray', Positive='true')" // IE
},
//
// The styles for the hidden overlay (should not need to be adjusted by the page author)
//
"#MathJax_ZoomOverlay": {
position:"absolute", left:0, top:0, "z-index":300, display:"inline-block",
width:"100%", height:"100%", border:0, padding:0, margin:0,
"background-color":"white", opacity:0, filter:"alpha(opacity=0)"
},
"#MathJax_ZoomEventTrap": {
position:"absolute", left:0, top:0, "z-index":302,
display:"inline-block", border:0, padding:0, margin:0,
"background-color":"white", opacity:0, filter:"alpha(opacity=0)"
}
}
});
var FALSE, HOVER, EVENT;
MathJax.Hub.Register.StartupHook("MathEvents Ready",function () {
EVENT = MathJax.Extension.MathEvents.Event;
FALSE = MathJax.Extension.MathEvents.Event.False;
HOVER = MathJax.Extension.MathEvents.Hover;
});
/*************************************************************/
var ZOOM = MathJax.Extension.MathZoom = {
version: VERSION,
settings: HUB.config.menuSettings,
scrollSize: 18, // width of scrool bars
//
// Process events passed from output jax
//
HandleEvent: function (event,type,math) {
if (ZOOM.settings.CTRL && !event.ctrlKey) return true;
if (ZOOM.settings.ALT && !event.altKey) return true;
if (ZOOM.settings.CMD && !event.metaKey) return true;
if (ZOOM.settings.Shift && !event.shiftKey) return true;
if (!ZOOM[type]) return true;
return ZOOM[type](event,math);
},
//
// Zoom on click
//
Click: function (event,math) {
if (this.settings.zoom === "Click") {return this.Zoom(event,math)}
},
//
// Zoom on double click
//
DblClick: function (event,math) {
if (this.settings.zoom === "Double-Click") {return this.Zoom(event,math)}
},
//
// Zoom on hover (called by MathEvents.Hover)
//
Hover: function (event,math) {
if (this.settings.zoom === "Hover") {this.Zoom(event,math); return true}
return false;
},
//
// Handle the actual zooming
//
Zoom: function (event,math) {
//
// Remove any other zoom and clear timers
//
this.Remove(); HOVER.ClearHoverTimer(); EVENT.ClearSelection();
//
// Find the jax
//
var JAX = MathJax.OutputJax[math.jaxID];
var jax = JAX.getJaxFromMath(math);
if (jax.hover) {HOVER.UnHover(jax)}
//
// Create the DOM elements for the zoom box
//
var Mw = Math.floor(.85*document.body.clientWidth),
Mh = Math.floor(.85*Math.max(document.body.clientHeight,document.documentElement.clientHeight));
var div = HTML.Element(
"span",{
style: {position:"relative", display:"inline-block", height:0, width:0},
id:"MathJax_ZoomFrame"
},[
["span",{id:"MathJax_ZoomOverlay", onmousedown:this.Remove}],
["span",{
id:"MathJax_Zoom", onclick:this.Remove,
style:{
visibility:"hidden", fontSize:this.settings.zscale,
"max-width":Mw+"px", "max-height":Mh+"px"
}
},[["span",{style:{display:"inline-block", "white-space":"nowrap"}}]]
]]
);
var zoom = div.lastChild, span = zoom.firstChild, overlay = div.firstChild;
math.parentNode.insertBefore(div,math);
if (span.addEventListener) {span.addEventListener("mousedown",this.Remove,true)}
if (this.msieTrapEventBug) {
var trap = HTML.Element("span",{id:"MathJax_ZoomEventTrap", onmousedown:this.Remove});
div.insertBefore(trap,zoom);
}
//
// Display the zoomed math
//
if (this.msieZIndexBug) {
// MSIE doesn't do z-index properly, so move the div to the document.body,
// and use an image as a tracker for the usual position
var tracker = HTML.addElement(document.body,"img",{
src:"about:blank", id:"MathJax_ZoomTracker", width:0, height:0,
style:{width:0, height:0, position:"relative"}
});
div.style.position = "relative";
div.style.zIndex = CONFIG.styles["#MathJax_ZoomOverlay"]["z-index"];
div = tracker;
}
var bbox = JAX.Zoom(jax,span,math,Mw,Mh);
//
// Fix up size and position for browsers with bugs (IE)
//
if (this.msiePositionBug) {
if (this.msieSizeBug)
{zoom.style.height = bbox.zH+"px"; zoom.style.width = bbox.zW+"px"} // IE8 gets the dimensions completely wrong
if (zoom.offsetHeight > Mh) {zoom.style.height = Mh+"px"; zoom.style.width = (bbox.zW+this.scrollSize)+"px"} // IE doesn't do max-height?
if (zoom.offsetWidth > Mw) {zoom.style.width = Mw+"px"; zoom.style.height = (bbox.zH+this.scrollSize)+"px"}
}
if (this.operaPositionBug) {zoom.style.width = Math.min(Mw,bbox.zW)+"px"} // Opera gets width as 0?
if (zoom.offsetWidth < Mw && zoom.offsetHeight < Mh) {zoom.style.overflow = "visible"}
this.Position(zoom,bbox);
if (this.msieTrapEventBug) {
trap.style.height = zoom.clientHeight+"px"; trap.style.width = zoom.clientWidth+"px";
trap.style.left = (parseFloat(zoom.style.left)+zoom.clientLeft)+"px";
trap.style.top = (parseFloat(zoom.style.top)+zoom.clientTop)+"px";
}
zoom.style.visibility = "";
//
// Add event handlers
//
if (this.settings.zoom === "Hover") {overlay.onmouseover = this.Remove}
if (window.addEventListener) {addEventListener("resize",this.Resize,false)}
else if (window.attachEvent) {attachEvent("onresize",this.Resize)}
else {this.onresize = window.onresize; window.onresize = this.Resize}
//
// Let others know about the zoomed math
//
HUB.signal.Post(["math zoomed",jax]);
//
// Canel further actions
//
return FALSE(event);
},
//
// Set the position of the zoom box and overlay
//
Position: function (zoom,bbox) {
var XY = this.Resize(), x = XY.x, y = XY.y, W = bbox.mW;
var dx = -Math.floor((zoom.offsetWidth-W)/2), dy = bbox.Y;
zoom.style.left = Math.max(dx,10-x)+"px"; zoom.style.top = Math.max(dy,10-y)+"px";
if (!ZOOM.msiePositionBug) {ZOOM.SetWH()} // refigure overlay width/height
},
//
// Handle resizing of overlay while zoom is displayed
//
Resize: function (event) {
if (ZOOM.onresize) {ZOOM.onresize(event)}
var x = 0, y = 0, obj,
div = document.getElementById("MathJax_ZoomFrame"),
overlay = document.getElementById("MathJax_ZoomOverlay");
obj = div; while (obj.offsetParent) {x += obj.offsetLeft; obj = obj.offsetParent}
if (ZOOM.operaPositionBug) {div.style.border = "1px solid"} // to get vertical position right
obj = div; while (obj.offsetParent) {y += obj.offsetTop; obj = obj.offsetParent}
if (ZOOM.operaPositionBug) {div.style.border = ""}
overlay.style.left = (-x)+"px"; overlay.style.top = (-y)+"px";
if (ZOOM.msiePositionBug) {setTimeout(ZOOM.SetWH,0)} else {ZOOM.SetWH()}
return {x:x, y:y};
},
SetWH: function () {
var overlay = document.getElementById("MathJax_ZoomOverlay");
overlay.style.width = overlay.style.height = "1px"; // so scrollWidth/Height will be right below
var doc = document.documentElement || document.body;
overlay.style.width = doc.scrollWidth + "px";
overlay.style.height = Math.max(doc.clientHeight,doc.scrollHeight) + "px";
},
//
// Remove zoom display and event handlers
//
Remove: function (event) {
var div = document.getElementById("MathJax_ZoomFrame");
if (div) {
var JAX = MathJax.OutputJax[div.nextSibling.jaxID];
var jax = JAX.getJaxFromMath(div.nextSibling);
HUB.signal.Post(["math unzoomed",jax]);
div.parentNode.removeChild(div);
div = document.getElementById("MathJax_ZoomTracker");
if (div) {div.parentNode.removeChild(div)}
if (ZOOM.operaRefreshBug) {
// force a redisplay of the page
// (Opera doesn't refresh properly after the zoom is removed)
var overlay = HTML.addElement(document.body,"div",{
style:{position:"fixed", left:0, top:0, width:"100%", height:"100%",
backgroundColor:"white", opacity:0},
id: "MathJax_OperaDiv"
});
document.body.removeChild(overlay);
}
if (window.removeEventListener) {removeEventListener("resize",ZOOM.Resize,false)}
else if (window.detachEvent) {detachEvent("onresize",ZOOM.Resize)}
else {window.onresize = ZOOM.onresize; delete ZOOM.onresize}
}
return FALSE(event);
}
};
/*************************************************************/
HUB.Browser.Select({
MSIE: function (browser) {
var mode = (document.documentMode || 0);
var isIE9 = (mode >= 9);
ZOOM.msiePositionBug = !isIE9;
ZOOM.msieSizeBug = browser.versionAtLeast("7.0") &&
(!document.documentMode || mode === 7 || mode === 8);
ZOOM.msieZIndexBug = (mode <= 7);
ZOOM.msieInlineBlockAlignBug = (mode <= 7);
ZOOM.msieTrapEventBug = !window.addEventListener;
if (document.compatMode === "BackCompat") {ZOOM.scrollSize = 52} // don't know why this is so far off
if (isIE9) {delete CONFIG.styles["#MathJax_Zoom"].filter}
},
Opera: function (browser) {
ZOOM.operaPositionBug = true;
ZOOM.operaRefreshBug = true;
}
});
ZOOM.topImg = (ZOOM.msieInlineBlockAlignBug ?
HTML.Element("img",{style:{width:0,height:0,position:"relative"},src:"about:blank"}) :
HTML.Element("span",{style:{width:0,height:0,display:"inline-block"}})
);
if (ZOOM.operaPositionBug || ZOOM.msieTopBug) {ZOOM.topImg.style.border="1px solid"}
/*************************************************************/
MathJax.Callback.Queue(
["StartupHook",MathJax.Hub.Register,"Begin Styles",{}],
["Styles",AJAX,CONFIG.styles],
["Post",HUB.Startup.signal,"MathZoom Ready"],
["loadComplete",AJAX,"[MathJax]/extensions/MathZoom.js"]
);
})(MathJax.Hub,MathJax.HTML,MathJax.Ajax,MathJax.OutputJax["HTML-CSS"],MathJax.OutputJax.NativeMML);

View File

@ -0,0 +1,567 @@
/*************************************************************
*
* MathJax/extensions/TeX/AMSmath.js
*
* Implements AMS math environments and macros.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2009-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/AMSmath"] = {
version: "2.0",
number: 0, // current equation number
startNumber: 0, // current starting equation number (for when equation is restarted)
labels: {}, // the set of labels
eqlabels: {}, // labels in the current equation
refs: [] // array of jax with unresolved references
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var MML = MathJax.ElementJax.mml,
TEX = MathJax.InputJax.TeX,
AMS = MathJax.Extension["TeX/AMSmath"];
var TEXDEF = TEX.Definitions,
STACKITEM = TEX.Stack.Item,
CONFIG = TEX.config.equationNumbers;
var COLS = function (W) {return W.join("em ") + "em"};
/******************************************************************************/
TEXDEF.Add({
macros: {
mathring: ['Accent','2DA'], // or 0x30A
nobreakspace: 'Tilde',
negmedspace: ['Spacer',MML.LENGTH.NEGATIVEMEDIUMMATHSPACE],
negthickspace: ['Spacer',MML.LENGTH.NEGATIVETHICKMATHSPACE],
intI: ['Macro','\\mathchoice{\\!}{}{}{}\\!\\!\\int'],
// iint: ['MultiIntegral','\\int\\intI'], // now in core TeX input jax
// iiint: ['MultiIntegral','\\int\\intI\\intI'], // now in core TeX input jax
iiiint: ['MultiIntegral','\\int\\intI\\intI\\intI'],
idotsint: ['MultiIntegral','\\int\\cdots\\int'],
dddot: ['Macro','\\mathop{#1}\\limits^{\\textstyle \\mathord{.}\\mathord{.}\\mathord{.}}',1],
ddddot: ['Macro','\\mathop{#1}\\limits^{\\textstyle \\mathord{.}\\mathord{.}\\mathord{.}\\mathord{.}}',1],
sideset: ['Macro','\\mathop{\\mathop{\\rlap{\\phantom{#3}}}\\nolimits#1\\!\\mathop{#3}\\nolimits#2}',3],
boxed: ['Macro','\\fbox{$\\displaystyle{#1}$}',1],
tag: 'HandleTag',
notag: 'HandleNoTag',
label: 'HandleLabel',
ref: 'HandleRef',
eqref: ['HandleRef',true],
substack: ['Macro','\\begin{subarray}{c}#1\\end{subarray}',1],
injlim: ['Macro','\\mathop{\\rm inj\\,lim}'],
projlim: ['Macro','\\mathop{\\rm proj\\,lim}'],
varliminf: ['Macro','\\mathop{\\underline{\\rm lim}}'],
varlimsup: ['Macro','\\mathop{\\overline{\\rm lim}}'],
varinjlim: ['Macro','\\mathop{\\underrightarrow{\\rm lim\\Rule{-1pt}{0pt}{1pt}}\\Rule{0pt}{0pt}{.45em}}'],
varprojlim: ['Macro','\\mathop{\\underleftarrow{\\rm lim\\Rule{-1pt}{0pt}{1pt}}\\Rule{0pt}{0pt}{.45em}}'],
DeclareMathOperator: 'HandleDeclareOp',
operatorname: 'HandleOperatorName',
genfrac: 'Genfrac',
frac: ['Genfrac',"","","",""],
tfrac: ['Genfrac',"","","",1],
dfrac: ['Genfrac',"","","",0],
binom: ['Genfrac',"(",")","0em",""],
tbinom: ['Genfrac',"(",")","0em",1],
dbinom: ['Genfrac',"(",")","0em",0],
cfrac: 'CFrac',
shoveleft: ['HandleShove',MML.ALIGN.LEFT],
shoveright: ['HandleShove',MML.ALIGN.RIGHT],
xrightarrow: ['xArrow',0x2192,5,6],
xleftarrow: ['xArrow',0x2190,7,3]
},
environment: {
align: ['AMSarray',null,true,true, 'rlrlrlrlrlrl',COLS([5/18,2,5/18,2,5/18,2,5/18,2,5/18,2,5/18])],
'align*': ['AMSarray',null,false,true, 'rlrlrlrlrlrl',COLS([5/18,2,5/18,2,5/18,2,5/18,2,5/18,2,5/18])],
multline: ['Multline',null,true],
'multline*': ['Multline',null,false],
split: ['AMSarray',null,false,false,'rl',COLS([5/18])],
gather: ['AMSarray',null,true,true, 'c'],
'gather*': ['AMSarray',null,false,true, 'c'],
alignat: ['AlignAt',null,true,true],
'alignat*': ['AlignAt',null,false,true],
alignedat: ['AlignAt',null,false,false],
aligned: ['AlignedArray',null,null,null,'rlrlrlrlrlrl',COLS([5/18,2,5/18,2,5/18,2,5/18,2,5/18,2,5/18]),".5em",'D'],
gathered: ['AlignedArray',null,null,null,'c',null,".5em",'D'],
subarray: ['Array',null,null,null,null,COLS([0,0,0,0]),"0.1em",'S',1],
smallmatrix: ['Array',null,null,null,'c',COLS([1/3]),".2em",'S',1],
'equation': ['EquationBegin','Equation',true],
'equation*': ['EquationBegin','EquationStar',false]
},
delimiter: {
'\\lvert': ['2223',{texClass:MML.TEXCLASS.OPEN}],
'\\rvert': ['2223',{texClass:MML.TEXCLASS.CLOSE}],
'\\lVert': ['2225',{texClass:MML.TEXCLASS.OPEN}],
'\\rVert': ['2225',{texClass:MML.TEXCLASS.CLOSE}]
}
},null,true);
/******************************************************************************/
TEX.Parse.Augment({
/*
* Add the tag to the environment (to be added to the table row later)
*/
HandleTag: function (name) {
var star = this.GetStar();
var arg = this.trimSpaces(this.GetArgument(name)), tag = arg;
if (!star) {arg = CONFIG.formatTag(arg)}
var global = this.stack.global; global.tagID = tag;
if (global.notags) {TEX.Error(name+" not allowed in "+global.notags+" environment")}
if (global.tag) {TEX.Error("Multiple "+name)}
global.tag = MML.mtd.apply(MML,this.InternalMath(arg)).With({id:CONFIG.formatID(tag)});
},
HandleNoTag: function (name) {
if (this.stack.global.tag) {delete this.stack.global.tag}
this.stack.global.notag = true; // prevent auto-tagging
},
/*
* Record a label name for a tag
*/
HandleLabel: function (name) {
var global = this.stack.global, label = this.GetArgument(name);
if (!AMS.refUpdate) {
if (global.label) {TEX.Error("Multiple "+name+"'s")}
global.label = label;
if (AMS.labels[label] || AMS.eqlabels[label]) {TEX.Error("Label '"+label+"' mutiply defined")}
AMS.eqlabels[label] = "???"; // will be replaced by tag value later
}
},
/*
* Handle a label reference
*/
HandleRef: function (name,eqref) {
var label = this.GetArgument(name);
var ref = AMS.labels[label] || AMS.eqlabels[label];
if (!ref) {ref = "??"; AMS.badref = !AMS.refUpdate}
var tag = ref; if (eqref) {tag = CONFIG.formatTag(tag)}
if (CONFIG.useLabelIds) {ref = label}
this.Push(MML.mrow.apply(MML,this.InternalMath(tag)).With({
href:CONFIG.formatURL(CONFIG.formatID(ref)), "class":"MathJax_ref"
}));
},
/*
* Handle \DeclareMathOperator
*/
HandleDeclareOp: function (name) {
var limits = (this.GetStar() ? "\\limits" : "");
var cs = this.trimSpaces(this.GetArgument(name));
if (cs.charAt(0) == "\\") {cs = cs.substr(1)}
var op = this.GetArgument(name);
op = op.replace(/\*/g,'\\text{*}').replace(/-/g,'\\text{-}');
TEX.Definitions.macros[cs] = ['Macro','\\mathop{\\rm '+op+'}'+limits];
},
HandleOperatorName: function (name) {
var limits = (this.GetStar() ? "\\limits" : "\\nolimits");
var op = this.trimSpaces(this.GetArgument(name));
op = op.replace(/\*/g,'\\text{*}').replace(/-/g,'\\text{-}');
this.string = '\\mathop{\\rm '+op+'}'+limits+" "+this.string.slice(this.i);
this.i = 0;
},
/*
* Record presence of \shoveleft and \shoveright
*/
HandleShove: function (name,shove) {
var top = this.stack.Top();
if (top.type !== "multline" || top.data.length) {TEX.Error(name+" must come at the beginning of the line")}
top.data.shove = shove;
},
/*
* Handle \cfrac
*/
CFrac: function (name) {
var lr = this.trimSpaces(this.GetBrackets(name,"")),
num = this.GetArgument(name),
den = this.GetArgument(name);
var frac = MML.mfrac(TEX.Parse('\\strut\\textstyle{'+num+'}',this.stack.env).mml(),
TEX.Parse('\\strut\\textstyle{'+den+'}',this.stack.env).mml());
lr = ({l:MML.ALIGN.LEFT, r:MML.ALIGN.RIGHT,"":""})[lr];
if (lr == null) {TEX.Error("Illegal alignment specified in "+name)}
if (lr) {frac.numalign = frac.denomalign = lr}
this.Push(frac);
},
/*
* Implement AMS generalized fraction
*/
Genfrac: function (name,left,right,thick,style) {
if (left == null) {left = this.GetDelimiterArg(name)} else {left = this.convertDelimiter(left)}
if (right == null) {right = this.GetDelimiterArg(name)} else {right = this.convertDelimiter(right)}
if (thick == null) {thick = this.GetArgument(name)}
if (style == null) {style = this.trimSpaces(this.GetArgument(name))}
var num = this.ParseArg(name);
var den = this.ParseArg(name);
var frac = MML.mfrac(num,den);
if (thick !== "") {frac.linethickness = thick}
if (left || right) {frac = MML.mfenced(frac).With({open: left, close: right})}
if (style !== "") {
var STYLE = (["D","T","S","SS"])[style];
if (STYLE == null) {TEX.Error("Bad math style for "+name)}
frac = MML.mstyle(frac);
if (STYLE === "D") {frac.displaystyle = true; frac.scriptlevel = 0}
else {frac.displaystyle = false; frac.scriptlevel = style - 1}
}
this.Push(frac);
},
/*
* Implements multline environment (mostly handled through STACKITEM below)
*/
Multline: function (begin,numbered) {
this.Push(begin); this.checkEqnEnv();
return STACKITEM.multline(numbered,this.stack).With({
arraydef: {
displaystyle: true,
rowspacing: ".5em",
width: TEX.config.MultLineWidth, columnwidth:"100%",
side: TEX.config.TagSide,
minlabelspacing: TEX.config.TagIndent
}
});
},
/*
* Handle AMS aligned environments
*/
AMSarray: function (begin,numbered,taggable,align,spacing) {
this.Push(begin); if (taggable) {this.checkEqnEnv()}
align = align.replace(/[^clr]/g,'').split('').join(' ');
align = align.replace(/l/g,'left').replace(/r/g,'right').replace(/c/g,'center');
return STACKITEM.AMSarray(begin.name,numbered,taggable,this.stack).With({
arraydef: {
displaystyle: true,
rowspacing: ".5em",
columnalign: align,
columnspacing: (spacing||"1em"),
rowspacing: "3pt",
side: TEX.config.TagSide,
minlabelspacing: TEX.config.TagIndent
}
});
},
/*
* Handle alignat environments
*/
AlignAt: function (begin,numbered,taggable) {
var n, valign, align = "", spacing = [];
if (!taggable) {valign = this.GetBrackets("\\begin{"+begin.name+"}")}
n = this.GetArgument("\\begin{"+begin.name+"}");
if (n.match(/[^0-9]/)) {TEX.Error("Argument to \\begin{"+begin.name+"} must me a positive integer")}
while (n > 0) {align += "rl"; spacing.push("0em 0em"); n--}
spacing = spacing.join(" ");
if (taggable) {return this.AMSarray(begin,numbered,taggable,align,spacing)}
var array = this.Array.call(this,begin,null,null,align,spacing,".5em",'D');
return this.setArrayAlign(array,valign);
},
/*
* Handle equation environment
*/
EquationBegin: function (begin,force) {
this.checkEqnEnv();
this.stack.global.forcetag = (force && CONFIG.autoNumber !== "none");
return begin;
},
EquationStar: function (begin,row) {
this.stack.global.tagged = true; // prevent automatic tagging
return row;
},
/*
* Check for bad nesting of equation environments
*/
checkEqnEnv: function () {
if (this.stack.global.eqnenv) {TEX.Error("Erroneous nesting of equation structures")}
this.stack.global.eqnenv = true;
},
/*
* Handle multiple integrals (make a mathop if followed by limits)
*/
MultiIntegral: function (name,integral) {
var next = this.GetNext();
if (next === "\\") {
var i = this.i; next = this.GetArgument(name); this.i = i;
if (next === "\\limits") {
if (name === "\\idotsint") {integral = "\\!\\!\\mathop{\\,\\,"+integral+"}"}
else {integral = "\\!\\!\\!\\mathop{\\,\\,\\,"+integral+"}"}
}
}
this.string = integral + " " + this.string.slice(this.i);
this.i = 0;
},
/*
* Handle stretchable arrows
*/
xArrow: function (name,chr,l,r) {
var def = {width: "+"+(l+r)+"mu", lspace: l+"mu"};
var bot = this.GetBrackets(name),
top = this.ParseArg(name);
var arrow = MML.mo(MML.chars(String.fromCharCode(chr))).With({
stretchy: true, texClass: MML.TEXCLASS.REL
});
var mml = MML.munderover(arrow);
mml.SetData(mml.over,MML.mpadded(top).With(def).With({voffset:".15em"}));
if (bot) {
bot = TEX.Parse(bot,this.stack.env).mml()
mml.SetData(mml.under,MML.mpadded(bot).With(def).With({voffset:"-.24em"}));
}
this.Push(mml);
},
/*
* Get a delimiter or empty argument
*/
GetDelimiterArg: function (name) {
var c = this.trimSpaces(this.GetArgument(name));
if (c == "") {return null}
if (TEXDEF.delimiter[c] == null) {TEX.Error("Missing or unrecognized delimiter for "+name)}
return this.convertDelimiter(c);
},
/*
* Get a star following a control sequence name, if any
*/
GetStar: function () {
var star = (this.GetNext() === "*");
if (star) {this.i++}
return star;
}
});
/******************************************************************************/
STACKITEM.Augment({
/*
* Increment equation number and form tag mtd element
*/
autoTag: function () {
var global = this.global;
if (!global.notag) {
AMS.number++; global.tagID = CONFIG.formatNumber(AMS.number.toString());
var mml = TEX.Parse("\\text{"+CONFIG.formatTag(global.tagID)+"}",{}).mml();
global.tag = MML.mtd(mml.With({id:CONFIG.formatID(global.tagID)}));
}
},
/*
* Get the tag and record the label, if any
*/
getTag: function () {
var global = this.global, tag = global.tag; global.tagged = true;
if (global.label) {
AMS.eqlabels[global.label] = global.tagID;
if (CONFIG.useLabelIds) {tag.id = CONFIG.formatID(global.label)}
}
delete global.tag; delete global.tagID; delete global.label;
return tag;
}
});
/*
* Implement multline environment via a STACKITEM
*/
STACKITEM.multline = STACKITEM.array.Subclass({
type: "multline",
Init: function (numbered,stack) {
this.SUPER(arguments).Init.apply(this);
this.numbered = (numbered && CONFIG.autoNumber !== "none");
this.save = {notag: stack.global.notag};
stack.global.tagged = !numbered && !stack.global.forcetag; // prevent automatic tagging in starred environments
},
EndEntry: function () {
var mtd = MML.mtd.apply(MML,this.data);
if (this.data.shove) {mtd.columnalign = this.data.shove}
this.row.push(mtd);
this.data = [];
},
EndRow: function () {
if (this.row.length != 1) {TEX.Error("multline rows must have exactly one column")}
this.table.push(this.row); this.row = [];
},
EndTable: function () {
this.SUPER(arguments).EndTable.call(this);
if (this.table.length) {
var m = this.table.length-1, i, label = -1;
if (!this.table[0][0].columnalign) {this.table[0][0].columnalign = MML.ALIGN.LEFT}
if (!this.table[m][0].columnalign) {this.table[m][0].columnalign = MML.ALIGN.RIGHT}
if (!this.global.tag && this.numbered) {this.autoTag()}
if (this.global.tag && !this.global.notags) {
label = (this.arraydef.side === "left" ? 0 : this.table.length - 1);
this.table[label] = [this.getTag()].concat(this.table[label]);
}
for (i = 0, m = this.table.length; i < m; i++) {
var mtr = (i === label ? MML.mlabeledtr : MML.mtr);
this.table[i] = mtr.apply(MML,this.table[i]);
}
}
this.global.notag = this.save.notag;
}
});
/*
* Save data about numbering and taging equations, and add
* tags at the ends of rows.
*/
STACKITEM.AMSarray = STACKITEM.array.Subclass({
type: "AMSarray",
Init: function (name,numbered,taggable,stack) {
this.SUPER(arguments).Init.apply(this);
this.numbered = (numbered && CONFIG.autoNumber !== "none");
this.save = {notags: stack.global.notags, notag: stack.global.notag};
stack.global.notags = (taggable ? null : name);
stack.global.tagged = !numbered && !stack.global.forcetag; // prevent automatic tagging in starred environments
},
EndRow: function () {
var mtr = MML.mtr;
if (!this.global.tag && this.numbered) {this.autoTag()}
if (this.global.tag &&! this.global.notags) {
this.row = [this.getTag()].concat(this.row);
mtr = MML.mlabeledtr;
}
if (this.numbered) {delete this.global.notag}
this.table.push(mtr.apply(MML,this.row)); this.row = [];
},
EndTable: function () {
this.SUPER(arguments).EndTable.call(this);
this.global.notags = this.save.notags;
this.global.notag = this.save.notag;
}
});
//
// Look for \tag on a formula and make an mtable to include it
//
STACKITEM.start.Augment({
oldCheckItem: STACKITEM.start.prototype.checkItem,
checkItem: function (item) {
if (item.type === "stop") {
var mml = this.mmlData(), global = this.global;
if (AMS.display && !global.tag && !global.tagged && !global.isInner &&
(CONFIG.autoNumber === "all" || global.forcetag)) {this.autoTag()}
if (global.tag) {
var row = [this.getTag(),MML.mtd(mml)];
var def = {
side: TEX.config.TagSide,
minlabelspacing: TEX.config.TagIndent,
columnalign: mml.displayAlign
};
if (mml.displayAlign === MML.INDENTALIGN.LEFT) {
def.width = "100%";
if (mml.displayIndent && !String(mml.displayIndent).match(/^0+(\.0*)?($|[a-z%])/)) {
def.columnwidth = mml.displayIndent + " fit"; def.columnspacing = "0"
row = [row[0],MML.mtd(),row[1]];
}
} else if (mml.displayAlign === MML.INDENTALIGN.RIGHT) {
def.width = "100%";
if (mml.displayIndent && !String(mml.displayIndent).match(/^0+(\.0*)?($|[a-z%])/)) {
def.columnwidth = "fit "+mml.displayIndent; def.columnspacing = "0"
row[2] = MML.mtd();
}
}
mml = MML.mtable(MML.mlabeledtr.apply(MML,row)).With(def);
}
return STACKITEM.mml(mml);
}
return this.oldCheckItem.call(this,item);
}
});
/******************************************************************************/
/*
* Add pre- and post-filters to handle the equation number maintainance.
*/
TEX.prefilterHooks.Add(function (data) {
AMS.display = data.display;
AMS.number = AMS.startNumber; // reset equation numbers (in case the equation restarted)
AMS.eqlabels = {}; AMS.badref = false;
if (AMS.refUpdate) {AMS.number = data.script.MathJax.startNumber}
});
TEX.postfilterHooks.Add(function (data) {
data.script.MathJax.startNumber = AMS.startNumber;
AMS.startNumber = AMS.number; // equation numbers for next equation
MathJax.Hub.Insert(AMS.labels,AMS.eqlabels); // save labels from this equation
if (AMS.badref && !data.math.texError) {AMS.refs.push(data.script)} // reprocess later
});
MathJax.Hub.Register.MessageHook("Begin Math Input",function () {
AMS.refs = []; // array of jax with bad references
AMS.refUpdate = false;
});
MathJax.Hub.Register.MessageHook("End Math Input",function (message) {
if (AMS.refs.length) {
AMS.refUpdate = true;
for (var i = 0, m = AMS.refs.length; i < m; i++)
{AMS.refs[i].MathJax.state = MathJax.ElementJax.STATE.UPDATE}
return MathJax.Hub.processInput({
scripts:AMS.refs,
start: new Date().getTime(),
i:0, j:0, jax:{}, jaxIDs:[]
});
}
return null;
});
//
// Clear the equation numbers and labels
//
TEX.resetEquationNumbers = function (n,keepLabels) {
AMS.startNumber = (n || 0);
if (!keepLabels) {AMS.labels = {}}
}
/******************************************************************************/
MathJax.Hub.Startup.signal.Post("TeX AMSmath Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/AMSmath.js");

View File

@ -0,0 +1,401 @@
/*************************************************************
*
* MathJax/extensions/TeX/AMSsymbols.js
*
* Implements macros for accessing the AMS symbol fonts.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2009-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/AMSsymbols"] = {
version: "2.0"
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var MML = MathJax.ElementJax.mml,
TEXDEF = MathJax.InputJax.TeX.Definitions;
TEXDEF.Add({
mathchar0mi: {
// Lowercase Greek letters
digamma: '03DD',
varkappa: '03F0',
// Uppercase Greek letters
varGamma: ['0393',{mathvariant: MML.VARIANT.ITALIC}],
varDelta: ['0394',{mathvariant: MML.VARIANT.ITALIC}],
varTheta: ['0398',{mathvariant: MML.VARIANT.ITALIC}],
varLambda: ['039B',{mathvariant: MML.VARIANT.ITALIC}],
varXi: ['039E',{mathvariant: MML.VARIANT.ITALIC}],
varPi: ['03A0',{mathvariant: MML.VARIANT.ITALIC}],
varSigma: ['03A3',{mathvariant: MML.VARIANT.ITALIC}],
varUpsilon: ['03A5',{mathvariant: MML.VARIANT.ITALIC}],
varPhi: ['03A6',{mathvariant: MML.VARIANT.ITALIC}],
varPsi: ['03A8',{mathvariant: MML.VARIANT.ITALIC}],
varOmega: ['03A9',{mathvariant: MML.VARIANT.ITALIC}],
// Hebrew letters
beth: '2136',
gimel: '2137',
daleth: '2138',
// Miscellaneous symbols
// hbar: '0127', // in MathJax_Main
backprime: ['2035',{variantForm: true}],
hslash: ['210F',{variantForm: true}],
varnothing: ['2205',{variantForm: true}],
blacktriangle: '25B2',
triangledown: '25BD',
blacktriangledown: '25BC',
square: '25A1',
Box: '25A1',
blacksquare: '25A0',
lozenge: '25CA',
Diamond: '25CA',
blacklozenge: '29EB',
circledS: ['24C8',{mathvariant: MML.VARIANT.NORMAL}],
bigstar: '2605',
// angle: '2220', // in MathJax_Main
sphericalangle: '2222',
measuredangle: '2221',
nexists: '2204',
complement: '2201',
mho: '2127',
eth: ['00F0',{mathvariant: MML.VARIANT.NORMAL}],
Finv: '2132',
diagup: '2571',
Game: '2141',
diagdown: '2572',
Bbbk: ['006B',{mathvariant: MML.VARIANT.DOUBLESTRUCK}],
yen: '00A5',
circledR: '00AE',
checkmark: '2713',
maltese: '2720'
},
mathchar0mo: {
// Binary operators
dotplus: '2214',
ltimes: '22C9',
smallsetminus: ['2216',{variantForm: true}],
rtimes: '22CA',
Cap: '22D2',
doublecap: '22D2',
leftthreetimes: '22CB',
Cup: '22D3',
doublecup: '22D3',
rightthreetimes: '22CC',
barwedge: '22BC',
curlywedge: '22CF',
veebar: '22BB',
curlyvee: '22CE',
doublebarwedge: '2A5E',
boxminus: '229F',
circleddash: '229D',
boxtimes: '22A0',
circledast: '229B',
boxdot: '22A1',
circledcirc: '229A',
boxplus: '229E',
centerdot: '22C5',
divideontimes: '22C7',
intercal: '22BA',
// Binary relations
leqq: '2266',
geqq: '2267',
leqslant: '2A7D',
geqslant: '2A7E',
eqslantless: '2A95',
eqslantgtr: '2A96',
lesssim: '2272',
gtrsim: '2273',
lessapprox: '2A85',
gtrapprox: '2A86',
approxeq: '224A',
lessdot: '22D6',
gtrdot: '22D7',
lll: '22D8',
llless: '22D8',
ggg: '22D9',
gggtr: '22D9',
lessgtr: '2276',
gtrless: '2277',
lesseqgtr: '22DA',
gtreqless: '22DB',
lesseqqgtr: '2A8B',
gtreqqless: '2A8C',
doteqdot: '2251',
Doteq: '2251',
eqcirc: '2256',
risingdotseq: '2253',
circeq: '2257',
fallingdotseq: '2252',
triangleq: '225C',
backsim: '223D',
thicksim: ['223C',{variantForm: true}],
backsimeq: '22CD',
thickapprox: ['2248',{variantForm: true}],
subseteqq: '2AC5',
supseteqq: '2AC6',
Subset: '22D0',
Supset: '22D1',
sqsubset: '228F',
sqsupset: '2290',
preccurlyeq: '227C',
succcurlyeq: '227D',
curlyeqprec: '22DE',
curlyeqsucc: '22DF',
precsim: '227E',
succsim: '227F',
precapprox: '2AB7',
succapprox: '2AB8',
vartriangleleft: '22B2',
lhd: '22B2',
vartriangleright: '22B3',
rhd: '22B3',
trianglelefteq: '22B4',
unlhd: '22B4',
trianglerighteq: '22B5',
unrhd: '22B5',
vDash: '22A8',
Vdash: '22A9',
Vvdash: '22AA',
smallsmile: '2323',
shortmid: ['2223',{variantForm: true}],
smallfrown: '2322',
shortparallel: ['2225',{variantForm: true}],
bumpeq: '224F',
between: '226C',
Bumpeq: '224E',
pitchfork: '22D4',
varpropto: '221D',
backepsilon: '220D',
blacktriangleleft: '25C0',
blacktriangleright: '25B6',
therefore: '2234',
because: '2235',
eqsim: '2242',
vartriangle: ['25B3',{variantForm: true}],
Join: '22C8',
// Negated relations
nless: '226E',
ngtr: '226F',
nleq: '2270',
ngeq: '2271',
nleqslant: ['2A87',{variantForm: true}],
ngeqslant: ['2A88',{variantForm: true}],
nleqq: ['2270',{variantForm: true}],
ngeqq: ['2271',{variantForm: true}],
lneq: '2A87',
gneq: '2A88',
lneqq: '2268',
gneqq: '2269',
lvertneqq: ['2268',{variantForm: true}],
gvertneqq: ['2269',{variantForm: true}],
lnsim: '22E6',
gnsim: '22E7',
lnapprox: '2A89',
gnapprox: '2A8A',
nprec: '2280',
nsucc: '2281',
npreceq: ['22E0',{variantForm: true}],
nsucceq: ['22E1',{variantForm: true}],
precneqq: '2AB5',
succneqq: '2AB6',
precnsim: '22E8',
succnsim: '22E9',
precnapprox: '2AB9',
succnapprox: '2ABA',
nsim: '2241',
ncong: '2246',
nshortmid: ['2224',{variantForm: true}],
nshortparallel: ['2226',{variantForm: true}],
nmid: '2224',
nparallel: '2226',
nvdash: '22AC',
nvDash: '22AD',
nVdash: '22AE',
nVDash: '22AF',
ntriangleleft: '22EA',
ntriangleright: '22EB',
ntrianglelefteq: '22EC',
ntrianglerighteq: '22ED',
nsubseteq: '2288',
nsupseteq: '2289',
nsubseteqq: ['2288',{variantForm: true}],
nsupseteqq: ['2289',{variantForm: true}],
subsetneq: '228A',
supsetneq: '228B',
varsubsetneq: ['228A',{variantForm: true}],
varsupsetneq: ['228B',{variantForm: true}],
subsetneqq: '2ACB',
supsetneqq: '2ACC',
varsubsetneqq: ['2ACB',{variantForm: true}],
varsupsetneqq: ['2ACC',{variantForm: true}],
// Arrows
leftleftarrows: '21C7',
rightrightarrows: '21C9',
leftrightarrows: '21C6',
rightleftarrows: '21C4',
Lleftarrow: '21DA',
Rrightarrow: '21DB',
twoheadleftarrow: '219E',
twoheadrightarrow: '21A0',
leftarrowtail: '21A2',
rightarrowtail: '21A3',
looparrowleft: '21AB',
looparrowright: '21AC',
leftrightharpoons: '21CB',
rightleftharpoons: ['21CC',{variantForm: true}],
curvearrowleft: '21B6',
curvearrowright: '21B7',
circlearrowleft: '21BA',
circlearrowright: '21BB',
Lsh: '21B0',
Rsh: '21B1',
upuparrows: '21C8',
downdownarrows: '21CA',
upharpoonleft: '21BF',
upharpoonright: '21BE',
downharpoonleft: '21C3',
restriction: '21BE',
multimap: '22B8',
downharpoonright: '21C2',
leftrightsquigarrow: '21AD',
rightsquigarrow: '21DD',
leadsto: '21DD',
dashrightarrow: '21E2',
dashleftarrow: '21E0',
// Negated arrows
nleftarrow: '219A',
nrightarrow: '219B',
nLeftarrow: '21CD',
nRightarrow: '21CF',
nleftrightarrow: '21AE',
nLeftrightarrow: '21CE'
},
delimiter: {
// corners
"\\ulcorner": '231C',
"\\urcorner": '231D',
"\\llcorner": '231E',
"\\lrcorner": '231F'
},
macros: {
implies: ['Macro','\\;\\Longrightarrow\\;'],
impliedby: ['Macro','\\;\\Longleftarrow\\;']
}
},null,true);
var REL = MML.mo.OPTYPES.REL;
MathJax.Hub.Insert(MML.mo.prototype,{
OPTABLE: {
infix: {
'\u2322': REL, // smallfrown
'\u2323': REL, // smallsmile
'\u25B3': REL, // vartriangle
'\uE006': REL, // nshortmid
'\uE007': REL, // nshortparallel
'\uE00C': REL, // lvertneqq
'\uE00D': REL, // gvertneqq
'\uE00E': REL, // ngeqq
'\uE00F': REL, // ngeqslant
'\uE010': REL, // nleqslant
'\uE011': REL, // nleqq
'\uE016': REL, // nsubseteqq
'\uE017': REL, // varsubsetneqq
'\uE018': REL, // nsupseteqq
'\uE019': REL, // varsupsetneqq
'\uE01A': REL, // varsubsetneq
'\uE01B': REL, // varsupsetneq
'\uE04B': REL, // npreceq
'\uE04F': REL // nsucceq
}
}
});
MathJax.Hub.Startup.signal.Post("TeX AMSsymbols Ready");
});
MathJax.Hub.Register.StartupHook("HTML-CSS Jax Ready",function () {
var HTMLCSS = MathJax.OutputJax["HTML-CSS"];
var VARIANT = HTMLCSS.FONTDATA.VARIANT;
if (HTMLCSS.fontInUse === "TeX") {
VARIANT["-TeX-variant"] = {
fonts: ["MathJax_AMS","MathJax_Main","MathJax_Size1"],
remap: {0x2268: 0xE00C, 0x2269: 0xE00D, 0x2270: 0xE011, 0x2271: 0xE00E,
0x2A87: 0xE010, 0x2A88: 0xE00F, 0x2224: 0xE006, 0x2226: 0xE007,
0x2288: 0xE016, 0x2289: 0xE018, 0x228A: 0xE01A, 0x228B: 0xE01B,
0x2ACB: 0xE017, 0x2ACC: 0xE019, 0x03DC: 0xE008, 0x03F0: 0xE009}
};
if (HTMLCSS.msieIE6) {
MathJax.Hub.Insert(VARIANT["-TeX-variant"].remap,{
0x2190:[0xE2C1,"-WinIE6"], 0x2192:[0xE2C0,"-WinIE6"],
0x2223:[0xE2C2,"-WinIE6"], 0x2225:[0xE2C3,"-WinIE6"],
0x223C:[0xE2C4,"-WinIE6"], 0x25B3:[0xE2D3,"-WinIE6"]
});
}
}
if (HTMLCSS.fontInUse === "STIX") {
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEXDEF = MathJax.InputJax.TeX.Definitions;
TEXDEF.mathchar0mi.varnothing = '2205';
TEXDEF.mathchar0mi.hslash = '210F';
TEXDEF.mathchar0mi.blacktriangle = '25B4';
TEXDEF.mathchar0mi.blacktriangledown = '25BE';
TEXDEF.mathchar0mi.square = '25FB';
TEXDEF.mathchar0mi.blacksquare = '25FC';
TEXDEF.mathchar0mi.vartriangle = ['25B3',{mathsize:"71%"}];
TEXDEF.mathchar0mi.triangledown = ['25BD',{mathsize:"71%"}];
TEXDEF.mathchar0mo.blacktriangleleft = '25C2';
TEXDEF.mathchar0mo.blacktriangleright = '25B8';
TEXDEF.mathchar0mo.smallsetminus = '2216';
MathJax.Hub.Insert(VARIANT["-STIX-variant"],{
remap: {0x2A87: 0xE010, 0x2A88: 0xE00F, 0x2270: 0xE011, 0x2271: 0xE00E,
0x22E0: 0xE04B, 0x22E1: 0xE04F, 0x2288: 0xE016, 0x2289: 0xE018}
});
});
}
});
MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () {
var SVG = MathJax.OutputJax.SVG;
var VARIANT = SVG.FONTDATA.VARIANT;
VARIANT["-TeX-variant"] = {
fonts: ["MathJax_AMS","MathJax_Main","MathJax_Size1"],
remap: {0x2268: 0xE00C, 0x2269: 0xE00D, 0x2270: 0xE011, 0x2271: 0xE00E,
0x2A87: 0xE010, 0x2A88: 0xE00F, 0x2224: 0xE006, 0x2226: 0xE007,
0x2288: 0xE016, 0x2289: 0xE018, 0x228A: 0xE01A, 0x228B: 0xE01B,
0x2ACB: 0xE017, 0x2ACC: 0xE019, 0x03DC: 0xE008, 0x03F0: 0xE009}
};
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/AMSsymbols.js");

View File

@ -0,0 +1,103 @@
/*************************************************************
*
* MathJax/extensions/TeX/HTML.js
*
* Implements the \href, \class, \style, \cssId macros.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2010-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/HTML"] = {
version: "2.0"
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX;
var TEXDEF = TEX.Definitions;
TEXDEF.Add({
macros: {
href: 'HREF_attribute',
"class": 'CLASS_attribute',
style: 'STYLE_attribute',
cssId: 'ID_attribute'
}
});
TEX.Parse.Augment({
//
// Implements \href{url}{math}
//
HREF_attribute: function (name) {
var url = this.GetArgument(name),
arg = this.GetArgumentMML(name);
this.Push(arg.With({href:url}));
},
//
// Implements \class{name}{math}
//
CLASS_attribute: function (name) {
var CLASS = this.GetArgument(name),
arg = this.GetArgumentMML(name);
if (arg["class"] != null) {CLASS = arg["class"] + " " + CLASS}
this.Push(arg.With({"class":CLASS}));
},
//
// Implements \style{style-string}{math}
//
STYLE_attribute: function (name) {
var style = this.GetArgument(name),
arg = this.GetArgumentMML(name);
// check that it looks like a style string
if (arg.style != null) {
if (style.charAt(style.length-1) !== ";") {style += ";"}
style = arg.style + " " + style;
}
this.Push(arg.With({style: style}));
},
//
// Implements \cssId{id}{math}
//
ID_attribute: function (name) {
var ID = this.GetArgument(name),
arg = this.GetArgumentMML(name);
this.Push(arg.With({id:ID}));
},
//
// returns an argument that is a single MathML element
// (in an mrow if necessary)
//
GetArgumentMML: function (name) {
var arg = this.ParseArg(name);
if (arg.inferred && arg.data.length == 1)
{arg = arg.data[0]} else {delete arg.inferred}
return arg;
}
});
MathJax.Hub.Startup.signal.Post("TeX HTML Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/HTML.js");

View File

@ -0,0 +1,76 @@
/*************************************************************
*
* MathJax/extensions/TeX/action.js
*
* Implements the \mathtip, \texttip, and \toggle macros, which give
* access from TeX to the <maction> tag in the MathML that underlies
* MathJax's internal format.
*
* Usage:
*
* \mathtip{math}{tip} % use "tip" (in math mode) as tooltip for "math"
* \texttip{math}{tip} % use "tip" (in text mode) as tooltip for "math"
* \toggle{math1}{math2}...\endtoggle
* % show math1, and when clicked, show math2, and so on.
* % When the last one is clicked, go back to math1.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2011-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/action"] = {
version: "2.0"
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX,
MML = MathJax.ElementJax.mml;
//
// Set up control sequenecs
//
TEX.Definitions.macros.toggle = 'Toggle';
TEX.Definitions.macros.mathtip = 'Mathtip';
TEX.Definitions.macros.texttip = ['Macro','\\mathtip{#1}{\\text{#2}}',2];
TEX.Parse.Augment({
//
// Implement \toggle {math1} {math2} ... \endtoggle
// (as an <maction actiontype="toggle">)
//
Toggle: function (name) {
var data = [], arg;
while ((arg = this.GetArgument(name)) !== "\\endtoggle")
{data.push(TEX.Parse(arg,this.stack.env).mml())}
this.Push(MML.maction.apply(MML,data).With({actiontype: MML.ACTIONTYPE.TOGGLE}));
},
//
// Implement \mathtip{math}{tip}
// (an an <maction actiontype="tooltip">)
//
Mathtip: function(name) {
var arg = this.ParseArg(name), tip = this.ParseArg(name);
this.Push(MML.maction(arg,tip).With({actiontype: MML.ACTIONTYPE.TOOLTIP}));
}
});
MathJax.Hub.Startup.signal.Post("TeX action Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/action.js");

View File

@ -0,0 +1,45 @@
/*************************************************************
*
* MathJax/extensions/TeX/autobold.js
*
* Adds \boldsymbol around mathematics that appears in a section
* of an HTML page that is in bold.
*
* Copyright (c) 2009-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/autobold"] = {
version: "2.0"
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX;
TEX.prefilterHooks.Add(function (data) {
var span = data.script.parentNode.insertBefore(document.createElement("span"),data.script);
span.visibility = "hidden";
span.style.fontFamily = "Times, serif";
span.appendChild(document.createTextNode("ABCXYZabcxyz"));
var W = span.offsetWidth;
span.style.fontWeight = "bold";
if (W && span.offsetWidth === W) {data.math = "\\boldsymbol{"+data.math+"}"}
span.parentNode.removeChild(span);
});
MathJax.Hub.Startup.signal.Post("TeX autobold Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/autobold.js");

View File

@ -0,0 +1,71 @@
/*************************************************************
*
* MathJax/extensions/TeX/autoload-all.js
*
* Provides pre-defined macros to autoload all the extensions
* so that all macros that MathJax knows about are available.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/autoload-all"] = {
version: "2.0"
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX,
MACROS = TEX.Definitions.macros,
ENVS = TEX.Definitions.environment;
var EXTENSIONS = {
action: ["mathtip","texttip","toggle"],
AMSmath: ["mathring","nobreakspace","negmedspace","negthickspace","intI",
"iiiint","idotsint","dddot","ddddot","sideset","boxed",
"substack","injlim","projlim","varliminf","varlimsup",
"varinjlim","varprojlim","DeclareMathOperator","operatorname",
"genfrac","tfrac","dfrac","binom","tbinom","dbinom","cfrac",
"shoveleft","shoveright","xrightarrow","xleftarrow"],
begingroup: ["begingroup","endgroup","gdef","global"],
cancel: ["cancel","bcancel","xcancel","cancelto"],
color: ["color","colorbox","fcolorbox","DefineColor"],
enclose: ["enclose"],
extpfeil: ["Newextarrow","xlongequal","xmapsto","xtofrom",
"xtwoheadleftarrow","xtwoheadrightarrow"],
mhchem: ["ce","cee","cf"]
};
for (var name in EXTENSIONS) {if (EXTENSIONS.hasOwnProperty(name)) {
var macros = EXTENSIONS[name];
for (var i = 0, m = macros.length; i < m; i++) {
MACROS[macros[i]] = ["Extension",name];
}
}}
ENVS["subarray"] = ['ExtensionEnv',null,'AMSmath'];
ENVS["smallmatrix"] = ['ExtensionEnv',null,'AMSmath'];
ENVS["equation"] = ['ExtensionEnv',null,'AMSmath'];
ENVS["equation*"] = ['ExtensionEnv',null,'AMSmath'];
MathJax.Hub.Startup.signal.Post("TeX autoload-all Ready");
});
MathJax.Callback.Queue(
["Require",MathJax.Ajax,"[MathJax]/extensions/TeX/AMSsymbols.js"],
["loadComplete",MathJax.Ajax,"[MathJax]/extensions/TeX/autoload-all.js"]
);

View File

@ -0,0 +1,90 @@
/*************************************************************
*
* MathJax/extensions/TeX/bbox.js
*
* This file implements the \bbox macro, which creates an box that
* can be styled (for background colors, and so on). You can include
* an optional dimension that tells how much extra padding to include
* around the bounding box for the mathematics, or a color specification
* for the background color to use, or both. E.g.,
*
* \bbox[2pt]{x+y} % an invisible box around x+y with 2pt of extra space
* \bbox[green]{x+y} % a green box around x+y
* \bbox[green,2pt]{x+y} % a green box with 2pt of extra space
*
* You can also specify style attributes, for example
*
* \bbox[red,border:3px solid blue,5px]{x+y}
*
* would give a red background with a 3px solid blue border that has 5px
* of padding between the border and the mathematics. Note that not all
* output formats support the style specifications. In particular, the
* NativeMML output depends on the browser to render the attributes, and
* not all MathML renderers will honor them (e.g., MathPlayer2 doesn't
* render border styles).
*
* This file will be loaded automatically when \bbox is first used.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2011-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/bbox"] = {
version: "2.0"
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX,
MML = MathJax.ElementJax.mml;
TEX.Definitions.macros.bbox = "BBox";
TEX.Parse.Augment({
BBox: function (name) {
var bbox = this.GetBrackets(name,""),
math = this.ParseArg(name);
var parts = bbox.split(/,/), def, background, style;
for (var i in parts) {
var part = parts[i].replace(/^\s+/,'').replace(/\s+$/,'');
var match = part.match(/^(\.\d+|\d+(\.\d*)?)(pt|em|ex|mu|px|in|cm|mm)$/);
if (match) {
var pad = match[1]+match[3];
if (def) {TEX.Error("Padding specified twice in "+name)}
def = {height:"+"+pad, depth:"+"+pad, lspace:pad, width:"+"+(2*match[1])+match[3]};
} else if (part.match(/^([a-z0-9]+|\#[0-9a-f]{6}|\#[0-9a-f]{3})$/i)) {
if (background) {TEX.Error("Background specified twice in "+name)}
background = part;
} else if (part.match(/^[-a-z]+:/i)) {
if (style) {TEX.Error("Style specified twice in "+name)}
style = part;
} else if (part !== "") {
TEX.Error("'"+part+"' doesn't look like a color, a padding dimension, or a style");
}
}
if (def) {math = MML.mpadded(math).With(def)}
if (background || style) {
math = MML.mstyle(math).With({mathbackground:background, style:style});
}
this.Push(math);
}
});
MathJax.Hub.Startup.signal.Post("TeX bbox Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/bbox.js");

View File

@ -0,0 +1,296 @@
/*************************************************************
*
* MathJax/extensions/TeX/begingroup.js
*
* Implements \begingroup and \endgroup commands that make local
* definitions possible and are removed when the \endgroup occurs.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2011-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/begingroup"] = {
version: "2.0"
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX,
TEXDEF = TEX.Definitions;
/****************************************************/
//
// A namespace for localizing macros and environments
// (\begingroup and \endgroup create and destroy these)
//
var NSFRAME = MathJax.Object.Subclass({
macros: null, // the local macro definitions
environments: null, // the local environments
Init: function (macros,environments) {
this.macros = (macros || {});
this.environments = (environments || {});
},
//
// Find a macro or environment by name
//
Find: function (name,type) {if (this[type][name]) {return this[type][name]}},
//
// Define or remove a macro or environment
//
Def: function (name,value,type) {this[type][name] = value},
Undef: function (name,type) {delete this[type][name]},
//
// Merge two namespaces (used when the equation namespace is combined with the root one)
//
Merge: function (frame) {
MathJax.Hub.Insert(this.macros,frame.macros);
MathJax.Hub.Insert(this.environments,frame.environments);
},
//
// Move global macros to the stack (globally) and remove from the frame
//
MergeGlobals: function (stack) {
var macros = this.macros;
for (var cs in macros) {if (macros.hasOwnProperty(cs) && macros[cs].global) {
stack.Def(cs,macros[cs],"macros",true);
delete macros[cs].global; delete macros[cs];
}}
},
//
// Clear the macro and environment lists
// (but not global macros unless "all" is true)
//
Clear: function (all) {
this.environments = {};
if (all) {this.macros = {}} else {
var macros = this.macros;
for (var cs in macros) {
if (macros.hasOwnProperty(cs) && !macros[cs].global) {delete macros[cs]}
}
}
return this;
}
});
/****************************************************/
//
// A Stack of namespace frames
//
var NSSTACK = TEX.nsStack = MathJax.Object.Subclass({
stack: null, // the namespace frames
top: 0, // the current top one (we don't pop for real until the equation completes)
isEqn: false, // true if this is the equation stack (not the global one)
//
// Set up the initial stack frame
//
Init: function (eqn) {
this.isEqn = eqn; this.stack = [];
if (!eqn) {this.Push(NSFRAME(TEXDEF.macros,TEXDEF.environments))}
else {this.Push(NSFRAME())}
},
//
// Define a macro or environment in the top frame
//
Def: function (name,value,type,global) {
var n = this.top-1;
if (global) {
//
// Define global macros in the base frame and remove that cs
// from all other frames. Mark the global ones in equations
// so they can be made global when merged with the root stack.
//
while (n > 0) {this.stack[n].Undef(name,type); n--}
if (!(value instanceof Array)) {value = [value]}
if (this.isEqn) {value.global = true}
}
this.stack[n].Def(name,value,type);
},
//
// Push a new namespace frame on the stack
//
Push: function (frame) {
this.stack.push(frame);
this.top = this.stack.length;
},
//
// Pop the top stack frame
// (if it is the root, just keep track of the pop so we can
// reset it if the equation is reprocessed)
//
Pop: function () {
var top;
if (this.top > 1) {
top = this.stack[--this.top];
if (this.isEqn) {this.stack.pop()}
} else if (this.isEqn) {
this.Clear();
}
return top;
},
//
// Search the stack from top to bottom for the first
// definition of the given control sequence in the given type
//
Find: function (name,type) {
for (var i = this.top-1; i >= 0; i--) {
var def = this.stack[i].Find(name,type);
if (def) {return def}
}
return null;
},
//
// Combine the equation stack with the global one
// (The bottom frame of the equation goes with the top frame of the global one,
// and the remainder are pushed on the global stack, truncated to the
// position where items were poped from it.)
//
Merge: function (stack) {
stack.stack[0].MergeGlobals(this);
this.stack[this.top-1].Merge(stack.stack[0]);
var data = [this.top,this.stack.length-this.top].concat(stack.stack.slice(1));
this.stack.splice.apply(this.stack,data);
this.top = this.stack.length;
},
//
// Put back the temporarily poped items
//
Reset: function () {this.top = this.stack.length},
//
// Clear the stack and start with a blank frame
//
Clear: function (all) {
this.stack = [this.stack[0].Clear()];
this.top = this.stack.length;
}
},{
nsFrame: NSFRAME
});
/****************************************************/
//
// Define the new macros
//
TEXDEF.macros.begingroup = "BeginGroup";
TEXDEF.macros.endgroup = "EndGroup";
TEXDEF.macros.global = ["Extension","newcommand"];
TEXDEF.macros.gdef = ["Extension","newcommand"];
TEX.Parse.Augment({
//
// Implement \begingroup
//
BeginGroup: function (name) {
TEX.eqnStack.Push(NSFRAME());
},
//
// Implements \endgroup
//
EndGroup: function (name) {
//
// If the equation has pushed frames, pop one,
// Otherwise clear the equation stack and pop the top global one
//
if (TEX.eqnStack.top > 1) {
TEX.eqnStack.Pop();
} else if (TEX.rootStack.top === 1) {
TEX.Error("Extra "+name+" or missing \\begingroup");
} else {
TEX.eqnStack.Clear();
TEX.rootStack.Pop();
}
},
//
// Replace the original routines with ones that looks through the
// equation and root stacks for the given name
//
csFindMacro: function (name) {
return (TEX.eqnStack.Find(name,"macros") || TEX.rootStack.Find(name,"macros"));
},
envFindName: function (name) {
return (TEX.eqnStack.Find(name,"environments") || TEX.rootStack.Find(name,"environments"));
}
});
/****************************************************/
TEX.rootStack = NSSTACK(); // the global namespace stack
TEX.eqnStack = NSSTACK(true); // the equation stack
//
// Reset the global stack and clear the equation stack
// (this gets us back to the initial stack state as it was
// before the equation was first processed, in case the equation
// get restarted due to an autoloaded file)
//
TEX.prefilterHooks.Add(function () {TEX.rootStack.Reset(); TEX.eqnStack.Clear(true)});
//
// We only get here if there were no errors and the equation is fully
// processed (all restarts are complete). So we merge the equation
// stack into the global stack, thus making the changes from this
// equation permanent.
//
TEX.postfilterHooks.Add(function () {TEX.rootStack.Merge(TEX.eqnStack)});
/*********************************************************/
MathJax.Hub.Register.StartupHook("TeX newcommand Ready",function () {
//
// Add the commands that depend on the newcommand code
//
TEXDEF.macros.global = "Global";
TEXDEF.macros.gdef = ["Macro","\\global\\def"];
TEX.Parse.Augment({
//
// Modify the way macros and environments are defined
// to make them go into the equation namespace stack
//
setDef: function (name,value) {
value.isUser = true;
TEX.eqnStack.Def(name,value,"macros",this.stack.env.isGlobal);
delete this.stack.env.isGlobal;
},
setEnv: function (name,value) {
value.isUser = true;
TEX.eqnStack.Def(name,value,"environments")
},
//
// Implement \global (for \global\let, \global\def and \global\newcommand)
//
Global: function (name) {
var i = this.i; var cs = this.GetCSname(name); this.i = i;
if (cs !== "let" && cs !== "def" && cs !== "newcommand")
{TEX.Error(name+" not followed by \\let, \\def, or \\newcommand")}
this.stack.env.isGlobal = true;
}
});
});
MathJax.Hub.Startup.signal.Post("TeX begingroup Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/begingroup.js");

View File

@ -0,0 +1,126 @@
/*************************************************************
*
* MathJax/extensions/TeX/boldsymbol.js
*
* Implements the \boldsymbol{...} command to make bold
* versions of all math characters (not just variables).
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2009-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/boldsymbol"] = {
version: "2.0"
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var MML = MathJax.ElementJax.mml;
var TEX = MathJax.InputJax.TeX;
var TEXDEF = TEX.Definitions;
var BOLDVARIANT = {};
BOLDVARIANT[MML.VARIANT.NORMAL] = MML.VARIANT.BOLD;
BOLDVARIANT[MML.VARIANT.ITALIC] = MML.VARIANT.BOLDITALIC;
BOLDVARIANT[MML.VARIANT.FRAKTUR] = MML.VARIANT.BOLDFRAKTUR;
BOLDVARIANT[MML.VARIANT.SCRIPT] = MML.VARIANT.BOLDSCRIPT;
BOLDVARIANT[MML.VARIANT.SANSSERIF] = MML.VARIANT.BOLDSANSSERIF;
BOLDVARIANT["-tex-caligraphic"] = "-tex-caligraphic-bold";
BOLDVARIANT["-tex-oldstyle"] = "-tex-oldstyle-bold";
TEXDEF.macros.boldsymbol = 'Boldsymbol';
TEX.Parse.Augment({
mmlToken: function (token) {
if (this.stack.env.boldsymbol) {
var variant = token.Get("mathvariant");
if (variant == null) {token.mathvariant = MML.VARIANT.BOLD}
else {token.mathvariant = (BOLDVARIANT[variant]||variant)}
}
return token;
},
Boldsymbol: function (name) {
var boldsymbol = this.stack.env.boldsymbol,
font = this.stack.env.font;
this.stack.env.boldsymbol = true;
this.stack.env.font = null;
var mml = this.ParseArg(name);
this.stack.env.font = font;
this.stack.env.boldsymbol = boldsymbol;
this.Push(mml);
}
});
MathJax.Hub.Startup.signal.Post("TeX boldsymbol Ready");
});
MathJax.Hub.Register.StartupHook("HTML-CSS Jax Ready",function () {
var HTMLCSS = MathJax.OutputJax["HTML-CSS"];
var FONTS = HTMLCSS.FONTDATA.FONTS;
var VARIANT = HTMLCSS.FONTDATA.VARIANT;
if (HTMLCSS.fontInUse === "TeX") {
FONTS["MathJax_Caligraphic-bold"] = "Caligraphic/Bold/Main.js";
VARIANT["-tex-caligraphic-bold"] =
{fonts:["MathJax_Caligraphic-bold","MathJax_Main-bold","MathJax_Main","MathJax_Math","MathJax_Size1"],
offsetA: 0x41, variantA: "bold-italic"};
VARIANT["-tex-oldstyle-bold"] =
{fonts:["MathJax_Caligraphic-bold","MathJax_Main-bold","MathJax_Main","MathJax_Math","MathJax_Size1"]};
if (HTMLCSS.msieCheckGreek && HTMLCSS.Font.testFont({
family:"MathJax_Greek", weight:"bold", style:"italic", testString: HTMLCSS.msieCheckGreek
})) {
VARIANT["bold-italic"].offsetG = 0x391; VARIANT["bold-italic"].variantG = "-Greek-Bold-Italic";
VARIANT["-Greek-Bold-Italic"] = {fonts:["MathJax_Greek-bold-italic"]};
FONTS["MathJax_Greek-bold-italic"] = "Greek/BoldItalic/Main.js";
}
if (MathJax.Hub.Browser.isChrome && !MathJax.Hub.Browser.versionAtLeast("5.0")) {
VARIANT["-tex-caligraphic-bold"].remap = {0x54: [0xE2F0,"-WinChrome"]};
}
} else if (HTMLCSS.fontInUse === "STIX") {
VARIANT["-tex-caligraphic-bold"] = {
fonts:["STIXGeneral-bold-italic","STIXNonUnicode-bold-italic","STIXNonUnicode","STIXGeneral","STIXSizeOneSym"],
offsetA: 0xE247, noLowerCase: 1
};
VARIANT["-tex-oldstyle-bold"] = {
fonts:["STIXGeneral-bold","STIXNonUnicode-bold","STIXGeneral","STIXSizeOneSym"], offsetN: 0xE263,
remap: {0xE264: 0xE267, 0xE265: 0xE26B, 0xE266: 0xE26F, 0xE267: 0xE273,
0xE268: 0xE277, 0xE269: 0xE27B, 0xE26A: 0xE27F, 0xE26B: 0xE283,
0xE26C: 0xE287}
};
}
});
MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () {
var SVG = MathJax.OutputJax.SVG;
var FONTS = SVG.FONTDATA.FONTS;
var VARIANT = SVG.FONTDATA.VARIANT;
FONTS["MathJax_Caligraphic-bold"] = "Caligraphic/Bold/Main.js";
VARIANT["-tex-caligraphic-bold"] =
{fonts:["MathJax_Caligraphic-bold","MathJax_Main-bold","MathJax_Main","MathJax_Math","MathJax_Size1"],
offsetA: 0x41, variantA: "bold-italic"};
VARIANT["-tex-oldstyle-bold"] =
{fonts:["MathJax_Caligraphic-bold","MathJax_Main-bold","MathJax_Main","MathJax_Math","MathJax_Size1"]};
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/boldsymbol.js");

View File

@ -0,0 +1,105 @@
/*************************************************************
*
* MathJax/extensions/TeX/cancel.js
*
* Implements the \cancel, \bcancel, \xcancel, and \cancelto macros.
*
* Usage:
*
* \cancel{math} % strikeout math from lower left to upper right
* \bcancel{math} % strikeout from upper left to lower right
* \xcancel{math} % strikeout with an X
* \cancelto{value}{math} % strikeout with arrow going to value
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2011-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/cancel"] = {
version: "2.0",
//
// The attributes allowed in \enclose{notation}[attributes]{math}
//
ALLOWED: {
arrow: 1,
color: 1, mathcolor: 1,
background: 1, mathbackground: 1,
padding: 1,
thickness: 1
}
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX,
MACROS = TEX.Definitions.macros,
MML = MathJax.ElementJax.mml,
CANCEL = MathJax.Extension["TeX/cancel"];
CANCEL.setAttributes = function (def,attr) {
if (attr !== "") {
attr = attr.replace(/ /g,"").split(/,/);
for (var i = 0, m = attr.length; i < m; i++) {
var keyvalue = attr[i].split(/[:=]/);
if (CANCEL.ALLOWED[keyvalue[0]]) {
if (keyvalue[1] === "true") {keyvalue[1] = true}
if (keyvalue[1] === "false") {keyvalue[1] = false}
def[keyvalue[0]] = keyvalue[1];
}
}
}
return def;
};
//
// Set up macro
//
MACROS.cancel = ['Cancel',MML.NOTATION.UPDIAGONALSTRIKE];
MACROS.bcancel = ['Cancel',MML.NOTATION.DOWNDIAGONALSTRIKE];
MACROS.xcancel = ['Cancel',MML.NOTATION.UPDIAGONALSTRIKE+" "+MML.NOTATION.DOWNDIAGONALSTRIKE];
MACROS.cancelto = 'CancelTo';
TEX.Parse.Augment({
//
// Implement \cancel[attributes]{math},
// \bcancel[attributes]{math}, and
// \xcancel[attributes]{math}
//
Cancel: function(name,notation) {
var attr = this.GetBrackets(name,""), math = this.ParseArg(name);
var def = CANCEL.setAttributes({notation: notation},attr);
this.Push(MML.menclose(math).With(def));
},
//
// Implement \cancelto{value}[attributes]{math}
//
CancelTo: function(name,notation) {
var value = this.ParseArg(name),
attr = this.GetBrackets(name,""),
math = this.ParseArg(name);
var def = CANCEL.setAttributes({notation: MML.NOTATION.UPDIAGONALSTRIKE, arrow:true},attr);
value = MML.mpadded(value).With({depth:"-.1em",height:"+.1em",voffset:".1em"});
this.Push(MML.msup(MML.menclose(math).With(def),value));
}
});
MathJax.Hub.Startup.signal.Post("TeX cancel Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/cancel.js");

View File

@ -0,0 +1,229 @@
/*************************************************************
*
* MathJax/extensions/TeX/color.js
*
* Implements LaTeX-compatible \color macro rather than MathJax's
* original (non-standard) version. It includes the rgb, gray, and
* named color models, and the \definecolor macro.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2011-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// The configuration defaults, augmented by the user settings
//
MathJax.Extension["TeX/color"] = {
version: "2.0",
config: MathJax.Hub.CombineConfig("TeX.color",{
padding: "5px",
border: "2px"
}),
colors: {
Apricot: "#FBB982",
Aquamarine: "#00B5BE",
Bittersweet: "#C04F17",
Black: "#221E1F",
Blue: "#2D2F92",
BlueGreen: "#00B3B8",
BlueViolet: "#473992",
BrickRed: "#B6321C",
Brown: "#792500",
BurntOrange: "#F7921D",
CadetBlue: "#74729A",
CarnationPink: "#F282B4",
Cerulean: "#00A2E3",
CornflowerBlue: "#41B0E4",
Cyan: "#00AEEF",
Dandelion: "#FDBC42",
DarkOrchid: "#A4538A",
Emerald: "#00A99D",
ForestGreen: "#009B55",
Fuchsia: "#8C368C",
Goldenrod: "#FFDF42",
Gray: "#949698",
Green: "#00A64F",
GreenYellow: "#DFE674",
JungleGreen: "#00A99A",
Lavender: "#F49EC4",
LimeGreen: "#8DC73E",
Magenta: "#EC008C",
Mahogany: "#A9341F",
Maroon: "#AF3235",
Melon: "#F89E7B",
MidnightBlue: "#006795",
Mulberry: "#A93C93",
NavyBlue: "#006EB8",
OliveGreen: "#3C8031",
Orange: "#F58137",
OrangeRed: "#ED135A",
Orchid: "#AF72B0",
Peach: "#F7965A",
Periwinkle: "#7977B8",
PineGreen: "#008B72",
Plum: "#92268F",
ProcessBlue: "#00B0F0",
Purple: "#99479B",
RawSienna: "#974006",
Red: "#ED1B23",
RedOrange: "#F26035",
RedViolet: "#A1246B",
Rhodamine: "#EF559F",
RoyalBlue: "#0071BC",
RoyalPurple: "#613F99",
RubineRed: "#ED017D",
Salmon: "#F69289",
SeaGreen: "#3FBC9D",
Sepia: "#671800",
SkyBlue: "#46C5DD",
SpringGreen: "#C6DC67",
Tan: "#DA9D76",
TealBlue: "#00AEB3",
Thistle: "#D883B7",
Turquoise: "#00B4CE",
Violet: "#58429B",
VioletRed: "#EF58A0",
White: "#FFFFFF",
WildStrawberry: "#EE2967",
Yellow: "#FFF200",
YellowGreen: "#98CC70",
YellowOrange: "#FAA21A"
},
/*
* Look up a color based on its model and definition
*/
getColor: function (model,def) {
if (!model) {model = "named"}
var fn = this["get_"+model];
if (!fn) {this.TEX.Error("Color model '"+model+"' not defined")}
return fn.call(this,def);
},
/*
* Get an RGB color
*/
get_rgb: function (rgb) {
rgb = rgb.split(/,/); var RGB = "#";
if (rgb.length !== 3) {this.TEX.Error("RGB colors require 3 decimal numbers")}
for (var i = 0; i < 3; i++) {
if (!rgb[i].match(/^(\d+(\.\d*)?|\.\d+)$/)) {this.TEX.Error("Invalid decimal number")}
var n = parseFloat(rgb[i]);
if (n < 0 || n > 1) {this.TEX.Error("RGB values must be between 0 and 1")}
n = Math.floor(n*255).toString(16); if (n.length < 2) {n = "0"+n}
RGB += n;
}
return RGB;
},
/*
* Get a gray-scale value
*/
get_gray: function (gray) {
if (!gray.match(/^(\d+(\.\d*)?|\.\d+)$/)) {this.TEX.Error("Invalid decimal number")}
var n = parseFloat(gray);
if (n < 0 || n > 1) {this.TEX.Error("Grey-scale values must be between 0 and 1")}
n = Math.floor(n*255).toString(16); if (n.length < 2) {n = "0"+n}
return "#"+n+n+n;
},
/*
* Get a named value
*/
get_named: function (name) {
if (this.colors[name]) {return this.colors[name]}
return name;
},
padding: function () {
var pad = "+"+this.config.padding;
var unit = this.config.padding.replace(/^.*?([a-z]*)$/,"$1");
var pad2 = "+"+(2*parseFloat(pad))+unit;
return {width:pad2, height:pad, depth:pad, lspace:this.config.padding};
}
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX,
MML = MathJax.ElementJax.mml;
var STACKITEM = TEX.Stack.Item;
var COLOR = MathJax.Extension["TeX/color"];
COLOR.TEX = TEX; // for reference in getColor above
TEX.Definitions.macros.color = "Color";
TEX.Definitions.macros.definecolor = "DefineColor";
TEX.Definitions.macros.colorbox = "ColorBox";
TEX.Definitions.macros.fcolorbox = "fColorBox";
TEX.Parse.Augment({
//
// Override \color macro definition
//
Color: function (name) {
var model = this.GetBrackets(name),
color = this.GetArgument(name);
color = COLOR.getColor(model,color);
var mml = STACKITEM.style().With({styles:{mathcolor:color}});
this.stack.env.color = color;
this.Push(mml);
},
//
// Define the \definecolor macro
//
DefineColor: function (name) {
var cname = this.GetArgument(name),
model = this.GetArgument(name),
def = this.GetArgument(name);
COLOR.colors[cname] = COLOR.getColor(model,def);
},
//
// Produce a text box with a colored background
//
ColorBox: function (name) {
var cname = this.GetArgument(name),
arg = this.InternalMath(this.GetArgument(name));
this.Push(MML.mpadded.apply(MML,arg).With({
mathbackground:COLOR.getColor("named",cname)
}).With(COLOR.padding()));
},
//
// Procude a framed text box with a colored background
//
fColorBox: function (name) {
var fname = this.GetArgument(name),
cname = this.GetArgument(name),
arg = this.InternalMath(this.GetArgument(name));
this.Push(MML.mpadded.apply(MML,arg).With({
mathbackground: COLOR.getColor("named",cname),
style: "border: "+COLOR.config.border+" solid "+COLOR.getColor("named",fname)
}).With(COLOR.padding()));
}
});
MathJax.Hub.Startup.signal.Post("TeX color Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/color.js");

View File

@ -0,0 +1,86 @@
/*************************************************************
*
* MathJax/extensions/TeX/enclose.js
*
* Implements the \enclose macros, which give access from TeX to the
* <menclose> tag in the MathML that underlies MathJax's internal format.
*
* Usage:
*
* \enclose{notation}{math} % enclose math using given notation
* \enclose{notation,notation,...}{math} % enclose with several notations
* \enclose{notation}[attributes]{math} % enclose with attributes
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2011-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/enclose"] = {
version: "2.0",
//
// The attributes allowed in \enclose{notation}[attributes]{math}
//
ALLOWED: {
arrow: 1,
color: 1, mathcolor: 1,
background: 1, mathbackground: 1,
padding: 1,
thickness: 1
}
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX,
MML = MathJax.ElementJax.mml,
ALLOW = MathJax.Extension["TeX/enclose"].ALLOWED;
//
// Set up macro
//
TEX.Definitions.macros.enclose = 'Enclose';
TEX.Parse.Augment({
//
// Implement \enclose{notation}[attr]{math}
// (create <menclose notation="notation">math</menclose>)
//
Enclose: function(name) {
var notation = this.GetArgument(name),
attr = this.GetBrackets(name),
math = this.ParseArg(name);
var def = {notation: notation.replace(/,/g," ")};
if (attr) {
attr = attr.replace(/ /g,"").split(/,/);
for (var i = 0, m = attr.length; i < m; i++) {
var keyvalue = attr[i].split(/[:=]/);
if (ALLOW[keyvalue[0]]) {
keyvalue[1] = keyvalue[1].replace(/^"(.*)"$/,"$1");
if (keyvalue[1] === "true") {keyvalue[1] = true}
if (keyvalue[1] === "false") {keyvalue[1] = false}
def[keyvalue[0]] = keyvalue[1];
}
}
}
this.Push(MML.menclose(math).With(def));
}
});
MathJax.Hub.Startup.signal.Post("TeX enclose Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/enclose.js");

View File

@ -0,0 +1,87 @@
/*************************************************************
*
* MathJax/extensions/TeX/extpfeil.js
*
* Implements additional stretchy arrow macros.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2011-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/extpfeil"] = {
version: "2.0"
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX,
TEXDEF = TEX.Definitions;
//
// Define the arrows to load the AMSmath extension
// (since they need its xArrow method)
//
MathJax.Hub.Insert(TEXDEF,{
macros: {
xtwoheadrightarrow: ['Extension','AMSmath'],
xtwoheadleftarrow: ['Extension','AMSmath'],
xmapsto: ['Extension','AMSmath'],
xlongequal: ['Extension','AMSmath'],
xtofrom: ['Extension','AMSmath'],
Newextarrow: ['Extension','AMSmath']
}
});
//
// Redefine the macros when AMSmath is loaded
//
MathJax.Hub.Register.StartupHook("TeX AMSmath Ready",function () {
MathJax.Hub.Insert(TEXDEF,{
macros: {
xtwoheadrightarrow: ['xArrow',0x21A0,12,16],
xtwoheadleftarrow: ['xArrow',0x219E,17,13],
xmapsto: ['xArrow',0x21A6,6,7],
xlongequal: ['xArrow',0x003D,7,7],
xtofrom: ['xArrow',0x21C4,12,12],
Newextarrow: 'NewExtArrow'
}
});
});
//
// Implements \Newextarrow to define a new arrow (not compatible with \newextarrow, but
// the equivalent for MathJax)
//
TEX.Parse.Augment({
NewExtArrow: function (name) {
var cs = this.GetArgument(name),
space = this.GetArgument(name),
chr = this.GetArgument(name);
if (!cs.match(/^\\([a-z]+|.)$/i))
{TEX.Error("First argument to "+name+" must be a control sequence name")}
if (!space.match(/^(\d+),(\d+)$/))
{TEX.Error("Second argument to "+name+" must be two integers separated by a comma")}
if (!chr.match(/^(\d+|0x[0-9A-F]+)$/i))
{TEX.Error("Third argument to "+name+" must be a unicode character number")}
cs = cs.substr(1); space = space.split(","); chr = parseInt(chr);
TEXDEF.macros[cs] = ['xArrow',chr,parseInt(space[0]),parseInt(space[1])];
}
});
MathJax.Hub.Startup.signal.Post("TeX extpfeil Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/extpfeil.js");

View File

@ -0,0 +1,78 @@
/*************************************************************
*
* MathJax/extensions/TeX/mathchoice.js
*
* Implements the \mathchoice macro (rarely used)
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2009-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var VERSION = "2.0";
var MML = MathJax.ElementJax.mml;
var TEX = MathJax.InputJax.TeX;
var TEXDEF = TEX.Definitions;
TEXDEF.macros.mathchoice = 'MathChoice';
TEX.Parse.Augment({
MathChoice: function (name) {
var D = this.ParseArg(name),
T = this.ParseArg(name),
S = this.ParseArg(name),
SS = this.ParseArg(name);
this.Push(MML.TeXmathchoice(D,T,S,SS));
}
});
MML.TeXmathchoice = MML.mbase.Subclass({
type: "TeXmathchoice",
choice: function () {
var values = this.getValues("displaystyle","scriptlevel");
if (values.scriptlevel > 0) {return Math.min(3,values.scriptlevel + 1)}
return (values.displaystyle ? 0 : 1);
},
setTeXclass: function (prev) {return this.Core().setTeXclass(prev)},
isSpacelike: function () {return this.Core().isSpacelike()},
isEmbellished: function () {return this.Core().isEmbellished()},
Core: function () {return this.data[this.choice()]},
toHTML: function (span) {
span = this.HTMLcreateSpan(span);
span.bbox = this.Core().toHTML(span).bbox;
// Firefox doesn't correctly handle a span with a negatively sized content,
// so move marginLeft to main span (this is a hack to get \iiiint to work).
// FIXME: This is a symptom of a more general problem with Firefox, and
// there probably needs to be a more general solution (e.g., modifying
// HTMLhandleSpace() to get the width and adjust the right margin to
// compensate for negative-width contents)
if (span.firstChild && span.firstChild.style.marginLeft) {
span.style.marginLeft = span.firstChild.style.marginLeft;
span.firstChild.style.marginLeft = "";
}
return span;
},
toSVG: function () {return this.Core().toSVG()}
});
MathJax.Hub.Startup.signal.Post("TeX mathchoice Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/mathchoice.js");

View File

@ -0,0 +1,413 @@
/*************************************************************
*
* MathJax/extensions/TeX/mhchem.js
*
* Implements the \ce command for handling chemical formulas
* from the mhchem LaTeX package.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2011-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/mhchem"] = {
version: "2.0"
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX,
MACROS = TEX.Definitions.macros;
/*
* This is the main class for handing the \ce and related commands.
* Its main method is Parse() which takes the argument to \ce and
* returns the corresponding TeX string.
*/
var CE = MathJax.Object.Subclass({
string: "", // the \ce string being parsed
i: 0, // the current position in the string
tex: "", // the processed TeX result
atom: false, // last processed token is an atom
sup: "", // pending superscript
sub: "", // pending subscript
//
// Store the string when a CE object is created
//
Init: function (string) {this.string = string},
//
// These are the special characters and the methods that
// handle them. All others are passed through verbatim.
//
ParseTable: {
'-': "Minus",
'+': "Plus",
'(': "Open",
')': "Close",
'[': "Open",
']': "Close",
'<': "Less",
'^': "Superscript",
'_': "Subscript",
'*': "Dot",
'.': "Dot",
'=': "Equal",
'#': "Pound",
'$': "Math",
'\\': "Macro",
' ': "Space"
},
//
// Basic arrow names for reactions
//
Arrows: {
'->': "rightarrow",
'<-': "leftarrow",
'<->': "leftrightarrow",
'<=>': "rightleftharpoons",
'<=>>': "Rightleftharpoons",
'^': "uparrow",
'v': "downarrow"
},
//
// Implementations for the various bonds
// (the ~ ones are hacks that don't work well in NativeMML)
//
Bonds: {
'-': "-",
'=': "=",
'#': "\\equiv",
'~': "\\tripledash",
'~-': "\\begin{CEstack}{}\\tripledash\\\\-\\end{CEstack}",
'~=': "\\raise2mu{\\begin{CEstack}{}\\tripledash\\\\-\\\\-\\end{CEstack}}",
'~--': "\\raise2mu{\\begin{CEstack}{}\\tripledash\\\\-\\\\-\\end{CEstack}}",
'-~-': "\\raise2mu{\\begin{CEstack}{}-\\\\\\tripledash\\\\-\\end{CEstack}}",
'...': "{\\cdot}{\\cdot}{\\cdot}",
'....': "{\\cdot}{\\cdot}{\\cdot}{\\cdot}",
'->': "\\rightarrow",
'<-': "\\leftarrow",
'??': "\\text{??}" // unknown bond
},
//
// This converts the CE string to a TeX string.
// It loops through the string and calls the proper
// method depending on the ccurrent character.
//
Parse: function () {
this.tex = ""; this.atom = false;
while (this.i < this.string.length) {
var c = this.string.charAt(this.i);
if (c.match(/[a-z]/i)) {this.ParseLetter()}
else if (c.match(/[0-9]/)) {this.ParseNumber()}
else {this["Parse"+(this.ParseTable[c]||"Other")](c)}
}
this.FinishAtom();
return this.tex;
},
//
// Make an atom name or a down arrow
//
ParseLetter: function () {
this.FinishAtom();
if (this.Match(/^v( |$)/)) {
this.tex += "{\\"+this.Arrows["v"]+"}";
} else {
this.tex += "\\text{"+this.Match(/^[a-z]+/i)+"}";
this.atom = true;
}
},
//
// Make a number of fraction preceeding an atom,
// or a subscript for an atom.
//
ParseNumber: function () {
var n = this.Match(/^\d+/);
if (this.atom && !this.sub) {
this.sub = n;
} else {
this.FinishAtom();
var match = this.Match(/^\/\d+/);
if (match) {
var frac = "\\frac{"+n+"}{"+match.substr(1)+"}";
this.tex += "\\mathchoice{\\textstyle"+frac+"}{"+frac+"}{"+frac+"}{"+frac+"}";
} else {
this.tex += n;
if (this.i < this.string.length) {this.tex += "\\,"}
}
}
},
//
// Make a superscript minus, or an arrow, or a single bond.
//
ParseMinus: function (c) {
if (this.atom && (this.i === this.string.length-1 || this.string.charAt(this.i+1) === " ")) {
this.sup += c;
} else {
this.FinishAtom();
if (this.string.substr(this.i,2) === "->") {this.i += 2; this.AddArrow("->"); return}
else {this.tex += "{-}"}
}
this.i++;
},
//
// Make a superscript plus, or pass it through
//
ParsePlus: function (c) {
if (this.atom) {this.sup += c} else {this.FinishAtom(); this.tex += c}
this.i++;
},
//
// Handle dots and double or triple bonds
//
ParseDot: function (c) {this.FinishAtom(); this.tex += "\\cdot "; this.i++},
ParseEqual: function (c) {this.FinishAtom(); this.tex += "{=}"; this.i++},
ParsePound: function (c) {this.FinishAtom(); this.tex += "{\\equiv}"; this.i++},
//
// Look for (v) or (^), or pass it through
//
ParseOpen: function (c) {
this.FinishAtom();
var match = this.Match(/^\([v^]\)/);
if (match) {this.tex += "{\\"+this.Arrows[match.charAt(1)]+"}"}
else {this.tex += "{"+c; this.i++}
},
//
// Allow ) and ] to get super- and subscripts
//
ParseClose: function (c) {this.FinishAtom(); this.atom = true; this.tex += c+"}"; this.i++},
//
// Make the proper arrow
//
ParseLess: function (c) {
this.FinishAtom();
var arrow = this.Match(/^(<->?|<=>>?)/);
if (!arrow) {this.tex += c; this.i++} else {this.AddArrow(arrow)}
},
//
// Look for a superscript, or an up arrow
//
ParseSuperscript: function (c) {
c = this.string.charAt(++this.i);
if (c === "{") {
this.i++; var m = this.Find("}");
if (m === "-.") {this.sup += "{-}{\\cdot}"} else if (m) {this.sup += CE(m).Parse()}
} else if (c === " " || c === "") {
this.tex += "{\\"+this.Arrows["^"]+"}"; this.i++;
} else {
var n = this.Match(/^(\d+|-\.)/);
if (n) {this.sup += n}
}
},
//
// Look for subscripts
//
ParseSubscript: function (c) {
if (this.string.charAt(++this.i) == "{") {
this.i++; this.sub += CE(this.Find("}")).Parse();
} else {
var n = this.Match(/^\d+/);
if (n) {this.sub += n}
}
},
//
// Look for raw TeX code to include
//
ParseMath: function (c) {
this.FinishAtom();
this.i++; this.tex += this.Find(c);
},
//
// Look for specific macros for bonds
// and allow \} to have subscripts
//
ParseMacro: function (c) {
this.FinishAtom();
this.i++; var match = this.Match(/^([a-z]+|.)/i)||" ";
if (match === "sbond") {this.tex += "{-}"}
else if (match === "dbond") {this.tex += "{=}"}
else if (match === "tbond") {this.tex += "{\\equiv}"}
else if (match === "bond") {
var bond = (this.Match(/^\{.*?\}/)||"");
bond = bond.substr(1,bond.length-2);
this.tex += "{"+(this.Bonds[bond]||"\\text{??}")+"}";
}
else if (match === "{") {this.tex += "{\\{"}
else if (match === "}") {this.tex += "\\}}"; this.atom = true}
else {this.tex += c+match}
},
//
// Ignore spaces
//
ParseSpace: function (c) {this.FinishAtom(); this.i++},
//
// Pass anything else on verbatim
//
ParseOther: function (c) {this.FinishAtom(); this.tex += c; this.i++},
//
// Process an arrow (looking for brackets for above and below)
//
AddArrow: function (arrow) {
var c = this.Match(/^[CT]\[/);
if (c) {this.i--; c = c.charAt(0)}
var above = this.GetBracket(c), below = this.GetBracket(c);
arrow = this.Arrows[arrow];
if (above || below) {
if (below) {arrow += "["+below+"]"}
arrow += "{"+above+"}";
arrow = "\\mathrel{\\x"+arrow+"}";
} else {
arrow = "\\long"+arrow+" ";
}
this.tex += arrow;
},
//
// Handle the super and subscripts for an atom
//
FinishAtom: function () {
if (this.sup || this.sub) {
if (this.sup && this.sub && !this.atom) {
//
// right-justify super- and subscripts when they are before the atom
//
var n = Math.abs(this.sup.length-this.sub.length);
if (n) {
var zeros = "0000000000".substr(0,n);
var script = (this.sup.length > this.sub.length ? "sub" : "sup");
this[script] = "\\phantom{"+zeros+"}" + this[script];
}
}
if (!this.sup) {this.sup = "\\Space{0pt}{0pt}{.2em}"} // forces subscripts to align properly
this.tex += "^{"+this.sup+"}_{"+this.sub+"}";
this.sup = this.sub = "";
}
this.atom = false;
},
//
// Find a bracket group and handle C and T prefixes
//
GetBracket: function (c) {
if (this.string.charAt(this.i) !== "[") {return ""}
this.i++; var bracket = this.Find("]");
if (c === "C") {bracket = "\\ce{"+bracket+"}"} else
if (c === "T") {
if (!bracket.match(/^\{.*\}$/)) {bracket = "{"+bracket+"}"}
bracket = "\\text"+bracket;
};
return bracket;
},
//
// Check if the string matches a regular expression
// and move past it if so, returning the match
//
Match: function (regex) {
var match = regex.exec(this.string.substr(this.i));
if (match) {match = match[0]; this.i += match.length}
return match;
},
//
// Find a particular character, skipping over braced groups
//
Find: function (c) {
var m = this.string.length, i = this.i, braces = 0;
while (this.i < m) {
var C = this.string.charAt(this.i++);
if (C === c && braces === 0) {return this.string.substr(i,this.i-i-1)}
if (C === "{") {braces++} else
if (C === "}") {
if (braces) {braces--}
else {TEX.Error("Extra close brace or missing open brace")}
}
}
if (braces) {TEX.Error("Missing close brace")};
TEX.Error("Can't find closing "+c);
}
});
/***************************************************************************/
//
// Set up the macros for chemistry
//
MACROS.ce = 'CE';
MACROS.cf = 'CE';
MACROS.cee = 'CE';
//
// Include some missing arrows (some are hacks)
//
MACROS.xleftrightarrow = ['xArrow',0x2194,6,6];
MACROS.xrightleftharpoons = ['xArrow',0x21CC,5,7]; // FIXME: doesn't stretch in HTML-CSS output
MACROS.xRightleftharpoons = ['xArrow',0x21CC,5,7]; // FIXME: how should this be handled?
// FIXME: These don't work well in FF NativeMML mode
MACROS.longrightleftharpoons = ["Macro","\\stackrel{\\textstyle{{-}\\!\\!{\\rightharpoonup}}}{\\smash{{\\leftharpoondown}\\!\\!{-}}}"];
MACROS.longRightleftharpoons = ["Macro","\\stackrel{\\textstyle{-}\\!\\!{\\rightharpoonup}}{\\small\\smash\\leftharpoondown}"];
//
// Needed for \bond for the ~ forms
//
MACROS.tripledash = ["Macro","\\raise3mu{\\tiny\\text{-}\\kern2mu\\text{-}\\kern2mu\\text{-}}"];
TEX.Definitions.environment.CEstack = ['Array',null,null,null,'r',null,"0.001em",'T',1]
//
// Add \hyphen used in some mhchem examples
//
MACROS.hyphen = ["Macro","\\text{-}"];
TEX.Parse.Augment({
//
// Implements \ce and friends
//
CE: function (name) {
var arg = this.GetArgument(name);
var tex = CE(arg).Parse();
this.string = tex + this.string.substr(this.i); this.i = 0;
}
});
//
// Indicate that the extension is ready
//
MathJax.Hub.Startup.signal.Post("TeX mhchem Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/mhchem.js");

View File

@ -0,0 +1,237 @@
/*************************************************************
*
* MathJax/extensions/TeX/newcommand.js
*
* Implements the \newcommand, \newenvironment and \def
* macros, and is loaded automatically when needed.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2009-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/newcommand"] = {
version: "2.0"
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX;
var TEXDEF = TEX.Definitions;
TEXDEF.Add({
macros: {
newcommand: 'NewCommand',
renewcommand: 'NewCommand',
newenvironment: 'NewEnvironment',
renewenvironment: 'NewEnvironment',
def: 'MacroDef',
let: 'Let'
}
},null,true);
TEX.Parse.Augment({
/*
* Implement \newcommand{\name}[n]{...}
*/
NewCommand: function (name) {
var cs = this.trimSpaces(this.GetArgument(name)),
n = this.GetBrackets(name),
opt = this.GetBrackets(name),
def = this.GetArgument(name);
if (cs.charAt(0) === "\\") {cs = cs.substr(1)}
if (!cs.match(/^(.|[a-z]+)$/i)) {TEX.Error("Illegal control sequence name for "+name)}
if (n) {
n = this.trimSpaces(n);
if (!n.match(/^[0-9]+$/)) {TEX.Error("Illegal number of parameters specified in "+name)}
}
this.setDef(cs,['Macro',def,n,opt]);
},
/*
* Implement \newenvironment{name}[n][default]{begincmd}{endcmd}
*/
NewEnvironment: function (name) {
var env = this.trimSpaces(this.GetArgument(name)),
n = this.GetBrackets(name),
bdef = this.GetArgument(name),
edef = this.GetArgument(name);
if (n) {
n = this.trimSpaces(n);
if (!n.match(/^[0-9]+$/)) {TEX.Error("Illegal number of parameters specified in "+name)}
}
this.setEnv(env,['BeginEnv','EndEnv',bdef,edef,n]);
},
/*
* Implement \def command
*/
MacroDef: function (name) {
var cs = this.GetCSname(name),
params = this.GetTemplate(name,"\\"+cs),
def = this.GetArgument(name);
if (!(params instanceof Array)) {this.setDef(cs,['Macro',def,params])}
else {this.setDef(cs,['MacroWithTemplate',def].concat(params))}
},
/*
* Implements the \let command
*/
Let: function (name) {
var cs = this.GetCSname(name), macro;
var c = this.GetNext(); if (c === "=") {this.i++; c = this.GetNext()}
//
// All \let commands create entries in the macros array, but we
// have to look in the various mathchar and delimiter arrays if
// the source isn't a macro already, and attach the data to a
// macro with the proper routine to process it.
//
// A command of the form \let\cs=char produces a macro equivalent
// to \def\cs{char}, which is as close as MathJax can get for this.
// So \let\bgroup={ is possible, but doesn't work as it does in TeX.
//
if (c === "\\") {
name = this.GetCSname(name);
macro = this.csFindMacro(name);
if (!macro) {
if (TEXDEF.mathchar0mi[name]) {macro = ["csMathchar0mi",TEXDEF.mathchar0mi[name]]} else
if (TEXDEF.mathchar0mo[name]) {macro = ["csMathchar0mo",TEXDEF.mathchar0mo[name]]} else
if (TEXDEF.mathchar7[name]) {macro = ["csMathchar7",TEXDEF.mathchar7[name]]} else
if (TEXDEF.delimiter["\\"+name] != null) {macro = ["csDelimiter",TEXDEF.delimiter["\\"+name]]}
}
} else {macro = ["Macro",c]; this.i++}
this.setDef(cs,macro);
},
/*
* Routines to set the macro and environment definitions
* (overridden by begingroup to make localized versions)
*/
setDef: function (name,value) {value.isUser = true; TEXDEF.macros[name] = value},
setEnv: function (name,value) {value.isUser = true; TEXDEF.environment[name] = value},
/*
* Get a CS name or give an error
*/
GetCSname: function (cmd) {
var c = this.GetNext();
if (c !== "\\") {TEX.Error("\\ must be followed by a control sequence")}
var cs = this.trimSpaces(this.GetArgument(cmd));
return cs.substr(1);
},
/*
* Get a \def parameter template
*/
GetTemplate: function (cmd,cs) {
var c, params = [], n = 0;
c = this.GetNext(); var i = this.i;
while (this.i < this.string.length) {
c = this.GetNext();
if (c === '#') {
if (i !== this.i) {params[n] = this.string.substr(i,this.i-i)}
c = this.string.charAt(++this.i);
if (!c.match(/^[1-9]$/)) {TEX.Error("Illegal use of # in template for "+cs)}
if (parseInt(c) != ++n) {TEX.Error("Parameters for "+cs+" must be numbered sequentially")}
i = this.i+1;
} else if (c === '{') {
if (i !== this.i) {params[n] = this.string.substr(i,this.i-i)}
if (params.length > 0) {return [n,params]} else {return n}
}
this.i++;
}
TEX.Error("Missing replacement string for definition of "+cmd);
},
/*
* Process a macro with a parameter template
*/
MacroWithTemplate: function (name,text,n,params) {
if (n) {
var args = []; this.GetNext();
if (params[0] && !this.MatchParam(params[0]))
{TEX.Error("Use of "+name+" doesn't match its definition")}
for (var i = 0; i < n; i++) {args.push(this.GetParameter(name,params[i+1]))}
text = this.SubstituteArgs(args,text);
}
this.string = this.AddArgs(text,this.string.slice(this.i));
this.i = 0;
if (++this.macroCount > TEX.config.MAXMACROS)
{TEX.Error("MathJax maximum macro substitution count exceeded; is there a recursive macro call?")}
},
/*
* Process a user-defined environment
*/
BeginEnv: function (begin,bdef,edef,n) {
if (n) {
var args = [];
for (var i = 0; i < n; i++) {args.push(this.GetArgument("\\begin{"+name+"}"))}
bdef = this.SubstituteArgs(args,bdef);
edef = this.SubstituteArgs(args,edef);
}
begin.edef = edef;
this.string = this.AddArgs(bdef,this.string.slice(this.i)); this.i = 0;
return begin;
},
EndEnv: function (begin,row) {
this.string = this.AddArgs(begin.edef,this.string.slice(this.i)); this.i = 0
return row;
},
/*
* Find a single parameter delimited by a trailing template
*/
GetParameter: function (name,param) {
if (param == null) {return this.GetArgument(name)}
var i = this.i, j = 0, hasBraces = 0;
while (this.i < this.string.length) {
if (this.string.charAt(this.i) === '{') {
if (this.i === i) {hasBraces = 1}
this.GetArgument(name); j = this.i - i;
} else if (this.MatchParam(param)) {
if (hasBraces) {i++; j -= 2}
return this.string.substr(i,j);
} else {
this.i++; j++; hasBraces = 0;
}
}
TEX.Error("Runaway argument for "+name+"?");
},
/*
* Check if a template is at the current location.
* (The match must be exact, with no spacing differences. TeX is
* a little more forgiving than this about spaces after macro names)
*/
MatchParam: function (param) {
if (this.string.substr(this.i,param.length) !== param) {return 0}
this.i += param.length;
return 1;
}
});
TEX.Environment = function (name) {
TEXDEF.environment[name] = ['BeginEnv','EndEnv'].concat([].slice.call(arguments,1));
TEXDEF.environment[name].isUser = true;
}
MathJax.Hub.Startup.signal.Post("TeX newcommand Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/newcommand.js");

View File

@ -0,0 +1,306 @@
/*************************************************************
*
* MathJax/extensions/TeX/noErrors.js
*
* Prevents the TeX error messages from being displayed and shows the
* original TeX code instead. You can configure whether the dollar signs
* are shown or not for in-line math, and whether to put all the TeX on
* one line or use multiple-lines.
*
* To configure this extension, use
*
* MathJax.Hub.Config({
* TeX: {
* noErrors: {
* inlineDelimiters: ["",""], // or ["$","$"] or ["\\(","\\)"]
* multiLine: true, // false for TeX on all one line
* style: {
* "font-size": "90%",
* "text-align": "left",
* "color": "black",
* "padding": "1px 3px",
* "border": "1px solid"
* // add any additional CSS styles that you want
* // (be sure there is no extra comma at the end of the last item)
* }
* }
* }
* });
*
* Display-style math is always shown in multi-line format, and without
* delimiters, as it will already be set off in its own centered
* paragraph, like standard display mathematics.
*
* The default settings place the invalid TeX in a multi-line box with a
* black border. If you want it to look as though the TeX is just part of
* the paragraph, use
*
* MathJax.Hub.Config({
* TeX: {
* noErrors: {
* inlineDelimiters: ["$","$"], // or ["",""] or ["\\(","\\)"]
* multiLine: false,
* style: {
* "font-size": "normal",
* "border": ""
* }
* }
* }
* });
*
* You may also wish to set the font family, as the default is "serif"
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2009-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (HUB,HTML) {
var VERSION = "2.0";
var CONFIG = HUB.CombineConfig("TeX.noErrors",{
disabled: false, // set to true to return to original error messages
multiLine: true,
inlineDelimiters: ["",""], // or use ["$","$"] or ["\\(","\\)"]
style: {
"font-size": "90%",
"text-align": "left",
"color": "black",
"padding": "1px 3px",
"border": "1px solid"
}
});
var NBSP = "\u00A0";
//
// The configuration defaults, augmented by the user settings
//
MathJax.Extension["TeX/noErrors"] = {
version: VERSION,
config: CONFIG
};
HUB.Register.StartupHook("TeX Jax Ready",function () {
var FORMAT = MathJax.InputJax.TeX.formatError;
MathJax.InputJax.TeX.Augment({
//
// Make error messages be the original TeX code
// Mark them as errors and multi-line or not, and for
// multi-line TeX, make spaces non-breakable (to get formatting right)
//
formatError: function (err,math,displaystyle,script) {
if (CONFIG.disabled) {return FORMAT.apply(this,arguments)}
var message = err.message.replace(/\n.*/,"");
HUB.signal.Post(["TeX Jax - parse error",message,math,displaystyle,script]);
var delim = CONFIG.inlineDelimiters;
var multiLine = (displaystyle || CONFIG.multiLine);
if (!displaystyle) {math = delim[0] + math + delim[1]}
if (multiLine) {math = math.replace(/ /g,NBSP)} else {math = math.replace(/\n/g," ")}
return MathJax.ElementJax.mml.merror(math).With({isError:true, multiLine: multiLine});
}
});
});
/*******************************************************************
*
* Fix HTML-CSS output
*/
HUB.Register.StartupHook("HTML-CSS Jax Config",function () {
HUB.Config({
"HTML-CSS": {
styles: {
".MathJax .noError": HUB.Insert({
"vertical-align": (HUB.Browser.isMSIE && CONFIG.multiLine ? "-2px" : "")
},CONFIG.style)
}
}
});
});
HUB.Register.StartupHook("HTML-CSS Jax Ready",function () {
var MML = MathJax.ElementJax.mml;
var HTMLCSS = MathJax.OutputJax["HTML-CSS"];
var MATH = MML.math.prototype.toHTML,
MERROR = MML.merror.prototype.toHTML;
//
// Override math toHTML routine so that error messages
// don't have the clipping and other unneeded overhead
//
MML.math.Augment({
toHTML: function (span,node) {
var data = this.data[0];
if (data && data.data[0] && data.data[0].isError) {
span.style.fontSize = "";
span = this.HTMLcreateSpan(span);
span.bbox = data.data[0].toHTML(span).bbox;
} else {
span = MATH.call(this,span,node);
}
return span;
}
});
//
// Override merror toHTML routine so that it puts out the
// TeX code in an inline-block with line breaks as in the original
//
MML.merror.Augment({
toHTML: function (span) {
if (!this.isError) {return MERROR.call(this,span)}
span = this.HTMLcreateSpan(span); span.className = "noError"
if (this.multiLine) {span.style.display = "inline-block"}
var text = this.data[0].data[0].data.join("").split(/\n/);
for (var i = 0, m = text.length; i < m; i++) {
HTMLCSS.addText(span,text[i]);
if (i !== m-1) {HTMLCSS.addElement(span,"br",{isMathJax:true})}
}
var HD = HTMLCSS.getHD(span.parentNode), W = HTMLCSS.getW(span.parentNode);
if (m > 1) {
var H = (HD.h + HD.d)/2, x = HTMLCSS.TeX.x_height/2;
span.parentNode.style.verticalAlign = HTMLCSS.Em(HD.d+(x-H));
HD.h = x + H; HD.d = H - x;
}
span.bbox = {h: HD.h, d: HD.d, w: W, lw: 0, rw: W};
return span;
}
});
});
/*******************************************************************
*
* Fix SVG output
*/
HUB.Register.StartupHook("SVG Jax Config",function () {
HUB.Config({
"SVG": {
styles: {
".MathJax_SVG .noError": HUB.Insert({
"vertical-align": (HUB.Browser.isMSIE && CONFIG.multiLine ? "-2px" : "")
},CONFIG.style)
}
}
});
});
HUB.Register.StartupHook("SVG Jax Ready",function () {
var MML = MathJax.ElementJax.mml;
var MATH = MML.math.prototype.toSVG,
MERROR = MML.merror.prototype.toSVG;
//
// Override math toSVG routine so that error messages
// don't have the clipping and other unneeded overhead
//
MML.math.Augment({
toSVG: function (span,node) {
var data = this.data[0];
if (data && data.data[0] && data.data[0].isError)
{span = data.data[0].toSVG(span)} else {span = MATH.call(this,span,node)}
return span;
}
});
//
// Override merror toSVG routine so that it puts out the
// TeX code in an inline-block with line breaks as in the original
//
MML.merror.Augment({
toSVG: function (span) {
if (!this.isError || this.Parent().type !== "math") {return MERROR.call(this,span)}
span = HTML.addElement(span,"span",{className: "noError", isMathJax:true});
if (this.multiLine) {span.style.display = "inline-block"}
var text = this.data[0].data[0].data.join("").split(/\n/);
for (var i = 0, m = text.length; i < m; i++) {
HTML.addText(span,text[i]);
if (i !== m-1) {HTML.addElement(span,"br",{isMathJax:true})}
}
if (m > 1) {
var H = span.offsetHeight/2;
span.style.verticalAlign = (-H+(H/m))+"px";
}
return span;
}
});
});
/*******************************************************************
*
* Fix NativeMML output
*/
HUB.Register.StartupHook("NativeMML Jax Ready",function () {
var MML = MathJax.ElementJax.mml;
var CONFIG = MathJax.Extension["TeX/noErrors"].config;
var MATH = MML.math.prototype.toNativeMML,
MERROR = MML.merror.prototype.toNativeMML;
//
// Override math toNativeMML routine so that error messages
// don't get placed inside math tags.
//
MML.math.Augment({
toNativeMML: function (span) {
var data = this.data[0];
if (data && data.data[0] && data.data[0].isError)
{span = data.data[0].toNativeMML(span)} else {span = MATH.call(this,span)}
return span;
}
});
//
// Override merror toNativeMML routine so that it puts out the
// TeX code in an inline-block with line breaks as in the original
//
MML.merror.Augment({
toNativeMML: function (span) {
if (!this.isError) {return MERROR.call(this,span)}
span = span.appendChild(document.createElement("span"));
var text = this.data[0].data[0].data.join("").split(/\n/);
for (var i = 0, m = text.length; i < m; i++) {
span.appendChild(document.createTextNode(text[i]));
if (i !== m-1) {span.appendChild(document.createElement("br"))}
}
if (this.multiLine) {
span.style.display = "inline-block";
if (m > 1) {span.style.verticalAlign = "middle"}
}
for (var id in CONFIG.style) {if (CONFIG.style.hasOwnProperty(id)) {
var ID = id.replace(/-./g,function (c) {return c.charAt(1).toUpperCase()});
span.style[ID] = CONFIG.style[id];
}}
return span;
}
});
});
/*******************************************************************/
HUB.Startup.signal.Post("TeX noErrors Ready");
})(MathJax.Hub,MathJax.HTML);
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/noErrors.js");

View File

@ -0,0 +1,69 @@
/*************************************************************
*
* MathJax/extensions/TeX/noUndefined.js
*
* This causes undefined control sequences to be shown as their macro
* names rather than producing an error message. So $X_{\xxx}$ would
* display as an X with a subscript consiting of the text "\xxx".
*
* To configure this extension, use for example
*
* MathJax.Hub.Config({
* TeX: {
* noUndefined: {
* attributes: {
* mathcolor: "red",
* mathbackground: "#FFEEEE",
* mathsize: "90%"
* }
* }
* }
* });
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2010-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// The configuration defaults, augmented by the user settings
//
MathJax.Extension["TeX/noUndefined"] = {
version: "2.0",
config: MathJax.Hub.CombineConfig("TeX.noUndefined",{
disabled: false, // set to true to return to original error messages
attributes: {
mathcolor: "red"
}
})
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var CONFIG = MathJax.Extension["TeX/noUndefined"].config;
var MML = MathJax.ElementJax.mml;
var UNDEFINED = MathJax.InputJax.TeX.Parse.prototype.csUndefined;
MathJax.InputJax.TeX.Parse.Augment({
csUndefined: function (name) {
if (CONFIG.disabled) {return UNDEFINED.apply(this,arguments)}
MathJax.Hub.signal.Post(["TeX Jax - undefined control sequence",name]);
this.Push(MML.mtext(name).With(CONFIG.attributes));
}
});
MathJax.Hub.Startup.signal.Post("TeX noUndefined Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/noUndefined.js");

View File

@ -0,0 +1,164 @@
/*************************************************************
*
* MathJax/extensions/TeX/unicode.js
*
* Implements the \unicode extension to TeX to allow arbitrary unicode
* code points to be entered into the TeX file. You can specify
* the height and depth of the character (the width is determined by
* the browser), and the default font from which to take the character.
*
* Examples:
* \unicode{65} % the character 'A'
* \unicode{x41} % the character 'A'
* \unicode[.55,0.05]{x22D6} % less-than with dot, with height .55 and depth 0.05
* \unicode[.55,0.05][Geramond]{x22D6} % same taken from Geramond font
* \unicode[Garamond]{x22D6} % same, but with default height, depth of .8,.2
*
* Once a size and font are provided for a given code point, they need
* not be specified again in subsequent \unicode calls for that character.
* Note that a font list can be given, but Internet Explorer has a buggy
* implementation of font-family where it only looks in the first
* available font and if the glyph is not in that, it does not look at
* later fonts, but goes directly to the default font as set in the
* Internet-Options/Font panel. For this reason, the default font list is
* "STIXGeneral,'Arial Unicode MS'", so if the user has STIX fonts, the
* symbol will be taken from that (almost all the symbols are in
* STIXGeneral), otherwise Arial Unicode MS is tried.
*
* To configure the default font list, use
*
* MathJax.Hub.Config({
* TeX: {
* unicode: {
* fonts: "STIXGeneral,'Arial Unicode MS'"
* }
* }
* });
*
* The result of \unicode will have TeX class ORD (i.e., it will act like a
* variable). Use \mathbin, \mathrel, etc, to specify a different class.
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2009-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// The configuration defaults, augmented by the user settings
//
MathJax.Extension["TeX/unicode"] = {
version: "2.0",
unicode: {},
config: MathJax.Hub.CombineConfig("TeX.unicode",{
fonts: "STIXGeneral,'Arial Unicode MS'"
})
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX;
var MML = MathJax.ElementJax.mml;
var UNICODE = MathJax.Extension["TeX/unicode"].unicode;
//
// Add \unicode macro
//
TEX.Definitions.macros.unicode = 'Unicode';
//
// Implementation of \unicode in parser
//
TEX.Parse.Augment({
Unicode: function(name) {
var HD = this.GetBrackets(name), font;
if (HD) {
if (HD.replace(/ /g,"").match(/^(\d+(\.\d*)?|\.\d+),(\d+(\.\d*)?|\.\d+)$/))
{HD = HD.replace(/ /g,"").split(/,/); font = this.GetBrackets(name)}
else {font = HD; HD = null}
}
var n = this.trimSpaces(this.GetArgument(name)),
N = parseInt(n.match(/^x/) ? "0"+n : n);
if (!UNICODE[N]) {UNICODE[N] = [800,200,font,N]}
else if (!font) {font = UNICODE[N][2]}
if (HD) {
UNICODE[N][0] = Math.floor(HD[0]*1000);
UNICODE[N][1] = Math.floor(HD[1]*1000);
}
var variant = this.stack.env.font, def = {};
if (font) {
UNICODE[N][2] = def.fontfamily = font.replace(/"/g,"'");
if (variant) {
if (variant.match(/bold/)) {def.fontweight = "bold"}
if (variant.match(/italic|-mathit/)) {def.fontstyle = "italic"}
}
} else if (variant) {def.mathvariant = variant}
def.unicode = [].concat(UNICODE[N]); // make a copy
this.Push(MML.mtext(MML.entity("#"+n)).With(def));
}
});
MathJax.Hub.Startup.signal.Post("TeX unicode Ready");
});
MathJax.Hub.Register.StartupHook("HTML-CSS Jax Ready",function () {
var MML = MathJax.ElementJax.mml;
var FONTS = MathJax.Extension["TeX/unicode"].config.fonts;
//
// Override getVariant to make one that includes the font and size
//
var GETVARIANT = MML.mbase.prototype.HTMLgetVariant;
MML.mbase.Augment({
HTMLgetVariant: function () {
var variant = GETVARIANT.call(this);
if (variant.unicode) {delete variant.unicode; delete variant.FONTS} // clear font cache in case of restart
if (!this.unicode) {return variant}
variant.unicode = true;
if (!variant.defaultFont) {
variant = MathJax.Hub.Insert({},variant); // make a copy
variant.defaultFont = {family:FONTS};
}
var family = this.unicode[2]; if (family) {family += ","+FONTS} else {family = FONTS}
variant.defaultFont[this.unicode[3]] = [
this.unicode[0],this.unicode[1],500,0,500,
{isUnknown:true, isUnicode:true, font:family}
];
return variant;
}
});
});
MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () {
var MML = MathJax.ElementJax.mml;
var FONTS = MathJax.Extension["TeX/unicode"].config.fonts;
//
// Override getVariant to make one that includes the font and size
//
var GETVARIANT = MML.mbase.prototype.SVGgetVariant;
MML.mbase.Augment({
SVGgetVariant: function () {
var variant = GETVARIANT.call(this);
if (variant.unicode) {delete variant.unicode; delete variant.FONTS} // clear font cache in case of restart
if (!this.unicode) {return variant}
variant.unicode = true;
if (!variant.forceFamily) {variant = MathJax.Hub.Insert({},variant)} // make a copy
variant.defaultFamily = FONTS; variant.noRemap = true;
variant.h = this.unicode[0]; variant.d = this.unicode[1];
return variant;
}
});
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/unicode.js");

View File

@ -0,0 +1,58 @@
/*************************************************************
*
* MathJax/extensions/TeX/verb.js
*
* Implements the \verb|...| command for including text verbatim
* (with no processing of macros or special characters).
*
* ---------------------------------------------------------------------
*
* Copyright (c) 2009-2012 Design Science, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MathJax.Extension["TeX/verb"] = {
version: "2.0"
};
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var MML = MathJax.ElementJax.mml;
var TEX = MathJax.InputJax.TeX;
var TEXDEF = TEX.Definitions;
TEXDEF.macros.verb = 'Verb';
TEX.Parse.Augment({
/*
* Implement \verb|...|
*/
Verb: function (name) {
var c = this.GetNext(); var start = ++this.i;
if (c == "" ) {TEX.Error(name+" requires an argument")}
while (this.i < this.string.length && this.string.charAt(this.i) != c) {this.i++}
if (this.i == this.string.length)
{TEX.Error("Can't find closing delimiter for "+name)}
var text = this.string.slice(start,this.i); this.i++;
this.Push(MML.mtext(text).With({mathvariant:MML.VARIANT.MONOSPACE}));
}
});
MathJax.Hub.Startup.signal.Post("TeX verb Ready");
});
MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/verb.js");

Some files were not shown because too many files have changed in this diff Show More