lcg-framework 0.7.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lcg/__init__.py +115 -0
- lcg/content.py +2172 -0
- lcg/exercises.py +991 -0
- lcg/export/__init__.py +18 -0
- lcg/export/braille-tables/lcg.utb +3 -0
- lcg/export/braille.py +1901 -0
- lcg/export/epub.py +420 -0
- lcg/export/exercises_html.py +664 -0
- lcg/export/export.py +1476 -0
- lcg/export/hhp.py +132 -0
- lcg/export/html.py +1717 -0
- lcg/export/ims.py +124 -0
- lcg/export/mathml.py +2351 -0
- lcg/export/nemeth.py +1216 -0
- lcg/export/pdf.py +3447 -0
- lcg/export/styles/presentation-braille-test.py +43 -0
- lcg/export/styles/presentation-braille.py +687 -0
- lcg/i18n.py +1227 -0
- lcg/locales.py +103 -0
- lcg/make.py +370 -0
- lcg/nodes.py +576 -0
- lcg/parse.py +1651 -0
- lcg/plot.py +358 -0
- lcg/presentation.py +674 -0
- lcg/read.py +405 -0
- lcg/resources/css/default.css +180 -0
- lcg/resources/css/epub.css +39 -0
- lcg/resources/css/lcg-exercises.css +157 -0
- lcg/resources/css/lcg-widgets.css +484 -0
- lcg/resources/css/photoswipe.css +441 -0
- lcg/resources/css/pygments.css +62 -0
- lcg/resources/flash/jplayer.swf +0 -0
- lcg/resources/images/jplayer-seeking.gif +0 -0
- lcg/resources/images/lcg-icons.png +0 -0
- lcg/resources/media/exercise-responses/cs/correct-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-03.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-04.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-05.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-06.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-07.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-08.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-09.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-10.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-11.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-12.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-13.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-14.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-15.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-16.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-17.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-18.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-19.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-20.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-21.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-23.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-24.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-26.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-27.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-28.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-29.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-31.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-32.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-33.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-34.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-35.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-36.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-37.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-38.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-39.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-40.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-41.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-42.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-43.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-44.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-45.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-46.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-47.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-48.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-49.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-50.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-51.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-52.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-53.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-54.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-55.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-56.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-57.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-58.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-59.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-60.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-61.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-62.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-63.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-64.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-65.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-66.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-67.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-68.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/correct-69.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-03.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-04.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-05.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-06.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-07.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-08.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-09.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-10.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-11.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-12.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-13.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-14.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-15.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-16.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-17.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-18.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-19.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-20.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-21.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-22.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-23.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-24.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-25.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-26.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-27.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-28.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-29.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-30.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-31.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-32.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-33.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-34.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-35.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-36.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-37.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-38.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-39.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-40.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-41.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-42.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/excellent-43.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-07.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-09.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-10.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-12.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-14.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-16.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-17.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-19.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-21.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-22.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-23.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-24.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-25.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-28.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-29.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-30.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-33.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-34.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-35.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-36.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-38.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-41.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-43.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-44.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-45.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-46.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-47.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-48.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/good-51.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-03.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-04.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-05.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-06.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-07.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-08.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-09.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-10.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-11.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-12.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-13.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-14.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-15.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-16.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-17.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-18.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-19.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-20.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-21.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-22.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-23.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-24.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-25.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-26.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-27.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-28.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-29.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-30.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-31.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-32.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-33.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-34.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-35.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-36.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-37.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-38.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-39.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-40.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-41.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-42.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-43.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-44.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-45.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-46.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-47.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-48.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-49.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-50.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-51.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-52.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-53.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-54.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-55.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-56.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-57.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-58.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-59.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/incorrect-60.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-03.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-04.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-05.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-07.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-08.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-09.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-11.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-13.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-14.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-15.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-16.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-17.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-18.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-19.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-21.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-22.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-23.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-24.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-25.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-26.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-27.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-28.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-29.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-30.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-31.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-32.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-33.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-34.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-35.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-36.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-37.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-38.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/perfect-40.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-03.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-04.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-05.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-06.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-08.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-09.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-10.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-13.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-14.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-15.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-16.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-17.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-18.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-19.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-21.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-22.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-24.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-25.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-26.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-27.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-28.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-29.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-30.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-32.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-33.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-34.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-35.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-36.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-37.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-38.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-39.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/poor-40.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-03.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-04.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-05.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-06.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-07.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-08.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-09.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-10.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-11.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-13.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-14.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-15.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-16.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-17.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-18.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-19.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-21.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-22.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-23.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-25.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-26.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-27.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-28.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-29.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-32.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-33.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-34.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-35.mp3 +0 -0
- lcg/resources/media/exercise-responses/cs/sufficient-36.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/correct-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/correct-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/correct-03.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/correct-04.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/correct-05.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/correct-06.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/correct-07.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/correct-08.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/correct-09.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/excellent-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/excellent-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/excellent-03.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/good-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/good-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/good-03.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/good-04.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/incorrect-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/incorrect-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/incorrect-03.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/incorrect-04.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/incorrect-05.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/incorrect-06.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/incorrect-07.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/incorrect-08.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/incorrect-09.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/incorrect-10.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/incorrect-11.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/perfect-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/perfect-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/perfect-03.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/perfect-04.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/poor-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/poor-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/poor-03.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/sufficient-01.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/sufficient-02.mp3 +0 -0
- lcg/resources/media/exercise-responses/en/sufficient-03.mp3 +0 -0
- lcg/resources/scripts/effects.js +1123 -0
- lcg/resources/scripts/gettext.js +1265 -0
- lcg/resources/scripts/jplayer.min.js +3 -0
- lcg/resources/scripts/jquery.min.js +4 -0
- lcg/resources/scripts/lcg-exercises.js +533 -0
- lcg/resources/scripts/lcg.js +1705 -0
- lcg/resources/scripts/photoswipe-lightbox.esm.min.js +5 -0
- lcg/resources/scripts/photoswipe.esm.min.js +5 -0
- lcg/resources/scripts/prototype.js +6081 -0
- lcg/resources/scripts/scriptaculous.js +68 -0
- lcg/resources.py +438 -0
- lcg/test.py +2615 -0
- lcg/transform.py +990 -0
- lcg/translations/cs/LC_MESSAGES/lcg-exercises.mo +0 -0
- lcg/translations/cs/LC_MESSAGES/lcg.mo +0 -0
- lcg/translations/cs/LC_MESSAGES/test.mo +0 -0
- lcg/translations/de/LC_MESSAGES/lcg-exercises.mo +0 -0
- lcg/translations/de/LC_MESSAGES/lcg.mo +0 -0
- lcg/translations/es/LC_MESSAGES/lcg-exercises.mo +0 -0
- lcg/translations/es/LC_MESSAGES/lcg.mo +0 -0
- lcg/translations/fr/LC_MESSAGES/lcg-exercises.mo +0 -0
- lcg/translations/fr/LC_MESSAGES/lcg.mo +0 -0
- lcg/translations/hu/LC_MESSAGES/lcg-exercises.mo +0 -0
- lcg/translations/hu/LC_MESSAGES/lcg.mo +0 -0
- lcg/translations/it/LC_MESSAGES/lcg-exercises.mo +0 -0
- lcg/translations/it/LC_MESSAGES/lcg.mo +0 -0
- lcg/translations/no/LC_MESSAGES/lcg-exercises.mo +0 -0
- lcg/translations/no/LC_MESSAGES/lcg.mo +0 -0
- lcg/translations/pl/LC_MESSAGES/lcg-exercises.mo +0 -0
- lcg/translations/pl/LC_MESSAGES/lcg.mo +0 -0
- lcg/translations/po/LC_MESSAGES/lcg-exercises.mo +0 -0
- lcg/translations/po/LC_MESSAGES/lcg.mo +0 -0
- lcg/translations/ro/LC_MESSAGES/lcg-exercises.mo +0 -0
- lcg/translations/ro/LC_MESSAGES/lcg.mo +0 -0
- lcg/translations/sk/LC_MESSAGES/lcg-exercises.mo +0 -0
- lcg/translations/sk/LC_MESSAGES/lcg.mo +0 -0
- lcg/translations/tr/LC_MESSAGES/lcg-exercises.mo +0 -0
- lcg/translations/tr/LC_MESSAGES/lcg.mo +0 -0
- lcg/units.py +192 -0
- lcg/util.py +671 -0
- lcg/widgets.py +518 -0
- lcg_framework-0.7.0.dist-info/METADATA +108 -0
- lcg_framework-0.7.0.dist-info/RECORD +416 -0
- lcg_framework-0.7.0.dist-info/WHEEL +5 -0
- lcg_framework-0.7.0.dist-info/licenses/COPYING +340 -0
lcg/content.py
ADDED
|
@@ -0,0 +1,2172 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Copyright (C) 2004-2017 OUI Technology Ltd.
|
|
4
|
+
# Copyright (C) 2019-2025 Tomáš Cerha <t.cerha@gmail.com>
|
|
5
|
+
#
|
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU General Public License
|
|
17
|
+
# along with this program; if not, write to the Free Software
|
|
18
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
19
|
+
|
|
20
|
+
"""Generic content abstraction.
|
|
21
|
+
|
|
22
|
+
The classes defined in this module were designed to represent general content
|
|
23
|
+
elements of a structured document, such as sections, paragraphs, lists, tables
|
|
24
|
+
etc. Each content element is represented by an instance of one of the classes
|
|
25
|
+
defined below. Some types of elements can contain other elements and thus the
|
|
26
|
+
whole document is represented by a tree of content elements. When the
|
|
27
|
+
construction of the whole tree is finished, each element knows its ancestor
|
|
28
|
+
(the container) and all the successors (sub-content) in the tree.
|
|
29
|
+
|
|
30
|
+
The content hierarchy may be built independently, however you need to assign a
|
|
31
|
+
content to a 'lcg.ContentNode' instance to be able to export it.
|
|
32
|
+
|
|
33
|
+
Please note the difference between the hierarchy of content within one node and
|
|
34
|
+
the hierarchy of the nodes themselves.
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import unicode_literals
|
|
39
|
+
from future import standard_library
|
|
40
|
+
from builtins import range
|
|
41
|
+
|
|
42
|
+
import sys
|
|
43
|
+
import copy
|
|
44
|
+
import re
|
|
45
|
+
import lcg
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
_ = lcg.TranslatableTextFactory('lcg')
|
|
49
|
+
|
|
50
|
+
standard_library.install_aliases()
|
|
51
|
+
unistr = type(u'') # Python 2/3 transition hack.
|
|
52
|
+
if sys.version_info[0] > 2:
|
|
53
|
+
basestring = str
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Content(object):
|
|
57
|
+
"""Generic base class for all types of content.
|
|
58
|
+
|
|
59
|
+
All (more specific) content element classes are derived from this class.
|
|
60
|
+
|
|
61
|
+
Each content element instance may be contained in another content element
|
|
62
|
+
instance (see the 'Container' class) and thus they make a hierarchical
|
|
63
|
+
structure.
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
_ALLOWED_CONTAINER = None
|
|
67
|
+
|
|
68
|
+
def __init__(self, lang=None, resources=()):
|
|
69
|
+
"""Initialize the instance.
|
|
70
|
+
|
|
71
|
+
Arguments:
|
|
72
|
+
|
|
73
|
+
lang -- content language as a lowercase ISO 639-1 Alpha-2 language code.
|
|
74
|
+
resources -- sequence of resource instances associated with the
|
|
75
|
+
content. This allows passing resource instances explicitly with
|
|
76
|
+
content rather than relying on their allocation through resource
|
|
77
|
+
provider.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
assert resources is None or isinstance(resources, (tuple, list)), resources
|
|
81
|
+
self._parent = None
|
|
82
|
+
self._container = None
|
|
83
|
+
self._lang = lang
|
|
84
|
+
self._page_number = ''
|
|
85
|
+
self._resources = tuple(resources)
|
|
86
|
+
super(Content, self).__init__()
|
|
87
|
+
|
|
88
|
+
def set_container(self, container):
|
|
89
|
+
"""Set the parent 'Container' to 'container'.
|
|
90
|
+
|
|
91
|
+
This method is normally called automatically by the container
|
|
92
|
+
constructor to inform the contained 'Content' elements about their
|
|
93
|
+
position in content hierarchy. You only need to care about calling
|
|
94
|
+
this method if you implement your own 'Container' class and don't call
|
|
95
|
+
the default constructor for all the contained elements for some reason.
|
|
96
|
+
Otherwise it is always called automatically by LCG behind the scenes.
|
|
97
|
+
|
|
98
|
+
"""
|
|
99
|
+
if __debug__:
|
|
100
|
+
cls = self._ALLOWED_CONTAINER or Container
|
|
101
|
+
assert isinstance(container, cls), \
|
|
102
|
+
"Not a '%s' instance: %s" % (cls.__name__, container,)
|
|
103
|
+
self._container = container
|
|
104
|
+
|
|
105
|
+
def set_parent(self, node):
|
|
106
|
+
"""Set the parent 'lcg.ContentNode' to 'node'.
|
|
107
|
+
|
|
108
|
+
This method is deprecated. The parent node (the node to which the
|
|
109
|
+
content belongs) should always be obtained from the export context, not
|
|
110
|
+
from the node itself. This method is now called automatically when an
|
|
111
|
+
element is exported to keep the 'parent()' method working, but both
|
|
112
|
+
methods should be avoided in newly written code.
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
assert isinstance(node, lcg.ContentNode), \
|
|
116
|
+
"Not a 'lcg.ContentNode' instance: %s" % node
|
|
117
|
+
# assert self._parent is None or self._parent is node, \
|
|
118
|
+
# "Reparenting not allowed: %s -> %s" % (self._parent, node)
|
|
119
|
+
self._parent = node
|
|
120
|
+
|
|
121
|
+
def set_page_number(self, number):
|
|
122
|
+
"""Set page number of the content element.
|
|
123
|
+
|
|
124
|
+
This is to be used in table of contents.
|
|
125
|
+
|
|
126
|
+
Arguments:
|
|
127
|
+
|
|
128
|
+
number -- the page number; basestring
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
assert isinstance(number, basestring), number
|
|
132
|
+
self._page_number = number
|
|
133
|
+
|
|
134
|
+
def parent(self):
|
|
135
|
+
"""Deprecated. Use context.node() instead."""
|
|
136
|
+
parent = self._parent or self._container and self._container.parent()
|
|
137
|
+
return parent
|
|
138
|
+
|
|
139
|
+
def lang(self, inherited=True):
|
|
140
|
+
"""Return the content language as lowercase ISO 639-1 Alpha-2 language code.
|
|
141
|
+
|
|
142
|
+
Arguments:
|
|
143
|
+
|
|
144
|
+
inherited -- iff True, the language will be determined from the
|
|
145
|
+
parent element in the hierarchy if this element doesn't define
|
|
146
|
+
'lang' explicitly. When False, the value of 'lang' passed to the
|
|
147
|
+
constructor is returned.
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
lang = self._lang
|
|
151
|
+
if lang is None and inherited and self._container:
|
|
152
|
+
lang = self._container.lang()
|
|
153
|
+
return lang
|
|
154
|
+
|
|
155
|
+
def resources(self):
|
|
156
|
+
"""Return the value of 'resources' passed to the constructor as a tuple."""
|
|
157
|
+
return self._resources
|
|
158
|
+
|
|
159
|
+
def container(self):
|
|
160
|
+
"""Return the direct container of this element."""
|
|
161
|
+
return self._container
|
|
162
|
+
|
|
163
|
+
def container_path(self):
|
|
164
|
+
"""Return a list of containers of the given element
|
|
165
|
+
|
|
166
|
+
The list is sorted from top to bottom, the last element being
|
|
167
|
+
this element itself.
|
|
168
|
+
|
|
169
|
+
"""
|
|
170
|
+
path = [self]
|
|
171
|
+
while path[0]._container is not None:
|
|
172
|
+
path.insert(0, path[0]._container)
|
|
173
|
+
return tuple(path)
|
|
174
|
+
|
|
175
|
+
def _neighbor_element(self, direction, stop_classes):
|
|
176
|
+
element = self
|
|
177
|
+
container = self.container()
|
|
178
|
+
while True:
|
|
179
|
+
if container is None:
|
|
180
|
+
return None
|
|
181
|
+
content = container.content()
|
|
182
|
+
index = content.index(element)
|
|
183
|
+
if direction < 0:
|
|
184
|
+
if index > 0:
|
|
185
|
+
neighbor = content[index - 1]
|
|
186
|
+
break
|
|
187
|
+
else:
|
|
188
|
+
if index < len(content) - 1:
|
|
189
|
+
neighbor = content[index + 1]
|
|
190
|
+
break
|
|
191
|
+
if isinstance(container, stop_classes):
|
|
192
|
+
return None
|
|
193
|
+
element = container
|
|
194
|
+
container = container.container()
|
|
195
|
+
while isinstance(neighbor, Container):
|
|
196
|
+
content = neighbor.content()
|
|
197
|
+
if not content:
|
|
198
|
+
return neighbor
|
|
199
|
+
neighbor = content[-1]
|
|
200
|
+
return neighbor
|
|
201
|
+
|
|
202
|
+
def previous_element(self, stop_classes=()):
|
|
203
|
+
""" """
|
|
204
|
+
return self._neighbor_element(-1, stop_classes)
|
|
205
|
+
|
|
206
|
+
def next_element(self, stop_classes=()):
|
|
207
|
+
""" """
|
|
208
|
+
return self._neighbor_element(1, stop_classes)
|
|
209
|
+
|
|
210
|
+
def sections(self):
|
|
211
|
+
"""Return the contained sections as a list of 'Section' instances.
|
|
212
|
+
|
|
213
|
+
This method allows introspection of content hierarchy (for example for
|
|
214
|
+
creation of a table of contents).
|
|
215
|
+
|
|
216
|
+
An empty list is returned in the base class. Derived classes which
|
|
217
|
+
contain sections should override this method to return the contained
|
|
218
|
+
sections.
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
return []
|
|
222
|
+
|
|
223
|
+
def page_number(self):
|
|
224
|
+
"""Return page number of the content element.
|
|
225
|
+
|
|
226
|
+
This is to be used in table of contents.
|
|
227
|
+
|
|
228
|
+
Arguments:
|
|
229
|
+
|
|
230
|
+
context -- formatting context as a 'Exporter.Context' instance
|
|
231
|
+
created and returned by 'Exporter.context' method
|
|
232
|
+
|
|
233
|
+
"""
|
|
234
|
+
return self._page_number
|
|
235
|
+
|
|
236
|
+
def export(self, context):
|
|
237
|
+
"""Return the formatted content in an output specific form.
|
|
238
|
+
|
|
239
|
+
The return value can later be processed by 'Exporter' methods.
|
|
240
|
+
|
|
241
|
+
Arguments:
|
|
242
|
+
|
|
243
|
+
context -- formatting context as a 'Exporter.Context' instance
|
|
244
|
+
created and returned by 'Exporter.context' method
|
|
245
|
+
|
|
246
|
+
"""
|
|
247
|
+
return context.exporter().export_element(context, self)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class Container(Content):
|
|
251
|
+
"""Container of other content elements.
|
|
252
|
+
|
|
253
|
+
Containers allow to build a hierarchy of 'Content' instances inside the
|
|
254
|
+
scope of one node. This is an addition to the hierarchy of the nodes
|
|
255
|
+
themselves.
|
|
256
|
+
|
|
257
|
+
All the contained (wrapped) content elements will be notified about the
|
|
258
|
+
fact, that they are contained within this container and thus belong to the
|
|
259
|
+
hierarchy.
|
|
260
|
+
|
|
261
|
+
"""
|
|
262
|
+
_ALLOWED_CONTENT = Content
|
|
263
|
+
_SUBSEQUENCES = False
|
|
264
|
+
_SUBSEQUENCE_LENGTH = None
|
|
265
|
+
|
|
266
|
+
def __init__(self, content, name=(), id=None, role=None, label=None,
|
|
267
|
+
halign=None, valign=None, orientation=None, width=None, height=None,
|
|
268
|
+
padding=None, presentation=None, **kwargs):
|
|
269
|
+
"""Initialize the instance.
|
|
270
|
+
|
|
271
|
+
Arguments:
|
|
272
|
+
|
|
273
|
+
content -- the actual content wrapped into this container as a
|
|
274
|
+
sequence of 'Content' instances in the order in which they should
|
|
275
|
+
appear in the output.
|
|
276
|
+
name -- optional string identifier or a sequence of them. These
|
|
277
|
+
identifiers may be then used as output presentation selectors (for
|
|
278
|
+
example in HTML they will compose the value of the 'class'
|
|
279
|
+
attribute).
|
|
280
|
+
id -- unique identifier as a string. The identifier must be unique
|
|
281
|
+
within the whole content hierarchy of one 'lcg.ContentNode'.
|
|
282
|
+
role -- ARIA role name for HTML output as a string or None.
|
|
283
|
+
label -- container label to be used for aria-label in HTML output.
|
|
284
|
+
halign -- horizontal alignment of the container content; one of the
|
|
285
|
+
'lcg.HorizontalAlignment' constants or 'None' (default alignment).
|
|
286
|
+
valign -- vertical alignment of the container content; one of the
|
|
287
|
+
'VerticalAlignment' constants or 'None' (default alignment).
|
|
288
|
+
orientation -- orientation of the container content flow (vertical
|
|
289
|
+
vs.horizontalal); one of the 'Orientation' constants or 'None'
|
|
290
|
+
(default orientation).
|
|
291
|
+
width -- explicit output width as 'lcg.Unit' subclass instance.
|
|
292
|
+
Applies to the outer dimension of the whole box including any
|
|
293
|
+
padding and/or box_margin. Currently only supported in PDF export.
|
|
294
|
+
height -- explicit output width as 'lcg.Unit' subclass instance.
|
|
295
|
+
Applies to the outer dimension of the whole box including any
|
|
296
|
+
padding and/or box_margin. Currently only supported in PDF export.
|
|
297
|
+
padding -- space around the container content. If not None, it can
|
|
298
|
+
be either a 'Unit' instance determining equal padding for all sides
|
|
299
|
+
or a sequence of four 'Unit' instances determining padding for each
|
|
300
|
+
side separately in the order (top, right, bottom, left) or a
|
|
301
|
+
sequence of two 'Unit' instances, where the first value determines
|
|
302
|
+
top and bottom padding and the second value determines left and
|
|
303
|
+
right padding. Note, that 'presentation' may also define
|
|
304
|
+
'box_margin' for a boxed group, but there are major differences
|
|
305
|
+
between box_margin and padding. Box margin only applies to boxed
|
|
306
|
+
groups and is ignored for non boxed groups. Padding applies to
|
|
307
|
+
both. Also, box_margin is (through presentation inheritance)
|
|
308
|
+
inherited to any inner boxed groups which don't explicitly override
|
|
309
|
+
it. Padding only applies to the container for which it is defined.
|
|
310
|
+
Currently only supported in PDF export.
|
|
311
|
+
presentation -- 'Presentation' instance defining various presentation
|
|
312
|
+
properties; if 'None' then no explicit presentation for this container
|
|
313
|
+
is defined.
|
|
314
|
+
|
|
315
|
+
Note that 'halign', 'valign', 'orientation', 'width', 'height' and 'presentation'
|
|
316
|
+
parameters may be ignored by some exporters.
|
|
317
|
+
|
|
318
|
+
"""
|
|
319
|
+
super(Container, self).__init__(**kwargs)
|
|
320
|
+
assert isinstance(name, basestring) or lcg.is_sequence_of(name, basestring), name
|
|
321
|
+
assert halign is None or isinstance(halign, basestring), halign
|
|
322
|
+
assert valign is None or isinstance(valign, basestring), valign
|
|
323
|
+
assert orientation is None or isinstance(orientation, basestring), orientation
|
|
324
|
+
assert id is None or isinstance(id, basestring), id
|
|
325
|
+
assert width is None or isinstance(width, lcg.Unit), width
|
|
326
|
+
assert height is None or isinstance(height, lcg.Unit), height
|
|
327
|
+
assert (padding is None or isinstance(padding, lcg.Unit)
|
|
328
|
+
or lcg.is_sequence_of(padding, lcg.Unit) and len(padding) in (2, 4)), padding
|
|
329
|
+
self._id = id
|
|
330
|
+
if isinstance(name, basestring):
|
|
331
|
+
names = (name,)
|
|
332
|
+
else:
|
|
333
|
+
names = tuple(name)
|
|
334
|
+
self._names = names
|
|
335
|
+
self._role = role
|
|
336
|
+
self._label = label
|
|
337
|
+
from lcg import Presentation
|
|
338
|
+
assert presentation is None or isinstance(presentation, Presentation), presentation
|
|
339
|
+
super(Container, self).__init__(**kwargs)
|
|
340
|
+
self._halign = halign
|
|
341
|
+
self._valign = valign
|
|
342
|
+
self._orientation = orientation
|
|
343
|
+
self._width = width
|
|
344
|
+
self._height = height
|
|
345
|
+
self._padding = padding
|
|
346
|
+
self._presentation = presentation
|
|
347
|
+
self._contained_resources = None
|
|
348
|
+
if self._SUBSEQUENCES:
|
|
349
|
+
assert isinstance(content, (list, tuple)), "Not a sequence: %s" % content
|
|
350
|
+
self._content = tuple([tuple(subseq) for subseq in content])
|
|
351
|
+
for subseq in content:
|
|
352
|
+
assert isinstance(subseq, (list, tuple)), "Not a sequence: %s" % subseq
|
|
353
|
+
if self._SUBSEQUENCE_LENGTH is not None:
|
|
354
|
+
assert len(subseq) == self._SUBSEQUENCE_LENGTH
|
|
355
|
+
for c in subseq:
|
|
356
|
+
assert isinstance(c, self._ALLOWED_CONTENT), \
|
|
357
|
+
"Not a '%s' instance: %s" % (self._ALLOWED_CONTENT, c)
|
|
358
|
+
c.set_container(self)
|
|
359
|
+
elif isinstance(content, (list, tuple)):
|
|
360
|
+
assert lcg.is_sequence_of(content, self._ALLOWED_CONTENT), \
|
|
361
|
+
"Not a '%s' instances sequence: %s" % (self._ALLOWED_CONTENT, content)
|
|
362
|
+
self._content = tuple(content)
|
|
363
|
+
for c in content:
|
|
364
|
+
c.set_container(self)
|
|
365
|
+
else:
|
|
366
|
+
assert isinstance(content, self._ALLOWED_CONTENT), \
|
|
367
|
+
"Not a '%s' instance: %s" % (self._ALLOWED_CONTENT, content)
|
|
368
|
+
self._content = (content,)
|
|
369
|
+
content.set_container(self)
|
|
370
|
+
|
|
371
|
+
def content(self):
|
|
372
|
+
"""Return the sequence of contained content elements.
|
|
373
|
+
"""
|
|
374
|
+
return self._content
|
|
375
|
+
|
|
376
|
+
def id(self):
|
|
377
|
+
"""Return the unique identifier of this container as a string."""
|
|
378
|
+
return self._id
|
|
379
|
+
|
|
380
|
+
def sections(self):
|
|
381
|
+
result = []
|
|
382
|
+
for c in self._content:
|
|
383
|
+
if isinstance(c, Section):
|
|
384
|
+
result.append(c)
|
|
385
|
+
elif isinstance(c, Container):
|
|
386
|
+
result.extend(c.sections())
|
|
387
|
+
return result
|
|
388
|
+
|
|
389
|
+
def names(self):
|
|
390
|
+
"""Return the tuple of strings passed as 'name' to the constructor."""
|
|
391
|
+
return self._names
|
|
392
|
+
|
|
393
|
+
def role(self):
|
|
394
|
+
"""Return the 'role' passed to the constructor."""
|
|
395
|
+
return self._role
|
|
396
|
+
|
|
397
|
+
def label(self):
|
|
398
|
+
"""Return the 'label' passed to the constructor."""
|
|
399
|
+
return self._label
|
|
400
|
+
|
|
401
|
+
def halign(self):
|
|
402
|
+
"""Return the value of 'halign' as passed to the constructor."""
|
|
403
|
+
return self._halign
|
|
404
|
+
|
|
405
|
+
def valign(self):
|
|
406
|
+
"""Return the value of 'valign' as passed to the constructor."""
|
|
407
|
+
return self._valign
|
|
408
|
+
|
|
409
|
+
def orientation(self):
|
|
410
|
+
"""Return the value of 'orientation' as passed to the constructor."""
|
|
411
|
+
return self._orientation
|
|
412
|
+
|
|
413
|
+
def width(self):
|
|
414
|
+
"""Return the value of 'width' as passed to the constructor."""
|
|
415
|
+
return self._width
|
|
416
|
+
|
|
417
|
+
def height(self):
|
|
418
|
+
"""Return the value of 'height' as passed to the constructor."""
|
|
419
|
+
return self._height
|
|
420
|
+
|
|
421
|
+
def padding(self):
|
|
422
|
+
"""Return the value of 'padding' as passed to the constructor."""
|
|
423
|
+
return self._padding
|
|
424
|
+
|
|
425
|
+
def presentation(self):
|
|
426
|
+
"""Return the value of 'presentation' as passed to the constructor."""
|
|
427
|
+
return self._presentation
|
|
428
|
+
|
|
429
|
+
def resources(self):
|
|
430
|
+
"""Return the value of 'resources' as passed to the constructor."""
|
|
431
|
+
if self._contained_resources is None:
|
|
432
|
+
resources = []
|
|
433
|
+
if self._SUBSEQUENCES:
|
|
434
|
+
content = self._content
|
|
435
|
+
else:
|
|
436
|
+
content = (self._content,)
|
|
437
|
+
for subseq in content:
|
|
438
|
+
for c in subseq:
|
|
439
|
+
resources.extend(c.resources())
|
|
440
|
+
self._contained_resources = tuple(resources)
|
|
441
|
+
return self._resources + self._contained_resources
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
# ======================== Inline content elements ========================
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class Strong(Container):
|
|
448
|
+
"""Text emphasized by bold font face."""
|
|
449
|
+
pass
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
class Emphasized(Container):
|
|
453
|
+
"""Text emphasized by slanted font face."""
|
|
454
|
+
pass
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
class Underlined(Container):
|
|
458
|
+
"""Underlined text."""
|
|
459
|
+
pass
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
class Code(Container):
|
|
463
|
+
"""Text representing a piece of computer code."""
|
|
464
|
+
pass
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class Citation(Container):
|
|
468
|
+
"""Citation of a text in another language."""
|
|
469
|
+
pass
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
class Quotation(Container):
|
|
473
|
+
"""Quotation of content from other source."""
|
|
474
|
+
|
|
475
|
+
def __init__(self, content, source=None, uri=None, **kwargs):
|
|
476
|
+
"""Arguments:
|
|
477
|
+
|
|
478
|
+
content -- quoted content as lcg.Content instance
|
|
479
|
+
source -- reference to the source of the quotation, such as the
|
|
480
|
+
original author, publication, web page etc. (basestring)
|
|
481
|
+
uri -- link to the source (basestring)
|
|
482
|
+
|
|
483
|
+
"""
|
|
484
|
+
assert source is None or isinstance(source, basestring), source
|
|
485
|
+
assert uri is None or isinstance(uri, basestring), uri
|
|
486
|
+
self._source = source
|
|
487
|
+
self._uri = uri
|
|
488
|
+
super(Quotation, self).__init__(content, **kwargs)
|
|
489
|
+
|
|
490
|
+
def source(self):
|
|
491
|
+
"""Return the quotation source as passed to the constructor."""
|
|
492
|
+
return self._source
|
|
493
|
+
|
|
494
|
+
def uri(self):
|
|
495
|
+
"""Return the quotation source URI as passed to the constructor."""
|
|
496
|
+
return self._uri
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
class Superscript(Container):
|
|
500
|
+
"""Text vertically aligned above the normal line level."""
|
|
501
|
+
pass
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
class Subscript(Container):
|
|
505
|
+
"""Text vertically aligned below the normal line level."""
|
|
506
|
+
pass
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
class TextContent(Content):
|
|
510
|
+
"""A simple piece of text."""
|
|
511
|
+
|
|
512
|
+
def __init__(self, text, **kwargs):
|
|
513
|
+
"""Initialize the instance.
|
|
514
|
+
|
|
515
|
+
Arguments:
|
|
516
|
+
|
|
517
|
+
text -- the actual text content of this element as a string or
|
|
518
|
+
unicode
|
|
519
|
+
kwargs -- keyword arguments for parent class constructor.
|
|
520
|
+
|
|
521
|
+
"""
|
|
522
|
+
assert isinstance(text, basestring), text
|
|
523
|
+
super(TextContent, self).__init__(**kwargs)
|
|
524
|
+
self._text = text
|
|
525
|
+
|
|
526
|
+
def __unicode__(self):
|
|
527
|
+
text = self._text.strip()
|
|
528
|
+
sample = text and text.splitlines()[0] or ''
|
|
529
|
+
if len(sample) > 20:
|
|
530
|
+
sample = sample[:20]
|
|
531
|
+
if len(sample) < len(text):
|
|
532
|
+
sample += '...'
|
|
533
|
+
cls = self.__class__.__name__
|
|
534
|
+
return '<%s at 0x%x text="%s">' % (cls, id(self), sample)
|
|
535
|
+
|
|
536
|
+
def text(self):
|
|
537
|
+
"""Return the value of 'text' as passed to the constructor."""
|
|
538
|
+
return self._text
|
|
539
|
+
|
|
540
|
+
def clone(self, text):
|
|
541
|
+
"""Return copy of the instance with text replaced by 'text'.
|
|
542
|
+
|
|
543
|
+
Arguments:
|
|
544
|
+
|
|
545
|
+
text -- text content of the element copy; basestring
|
|
546
|
+
|
|
547
|
+
"""
|
|
548
|
+
cloned = copy.copy(self)
|
|
549
|
+
cloned._text = text
|
|
550
|
+
return cloned
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
class Link(Container):
|
|
554
|
+
"""Link to internal or external location."""
|
|
555
|
+
|
|
556
|
+
class ExternalTarget(object):
|
|
557
|
+
"""Representation of an external target specified by its URI."""
|
|
558
|
+
|
|
559
|
+
def __init__(self, uri, title, descr=None, lang=None):
|
|
560
|
+
"""Arguments:
|
|
561
|
+
|
|
562
|
+
uri -- URI of the link target as a string
|
|
563
|
+
title -- title of the target as a string or unicode
|
|
564
|
+
descr -- ???
|
|
565
|
+
lang -- lowercase ISO 639-1 Alpha-2 language code
|
|
566
|
+
|
|
567
|
+
"""
|
|
568
|
+
self._uri = uri
|
|
569
|
+
self._title = title
|
|
570
|
+
self._descr = descr
|
|
571
|
+
self._lang = lang
|
|
572
|
+
|
|
573
|
+
def uri(self):
|
|
574
|
+
return self._uri
|
|
575
|
+
|
|
576
|
+
def title(self):
|
|
577
|
+
return self._title
|
|
578
|
+
|
|
579
|
+
def descr(self):
|
|
580
|
+
return self._descr
|
|
581
|
+
|
|
582
|
+
def lang(self):
|
|
583
|
+
return self._lang
|
|
584
|
+
|
|
585
|
+
def __init__(self, target, label=None, descr=None, type=None, lang=None):
|
|
586
|
+
"""Arguments:
|
|
587
|
+
|
|
588
|
+
target -- target of the link, instance of 'Section', 'lcg.ContentNode',
|
|
589
|
+
'lcg.Resource', 'Link.ExternalTarget' or 'basestring'. If a string is
|
|
590
|
+
used, it is a temporary reference to be resolved in export time.
|
|
591
|
+
It allows creation of links which refer to objects, which can not
|
|
592
|
+
be resolved at the time of Link instance creation (their instances
|
|
593
|
+
may not exist yet when the source text is parsed). The reference
|
|
594
|
+
is then automatically resolved by the method 'target()' into one of
|
|
595
|
+
the other allowed link targets.
|
|
596
|
+
label -- link label text as a string or a 'Content' instance (such as
|
|
597
|
+
'InlineImage' for image links)
|
|
598
|
+
descr -- breif target description text. If none the description is
|
|
599
|
+
taken from the 'target' instance depending on its type
|
|
600
|
+
type -- ???
|
|
601
|
+
lang -- lowercase ISO 639-1 Alpha-2 language code
|
|
602
|
+
|
|
603
|
+
"""
|
|
604
|
+
assert isinstance(target, (Section, lcg.ContentNode, lcg.Resource, self.ExternalTarget,
|
|
605
|
+
basestring)), target
|
|
606
|
+
assert label is None or isinstance(label, (basestring, Content)), label
|
|
607
|
+
assert descr is None or isinstance(descr, basestring), descr
|
|
608
|
+
assert type is None or isinstance(type, basestring), type
|
|
609
|
+
assert lang is None or isinstance(lang, basestring), lang
|
|
610
|
+
if isinstance(label, basestring):
|
|
611
|
+
content = TextContent(label)
|
|
612
|
+
elif label is not None:
|
|
613
|
+
content = label
|
|
614
|
+
else:
|
|
615
|
+
content = ()
|
|
616
|
+
self._target = target
|
|
617
|
+
self._descr = descr
|
|
618
|
+
self._type = type
|
|
619
|
+
super(Link, self).__init__(content, lang=lang)
|
|
620
|
+
|
|
621
|
+
def target(self, context):
|
|
622
|
+
"""Return the link target.
|
|
623
|
+
|
|
624
|
+
If a string reference was passed to the constructor argument 'target',
|
|
625
|
+
it is automatically resolved, so the returned instance is always one of
|
|
626
|
+
'Section', 'lcg.ContentNode', 'lcg.Resource' or 'Link.ExternalTarget'.
|
|
627
|
+
|
|
628
|
+
"""
|
|
629
|
+
target = self._target
|
|
630
|
+
if isinstance(target, basestring):
|
|
631
|
+
reference = target
|
|
632
|
+
target = context.resource(reference, warn=False)
|
|
633
|
+
if target is None and '@' not in reference:
|
|
634
|
+
if '#' in reference:
|
|
635
|
+
node_id, section_id = reference.split('#', 1)
|
|
636
|
+
node = context.node()
|
|
637
|
+
if node_id:
|
|
638
|
+
node = node.root().find_node(node_id)
|
|
639
|
+
if node:
|
|
640
|
+
target = node.find_section(context.lang(), section_id)
|
|
641
|
+
else:
|
|
642
|
+
target = context.node().root().find_node(reference)
|
|
643
|
+
if target is None:
|
|
644
|
+
target = self.ExternalTarget(reference, reference)
|
|
645
|
+
return target
|
|
646
|
+
|
|
647
|
+
def descr(self):
|
|
648
|
+
"""Return the link description as passed to the constructor argument 'descr'."""
|
|
649
|
+
return self._descr
|
|
650
|
+
|
|
651
|
+
def type(self):
|
|
652
|
+
"""Return the value of 'type' as passed to the constructor."""
|
|
653
|
+
return self._type
|
|
654
|
+
|
|
655
|
+
def node_id(self):
|
|
656
|
+
"Return target node id (basestring) or 'None'."
|
|
657
|
+
target = self._target
|
|
658
|
+
if isinstance(target, basestring) and '#' in target:
|
|
659
|
+
return target.split('#', 1)[0]
|
|
660
|
+
return None
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
class Abbreviation(TextContent):
|
|
664
|
+
"""Abbreviation with description."""
|
|
665
|
+
|
|
666
|
+
def __init__(self, text, descr, **kwargs):
|
|
667
|
+
"""Arguments:
|
|
668
|
+
|
|
669
|
+
text -- the abbreviation as a string or unicode.
|
|
670
|
+
descr -- the description as a string or unicode.
|
|
671
|
+
|
|
672
|
+
"""
|
|
673
|
+
assert isinstance(descr, basestring), descr
|
|
674
|
+
self._descr = descr
|
|
675
|
+
super(Abbreviation, self).__init__(text, **kwargs)
|
|
676
|
+
|
|
677
|
+
def descr(self):
|
|
678
|
+
"""Return the abbreviation description given in the constructor."""
|
|
679
|
+
return self._descr
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
class Anchor(TextContent):
|
|
683
|
+
"""Target of a link (an anchor)."""
|
|
684
|
+
|
|
685
|
+
def __init__(self, anchor, text='', **kwargs):
|
|
686
|
+
"""Arguments:
|
|
687
|
+
|
|
688
|
+
anchor -- name of the link target anchor as a string.
|
|
689
|
+
text -- text of the target place as a string or unicode.
|
|
690
|
+
|
|
691
|
+
"""
|
|
692
|
+
assert isinstance(anchor, basestring), anchor
|
|
693
|
+
self._anchor = anchor
|
|
694
|
+
super(Anchor, self).__init__(text, **kwargs)
|
|
695
|
+
|
|
696
|
+
def anchor(self):
|
|
697
|
+
"""Return link target name given in the constructor."""
|
|
698
|
+
return self._anchor
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
class _InlineObject(Content):
|
|
702
|
+
"""Super class for embedded objects, such as images, audio and video."""
|
|
703
|
+
|
|
704
|
+
def __init__(self, title=None, descr=None, name=None, lang=None):
|
|
705
|
+
"""Arguments:
|
|
706
|
+
|
|
707
|
+
title -- object title as a string or unicode. If not None, overrides
|
|
708
|
+
'resource.title()' for this particular use.
|
|
709
|
+
descr -- object description a string or unicode. If not None, overrides
|
|
710
|
+
'resource.descr()' for this particular use.
|
|
711
|
+
name -- arbitrary name (string) identifying the object. Used as CSS
|
|
712
|
+
class name in HTML to allow individual styling.
|
|
713
|
+
lang -- content language as an ISO 639-1 Alpha-2 language code (lowercase)
|
|
714
|
+
|
|
715
|
+
"""
|
|
716
|
+
assert title is None or isinstance(title, basestring), title
|
|
717
|
+
assert descr is None or isinstance(descr, basestring), descr
|
|
718
|
+
assert name is None or isinstance(name, basestring), name
|
|
719
|
+
self._title = title
|
|
720
|
+
self._descr = descr
|
|
721
|
+
self._name = name
|
|
722
|
+
super(_InlineObject, self).__init__(lang=lang)
|
|
723
|
+
|
|
724
|
+
def _resource_instance(self, context, resource, cls):
|
|
725
|
+
if isinstance(resource, basestring):
|
|
726
|
+
filename = resource
|
|
727
|
+
if ((filename.startswith('http:') or filename.startswith('https:') or
|
|
728
|
+
filename.startswith('ftp:'))):
|
|
729
|
+
resource = cls(filename, uri=filename)
|
|
730
|
+
else:
|
|
731
|
+
resource = context.resource(filename)
|
|
732
|
+
if resource is None:
|
|
733
|
+
resource = cls(filename, uri=filename)
|
|
734
|
+
else:
|
|
735
|
+
assert isinstance(resource, cls), resource
|
|
736
|
+
return resource
|
|
737
|
+
|
|
738
|
+
def title(self):
|
|
739
|
+
"""Return the value of 'title' as passed to the constructor."""
|
|
740
|
+
return self._title
|
|
741
|
+
|
|
742
|
+
def descr(self):
|
|
743
|
+
"""Return the value of 'descr' as passed to the constructor."""
|
|
744
|
+
return self._descr
|
|
745
|
+
|
|
746
|
+
def name(self):
|
|
747
|
+
"""Return the value of 'name' as passed to the constructor."""
|
|
748
|
+
return self._name
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
class InlineImage(_InlineObject):
|
|
752
|
+
"""Image embedded inside the document.
|
|
753
|
+
|
|
754
|
+
The image may be either a part of a textual paragraph or standalone
|
|
755
|
+
(outside paragraph). When inside paragraph, it may float left or right
|
|
756
|
+
from the surrounding text or be put directly into its position within the
|
|
757
|
+
text.
|
|
758
|
+
|
|
759
|
+
"""
|
|
760
|
+
|
|
761
|
+
LEFT = 'left'
|
|
762
|
+
RIGHT = 'right'
|
|
763
|
+
TOP = 'top'
|
|
764
|
+
BOTTOM = 'bottom'
|
|
765
|
+
MIDDLE = 'middle'
|
|
766
|
+
|
|
767
|
+
def __init__(self, image, align=None, width=None, height=None, standalone=False, **kwargs):
|
|
768
|
+
"""Arguments:
|
|
769
|
+
|
|
770
|
+
image -- the displayed image as an 'lcg.Image' resource instance.
|
|
771
|
+
align -- requested alignment of the image within the surrounding
|
|
772
|
+
text; one of the class constants: 'InlineImage.LEFT' and
|
|
773
|
+
'InlineImage.RIGHT' make the image float aside the text,
|
|
774
|
+
'InlineImage.TOP', 'InlineImage.BOTTOM' , 'InlineImage.MIDDLE'
|
|
775
|
+
align the image horizontally within the text line. 'None' let's the
|
|
776
|
+
output backend decide the best image position.
|
|
777
|
+
width -- output image width as lcg.Unit subclass instance. If only
|
|
778
|
+
one of width/height is specified (not None), the other dimension is
|
|
779
|
+
computed to maintain the original image proportions. Note that
|
|
780
|
+
various output backends may only support certain units.
|
|
781
|
+
height -- output image height as lcg.Unit subclass instance. See 'width'.
|
|
782
|
+
standalone -- iff true then never put the image inside a paragraph.
|
|
783
|
+
|
|
784
|
+
All other keyword arguments are passed to the parent class constructor.
|
|
785
|
+
|
|
786
|
+
"""
|
|
787
|
+
assert isinstance(image, (lcg.Image, basestring)), image
|
|
788
|
+
assert align in (None, self.LEFT, self.RIGHT, self.TOP, self.BOTTOM, self.MIDDLE), align
|
|
789
|
+
assert width is None or isinstance(width, lcg.Unit), width
|
|
790
|
+
assert height is None or isinstance(height, lcg.Unit), height
|
|
791
|
+
assert isinstance(standalone, bool), standalone
|
|
792
|
+
self._image = image
|
|
793
|
+
self._align = align
|
|
794
|
+
self._width = width
|
|
795
|
+
self._height = height
|
|
796
|
+
self._standalone = standalone
|
|
797
|
+
super(InlineImage, self).__init__(**kwargs)
|
|
798
|
+
|
|
799
|
+
def image(self, context):
|
|
800
|
+
"""Return the value of 'image' passed to the constructor.
|
|
801
|
+
|
|
802
|
+
If the 'image' was passed as a string reference, it is automatically
|
|
803
|
+
converted into an 'lcg.Image' instance.
|
|
804
|
+
|
|
805
|
+
"""
|
|
806
|
+
return self._resource_instance(context, self._image, lcg.Image)
|
|
807
|
+
|
|
808
|
+
def align(self):
|
|
809
|
+
"""Return the value of 'align' as passed to the constructor."""
|
|
810
|
+
return self._align
|
|
811
|
+
|
|
812
|
+
def width(self):
|
|
813
|
+
"""Return the value of 'width' as passed to the constructor."""
|
|
814
|
+
return self._width
|
|
815
|
+
|
|
816
|
+
def height(self):
|
|
817
|
+
"""Return the value of 'height' as passed to the constructor."""
|
|
818
|
+
return self._height
|
|
819
|
+
|
|
820
|
+
def standalone(self):
|
|
821
|
+
"""Return the value of 'standalone' as passed to the constructor."""
|
|
822
|
+
return self._standalone
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
class InlineAudio(_InlineObject):
|
|
826
|
+
"""Audio file embedded inside the document.
|
|
827
|
+
|
|
828
|
+
When possible, this element should be represented by an interactive control
|
|
829
|
+
which invokes playback of the embedded audio.
|
|
830
|
+
|
|
831
|
+
"""
|
|
832
|
+
|
|
833
|
+
def __init__(self, audio, image=None, **kwargs):
|
|
834
|
+
"""Arguments:
|
|
835
|
+
|
|
836
|
+
audio -- the target audio file as 'lcg.Audio' resource instance.
|
|
837
|
+
image -- visual presentation image as an 'lcg.Image' resource instance or None.
|
|
838
|
+
|
|
839
|
+
All other arguments are passed to the parent class constructor.
|
|
840
|
+
|
|
841
|
+
"""
|
|
842
|
+
assert isinstance(audio, (lcg.Audio, basestring)), audio
|
|
843
|
+
assert image is None or isinstance(image, (lcg.Image, basestring)), image
|
|
844
|
+
self._audio = audio
|
|
845
|
+
self._image = image
|
|
846
|
+
super(InlineAudio, self).__init__(**kwargs)
|
|
847
|
+
|
|
848
|
+
def audio(self, context):
|
|
849
|
+
"""Return the value of 'audio' as passed to the constructor.
|
|
850
|
+
|
|
851
|
+
If the 'audio' was passed as a string reference, it is automatically
|
|
852
|
+
converted into an 'lcg.Audio' instance.
|
|
853
|
+
|
|
854
|
+
"""
|
|
855
|
+
return self._resource_instance(context, self._audio, lcg.Audio)
|
|
856
|
+
|
|
857
|
+
def image(self, context):
|
|
858
|
+
"""Return the value of 'image' as passed to the constructor.
|
|
859
|
+
|
|
860
|
+
If the 'image' was passed as a string reference, it is automatically
|
|
861
|
+
converted into an 'lcg.Image' instance.
|
|
862
|
+
|
|
863
|
+
"""
|
|
864
|
+
return self._resource_instance(context, self._image, lcg.Image)
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
class InlineVideo(_InlineObject):
|
|
868
|
+
"""Video file embedded inside the document.
|
|
869
|
+
|
|
870
|
+
When possible, this element should be represented as an embedded video
|
|
871
|
+
player preloaded with given video and playing it on invocation.
|
|
872
|
+
|
|
873
|
+
"""
|
|
874
|
+
|
|
875
|
+
def __init__(self, video, image=None, size=None, **kwargs):
|
|
876
|
+
"""Arguments:
|
|
877
|
+
|
|
878
|
+
video -- 'lcg.Video' resource instance.
|
|
879
|
+
image -- video thumbnail image as an 'lcg.Image' resource instance or None.
|
|
880
|
+
size -- size in pixels as a tuple of two integers (WIDTH, HEIGHT)
|
|
881
|
+
|
|
882
|
+
All other keyword arguments are passed to the parent class constructor.
|
|
883
|
+
|
|
884
|
+
"""
|
|
885
|
+
assert isinstance(video, (lcg.Video, basestring)), video
|
|
886
|
+
assert image is None or isinstance(image, (lcg.Image, basestring)), image
|
|
887
|
+
assert size is None or isinstance(size, tuple), size
|
|
888
|
+
self._video = video
|
|
889
|
+
self._image = image
|
|
890
|
+
self._size = size
|
|
891
|
+
super(InlineVideo, self).__init__(**kwargs)
|
|
892
|
+
|
|
893
|
+
def video(self, context):
|
|
894
|
+
"""Return the value of 'video' as passed to the constructor.
|
|
895
|
+
|
|
896
|
+
If the 'video' was passed as a string reference, it is automatically
|
|
897
|
+
converted into an 'lcg.Video' instance.
|
|
898
|
+
|
|
899
|
+
"""
|
|
900
|
+
return self._resource_instance(context, self._video, lcg.Video)
|
|
901
|
+
|
|
902
|
+
def image(self, context):
|
|
903
|
+
"""Return the value of 'image' as passed to the constructor.
|
|
904
|
+
|
|
905
|
+
If the 'image' was passed as a string reference, it is automatically
|
|
906
|
+
converted into an 'lcg.Image' instance.
|
|
907
|
+
|
|
908
|
+
"""
|
|
909
|
+
return self._resource_instance(context, self._image, lcg.Image)
|
|
910
|
+
|
|
911
|
+
def size(self):
|
|
912
|
+
"""Return the value of 'size' as passed to the constructor."""
|
|
913
|
+
return self._size
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
class InlineExternalVideo(Content):
|
|
917
|
+
"""Embedded video from external services such as YouTube or Vimeo
|
|
918
|
+
|
|
919
|
+
When possible, this element should be represented as an embedded video
|
|
920
|
+
player preloaded with given video and playing it on invocation.
|
|
921
|
+
|
|
922
|
+
"""
|
|
923
|
+
|
|
924
|
+
def __init__(self, service, video_id, title=None, descr=None, size=None, lang=None):
|
|
925
|
+
"""Arguments:
|
|
926
|
+
|
|
927
|
+
service -- string identifier of the video service. The two currently
|
|
928
|
+
supported services are 'youtube' and 'vimeo'.
|
|
929
|
+
video_id -- string identifier of the video within the given service.
|
|
930
|
+
title -- video title as a string or None.
|
|
931
|
+
size -- explicit video size in pixels as a tuple of two integers
|
|
932
|
+
(WIDTH, HEIGHT) or None for the default size.
|
|
933
|
+
lang -- content language as an ISO 639-1 Alpha-2 language code (lowercase)
|
|
934
|
+
|
|
935
|
+
"""
|
|
936
|
+
assert service in ('youtube', 'vimeo'), service
|
|
937
|
+
assert isinstance(video_id, basestring), video_id
|
|
938
|
+
assert size is None or isinstance(size, tuple), size
|
|
939
|
+
self._service = service
|
|
940
|
+
self._video_id = video_id
|
|
941
|
+
self._title = title
|
|
942
|
+
self._descr = descr
|
|
943
|
+
self._size = size
|
|
944
|
+
super(InlineExternalVideo, self).__init__(lang=lang)
|
|
945
|
+
|
|
946
|
+
def service(self):
|
|
947
|
+
"""Return the string identifier of the video service."""
|
|
948
|
+
return self._service
|
|
949
|
+
|
|
950
|
+
def video_id(self):
|
|
951
|
+
"""Return the string identifier of the video within the service."""
|
|
952
|
+
return self._video_id
|
|
953
|
+
|
|
954
|
+
def title(self):
|
|
955
|
+
"""Return the value of 'title' as passed to the constructor."""
|
|
956
|
+
return self._title
|
|
957
|
+
|
|
958
|
+
def descr(self):
|
|
959
|
+
"""Return the value of 'descr' as passed to the constructor."""
|
|
960
|
+
return self._descr
|
|
961
|
+
|
|
962
|
+
def size(self):
|
|
963
|
+
"""Return the video size in pixels as a pair of integers or None."""
|
|
964
|
+
return self._size
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
class Title(Content):
|
|
968
|
+
"""Inline element, which is substituted by the title of the requested item in export time.
|
|
969
|
+
|
|
970
|
+
It may not be possible to find out what is the title of the current node or some other element
|
|
971
|
+
in the time of content construction. So this symbolic element may be used to refer to the item
|
|
972
|
+
an it will be simply replaced by the title text in export time.
|
|
973
|
+
|
|
974
|
+
The constructor argument 'id' may be used to refer to the required item. It may be a section
|
|
975
|
+
id in the current page or an identifier of another node. The default value (None) refers to
|
|
976
|
+
the title of the current node. If the item refered by id can not be found, the id itself is
|
|
977
|
+
used for substitution.
|
|
978
|
+
|
|
979
|
+
"""
|
|
980
|
+
|
|
981
|
+
def __init__(self, id=None):
|
|
982
|
+
"""Arguments:
|
|
983
|
+
|
|
984
|
+
id -- reference to the item providing the required title; it may be a
|
|
985
|
+
section id (??? what is it ???) in the current page or an
|
|
986
|
+
identifier of another node (??? what is it ???). 'None' refers to
|
|
987
|
+
the title of the current node. If the item refered by 'id' cannot
|
|
988
|
+
be found, the id itself is used for substitution.
|
|
989
|
+
|
|
990
|
+
"""
|
|
991
|
+
super(Title, self).__init__()
|
|
992
|
+
self._id = id
|
|
993
|
+
|
|
994
|
+
def id(self):
|
|
995
|
+
"""Return the value of 'id' as passed to the constructor."""
|
|
996
|
+
return self._id
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
class HorizontalSeparator(Content):
|
|
1000
|
+
"""Horizontal separator.
|
|
1001
|
+
|
|
1002
|
+
Typically rendered as a horizontal line separating visually the content
|
|
1003
|
+
above and below it.
|
|
1004
|
+
|
|
1005
|
+
"""
|
|
1006
|
+
|
|
1007
|
+
def __init__(self, thickness=None, color=None, **kwargs):
|
|
1008
|
+
"""Arguments:
|
|
1009
|
+
thickness -- Thickness of the line as 'lcg.Unit' or None for the (media
|
|
1010
|
+
dependent) default thickness. Currently only supported by the PDF backend.
|
|
1011
|
+
color -- line color as 'lcg.Color' or None for the (media dependent)
|
|
1012
|
+
default color. Currently only supported by the PDF backend.
|
|
1013
|
+
"""
|
|
1014
|
+
assert thickness is None or isinstance(thickness, lcg.Unit), thickness
|
|
1015
|
+
assert color is None or isinstance(color, lcg.Color), color
|
|
1016
|
+
self._thickness = thickness
|
|
1017
|
+
self._color = color
|
|
1018
|
+
super(HorizontalSeparator, self).__init__(**kwargs)
|
|
1019
|
+
|
|
1020
|
+
def thickness(self):
|
|
1021
|
+
return self._thickness
|
|
1022
|
+
|
|
1023
|
+
def color(self):
|
|
1024
|
+
return self._color
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
class NewPage(Content):
|
|
1028
|
+
"""New page starts here."""
|
|
1029
|
+
pass
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
class NewLine(Content):
|
|
1033
|
+
"""Explicit line break."""
|
|
1034
|
+
pass
|
|
1035
|
+
|
|
1036
|
+
|
|
1037
|
+
class PageNumber(Content):
|
|
1038
|
+
"""Current page number.
|
|
1039
|
+
|
|
1040
|
+
The page number is generated as an ordinal arabic number starting from one.
|
|
1041
|
+
|
|
1042
|
+
This content may be used only inside page headers and footers.
|
|
1043
|
+
|
|
1044
|
+
"""
|
|
1045
|
+
|
|
1046
|
+
def __init__(self, total=False, separator=None, lang=None):
|
|
1047
|
+
"""
|
|
1048
|
+
Arguments:
|
|
1049
|
+
|
|
1050
|
+
total -- iff true, output not only the page number, but also the
|
|
1051
|
+
total number of pages; if 'separator' is 'None', output only the
|
|
1052
|
+
total number of pages
|
|
1053
|
+
separator -- basestring or 'None'; if it is a basestring and 'total'
|
|
1054
|
+
is true, insert it betwenn the page numebr and the total number of
|
|
1055
|
+
pages
|
|
1056
|
+
lang -- content language as an ISO 639-1 Alpha-2 language code (lowercase)
|
|
1057
|
+
|
|
1058
|
+
"""
|
|
1059
|
+
super(PageNumber, self).__init__(lang=lang)
|
|
1060
|
+
self._total = total
|
|
1061
|
+
self._separator = separator
|
|
1062
|
+
|
|
1063
|
+
def total(self):
|
|
1064
|
+
"""Return the value of 'total' as passed to the constructor."""
|
|
1065
|
+
return self._total
|
|
1066
|
+
|
|
1067
|
+
def separator(self):
|
|
1068
|
+
"""Return the value of 'separator' as passed to the constructor."""
|
|
1069
|
+
return self._separator
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
class PageHeading(Content):
|
|
1073
|
+
"""Current page heading.
|
|
1074
|
+
|
|
1075
|
+
This content may be used only inside page headers and footers.
|
|
1076
|
+
|
|
1077
|
+
"""
|
|
1078
|
+
pass
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
class HSpace(Content):
|
|
1082
|
+
"""Horizontal space of given size.
|
|
1083
|
+
|
|
1084
|
+
This should be used only in places where explicit space is needed. In many
|
|
1085
|
+
cases better means such as higher level elements, alignment or style sheets
|
|
1086
|
+
can be used.
|
|
1087
|
+
|
|
1088
|
+
"""
|
|
1089
|
+
|
|
1090
|
+
def __init__(self, size, lang=None):
|
|
1091
|
+
"""
|
|
1092
|
+
@type: L{lcg.Unit}
|
|
1093
|
+
@param: Size of the space.
|
|
1094
|
+
@type: string
|
|
1095
|
+
@param: Content language as an ISO 639-1 Alpha-2 language code (lowercase).
|
|
1096
|
+
"""
|
|
1097
|
+
super(HSpace, self).__init__(lang=lang)
|
|
1098
|
+
assert isinstance(size, lcg.Unit), size
|
|
1099
|
+
self._size = size
|
|
1100
|
+
|
|
1101
|
+
def size(self, context):
|
|
1102
|
+
"""Return the value of 'size' as passed to the constructor."""
|
|
1103
|
+
return self._size
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
class VSpace(HSpace):
|
|
1107
|
+
"""Vertical space of given size.
|
|
1108
|
+
|
|
1109
|
+
This should be used only in places where explicit space is needed. In many
|
|
1110
|
+
cases better means such as higher level elements, alignment or style sheets
|
|
1111
|
+
can be used.
|
|
1112
|
+
|
|
1113
|
+
"""
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
class HtmlContent(Content):
|
|
1117
|
+
"""Special element for dirtect HTML embedding in LCG content hierarchy.
|
|
1118
|
+
|
|
1119
|
+
This class only supports HTML export and an attempt to export an element of
|
|
1120
|
+
this class to any other target format will lead to an error.
|
|
1121
|
+
|
|
1122
|
+
The HTML content may be passed directly as a Python string or as a Python
|
|
1123
|
+
function which will be called to generate the content dynamically in export
|
|
1124
|
+
time. The function will be called with at least two arguments (context,
|
|
1125
|
+
element), where 'element' is the 'HtmlContent' instance itself and
|
|
1126
|
+
'context' is a 'wiking.Exporter.Context' instance. Any positional
|
|
1127
|
+
arguments passed to the constructor are also passed to the export function.
|
|
1128
|
+
|
|
1129
|
+
Use with caution. Defining specific content classes for more generic
|
|
1130
|
+
content elements is encouraged over using this class. This class should
|
|
1131
|
+
only be used for simple cases where defining a class makes too much
|
|
1132
|
+
unnecessary noise...
|
|
1133
|
+
|
|
1134
|
+
This class demonstrates a content element, which exports itself actively
|
|
1135
|
+
and doesn't rely on the exporter as the other generic elements defined in
|
|
1136
|
+
this module.
|
|
1137
|
+
|
|
1138
|
+
"""
|
|
1139
|
+
|
|
1140
|
+
def __init__(self, content, *args, **kwargs):
|
|
1141
|
+
"""Initialize the instance.
|
|
1142
|
+
|
|
1143
|
+
Arguments:
|
|
1144
|
+
|
|
1145
|
+
content -- the actual HTML content of this element as a basestring
|
|
1146
|
+
or an export function (see class docstring for details).
|
|
1147
|
+
*args -- additional positional arguments passed to the export
|
|
1148
|
+
function (given in 'content'). No arguments are allowed when
|
|
1149
|
+
'content' is a basestring.
|
|
1150
|
+
kwargs -- keyword arguments for parent class constructor.
|
|
1151
|
+
|
|
1152
|
+
"""
|
|
1153
|
+
assert isinstance(content, basestring) or callable(content), content
|
|
1154
|
+
self._content = content
|
|
1155
|
+
self._export_args = args
|
|
1156
|
+
super(HtmlContent, self).__init__(**kwargs)
|
|
1157
|
+
|
|
1158
|
+
def export(self, context):
|
|
1159
|
+
assert isinstance(context.exporter(), lcg.HtmlExporter), \
|
|
1160
|
+
"Only HTML export is supported for this element."
|
|
1161
|
+
if isinstance(self._content, basestring):
|
|
1162
|
+
g = context.generator()
|
|
1163
|
+
return g.noescape(self._content)
|
|
1164
|
+
else:
|
|
1165
|
+
return self._content(context, self, *self._export_args)
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
class Heading(Container):
|
|
1169
|
+
"""Heading, e.g. heading of a section.
|
|
1170
|
+
|
|
1171
|
+
Purpose of this element is twofold: to provide information about level of
|
|
1172
|
+
the heading and to distinguish the element from other parts of a document
|
|
1173
|
+
for the purpose of applying specific styles to its content.
|
|
1174
|
+
|
|
1175
|
+
"""
|
|
1176
|
+
|
|
1177
|
+
def __init__(self, content, level, **kwargs):
|
|
1178
|
+
"""
|
|
1179
|
+
Arguments:
|
|
1180
|
+
|
|
1181
|
+
cotnent -- the actual content of this element as lcg.Content element
|
|
1182
|
+
or their sequence
|
|
1183
|
+
level -- level of the heading, positive integer, starting from 1
|
|
1184
|
+
kwargs -- keyword arguments for parent class constructor
|
|
1185
|
+
|
|
1186
|
+
"""
|
|
1187
|
+
assert isinstance(level, int) and level > 0, level
|
|
1188
|
+
super(Heading, self).__init__(content, **kwargs)
|
|
1189
|
+
self._level = level
|
|
1190
|
+
|
|
1191
|
+
def level(self):
|
|
1192
|
+
"Return level of the heading, positive integer, starting from 1."
|
|
1193
|
+
return self._level
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
class PreformattedText(TextContent):
|
|
1197
|
+
"""Preformatted text."""
|
|
1198
|
+
|
|
1199
|
+
def __init__(self, text, mime_type=None, **kwargs):
|
|
1200
|
+
"""Arguments:
|
|
1201
|
+
|
|
1202
|
+
text -- the text itself as a basestring.
|
|
1203
|
+
mime_type -- MIME type of the text as a basestring. Used for
|
|
1204
|
+
syntax highlighting in HTML export.
|
|
1205
|
+
|
|
1206
|
+
"""
|
|
1207
|
+
super(PreformattedText, self).__init__(text, **kwargs)
|
|
1208
|
+
self._mime_type = mime_type
|
|
1209
|
+
|
|
1210
|
+
def mime_type(self):
|
|
1211
|
+
"""Return MIME type of the text if it was passed to the constructor or None."""
|
|
1212
|
+
return self._mime_type
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
# ======================== Block level elements ========================
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
SectionContainer = Container
|
|
1219
|
+
"""Deprecated: Use 'Container' instead."""
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
class Paragraph(Container):
|
|
1223
|
+
"""A paragraph of text, where the text can be any 'Content'."""
|
|
1224
|
+
|
|
1225
|
+
def __init__(self, content, noindent=False, **kwargs):
|
|
1226
|
+
"""Arguments:
|
|
1227
|
+
|
|
1228
|
+
content -- sequence of list items as 'Content' instances.
|
|
1229
|
+
noindent -- True if first line indentation should be suppressed.
|
|
1230
|
+
Note, that only PDF export currently supports first paragraph line
|
|
1231
|
+
indentation.
|
|
1232
|
+
|
|
1233
|
+
"""
|
|
1234
|
+
assert isinstance(noindent, bool)
|
|
1235
|
+
self._noindent = noindent
|
|
1236
|
+
super(Paragraph, self).__init__(content, **kwargs)
|
|
1237
|
+
|
|
1238
|
+
def noindent(self):
|
|
1239
|
+
"""Return 'noindent' as it was given in the constructor."""
|
|
1240
|
+
return self._noindent
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
class ItemizedList(Container):
|
|
1244
|
+
"""An itemized list."""
|
|
1245
|
+
|
|
1246
|
+
NUMERIC = 'numeric'
|
|
1247
|
+
LOWER_ALPHA = 'lower-alpha'
|
|
1248
|
+
UPPER_ALPHA = 'upper-alpha'
|
|
1249
|
+
|
|
1250
|
+
def __init__(self, content, order=None, **kwargs):
|
|
1251
|
+
"""Arguments:
|
|
1252
|
+
|
|
1253
|
+
content -- sequence of list items as 'Content' instances.
|
|
1254
|
+
order -- one of class constants determining the list item ordering style or
|
|
1255
|
+
None for an unordered list (bullet list).
|
|
1256
|
+
|
|
1257
|
+
"""
|
|
1258
|
+
assert order in (None, self.LOWER_ALPHA, self.UPPER_ALPHA, self.NUMERIC)
|
|
1259
|
+
self._order = order
|
|
1260
|
+
super(ItemizedList, self).__init__(content, **kwargs)
|
|
1261
|
+
|
|
1262
|
+
def order(self):
|
|
1263
|
+
"""Return the value of 'order' as passed to the constructor."""
|
|
1264
|
+
return self._order
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
class DefinitionList(Container):
|
|
1268
|
+
"""A list of definitions.
|
|
1269
|
+
|
|
1270
|
+
The constructor accepts a sequence of definitions, where each definition is a pair '(TERM,
|
|
1271
|
+
DESCRIPTION)', where both items are 'Content' instances.
|
|
1272
|
+
|
|
1273
|
+
"""
|
|
1274
|
+
_SUBSEQUENCES = True
|
|
1275
|
+
_SUBSEQUENCE_LENGTH = 2
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
|
+
class FieldSet(Container):
|
|
1279
|
+
"""A list of label and value pairs.
|
|
1280
|
+
|
|
1281
|
+
The constructor accepts a sequence of '(LABEL, VALUE)' pairs, where both
|
|
1282
|
+
items are 'Content' instances. Logically similar to 'DefinitionList', but
|
|
1283
|
+
more suitable for an enumeration of shorter fields presented side by side
|
|
1284
|
+
(while definition description usually follows on another line below the
|
|
1285
|
+
term).
|
|
1286
|
+
|
|
1287
|
+
"""
|
|
1288
|
+
_SUBSEQUENCES = True
|
|
1289
|
+
_SUBSEQUENCE_LENGTH = 2
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
class TableCell(Container):
|
|
1293
|
+
"""Table cell is a container of cell content and may appear within 'TableRow'."""
|
|
1294
|
+
LEFT = lcg.HorizontalAlignment.LEFT
|
|
1295
|
+
RIGHT = lcg.HorizontalAlignment.RIGHT
|
|
1296
|
+
CENTER = lcg.HorizontalAlignment.CENTER
|
|
1297
|
+
|
|
1298
|
+
def __init__(self, content, align=None, **kwargs):
|
|
1299
|
+
"""Arguments:
|
|
1300
|
+
|
|
1301
|
+
align -- requested cell content alignment, 'None' or one of the
|
|
1302
|
+
constants 'TableCell.LEFT', 'TableCell.RIGHT', 'TableCell.CENTER'
|
|
1303
|
+
or 'None' for the default alignment.
|
|
1304
|
+
|
|
1305
|
+
"""
|
|
1306
|
+
assert align in (None, self.LEFT, self.RIGHT, self.CENTER), align
|
|
1307
|
+
self._align = align
|
|
1308
|
+
super(TableCell, self).__init__(content, **kwargs)
|
|
1309
|
+
|
|
1310
|
+
def align(self):
|
|
1311
|
+
"""Return the value of 'align' as passed to the constructor."""
|
|
1312
|
+
return self._align
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
class TableHeading(TableCell):
|
|
1316
|
+
"""Table heading is a container of heading content and may appear within 'TableRow'."""
|
|
1317
|
+
pass
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
class TableRow(Container):
|
|
1321
|
+
"""Table row is a container of cells or headings and may appear within 'Table'."""
|
|
1322
|
+
_ALLOWED_CONTENT = (TableCell, TableHeading)
|
|
1323
|
+
|
|
1324
|
+
def __init__(self, content, line_above=None, line_below=None, iterated=False, **kwargs):
|
|
1325
|
+
"""Arguments:
|
|
1326
|
+
|
|
1327
|
+
line_above -- iff true, request line above the row
|
|
1328
|
+
line_below -- iff true, request line below the row
|
|
1329
|
+
iterated -- iff true, this row expands into any number of rows
|
|
1330
|
+
according to the iterator variable used within one or more of its
|
|
1331
|
+
cells
|
|
1332
|
+
|
|
1333
|
+
"""
|
|
1334
|
+
self._line_above = line_above
|
|
1335
|
+
self._line_below = line_below
|
|
1336
|
+
self._iterated = iterated
|
|
1337
|
+
super(TableRow, self).__init__(content, **kwargs)
|
|
1338
|
+
|
|
1339
|
+
def line_above(self):
|
|
1340
|
+
return self._line_above
|
|
1341
|
+
|
|
1342
|
+
def line_below(self):
|
|
1343
|
+
return self._line_below
|
|
1344
|
+
|
|
1345
|
+
def set_line_below(self, n):
|
|
1346
|
+
self._line_below = n
|
|
1347
|
+
|
|
1348
|
+
def iterated(self):
|
|
1349
|
+
return self._iterated
|
|
1350
|
+
|
|
1351
|
+
|
|
1352
|
+
class Table(Container):
|
|
1353
|
+
"""Table is a container of 'TableRow' instances."""
|
|
1354
|
+
_ALLOWED_CONTENT = (TableRow, HorizontalSeparator,)
|
|
1355
|
+
|
|
1356
|
+
def __init__(self, content, title=None, long=False, column_widths=None, bars=(),
|
|
1357
|
+
compact=False, transformations=('facing', 'transpose',), **kwargs):
|
|
1358
|
+
"""Arguments:
|
|
1359
|
+
|
|
1360
|
+
content -- sequence of 'TableRow' and 'HorizontalSeparator' instances
|
|
1361
|
+
title -- 'None' or table caption as a string or unicode
|
|
1362
|
+
long -- boolean indicating whether the table is long. Long table is
|
|
1363
|
+
usually a table that may not fit on a single page. Long tables may
|
|
1364
|
+
be handled in a different way, e.g. by splitting into several parts
|
|
1365
|
+
even when the table could fit on a new separate page or by using
|
|
1366
|
+
different column widths on each page when the column widths are
|
|
1367
|
+
variable.
|
|
1368
|
+
column_widths -- sequence of 'lcg.Unit's defining widths of columns, it
|
|
1369
|
+
must have the same number of elements and in the same order as
|
|
1370
|
+
table columns. If any of the elements is 'None', the width of the
|
|
1371
|
+
corresponding column is to be determined automatically.
|
|
1372
|
+
Alternatively the whole 'column_widths' value may be 'None' to
|
|
1373
|
+
determine widths of all the columns automatically.
|
|
1374
|
+
bars -- sequence of vertical bars positions, integers. The position
|
|
1375
|
+
before the first column is numbered 0, the next position is 1 and
|
|
1376
|
+
the position after the last column is numbered N where N is the
|
|
1377
|
+
number of columns in the table.
|
|
1378
|
+
compact -- If True, suppress default padding in all table cells
|
|
1379
|
+
(including headings). Use containers to add custom padding. Only
|
|
1380
|
+
implemented in PDF output.
|
|
1381
|
+
transformations -- permitted table transformations; currently used
|
|
1382
|
+
only in Braille backend. It's a sequence containing any of the
|
|
1383
|
+
following strings: 'facing' (table may be spread across facing
|
|
1384
|
+
pages), 'transpose' (table may be transposed), 'row-expand' (table
|
|
1385
|
+
may be expanded by rows), 'column-expand' (table may be expanded by
|
|
1386
|
+
columns), 'split' (table may be vertically split into several
|
|
1387
|
+
narrower tables).
|
|
1388
|
+
|
|
1389
|
+
"""
|
|
1390
|
+
assert title is None or isinstance(title, basestring)
|
|
1391
|
+
assert isinstance(long, bool), long
|
|
1392
|
+
assert column_widths is None or isinstance(column_widths, (tuple, list)), column_widths
|
|
1393
|
+
assert isinstance(bars, (tuple, list)), bars
|
|
1394
|
+
assert isinstance(compact, bool), compact
|
|
1395
|
+
self._title = title
|
|
1396
|
+
self._long = long
|
|
1397
|
+
self._column_widths = column_widths
|
|
1398
|
+
self._bars = bars
|
|
1399
|
+
self._compact = compact
|
|
1400
|
+
self._transformations = transformations
|
|
1401
|
+
super(Table, self).__init__(content, **kwargs)
|
|
1402
|
+
|
|
1403
|
+
def title(self):
|
|
1404
|
+
"""Return the value of 'title' as passed to the constructor."""
|
|
1405
|
+
return self._title
|
|
1406
|
+
|
|
1407
|
+
def long(self):
|
|
1408
|
+
"""Return the value of 'long' as passed to the constructor."""
|
|
1409
|
+
return self._long
|
|
1410
|
+
|
|
1411
|
+
def column_widths(self):
|
|
1412
|
+
"""Return the value of 'column_widths' as passed to the constructor."""
|
|
1413
|
+
return self._column_widths
|
|
1414
|
+
|
|
1415
|
+
def bars(self):
|
|
1416
|
+
"""Return positions of vertical bars.
|
|
1417
|
+
|
|
1418
|
+
See '__init__()' documentation for information about the positions.
|
|
1419
|
+
|
|
1420
|
+
"""
|
|
1421
|
+
return self._bars
|
|
1422
|
+
|
|
1423
|
+
def compact(self):
|
|
1424
|
+
"""Return the value of 'compact' passed to the constructor."""
|
|
1425
|
+
return self._compact
|
|
1426
|
+
|
|
1427
|
+
def transformations(self):
|
|
1428
|
+
"""Return the value of 'transformations' as passed to the constructor."""
|
|
1429
|
+
return self._transformations
|
|
1430
|
+
|
|
1431
|
+
|
|
1432
|
+
class Section(Container):
|
|
1433
|
+
"""Section wraps the subordinary contents into an inline section.
|
|
1434
|
+
|
|
1435
|
+
Section is very similar to a 'Container', but there are a few
|
|
1436
|
+
differences:
|
|
1437
|
+
|
|
1438
|
+
* Every section has a title, which appears in the output document as a
|
|
1439
|
+
heading.
|
|
1440
|
+
|
|
1441
|
+
* Sections can be used as link targets. You can define section id
|
|
1442
|
+
explicitly or it is assigned automatically. The automatically assigned
|
|
1443
|
+
ids are based on section order and hierarchy, so they will not change
|
|
1444
|
+
across several LCG invocations as long as the hierarchy remains
|
|
1445
|
+
unchanged.
|
|
1446
|
+
|
|
1447
|
+
* Sections are numbered. Each section knows its number within its
|
|
1448
|
+
container.
|
|
1449
|
+
|
|
1450
|
+
"""
|
|
1451
|
+
_ID_PREFIX = 'sec'
|
|
1452
|
+
|
|
1453
|
+
def __init__(self, title, content, heading=None, id=None, anchor=None,
|
|
1454
|
+
descr=None, in_toc=True, **kwargs):
|
|
1455
|
+
"""Arguments:
|
|
1456
|
+
|
|
1457
|
+
title -- plain text section title; basestring
|
|
1458
|
+
content -- the actual content wrapped into this section as a
|
|
1459
|
+
sequence of 'Content' instances in the order in which they should
|
|
1460
|
+
appear in the output
|
|
1461
|
+
heading -- content to be used as section heading. By default (when
|
|
1462
|
+
None), the content is created automatically as TextContent(title),
|
|
1463
|
+
but you may pass any lcg.Content instance when some more fancy
|
|
1464
|
+
content is desired.
|
|
1465
|
+
anchor -- deprecated - use 'id' instead.
|
|
1466
|
+
in_toc -- a boolean flag indicating whether this section is supposed
|
|
1467
|
+
to be included in the Table of Contents
|
|
1468
|
+
|
|
1469
|
+
Other keyword arguments are defined by parent classes.
|
|
1470
|
+
|
|
1471
|
+
"""
|
|
1472
|
+
if anchor:
|
|
1473
|
+
assert id is None
|
|
1474
|
+
id = anchor
|
|
1475
|
+
assert isinstance(title, basestring), title
|
|
1476
|
+
assert heading is None or isinstance(heading, Content), heading
|
|
1477
|
+
assert isinstance(in_toc, bool), in_toc
|
|
1478
|
+
self._title = title
|
|
1479
|
+
self._in_toc = in_toc
|
|
1480
|
+
self._descr = descr
|
|
1481
|
+
self._heading = heading or TextContent(title)
|
|
1482
|
+
super(Section, self).__init__(content, id=id, **kwargs)
|
|
1483
|
+
|
|
1484
|
+
def section_path(self):
|
|
1485
|
+
"""Return the sequence of parent sections in the container hierarchy.
|
|
1486
|
+
|
|
1487
|
+
The returned value is a list of 'Section' instances, which are above
|
|
1488
|
+
this section in the hieararchy. The document's top level section
|
|
1489
|
+
appears as the first and 'self' is always the last element of the list.
|
|
1490
|
+
|
|
1491
|
+
"""
|
|
1492
|
+
return [c for c in self.container_path() if isinstance(c, Section)]
|
|
1493
|
+
|
|
1494
|
+
def title(self):
|
|
1495
|
+
"""Return the section title as a basestring."""
|
|
1496
|
+
return self._title
|
|
1497
|
+
|
|
1498
|
+
def descr(self):
|
|
1499
|
+
"""Return a breif (but more verbose than title) description as a basestring or None."""
|
|
1500
|
+
return self._descr
|
|
1501
|
+
|
|
1502
|
+
def heading(self):
|
|
1503
|
+
"""Return section heading as a 'Heading' instance.
|
|
1504
|
+
|
|
1505
|
+
Return the content passed as 'heading' argument wrapped in the
|
|
1506
|
+
'Heading' element (denoting the actual section heading level depending
|
|
1507
|
+
on section position in the document).
|
|
1508
|
+
|
|
1509
|
+
"""
|
|
1510
|
+
return Heading(self._heading, level=len(self.section_path()) + 1)
|
|
1511
|
+
|
|
1512
|
+
def in_toc(self):
|
|
1513
|
+
"""Return true if the section is supposed to appear in TOC."""
|
|
1514
|
+
return self._in_toc
|
|
1515
|
+
|
|
1516
|
+
def id(self):
|
|
1517
|
+
"""Return unique section identifier as a string.
|
|
1518
|
+
|
|
1519
|
+
If 'id' was passed to the constructor, it is used. Otherwise the
|
|
1520
|
+
identifier is automatically generated. The generated id is unique
|
|
1521
|
+
within the parent node and does not change if the section structure
|
|
1522
|
+
remains unchanged (external references should work throughout multiple
|
|
1523
|
+
exports in this case).
|
|
1524
|
+
|
|
1525
|
+
"""
|
|
1526
|
+
def section_number(x):
|
|
1527
|
+
container = x.container()
|
|
1528
|
+
while container and not isinstance(container, Section) and container.container():
|
|
1529
|
+
container = container.container()
|
|
1530
|
+
if container:
|
|
1531
|
+
return list(container.sections()).index(x) + 1
|
|
1532
|
+
else:
|
|
1533
|
+
return 1
|
|
1534
|
+
if self._id is None:
|
|
1535
|
+
path = self.section_path()
|
|
1536
|
+
if len(path) >= 2:
|
|
1537
|
+
self._id = path[-2].id() + '.' + unistr(section_number(self))
|
|
1538
|
+
else:
|
|
1539
|
+
numbers = [unistr(section_number(x)) for x in path]
|
|
1540
|
+
self._id = self._ID_PREFIX + '.'.join(numbers)
|
|
1541
|
+
return self._id
|
|
1542
|
+
|
|
1543
|
+
# Temporary backwards compatibility.
|
|
1544
|
+
anchor = id
|
|
1545
|
+
|
|
1546
|
+
|
|
1547
|
+
class TableOfContents(Content):
|
|
1548
|
+
"""A table of contents which lists the content subtree.
|
|
1549
|
+
|
|
1550
|
+
This element works as a sort of macro, which expands to the hierarchical
|
|
1551
|
+
listing of content nodes and their subcontent (sections) on the output.
|
|
1552
|
+
|
|
1553
|
+
The items actually present in the table of contents depend on the content
|
|
1554
|
+
itself (i.e. hidden sections are omitted automatically) and on the arguments
|
|
1555
|
+
passed to the 'TableOfContents' constructor (limiting the hierachy depth,
|
|
1556
|
+
selection of the root item of the hierarchy, etc).
|
|
1557
|
+
|
|
1558
|
+
See also the 'NodeIndex' class if you need to display a hierarchy of nodes.
|
|
1559
|
+
|
|
1560
|
+
"""
|
|
1561
|
+
_TOC_ITEM_TYPE = Content
|
|
1562
|
+
|
|
1563
|
+
def __init__(self, item=None, title=None, depth=None, detailed=True, **kwargs):
|
|
1564
|
+
"""Arguments:
|
|
1565
|
+
item -- the place where to start in the content hierarchy tree as a
|
|
1566
|
+
'Container' instance instance or None. If not None, the table of
|
|
1567
|
+
contents will start from that point -- the contained sections (see
|
|
1568
|
+
'Container.sections()') will become top level items in the table.
|
|
1569
|
+
If None, the container of given 'TableOfContents' element itself is
|
|
1570
|
+
used so the result is a local Table of Contents at given place.
|
|
1571
|
+
See also 'Section' documentation for more information how section
|
|
1572
|
+
hierarchy representation is built.
|
|
1573
|
+
title -- the title of the index as a string or unicode
|
|
1574
|
+
depth -- hierarchy depth limit as an integer or 'None' for unlimited depth
|
|
1575
|
+
detailed -- boolean indicating whether the whole 'Content' hierarchy
|
|
1576
|
+
('True') or only 'lcg.ContentNode' hierarchy ('False') of the leaf
|
|
1577
|
+
nodes of the node tree will be included in the index. This argument
|
|
1578
|
+
has no effect when 'item' is a 'Container' instance.
|
|
1579
|
+
|
|
1580
|
+
All other arguments have the same meaning as in the parent class constructor.
|
|
1581
|
+
|
|
1582
|
+
"""
|
|
1583
|
+
assert item is None or isinstance(item, self._TOC_ITEM_TYPE)
|
|
1584
|
+
assert title is None or isinstance(title, basestring)
|
|
1585
|
+
assert depth is None or isinstance(depth, int)
|
|
1586
|
+
assert isinstance(detailed, bool)
|
|
1587
|
+
self._item = item
|
|
1588
|
+
self._title = title
|
|
1589
|
+
self._depth = depth
|
|
1590
|
+
self._detailed = detailed
|
|
1591
|
+
super(TableOfContents, self).__init__(**kwargs)
|
|
1592
|
+
|
|
1593
|
+
def _root_item(self):
|
|
1594
|
+
"""Return the position in the hierarchy where to start the table of contents."""
|
|
1595
|
+
item = self._item
|
|
1596
|
+
if not item:
|
|
1597
|
+
# Create a local table of contens automatically (taking this elements container as a
|
|
1598
|
+
# start point).
|
|
1599
|
+
assert isinstance(self._container, Container), self._container
|
|
1600
|
+
item = self._container
|
|
1601
|
+
return item
|
|
1602
|
+
|
|
1603
|
+
def title(self):
|
|
1604
|
+
"""Return the value of 'title' as passed to the constructor."""
|
|
1605
|
+
return self._title
|
|
1606
|
+
|
|
1607
|
+
def items(self, lang):
|
|
1608
|
+
"""Return the hierarchy of items present in the table of contents.
|
|
1609
|
+
|
|
1610
|
+
The returned value is a recursive structure. It is a list of pairs
|
|
1611
|
+
(tuples) ITEM, SUBITEMS, where ITEM is always either 'Section' or
|
|
1612
|
+
'lcg.ContentNode' instance and SUBITEMS is a nested list of the same
|
|
1613
|
+
type if given item has subitems displayed in the table of contents.
|
|
1614
|
+
Otherwise SUBITEMS is an empty list.
|
|
1615
|
+
|
|
1616
|
+
The method handles hidden items, depth limit and other conditions
|
|
1617
|
+
internally, so that the caller doesn't need to care which items belong
|
|
1618
|
+
to the table of contents. All returned items should be displayed.
|
|
1619
|
+
|
|
1620
|
+
"""
|
|
1621
|
+
def subitems(item, depth=None):
|
|
1622
|
+
if depth is not None:
|
|
1623
|
+
if depth <= 0:
|
|
1624
|
+
return []
|
|
1625
|
+
depth -= 1
|
|
1626
|
+
if isinstance(item, lcg.ContentNode):
|
|
1627
|
+
items = [node for node in item.children() if not node.hidden()]
|
|
1628
|
+
else:
|
|
1629
|
+
items = []
|
|
1630
|
+
if len(items) == 0 and self._detailed:
|
|
1631
|
+
if isinstance(item, (tuple, list)):
|
|
1632
|
+
items = item
|
|
1633
|
+
else:
|
|
1634
|
+
if isinstance(item, lcg.ContentNode):
|
|
1635
|
+
sections = item.content(lang).sections()
|
|
1636
|
+
else:
|
|
1637
|
+
sections = item.sections()
|
|
1638
|
+
items = [s for s in sections if s.in_toc()]
|
|
1639
|
+
return [(subitem, subitems(subitem, depth)) for subitem in items]
|
|
1640
|
+
return subitems(self._root_item(), depth=self._depth)
|
|
1641
|
+
|
|
1642
|
+
|
|
1643
|
+
class NodeIndex(TableOfContents):
|
|
1644
|
+
"""A table of contents which lists the node subtree of the current node.
|
|
1645
|
+
|
|
1646
|
+
This class is just a specific type of 'TableOfContents', which by default
|
|
1647
|
+
starts at the parent node of the content where it is used and which by
|
|
1648
|
+
default displays only nodes, not their inner conent (detailed=False).
|
|
1649
|
+
|
|
1650
|
+
"""
|
|
1651
|
+
_TOC_ITEM_TYPE = lcg.ContentNode
|
|
1652
|
+
|
|
1653
|
+
def __init__(self, title=None, node=None, depth=None, detailed=False):
|
|
1654
|
+
super(NodeIndex, self).__init__(node, title=title, depth=depth, detailed=detailed)
|
|
1655
|
+
|
|
1656
|
+
def _root_item(self):
|
|
1657
|
+
return self._item or self.parent()
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
class RootIndex(NodeIndex):
|
|
1661
|
+
"""'NodeIndex' starting from the top level node of the whole tree."""
|
|
1662
|
+
|
|
1663
|
+
def _root_item(self):
|
|
1664
|
+
return self.parent().root()
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
class NoneContent(Content):
|
|
1668
|
+
"""Deprecated.
|
|
1669
|
+
|
|
1670
|
+
Intended for places where 'Content' is required but there is nothing to put
|
|
1671
|
+
in. It is, however, actually useless, since 'Content' may be used for the
|
|
1672
|
+
same purpose.
|
|
1673
|
+
|
|
1674
|
+
"""
|
|
1675
|
+
pass
|
|
1676
|
+
|
|
1677
|
+
|
|
1678
|
+
class SetVariable(Content):
|
|
1679
|
+
"""Pseudo-content serving for setting node global variables.
|
|
1680
|
+
|
|
1681
|
+
This content doesn't produce any output, it just sets a node global
|
|
1682
|
+
variable value. It is used by structured text parser to set global
|
|
1683
|
+
variables at proper places.
|
|
1684
|
+
|
|
1685
|
+
"""
|
|
1686
|
+
|
|
1687
|
+
def __init__(self, name, value, **kwargs):
|
|
1688
|
+
"""
|
|
1689
|
+
Arguments:
|
|
1690
|
+
|
|
1691
|
+
name -- name of the variable, string
|
|
1692
|
+
value -- value of the variable, 'Content' instance
|
|
1693
|
+
|
|
1694
|
+
"""
|
|
1695
|
+
assert isinstance(name, basestring), name
|
|
1696
|
+
assert isinstance(value, Content), value
|
|
1697
|
+
self._name = name
|
|
1698
|
+
self._value = value
|
|
1699
|
+
super(SetVariable, self).__init__(**kwargs)
|
|
1700
|
+
|
|
1701
|
+
def name(self):
|
|
1702
|
+
"""Return name of the variable."""
|
|
1703
|
+
return self._name
|
|
1704
|
+
|
|
1705
|
+
def value(self):
|
|
1706
|
+
"""Return value of the variable as a 'Content' instance."""
|
|
1707
|
+
return self._value
|
|
1708
|
+
|
|
1709
|
+
|
|
1710
|
+
class Substitution(Content):
|
|
1711
|
+
"""Variable to be substituted by the actual value on export."""
|
|
1712
|
+
|
|
1713
|
+
def __init__(self, name, markup=None, **kwargs):
|
|
1714
|
+
"""
|
|
1715
|
+
Arguments:
|
|
1716
|
+
|
|
1717
|
+
name -- name of the variable, string, without $, may contain dots for
|
|
1718
|
+
nested variable lookup, eg. 'x.y'.
|
|
1719
|
+
markup -- original source markup (to be substituted if the value
|
|
1720
|
+
doesn't exist), eg. '$x.y' or '${x.y}'. If None, '$'+name is
|
|
1721
|
+
supposed.
|
|
1722
|
+
|
|
1723
|
+
"""
|
|
1724
|
+
assert isinstance(name, basestring), name
|
|
1725
|
+
assert markup is None or isinstance(markup, basestring), markup
|
|
1726
|
+
self._name = name
|
|
1727
|
+
self._markup = markup or '$' + name
|
|
1728
|
+
super(Substitution, self).__init__(**kwargs)
|
|
1729
|
+
|
|
1730
|
+
def name(self):
|
|
1731
|
+
"""Return name of the variable."""
|
|
1732
|
+
return self._name
|
|
1733
|
+
|
|
1734
|
+
def markup(self):
|
|
1735
|
+
"""Return source text representation of the variable."""
|
|
1736
|
+
return self._markup
|
|
1737
|
+
|
|
1738
|
+
|
|
1739
|
+
class Figure(Container):
|
|
1740
|
+
"""A container that can have a caption, typicaly for images"""
|
|
1741
|
+
|
|
1742
|
+
LEFT = 'left'
|
|
1743
|
+
RIGHT = 'right'
|
|
1744
|
+
|
|
1745
|
+
def __init__(self, content, caption=None, align=None, **kwargs):
|
|
1746
|
+
"""Arguments:
|
|
1747
|
+
|
|
1748
|
+
caption - caption text as lcg.Content or a series of lcg.Content objects
|
|
1749
|
+
|
|
1750
|
+
align - alignment and text wrapping around this container,
|
|
1751
|
+
possible values are 'left', 'right' or None
|
|
1752
|
+
"""
|
|
1753
|
+
super(Figure, self).__init__(content, **kwargs)
|
|
1754
|
+
self._caption = caption
|
|
1755
|
+
self._align = align
|
|
1756
|
+
|
|
1757
|
+
def caption(self):
|
|
1758
|
+
return self._caption
|
|
1759
|
+
|
|
1760
|
+
def align(self):
|
|
1761
|
+
return self._align
|
|
1762
|
+
|
|
1763
|
+
|
|
1764
|
+
class MathML(Content):
|
|
1765
|
+
"""Representation of MathML content for inclusion in LCG documents.
|
|
1766
|
+
|
|
1767
|
+
It works by making an instance containing a unicode representation of the
|
|
1768
|
+
MathML object. You can access the MathML content either by using
|
|
1769
|
+
'content()' method which returns the unicode or by using 'dom_content()'
|
|
1770
|
+
which returns a parsed DOM structure to work with.
|
|
1771
|
+
|
|
1772
|
+
It is expected that the MathML content is a presentation form of MathML 3.
|
|
1773
|
+
|
|
1774
|
+
"""
|
|
1775
|
+
class EntityHandler(object):
|
|
1776
|
+
"""Entity dictionary to be used in 'tree_content()' method.
|
|
1777
|
+
|
|
1778
|
+
This class provides just trivial implementation which always returns
|
|
1779
|
+
the entity name itself. Subclasses (or different) classes may provide
|
|
1780
|
+
other mechanisms, e.g. mapping the entities to the corresponding
|
|
1781
|
+
Unicode characters.
|
|
1782
|
+
|
|
1783
|
+
"""
|
|
1784
|
+
|
|
1785
|
+
def __getitem__(self, key):
|
|
1786
|
+
return key
|
|
1787
|
+
|
|
1788
|
+
def get(self, key, default=None):
|
|
1789
|
+
try:
|
|
1790
|
+
return self[key]
|
|
1791
|
+
except KeyError:
|
|
1792
|
+
return default
|
|
1793
|
+
|
|
1794
|
+
def __init__(self, content):
|
|
1795
|
+
"""
|
|
1796
|
+
Arguments:
|
|
1797
|
+
|
|
1798
|
+
content -- the MathML content represented as a MathML XML unicode
|
|
1799
|
+
|
|
1800
|
+
"""
|
|
1801
|
+
super(MathML, self).__init__()
|
|
1802
|
+
assert isinstance(content, basestring), content
|
|
1803
|
+
self._content = content
|
|
1804
|
+
|
|
1805
|
+
def content(self):
|
|
1806
|
+
"""Return the XML unicode content given in the constructor."""
|
|
1807
|
+
return self._content
|
|
1808
|
+
|
|
1809
|
+
def _str_content(self):
|
|
1810
|
+
content = self._content
|
|
1811
|
+
# Unfortunately even the etree parser doesn't work very well with
|
|
1812
|
+
# "custom" entities -- it discards them in attributes. So we have to
|
|
1813
|
+
# expand the entities manualy.
|
|
1814
|
+
regexp = re.compile('&([a-zA-Z]+);')
|
|
1815
|
+
i = 0
|
|
1816
|
+
while True:
|
|
1817
|
+
match = regexp.search(content[i:], re.M)
|
|
1818
|
+
if not match:
|
|
1819
|
+
break
|
|
1820
|
+
entity = match.group(1)
|
|
1821
|
+
start, end = match.span()
|
|
1822
|
+
if entity in ('amp', 'lt', 'gt', 'quot', 'apos', 'bsol', 'newline',):
|
|
1823
|
+
i += end
|
|
1824
|
+
else:
|
|
1825
|
+
from lcg.export.mathml import entities
|
|
1826
|
+
expansion = entities.get(entity)
|
|
1827
|
+
if expansion is None:
|
|
1828
|
+
i += end
|
|
1829
|
+
else:
|
|
1830
|
+
content = content[:i + start] + expansion + content[i + end:]
|
|
1831
|
+
i += start + len(expansion)
|
|
1832
|
+
content = content.encode('utf-8')
|
|
1833
|
+
return content
|
|
1834
|
+
|
|
1835
|
+
def _dom_content(self, element):
|
|
1836
|
+
from xml.etree import ElementTree
|
|
1837
|
+
top_elements = element.getElementsByTagName('math')
|
|
1838
|
+
assert len(top_elements) == 1
|
|
1839
|
+
top = top_elements[0]
|
|
1840
|
+
tree = ElementTree.Element(top.tagName)
|
|
1841
|
+
|
|
1842
|
+
def export(parent_tree, node):
|
|
1843
|
+
node_type = node.nodeType
|
|
1844
|
+
if node_type == node.ELEMENT_NODE:
|
|
1845
|
+
tree = ElementTree.SubElement(parent_tree, node.tagName)
|
|
1846
|
+
attributes = node.attributes
|
|
1847
|
+
for i in range(attributes.length):
|
|
1848
|
+
a = attributes.item(i)
|
|
1849
|
+
tree.set(a.name, a.value)
|
|
1850
|
+
for n in node.childNodes:
|
|
1851
|
+
export(tree, n)
|
|
1852
|
+
elif node_type == node.TEXT_NODE or node_type == node.ENTITY_NODE:
|
|
1853
|
+
value = node.nodeValue.strip()
|
|
1854
|
+
if value:
|
|
1855
|
+
assert not parent_tree.text, node
|
|
1856
|
+
parent_tree.text = node.nodeValue
|
|
1857
|
+
elif node_type == node.COMMENT_NODE:
|
|
1858
|
+
pass
|
|
1859
|
+
else:
|
|
1860
|
+
raise Exception('Unhandled node type', node, node.nodeType)
|
|
1861
|
+
for n in top.childNodes:
|
|
1862
|
+
export(tree, n)
|
|
1863
|
+
return tree
|
|
1864
|
+
|
|
1865
|
+
def _dom_tree_content(self):
|
|
1866
|
+
# This can't handle entity references (e.g. ±).
|
|
1867
|
+
from xml.dom.minidom import parseString
|
|
1868
|
+
return self._dom_content(parseString(self._str_content()))
|
|
1869
|
+
|
|
1870
|
+
def _tree_content(self, entity_dictionary):
|
|
1871
|
+
from xml.etree import ElementTree
|
|
1872
|
+
import io
|
|
1873
|
+
parser = ElementTree.XMLParser()
|
|
1874
|
+
if sys.version_info[0] == 2:
|
|
1875
|
+
# TODO: This doesn't work in Python 3. The only way of getting it to work
|
|
1876
|
+
# seems to be defining all entities in a DOCTYPE as suggested in:
|
|
1877
|
+
# https://stackoverflow.com/questions/35591478/ +
|
|
1878
|
+
# how-to-parse-html-with-entities-such-as-nbsp-using-builtin-library-elementtree
|
|
1879
|
+
# But is this really needed? Valid MathML entities are already defined.
|
|
1880
|
+
# Do we need to allow invalid entities for some reasons?
|
|
1881
|
+
parser.parser.UseForeignDTD(True)
|
|
1882
|
+
if entity_dictionary is None:
|
|
1883
|
+
entity_dictionary = self.EntityHandler()
|
|
1884
|
+
parser.entity = entity_dictionary
|
|
1885
|
+
etree = ElementTree.ElementTree()
|
|
1886
|
+
content = self._str_content()
|
|
1887
|
+
try:
|
|
1888
|
+
tree = etree.parse(io.BytesIO(content), parser=parser)
|
|
1889
|
+
except ElementTree.ParseError as e:
|
|
1890
|
+
raise lcg.ParseError("Error when parsing MathML element", e, content)
|
|
1891
|
+
regexp = re.compile('{.*}')
|
|
1892
|
+
for e in tree.iter():
|
|
1893
|
+
match = regexp.match(e.tag)
|
|
1894
|
+
if match:
|
|
1895
|
+
e.tag = e.tag[match.end():]
|
|
1896
|
+
return tree
|
|
1897
|
+
|
|
1898
|
+
def _transform_content(self, math):
|
|
1899
|
+
from xml.etree import ElementTree
|
|
1900
|
+
math = copy.copy(math)
|
|
1901
|
+
for node in math.getiterator():
|
|
1902
|
+
children = list(node)
|
|
1903
|
+
for i in range(len(children)):
|
|
1904
|
+
c = children[i]
|
|
1905
|
+
if c.tag == 'semantics':
|
|
1906
|
+
c_children = list(c)
|
|
1907
|
+
if c_children:
|
|
1908
|
+
node.insert(i, c_children[0])
|
|
1909
|
+
node.remove(c)
|
|
1910
|
+
else:
|
|
1911
|
+
# Not valid, but let's handle it some way
|
|
1912
|
+
c.clear()
|
|
1913
|
+
c.tag = 'mrow'
|
|
1914
|
+
for node in math.getiterator('mfenced'):
|
|
1915
|
+
opening = node.attrib.get('open', '(')
|
|
1916
|
+
closing = node.attrib.get('close', ')')
|
|
1917
|
+
separators = node.attrib.get('separators', ',').split()
|
|
1918
|
+
children = list(node)
|
|
1919
|
+
node.clear()
|
|
1920
|
+
node.tag = 'mrow'
|
|
1921
|
+
if opening:
|
|
1922
|
+
ElementTree.SubElement(node, 'mo', dict(fence='true')).text = opening
|
|
1923
|
+
i = 0
|
|
1924
|
+
for c in children:
|
|
1925
|
+
if separators and i > 0:
|
|
1926
|
+
s = separators[-1] if i > len(separators) else separators[i - 1]
|
|
1927
|
+
ElementTree.SubElement(node, 'mo', dict(separator='true')).text = s
|
|
1928
|
+
node.append(c)
|
|
1929
|
+
i += 1
|
|
1930
|
+
if closing:
|
|
1931
|
+
ElementTree.SubElement(node, 'mo', dict(fence='true')).text = closing
|
|
1932
|
+
return math
|
|
1933
|
+
|
|
1934
|
+
def tree_content(self, entity_dictionary=None, transform=False, top_only=False):
|
|
1935
|
+
"""Return a parsed 'xml.etree.Element' instance of the math content.
|
|
1936
|
+
|
|
1937
|
+
Arguments:
|
|
1938
|
+
|
|
1939
|
+
entity_dictionary -- an object providing common dictionary access
|
|
1940
|
+
methods, 'get()' and '__getitem__()', mapping entity names to
|
|
1941
|
+
values. There are no requirements on the returned values, they are
|
|
1942
|
+
completely implementation dependent. Typical values may be
|
|
1943
|
+
unchanged entity names or unicodes corresponding to the given
|
|
1944
|
+
entities. If not given, 'MathML.EntityHandler' instance is used.
|
|
1945
|
+
transform -- iff true then expand elements which can be expressed
|
|
1946
|
+
using other elements (e.g. mfenced)
|
|
1947
|
+
top_only -- if true then check that the top element is 'math'
|
|
1948
|
+
element, otherwise look for 'math' element in the tree content
|
|
1949
|
+
|
|
1950
|
+
"""
|
|
1951
|
+
tree = self._tree_content(entity_dictionary)
|
|
1952
|
+
if top_only:
|
|
1953
|
+
if tree.tag != 'math':
|
|
1954
|
+
raise Exception("Invalid MathML top element", tree)
|
|
1955
|
+
else:
|
|
1956
|
+
try:
|
|
1957
|
+
tree = tree.getiterator('math').pop()
|
|
1958
|
+
except IndexError:
|
|
1959
|
+
raise Exception("No math element found", tree)
|
|
1960
|
+
if transform:
|
|
1961
|
+
tree = self._transform_content(tree)
|
|
1962
|
+
return tree
|
|
1963
|
+
|
|
1964
|
+
|
|
1965
|
+
class InlineSVG(Content):
|
|
1966
|
+
"""Generic SVG content for inclusion in LCG documents.
|
|
1967
|
+
|
|
1968
|
+
SVG may be included in HTML and PDF backends. Requires svglib when PDF
|
|
1969
|
+
output is used.
|
|
1970
|
+
|
|
1971
|
+
"""
|
|
1972
|
+
def __init__(self, content, *args, **kwargs):
|
|
1973
|
+
"""Initialize the instance.
|
|
1974
|
+
|
|
1975
|
+
Arguments:
|
|
1976
|
+
|
|
1977
|
+
content -- the actual SVG content of this element as a string, bytes
|
|
1978
|
+
or a function of one argument (exporter context) returning the SVG
|
|
1979
|
+
as string or bytes.
|
|
1980
|
+
*args -- additional positional arguments passed to the export
|
|
1981
|
+
function (given in 'content'). No arguments are allowed when
|
|
1982
|
+
'content' is string or bytes.
|
|
1983
|
+
**kwargs -- keyword arguments for parent class constructor.
|
|
1984
|
+
|
|
1985
|
+
"""
|
|
1986
|
+
assert isinstance(content, (str, bytes)) or callable(content), content
|
|
1987
|
+
self._content = content
|
|
1988
|
+
self._export_args = args
|
|
1989
|
+
super(InlineSVG, self).__init__(**kwargs)
|
|
1990
|
+
|
|
1991
|
+
def svg(self, context):
|
|
1992
|
+
"""Return the SVG as bytes."""
|
|
1993
|
+
content = self._content
|
|
1994
|
+
if callable(content):
|
|
1995
|
+
content = content(context, *self._export_args)
|
|
1996
|
+
if not isinstance(content, bytes):
|
|
1997
|
+
content = content.encode('utf-8')
|
|
1998
|
+
return content
|
|
1999
|
+
|
|
2000
|
+
# Convenience functions for simple content construction.
|
|
2001
|
+
|
|
2002
|
+
def coerce(content, formatted=False):
|
|
2003
|
+
"""Coerce the argument into an LCG content element.
|
|
2004
|
+
|
|
2005
|
+
Arguments:
|
|
2006
|
+
|
|
2007
|
+
content -- can be a sequence, string or a Content instance. A sequence
|
|
2008
|
+
is turned to a 'Container' of the items. Moreover each item is coerced
|
|
2009
|
+
recursively and 'None' items are omitted. A string is parsed for
|
|
2010
|
+
inline markup or simply turned into 'TextContent' instance depending on
|
|
2011
|
+
the 'formatted' argument. A 'Content' instance is returned as is. Any
|
|
2012
|
+
other argument raises AssertionError.
|
|
2013
|
+
|
|
2014
|
+
formatted -- a boolean flag indicating how strings should treated. If
|
|
2015
|
+
False (the default) strings are simply turned into 'TextContent'
|
|
2016
|
+
instances. If True, strings are parsed for inline markup using
|
|
2017
|
+
'lcg.Parser.parse_inline_markup()'. Applies recursively if a sequence
|
|
2018
|
+
is passed as the 'content' argument.
|
|
2019
|
+
|
|
2020
|
+
"""
|
|
2021
|
+
assert isinstance(formatted, bool)
|
|
2022
|
+
if isinstance(content, (list, tuple)):
|
|
2023
|
+
container = Container
|
|
2024
|
+
items = []
|
|
2025
|
+
for item in content:
|
|
2026
|
+
if item is not None:
|
|
2027
|
+
if not isinstance(item, Content):
|
|
2028
|
+
item = coerce(item, formatted=formatted)
|
|
2029
|
+
items.append(item)
|
|
2030
|
+
return container(items)
|
|
2031
|
+
elif isinstance(content, basestring):
|
|
2032
|
+
if formatted:
|
|
2033
|
+
from lcg import Parser
|
|
2034
|
+
return Parser().parse_inline_markup(content)
|
|
2035
|
+
else:
|
|
2036
|
+
return TextContent(content)
|
|
2037
|
+
else:
|
|
2038
|
+
assert isinstance(content, Content), ('Invalid content', content,)
|
|
2039
|
+
return content
|
|
2040
|
+
|
|
2041
|
+
|
|
2042
|
+
def join(items, separator=' '):
|
|
2043
|
+
"""Coerce all items and put the coerced separator in between them."""
|
|
2044
|
+
sep = coerce(separator)
|
|
2045
|
+
result = []
|
|
2046
|
+
for item in items:
|
|
2047
|
+
if result:
|
|
2048
|
+
result.append(sep)
|
|
2049
|
+
result.append(coerce(item))
|
|
2050
|
+
return coerce(result)
|
|
2051
|
+
|
|
2052
|
+
|
|
2053
|
+
def link(target, label=None, type=None, descr=None):
|
|
2054
|
+
"""Return a 'Link' instance.
|
|
2055
|
+
|
|
2056
|
+
Arguments:
|
|
2057
|
+
|
|
2058
|
+
target -- link target. It can be a direct URI as a string or unicode
|
|
2059
|
+
instance or any referable content element, such as 'Section',
|
|
2060
|
+
'lcg.ContentNode', 'lcg.Resource' or 'Link.ExternalTarget'.
|
|
2061
|
+
label -- link label is mandatory when the target is a direct URI (string
|
|
2062
|
+
or unicode) and optional for LCG content element targets. In this case
|
|
2063
|
+
it just overrides the default title of the refered object.
|
|
2064
|
+
type -- mime type specification of the refered object.
|
|
2065
|
+
descr -- additional description of the link target. Only applicable when
|
|
2066
|
+
the link target is a direct URI.
|
|
2067
|
+
|
|
2068
|
+
"""
|
|
2069
|
+
if isinstance(target, basestring):
|
|
2070
|
+
assert label is not None
|
|
2071
|
+
target = Link.ExternalTarget(target, None, descr=descr)
|
|
2072
|
+
else:
|
|
2073
|
+
assert isinstance(target, (lcg.ContentNode, Section, Link.ExternalTarget, lcg.Resource)), \
|
|
2074
|
+
target
|
|
2075
|
+
assert descr is None
|
|
2076
|
+
return Link(target, label=label, type=type)
|
|
2077
|
+
|
|
2078
|
+
|
|
2079
|
+
def dl(items, formatted=False):
|
|
2080
|
+
"""Create a 'DefinitionList' from a sequence of (TERM, DESCRIPTION) pairs.
|
|
2081
|
+
|
|
2082
|
+
Each term and description in the sequence is automatically coerced into a content instance, but
|
|
2083
|
+
only the description is treated as formatted.
|
|
2084
|
+
|
|
2085
|
+
"""
|
|
2086
|
+
return DefinitionList([(coerce(term), coerce(descr, formatted=formatted))
|
|
2087
|
+
for term, descr in items])
|
|
2088
|
+
|
|
2089
|
+
|
|
2090
|
+
def ul(items, formatted=False):
|
|
2091
|
+
"""Create an 'ItemizedList' by coercing given sequence of items."""
|
|
2092
|
+
return ItemizedList([coerce(item, formatted=formatted) for item in items])
|
|
2093
|
+
|
|
2094
|
+
|
|
2095
|
+
def ol(items, formatted=False, alpha=False):
|
|
2096
|
+
"""Create an 'ItemizedList' by coercing given sequence of items."""
|
|
2097
|
+
return ItemizedList([coerce(item, formatted=formatted) for item in items],
|
|
2098
|
+
order=(alpha and ItemizedList.LOWER_ALPHA or ItemizedList.NUMERIC))
|
|
2099
|
+
|
|
2100
|
+
|
|
2101
|
+
def fieldset(pairs, formatted=False):
|
|
2102
|
+
"""Create a 'FieldSet' out of given sequence of (LABEL, VALUE) pairs.
|
|
2103
|
+
|
|
2104
|
+
Both label and value are coerced, but only value is treated as formatted.
|
|
2105
|
+
|
|
2106
|
+
"""
|
|
2107
|
+
fields = [(coerce(label), coerce(value, formatted=formatted)) for label, value in pairs]
|
|
2108
|
+
return FieldSet(fields)
|
|
2109
|
+
|
|
2110
|
+
|
|
2111
|
+
def sec(title, content, heading=None, name='default-section', **kwargs):
|
|
2112
|
+
"""Create a 'Section' by coercing all content."""
|
|
2113
|
+
formatted = kwargs.pop('formatted', False)
|
|
2114
|
+
return Section(title=title, heading=coerce(heading, formatted=formatted) if heading else None,
|
|
2115
|
+
content=[coerce(item, formatted=formatted) for item in content],
|
|
2116
|
+
name=name, **kwargs)
|
|
2117
|
+
|
|
2118
|
+
|
|
2119
|
+
def _container(container, items, formatted=False, **kwargs):
|
|
2120
|
+
return container([coerce(item, formatted=formatted) for item in items], **kwargs)
|
|
2121
|
+
|
|
2122
|
+
|
|
2123
|
+
def p(*items, **kwargs):
|
|
2124
|
+
"""Create a 'Paragraph' by coercing all arguments."""
|
|
2125
|
+
return _container(Paragraph, items, **kwargs)
|
|
2126
|
+
|
|
2127
|
+
|
|
2128
|
+
def strong(*items, **kwargs):
|
|
2129
|
+
"""Create a 'Strong' instance by coercing all arguments."""
|
|
2130
|
+
return _container(Strong, items, **kwargs)
|
|
2131
|
+
|
|
2132
|
+
|
|
2133
|
+
def em(*items, **kwargs):
|
|
2134
|
+
"""Create an 'Emphasized' instance by coercing all arguments."""
|
|
2135
|
+
return _container(Emphasized, items, **kwargs)
|
|
2136
|
+
|
|
2137
|
+
|
|
2138
|
+
def u(*items, **kwargs):
|
|
2139
|
+
"""Create an 'Underlined' instance by coercing all arguments."""
|
|
2140
|
+
return _container(Underlined, items, **kwargs)
|
|
2141
|
+
|
|
2142
|
+
|
|
2143
|
+
def code(*items, **kwargs):
|
|
2144
|
+
"""Create an 'Code' instance by coercing all arguments."""
|
|
2145
|
+
return _container(Code, items, **kwargs)
|
|
2146
|
+
|
|
2147
|
+
|
|
2148
|
+
def cite(*items, **kwargs):
|
|
2149
|
+
"""Create an 'Citation' instance by coercing all arguments."""
|
|
2150
|
+
return _container(Citation, items, **kwargs)
|
|
2151
|
+
|
|
2152
|
+
|
|
2153
|
+
def container(*items, **kwargs):
|
|
2154
|
+
return _container(Container, items, **kwargs)
|
|
2155
|
+
|
|
2156
|
+
|
|
2157
|
+
def br():
|
|
2158
|
+
return NewLine()
|
|
2159
|
+
|
|
2160
|
+
|
|
2161
|
+
def hr():
|
|
2162
|
+
return HorizontalSeparator()
|
|
2163
|
+
|
|
2164
|
+
|
|
2165
|
+
def pre(text, **kwargs):
|
|
2166
|
+
"""Create an 'PreformattedText' instance by coercing all arguments."""
|
|
2167
|
+
return PreformattedText(text, **kwargs)
|
|
2168
|
+
|
|
2169
|
+
|
|
2170
|
+
def abbr(text, descr, **kwargs):
|
|
2171
|
+
"""Create an 'Abbreviation' instance."""
|
|
2172
|
+
return Abbreviation(text, descr, **kwargs)
|