From 1ea53901457c8e929a4b3a70e93d50b8cfac4e06 Mon Sep 17 00:00:00 2001 From: mbutscher Date: Sat, 25 Jun 2011 07:59:48 +0000 Subject: [PATCH] branches/stable-2.1: * Bug fixed: Crash when rebuilding sqlite wiki on 64 bit Linux * Bug fixed: Deletion of wrong versioning data on MPT import * Bug fixed: Search index from 64bit Python not compatible with 32bit Python (due to hash function). Changed index format number from 2 to 3 branches/mbutscher/work: * New insertion "search" to create arbitrary searches * Internal: New function wxHelper.getHtmlFromClipboard() * Bug fixed: "Move to file storage" failed * Windows: Set focus for image tooltip back to editor (TODO: add for other OS') * Support for "wikirel:" protocol to support relative links to other wikis * Basic Vi emulation for editor (thanks to Ross) * Bug fixed: Crash when rebuilding sqlite wiki on 64 bit Linux * Bug fixed: Search index from 64bit Python not compatible with 32bit Python (due to hash function). Changed index format number from 2 to 3 * Support to set readonly per wiki page * Bug fixed: Deletion of wrong versioning data on MPT import * Trashcan support * Added "page:" prefix for boolean regex search to search for page name * No tree refresh during wiki rebuild * Bug fixed: Exception when changing "global/*" func. pages * Black lists (global and wiki-dependent) for non-camelcase (=bracketed) wikiwords added * Activation (double click or press return) on collapsed todo or attribute tree item with one child which represents a wiki page opens this wiki page * Some renaming of tree functions --- .gitattributes | 2 + Consts.py | 6 +- WikidPad.pot | 1163 +- WikidPad.xrc | 265 + WikidPadHelp/WikidPadHelp.wiki | 8 +- WikidPadHelp/data/ChangeLog.wiki | 38 + WikidPadHelp/data/Insertions.wiki | 15 + WikidPadHelp/data/Menu Wiki Page.wiki | 7 +- WikidPadHelp/data/Menu Wiki.wiki | 3 + WikidPadHelp/data/OptionsDialog.wiki | 15 + WikidPadHelp/data/SETTINGS.grl | Bin 468 -> 468 bytes .../data/SearchingTheWiki%2FBooleanRegex.wiki | 13 + WikidPadHelp/data/UrlLinking.wiki | 16 +- WikidPadHelp/data/WIKIRELATIONS.grl | Bin 38589 -> 38948 bytes WikidPadHelp/data/WIKIWORDATTRS.grl | Bin 13659 -> 13659 bytes WikidPadHelp/data/WIKIWORDMATCHTERMS.grl | Bin 19187 -> 19187 bytes WikidPadHelp/data/WIKIWORDS.grl | Bin 49213 -> 49232 bytes WikidPadHelp/data/WordLinking.wiki | 20 +- WikidPad_hu_HU.po | 9784 ++++++++--------- WikidPad_ru_RU.po | 1442 ++- WikidPad_sv.po | 1082 +- docs/dev-setup.txt | 6 +- extensions/HtmlExporter.py | 52 +- extensions/KeyBindings.py | 2 + extensions/wikidPadParser/WikidPadParser.py | 29 +- lib/pwiki/AdditionalDialogs.py | 44 +- lib/pwiki/Configuration.py | 9 +- lib/pwiki/DocPages.py | 116 +- lib/pwiki/Exporters.py | 31 + lib/pwiki/Importers.py | 54 +- lib/pwiki/MainApp.py | 4 +- lib/pwiki/MainAreaPanel.py | 2 +- lib/pwiki/MiscEvent.py | 48 +- lib/pwiki/OptionsDialog.py | 11 +- lib/pwiki/PWikiNonCore.py | 45 + lib/pwiki/PersonalWikiFrame.py | 114 +- lib/pwiki/SearchAndReplace.py | 2 + lib/pwiki/SearchAndReplaceBoolLang.py | 26 +- lib/pwiki/SqliteThin3.py | 7 +- lib/pwiki/StringOps.py | 8 +- lib/pwiki/Trashcan.py | 637 ++ lib/pwiki/ViHelper.py | 615 ++ lib/pwiki/WikiHtmlView.py | 2 +- lib/pwiki/WikiHtmlViewIE.py | 2 +- lib/pwiki/WikiHtmlViewWK.py | 621 +- lib/pwiki/WikiTreeCtrl.py | 209 +- lib/pwiki/WikiTxtCtrl.py | 1632 ++- lib/pwiki/wikidata/WikiDataManager.py | 67 +- lib/pwiki/wikidata/compact_sqlite/WikiData.py | 44 + .../wikidata/original_gadfly/WikiData.py | 48 + .../wikidata/original_sqlite/WikiData.py | 44 + lib/pwiki/wxHelper.py | 152 +- lib/whoosh/classify.py | 862 +- lib/whoosh/filedb/filestore.py | 390 +- lib/whoosh/filedb/filetables.py | 15 +- lib/whoosh/filedb/multiproc.py | 456 +- lib/whoosh/lang/lovins.py | 1084 +- lib/whoosh/lang/morph_en.py | 1882 ++-- lib/whoosh/lang/paicehusk.py | 492 +- lib/whoosh/lang/porter.py | 376 +- lib/whoosh/lang/wordnet.py | 516 +- lib/whoosh/matching.py | 2738 ++--- lib/whoosh/qparser/__init__.py | 40 +- lib/whoosh/qparser/dateparse.py | 1714 +-- lib/whoosh/ramdb/ramindex.py | 608 +- lib/whoosh/ramdb/ramreading.py | 458 +- lib/whoosh/spans.py | 1242 +-- lib/whoosh/spelling.py | 478 +- lib/whoosh/store.py | 134 +- lib/whoosh/support/charset.py | 1596 +-- lib/whoosh/support/filelock.py | 274 +- lib/whoosh/support/numeric.py | 492 +- lib/whoosh/support/relativedelta.py | 864 +- lib/whoosh/support/times.py | 840 +- lib/whoosh/system.py | 94 +- lib/whoosh/util.py | 846 +- lib/whoosh/writing.py | 796 +- setup_macosx.py | 82 +- wikidpad_unicode.iss | 11 +- 79 files changed, 20815 insertions(+), 17127 deletions(-) create mode 100644 lib/pwiki/Trashcan.py create mode 100644 lib/pwiki/ViHelper.py diff --git a/.gitattributes b/.gitattributes index 0e543c76..fc534a6e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -673,8 +673,10 @@ lib/pwiki/StringOps.py -text lib/pwiki/SystemInfo.py -text lib/pwiki/TempFileSet.py -text lib/pwiki/TextTree.py -text +lib/pwiki/Trashcan.py -text lib/pwiki/UserActionCoord.py -text lib/pwiki/Utilities.py -text +lib/pwiki/ViHelper.py -text lib/pwiki/WikiDocument.py -text lib/pwiki/WikiExceptions.py -text lib/pwiki/WikiHtmlView.py -text diff --git a/Consts.py b/Consts.py index 79ae0013..34b065ed 100644 --- a/Consts.py +++ b/Consts.py @@ -23,8 +23,8 @@ # (1, 9, 104, 2) is something after 1.9beta04 # (2, 0, 300, 0) is 2.0final -VERSION_TUPLE = ("wikidPad", 2, 2, 104, 2) -VERSION_STRING = "wikidPad 2.2beta04_2" +VERSION_TUPLE = ("wikidPad", 2, 2, 105, 0) +VERSION_STRING = "wikidPad 2.2beta05" HOMEPAGE = u"http://wikidpad.sourceforge.net" CONFIG_FILENAME = "WikidPad.config" @@ -100,7 +100,7 @@ # Version number of the current searchindex. If number doesn't match with # number in configuration file, index must be rebuild -SEARCHINDEX_FORMAT_NO = 2 +SEARCHINDEX_FORMAT_NO = 3 diff --git a/WikidPad.pot b/WikidPad.pot index f55528a7..a0736467 100644 --- a/WikidPad.pot +++ b/WikidPad.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2011-06-03 08:57\n" +"POT-Creation-Date: 2011-06-25 09:21\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -39,6 +39,10 @@ msgstr "" msgid " &Test " msgstr "" +#: WikidPad.xrc:0 +msgid " Close " +msgstr "" + #: WikidPad.xrc:0 msgid " Find &Next " msgstr "" @@ -55,6 +59,10 @@ msgstr "" msgid "&Create" msgstr "" +#: WikidPad.xrc:0 +msgid "&Delete All" +msgstr "" + #: WikidPad.xrc:0 msgid "&Delete Export(s)" msgstr "" @@ -95,6 +103,10 @@ msgstr "" msgid "&Replace by:" msgstr "" +#: WikidPad.xrc:0 +msgid "&Restore" +msgstr "" + #: WikidPad.xrc:0 msgid "&Save Export" msgstr "" @@ -260,6 +272,10 @@ msgstr "" msgid "Ask" msgstr "" +#: WikidPad.xrc:0 +msgid "Ask before deletion of wiki word" +msgstr "" + #: WikidPad.xrc:0 msgid "Ask settings on each paste" msgstr "" @@ -712,6 +728,10 @@ msgstr "" msgid "Max. characters on each tab:" msgstr "" +#: WikidPad.xrc:0 +msgid "Max. number of entries:" +msgstr "" + #: WikidPad.xrc:0 msgid "Middle button with CTRL:" msgstr "" @@ -756,6 +776,14 @@ msgstr "" msgid "N. T. &Backgr." msgstr "" +#: WikidPad.xrc:0 +msgid "Name clash" +msgstr "" + +#: WikidPad.xrc:0 +msgid "Name collision:" +msgstr "" + #: WikidPad.xrc:0 msgid "Natural" msgstr "" @@ -1000,6 +1028,14 @@ msgstr "" msgid "Rename dialog defaults" msgstr "" +#: WikidPad.xrc:0 +msgid "Rename trashcan entry to:" +msgstr "" + +#: WikidPad.xrc:0 +msgid "Rename wiki element to:" +msgstr "" + #: WikidPad.xrc:0 msgid "Replace &All" msgstr "" @@ -1132,6 +1168,10 @@ msgstr "" msgid "Single process per user*" msgstr "" +#: WikidPad.xrc:0 +msgid "Skip" +msgstr "" + #: WikidPad.xrc:0 msgid "Sort" msgstr "" @@ -1272,6 +1312,10 @@ msgstr "" msgid "Translate menu accelerators for keyboard layout* (Windows)" msgstr "" +#: WikidPad.xrc:0 +msgid "Trashcan" +msgstr "" + #: WikidPad.xrc:0 msgid "Tree auto-follow" msgstr "" @@ -1451,38 +1495,38 @@ msgstr "" msgid "..." msgstr "" -#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:792 +#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:804 msgid " OK " msgstr "" -#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:796 +#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:808 msgid " Cancel " msgstr "" -#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:1183 +#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:1195 msgid "Destination directory:" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:1377 +#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:1389 #: lib\pwiki\SearchAndReplaceDialogs.py:1485 msgid "Title:" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:1659 +#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:1671 msgid "Source directory:" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\DiffGui.py:436 lib\pwiki\PersonalWikiFrame.py:1338 -#: lib\pwiki\WikiTxtCtrl.py:3820 +#: WikidPad.xrc:0 lib\pwiki\DiffGui.py:436 lib\pwiki\PersonalWikiFrame.py:1347 +#: lib\pwiki\WikiTxtCtrl.py:3690 msgid "Copy" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\DiffGui.py:437 lib\pwiki\PersonalWikiFrame.py:1347 -#: lib\pwiki\WikiTxtCtrl.py:3823 +#: WikidPad.xrc:0 lib\pwiki\DiffGui.py:437 lib\pwiki\PersonalWikiFrame.py:1356 +#: lib\pwiki\WikiTxtCtrl.py:3693 msgid "Select All" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\DiffGui.py:439 lib\pwiki\WikiTxtCtrl.py:3839 +#: WikidPad.xrc:0 lib\pwiki\DiffGui.py:439 lib\pwiki\WikiTxtCtrl.py:3709 msgid "Close Tab" msgstr "" @@ -1494,13 +1538,13 @@ msgstr "" #: WikidPad.xrc:0 lib\pwiki\FileCleanup.py:528 lib\pwiki\FileCleanup.py:616 #: lib\pwiki\FileCleanup.py:686 lib\pwiki\FileCleanup.py:775 #: lib\pwiki\MptImporterGui.py:172 lib\pwiki\MptImporterGui.py:174 -#: lib\pwiki\OptionsDialog.py:139 lib\pwiki\OptionsDialog.py:893 +#: lib\pwiki\OptionsDialog.py:139 lib\pwiki\OptionsDialog.py:899 msgid "Default" msgstr "" #: WikidPad.xrc:0 lib\pwiki\FileCleanup.py:529 lib\pwiki\FileCleanup.py:531 #: lib\pwiki\FileCleanup.py:687 lib\pwiki\FileCleanup.py:1265 -#: lib\pwiki\WikiTxtCtrl.py:3822 +#: lib\pwiki\WikiTxtCtrl.py:3692 msgid "Delete" msgstr "" @@ -1527,41 +1571,41 @@ msgstr "" msgid "Import" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\OptionsDialog.py:688 +#: WikidPad.xrc:0 lib\pwiki\OptionsDialog.py:694 msgid "Versioning" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1313 -#: lib\pwiki\WikiTxtCtrl.py:3817 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1322 +#: lib\pwiki\WikiTxtCtrl.py:3687 msgid "Undo" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1317 -#: lib\pwiki\WikiTxtCtrl.py:3818 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1326 +#: lib\pwiki\WikiTxtCtrl.py:3688 msgid "Redo" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1333 -#: lib\pwiki\WikiTxtCtrl.py:3819 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1342 +#: lib\pwiki\WikiTxtCtrl.py:3689 msgid "Cut" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1342 -#: lib\pwiki\WikiTxtCtrl.py:3821 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1351 +#: lib\pwiki\WikiTxtCtrl.py:3691 msgid "Paste" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1635 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1644 msgid "&Delete" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:2049 -#: lib\pwiki\PersonalWikiFrame.py:2050 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:2064 +#: lib\pwiki\PersonalWikiFrame.py:2065 msgid "Open Wiki Word" msgstr "" -#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:2080 -#: lib\pwiki\PersonalWikiFrame.py:2081 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:2095 +#: lib\pwiki\PersonalWikiFrame.py:2096 msgid "Rename Wiki Word" msgstr "" @@ -1599,7 +1643,7 @@ msgstr "" msgid "Error starting WikidPad" msgstr "" -#: WikidPadStarter.py:207 lib\pwiki\PersonalWikiFrame.py:5139 +#: WikidPadStarter.py:207 lib\pwiki\PersonalWikiFrame.py:5161 #: lib\pwiki\SearchAndReplaceDialogs.py:724 #: lib\pwiki\SearchAndReplaceDialogs.py:1008 msgid "Error!" @@ -1712,15 +1756,15 @@ msgstr "" msgid "Set of HTML pages" msgstr "" -#: extensions\HtmlExporter.py:682 extensions\HtmlExporter.py:771 +#: extensions\HtmlExporter.py:683 extensions\HtmlExporter.py:772 msgid "Exporting %s" msgstr "" -#: extensions\HtmlExporter.py:1554 +#: extensions\HtmlExporter.py:1604 msgid "
[Allow evaluation of insertions in \"Options\", page \"Security\", option \"Process insertion scripts\"]
" msgstr "" -#: extensions\HtmlExporter.py:2202 +#: extensions\HtmlExporter.py:2252 msgid "[Unknown parser node with name \"%s\" found]" msgstr "" @@ -1777,17 +1821,17 @@ msgstr "" msgid "*%s page(s) referred to by* %s\n" msgstr "" -#: extensions\wikidPadParser\WikidPadParser.py:1649 -#: extensions\wikidPadParser\WikidPadParser.py:1676 +#: extensions\wikidPadParser\WikidPadParser.py:1654 +#: extensions\wikidPadParser\WikidPadParser.py:1681 msgid "This is a footnote" msgstr "" -#: extensions\wikidPadParser\WikidPadParser.py:1654 -#: extensions\wikidPadParser\WikidPadParser.py:1681 +#: extensions\wikidPadParser\WikidPadParser.py:1659 +#: extensions\wikidPadParser\WikidPadParser.py:1686 msgid "This is syntactically not a wiki word" msgstr "" -#: extensions\wikidPadParser\WikidPadParser.py:2298 +#: extensions\wikidPadParser\WikidPadParser.py:2311 msgid "++ Wiki Settings\n" "\n" "These are your default global settings.\n" @@ -1800,56 +1844,57 @@ msgid "++ Wiki Settings\n" "[icon: cog]\n" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:144 lib\pwiki\AdditionalDialogs.py:360 +#: lib\pwiki\AdditionalDialogs.py:144 lib\pwiki\AdditionalDialogs.py:361 msgid "Links to:" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:290 lib\pwiki\AdditionalDialogs.py:402 +#: lib\pwiki\AdditionalDialogs.py:291 lib\pwiki\AdditionalDialogs.py:411 msgid "'%s' is an invalid WikiWord" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:297 +#: lib\pwiki\AdditionalDialogs.py:298 msgid "'%s' is not an existing wikiword. Create?" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:298 +#: lib\pwiki\AdditionalDialogs.py:299 msgid "Create" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:408 +#: lib\pwiki\AdditionalDialogs.py:417 msgid "'%s' exists already" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:422 lib\pwiki\AdditionalDialogs.py:515 +#: lib\pwiki\AdditionalDialogs.py:433 lib\pwiki\AdditionalDialogs.py:527 msgid "Do you want to delete %i wiki page(s)?" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:674 +#: lib\pwiki\AdditionalDialogs.py:686 msgid "Can't process renaming:\n" "%s" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:675 +#: lib\pwiki\AdditionalDialogs.py:687 msgid "Can't rename" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:713 +#: lib\pwiki\AdditionalDialogs.py:725 msgid "Can't rename to itself" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:716 lib\pwiki\WikiExceptions.py:73 +#: lib\pwiki\AdditionalDialogs.py:728 lib\pwiki\TrashcanGui.py:379 +#: lib\pwiki\WikiExceptions.py:73 msgid "Word already exists" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:765 +#: lib\pwiki\AdditionalDialogs.py:777 msgid "Select Icon" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:774 +#: lib\pwiki\AdditionalDialogs.py:786 msgid "Icon" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:840 +#: lib\pwiki\AdditionalDialogs.py:852 msgid "\n" "\n" "\n" @@ -1916,135 +1961,135 @@ msgid "\n" "\n" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:950 +#: lib\pwiki\AdditionalDialogs.py:962 msgid "" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1083 +#: lib\pwiki\AdditionalDialogs.py:1095 msgid "Continuous Export" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1186 +#: lib\pwiki\AdditionalDialogs.py:1198 msgid "Destination file:" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1220 +#: lib\pwiki\AdditionalDialogs.py:1232 msgid "Destination directory does not exist" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1225 +#: lib\pwiki\AdditionalDialogs.py:1237 msgid "Destination must be a directory" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1231 +#: lib\pwiki\AdditionalDialogs.py:1243 msgid "Destination must be a file" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1241 lib\pwiki\PersonalWikiFrame.py:4725 +#: lib\pwiki\AdditionalDialogs.py:1253 lib\pwiki\PersonalWikiFrame.py:4747 msgid "Exporting" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1243 lib\pwiki\PersonalWikiFrame.py:4727 +#: lib\pwiki\AdditionalDialogs.py:1255 lib\pwiki\PersonalWikiFrame.py:4749 msgid "Preparing" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1263 +#: lib\pwiki\AdditionalDialogs.py:1275 msgid "Error while exporting" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1279 lib\pwiki\PersonalWikiFrame.py:4668 +#: lib\pwiki\AdditionalDialogs.py:1291 lib\pwiki\PersonalWikiFrame.py:4690 msgid "Select Export Directory" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1293 lib\pwiki\AdditionalDialogs.py:1732 +#: lib\pwiki\AdditionalDialogs.py:1305 lib\pwiki\AdditionalDialogs.py:1744 msgid "All files (*.*)" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1298 +#: lib\pwiki\AdditionalDialogs.py:1310 msgid "Select Export File" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1325 lib\pwiki\PersonalWikiFrame.py:4692 -#: lib\pwiki\PersonalWikiFrame.py:4708 lib\pwiki\Printing.py:183 +#: lib\pwiki\AdditionalDialogs.py:1337 lib\pwiki\PersonalWikiFrame.py:4714 +#: lib\pwiki\PersonalWikiFrame.py:4730 lib\pwiki\Printing.py:183 msgid "No real wiki word selected as root" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1378 +#: lib\pwiki\AdditionalDialogs.py:1390 msgid "Choose export title" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1387 +#: lib\pwiki\AdditionalDialogs.py:1399 msgid "Do you want to overwrite existing export '%s'?" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1388 +#: lib\pwiki\AdditionalDialogs.py:1400 msgid "Overwrite export" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1427 +#: lib\pwiki\AdditionalDialogs.py:1439 msgid "Do you want to delete %i export(s)?" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1428 +#: lib\pwiki\AdditionalDialogs.py:1440 msgid "Delete export" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1465 +#: lib\pwiki\AdditionalDialogs.py:1477 msgid "Selected export type does not support saving" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1506 lib\pwiki\CmdLineAction.py:213 +#: lib\pwiki\AdditionalDialogs.py:1518 lib\pwiki\CmdLineAction.py:213 msgid "Export type '%s' of saved export is not supported" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1517 lib\pwiki\CmdLineAction.py:224 +#: lib\pwiki\AdditionalDialogs.py:1529 lib\pwiki\CmdLineAction.py:224 msgid "Saved export uses different version for additional options than current export\n" "Export type: '%s'\n" "Saved export version: %i\n" "Current export version: %i" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1525 lib\pwiki\CmdLineAction.py:233 +#: lib\pwiki\AdditionalDialogs.py:1537 lib\pwiki\CmdLineAction.py:233 msgid "Type of additional option storage ('%s') is unknown" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1549 lib\pwiki\CmdLineAction.py:255 +#: lib\pwiki\AdditionalDialogs.py:1561 lib\pwiki\CmdLineAction.py:255 msgid "Error during retrieving saved export: " msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1662 +#: lib\pwiki\AdditionalDialogs.py:1674 msgid "Source file:" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1678 +#: lib\pwiki\AdditionalDialogs.py:1690 msgid "Source does not exist" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1688 +#: lib\pwiki\AdditionalDialogs.py:1700 msgid "Source must be a directory" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1693 +#: lib\pwiki\AdditionalDialogs.py:1705 msgid "Source must be a file" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1704 +#: lib\pwiki\AdditionalDialogs.py:1716 msgid "Error while importing" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1718 +#: lib\pwiki\AdditionalDialogs.py:1730 msgid "Select Import Directory" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1733 +#: lib\pwiki\AdditionalDialogs.py:1745 msgid "*" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1737 +#: lib\pwiki\AdditionalDialogs.py:1749 msgid "Select Import File" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1853 +#: lib\pwiki\AdditionalDialogs.py:1865 msgid "\n" "\n" "\n" @@ -2095,63 +2140,63 @@ msgid "\n" "\n" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1904 lib\pwiki\PersonalWikiFrame.py:1967 +#: lib\pwiki\AdditionalDialogs.py:1916 lib\pwiki\PersonalWikiFrame.py:1982 msgid "About WikidPad" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1908 +#: lib\pwiki\AdditionalDialogs.py:1920 msgid "N/A" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1918 +#: lib\pwiki\AdditionalDialogs.py:1930 msgid "Okay" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:2019 +#: lib\pwiki\AdditionalDialogs.py:2031 msgid "Wiki Properties" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:2026 +#: lib\pwiki\AdditionalDialogs.py:2038 msgid "No wiki loaded" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:2030 +#: lib\pwiki\AdditionalDialogs.py:2042 msgid "Wiki config. path:" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:2036 +#: lib\pwiki\AdditionalDialogs.py:2048 msgid "Wiki database backend:" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:2044 +#: lib\pwiki\AdditionalDialogs.py:2056 msgid "Number of wiki pages:" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:2053 +#: lib\pwiki\AdditionalDialogs.py:2065 msgid "Wiki is read-only. Reason:" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:2056 +#: lib\pwiki\AdditionalDialogs.py:2068 msgid "Write access to database lost. Try \"Wiki\"->\"Reconnect\"" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:2058 +#: lib\pwiki\AdditionalDialogs.py:2070 msgid "Wiki was set read-only in options dialog" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:2063 lib\pwiki\AdditionalDialogs.py:2065 +#: lib\pwiki\AdditionalDialogs.py:2075 lib\pwiki\AdditionalDialogs.py:2077 msgid "Can't write wiki config.:" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:2063 lib\pwiki\AdditionalDialogs.py:2067 +#: lib\pwiki\AdditionalDialogs.py:2075 lib\pwiki\AdditionalDialogs.py:2079 msgid "Unknown reason" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:2080 +#: lib\pwiki\AdditionalDialogs.py:2092 msgid "Jobs" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:2097 +#: lib\pwiki\AdditionalDialogs.py:2109 msgid "Number of Jobs:" msgstr "" @@ -2548,35 +2593,43 @@ msgstr "" msgid "History" msgstr "" -#: lib\pwiki\DocPages.py:2226 +#: lib\pwiki\DocPages.py:2292 msgid "Func. tag %s does not exist" msgstr "" -#: lib\pwiki\DocPages.py:2487 +#: lib\pwiki\DocPages.py:2561 msgid "Global text blocks" msgstr "" -#: lib\pwiki\DocPages.py:2488 +#: lib\pwiki\DocPages.py:2562 msgid "Wiki text blocks" msgstr "" -#: lib\pwiki\DocPages.py:2489 +#: lib\pwiki\DocPages.py:2563 msgid "Global spell list" msgstr "" -#: lib\pwiki\DocPages.py:2490 +#: lib\pwiki\DocPages.py:2564 msgid "Wiki spell list" msgstr "" -#: lib\pwiki\DocPages.py:2491 +#: lib\pwiki\DocPages.py:2565 msgid "Global cc. blacklist" msgstr "" -#: lib\pwiki\DocPages.py:2492 +#: lib\pwiki\DocPages.py:2566 msgid "Wiki cc. blacklist" msgstr "" -#: lib\pwiki\DocPages.py:2493 +#: lib\pwiki\DocPages.py:2567 +msgid "Global ncc. blacklist" +msgstr "" + +#: lib\pwiki\DocPages.py:2568 +msgid "Wiki ncc. blacklist" +msgstr "" + +#: lib\pwiki\DocPages.py:2569 msgid "Favorite wikis" msgstr "" @@ -2584,22 +2637,22 @@ msgstr "" msgid "Set of *.wiki files" msgstr "" -#: lib\pwiki\Exporters.py:561 lib\pwiki\Importers.py:61 +#: lib\pwiki\Exporters.py:560 lib\pwiki\Exporters.py:836 +msgid "No usable separator found" +msgstr "" + +#: lib\pwiki\Exporters.py:592 lib\pwiki\Importers.py:62 msgid "Multipage text" msgstr "" -#: lib\pwiki\Exporters.py:595 lib\pwiki\Importers.py:74 +#: lib\pwiki\Exporters.py:626 lib\pwiki\Importers.py:75 msgid "Multipage files (*.mpt)" msgstr "" -#: lib\pwiki\Exporters.py:596 lib\pwiki\Importers.py:75 +#: lib\pwiki\Exporters.py:627 lib\pwiki\Importers.py:76 msgid "Text file (*.txt)" msgstr "" -#: lib\pwiki\Exporters.py:805 -msgid "No usable separator found" -msgstr "" - #: lib\pwiki\FileCleanup.py:203 msgid "Scan links in %s" msgstr "" @@ -2636,19 +2689,19 @@ msgstr "" msgid "Only a real wiki page can be a clipboard catcher" msgstr "" -#: lib\pwiki\Importers.py:171 +#: lib\pwiki\Importers.py:178 msgid "Opening import file failed" msgstr "" -#: lib\pwiki\Importers.py:202 lib\pwiki\Importers.py:217 +#: lib\pwiki\Importers.py:209 lib\pwiki\Importers.py:224 msgid "Bad file format, header not detected" msgstr "" -#: lib\pwiki\Importers.py:210 +#: lib\pwiki\Importers.py:217 msgid "File format number %i is not supported" msgstr "" -#: lib\pwiki\Importers.py:466 +#: lib\pwiki\Importers.py:483 msgid "Bad wiki word: %s, %s" msgstr "" @@ -2673,7 +2726,7 @@ msgid "Other WikidPad process(es) seem(s) to run already\n" msgstr "" #: lib\pwiki\MainApp.py:334 lib\pwiki\MainApp.py:357 lib\pwiki\MainApp.py:367 -#: lib\pwiki\PersonalWikiFrame.py:3099 +#: lib\pwiki\PersonalWikiFrame.py:3114 msgid "Continue?" msgstr "" @@ -2697,7 +2750,7 @@ msgstr "" msgid "Plugin options" msgstr "" -#: lib\pwiki\MainApp.py:845 lib\pwiki\OptionsDialog.py:689 +#: lib\pwiki\MainApp.py:845 lib\pwiki\OptionsDialog.py:695 msgid "Wiki language" msgstr "" @@ -2705,11 +2758,11 @@ msgstr "" msgid "Plugins" msgstr "" -#: lib\pwiki\MainAreaPanel.py:665 lib\pwiki\WikiTxtCtrl.py:2341 +#: lib\pwiki\MainAreaPanel.py:665 lib\pwiki\WikiTxtCtrl.py:2397 msgid "This can only be done for the page of a wiki word" msgstr "" -#: lib\pwiki\MainAreaPanel.py:666 lib\pwiki\WikiTxtCtrl.py:2342 +#: lib\pwiki\MainAreaPanel.py:666 lib\pwiki\WikiTxtCtrl.py:2398 msgid "Not a wiki page" msgstr "" @@ -2768,144 +2821,156 @@ msgstr "" msgid "Name collision: Item '%s' exists already in database" msgstr "" -#: lib\pwiki\OptionsDialog.py:669 +#: lib\pwiki\OptionsDialog.py:675 msgid "Application" msgstr "" -#: lib\pwiki\OptionsDialog.py:670 +#: lib\pwiki\OptionsDialog.py:676 msgid "User interface" msgstr "" -#: lib\pwiki\OptionsDialog.py:671 +#: lib\pwiki\OptionsDialog.py:677 msgid "Security" msgstr "" -#: lib\pwiki\OptionsDialog.py:672 +#: lib\pwiki\OptionsDialog.py:678 msgid "Tree" msgstr "" -#: lib\pwiki\OptionsDialog.py:673 +#: lib\pwiki\OptionsDialog.py:679 msgid "HTML preview/export" msgstr "" -#: lib\pwiki\OptionsDialog.py:674 +#: lib\pwiki\OptionsDialog.py:680 msgid "HTML header" msgstr "" -#: lib\pwiki\OptionsDialog.py:675 +#: lib\pwiki\OptionsDialog.py:681 msgid "Editor" msgstr "" -#: lib\pwiki\OptionsDialog.py:676 +#: lib\pwiki\OptionsDialog.py:682 msgid "Editor Colors" msgstr "" -#: lib\pwiki\OptionsDialog.py:677 +#: lib\pwiki\OptionsDialog.py:683 msgid "Clipboard Catcher" msgstr "" -#: lib\pwiki\OptionsDialog.py:678 +#: lib\pwiki\OptionsDialog.py:684 msgid "File Launcher" msgstr "" -#: lib\pwiki\OptionsDialog.py:679 +#: lib\pwiki\OptionsDialog.py:685 msgid "Mouse" msgstr "" -#: lib\pwiki\OptionsDialog.py:680 +#: lib\pwiki\OptionsDialog.py:686 msgid "Chron. view" msgstr "" -#: lib\pwiki\OptionsDialog.py:681 +#: lib\pwiki\OptionsDialog.py:687 msgid "Searching" msgstr "" -#: lib\pwiki\OptionsDialog.py:682 lib\pwiki\OptionsDialog.py:691 +#: lib\pwiki\OptionsDialog.py:688 lib\pwiki\OptionsDialog.py:697 msgid "Advanced" msgstr "" -#: lib\pwiki\OptionsDialog.py:683 +#: lib\pwiki\OptionsDialog.py:689 msgid "Timing" msgstr "" -#: lib\pwiki\OptionsDialog.py:684 +#: lib\pwiki\OptionsDialog.py:690 msgid "Autosave" msgstr "" -#: lib\pwiki\OptionsDialog.py:686 +#: lib\pwiki\OptionsDialog.py:692 msgid "Current Wiki" msgstr "" -#: lib\pwiki\OptionsDialog.py:687 +#: lib\pwiki\OptionsDialog.py:693 msgid "Headings" msgstr "" -#: lib\pwiki\OptionsDialog.py:831 +#: lib\pwiki\OptionsDialog.py:837 msgid "IE" msgstr "" -#: lib\pwiki\OptionsDialog.py:833 +#: lib\pwiki\OptionsDialog.py:839 msgid "Mozilla" msgstr "" -#: lib\pwiki\OptionsDialog.py:837 +#: lib\pwiki\OptionsDialog.py:843 msgid "Webkit" msgstr "" -#: lib\pwiki\OptionsDialog.py:971 +#: lib\pwiki\OptionsDialog.py:980 msgid "Wave files (*.wav)|*.wav" msgstr "" -#: lib\pwiki\OptionsDialog.py:982 lib\pwiki\OptionsDialog.py:1303 +#: lib\pwiki\OptionsDialog.py:991 lib\pwiki\OptionsDialog.py:1312 msgid "All files (*.*)|*" msgstr "" -#: lib\pwiki\OptionsDialog.py:1293 +#: lib\pwiki\OptionsDialog.py:1302 msgid "Select Directory" msgstr "" -#: lib\pwiki\OptionsDialog.py:1301 +#: lib\pwiki\OptionsDialog.py:1310 msgid "Select File" msgstr "" -#: lib\pwiki\PWikiNonCore.py:41 +#: lib\pwiki\PWikiNonCore.py:44 msgid "Choose a file to create URL for" msgstr "" -#: lib\pwiki\PWikiNonCore.py:70 lib\pwiki\PWikiNonCore.py:71 +#: lib\pwiki\PWikiNonCore.py:73 lib\pwiki\PWikiNonCore.py:74 msgid " Scanning " msgstr "" -#: lib\pwiki\PWikiNonCore.py:106 +#: lib\pwiki\PWikiNonCore.py:138 msgid "&File URL..." msgstr "" -#: lib\pwiki\PWikiNonCore.py:106 +#: lib\pwiki\PWikiNonCore.py:138 msgid "Use file dialog to add URL" msgstr "" -#: lib\pwiki\PWikiNonCore.py:110 +#: lib\pwiki\PWikiNonCore.py:142 msgid "Current &Date" msgstr "" -#: lib\pwiki\PWikiNonCore.py:110 +#: lib\pwiki\PWikiNonCore.py:142 msgid "Insert current date" msgstr "" -#: lib\pwiki\PWikiNonCore.py:115 +#: lib\pwiki\PWikiNonCore.py:148 msgid "File cleanup..." msgstr "" -#: lib\pwiki\PWikiNonCore.py:115 +#: lib\pwiki\PWikiNonCore.py:148 msgid "Remove orphaned files and dead links" msgstr "" +#: lib\pwiki\PWikiNonCore.py:153 +msgid "Page read only" +msgstr "" + +#: lib\pwiki\PWikiNonCore.py:153 +msgid "Set current page read only" +msgstr "" + +#: lib\pwiki\PWikiNonCore.py:159 lib\pwiki\PWikiNonCore.py:160 +msgid "Open trashcan" +msgstr "" + #: lib\pwiki\PersonalWikiFrame.py:154 msgid "Bad formatted command line." msgstr "" #: lib\pwiki\PersonalWikiFrame.py:399 lib\pwiki\PersonalWikiFrame.py:407 -#: lib\pwiki\PersonalWikiFrame.py:1208 +#: lib\pwiki\PersonalWikiFrame.py:1217 msgid "Wiki doesn't exist: %s" msgstr "" @@ -3009,7 +3074,7 @@ msgstr "" msgid "Other Export..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:845 lib\pwiki\PersonalWikiFrame.py:1874 +#: lib\pwiki\PersonalWikiFrame.py:845 lib\pwiki\PersonalWikiFrame.py:1889 msgid "Open general export dialog" msgstr "" @@ -3021,1176 +3086,1176 @@ msgstr "" msgid "Show the print dialog" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:854 +#: lib\pwiki\PersonalWikiFrame.py:860 msgid "&Properties..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:855 +#: lib\pwiki\PersonalWikiFrame.py:861 msgid "Show general information about current wiki" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:859 +#: lib\pwiki\PersonalWikiFrame.py:865 msgid "Maintenance" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:863 +#: lib\pwiki\PersonalWikiFrame.py:869 msgid "&Rebuild Wiki..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:864 +#: lib\pwiki\PersonalWikiFrame.py:870 msgid "Rebuild this wiki and its cache completely" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:878 +#: lib\pwiki\PersonalWikiFrame.py:884 msgid "&Update cache..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:879 +#: lib\pwiki\PersonalWikiFrame.py:885 msgid "Update cache where marked as not up to date" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:884 +#: lib\pwiki\PersonalWikiFrame.py:890 msgid "&Initiate update..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:885 +#: lib\pwiki\PersonalWikiFrame.py:891 msgid "Initiate full cache update which is done mainly in background" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:905 +#: lib\pwiki\PersonalWikiFrame.py:913 msgid "Show job count..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:906 +#: lib\pwiki\PersonalWikiFrame.py:914 msgid "Show how many update jobs are waiting in background" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:911 +#: lib\pwiki\PersonalWikiFrame.py:920 msgid "Open as &Type..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:912 +#: lib\pwiki\PersonalWikiFrame.py:921 msgid "Open wiki with a specified wiki database type" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:916 +#: lib\pwiki\PersonalWikiFrame.py:925 msgid "Reconnect..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:917 +#: lib\pwiki\PersonalWikiFrame.py:926 msgid "Reconnect to database after connection failure" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:923 +#: lib\pwiki\PersonalWikiFrame.py:932 msgid "&Optimise Database" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:924 +#: lib\pwiki\PersonalWikiFrame.py:933 msgid "Free unused space in database" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:931 +#: lib\pwiki\PersonalWikiFrame.py:940 msgid "&Copy .wiki files to database" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:932 +#: lib\pwiki\PersonalWikiFrame.py:941 msgid "Copy .wiki files to database" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:942 +#: lib\pwiki\PersonalWikiFrame.py:951 msgid "E&xit" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:942 lib\pwiki\PersonalWikiFrame.py:5705 +#: lib\pwiki\PersonalWikiFrame.py:951 lib\pwiki\PersonalWikiFrame.py:5735 msgid "Exit" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1110 +#: lib\pwiki\PersonalWikiFrame.py:1119 msgid "Reread text blocks" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1111 +#: lib\pwiki\PersonalWikiFrame.py:1120 msgid "Reread the text block file(s) and recreate menu" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1165 +#: lib\pwiki\PersonalWikiFrame.py:1174 msgid "Add wiki" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1166 +#: lib\pwiki\PersonalWikiFrame.py:1175 msgid "Add a wiki to the favorites" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1171 lib\pwiki\PersonalWikiFrame.py:1172 +#: lib\pwiki\PersonalWikiFrame.py:1180 lib\pwiki\PersonalWikiFrame.py:1181 msgid "Manage favorites" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1194 lib\pwiki\PersonalWikiFrame.py:1924 -#: lib\pwiki\PersonalWikiFrame.py:2443 lib\pwiki\PersonalWikiFrame.py:3859 -#: lib\pwiki\PersonalWikiFrame.py:4945 lib\pwiki\PersonalWikiFrame.py:5282 +#: lib\pwiki\PersonalWikiFrame.py:1203 lib\pwiki\PersonalWikiFrame.py:1939 +#: lib\pwiki\PersonalWikiFrame.py:2458 lib\pwiki\PersonalWikiFrame.py:3879 +#: lib\pwiki\PersonalWikiFrame.py:4967 lib\pwiki\PersonalWikiFrame.py:5304 msgid "Error while starting new WikidPad instance" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1301 +#: lib\pwiki\PersonalWikiFrame.py:1310 msgid "W&iki" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1312 +#: lib\pwiki\PersonalWikiFrame.py:1321 msgid "&Undo" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1316 +#: lib\pwiki\PersonalWikiFrame.py:1325 msgid "&Redo" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1324 +#: lib\pwiki\PersonalWikiFrame.py:1333 msgid "&Search and Replace..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1326 +#: lib\pwiki\PersonalWikiFrame.py:1335 msgid "Search and replace inside current page" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1332 +#: lib\pwiki\PersonalWikiFrame.py:1341 msgid "Cu&t" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1337 +#: lib\pwiki\PersonalWikiFrame.py:1346 msgid "&Copy" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1341 +#: lib\pwiki\PersonalWikiFrame.py:1350 msgid "&Paste" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1346 +#: lib\pwiki\PersonalWikiFrame.py:1355 msgid "Select &All" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1352 +#: lib\pwiki\PersonalWikiFrame.py:1361 msgid "Copy to Sc&ratchPad" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1354 +#: lib\pwiki\PersonalWikiFrame.py:1363 msgid "Copy selected text to ScratchPad" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1360 +#: lib\pwiki\PersonalWikiFrame.py:1369 msgid "Paste T&extblock" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1368 +#: lib\pwiki\PersonalWikiFrame.py:1377 msgid "C&lipboard Catcher" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1371 lib\pwiki\PersonalWikiFrame.py:4256 +#: lib\pwiki\PersonalWikiFrame.py:1380 lib\pwiki\PersonalWikiFrame.py:4276 msgid "Set at Page" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1373 +#: lib\pwiki\PersonalWikiFrame.py:1382 msgid "Text copied to clipboard is also appended to this page" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1379 +#: lib\pwiki\PersonalWikiFrame.py:1388 msgid "Set at Cursor" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1381 +#: lib\pwiki\PersonalWikiFrame.py:1390 msgid "Text copied to clipboard is also added to cursor position" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1387 +#: lib\pwiki\PersonalWikiFrame.py:1396 msgid "Set Off" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1389 +#: lib\pwiki\PersonalWikiFrame.py:1398 msgid "Switch off clipboard catcher" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1399 +#: lib\pwiki\PersonalWikiFrame.py:1408 msgid "Spell Check..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1401 +#: lib\pwiki\PersonalWikiFrame.py:1410 msgid "Spell check current and possibly further pages" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1405 +#: lib\pwiki\PersonalWikiFrame.py:1414 msgid "Spell Check While Type" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1406 +#: lib\pwiki\PersonalWikiFrame.py:1415 msgid "Set if editor should do spell checking during typing" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1411 +#: lib\pwiki\PersonalWikiFrame.py:1420 msgid "Clear Ignore List" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1413 +#: lib\pwiki\PersonalWikiFrame.py:1422 msgid "Clear the list of words to ignore for spell check while type" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1422 +#: lib\pwiki\PersonalWikiFrame.py:1431 msgid "&Insert" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1446 +#: lib\pwiki\PersonalWikiFrame.py:1455 msgid "&Settings" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1449 +#: lib\pwiki\PersonalWikiFrame.py:1458 msgid "&Date Format..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1450 +#: lib\pwiki\PersonalWikiFrame.py:1459 msgid "Set date format for inserting current date" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1454 +#: lib\pwiki\PersonalWikiFrame.py:1463 msgid "Auto-&Wrap" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1455 +#: lib\pwiki\PersonalWikiFrame.py:1464 msgid "Set if editor should wrap long lines" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1469 +#: lib\pwiki\PersonalWikiFrame.py:1478 msgid "Auto-&Indent" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1470 +#: lib\pwiki\PersonalWikiFrame.py:1479 msgid "Auto indentation" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1476 +#: lib\pwiki\PersonalWikiFrame.py:1485 msgid "Auto-&Bullets" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1477 +#: lib\pwiki\PersonalWikiFrame.py:1486 msgid "Show bullet on next line if current has one" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1483 +#: lib\pwiki\PersonalWikiFrame.py:1492 msgid "Tabs to spaces" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1484 +#: lib\pwiki\PersonalWikiFrame.py:1493 msgid "Write spaces when hitting TAB key" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1492 +#: lib\pwiki\PersonalWikiFrame.py:1501 msgid "Show T&oolbar" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1494 +#: lib\pwiki\PersonalWikiFrame.py:1503 msgid "Show toolbar" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1501 +#: lib\pwiki\PersonalWikiFrame.py:1510 msgid "Show &Tree View" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1503 +#: lib\pwiki\PersonalWikiFrame.py:1512 msgid "Show Tree Control" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1510 +#: lib\pwiki\PersonalWikiFrame.py:1519 msgid "Show &Chron. View" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1512 +#: lib\pwiki\PersonalWikiFrame.py:1521 msgid "Show chronological view" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1519 +#: lib\pwiki\PersonalWikiFrame.py:1528 msgid "Show &Page Structure" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1521 +#: lib\pwiki\PersonalWikiFrame.py:1530 msgid "Show structure (headings) of the page" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1533 +#: lib\pwiki\PersonalWikiFrame.py:1542 msgid "Show &Indentation Guides" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1534 +#: lib\pwiki\PersonalWikiFrame.py:1543 msgid "Show indentation guides in editor" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1539 +#: lib\pwiki\PersonalWikiFrame.py:1548 msgid "Show Line &Numbers" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1540 +#: lib\pwiki\PersonalWikiFrame.py:1549 msgid "Show line numbers in editor" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1547 +#: lib\pwiki\PersonalWikiFrame.py:1556 msgid "Stay on Top" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1549 +#: lib\pwiki\PersonalWikiFrame.py:1558 msgid "Stay on Top of all other windows" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1557 +#: lib\pwiki\PersonalWikiFrame.py:1566 msgid "&Zoom In" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1558 lib\pwiki\PersonalWikiFrame.py:2119 +#: lib\pwiki\PersonalWikiFrame.py:1567 lib\pwiki\PersonalWikiFrame.py:2134 msgid "Zoom In" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1561 +#: lib\pwiki\PersonalWikiFrame.py:1570 msgid "Zoo&m Out" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1562 lib\pwiki\PersonalWikiFrame.py:2124 +#: lib\pwiki\PersonalWikiFrame.py:1571 lib\pwiki\PersonalWikiFrame.py:2139 msgid "Zoom Out" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1584 +#: lib\pwiki\PersonalWikiFrame.py:1593 msgid "Toggle Ed./Prev" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1586 lib\pwiki\PersonalWikiFrame.py:2115 +#: lib\pwiki\PersonalWikiFrame.py:1595 lib\pwiki\PersonalWikiFrame.py:2130 msgid "Switch between editor and preview" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1590 +#: lib\pwiki\PersonalWikiFrame.py:1599 msgid "Enter Edit Mode" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1591 +#: lib\pwiki\PersonalWikiFrame.py:1600 msgid "Show editor in tab" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1595 +#: lib\pwiki\PersonalWikiFrame.py:1604 msgid "Enter Preview Mode" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1597 +#: lib\pwiki\PersonalWikiFrame.py:1606 msgid "Show preview in tab" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1621 +#: lib\pwiki\PersonalWikiFrame.py:1630 msgid "&Save" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1622 +#: lib\pwiki\PersonalWikiFrame.py:1631 msgid "Save all open pages" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1629 +#: lib\pwiki\PersonalWikiFrame.py:1638 msgid "&Rename" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1630 +#: lib\pwiki\PersonalWikiFrame.py:1639 msgid "Rename current wiki word" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1636 +#: lib\pwiki\PersonalWikiFrame.py:1645 msgid "Delete current wiki word" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1643 +#: lib\pwiki\PersonalWikiFrame.py:1652 msgid "Set as Roo&t" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1644 +#: lib\pwiki\PersonalWikiFrame.py:1653 msgid "Set current wiki word as tree root" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1648 +#: lib\pwiki\PersonalWikiFrame.py:1657 msgid "R&eset Root" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1649 +#: lib\pwiki\PersonalWikiFrame.py:1658 msgid "Set home wiki word as tree root" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1653 +#: lib\pwiki\PersonalWikiFrame.py:1662 msgid "S&ynchronise Tree" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1654 +#: lib\pwiki\PersonalWikiFrame.py:1663 msgid "Find the current wiki word in the tree" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1659 +#: lib\pwiki\PersonalWikiFrame.py:1668 msgid "&Follow Link" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1660 +#: lib\pwiki\PersonalWikiFrame.py:1669 msgid "Activate link/word" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1665 +#: lib\pwiki\PersonalWikiFrame.py:1674 msgid "Follow Link in &New Tab" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1666 +#: lib\pwiki\PersonalWikiFrame.py:1675 msgid "Activate link/word in new tab" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1671 +#: lib\pwiki\PersonalWikiFrame.py:1680 msgid "Copy &URL to Clipboard" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1673 +#: lib\pwiki\PersonalWikiFrame.py:1682 msgid "Copy full \"wiki:\" URL of the word to clipboard" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1679 +#: lib\pwiki\PersonalWikiFrame.py:1688 msgid "&Add version" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1680 +#: lib\pwiki\PersonalWikiFrame.py:1689 msgid "Add new version" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1688 +#: lib\pwiki\PersonalWikiFrame.py:1703 msgid "&Bold" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1689 lib\pwiki\PersonalWikiFrame.py:2100 +#: lib\pwiki\PersonalWikiFrame.py:1704 lib\pwiki\PersonalWikiFrame.py:2115 msgid "Bold" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1695 +#: lib\pwiki\PersonalWikiFrame.py:1710 msgid "&Italic" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1696 lib\pwiki\PersonalWikiFrame.py:2106 +#: lib\pwiki\PersonalWikiFrame.py:1711 lib\pwiki\PersonalWikiFrame.py:2121 msgid "Italic" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1702 +#: lib\pwiki\PersonalWikiFrame.py:1717 msgid "&Heading" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1703 +#: lib\pwiki\PersonalWikiFrame.py:1718 msgid "Add Heading" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1711 +#: lib\pwiki\PersonalWikiFrame.py:1726 msgid "&Rewrap Text" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1713 +#: lib\pwiki\PersonalWikiFrame.py:1728 msgid "Rewrap Text" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1718 +#: lib\pwiki\PersonalWikiFrame.py:1733 msgid "&Convert" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1721 +#: lib\pwiki\PersonalWikiFrame.py:1736 msgid "Selection to &Link" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1722 +#: lib\pwiki\PersonalWikiFrame.py:1737 msgid "Remove non-allowed characters and make sel. a wiki word link" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1727 +#: lib\pwiki\PersonalWikiFrame.py:1742 msgid "Selection to &Wiki Word" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1729 +#: lib\pwiki\PersonalWikiFrame.py:1744 msgid "Put selected text in a new or existing wiki word" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1734 +#: lib\pwiki\PersonalWikiFrame.py:1749 msgid "Absolute/Relative &File URL" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1736 +#: lib\pwiki\PersonalWikiFrame.py:1751 msgid "Convert file URL from absolute to relative and vice versa" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1750 +#: lib\pwiki\PersonalWikiFrame.py:1765 msgid "&Icon Name" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1762 +#: lib\pwiki\PersonalWikiFrame.py:1777 msgid "&Color Name" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1770 +#: lib\pwiki\PersonalWikiFrame.py:1785 msgid "&Add Attribute" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1779 +#: lib\pwiki\PersonalWikiFrame.py:1794 msgid "&Icon Attribute" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1789 +#: lib\pwiki\PersonalWikiFrame.py:1804 msgid "&Color Attribute" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1798 +#: lib\pwiki\PersonalWikiFrame.py:1813 msgid "&Back" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1799 +#: lib\pwiki\PersonalWikiFrame.py:1814 msgid "Go backward" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1804 +#: lib\pwiki\PersonalWikiFrame.py:1819 msgid "&Forward" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1805 +#: lib\pwiki\PersonalWikiFrame.py:1820 msgid "Go forward" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1810 +#: lib\pwiki\PersonalWikiFrame.py:1825 msgid "&Wiki Home" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1811 +#: lib\pwiki\PersonalWikiFrame.py:1826 msgid "Go to wiki homepage" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1817 +#: lib\pwiki\PersonalWikiFrame.py:1832 msgid "Up&ward" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1819 lib\pwiki\PersonalWikiFrame.py:2067 -#: lib\pwiki\PersonalWikiFrame.py:2068 +#: lib\pwiki\PersonalWikiFrame.py:1834 lib\pwiki\PersonalWikiFrame.py:2082 +#: lib\pwiki\PersonalWikiFrame.py:2083 msgid "Go upward from a subpage" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1824 +#: lib\pwiki\PersonalWikiFrame.py:1839 msgid "Go to &Page..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1825 +#: lib\pwiki\PersonalWikiFrame.py:1840 msgid "Open wiki word" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1830 +#: lib\pwiki\PersonalWikiFrame.py:1845 msgid "Go to P&arent..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1832 +#: lib\pwiki\PersonalWikiFrame.py:1847 msgid "List parents of current wiki word" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1835 +#: lib\pwiki\PersonalWikiFrame.py:1850 msgid "List &Children..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1837 +#: lib\pwiki\PersonalWikiFrame.py:1852 msgid "List children of current wiki word" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1840 +#: lib\pwiki\PersonalWikiFrame.py:1855 msgid "List Pa&rentless Pages" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1842 +#: lib\pwiki\PersonalWikiFrame.py:1857 msgid "List nodes with no parent relations" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1847 +#: lib\pwiki\PersonalWikiFrame.py:1862 msgid "Show &History..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1848 +#: lib\pwiki\PersonalWikiFrame.py:1863 msgid "View tab history" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1851 +#: lib\pwiki\PersonalWikiFrame.py:1866 msgid "&Up History..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1852 +#: lib\pwiki\PersonalWikiFrame.py:1867 msgid "Up in tab history" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1855 +#: lib\pwiki\PersonalWikiFrame.py:1870 msgid "&Down History..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1856 +#: lib\pwiki\PersonalWikiFrame.py:1871 msgid "Down in tab history" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1861 +#: lib\pwiki\PersonalWikiFrame.py:1876 msgid "Add B&ookmark" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1862 +#: lib\pwiki\PersonalWikiFrame.py:1877 msgid "Add bookmark to page" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1866 +#: lib\pwiki\PersonalWikiFrame.py:1881 msgid "Go to &Bookmark..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1867 +#: lib\pwiki\PersonalWikiFrame.py:1882 msgid "List bookmarks" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1873 +#: lib\pwiki\PersonalWikiFrame.py:1888 msgid "&Export..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1877 +#: lib\pwiki\PersonalWikiFrame.py:1892 msgid "&Continuous Export..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1878 +#: lib\pwiki\PersonalWikiFrame.py:1893 msgid "Open export dialog for continuous export of changes" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1884 +#: lib\pwiki\PersonalWikiFrame.py:1899 msgid "&Import..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1885 +#: lib\pwiki\PersonalWikiFrame.py:1900 msgid "Import dialog" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1891 +#: lib\pwiki\PersonalWikiFrame.py:1906 msgid "Scripts" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1892 +#: lib\pwiki\PersonalWikiFrame.py:1907 msgid "Run scripts, evaluate expressions" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1894 +#: lib\pwiki\PersonalWikiFrame.py:1909 msgid "&Eval" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1895 +#: lib\pwiki\PersonalWikiFrame.py:1910 msgid "Evaluate script blocks" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1900 +#: lib\pwiki\PersonalWikiFrame.py:1915 msgid "Run Function &%i\tCtrl-%i" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1901 +#: lib\pwiki\PersonalWikiFrame.py:1916 msgid "Run script function %i" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1906 +#: lib\pwiki\PersonalWikiFrame.py:1921 msgid "O&ptions..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1907 +#: lib\pwiki\PersonalWikiFrame.py:1922 msgid "Set options" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1928 +#: lib\pwiki\PersonalWikiFrame.py:1943 msgid "&Open help wiki" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1929 +#: lib\pwiki\PersonalWikiFrame.py:1944 msgid "Open WikidPadHelp, the help wiki" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1938 +#: lib\pwiki\PersonalWikiFrame.py:1953 msgid "&Visit Homepage" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1939 +#: lib\pwiki\PersonalWikiFrame.py:1954 msgid "Visit wikidPad homepage" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1949 +#: lib\pwiki\PersonalWikiFrame.py:1964 msgid "Show &License" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1950 +#: lib\pwiki\PersonalWikiFrame.py:1965 msgid "Show license of WikidPad and used components" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1967 +#: lib\pwiki\PersonalWikiFrame.py:1982 msgid "&About" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1971 +#: lib\pwiki\PersonalWikiFrame.py:1986 msgid "&Wiki" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1972 +#: lib\pwiki\PersonalWikiFrame.py:1987 msgid "&Edit" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1973 +#: lib\pwiki\PersonalWikiFrame.py:1988 msgid "&View" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1974 +#: lib\pwiki\PersonalWikiFrame.py:1989 msgid "&Tabs" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1975 +#: lib\pwiki\PersonalWikiFrame.py:1990 msgid "Wiki &Page" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1976 +#: lib\pwiki\PersonalWikiFrame.py:1991 msgid "&Format" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1977 +#: lib\pwiki\PersonalWikiFrame.py:1992 msgid "&Navigate" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1978 +#: lib\pwiki\PersonalWikiFrame.py:1993 msgid "E&xtra" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:1994 +#: lib\pwiki\PersonalWikiFrame.py:2009 msgid "Pl&ugins" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2000 +#: lib\pwiki\PersonalWikiFrame.py:2015 msgid "&Help" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2002 +#: lib\pwiki\PersonalWikiFrame.py:2017 msgid "He&lp" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2028 lib\pwiki\PersonalWikiFrame.py:2029 +#: lib\pwiki\PersonalWikiFrame.py:2043 lib\pwiki\PersonalWikiFrame.py:2044 msgid "Back" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2034 lib\pwiki\PersonalWikiFrame.py:2035 +#: lib\pwiki\PersonalWikiFrame.py:2049 lib\pwiki\PersonalWikiFrame.py:2050 msgid "Forward" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2040 lib\pwiki\PersonalWikiFrame.py:2041 +#: lib\pwiki\PersonalWikiFrame.py:2055 lib\pwiki\PersonalWikiFrame.py:2056 msgid "Wiki Home" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2055 lib\pwiki\PersonalWikiFrame.py:2056 +#: lib\pwiki\PersonalWikiFrame.py:2070 lib\pwiki\PersonalWikiFrame.py:2071 msgid "Search" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2061 lib\pwiki\PersonalWikiFrame.py:2062 +#: lib\pwiki\PersonalWikiFrame.py:2076 lib\pwiki\PersonalWikiFrame.py:2077 msgid "Find current word in tree" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2071 lib\pwiki\PersonalWikiFrame.py:2090 -#: lib\pwiki\PersonalWikiFrame.py:2110 +#: lib\pwiki\PersonalWikiFrame.py:2086 lib\pwiki\PersonalWikiFrame.py:2105 +#: lib\pwiki\PersonalWikiFrame.py:2125 msgid "Separator" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2075 lib\pwiki\PersonalWikiFrame.py:2076 +#: lib\pwiki\PersonalWikiFrame.py:2090 lib\pwiki\PersonalWikiFrame.py:2091 msgid "Save Wiki Word" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2086 lib\pwiki\PersonalWikiFrame.py:2087 -#: lib\pwiki\PersonalWikiFrame.py:4401 +#: lib\pwiki\PersonalWikiFrame.py:2101 lib\pwiki\PersonalWikiFrame.py:2102 +#: lib\pwiki\PersonalWikiFrame.py:4422 msgid "Delete Wiki Word" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2094 +#: lib\pwiki\PersonalWikiFrame.py:2109 msgid "Heading" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2114 +#: lib\pwiki\PersonalWikiFrame.py:2129 msgid "Switch Editor/Preview" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2135 +#: lib\pwiki\PersonalWikiFrame.py:2150 msgid "Wikize Selected Word " msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2136 +#: lib\pwiki\PersonalWikiFrame.py:2151 msgid "Wikize Selected Word" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2208 +#: lib\pwiki\PersonalWikiFrame.py:2223 msgid "Line: 9999 Col: 9999 Pos: 9999999988888" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2380 +#: lib\pwiki\PersonalWikiFrame.py:2395 msgid "Regular expression error" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2389 +#: lib\pwiki\PersonalWikiFrame.py:2404 msgid "Are you sure you want to reconnect? You may lose some data by this process." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2391 +#: lib\pwiki\PersonalWikiFrame.py:2406 msgid "Reconnect database" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2405 lib\pwiki\PersonalWikiFrame.py:3466 +#: lib\pwiki\PersonalWikiFrame.py:2420 lib\pwiki\PersonalWikiFrame.py:3481 msgid "Error while trying to reconnect:\n" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2407 +#: lib\pwiki\PersonalWikiFrame.py:2422 msgid "There was an error while reconnecting the database\n" "\n" "Would you like to try it again?\n" "%s" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2410 +#: lib\pwiki\PersonalWikiFrame.py:2425 msgid "Error reconnecting!" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2426 lib\pwiki\PersonalWikiFrame.py:3513 +#: lib\pwiki\PersonalWikiFrame.py:2441 lib\pwiki\PersonalWikiFrame.py:3528 msgid "Error while trying to write:\n" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2428 +#: lib\pwiki\PersonalWikiFrame.py:2443 msgid "There was an error while writing to the database\n" "\n" "Would you like to try it again?\n" "%s" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2431 +#: lib\pwiki\PersonalWikiFrame.py:2446 msgid "Error writing!" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2550 +#: lib\pwiki\PersonalWikiFrame.py:2565 msgid "There was an error loading the icons for the tree control." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2816 +#: lib\pwiki\PersonalWikiFrame.py:2831 msgid "No data handler available to create database." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2828 +#: lib\pwiki\PersonalWikiFrame.py:2843 msgid "A wiki already exists in '%s', overwrite? (This deletes everything in and below this directory!)" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2830 +#: lib\pwiki\PersonalWikiFrame.py:2845 msgid "Warning" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2871 +#: lib\pwiki\PersonalWikiFrame.py:2886 msgid "A wiki database already exists in this location, overwrite?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2873 +#: lib\pwiki\PersonalWikiFrame.py:2888 msgid "Wiki DB Exists" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2884 +#: lib\pwiki\PersonalWikiFrame.py:2899 msgid "There was an error creating the wiki database." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2953 lib\pwiki\PersonalWikiFrame.py:2954 +#: lib\pwiki\PersonalWikiFrame.py:2968 lib\pwiki\PersonalWikiFrame.py:2969 msgid "Choose database type" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:2991 +#: lib\pwiki\PersonalWikiFrame.py:3006 msgid "Inaccessible or missing file: %s" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3042 lib\pwiki\PersonalWikiFrame.py:3127 +#: lib\pwiki\PersonalWikiFrame.py:3057 lib\pwiki\PersonalWikiFrame.py:3142 msgid "Error connecting to database in '%s'" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3047 +#: lib\pwiki\PersonalWikiFrame.py:3062 msgid "The wiki needs an update to work with this version of WikidPad. Older versions of WikidPad may be unable to read the wiki after an update." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3050 +#: lib\pwiki\PersonalWikiFrame.py:3065 msgid "Update database?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3085 +#: lib\pwiki\PersonalWikiFrame.py:3100 msgid "Wiki '%s' is probably in use by different\n" "instance of WikidPad. Connect anyway (dangerous!)?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3087 +#: lib\pwiki\PersonalWikiFrame.py:3102 msgid "Wiki already in use" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3096 +#: lib\pwiki\PersonalWikiFrame.py:3111 msgid "Configuration file '%s' is corrupted or missing.\n" "You may have to change some settings in configuration page \"Current Wiki\" and below which were lost." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3134 +#: lib\pwiki\PersonalWikiFrame.py:3149 msgid "Can't write to database '%s'" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3343 +#: lib\pwiki\PersonalWikiFrame.py:3358 msgid "There is no (write-)access to underlying wiki\n" "Close anyway and loose possible changes?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3345 +#: lib\pwiki\PersonalWikiFrame.py:3360 msgid "Close anyway" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3436 +#: lib\pwiki\PersonalWikiFrame.py:3451 msgid "This operation requires an open database" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3437 +#: lib\pwiki\PersonalWikiFrame.py:3452 msgid "No open database" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3449 +#: lib\pwiki\PersonalWikiFrame.py:3464 msgid "No connection to database. Try to reconnect?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3450 +#: lib\pwiki\PersonalWikiFrame.py:3465 msgid "Reconnect database?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3457 +#: lib\pwiki\PersonalWikiFrame.py:3472 msgid "Trying to reconnect database..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3469 +#: lib\pwiki\PersonalWikiFrame.py:3484 msgid "Error while reconnecting database" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3494 +#: lib\pwiki\PersonalWikiFrame.py:3509 msgid "This operation needs write access to database\n" "Try to write?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3495 +#: lib\pwiki\PersonalWikiFrame.py:3510 msgid "Try writing?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3502 +#: lib\pwiki\PersonalWikiFrame.py:3517 msgid "Trying to write to database..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3516 +#: lib\pwiki\PersonalWikiFrame.py:3531 msgid "Error while writing to database" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3540 +#: lib\pwiki\PersonalWikiFrame.py:3555 msgid "Database connection error: %s.\n" "Try to re-establish, then run \"Wiki\"->\"Reconnect\"" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3542 lib\pwiki\PersonalWikiFrame.py:3559 +#: lib\pwiki\PersonalWikiFrame.py:3557 lib\pwiki\PersonalWikiFrame.py:3574 msgid "Connection lost" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3557 +#: lib\pwiki\PersonalWikiFrame.py:3572 msgid "No write access to database: %s.\n" " Try to re-establish, then run \"Wiki\"->\"Reconnect\"" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3576 +#: lib\pwiki\PersonalWikiFrame.py:3591 msgid "Trying to reconnect ..." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3585 +#: lib\pwiki\PersonalWikiFrame.py:3600 msgid "Error while trying to reconnect:" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3831 +#: lib\pwiki\PersonalWikiFrame.py:3851 msgid "Couldn't start file" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3839 +#: lib\pwiki\PersonalWikiFrame.py:3859 msgid "Couldn't open wiki: %s" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3890 +#: lib\pwiki\PersonalWikiFrame.py:3910 msgid "Mod.: %s" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3891 +#: lib\pwiki\PersonalWikiFrame.py:3911 msgid "; Crea.: %s" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3928 +#: lib\pwiki\PersonalWikiFrame.py:3948 msgid "Parent nodes of '%s'" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3940 +#: lib\pwiki\PersonalWikiFrame.py:3960 msgid "Parentless nodes" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3952 +#: lib\pwiki\PersonalWikiFrame.py:3972 msgid "Child nodes of '%s'" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:3965 +#: lib\pwiki\PersonalWikiFrame.py:3985 msgid "Bookmarks" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4112 +#: lib\pwiki\PersonalWikiFrame.py:4132 msgid "Wiki: %s" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4251 +#: lib\pwiki\PersonalWikiFrame.py:4271 msgid "Set at Page: %s\t%s" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4267 +#: lib\pwiki\PersonalWikiFrame.py:4287 msgid "Error saving global configuration" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4278 +#: lib\pwiki\PersonalWikiFrame.py:4298 msgid "Error saving current configuration" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4300 +#: lib\pwiki\PersonalWikiFrame.py:4320 msgid "No real wiki word selected to rename" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4304 +#: lib\pwiki\PersonalWikiFrame.py:4324 msgid "The scratch pad cannot be renamed." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4328 +#: lib\pwiki\PersonalWikiFrame.py:4348 msgid "Description:" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4329 +#: lib\pwiki\PersonalWikiFrame.py:4349 msgid "Store new version" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4343 +#: lib\pwiki\PersonalWikiFrame.py:4363 msgid "Do you want to delete all stored versions?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4344 +#: lib\pwiki\PersonalWikiFrame.py:4364 msgid "Delete All Versions" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4389 +#: lib\pwiki\PersonalWikiFrame.py:4409 msgid "The scratch pad cannot be deleted" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4393 +#: lib\pwiki\PersonalWikiFrame.py:4413 msgid "No real wiki word to delete" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4400 +#: lib\pwiki\PersonalWikiFrame.py:4421 msgid "Are you sure you want to delete wiki word '%s'?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4431 +#: lib\pwiki\PersonalWikiFrame.py:4453 msgid "No real wiki word to modify" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4447 +#: lib\pwiki\PersonalWikiFrame.py:4469 msgid "Replace text by WikiWord:" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4448 +#: lib\pwiki\PersonalWikiFrame.py:4470 msgid "Replace by Wiki Word" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4457 +#: lib\pwiki\PersonalWikiFrame.py:4479 msgid "'%s' is an invalid wiki word." msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4472 +#: lib\pwiki\PersonalWikiFrame.py:4494 msgid "Wiki word %s exists already\n" "Would you like to append to the word?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4475 +#: lib\pwiki\PersonalWikiFrame.py:4497 msgid "Word exists" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4733 +#: lib\pwiki\PersonalWikiFrame.py:4755 msgid "Error on export" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4805 +#: lib\pwiki\PersonalWikiFrame.py:4827 msgid "Are you sure you want to start a full rebuild of wiki in background?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4807 +#: lib\pwiki\PersonalWikiFrame.py:4829 msgid "Initiate update" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4814 lib\pwiki\PersonalWikiFrame.py:4815 +#: lib\pwiki\PersonalWikiFrame.py:4836 lib\pwiki\PersonalWikiFrame.py:4837 msgid " Initiating update " msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4829 +#: lib\pwiki\PersonalWikiFrame.py:4851 msgid "Error initiating update" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4838 +#: lib\pwiki\PersonalWikiFrame.py:4860 msgid "Are you sure you want to rebuild this wiki? You may want to backup your data first!" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4840 +#: lib\pwiki\PersonalWikiFrame.py:4862 msgid "Rebuild wiki" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4847 lib\pwiki\PersonalWikiFrame.py:4848 +#: lib\pwiki\PersonalWikiFrame.py:4869 lib\pwiki\PersonalWikiFrame.py:4870 msgid " Rebuilding wiki " msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4863 +#: lib\pwiki\PersonalWikiFrame.py:4885 msgid "Error rebuilding wiki" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4955 +#: lib\pwiki\PersonalWikiFrame.py:4977 msgid "This could overwrite pages in the database. Continue?" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:4956 +#: lib\pwiki\PersonalWikiFrame.py:4978 msgid "Import pagefiles" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:5058 +#: lib\pwiki\PersonalWikiFrame.py:5080 msgid "No list of strings passed to \"listmcstr\" dialog" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:5081 +#: lib\pwiki\PersonalWikiFrame.py:5103 msgid "Unknown dialog type" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:5250 lib\pwiki\PersonalWikiFrame.py:5268 -#: lib\pwiki\PersonalWikiFrame.py:5293 +#: lib\pwiki\PersonalWikiFrame.py:5272 lib\pwiki\PersonalWikiFrame.py:5290 +#: lib\pwiki\PersonalWikiFrame.py:5315 msgid "Choose a Wiki to open" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:5307 +#: lib\pwiki\PersonalWikiFrame.py:5329 msgid "Name for new wiki (must be in the form of a WikiWord):" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:5308 +#: lib\pwiki\PersonalWikiFrame.py:5330 msgid "Create New Wiki" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:5323 +#: lib\pwiki\PersonalWikiFrame.py:5345 msgid "Directory to store new wiki" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:5334 -#: lib\pwiki\wikidata\WikiDataManager.py:1420 -#: lib\pwiki\wikidata\WikiDataManager.py:1476 +#: lib\pwiki\PersonalWikiFrame.py:5356 +#: lib\pwiki\wikidata\WikiDataManager.py:1447 +#: lib\pwiki\wikidata\WikiDataManager.py:1503 msgid "'%s' is an invalid wiki word. %s" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:5638 +#: lib\pwiki\PersonalWikiFrame.py:5668 msgid "Clipboard Catcher at Cursor" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:5642 +#: lib\pwiki\PersonalWikiFrame.py:5672 msgid "Clipboard Catcher off" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:5703 +#: lib\pwiki\PersonalWikiFrame.py:5733 msgid "Restore" msgstr "" -#: lib\pwiki\PersonalWikiFrame.py:5704 +#: lib\pwiki\PersonalWikiFrame.py:5734 msgid "Save" msgstr "" @@ -4357,242 +4422,262 @@ msgstr "" msgid "Select wiki for favorites" msgstr "" +#: lib\pwiki\Trashcan.py:262 +msgid "Trashcan data damaged" +msgstr "" + +#: lib\pwiki\TrashcanGui.py:201 +msgid "Delete %i elements from trashcan?" +msgstr "" + +#: lib\pwiki\TrashcanGui.py:202 lib\pwiki\TrashcanGui.py:228 +msgid "Delete from trashcan" +msgstr "" + +#: lib\pwiki\TrashcanGui.py:227 +msgid "Delete all elements from trashcan?" +msgstr "" + +#: lib\pwiki\ViHelper.py:464 +msgid "Follow Hint:" +msgstr "" + #: lib\pwiki\WikiExceptions.py:72 msgid "Multiple words rename to same word" msgstr "" -#: lib\pwiki\WikiHtmlView.py:558 lib\pwiki\WikiTxtCtrl.py:2178 +#: lib\pwiki\WikiHtmlView.py:558 lib\pwiki\WikiTxtCtrl.py:2225 msgid "Folder does not exist" msgstr "" #: lib\pwiki\WikiHtmlView.py:650 lib\pwiki\WikiHtmlViewIE.py:494 -#: lib\pwiki\WikiHtmlViewWK.py:968 lib\pwiki\WikiTxtCtrl.py:3364 +#: lib\pwiki\WikiHtmlViewWK.py:999 lib\pwiki\WikiTxtCtrl.py:3204 msgid "Link to page: %s" msgstr "" -#: lib\pwiki\WikiHtmlViewWK.py:73 lib\pwiki\WikiTxtDialogs.py:47 +#: lib\pwiki\WikiHtmlViewWK.py:75 lib\pwiki\WikiTxtDialogs.py:47 msgid "Incremental search (ENTER/ESC to finish)" msgstr "" -#: lib\pwiki\WikiHtmlViewWK.py:759 +#: lib\pwiki\WikiHtmlViewWK.py:773 msgid "Open Link (External)" msgstr "" -#: lib\pwiki\WikiHtmlViewWK.py:762 +#: lib\pwiki\WikiHtmlViewWK.py:776 msgid "Follow &Link" msgstr "" -#: lib\pwiki\WikiHtmlViewWK.py:774 +#: lib\pwiki\WikiHtmlViewWK.py:788 msgid "Follow Link in New &Tab" msgstr "" -#: lib\pwiki\WikiHtmlViewWK.py:781 +#: lib\pwiki\WikiHtmlViewWK.py:795 msgid "Follow Link in New Back&ground Tab" msgstr "" -#: lib\pwiki\WikiTreeCtrl.py:525 +#: lib\pwiki\WikiTreeCtrl.py:536 msgid "Views" msgstr "" -#: lib\pwiki\WikiTreeCtrl.py:902 +#: lib\pwiki\WikiTreeCtrl.py:946 msgid "searches" msgstr "" -#: lib\pwiki\WikiTreeCtrl.py:984 +#: lib\pwiki\WikiTreeCtrl.py:1028 msgid "modified-within" msgstr "" -#: lib\pwiki\WikiTreeCtrl.py:1012 +#: lib\pwiki\WikiTreeCtrl.py:1056 msgid "1 day" msgstr "" -#: lib\pwiki\WikiTreeCtrl.py:1014 +#: lib\pwiki\WikiTreeCtrl.py:1058 msgid "%i days" msgstr "" -#: lib\pwiki\WikiTreeCtrl.py:1052 +#: lib\pwiki\WikiTreeCtrl.py:1096 msgid "parentless-nodes" msgstr "" -#: lib\pwiki\WikiTreeCtrl.py:1083 +#: lib\pwiki\WikiTreeCtrl.py:1127 msgid "undefined-nodes" msgstr "" -#: lib\pwiki\WikiTreeCtrl.py:1113 +#: lib\pwiki\WikiTreeCtrl.py:1157 msgid "Func. pages" msgstr "" -#: lib\pwiki\WikiTreeCtrl.py:1245 +#: lib\pwiki\WikiTreeCtrl.py:1291 msgid "Add icon attribute" msgstr "" -#: lib\pwiki\WikiTreeCtrl.py:1252 +#: lib\pwiki\WikiTreeCtrl.py:1298 msgid "Add color attribute" msgstr "" -#: lib\pwiki\WikiTreeCtrl.py:1856 +#: lib\pwiki\WikiTreeCtrl.py:1923 msgid "Append Wiki Word" msgstr "" -#: lib\pwiki\WikiTreeCtrl.py:1871 +#: lib\pwiki\WikiTreeCtrl.py:1938 msgid "Prepend Wiki Word" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:1270 +#: lib\pwiki\WikiTxtCtrl.py:1310 msgid "Select Template" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:1272 +#: lib\pwiki\WikiTxtCtrl.py:1312 msgid "Select Template (deletes current content!)" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:1378 +#: lib\pwiki\WikiTxtCtrl.py:1425 msgid "Use Template" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:2208 lib\pwiki\WikiTxtCtrl.py:2247 +#: lib\pwiki\WikiTxtCtrl.py:2255 lib\pwiki\WikiTxtCtrl.py:2294 msgid "File does not exist" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:2214 +#: lib\pwiki\WikiTxtCtrl.py:2261 msgid "Are you sure you want to delete the file: %s" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:2215 +#: lib\pwiki\WikiTxtCtrl.py:2262 msgid "Delete File" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:2255 +#: lib\pwiki\WikiTxtCtrl.py:2302 msgid "Enter new name" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:2256 +#: lib\pwiki\WikiTxtCtrl.py:2303 msgid "Rename File" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:2266 +#: lib\pwiki\WikiTxtCtrl.py:2313 msgid "Target is not a file" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:2270 +#: lib\pwiki\WikiTxtCtrl.py:2317 msgid "Target file exists already. Overwrite?" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:2271 +#: lib\pwiki\WikiTxtCtrl.py:2318 msgid "Overwrite File" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:2363 +#: lib\pwiki\WikiTxtCtrl.py:2419 msgid "Set in menu \"Wiki\", item \"Options...\", options page \"Security\", \n" "item \"Script security\" an appropriate value to execute a script." msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:2366 +#: lib\pwiki\WikiTxtCtrl.py:2422 msgid "Script execution disabled" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:2438 +#: lib\pwiki\WikiTxtCtrl.py:2494 msgid "\n" "Exception: %s" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3146 +#: lib\pwiki\WikiTxtCtrl.py:2986 msgid "No more fields in this 'form' page" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3315 +#: lib\pwiki\WikiTxtCtrl.py:3155 msgid "Line: %d Col: %d Pos: %d" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3420 +#: lib\pwiki\WikiTxtCtrl.py:3260 msgid "Not a valid image" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3569 +#: lib\pwiki\WikiTxtCtrl.py:3405 msgid "Couldn't copy file" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3813 +#: lib\pwiki\WikiTxtCtrl.py:3683 msgid "Ignore" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3814 +#: lib\pwiki\WikiTxtCtrl.py:3684 msgid "Add Globally" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3815 +#: lib\pwiki\WikiTxtCtrl.py:3685 msgid "Add Locally" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3825 +#: lib\pwiki\WikiTxtCtrl.py:3695 msgid "Follow Link" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3826 +#: lib\pwiki\WikiTxtCtrl.py:3696 msgid "Follow Link New Tab" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3827 +#: lib\pwiki\WikiTxtCtrl.py:3697 msgid "Follow Link New Tab Backgrd." msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3829 +#: lib\pwiki\WikiTxtCtrl.py:3699 msgid "Convert Absolute/Relative File URL" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3830 +#: lib\pwiki\WikiTxtCtrl.py:3700 msgid "Open Containing Folder" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3831 +#: lib\pwiki\WikiTxtCtrl.py:3701 msgid "Rename file" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3832 +#: lib\pwiki\WikiTxtCtrl.py:3702 msgid "Delete file" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3834 +#: lib\pwiki\WikiTxtCtrl.py:3704 msgid "Copy anchor URL to clipboard" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3836 +#: lib\pwiki\WikiTxtCtrl.py:3706 msgid "Other..." msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3837 +#: lib\pwiki\WikiTxtCtrl.py:3707 msgid "Use Template..." msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3841 +#: lib\pwiki\WikiTxtCtrl.py:3711 msgid "Show folding" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3842 +#: lib\pwiki\WikiTxtCtrl.py:3712 msgid "Show folding marks and allow folding" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3843 +#: lib\pwiki\WikiTxtCtrl.py:3713 msgid "&Toggle current folding" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3844 +#: lib\pwiki\WikiTxtCtrl.py:3714 msgid "Toggle folding of the current line" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3845 +#: lib\pwiki\WikiTxtCtrl.py:3715 msgid "&Unfold All" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3846 +#: lib\pwiki\WikiTxtCtrl.py:3716 msgid "Unfold everything in current editor" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3847 +#: lib\pwiki\WikiTxtCtrl.py:3717 msgid "&Fold All" msgstr "" -#: lib\pwiki\WikiTxtCtrl.py:3848 +#: lib\pwiki\WikiTxtCtrl.py:3718 msgid "Fold everything in current editor" msgstr "" @@ -4715,78 +4800,78 @@ msgstr "" msgid "Copy of file '%s' couldn't be created" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:64 +#: lib\pwiki\wikidata\WikiDataManager.py:65 msgid "Database exists already and is currently in use" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:69 -#: lib\pwiki\wikidata\WikiDataManager.py:2052 +#: lib\pwiki\wikidata\WikiDataManager.py:70 +#: lib\pwiki\wikidata\WikiDataManager.py:2093 msgid "Data handler %s not available" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:90 +#: lib\pwiki\wikidata\WikiDataManager.py:91 msgid "Database is already in use with database handler \"%s\". Can't open with different handler." msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:96 +#: lib\pwiki\wikidata\WikiDataManager.py:97 msgid "Database is already in use with wiki language handler \"%s\". Can't open with different handler." msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:300 +#: lib\pwiki\wikidata\WikiDataManager.py:301 msgid "Wiki is probably already in use by other instance" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:323 -#: lib\pwiki\wikidata\WikiDataManager.py:333 +#: lib\pwiki\wikidata\WikiDataManager.py:324 +#: lib\pwiki\wikidata\WikiDataManager.py:334 msgid "Wiki configuration file is corrupted" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:366 +#: lib\pwiki\wikidata\WikiDataManager.py:367 msgid "No data handler information found, probably \"Original Gadfly\" is right." msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:372 +#: lib\pwiki\wikidata\WikiDataManager.py:373 msgid "Required data handler \"%s\" unknown to WikidPad" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:378 +#: lib\pwiki\wikidata\WikiDataManager.py:379 msgid "Error on initializing data handler \"%s\"" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:389 +#: lib\pwiki\wikidata\WikiDataManager.py:390 msgid "Required wiki language handler \"%s\" not available" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:929 +#: lib\pwiki\wikidata\WikiDataManager.py:953 msgid "Word '%s' not in wiki" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:1234 -#: lib\pwiki\wikidata\WikiDataManager.py:1305 +#: lib\pwiki\wikidata\WikiDataManager.py:1258 +#: lib\pwiki\wikidata\WikiDataManager.py:1331 msgid "Update basic link info" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:1250 +#: lib\pwiki\wikidata\WikiDataManager.py:1274 msgid "Starting update thread" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:1325 +#: lib\pwiki\wikidata\WikiDataManager.py:1351 msgid "Update attributes of %s" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:1347 +#: lib\pwiki\wikidata\WikiDataManager.py:1373 msgid "Update syntax of %s" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:1368 +#: lib\pwiki\wikidata\WikiDataManager.py:1394 msgid "Update index of %s" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:1388 +#: lib\pwiki\wikidata\WikiDataManager.py:1414 msgid "Final cleanup" msgstr "" -#: lib\pwiki\wikidata\WikiDataManager.py:1481 +#: lib\pwiki\wikidata\WikiDataManager.py:1508 msgid "Cannot rename '%s' to '%s', '%s' already exists" msgstr "" @@ -4829,21 +4914,21 @@ msgstr "" msgid "Wiki page not found: %s" msgstr "" -#: lib\pwiki\wikidata\compact_sqlite\WikiData.py:484 -#: lib\pwiki\wikidata\original_gadfly\WikiData.py:464 -#: lib\pwiki\wikidata\original_sqlite\WikiData.py:521 +#: lib\pwiki\wikidata\compact_sqlite\WikiData.py:528 +#: lib\pwiki\wikidata\original_gadfly\WikiData.py:512 +#: lib\pwiki\wikidata\original_sqlite\WikiData.py:565 msgid "You cannot delete the root wiki node" msgstr "" -#: lib\pwiki\wikidata\original_gadfly\WikiData.py:1065 -#: lib\pwiki\wikidata\original_sqlite\WikiData.py:1168 +#: lib\pwiki\wikidata\original_gadfly\WikiData.py:1113 +#: lib\pwiki\wikidata\original_sqlite\WikiData.py:1212 msgid "Wiki page not found (no path information) for word: %s" msgstr "" -#: lib\pwiki\wikidata\original_gadfly\WikiData.py:1083 -#: lib\pwiki\wikidata\original_gadfly\WikiData.py:1097 -#: lib\pwiki\wikidata\original_sqlite\WikiData.py:1186 -#: lib\pwiki\wikidata\original_sqlite\WikiData.py:1200 +#: lib\pwiki\wikidata\original_gadfly\WikiData.py:1131 +#: lib\pwiki\wikidata\original_gadfly\WikiData.py:1145 +#: lib\pwiki\wikidata\original_sqlite\WikiData.py:1230 +#: lib\pwiki\wikidata\original_sqlite\WikiData.py:1244 msgid "Wiki page not found (bad path information) for word: %s" msgstr "" diff --git a/WikidPad.xrc b/WikidPad.xrc index 0e30752f..53c60a98 100644 --- a/WikidPad.xrc +++ b/WikidPad.xrc @@ -2854,6 +2854,73 @@ wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + + + + wxTOP|wxLEFT|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + + + 8 + + bold + normal + 0 + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + 2 + 1 + + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + wxALL|wxEXPAND + 5 + + + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + + Database + Separate files + + 0 + + + wxALL|wxEXPAND + 5 + 0,1 + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + + + + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + @@ -5523,6 +5590,204 @@ + + Trashcan + 1 + + + wxHORIZONTAL + + + wxVERTICAL + + + + wxALL|wxEXPAND + 5 + 300,300 + + + + + + #FF0000 + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + + + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + 3 + + 80,0 + + + + + + + wxALL|wxEXPAND + + + + wxVERTICAL + + + + 1 + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + + + + Name clash + 1 + + + wxVERTICAL + + + 2 + + + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + + 1 + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + 0,0 + + + + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + 0,0 + + + + + 0 + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + 1 + + + + 0 + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + wxALL|wxEXPAND + + + + + + #FF0000 + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + + wxHORIZONTAL + + + + 1 + + + wxALL|wxALIGN_CENTRE_VERTICAL + 5 + + + + + + wxALL|wxALIGN_CENTRE_VERTICAL + 5 + + + + wxALL|wxEXPAND|wxALIGN_CENTRE_VERTICAL + 5 + + + Print 1 diff --git a/WikidPadHelp/WikidPadHelp.wiki b/WikidPadHelp/WikidPadHelp.wiki index ea64f6a0..af127534 100644 --- a/WikidPadHelp/WikidPadHelp.wiki +++ b/WikidPadHelp/WikidPadHelp.wiki @@ -3,16 +3,20 @@ data_dir = data [main] footnotes_as_wikiwords = False +indexsearch_enabled = False last_wiki_word = WikidPadHelp hotkey_showhide_bywiki = log_window_autoshow = Gray tree_expandednodes_descriptorpathes_views = +trashcan_storagelocation = 0 filestorage_identity_filenamemustmatch = False +trashcan_maxnoofbags = 200 tabs_maxcharacters = 0 +trashcan_askondelete = True wiki_icon = headingsasaliases_depth = 0 wiki_name = WikidPadHelp -wikipagetitle_creationmode = 1 +tree_expandednodes_descriptorpathes_main = wikipagetitleprefix = ++ wikipagetitle_fromlinktitle = True wiki_lastactivetabno = 0 @@ -21,7 +25,7 @@ wikipagefiles_gracefuloutsideaddandremove = True versioning_storagelocation = 0 wiki_lasttabssubctrls = textedit template_pagenamesre = ^template/ -tree_expandednodes_descriptorpathes_main = +wikipagetitle_creationmode = 1 tree_last_root_wiki_word = WikidPadHelp tree_force_scratchpad_visibility = True further_wiki_words = diff --git a/WikidPadHelp/data/ChangeLog.wiki b/WikidPadHelp/data/ChangeLog.wiki index 50abb7b1..703220a4 100644 --- a/WikidPadHelp/data/ChangeLog.wiki +++ b/WikidPadHelp/data/ChangeLog.wiki @@ -1,5 +1,43 @@ ++ Change Log +Jun. 25, 2011 (2.2beta05) + + * Trashcan support. See [Menu Wiki#++ Open trashcan], + [OptionsDialog#+++++ Trashcan] + * New insertion "search" to create arbitrary searches. + See [Insertions#* *search* ] + * Added "page:" prefix for boolean regex search to search for + page name. See [SearchingTheWiki/BooleanRegex#++++ page:] + * Support for "wikirel:" protocol to support relative links to + other wikis. See [UrlLinking#++ Relative URLs] + * Support to set readonly per wiki page. + See [Menu Wiki Page#++ Page read only] + * Black lists (global and wiki-dependent) for non-camelcase + (=bracketed) wikiwords added. + See [WordLinking!exclude_from_linking] + + * Basic Vi emulation for editor (thanks to Ross) + * Grammar change to forbid bold and italics to span over a heading + * Activation (double click or press return) on collapsed todo or + attribute tree item with one child which represents a wiki page + opens this wiki page + + * Internal: New function wxHelper.getHtmlFromClipboard() + + * Bug fixed: WikidPad can't be closed if volume access lost + during session + * Bug fixed: Crash when rebuilding sqlite wiki on 64 bit Linux + * Bug fixed: Search index from 64bit Python not compatible with + 32bit Python (due to hash function). Changed index format + number from 2 to 3 + * Bug fixed: "Move to file storage" failed + + * Internal: Bug fixed: Error in error handling for plugins + * Internal: Bug fixed: Some functions in plugins were not + registered for calling + + + May 28, 2011 (2.2beta04) * Changes to editor (thanks to Ross): diff --git a/WikidPadHelp/data/Insertions.wiki b/WikidPadHelp/data/Insertions.wiki index 53529f3f..6fd1d05b 100644 --- a/WikidPadHelp/data/Insertions.wiki +++ b/WikidPadHelp/data/Insertions.wiki @@ -55,6 +55,21 @@ The following keys and values are built into WikidPad (further are possible by p on export) * *savedsearch* lists all wiki words found by the saved search with the name given as value. + * *search* lists all wiki words found by the search string given + as value. It supports the following appendices: + * *type boolean*: Boolean regex search (default) + * *type regex*: Simple regex search + * *type asis*: Search "As is" + * *type index*: Index based search (ignored if index not + available) + * *casesensitive*: Search case sensitive (default is + insensitive) + * *wholewordsonly*: Search whole words only + * *removeself*: Page on which the insertion is placed is + omitted from search result (if you e.g. place a search + insertion for "foo" on a page this page itself would be + found just because it contains the search insertion + with "foo" in it) * *toc* shows a little table of contents with the headings of the current page. In HTML preview/export you can click on the lines to go to the actual headings. The value of this diff --git a/WikidPadHelp/data/Menu Wiki Page.wiki b/WikidPadHelp/data/Menu Wiki Page.wiki index 0162c24e..ec36f8df 100644 --- a/WikidPadHelp/data/Menu Wiki Page.wiki +++ b/WikidPadHelp/data/Menu Wiki Page.wiki @@ -9,7 +9,7 @@ Rename current page. After chosing the new you are also asked if you want WikidP Be aware that this process is only a simple text search and replace which may produce wrong results under some circumstances. ++ Delete -Delete current page. +Move current page to trashcan ++ Set as Root Set current page as root of the tree. @@ -36,4 +36,7 @@ Copy "wiki:" URL of current wiki page to clipboard. If you right click on an "anchor:" definition in editor and choose "Copy Anchor URL to clipboard" the URL contains also a link to the particular anchor. ++ Add version -Add a new version \ No newline at end of file +Add a new version + +++ Page read only +Set/unset current page read-only. Be aware that versions of WikidPad prior to 2.2beta05 ignore this setting and allow to modify the page nevertheless \ No newline at end of file diff --git a/WikidPadHelp/data/Menu Wiki.wiki b/WikidPadHelp/data/Menu Wiki.wiki index 306e68d4..8bcd7dfb 100644 --- a/WikidPadHelp/data/Menu Wiki.wiki +++ b/WikidPadHelp/data/Menu Wiki.wiki @@ -27,6 +27,9 @@ You can also use "Other Export" to show a dialog to set more precisely what to e ++ Print Start print dialog for a plain text print of some or all wiki pages. See [Printing]. +++ Open trashcan +Show list of items stored to the trashcan of the wiki and allow to restore or finally delete them. + ++ Properties Shows internal name of database backend (may be needed for support requests) and the number of wiki pages in the wiki. diff --git a/WikidPadHelp/data/OptionsDialog.wiki b/WikidPadHelp/data/OptionsDialog.wiki index ccb675f4..17a35541 100644 --- a/WikidPadHelp/data/OptionsDialog.wiki +++ b/WikidPadHelp/data/OptionsDialog.wiki @@ -501,6 +501,21 @@ Set here the name of icon to use in the system tray instead of the default icon. Wiki-bound hotkey to show/hide wiki. See OptionsDialog#*App-bound# hotkey* ++++++ Trashcan +Controls behavior of the trashcan which temporarily stores deleted wiki pages to undo their deletion + +*Max. number of entries* +Number of pages to store before the oldest (earliest trashed) ones are irreversibly deleted. To switch of trashcan set this to 0. + +*Where to store* +Set where trashcan information should be stored, either in the main "Database" files or as "Separate files" which creates one file for each trashcan element to save and additionally one file for the overview list of trashed items. "Database" is the recommended option. + +The database backend "Compact Sqlite" only supports storing in database so this option is grayed out for such wikis. + +*Ask before deletion of wiki word* +If you switch this off wiki words will be moved to trashcan or deleted without previously asking if you are sure. The maximum number of entries in trashcan should be reasonably high (recommended: 200) if you switch this off. + + ++++ Headings *Heading prefix* diff --git a/WikidPadHelp/data/SETTINGS.grl b/WikidPadHelp/data/SETTINGS.grl index ad75d499da8a776c3236bfee53968bddc411bf51..5997b6982205e692f00f87c013fc6e80358d88d5 100644 GIT binary patch delta 45 zcmcb@e1%y`gNcEGp;!P&gg#!La&W8PvgC$v5y4$QWH(CHFfy7>_F06RAh AG5`Po delta 45 zcmcb@e1%y`gNcEGp;!P&q$;_+DLJ|H{3-`y;rdf=t2av3Ffy7<_F06cpR Ap#T5? diff --git a/WikidPadHelp/data/SearchingTheWiki%2FBooleanRegex.wiki b/WikidPadHelp/data/SearchingTheWiki%2FBooleanRegex.wiki index 82e8d179..f7970c35 100644 --- a/WikidPadHelp/data/SearchingTheWiki%2FBooleanRegex.wiki +++ b/WikidPadHelp/data/SearchingTheWiki%2FBooleanRegex.wiki @@ -73,3 +73,16 @@ attr:Mary and attr:lamb Find pages having an attribute with key containing "Ma << todo:grass and garden Find pages having a todo item containing "grass" in the key and the text "garden" somewhere. >> + + +++++ page: +Search for a pages whose names match the regular expression following after the prefix. The regex is matched at the beginning of the page name and is always case-sensitive (may change later). + + +Examples + +<< +page:Wiki Find all pages with names beginning with "Wiki" + +page:.*Wiki Find all pages with names containing "Wiki" +>> diff --git a/WikidPadHelp/data/UrlLinking.wiki b/WikidPadHelp/data/UrlLinking.wiki index 49d3bafc..4e70540e 100644 --- a/WikidPadHelp/data/UrlLinking.wiki +++ b/WikidPadHelp/data/UrlLinking.wiki @@ -16,17 +16,17 @@ To create an absolute URL to a file you can also use "Add file URL" in "Editor" To show a different title in the HTML preview/export as a link, use e.g. -[ftp://ftp.whereever.com/ |Go somewhere] +[ftp://ftp.whereever.com/|Go somewhere] Switch to preview to see the "Go somewhere" only. You can also use an image URL (see below) as title to show a clickable image: -[https://somewheresecure.com |rel://files/testimg.gif>r40% ] +[https://somewheresecure.com|rel://files/testimg.gif>r40%] ++ Relative URLs You can create URLs which are relative to the wiki directory. These start with rel:// followed by the relative position. For HTML export the rel:// is removed to create a relative HTML link (without protocol) which is adapted to new position. For HTML preview they are converted to "file:" URLs. -You can drag a file from Windows Explorer while holding shift key to create a relative URL. This does not work with files ending with ".wiki", these are always absolute and have the "wiki:" protocol. +You can drag a file from Windows Explorer while holding shift key to create a relative URL (in default configuration). For files ending with ".wiki", the "wikirel:" protocol is used instead of "rel:". Example: @@ -34,7 +34,7 @@ rel://../above/andBelow.txt ++ Converting between relative and absolute URL -If you right-click on a "file:" or "rel:" URL and choose "Convert Absolute/Relative File URL" you can change an URL from one type to the other. Alternatively you can place the text cursor in such an URL and select from regular menu "Format"->"Convert"->"Absolute/Relative File URL". +If you right-click on a "file:"/"wiki:" or "rel:"/"wikirel:" URL and choose "Convert Absolute/Relative File URL" you can change an URL from one type to the other. Alternatively you can place the text cursor in such an URL and select from regular menu "Format"->"Convert"->"Absolute/Relative File URL". Converting a relative URL to an absolute one is always possible, but there may be absolute URLs (especially on Windows if pointing to a different drive than the one the wiki is on) that can't be converted to relative URLs. In this case nothing happens. @@ -58,19 +58,19 @@ For controlling of individual URLs, see "URL Appendix" below. ++ Wiki: URLs To link from one wiki to another you can create links beginning with "wiki:", e.g. -wiki:///C|/Documents%20and%20Settings/foowiki/foowiki.wiki +wiki:///C:/Documents%20and%20Settings/foowiki/foowiki.wiki You can add parameter "page" to specify the wiki page to open (replace spaces by "%20"): -wiki:///C|/Documents%20and%20Settings/foowiki/foowiki.wiki?page=ThePage%20with%20spaces +wiki:///C:/Documents%20and%20Settings/foowiki/foowiki.wiki?page=ThePage%20with%20spaces You can also specify an anchor on the page to jump to: -wiki:///C|/Documents%20and%20Settings/foowiki/foowiki.wiki?page=ThePage%20with%20spaces&anchor=here +wiki:///C:/Documents%20and%20Settings/foowiki/foowiki.wiki?page=ThePage%20with%20spaces&anchor=here -To create these URLs you can also use in menu "Wiki Words" the function "Copy URL to clipboard" and then paste the URL where you like. +To create these URLs you can also use in menu "Wiki Page" the function "Copy URL to clipboard" and then paste the URL where you like. The above menu entry is also available in context menus for wiki word tree items, tabs and for "anchor: ..." lines in the editor. In the last case the URL contains also the anchor you clicked on. diff --git a/WikidPadHelp/data/WIKIRELATIONS.grl b/WikidPadHelp/data/WIKIRELATIONS.grl index 430ebfb0d64b6a5e9e3c369a194e91a38d56ca28..f26bd0eef47d5f556d03fc30b1f0b955a4cb2c3e 100644 GIT binary patch delta 2612 zcmZ{lOKcle6o#G1eLC@s9ozUZX`L!*OQc%k$Jlr5#wblhDWoW(7L@>zy79OfJMn}# zfi{WlRO;4`_#_n%2?1h(5TJks5(|V_b%n}?E?9I05~vb_t`K6uore?8%^X13@>imb! z^}(lUduKKM!Z<_^ zOCXkI!|+qx4KsE6iMFl!;FD~pY`DN_A)A{KOT~;N<#qaegy(slTHQUgY#DSC*?A7|8cF@E4NCk z*_++Jv>zs(r;Q<4%0ICG4?-~(9G3#avGP%@l#z1MrBz|6S4Z=JQ0g$8oS;pi%71-r z!a+8?OEC1sU{Z7)$I~L$d7ys;sPv#+Yg@1Bne_e)X_&+P=b^v&v`tn?V3lYD!8Oqf zKQ4GE%n;%eSXuRZyl~FMLgE<{%ZX#MUN;tYL{_)HFQ%0>&!P~IV$dr1@zL-b*a=uG z&;}OLt>=&jCXjnj=>pFoE`&H9=FoUANpP5W;4?%K=tE>P(_v*WvrEF_RJJU12UFEo z)fl}`9JqNpub1+hk8rBT{&sL~a-A3N7SmFDs%ZTCMDrI79%}!uP9T&9I ztNm8QLbVn?@Kn|eJgC_z^{DmwF<{#Y5c%aTio}(B`bFU_`NF&^t>lckhmt*b}<&o5C!nx%fue2N_fAtnIJa78Qd#wPzy;YWAq1p7oq3wWn zbL(($*9sT6?IhF9?Waiiab}2wf8XxD3_5*p+ePxmX_+g_!s6o8f-tW%MICi5HVl(3 zA6yWJ4tAlUkIF{mKZ3hExhzgg^JQ(ebs`xs>{}ITfk$7LitmBXm@>Fa(>zHT8yrYC gfnepMx;>_y?gh}|4d`A2mk;8 delta 2458 zcmZ{mOK4+d6o6}AX?xSWns>uY(y=%c3HOov$U^##>Umth+u6A~GbRKHIh`$ZXmRPgFXX&^yEM{*C?~j`jywV5S|A+;JNWE#K$( zc4h#cPMLbKA}7M^w(~NDs`F~(y{TwyDH>ZzEUT>QI^a*c;q~h}*^K9)EV&_`HlNo5 zJ8RHS(sm^kMA!W`LB^ohD-Hy+*BtO;#x$se{OL?E zJO&%#e(>gc;Cj|hF^tGDBI{=b9zGl&lKE!VMdV*)he+Jhhe|ZF9*reYftD={M>m~H zGz{iwqvAb?eo)T2VL9iOow2OoXM+M_0nNJ`{>oXKjAK~Y?n++Gj>F@X?3HT_%LF7L z;1?wthq&5>Cwl-r=GWI=i^f)Q?{w&O?oMQ8aV1huQ*CqlN0>IkO2Go9yDln#3d1qT zAIV-3eb745*3dHZ@{kj&1)Ta2)6P zqNzq~Ib<7G2PNU~MHS@~5W683iw-jPPvJE+-)HB9FGRpF@6p zsZj#6K`b>$;I83NWN``Kj=6YT&Pnad20y3jhir0@M=@-d@X*Xf-c3%e#lx$3Cvvn> zWwK(0M`g-@=K^zy<O81of7Nfn1~ zZ3B5~nxO^QI&?v);#3>f@i$t3GQO-wMZg^m#lvsE6^SKD9tALvJbJ6$unLX=P5h6c zK5I>kmOHNHpI~XWPp)Nyd#es2U#Jcd`Tc5Z43vJptJe30HmfP?&@wpTx0*vkCo4|S6)bJk3JZrW>O5zj;UQIZ!b|r|=Afz- vN{Mu#M7%&^F)F86>5Erep#LxnwR-%(MG_mQmB`=2qs=jRy74b`-S_mQ}dQjm9I&hEhDP8+2zYfdhe5Zqj;wS{wXuSwkG cW4Z#9|7vDWPBsyl>}cXMSSUinLNRqXS0@rE&Jq|?pd4dJmeTBPx1T!QjqShH(3CvV3D2KWCKl>$wGEI aU_QHt`Q#o=lgWqdVl_7jZzTw zr@T0vk3^ecckLBYC$q5%S87D6+kTN1r>NhXBKXkz&CMKSy2{+L- z-m5#kxf@YQ5;08Y?yO@AB25dvFwFO+tovmB?0Tzcc1*kLaE3+XjL5Kq%{9R&VKlN~ z`bVqcd*#G?{`GomSpTx%(_3HN3=A}+ih%qdY?mTd*VEe<{h)2llaH??=Gv00FU5W4 I$w}GTsa7k@55xi4$?rs9db6iR3+5H)i8uv;AUS|dyZ#u$#xFn{!7 z(7uU(oP)K*0Q8+m*&8!ksT`3+V=awDL@VckYNDuNMk7K6)GE^VYQfrg=Y1N2V%C1F z6niuO4oIg%iYVir^>1$sj&SFY5srk??kb6#gfmPTqL_s<_`(5Wb++Az%AFU=#>UygL&QPWaC`7UT*>CAtD^78@Mp73C})or_; zw&QuXfoth7DuFnQ5E;g~GQw@7u-hv6$#SsXINJOaiC5-4d+FH+uaG8GVeSf-2!~`1 oW5y^+kyfI59O9QVaBX?a@3|hVZ\n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: pygettext.py 1.5mod\n" -"X-Poedit-Language: Hungarian\n" -"X-Poedit-Country: HUNGARY\n" - -#: WikidPad.xrc:0 -msgid " Context characters" -msgstr " Szövegkörnyezet karakterek" - -#: WikidPad.xrc:0 -msgid " Defaults" -msgstr " Alapbeállítások" - -#: WikidPad.xrc:0 -msgid " &Delete " -msgstr "Törlés" - -#: WikidPad.xrc:0 -msgid " &Find " -msgstr "Keres" - -#: WikidPad.xrc:0 -msgid " &Replace " -msgstr "Csere" - -#: WikidPad.xrc:0 -msgid " &Test " -msgstr "Teszt" - -#: WikidPad.xrc:0 -msgid " Find &Next " -msgstr "Következő találat" - -#: WikidPad.xrc:0 -msgid " Replace &All " -msgstr "Cseréli mindet" - -#: WikidPad.xrc:0 -msgid "&Case sensitive" -msgstr "Nagybetűérzékeny" - -#: WikidPad.xrc:0 -msgid "&Create" -msgstr "Létrehoz" - -#: WikidPad.xrc:0 -msgid "&Delete Export(s)" -msgstr "Export törlése" - -#: WikidPad.xrc:0 -msgid "&Delete Search(es)" -msgstr "Keresés törlése" - -#: WikidPad.xrc:0 -msgid "&Ignore" -msgstr "Elvet" - -#: WikidPad.xrc:0 -msgid "&Load Export" -msgstr "Export betöltése" - -#: WikidPad.xrc:0 -msgid "&Load Search" -msgstr "Keresési eredmény betöltése" - -#: WikidPad.xrc:0 -msgid "&New Tab" -msgstr "Új fül" - -#: WikidPad.xrc:0 -msgid "&Options" -msgstr "Opciók" - -#: WikidPad.xrc:0 -msgid "&Page names" -msgstr "Oldalaknevek" - -#: WikidPad.xrc:0 -msgid "&Replace" -msgstr "Cserél" - -#: WikidPad.xrc:0 -msgid "&Replace by:" -msgstr "Cserél evvel:" - -#: WikidPad.xrc:0 -msgid "&Save Export" -msgstr "Export mentése" - -#: WikidPad.xrc:0 -msgid "&Save Search" -msgstr "Keresési eredmény mentése" - -#: WikidPad.xrc:0 -msgid "&Search:" -msgstr "Keresés:" - -#: WikidPad.xrc:0 -msgid "&Whole word" -msgstr "Teljes szó" - -#: WikidPad.xrc:0 -msgid "&beginning" -msgstr "előről" - -#: WikidPad.xrc:0 -msgid "(0: worst, 100: best)" -msgstr "(0: legrosszabb, 100: legjobb)" - -#: WikidPad.xrc:0 -msgid "(Use selected defaults if you are unsure)" -msgstr "(Az alapbeállításokat használd, ha nem vagy biztos)" - -#: WikidPad.xrc:0 -msgid "* Needs WikidPad restart" -msgstr "* WikidPad újraindítását igényeli" - -#: WikidPad.xrc:0 -msgid "0" -msgstr "0" - -#: WikidPad.xrc:0 -msgid "" -"0: Never complete version; 1: Always complete v.;\n" -"10: Every tenth v. is complete" -msgstr "" -"0: Sosem teljes verziót; 1: Mindig teljes v;\n" -"10: Minden tizedik verz. teljes" - -#: WikidPad.xrc:0 -msgid "1 (since 1.9beta6)" -msgstr "1 (1.9beta6 óta)" - -#: WikidPad.xrc:0 -msgid "Above" -msgstr "Fölött" - -#: WikidPad.xrc:0 -msgid "Absolute URL" -msgstr "Abszolut URL-cím" - -#: WikidPad.xrc:0 -msgid "Active link color:" -msgstr "Aktív link színe" - -#: WikidPad.xrc:0 -msgid "Add" -msgstr "Hozzáad" - -#: WikidPad.xrc:0 -msgid "Add &Globally" -msgstr "Hozzáad globálisan" - -#: WikidPad.xrc:0 -msgid "Add &Locally" -msgstr "Hozzáad helyben" - -#: WikidPad.xrc:0 -msgid "Add Wiki to Favorites" -msgstr "Wiki hozzáadása Kedvencekhez" - -#: WikidPad.xrc:0 -msgid "Add spaces" -msgstr "Szóközök hozzáadása" - -#: WikidPad.xrc:0 -msgid "After:" -msgstr "Utána:" - -#: WikidPad.xrc:0 -msgid "All Pages" -msgstr "Osszes lap" - -#: WikidPad.xrc:0 -msgid "Allow everything" -msgstr "Mindent engedélyez" - -#: WikidPad.xrc:0 -msgid "Alphabetically" -msgstr "Betűrendben" - -#: WikidPad.xrc:0 -msgid "Also rename subpages" -msgstr "Alárendelt lapokat is átnevezi" - -#: WikidPad.xrc:0 -msgid "Always" -msgstr "Mindig" - -#: WikidPad.xrc:0 -msgid "Always show import table" -msgstr "Mindig mutassa az importtáblát" - -#: WikidPad.xrc:0 -msgid "App-bound hotkey:" -msgstr "Alkamazást elrejtő gyorsillentyű" - -#: WikidPad.xrc:0 -msgid "Append after clipboard snippet:" -msgstr "A vágólaprészlet végéhez fűz:" - -#: WikidPad.xrc:0 -msgid "Append after links:" -msgstr "A linkek végéhez fűz:" - -#: WikidPad.xrc:0 -msgid "Append closing bracket on auto-complete" -msgstr "Csukó zárójel hozzáfűzése automatikus kiegészítéskor" - -#: WikidPad.xrc:0 -msgid "Append wiki word" -msgstr "Wikiszó hozzáfűzése" - -#: WikidPad.xrc:0 -msgid "As &Tab" -msgstr "Fülként" - -#: WikidPad.xrc:0 -msgid "As &is" -msgstr "Ahogy van" - -#: WikidPad.xrc:0 -msgid "As Res&ultlist" -msgstr "Eredménylistaként" - -#: WikidPad.xrc:0 -msgid "As Tree" -msgstr "Faként" - -#: WikidPad.xrc:0 -msgid "As is" -msgstr "Ahogy van" - -#: WikidPad.xrc:0 -msgid "As list" -msgstr "Listaként" - -#: WikidPad.xrc:0 -msgid "As tree" -msgstr "Faként" - -#: WikidPad.xrc:0 -msgid "Ask" -msgstr "Kérdés" - -#: WikidPad.xrc:0 -msgid "Ask settings on each paste" -msgstr "Beállításokat bekéri beillesztéskor" - -#: WikidPad.xrc:0 -msgid "At position:" -msgstr "Pozíciónál:" - -#: WikidPad.xrc:0 -msgid "Attribute/script color:" -msgstr "Attribútum/szkript színe" - -#: WikidPad.xrc:0 -msgid "Attrs. to" -msgstr "Attribútumok amiket " - -#: WikidPad.xrc:0 -msgid "Auto-hide" -msgstr "Automatikus elrejtés" - -#: WikidPad.xrc:0 -msgid "Auto-hide log window" -msgstr "Logablak automatikus elrejtése" - -#: WikidPad.xrc:0 -msgid "Auto-hide structure window" -msgstr "Struktúraablak automatikus elrejtése" - -#: WikidPad.xrc:0 -msgid "Auto-show log window" -msgstr "Logablak automatikus megjelenítése" - -#: WikidPad.xrc:0 -msgid "Auto-unbullets" -msgstr "Felsorolás automatikus kiszedése" - -#: WikidPad.xrc:0 -msgid "Autosave active" -msgstr "Automatikus mentés aktív" - -#: WikidPad.xrc:0 -msgid "Avoid doubled snippets" -msgstr "Töredékismétlés elkerülése" - -#: WikidPad.xrc:0 -msgid "Background color:" -msgstr "Háttérszín:" - -#: WikidPad.xrc:0 -msgid "Background image:" -msgstr "Háttérkép:" - -#: WikidPad.xrc:0 -msgid "Basic wiki settings" -msgstr "Alap wikibeállítások" - -#: WikidPad.xrc:0 -msgid "Before:" -msgstr "Előtte:" - -#: WikidPad.xrc:0 -msgid "Below" -msgstr "Alatta" - -#: WikidPad.xrc:0 -msgid "Between links:" -msgstr "Linkek közé:" - -#: WikidPad.xrc:0 -msgid "Bookmark" -msgstr "Könyvjelző" - -#: WikidPad.xrc:0 -msgid "Bool&ean regex" -msgstr "Boolean regex." - -#: WikidPad.xrc:0 -msgid "Bottom" -msgstr "Lent" - -#: WikidPad.xrc:0 -msgid "C" -msgstr "C" - -#: WikidPad.xrc:0 -msgid "Cancel" -msgstr "Elvet" - -#: WikidPad.xrc:0 -msgid "Change these only if you know what you are doing!" -msgstr "Csak akkor változtassa meg, ha tudja mit csinál!" - -#: WikidPad.xrc:0 -msgid "Choose Wiki Word" -msgstr "Wikiszó kiválasztása" - -#: WikidPad.xrc:0 -msgid "Choose date format" -msgstr "Dátumformátum kiválasztása" - -#: WikidPad.xrc:0 -msgid "Choose plain text font" -msgstr "Sima szöveg betűtípusának kiválasztása" - -#: WikidPad.xrc:0 -msgid "Chronological View" -msgstr "Időrendi nézet" - -#: WikidPad.xrc:0 -msgid "Clear" -msgstr "Törlés" - -#: WikidPad.xrc:0 -msgid "Clear List" -msgstr "Lista törlése" - -#: WikidPad.xrc:0 -msgid "Clone tab" -msgstr "Fül klónozása" - -#: WikidPad.xrc:0 -msgid "Close tab" -msgstr "Fül bezárása" - -#: WikidPad.xrc:0 -msgid "Collapse whole tree" -msgstr "A teljes fastruktúra összecsukása" - -#: WikidPad.xrc:0 -msgid "Compatible filenames" -msgstr "Kompatibilis fájlnevek" - -#: WikidPad.xrc:0 -msgid "Config. dir." -msgstr "Konfig. könyvt." - -#: WikidPad.xrc:0 -msgid "Copy URL to clipboard" -msgstr "URL másolása vágólapra" - -#: WikidPad.xrc:0 -msgid "Copy into file storage" -msgstr "Másolja a fájltárolóba" - -#: WikidPad.xrc:0 -msgid "Copy to clipboard:" -msgstr "Másolás vágólapra" - -#: WikidPad.xrc:0 -msgid "Count Occurrences up to max.:" -msgstr "Előfordulások számolása max.:" - -#: WikidPad.xrc:0 -msgid "Create wiki lock file" -msgstr "Wiki zárolásfájl létrehozása" - -#: WikidPad.xrc:0 -msgid "Current subtree" -msgstr "Jelenlegi alárendelt fa" - -#: WikidPad.xrc:0 -msgid "Current wiki page" -msgstr "Jelenlegi wiki lap" - -#: WikidPad.xrc:0 -msgid "Custom..." -msgstr "Igény szerinti..." - -#: WikidPad.xrc:0 -msgid "DOCTYPE:" -msgstr "Dok. típus:" - -#: WikidPad.xrc:0 -msgid "Database" -msgstr "Adatbázisban" - -#: WikidPad.xrc:0 -msgid "Database type:" -msgstr "Adatbázis típusa:" - -#: WikidPad.xrc:0 -msgid "Date format:" -msgstr "Dátumformátum" - -#: WikidPad.xrc:0 -msgid "Default export dir.:" -msgstr "Export alapértelmezett könyvtára:" - -#: WikidPad.xrc:0 -msgid "Default open/new dir.:" -msgstr "Megnyitás/új alapértelmezett könyvtára" - -#: WikidPad.xrc:0 -msgid "Defaults" -msgstr "Alapértelmezett értékek" - -#: WikidPad.xrc:0 -msgid "Del" -msgstr "Töröl" - -#: WikidPad.xrc:0 -msgid "Delay after key pressed:" -msgstr "Billentyűnyomás után várjon:" - -#: WikidPad.xrc:0 -msgid "Delay after page dirty:" -msgstr "Lap változtatása után várjon:" - -#: WikidPad.xrc:0 -msgid "Delay before auto-close:" -msgstr "Automatikus bezárás előtt várjon" - -#: WikidPad.xrc:0 -msgid "Details" -msgstr "Részletek" - -#: WikidPad.xrc:0 -msgid "Done" -msgstr "Kész" - -#: WikidPad.xrc:0 -msgid "Down" -msgstr "Le" - -#: WikidPad.xrc:0 -msgid "Dropping files with CTRL:" -msgstr "Fájlok ejtése CTRL-lel:" - -#: WikidPad.xrc:0 -msgid "Dropping files with SHIFT:" -msgstr "Fájlok ejtése SHIFT-tel:" - -#: WikidPad.xrc:0 -msgid "Dropping files:" -msgstr "Fájlok ejtése:" - -#: WikidPad.xrc:0 -msgid "Editor timing" -msgstr "Szerkesztő időzítése" - -#: WikidPad.xrc:0 -msgid "Encoding:" -msgstr "Kódolás:" - -#: WikidPad.xrc:0 -msgid "Export" -msgstr "Export" - -#: WikidPad.xrc:0 -msgid "Export to:" -msgstr "Exportálás ebbe:" - -#: WikidPad.xrc:0 -msgid "Fast search defaults" -msgstr "Gyorskeresés alapbeállításai" - -#: WikidPad.xrc:0 -msgid "File dropping" -msgstr "Fájl ejtése" - -#: WikidPad.xrc:0 -msgid "File storage identity check" -msgstr "Fájltároló azonosításának ellenőrzése" - -#: WikidPad.xrc:0 -msgid "File type:" -msgstr "Fájltípus:" - -#: WikidPad.xrc:0 -msgid "Filename must match" -msgstr "A fájlnévnek egyeznie kell!" - -#: WikidPad.xrc:0 -msgid "Filename prefix:" -msgstr "Fájlnév előtagja" - -#: WikidPad.xrc:0 -msgid "First word at startup:" -msgstr "Wikiszó indításkor" - -#: WikidPad.xrc:0 -msgid "Font name for HTML preview:" -msgstr "HTML-előnézet betűtípusa:" - -#: WikidPad.xrc:0 -msgid "Font:" -msgstr "Font:" - -#: WikidPad.xrc:0 -msgid "Forbid cancel on search" -msgstr "Megszakítás tiltása kereséskor" - -#: WikidPad.xrc:0 -msgid "Force ScratchPad visibility in tree" -msgstr "A vázlatfüzet mindenképpen jelenjen meg a fában" - -#: WikidPad.xrc:0 -msgid "Format version" -msgstr "Formátum verzió" - -#: WikidPad.xrc:0 -msgid "Given:" -msgstr "Adott:" - -#: WikidPad.xrc:0 -msgid "Go to next page" -msgstr "Ugrás a következő lapra" - -#: WikidPad.xrc:0 -msgid "Graceful handling of missing page files" -msgstr "Hiányzó fájlnevek óvatos kezelése" - -#: WikidPad.xrc:0 -msgid "HTML" -msgstr "HTML" - -#: WikidPad.xrc:0 -msgid "Heading of new pages" -msgstr "Új lapok fejléce" - -#: WikidPad.xrc:0 -msgid "Heading prefix:" -msgstr "Fejléc előtag" - -#: WikidPad.xrc:0 -msgid "Headings as aliases" -msgstr "Fejlécek álnevekként" - -#: WikidPad.xrc:0 -msgid "Hidden" -msgstr "Rejtett" - -#: WikidPad.xrc:0 -msgid "Hide Log" -msgstr "Log rejtése" - -#: WikidPad.xrc:0 -msgid "Hide undefined wiki words in Tree" -msgstr "A nem definiált wikiszavak rejtse el a fában" - -#: WikidPad.xrc:0 -msgid "Highlight start delay:" -msgstr "Kiemelés indításának késleltetése:" - -#: WikidPad.xrc:0 -msgid "IE" -msgstr "IE" - -#: WikidPad.xrc:0 -msgid "Icon:" -msgstr "Ikon:" - -#: WikidPad.xrc:0 -msgid "Ig&nore All" -msgstr "Mindet elvet" - -#: WikidPad.xrc:0 -msgid "Ignore wiki lock file" -msgstr "Wiki zárolásfájl mellőzése" - -#: WikidPad.xrc:0 -msgid "Image pasting" -msgstr "Kép beillesztése" - -#: WikidPad.xrc:0 -msgid "Import format:" -msgstr "Importálás formátuma:" - -#: WikidPad.xrc:0 -msgid "Import questions" -msgstr "Importálási kérdések" - -#: WikidPad.xrc:0 -msgid "Incremental search" -msgstr "Részletkeresés" - -#: WikidPad.xrc:0 -msgid "Internal" -msgstr "Belső" - -#: WikidPad.xrc:0 -msgid "Intersect" -msgstr "Összevet" - -#: WikidPad.xrc:0 -msgid "JPEG" -msgstr "JPEG" - -#: WikidPad.xrc:0 -msgid "Left" -msgstr "Bal" - -#: WikidPad.xrc:0 -msgid "Left double click in preview:" -msgstr "Bal duplakattintás előnézetben:" - -#: WikidPad.xrc:0 -msgid "Link color:" -msgstr "Link színe:" - -#: WikidPad.xrc:0 -msgid "Load and R&un Export" -msgstr "Export betöltése és futtatása" - -#: WikidPad.xrc:0 -msgid "Load and R&un Search" -msgstr "Keresés betöltése és futtatása" - -#: WikidPad.xrc:0 -msgid "Margin color:" -msgstr "Szegélyszín" - -#: WikidPad.xrc:0 -msgid "Max. characters on each tab:" -msgstr "Karakterek maximális száma a fül címében" - -#: WikidPad.xrc:0 -msgid "Middle button with CTRL:" -msgstr "Középső kattinás CTRL-lel:" - -#: WikidPad.xrc:0 -msgid "Middle button without CTRL:" -msgstr "Középső kattintás CTRL nélkül:" - -#: WikidPad.xrc:0 -msgid "Middle click on tab:" -msgstr "Középső kattintás a fülon" - -#: WikidPad.xrc:0 -msgid "Minimize on close button" -msgstr "Bezárás gombra összecsuk" - -#: WikidPad.xrc:0 -msgid "Minimize to Tray" -msgstr "Összecsuk tálcára" - -#: WikidPad.xrc:0 -msgid "Modif. date is enough" -msgstr "Módosítás dátuma elegendő" - -#: WikidPad.xrc:0 -msgid "Modif. date must match" -msgstr "Mód. dátumnak egyeznie kell" - -#: WikidPad.xrc:0 -msgid "Modify all links to the wiki word (unreliable!)" -msgstr "A wikiszó minden hivatkozását lecseréli (megbízhatatlan)" - -#: WikidPad.xrc:0 -msgid "Move into file storage" -msgstr "Áthelyezés a fájltárolóba" - -#: WikidPad.xrc:0 -msgid "Mozilla" -msgstr "Mozilla" - -#: WikidPad.xrc:0 -msgid "N. T. &Backgr." -msgstr "N. T. Háttér" - -#: WikidPad.xrc:0 -msgid "Natural" -msgstr "Természetes" - -#: WikidPad.xrc:0 -msgid "New tab in background" -msgstr "Új fül háttérben" - -#: WikidPad.xrc:0 -msgid "New tab in foreground" -msgstr "Új fül az előtérben" - -#: WikidPad.xrc:0 -msgid "New tab with edit" -msgstr "Előnézet: új fülön szerkeszt" - -#: WikidPad.xrc:0 -msgid "New window on wiki URL" -msgstr "Wiki URL nyitása új ablakban" - -#: WikidPad.xrc:0 -msgid "Newest visited" -msgstr "Legfrissebb látogatott" - -#: WikidPad.xrc:0 -msgid "No cycles in tree" -msgstr "Ciklus tiltása a Fában" - -#: WikidPad.xrc:0 -msgid "No global.import_scripts" -msgstr "Globális importszkriptek nem engedélyezettek" - -#: WikidPad.xrc:0 -msgid "No import_scripts" -msgstr "Importszkriptek nem engedélyezettek" - -#: WikidPad.xrc:0 -msgid "No scripts" -msgstr "Szkriptek nem engedélyezettek" - -#: WikidPad.xrc:0 -msgid "No title" -msgstr "Nincs cím" - -#: WikidPad.xrc:0 -msgid "None" -msgstr "Nincs" - -#: WikidPad.xrc:0 -msgid "Not at all" -msgstr "Egyáltalán ne" - -#: WikidPad.xrc:0 -msgid "Nothing" -msgstr "Semmi" - -#: WikidPad.xrc:0 -msgid "OK" -msgstr "Rendben" - -#: WikidPad.xrc:0 -msgid "Oldest visited" -msgstr "Legrégebbi látogatott" - -#: WikidPad.xrc:0 -msgid "Open in ..." -msgstr "Nyissa meg itt..." - -#: WikidPad.xrc:0 -msgid "Open in new window" -msgstr "Nyissa meg új ablakban" - -#: WikidPad.xrc:0 -msgid "Options" -msgstr "Opciók" - -#: WikidPad.xrc:0 -msgid "Options page:" -msgstr "Opciók lap:" - -#: WikidPad.xrc:0 -msgid "Order pages:" -msgstr "Parancslapok:" - -#: WikidPad.xrc:0 -msgid "Order:" -msgstr "Sorrend:" - -#: WikidPad.xrc:0 -msgid "PNG" -msgstr "PNG" - -#: WikidPad.xrc:0 -msgid "Page Setup" -msgstr "Oldalbeállítás" - -#: WikidPad.xrc:0 -msgid "Page file names ASCII only" -msgstr "Csak ASCII oldalfájl-nevek" - -#: WikidPad.xrc:0 -msgid "Page names matching regular expression:" -msgstr "Szabályos kifejezéssel egyező lapok neve" - -#: WikidPad.xrc:0 -msgid "Page separator:" -msgstr "Lapelválasztó:" - -#: WikidPad.xrc:0 -msgid "Pages in list and " -msgstr "Lapok a listában és " - -#: WikidPad.xrc:0 -msgid "Paste File" -msgstr "Fájl beillesztése" - -#: WikidPad.xrc:0 -msgid "Paste Image" -msgstr "Kép beillesztése" - -#: WikidPad.xrc:0 -msgid "Paste from clipboard:" -msgstr "Beillesztés vágólapról:" - -#: WikidPad.xrc:0 -msgid "Path or URL:" -msgstr "Elérési út vagy URL:" - -#: WikidPad.xrc:0 -msgid "Path to file launcher:" -msgstr "Fájlindító elérési útja:" - -#: WikidPad.xrc:0 -msgid "Plain text" -msgstr "Sima szöveg" - -#: WikidPad.xrc:0 -msgid "Plain text color:" -msgstr "Sima szöveg színe" - -#: WikidPad.xrc:0 -msgid "Position main tree:" -msgstr "Fa pozíció" - -#: WikidPad.xrc:0 -msgid "Position view tree:" -msgstr "Pozíció fanézetben" - -#: WikidPad.xrc:0 -msgid "Position:" -msgstr "Pozíció:" - -#: WikidPad.xrc:0 -msgid "Prefer memory where possible" -msgstr "Ahol lehet memóriát használja" - -#: WikidPad.xrc:0 -msgid "Prepend before clipboard snippet:" -msgstr "Vágolaprészlet elé illeszt" - -#: WikidPad.xrc:0 -msgid "Prepend before links:" -msgstr "Linkek elé illeszt" - -#: WikidPad.xrc:0 -msgid "Prepend wiki word" -msgstr "Wikiszó elévetés" - -#: WikidPad.xrc:0 -msgid "Preview" -msgstr "Előnézet" - -#: WikidPad.xrc:0 -msgid "Preview renderer:" -msgstr "Előnézet megjelenítő:" - -#: WikidPad.xrc:0 -msgid "Preview:" -msgstr "Előnézet:" - -#: WikidPad.xrc:0 -msgid "Print" -msgstr "Nyomtatás" - -#: WikidPad.xrc:0 -msgid "Print as:" -msgstr "Nyomtat mint:" - -#: WikidPad.xrc:0 -msgid "Process auto-generated areas" -msgstr "Automatikusan létrehozott területek feldolgozása" - -#: WikidPad.xrc:0 -msgid "Process insertion scripts" -msgstr "Beillesztett szkriptek feldolgozása" - -#: WikidPad.xrc:0 -msgid "Quality:" -msgstr "Minőség:" - -#: WikidPad.xrc:0 -msgid "R&egular expression" -msgstr "Szabályos kifejezés" - -#: WikidPad.xrc:0 -msgid "Read-only wiki" -msgstr "Csak olvasható Wiki" - -#: WikidPad.xrc:0 -msgid "Recent wikis list length:" -msgstr "Wiki előzménylista hossza:" - -#: WikidPad.xrc:0 -msgid "Relative URL" -msgstr "Releatív URL" - -#: WikidPad.xrc:0 -msgid "Remember expanded tree nodes:" -msgstr "Megnyitott facsomópontok megjegyzése:" - -#: WikidPad.xrc:0 -msgid "Rename" -msgstr "Átnevez" - -#: WikidPad.xrc:0 -msgid "Rename Word:" -msgstr "Wikiszó átnevezése" - -#: WikidPad.xrc:0 -msgid "Rename dialog defaults" -msgstr "Átnevezés párbeszédablak alapbeállításai" - -#: WikidPad.xrc:0 -msgid "Replace &All" -msgstr "Mindet cserél" - -#: WikidPad.xrc:0 -msgid "Replace By" -msgstr "Cseréli erre:" - -#: WikidPad.xrc:0 -msgid "Replace with:" -msgstr "Cseréli evvel:" - -#: WikidPad.xrc:0 -msgid "Result" -msgstr "Eredmény" - -#: WikidPad.xrc:0 -msgid "Reverse script search order (global imports first)" -msgstr "Fordított szkriptkeresési sorrend (globális importáltak elől)" - -#: WikidPad.xrc:0 -msgid "Right" -msgstr "Jobb" - -#: WikidPad.xrc:0 -msgid "Same Tab" -msgstr "Azonos fül" - -#: WikidPad.xrc:0 -msgid "Saved Exports:" -msgstr "Mentett exportok" - -#: WikidPad.xrc:0 -msgid "Saved Searches:" -msgstr "Mentett keresések:" - -#: WikidPad.xrc:0 -msgid "Script security:" -msgstr "Szkriptbiztonság:" - -#: WikidPad.xrc:0 -msgid "Search Page" -msgstr "Lapon keres" - -#: WikidPad.xrc:0 -msgid "Search Wiki" -msgstr "Wikiben keres" - -#: WikidPad.xrc:0 -msgid "Search from" -msgstr "Keresés ettől" - -#: WikidPad.xrc:0 -msgid "Search text" -msgstr "Keresett szöveg" - -#: WikidPad.xrc:0 -msgid "Select facename" -msgstr "Facename kiválasztása" - -#: WikidPad.xrc:0 -msgid "Selection bg. color:" -msgstr "Kiválasztás háttérszine" - -#: WikidPad.xrc:0 -msgid "Selection fg. color:" -msgstr "Kiválasztás előtérszíne" - -#: WikidPad.xrc:0 -msgid "Separate files" -msgstr "Önálló fáljloban" - -#: WikidPad.xrc:0 -msgid "Set As Root" -msgstr "Beállítás gyökérnek" - -#: WikidPad.xrc:0 -msgid "Set page &list" -msgstr "Laplista beállítása" - -#: WikidPad.xrc:0 -msgid "Short hint delay:" -msgstr "Rövidtipp késleltetése:" - -#: WikidPad.xrc:0 -msgid "Shortcut:" -msgstr "Billentyűparancs:" - -#: WikidPad.xrc:0 -msgid "Show in toolbar" -msgstr "Eszköztárban mutat" - -#: WikidPad.xrc:0 -msgid "Show pics as links in export" -msgstr "Exportban a képeket linkként mutassa" - -#: WikidPad.xrc:0 -msgid "Show pics as links in preview" -msgstr "Előnézetben a képeket linkként mutassa" - -#: WikidPad.xrc:0 -msgid "Show pictures as links" -msgstr "Képek mutatása linkként" - -#: WikidPad.xrc:0 -msgid "Show word list on hovering" -msgstr "Rámutatáskor a szavak listáját mutassa" - -#: WikidPad.xrc:0 -msgid "Show word list on select" -msgstr "Kiválasztáskor mutassa a szavak listáját" - -#: WikidPad.xrc:0 -msgid "Si&mple regex" -msgstr "Egyszerű regex" - -#: WikidPad.xrc:0 -msgid "Single page separator lines:" -msgstr "Önálló oldalakat elválasztó vonal:" - -#: WikidPad.xrc:0 -msgid "Single process per user*" -msgstr "Felhasználónként egy porcessz*" - -#: WikidPad.xrc:0 -msgid "Sort" -msgstr "Rendez" - -#: WikidPad.xrc:0 -msgid "Sort alphabetically" -msgstr "Betűrendbe rendez" - -#: WikidPad.xrc:0 -msgid "Sort order:" -msgstr "Rendezési sorrend:" - -#: WikidPad.xrc:0 -msgid "Sound" -msgstr "Hang" - -#: WikidPad.xrc:0 -msgid "Sound file:" -msgstr "Hangfájl:" - -#: WikidPad.xrc:0 -msgid "Spell Check" -msgstr "Helyesírásellenőrző" - -#: WikidPad.xrc:0 -msgid "Start browser after export" -msgstr "Böngésző indítása export után" - -#: WikidPad.xrc:0 -msgid "Statusbar time format:" -msgstr "Státuszsáv időformátuma" - -#: WikidPad.xrc:0 -msgid "Steps to complete version:" -msgstr "Változatok a teljes verzióig:" - -#: WikidPad.xrc:0 -msgid "Store relative pathes to wikis" -msgstr "Wikikben relatív útvonalak tárolása" - -#: WikidPad.xrc:0 -msgid "Structure window heading depth:" -msgstr "Struktúraablak fejlécmélysége" - -#: WikidPad.xrc:0 -msgid "Structure window position:" -msgstr "Struktúraablak pozíciója:" - -#: WikidPad.xrc:0 -msgid "Structure window selection auto-follow" -msgstr "Struktúraablak kiválasztás automatikus követése" - -#: WikidPad.xrc:0 -msgid "Swap From <-> To" -msgstr "Cserélje erről <-> erre" - -#: WikidPad.xrc:0 -msgid "Switch Ed./Prev" -msgstr "Szerk./Előnéz. vált." - -#: WikidPad.xrc:0 -msgid "Switch Tabs in MRU order" -msgstr "Fülek váltása időrenben" - -#: WikidPad.xrc:0 -msgid "Switch tab to edit" -msgstr "Előnézet: fülön szerkeszt" - -#: WikidPad.xrc:0 -msgid "Sync. highlighting limit:" -msgstr "Szinkr. kiemelési határérték:" - -#: WikidPad.xrc:0 -msgid "Synchronize editor by preview selection" -msgstr "Szerkesztő szinkronizálása az előnézeti kiválasztáshoz" - -#: WikidPad.xrc:0 -msgid "System" -msgstr "Rendszer" - -#: WikidPad.xrc:0 -msgid "Tab width:" -msgstr "Fül szélessége" - -#: WikidPad.xrc:0 -msgid "Table of contents:" -msgstr "Tartalomjegyzék:" - -#: WikidPad.xrc:0 -msgid "Template page names reg. exp.:" -msgstr "Sablonlapok reg.ex.:" - -#: WikidPad.xrc:0 -msgid "Temporary file directory:" -msgstr "Időszakos fájlok könyvtára:" - -#: WikidPad.xrc:0 -msgid "Temporary files" -msgstr "Időszakos fájlok" - -#: WikidPad.xrc:0 -msgid "Temporary path:" -msgstr "Időszakos útvonala:" - -#: WikidPad.xrc:0 -msgid "Test" -msgstr "Teszt" - -#: WikidPad.xrc:0 -msgid "Text color:" -msgstr "Szövegszin:" - -#: WikidPad.xrc:0 -msgid "Text cursor color:" -msgstr "Szövegkurzor szín:" - -#: WikidPad.xrc:0 -msgid "Timeline" -msgstr "Időrend" - -#: WikidPad.xrc:0 -msgid "Title of toc:" -msgstr "Tartalomjegyzék elnevezése:" - -#: WikidPad.xrc:0 -msgid "To Word:" -msgstr "Szóra:" - -#: WikidPad.xrc:0 -msgid "To check" -msgstr "Ellenőrzendő" - -#: WikidPad.xrc:0 -msgid "Top" -msgstr "Tetejére" - -#: WikidPad.xrc:0 -msgid "Tree auto-follow" -msgstr "Fa - automatikus követés" - -#: WikidPad.xrc:0 -msgid "Tree auto-hide" -msgstr "Fa automatikus rejtése" - -#: WikidPad.xrc:0 -msgid "Tree timing" -msgstr "Fa időzítése" - -#: WikidPad.xrc:0 -msgid "Tree update after save" -msgstr "Fa frissítése mentés után" - -#: WikidPad.xrc:0 -msgid "UI Language:" -msgstr "Kezelői felület nyelve" - -#: WikidPad.xrc:0 -msgid "UTF-8 with BOM" -msgstr "UTF-8 BOM-mal" - -#: WikidPad.xrc:0 -msgid "UTF-8 without BOM" -msgstr "UTF-8 BOM-nélkül" - -#: WikidPad.xrc:0 -msgid "Unordered" -msgstr "Rendezetlen" - -#: WikidPad.xrc:0 -msgid "Up" -msgstr "Fel" - -#: WikidPad.xrc:0 -msgid "Up to heading depth:" -msgstr "Fejléc mélységéig:" - -#: WikidPad.xrc:0 -msgid "Update step min. delay:" -msgstr "Frissítési lépések késleltetése min." - -#: WikidPad.xrc:0 -msgid "Uppercase first" -msgstr "Nagybetűsek előre" - -#: WikidPad.xrc:0 -msgid "Use IME workaround for editor input*" -msgstr "IME munkakörnyezet használata a szerkesztő bemeneteként*" - -#: WikidPad.xrc:0 -msgid "Use link title if present" -msgstr "Használja a hivatkozás elnevezését, ha létezik" - -#: WikidPad.xrc:0 -msgid "User notification:" -msgstr "Felhasználó értesítése:" - -#: WikidPad.xrc:0 -msgid "Version Import" -msgstr "Verzió import" - -#: WikidPad.xrc:0 -msgid "Visited link color:" -msgstr "Meglátogatott hivatkozások színe:" - -#: WikidPad.xrc:0 -msgid "Warn about other processes (Windows only)" -msgstr "Más folyamatokra figyelmeztet (csak Windows)" - -#: WikidPad.xrc:0 -msgid "What to export:" -msgstr "Exportálandó:" - -#: WikidPad.xrc:0 -msgid "What to print:" -msgstr "Nyomtatandó:" - -#: WikidPad.xrc:0 -msgid "Where to store:" -msgstr "Tárolás helye:" - -#: WikidPad.xrc:0 -msgid "While wiki open" -msgstr "Amíg a wiki nyitva:" - -#: WikidPad.xrc:0 -msgid "Whole wiki" -msgstr "Teljes Wiki" - -#: WikidPad.xrc:0 -msgid "Wiki Search" -msgstr "Wiki keresés" - -#: WikidPad.xrc:0 -msgid "Wiki Word" -msgstr "Wikiszó" - -#: WikidPad.xrc:0 -msgid "Wiki icon:" -msgstr "Wiki ikon:" - -#: WikidPad.xrc:0 -msgid "Wiki language:" -msgstr "Wiki nyelve" - -#: WikidPad.xrc:0 -msgid "Wiki word to heading:" -msgstr "Wikiszó fejlécbe" - -#: WikidPad.xrc:0 -msgid "Wiki-bound hotkey:" -msgstr "Wikit elrejtő gyorsbillentyű:" - -#: WikidPad.xrc:0 -msgid "Wiki-wide search" -msgstr "Wiki-szintű keresés:" - -#: WikidPad.xrc:0 -msgid "Write saved searches" -msgstr "Mentett keresések kiírása" - -#: WikidPad.xrc:0 -msgid "Write version data of pages" -msgstr "A lapok verzióadatai írja ki" - -#: WikidPad.xrc:0 -msgid "Write wiki func. pages" -msgstr "Wiki funkciólapok írása" - -#: WikidPad.xrc:0 -msgid "bytes" -msgstr "bájt" - -#: WikidPad.xrc:0 -msgid "cursor" -msgstr "kurzor" - -#: WikidPad.xrc:0 -msgid "include in export:" -msgstr "ellenőriz exportban:" - -#: WikidPad.xrc:0 -msgid "include in preview:" -msgstr "ellenőriz előnézetben:" - -#: WikidPad.xrc:0 -msgid "level(s) below" -msgstr "szinttel alatta" - -#: WikidPad.xrc:0 -msgid "milliseconds" -msgstr "ezredmásodperc" - -#: WikidPad.xrc:0 -msgid "not " -msgstr "nem " - -#: WikidPad.xrc:0 -msgid "second(s)" -msgstr "másodperc" - -#: WikidPad.xrc:0 -#: extensions\GraphvizStructureView.py:667 -#: extensions\GraphvizStructureView.py:691 -#: extensions\GraphvizStructureView.py:704 -#: extensions\GraphvizStructureView.py:717 -msgid "..." -msgstr "..." - -#: WikidPad.xrc:0 -#: lib\pwiki\AdditionalDialogs.py:793 -msgid " OK " -msgstr " OK " - -#: WikidPad.xrc:0 -#: lib\pwiki\AdditionalDialogs.py:797 -msgid " Cancel " -msgstr " Elvet " - -#: WikidPad.xrc:0 -#: lib\pwiki\AdditionalDialogs.py:1265 -msgid "Destination directory:" -msgstr "Célkönyvtár:" - -#: WikidPad.xrc:0 -#: lib\pwiki\AdditionalDialogs.py:1473 -#: lib\pwiki\SearchAndReplaceDialogs.py:1429 -msgid "Title:" -msgstr "Cím:" - -#: WikidPad.xrc:0 -#: lib\pwiki\AdditionalDialogs.py:1771 -msgid "Source directory:" -msgstr "Forráskönyvtár:" - -#: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:168 -#: lib\pwiki\MptImporterGui.py:170 -msgid "Yes" -msgstr "Igen" - -#: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:168 -#: lib\pwiki\MptImporterGui.py:170 -#: lib\pwiki\OptionsDialog.py:137 -#: lib\pwiki\OptionsDialog.py:854 -msgid "Default" -msgstr "Alapértelmezett" - -#: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:169 -msgid "Overwrite" -msgstr "Felülír" - -#: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:169 -#: lib\pwiki\MptImporterGui.py:170 -msgid "No" -msgstr "Nem" - -#: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:193 -msgid "Import" -msgstr "Importálás" - -#: WikidPad.xrc:0 -#: lib\pwiki\OptionsDialog.py:670 -msgid "Versioning" -msgstr "Verziózás" - -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1209 -#: lib\pwiki\WikiTxtCtrl.py:3387 -msgid "Undo" -msgstr "Visszavon" - -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1213 -#: lib\pwiki\WikiTxtCtrl.py:3388 -msgid "Redo" -msgstr "Ismét" - -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1229 -#: lib\pwiki\WikiTxtCtrl.py:3389 -msgid "Cut" -msgstr "Kivág" - -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1234 -#: lib\pwiki\WikiTxtCtrl.py:3390 -msgid "Copy" -msgstr "Másol" - -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1238 -#: lib\pwiki\WikiTxtCtrl.py:3391 -msgid "Paste" -msgstr "Beilleszt" - -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1243 -#: lib\pwiki\WikiTxtCtrl.py:3393 -msgid "Select All" -msgstr "Kiválaszt mind" - -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1551 -msgid "&Delete" -msgstr "Töröl" - -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1974 -#: lib\pwiki\PersonalWikiFrame.py:1975 -msgid "Open Wiki Word" -msgstr "Wikiszó megnyitása" - -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1999 -#: lib\pwiki\PersonalWikiFrame.py:2000 -msgid "Rename Wiki Word" -msgstr "Wikiszó átnevezése" - -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:2005 -#: lib\pwiki\WikiTxtCtrl.py:3392 -msgid "Delete" -msgstr "Töröl" - -#: WikidPad.xrc:0 -#: lib\pwiki\SearchAndReplaceDialogs.py:819 -msgid "Close" -msgstr "Bezár" - -#: WikidPad.xrc:0 -#: lib\pwiki\SearchAndReplaceDialogs.py:1655 -msgid "Set page list" -msgstr "Oldallista beállítása" - -#: WikidPad.xrc:0 -#: lib\pwiki\SearchAndReplaceDialogs.py:1903 -msgid "As Resultlist" -msgstr "Eredménylistaként" - -#: WikidPad.xrc:0 -#: lib\pwiki\SearchAndReplaceDialogs.py:1908 -#: lib\pwiki\SearchAndReplaceDialogs.py:2014 -msgid "As Full Search" -msgstr "Teljes keresésként" - -#: WikidPad.xrc:0 -#: lib\pwiki\SearchAndReplaceDialogs.py:2308 -#: lib\pwiki\WikiHtmlView.py:711 -msgid "Activate New Tab" -msgstr "Új Fül Aktiválása" - -#: WikidPad.xrc:0 -#: lib\pwiki\WikiTxtCtrl.py:3407 -msgid "Close Tab" -msgstr "Fül Bezárása" - -#: WikidPad.xrc:0 -#: lib\pwiki\timeView\TimelinePanel.py:798 -msgid "Show empty days" -msgstr "Üres napok megjelenítése" - -#: WikidPad.xrc:0 -#: lib\pwiki\timeView\TimelinePanel.py:800 -msgid "Sort dates ascending" -msgstr "Dátumokat emelkedő sorrendben" - -#: WikidPadStarter.py:196 -msgid "Error starting WikidPad" -msgstr "Hiba WikidPad indításakor" - -#: WikidPadStarter.py:197 -#: lib\pwiki\PersonalWikiFrame.py:5084 -#: lib\pwiki\SearchAndReplaceDialogs.py:688 -#: lib\pwiki\SearchAndReplaceDialogs.py:968 -msgid "Error!" -msgstr "Hiba!" - -#: WikidPadStarter.py:204 -#: lib\pwiki\MptImporterGui.py:197 -msgid "Error" -msgstr "Hiba" - -#: extensions\GnuplotClBridge.py:91 -msgid "[Please set path to Gnuplot executable]" -msgstr "[Állítsa be a futtatható Gnuplot útvonalát]" - -#: extensions\GnuplotClBridge.py:129 -msgid "[Gnuplot error: %s]" -msgstr "[Gnuplot hiba: %s]" - -#: extensions\GnuplotClBridge.py:180 -msgid "Path to Gnuplot:" -msgstr "Gnuplot útvonala:" - -#: extensions\GraphvizClBridge.py:106 -msgid "[Please set path to GraphViz executables]" -msgstr "[Állítsa be a futtatható GraphViz útvonalát]" - -#: extensions\GraphvizClBridge.py:137 -msgid "[%s Error: %s]" -msgstr "[%s Hiba: %s]" - -#: extensions\GraphvizClBridge.py:238 -msgid "Directory of executables:" -msgstr "Parancskönyvtár:" - -#: extensions\GraphvizClBridge.py:242 -msgid "Name of dot executable:" -msgstr "Dot parancs neve:" - -#: extensions\GraphvizClBridge.py:246 -msgid "Name of neato executable:" -msgstr "Neato parancsok neve:" - -#: extensions\GraphvizClBridge.py:250 -msgid "Name of twopi executable:" -msgstr "Twopi parancsok neve:" - -#: extensions\GraphvizClBridge.py:254 -msgid "Name of circo executable:" -msgstr "Circo parancsok neve:" - -#: extensions\GraphvizClBridge.py:258 -msgid "Name of fdp executable:" -msgstr "Fdp parancsok neve:" - -#: extensions\GraphvizStructureView.py:355 -msgid "%s Error: %s" -msgstr "%s Hiba: %s" - -#: extensions\GraphvizStructureView.py:421 -#: extensions\GraphvizStructureView.py:422 -msgid "Show relation graph" -msgstr "Mutassa a relációs ábrát" - -#: extensions\GraphvizStructureView.py:424 -msgid "Show rel. graph source" -msgstr "Mutassa a relációs ábra forrását" - -#: extensions\GraphvizStructureView.py:426 -msgid "Show relation graph source" -msgstr "Mutassa a relációs ábra forrását" - -#: extensions\GraphvizStructureView.py:428 -#: extensions\GraphvizStructureView.py:429 -msgid "Show child graph" -msgstr "Mutassa a leszármazott ábrát" - -#: extensions\GraphvizStructureView.py:431 -#: extensions\GraphvizStructureView.py:433 -msgid "Show child graph source" -msgstr "Mutassa a leszármaott ábra forrását" - -#: extensions\GraphvizStructureView.py:650 -msgid " GraphVizStructure" -msgstr " GraphVizStruktúra" - -#: extensions\GraphvizStructureView.py:670 -msgid "Node font name:" -msgstr "Csomópont betűtípusa:" - -#: extensions\GraphvizStructureView.py:682 -msgid "Node font size:" -msgstr "Csomópont betűmérete" - -#: extensions\GraphvizStructureView.py:694 -msgid "Node border color:" -msgstr "Csomópont határoló szín:" - -#: extensions\GraphvizStructureView.py:707 -msgid "Node background color:" -msgstr "Csomópont háttérszín:" - -#: extensions\GraphvizStructureView.py:720 -msgid "Edge color:" -msgstr "Nyílhegy szine:" - -#: extensions\MimeTexCGIBridge.py:92 -msgid "[Please set path to MimeTeX executable]" -msgstr "[Állítsa be a parancs Mimetex elérési útját]" - -#: extensions\MimeTexCGIBridge.py:111 -msgid "[Invalid response from MimeTeX]" -msgstr "[Érvénytelen válasz a MimeTeX-től]" - -#: extensions\MimeTexCGIBridge.py:172 -msgid "Path to MimeTeX:" -msgstr "MimeTex-útvonal:" - -#: extensions\PloticusClBridge.py:108 -msgid "[Please set path to Ploticus executable]" -msgstr "[Állítsa be a Ploticus parancs elérési útját]" - -#: extensions\PloticusClBridge.py:140 -msgid "[Ploticus error: %s]" -msgstr "[Ploticus hiba: %s]" - -#: extensions\PloticusClBridge.py:200 -msgid "Path to Ploticus:" -msgstr "Ploticus-útvonal" - -#: extensions\PloticusClBridge.py:206 -msgid "Output format:" -msgstr "Kimeneti formátum:" - -#: extensions\WikidPadParserStub.py:121 -msgid "Footnotes as wiki words" -msgstr "Lábjegyzetek wikiszóként" - -#: extensions\autoNew.py:58 -#: extensions\autoNew.py:59 -msgid "Create new page" -msgstr "Új lap" - -#: extensions\referrals.py:74 -#: extensions\referrals.py:75 -#: extensions\referrals.py:109 -#: extensions\referrals.py:129 -msgid "Insert referring pages" -msgstr "Hivatkozó lapok beillesztése" - -#: extensions\referrals.py:109 -#: extensions\referrals.py:129 -msgid "Referers" -msgstr "Hivatkozók" - -#: extensions\referrals.py:147 -msgid "*%s page(s) referring to* %s\n" -msgstr "*%s lap(ok) hivatkozi(na)k a* %s-ra\n" - -#: extensions\referrals.py:156 -msgid "*%s page(s) referred to by* %s\n" -msgstr "*%s lap(ok)ra a *%s hivatkozi(na)k \n" - -#: extensions\wikidPadParser\WikidPadParser.py:1580 -#: extensions\wikidPadParser\WikidPadParser.py:1607 -msgid "This is a footnote" -msgstr "Lábjegyzet" - -#: extensions\wikidPadParser\WikidPadParser.py:1585 -#: extensions\wikidPadParser\WikidPadParser.py:1612 -msgid "This is syntactically not a wiki word" -msgstr "Szintaktikailag nem wikiszó" - -#: extensions\wikidPadParser\WikidPadParser.py:2234 -msgid "" -"++ Wiki Settings\n" -"\n" -"These are your default global settings.\n" -"\n" -"[global.importance.low.color: grey]\n" -"[global.importance.high.bold: true]\n" -"[global.contact.icon: contact]\n" -"[global.wrap: 70]\n" -"\n" -"[icon: cog]\n" -msgstr "" -"++ Wiki beállítások\n" -"\n" -"Az Ön glogális wiki alapbeállításai.\n" -"\n" -"[global.importance.low.color: grey]\n" -"[global.importance.high.bold: true]\n" -"[global.contact.icon: contact]\n" -"[global.wrap: 70]\n" -"\n" -"[icon: cog]\n" - -#: lib\pwiki\AdditionalDialogs.py:145 -#: lib\pwiki\AdditionalDialogs.py:356 -msgid "Links to:" -msgstr "Link ehhez:" - -#: lib\pwiki\AdditionalDialogs.py:286 -#: lib\pwiki\AdditionalDialogs.py:398 -msgid "'%s' is an invalid WikiWord" -msgstr "'%s' szabálytalan Wikiszó" - -#: lib\pwiki\AdditionalDialogs.py:293 -msgid "'%s' is not an existing wikiword. Create?" -msgstr "'%s' nem létező wikiszó. Létrehozzam?" - -#: lib\pwiki\AdditionalDialogs.py:294 -msgid "Create" -msgstr "Létrehoz" - -#: lib\pwiki\AdditionalDialogs.py:404 -msgid "'%s' exists already" -msgstr "'%s' már létezik" - -#: lib\pwiki\AdditionalDialogs.py:418 -#: lib\pwiki\AdditionalDialogs.py:511 -msgid "Do you want to delete %i wiki page(s)?" -msgstr "Törölni akarja %i wikilap(oka)t?" - -#: lib\pwiki\AdditionalDialogs.py:670 -msgid "" -"Can't process renaming:\n" -"%s" -msgstr "" -"Az átnevezés nem folytatható:\n" -"%s" - -#: lib\pwiki\AdditionalDialogs.py:671 -msgid "Can't rename" -msgstr "Nem átnevezhető" - -#: lib\pwiki\AdditionalDialogs.py:709 -msgid "Can't rename to itself" -msgstr "Önmagára nem átnevezhető" - -#: lib\pwiki\AdditionalDialogs.py:713 -#: lib\pwiki\WikiExceptions.py:73 -msgid "Word already exists" -msgstr "Szó már létezik" - -#: lib\pwiki\AdditionalDialogs.py:766 -msgid "Select Icon" -msgstr "Ikon kiválasztás" - -#: lib\pwiki\AdditionalDialogs.py:775 -msgid "Icon" -msgstr "Ikon" - -#: lib\pwiki\AdditionalDialogs.py:926 -msgid "" -"\n" -"\n" -"\n" -"\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
DirectiveMeaning
%aLocale's abbreviated weekday name.
%ALocale's full weekday name.
%bLocale's abbreviated month name.
%BLocale's full month name.
%cLocale's appropriate date and time representation.
%dDay of the month as a decimal number [01,31].
%HHour (24-hour clock) as a decimal number [00,23].
%IHour (12-hour clock) as a decimal number [01,12].
%jDay of the year as a decimal number [001,366].
%mMonth as a decimal number [01,12].
%MMinute as a decimal number [00,59].
%pLocale's equivalent of either AM or PM.
%SSecond as a decimal number [00,61].
%uWeekday as a decimal number [1(Monday),7].
%UWeek number of the year (Sunday as the first day of the\n" -" week) as a decimal number [00,53]. All days in a new year\n" -" preceding the first Sunday are considered to be in week 0.
%wWeekday as a decimal number [0(Sunday),6].
%WWeek number of the year (Monday as the first day of the\n" -" week) as a decimal number [00,53]. All days in a new year\n" -" preceding the first Monday are considered to be in week 0.
%xLocale's appropriate date representation.
%XLocale's appropriate time representation.
%yYear without century as a decimal number [00,99].
%YYear with century as a decimal number.
%ZTime zone name (no characters if no time zone exists).
%%A literal \"%\" character.
\\n" -"A newline.
\\\\A literal \"\\\" character.
\n" -"\n" -"\n" -msgstr "" -"\n" -"\n" -"\n" -"\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
UtasításJelentés
%aA hét napjai rövidítve helyi nyelven
%AA hét napjai helyi nyelven.
%bA hónapok rövidítve helyi nyelven.
%BA hónapok neve helyi nyelven.
%cDátum és idő szabályos megjelenítése helyi nyelven.
%dA hónap napjai számokkal. [01,31].
%HÓra (24 órás megjelenítés) számokkal [00,23].
%IÓra (12 órás megjelenítés) számokkal. [01,12].
%jÉv napja számokkal [001,366].
%mHónapok számokkal. [01,12].
%MPercek, számokkal. [00,59].
%pDE DU megfelelője helyi nyelven.
%SMásodperc számokkal [00,61].
%uHét napja számokkal [1(Hétfő),7].
%UAz év hetének sorszáma (vasárnap az első nap\n" -" a héten) [00,53]. Az új évet következő napok\n" -" számítanak és az első vasárnap a 0. hétre esik.
%wA hét napja számokkal [0(vasárnap),6].
%WAz év hetének sorszáma (hétfő az első nap a\n" -" héten) [00,53]. Az új évet következő napok\n" -" számítanak és az első hétfő a 0. hétre esik.
%xA dátum szabályos megjelenítése helyi nyelven.
%XAz idő szabályos megjelenítése helyi nyelven.
%yÉvszám arabszámmal évszázad nélkül [00,99].
%YÉvszám arabszámmal évszázaddal .
%ZAz időzóna elnevezése (nincs betű nemlétező időzónánál).
%%Betű \"%\" karakter.
\\n" -"Új sor
\\\\Betű \"\\\" Karakter.
\n" -"\n" -"\n" - -#: lib\pwiki\AdditionalDialogs.py:1036 -msgid "" -msgstr "<érvénytelen>" - -#: lib\pwiki\AdditionalDialogs.py:1166 -msgid "Continuous Export" -msgstr "Folyamatos export" - -#: lib\pwiki\AdditionalDialogs.py:1268 -msgid "Destination file:" -msgstr "Célfájl:" - -#: lib\pwiki\AdditionalDialogs.py:1300 -msgid "Destination directory does not exist" -msgstr "Célkönyvtár nem létezik" - -#: lib\pwiki\AdditionalDialogs.py:1305 -msgid "Destination must be a directory" -msgstr "A cél, könyvtár kell legyen" - -#: lib\pwiki\AdditionalDialogs.py:1311 -msgid "Destination must be a file" -msgstr "A cél, fálj kell legyen" - -#: lib\pwiki\AdditionalDialogs.py:1321 -#: lib\pwiki\PersonalWikiFrame.py:4685 -msgid "Exporting" -msgstr "Exportál" - -#: lib\pwiki\AdditionalDialogs.py:1323 -#: lib\pwiki\PersonalWikiFrame.py:4687 -msgid "Preparing" -msgstr "Előkészítés" - -#: lib\pwiki\AdditionalDialogs.py:1343 -msgid "Error while exporting" -msgstr "Hiba exportáláskor" - -#: lib\pwiki\AdditionalDialogs.py:1359 -#: lib\pwiki\PersonalWikiFrame.py:4633 -msgid "Select Export Directory" -msgstr "Válassza ki ez exportkönyvtárat" - -#: lib\pwiki\AdditionalDialogs.py:1373 -#: lib\pwiki\AdditionalDialogs.py:1844 -msgid "All files (*.*)" -msgstr "Összes fájl (*.*)" - -#: lib\pwiki\AdditionalDialogs.py:1378 -msgid "Select Export File" -msgstr "Exportfájl Kiválasztása" - -#: lib\pwiki\AdditionalDialogs.py:1405 -#: lib\pwiki\PersonalWikiFrame.py:4656 -#: lib\pwiki\PersonalWikiFrame.py:4670 -#: lib\pwiki\Printing.py:182 -msgid "No real wiki word selected as root" -msgstr "A gyökérnek választott nem wikiszó" - -#: lib\pwiki\AdditionalDialogs.py:1474 -msgid "Choose export title" -msgstr "Export-cím kiválasztása" - -#: lib\pwiki\AdditionalDialogs.py:1483 -msgid "Do you want to overwrite existing export '%s'?" -msgstr "Felül akarja írni a létező exportot '%s'?" - -#: lib\pwiki\AdditionalDialogs.py:1484 -msgid "Overwrite export" -msgstr "Export felülírása" - -#: lib\pwiki\AdditionalDialogs.py:1531 -msgid "Do you want to delete %i export(s)?" -msgstr "Törölni akarja az %i exporto(ka)t?" - -#: lib\pwiki\AdditionalDialogs.py:1532 -msgid "Delete export" -msgstr "Export törlése" - -#: lib\pwiki\AdditionalDialogs.py:1569 -msgid "Selected export type does not support saving" -msgstr "A választot exporttípus nem támogatja a mentést" - -#: lib\pwiki\AdditionalDialogs.py:1613 -msgid "Export type '%s' of saved export is not supported" -msgstr "A(z) '%s' mentett export típusa nem támogatott " - -#: lib\pwiki\AdditionalDialogs.py:1624 -msgid "" -"Saved export uses different version for additional options than current export\n" -"Export type: '%s'\n" -"Saved export version: %i\n" -"Current export version: %i" -msgstr "" -"A mentett export a kiegészítő opciókra eltérő verziót használ mint a jelenlegi export\n" -"Exportport típusa: '%n'\n" -"Mentett export verziója: %i\n" -"Aktuális export verziója: %i" - -#: lib\pwiki\AdditionalDialogs.py:1632 -msgid "Type of additional option storage ('%s') is unknown" -msgstr "A további opciók tárolásának típusa ('%s') ismeretlen" - -#: lib\pwiki\AdditionalDialogs.py:1663 -msgid "Error during retrieving saved export: " -msgstr "Hiba a mentett export beolvasásakor:" - -#: lib\pwiki\AdditionalDialogs.py:1774 -msgid "Source file:" -msgstr "Forrásfájl:" - -#: lib\pwiki\AdditionalDialogs.py:1790 -msgid "Source does not exist" -msgstr "Forrás nem létezik" - -#: lib\pwiki\AdditionalDialogs.py:1800 -msgid "Source must be a directory" -msgstr "A forrás, könyvtár kell legyen" - -#: lib\pwiki\AdditionalDialogs.py:1805 -msgid "Source must be a file" -msgstr "A forrás, fálj kell legyen" - -#: lib\pwiki\AdditionalDialogs.py:1816 -msgid "Error while importing" -msgstr "Hiba importáláskor" - -#: lib\pwiki\AdditionalDialogs.py:1830 -msgid "Select Import Directory" -msgstr "Import könyvtár kiválasztás" - -#: lib\pwiki\AdditionalDialogs.py:1845 -msgid "*" -msgstr "*" - -#: lib\pwiki\AdditionalDialogs.py:1849 -msgid "Select Import File" -msgstr "Importfájlt kiválasztás" - -#: lib\pwiki\AdditionalDialogs.py:1962 -msgid "" -"\n" -"\n" -"\n" -"
\n" -" \n" -" \n" -" \n" -" \n" -"

%s

\n" -"\n" -"

\n" -"wikidPad is a Wiki-like notebook for storing your thoughts, ideas, todo lists, contacts, or anything else you can think of to write down.\n" -"What makes wikidPad different from other notepad applications is the ease with which you can cross-link your information.

\n" -"

\n" -"\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
Author:Michael Butscher
Email:mbutscher@gmx.de
URL:http://www.mbutscher.de/software.html
 
Author:Jason Horman
Email:wikidpad@jhorman.org
URL:http://www.jhorman.org/wikidPad/
 
Author:Gerhard Reitmayr
Email:gerhard.reitmayr@gmail.com
 
 
Translations:
Swedish:Stefan Berg
\n" -"
\n" -" \n" -" \n" -"\n" -" \n" -"
\n" -" \n" -"

Your configuration directory is: %s
\n" -" Sqlite version: %s
\n" -" wxPython version: %s\n" -"

\n" -" \n" -"\n" -"\n" -msgstr "" -"\n" -"\n" -"\n" -"
\n" -" \n" -" \n" -" \n" -" \n" -"

%s

\n" -"\n" -"

\n" -"A WikidPad egy Wikiszerű jegyzettömb, gondolatok, ötletek, tennivalók, kapcsolatok, vagy bármi más feljegyzésére, amit fontosnak gondol leírni.\n" -"Ami a WikidPad-ot megkülönbözteti más jegyzettömb alkalmazásoktól, az a könnyedség, amivel információhoz kereszthivatkozásokat készíthe.

\n" -"

\n" -"\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
Author:Michael Butscher
Email:mbutscher@gmx.de
URL:http://www.mbutscher.de/software.html
 
Author:Jason Horman
Email:wikidpad@jhorman.org
URL:http://www.jhorman.org/wikidPad/
 
Author:Gerhard Reitmayr
Email:gerhard.reitmayr@gmail.com
 
 
Fordítás:
Swedish:Stefan Berg
Magyar:Török Árpád
\n" -"
\n" -" \n" -" \n" -"\n" -" \n" -"
\n" -" \n" -"

Az ön konfigurációs könyvtára: %s
\n" -" Sqlite verzió: %s
\n" -" wxPython verzió: %s\n" -"

\n" -" \n" -"\n" -"\n" - -#: lib\pwiki\AdditionalDialogs.py:2010 -#: lib\pwiki\PersonalWikiFrame.py:1892 -msgid "About WikidPad" -msgstr "WikidPad névjegye" - -#: lib\pwiki\AdditionalDialogs.py:2014 -msgid "N/A" -msgstr "Nincs Adat" - -#: lib\pwiki\AdditionalDialogs.py:2024 -msgid "Okay" -msgstr "Rendben" - -#: lib\pwiki\AdditionalDialogs.py:2132 -msgid "No wiki loaded" -msgstr "Nincs betöltött Wikilap" - -#: lib\pwiki\AdditionalDialogs.py:2136 -msgid "Wiki config. path:" -msgstr "Wiki konfig. útvonal" - -#: lib\pwiki\AdditionalDialogs.py:2142 -msgid "Wiki database backend:" -msgstr "Wiki adatbázisfeldolgozó" - -#: lib\pwiki\AdditionalDialogs.py:2150 -msgid "Number of wiki pages:" -msgstr "Wiki lapok száma:" - -#: lib\pwiki\AdditionalDialogs.py:2159 -msgid "Wiki is read-only. Reason:" -msgstr "A wiki csak olvasható, mert:" - -#: lib\pwiki\AdditionalDialogs.py:2162 -msgid "Write access to database lost. Try \"Wiki\"->\"Reconnect\"" -msgstr "Az adatbázis írási elérése megszűnt. Próbálja a \"Wik\"->\"Újracsatlakozi\"" - -#: lib\pwiki\AdditionalDialogs.py:2164 -msgid "Wiki was set read-only in options dialog" -msgstr "A wiki az opcióknál csak olvashatóra lett állítva" - -#: lib\pwiki\AdditionalDialogs.py:2169 -#: lib\pwiki\AdditionalDialogs.py:2171 -msgid "Can't write wiki config.:" -msgstr "Nem tudja írni a wiki konfig.-ot " - -#: lib\pwiki\AdditionalDialogs.py:2169 -#: lib\pwiki\AdditionalDialogs.py:2173 -msgid "Unknown reason" -msgstr "Ismeretlen ok" - -#: lib\pwiki\AdditionalDialogs.py:2203 -msgid "Number of Jobs:" -msgstr "Job-ok száma:" - -#: lib\pwiki\AttributeHandling.py:32 -msgid "AQUAMARINE" -msgstr "KÉKESZÖLD" - -#: lib\pwiki\AttributeHandling.py:33 -msgid "BLACK" -msgstr "FEKETE" - -#: lib\pwiki\AttributeHandling.py:34 -msgid "BLUE VIOLET" -msgstr "IBOLYAKÉK" - -#: lib\pwiki\AttributeHandling.py:35 -msgid "BLUE" -msgstr "KÉK" - -#: lib\pwiki\AttributeHandling.py:36 -msgid "BROWN" -msgstr "BARNA" - -#: lib\pwiki\AttributeHandling.py:37 -msgid "CADET BLUE" -msgstr "ISKOLAKÉK" - -#: lib\pwiki\AttributeHandling.py:38 -msgid "CORAL" -msgstr "KORALL" - -#: lib\pwiki\AttributeHandling.py:39 -msgid "CORNFLOWER BLUE" -msgstr "BÚZAVIRÁGKÉK" - -#: lib\pwiki\AttributeHandling.py:40 -msgid "CYAN" -msgstr "CIÁN" - -#: lib\pwiki\AttributeHandling.py:41 -msgid "DARK GREEN" -msgstr "SÖTÉTZÖLD" - -#: lib\pwiki\AttributeHandling.py:42 -msgid "DARK GREY" -msgstr "SÖTÉTSZÜRKE" - -#: lib\pwiki\AttributeHandling.py:43 -msgid "DARK OLIVE GREEN" -msgstr "SÖTÉT OLIVAZÖLD" - -#: lib\pwiki\AttributeHandling.py:44 -msgid "DARK ORCHID" -msgstr "SÖTÉR ORCHIDEA" - -#: lib\pwiki\AttributeHandling.py:45 -msgid "DARK SLATE BLUE" -msgstr "SÖTÉT PALAKÉK" - -#: lib\pwiki\AttributeHandling.py:46 -msgid "DARK SLATE GREY" -msgstr "SÖTÉT PALASZÜRKE" - -#: lib\pwiki\AttributeHandling.py:47 -msgid "DARK TURQUOISE" -msgstr "SÖTÉT TÜRKIZ" - -#: lib\pwiki\AttributeHandling.py:48 -msgid "DIM GREY" -msgstr "HALVÁNY SZÜRKE" - -#: lib\pwiki\AttributeHandling.py:49 -msgid "FIREBRICK" -msgstr "TÉGLAVÖRÖS" - -#: lib\pwiki\AttributeHandling.py:50 -msgid "FOREST GREEN" -msgstr "ERDEI ZÖLD" - -#: lib\pwiki\AttributeHandling.py:51 -msgid "GOLD" -msgstr "ARANY" - -#: lib\pwiki\AttributeHandling.py:52 -msgid "GOLDENROD" -msgstr "ARANYRÚD" - -#: lib\pwiki\AttributeHandling.py:53 -msgid "GREEN YELLOW" -msgstr "ZÖLDESSÁRGA" - -#: lib\pwiki\AttributeHandling.py:54 -msgid "GREEN" -msgstr "ZÖLD" - -#: lib\pwiki\AttributeHandling.py:55 -msgid "GREY" -msgstr "SZÜRKE" - -#: lib\pwiki\AttributeHandling.py:56 -msgid "INDIAN RED" -msgstr "INDIÁNVÖRÖS" - -#: lib\pwiki\AttributeHandling.py:57 -msgid "KHAKI" -msgstr "KEKI" - -#: lib\pwiki\AttributeHandling.py:58 -msgid "LIGHT BLUE" -msgstr "VILÁGOSKÉK" - -#: lib\pwiki\AttributeHandling.py:59 -msgid "LIGHT GREY" -msgstr "VILÁGOSSZÜRKE" - -#: lib\pwiki\AttributeHandling.py:60 -msgid "LIGHT STEEL BLUE" -msgstr "VILÁGOS ACÉLKÉK" - -#: lib\pwiki\AttributeHandling.py:61 -msgid "LIME GREEN" -msgstr "LIME ZÖLD" - -#: lib\pwiki\AttributeHandling.py:62 -msgid "MAGENTA" -msgstr "BÍBORVÖRÖS" - -#: lib\pwiki\AttributeHandling.py:63 -msgid "MAROON" -msgstr "GESZTENYE" - -#: lib\pwiki\AttributeHandling.py:64 -msgid "MEDIUM AQUAMARINE" -msgstr "KÖZÉP KÉKESZÖLD" - -#: lib\pwiki\AttributeHandling.py:65 -msgid "MEDIUM BLUE" -msgstr "KÖZÉPKÉK" - -#: lib\pwiki\AttributeHandling.py:66 -msgid "MEDIUM FOREST GREEN" -msgstr "KÖZÉP ERDEI ZÖLD" - -#: lib\pwiki\AttributeHandling.py:67 -msgid "MEDIUM GOLDENROD" -msgstr "KŐZÉP ARANYRÚD" - -#: lib\pwiki\AttributeHandling.py:68 -msgid "MEDIUM ORCHID" -msgstr "KÖZÉP ORCHIDEA" - -#: lib\pwiki\AttributeHandling.py:69 -msgid "MEDIUM SEA GREEN" -msgstr "KÖZÉP TENGERÉSKÉK" - -#: lib\pwiki\AttributeHandling.py:70 -msgid "MEDIUM SLATE BLUE" -msgstr "KÖZÉP PALAKÉK" - -#: lib\pwiki\AttributeHandling.py:71 -msgid "MEDIUM SPRING GREEN" -msgstr "KÖZÉP TAVASZI ZÖLD" - -#: lib\pwiki\AttributeHandling.py:72 -msgid "MEDIUM TURQUOISE" -msgstr "KÖZÉP TÜRKIZ" - -#: lib\pwiki\AttributeHandling.py:73 -msgid "MEDIUM VIOLET RED" -msgstr "KÖZÉP VIOLAVÖRÖS" - -#: lib\pwiki\AttributeHandling.py:74 -msgid "MIDNIGHT BLUE" -msgstr "ÉJKÉK" - -#: lib\pwiki\AttributeHandling.py:75 -msgid "NAVY" -msgstr "TENGERÉSZKÉK" - -#: lib\pwiki\AttributeHandling.py:76 -msgid "ORANGE RED" -msgstr "NARANCSVÖRÖS" - -#: lib\pwiki\AttributeHandling.py:77 -msgid "ORANGE" -msgstr "NARANCSSÁRGA" - -#: lib\pwiki\AttributeHandling.py:78 -msgid "ORCHID" -msgstr "ORCHIDEA" - -#: lib\pwiki\AttributeHandling.py:79 -msgid "PALE GREEN" -msgstr "HALVÁNYZÖLD" - -#: lib\pwiki\AttributeHandling.py:80 -msgid "PINK" -msgstr "RÓZSSZÍN" - -#: lib\pwiki\AttributeHandling.py:81 -msgid "PLUM" -msgstr "SZILVAKÉK" - -#: lib\pwiki\AttributeHandling.py:82 -msgid "PURPLE" -msgstr "BORDÓ" - -#: lib\pwiki\AttributeHandling.py:83 -msgid "RED" -msgstr "VÖRÖS" - -#: lib\pwiki\AttributeHandling.py:84 -msgid "SALMON" -msgstr "LAZACVÖRÖS" - -#: lib\pwiki\AttributeHandling.py:85 -msgid "SEA GREEN" -msgstr "TENGERZÖLD" - -#: lib\pwiki\AttributeHandling.py:86 -msgid "SIENNA" -msgstr "SZIENNA" - -#: lib\pwiki\AttributeHandling.py:87 -msgid "SKY BLUE" -msgstr "ÉGKÉK" - -#: lib\pwiki\AttributeHandling.py:88 -msgid "SLATE BLUE" -msgstr "PALAKÉK" - -#: lib\pwiki\AttributeHandling.py:89 -msgid "SPRING GREEN" -msgstr "TAVASZI ZÖLD" - -#: lib\pwiki\AttributeHandling.py:90 -msgid "STEEL BLUE" -msgstr "ACÉLKÉK" - -#: lib\pwiki\AttributeHandling.py:91 -msgid "TAN" -msgstr "KREOL" - -#: lib\pwiki\AttributeHandling.py:92 -msgid "THISTLE" -msgstr "BOGÁNCS" - -#: lib\pwiki\AttributeHandling.py:93 -msgid "TURQUOISE" -msgstr "TÜRKIZ" - -#: lib\pwiki\AttributeHandling.py:94 -msgid "VIOLET RED" -msgstr "VIOLAVÖRÖS" - -#: lib\pwiki\AttributeHandling.py:95 -msgid "VIOLET" -msgstr "VIOLA" - -#: lib\pwiki\AttributeHandling.py:96 -msgid "WHEAT" -msgstr "BÚZA" - -#: lib\pwiki\AttributeHandling.py:97 -msgid "WHITE" -msgstr "FEHÉR" - -#: lib\pwiki\AttributeHandling.py:98 -msgid "YELLOW GREEN" -msgstr "SÁRGÁSZÖLD" - -#: lib\pwiki\AttributeHandling.py:99 -msgid "YELLOW" -msgstr "SÁRGA" - -#: lib\pwiki\AttributeHandling.py:286 -msgid "Alias value isn't a valid wikiword: [%s: %s], %s" -msgstr "Az álnév nem valós wikiszó: [%s: %s], %s" - -#: lib\pwiki\AttributeHandling.py:297 -msgid "A real wikiword with the alias name exists already: [%s: %s]" -msgstr "Wikiszó ezzel az álnévvel már létezik: [%s: %s]" - -#: lib\pwiki\AttributeHandling.py:348 -msgid "Template value isn't a valid wikiword: [%s: %s], %s" -msgstr "A sablonérték nem valós wikiszó: [%s: %s], %s" - -#: lib\pwiki\AttributeHandling.py:359 -msgid "Template value isn't an existing wikiword: [%s: %s]" -msgstr "A sablonérték nemlétező wikiszó: [%s: %s]" - -#: lib\pwiki\AttributeHandling.py:406 -msgid "The attribute %s was already set differently on this page" -msgstr "A(z) %s attribútumot már beállították másként a lapon" - -#: lib\pwiki\AttributeHandling.py:418 -msgid "Attribute '%s' is already defined on the wiki page(s): %s" -msgstr "A(z) %s attribútumot már beállították a wikilap(ok)on: %s" - -#: lib\pwiki\AttributeHandling.py:427 -msgid "Icon name doesn't exist: [%s: %s]" -msgstr "Nemlétező ikonnév: [%s: %s]" - -#: lib\pwiki\AttributeHandling.py:434 -msgid "Color name doesn't exist: [%s: %s]" -msgstr "Nemlétező szín: [%s: %s]" - -#: lib\pwiki\AttributeHandling.py:481 -msgid "The attribute 'global.graph.relations.exclude' (e.g. on page '%s') overrides the '...include' attribute" -msgstr "A 'global.graph.relations.exclude' attribútum (pl. a '%s' lapon) felülírja a(z) '...include' attribútumot" - -#: lib\pwiki\AttributeHandling.py:607 -msgid "Same attribute twice: [%s: %s]" -msgstr "Azonos attribútum kétszer: [%s: %s]" - -#: lib\pwiki\CmdLineAction.py:238 -msgid "" -"Options:\n" -"\n" -" -h, --help: Show this message box\n" -" -w, --wiki : set the wiki to open on startup\n" -" -p, --page : set the page to open on startup\n" -" -x, --exit: exit immediately after performing command line actions\n" -" --export-what : choose if you want to export page, subtree or wiki\n" -" --export-type : tag of the export type\n" -" --export-dest : path of destination directory for export\n" -" --export-compfn: Use compatible filenames on export\n" -" --rebuild: rebuild the Wiki database\n" -" --no-recent: Do not record opened wikis in recently opened wikis list\n" -" --preview: If no pages are given, all opened pages from previous session\n" -" are opened in preview mode. Otherwise all pages given after that\n" -" option are opened in preview mode.\n" -" --editor: Same as --preview but opens in text editor mode.\n" -"\n" -msgstr "" -"Opciók:\n" -"\n" -" -h, --help: Ezen üzenetdoboz megjelenítése \n" -" -w, --wiki : indításkor megnyitandó wiki beállítása\n" -" -p, --page : indításkor megnyitandó lap beállítása\n" -" -x, --exit: a parancssor végrehajtását követően azonnal kilép\n" -" --export-what : válassza, ha exportálni szeretne lapot, alárendelt fát, vagy wikit \n" -" --export-type : az export típus kiterjesztése\n" -" --export-dest : az exportálás célkönyvtára\n" -" --export-compfn: exportáláskor kompatibilis fájlnév alkalmazása\n" -" --rebuild: Wiki adatbázis újraépítése\n" -" --no-recent: Ne jegyezze meg a megnyitott wikiket az \"Előzményekben\"\n" -" --preview: Ha nincsenek lapok megadva, akkor az előző mentben megnyitott valamennyit\n" -" megnyitja előnézetben. Ellenkező esetben az opció után meghatározott lapokat\n" -" nyitja meg előnézetben.\n" -" --editor: Mint a --preview, de szerkesztő módban nyitja meg.\n" -"\n" - -#: lib\pwiki\CmdLineAction.py:261 -msgid "Usage information" -msgstr "Alkalmazási információ" - -#: lib\pwiki\Configuration.py:205 -#: lib\pwiki\Configuration.py:252 -#: lib\pwiki\Configuration.py:370 -#: lib\pwiki\Configuration.py:376 -#: lib\pwiki\Configuration.py:421 -msgid "Unknown option %s:%s" -msgstr "Ismeretlen opció %s:%s" - -#: lib\pwiki\Configuration.py:279 -msgid "Config file not found" -msgstr "Konfigurációs fájl nem található" - -#: lib\pwiki\Configuration.py:413 -msgid "Ambiguos option set %s:%s" -msgstr "Ambíciózus opciók beállítva %s:%s" - -#: lib\pwiki\DiffGui.py:155 -msgid "" -msgstr "" - -#: lib\pwiki\DocPagePresenter.py:231 -msgid "'%s' is an invalid wiki word. %s." -msgstr "'%s' érvénytelen wikiszó. %s." - -#: lib\pwiki\DocPagePresenter.py:265 -msgid "Wiki page not found, a new page will be created" -msgstr "Wiki lap nem található, új lap készül" - -#: lib\pwiki\DocPagePresenter.py:455 -#: lib\pwiki\DocPagePresenter.py:456 -msgid "History" -msgstr "Előzmények" - -#: lib\pwiki\DocPages.py:2064 -msgid "Func. tag %s does not exist" -msgstr "Parancstoldat %s nem létezik" - -#: lib\pwiki\DocPages.py:2325 -msgid "Global text blocks" -msgstr "Globális szövegblokkok" - -#: lib\pwiki\DocPages.py:2326 -msgid "Wiki text blocks" -msgstr "Wiki szövegblokkok" - -#: lib\pwiki\DocPages.py:2327 -msgid "Global spell list" -msgstr "Globális helyesírási lista" - -#: lib\pwiki\DocPages.py:2328 -msgid "Wiki spell list" -msgstr "Wiki helyesírási lista" - -#: lib\pwiki\DocPages.py:2329 -msgid "Global cc. blacklist" -msgstr "Globális cc. feketelista" - -#: lib\pwiki\DocPages.py:2330 -msgid "Wiki cc. blacklist" -msgstr "Wiki cc. feketelista" - -#: lib\pwiki\DocPages.py:2331 -msgid "Favorite wikis" -msgstr "Kedvenc wikik" - -#: lib\pwiki\Exporters.py:371 -msgid "One HTML page" -msgstr "Egy HTML lap" - -#: lib\pwiki\Exporters.py:372 -msgid "Set of HTML pages" -msgstr "Wiki HTML-lapsorozatként" - -#: lib\pwiki\Exporters.py:766 -#: lib\pwiki\Exporters.py:855 -msgid "Exporting %s" -msgstr "%s exportálása" - -#: lib\pwiki\Exporters.py:1623 -msgid "
[Allow evaluation of insertions in \"Options\", page \"Security\", option \"Process insertion scripts\"]
" -msgstr "
[Engedélyezi a beillesztések kiértékelését ebben \"Opciók\", page \"Biztonság\", option \"Beillesztett szkriptek feldolgozása\"]
" - -#: lib\pwiki\Exporters.py:2157 -msgid "[Unknown parser node with name \"%s\" found]" -msgstr "[Ismeretlen elemző csomópont \"%s\" ]" - -#: lib\pwiki\Exporters.py:2191 -msgid "Set of *.wiki files" -msgstr ".wiki fájlok sorozata" - -#: lib\pwiki\Exporters.py:2376 -#: lib\pwiki\Importers.py:61 -msgid "Multipage text" -msgstr "Többlapos szöveg" - -#: lib\pwiki\Exporters.py:2389 -#: lib\pwiki\Importers.py:74 -msgid "Multipage files (*.mpt)" -msgstr "Többlapos fájlok (*.mpt)" - -#: lib\pwiki\Exporters.py:2390 -#: lib\pwiki\Importers.py:75 -msgid "Text file (*.txt)" -msgstr "Szövegfájl (*.txt)" - -#: lib\pwiki\Exporters.py:2556 -msgid "No usable separator found" -msgstr "Nincs használható elválasztó" - -#: lib\pwiki\GtkHacks.py:290 -#: lib\pwiki\WindowsHacks.py:804 -msgid "Only a real wiki page can be a clipboard catcher" -msgstr "Csak érvényes wikilap lehet vágólapbeolvasó" - -#: lib\pwiki\Importers.py:171 -msgid "Opening import file failed" -msgstr "Importfájl megnyitása sikertelen" - -#: lib\pwiki\Importers.py:202 -#: lib\pwiki\Importers.py:217 -msgid "Bad file format, header not detected" -msgstr "Rossz fájlformátum, fejléc nem található" - -#: lib\pwiki\Importers.py:210 -msgid "File format number %i is not supported" -msgstr "%i fájlformátum-szám nem támogatott" - -#: lib\pwiki\Importers.py:465 -msgid "Bad wiki word: %s, %s" -msgstr "Rossz wikiszó %s" - -#: lib\pwiki\LogWindow.py:80 -msgid "Message" -msgstr "Üzenet" - -#: lib\pwiki\MainApp.py:139 -msgid "Error initializing environment, couldn't locate global config directory" -msgstr "Hiba a környezet inicializálásakor, a globális konfigurációs könyvtár nem található" - -#: lib\pwiki\MainApp.py:247 -msgid "" -"Invalid AppLock.lock file.\n" -"Ensure that WikidPad is not running,\n" -"then delete file \"%s\" if present yet.\n" -msgstr "" -"Érvénytelen AppLock.lock fájl.\n" -"Győződjön meg, hogy a WikidPad nem fut,\n" -"majd törölje a következő fájlt \"%s\" ha még létezik.\n" - -#: lib\pwiki\MainApp.py:305 -msgid "" -"Other WikidPad process(es) seem(s) to run already\n" -"Process identifier(s): %s\n" -"Continue?" -msgstr "" -"Úgy tűnik másik wiki processz(ek) már fut(nak)\n" -"Processz azonosító(k): %s\n" -"Folytatja?" - -#: lib\pwiki\MainApp.py:307 -msgid "Continue?" -msgstr "Folytatja?" - -#: lib\pwiki\MainApp.py:538 -msgid "" -"An error occurred during this session\n" -"See file %s" -msgstr "" -"Hiba következett be ebben a szakaszban\n" -"Lásd a(z) %s fájlt" - -#: lib\pwiki\MainApp.py:688 -msgid "Plugin options" -msgstr "Beépülő opciók" - -#: lib\pwiki\MainApp.py:737 -#: lib\pwiki\OptionsDialog.py:671 -msgid "Wiki language" -msgstr "Wikinyelv" - -#: lib\pwiki\MainAreaPanel.py:665 -#: lib\pwiki\WikiTxtCtrl.py:2152 -msgid "This can only be done for the page of a wiki word" -msgstr "Csak wikiszót tartalmazó lapra alkalmazható" - -#: lib\pwiki\MainAreaPanel.py:666 -#: lib\pwiki\WikiTxtCtrl.py:2153 -msgid "Not a wiki page" -msgstr "Nem Wikilap" - -#: lib\pwiki\MptImporterGui.py:172 -msgid "Wiki page" -msgstr "Wikilap" - -#: lib\pwiki\MptImporterGui.py:173 -msgid "Func. page" -msgstr "Funkc. lap" - -#: lib\pwiki\MptImporterGui.py:174 -msgid "Saved search" -msgstr "Mentett keresések" - -#: lib\pwiki\MptImporterGui.py:191 -msgid "Type" -msgstr "Típus" - -#: lib\pwiki\MptImporterGui.py:192 -msgid "Name" -msgstr "Név" - -#: lib\pwiki\MptImporterGui.py:194 -msgid "" -"Version\n" -"Import" -msgstr "" -"Verzió\n" -"Import" - -#: lib\pwiki\MptImporterGui.py:195 -msgid "" -"Rename\n" -"imported" -msgstr "" -"Átnevez\n" -"importált" - -#: lib\pwiki\MptImporterGui.py:196 -msgid "" -"Rename\n" -"present" -msgstr "" -"Átnevez\n" -"jelenlegi" - -#: lib\pwiki\MptImporterGui.py:381 -msgid "You can't rename imported and present item at the same time" -msgstr "Nem nevezhet át importált és megjelenített elemet egy időben" - -#: lib\pwiki\MptImporterGui.py:394 -msgid "Rename imported" -msgstr "Átnevez importált" - -#: lib\pwiki\MptImporterGui.py:403 -msgid "Rename present" -msgstr "Átnevez jelenlegi" - -#: lib\pwiki\MptImporterGui.py:414 -#: lib\pwiki\MptImporterGui.py:438 -msgid "Name collision: Item '%s' will be imported already" -msgstr "Névütközés: '%s' elem lesz importálva (már?)" - -#: lib\pwiki\MptImporterGui.py:420 -#: lib\pwiki\MptImporterGui.py:449 -msgid "Name collision: Item '%s' will already be created by renaming '%s'" -msgstr "Névütközés: '%s' készül (már?) '%s' átnevezéssel" - -#: lib\pwiki\MptImporterGui.py:429 -#: lib\pwiki\MptImporterGui.py:443 -msgid "Name collision: Item '%s' exists already in database" -msgstr "Névütközés: '%s' elem már szerepel az adatbázisban" - -#: lib\pwiki\OptionsDialog.py:652 -msgid "Application" -msgstr "Program" - -#: lib\pwiki\OptionsDialog.py:653 -msgid "User interface" -msgstr "Alkalmazói felület" - -#: lib\pwiki\OptionsDialog.py:654 -msgid "Security" -msgstr "Biztonság" - -#: lib\pwiki\OptionsDialog.py:655 -msgid "Tree" -msgstr "Fa" - -#: lib\pwiki\OptionsDialog.py:656 -msgid "HTML preview/export" -msgstr "HTML előnézet/export" - -#: lib\pwiki\OptionsDialog.py:657 -msgid "HTML header" -msgstr "HTML-fejléc" - -#: lib\pwiki\OptionsDialog.py:658 -msgid "Editor" -msgstr "Szerkesztő" - -#: lib\pwiki\OptionsDialog.py:659 -msgid "Editor Colors" -msgstr "A Szerkesztő színei" - -#: lib\pwiki\OptionsDialog.py:660 -msgid "Clipboard Catcher" -msgstr "Vágólapbeolvasó" - -#: lib\pwiki\OptionsDialog.py:661 -msgid "File Launcher" -msgstr "Fájlindító" - -#: lib\pwiki\OptionsDialog.py:662 -msgid "Mouse" -msgstr "Egér" - -#: lib\pwiki\OptionsDialog.py:663 -msgid "Chron. view" -msgstr "Időrendi nézet" - -#: lib\pwiki\OptionsDialog.py:664 -msgid "Searching" -msgstr "Keres" - -#: lib\pwiki\OptionsDialog.py:665 -#: lib\pwiki\OptionsDialog.py:673 -msgid "Advanced" -msgstr "Haladó" - -#: lib\pwiki\OptionsDialog.py:666 -msgid "Autosave" -msgstr "Automatikus mentés" - -#: lib\pwiki\OptionsDialog.py:668 -msgid "Current Wiki" -msgstr "Aktuális Wiki" - -#: lib\pwiki\OptionsDialog.py:669 -msgid "Headings" -msgstr "Fejlécek" - -#: lib\pwiki\OptionsDialog.py:938 -msgid "Wave files (*.wav)|*.wav" -msgstr "Hangfájlok (*.wav)|*wav" - -#: lib\pwiki\OptionsDialog.py:949 -#: lib\pwiki\OptionsDialog.py:1239 -msgid "All files (*.*)|*" -msgstr "Minden fájl (*.*)|*" - -#: lib\pwiki\OptionsDialog.py:1229 -msgid "Select Directory" -msgstr "Könyvtár kiválasztása" - -#: lib\pwiki\OptionsDialog.py:1237 -msgid "Select File" -msgstr "Fájl kiválasztása" - -#: lib\pwiki\PersonalWikiFrame.py:168 -msgid "Bad formatted command line." -msgstr "Rossz formátumú parancssor." - -#: lib\pwiki\PersonalWikiFrame.py:381 -msgid "Wiki doesn't exist: %s" -msgstr "Nemlétező Wiki: %s" - -#: lib\pwiki\PersonalWikiFrame.py:387 -msgid "Last wiki doesn't exist: %s" -msgstr "Az utolsó wiki nem létezik: %s" - -#: lib\pwiki\PersonalWikiFrame.py:693 -msgid "&New" -msgstr "Új" - -#: lib\pwiki\PersonalWikiFrame.py:694 -msgid "Create new wiki" -msgstr "Új wiki létrehozása" - -#: lib\pwiki\PersonalWikiFrame.py:697 -msgid "&Open" -msgstr "Megnyitás" - -#: lib\pwiki\PersonalWikiFrame.py:699 -msgid "In &This Window..." -msgstr "Ebben az ablakban..." - -#: lib\pwiki\PersonalWikiFrame.py:701 -msgid "Open wiki in this window" -msgstr "Wiki nyitása ebben az ablakban" - -#: lib\pwiki\PersonalWikiFrame.py:703 -msgid "In &New Window..." -msgstr "Új ablakban" - -#: lib\pwiki\PersonalWikiFrame.py:705 -msgid "Open wiki in a new window" -msgstr "Wiki megnyitása új ablakban" - -#: lib\pwiki\PersonalWikiFrame.py:707 -msgid "&Current in New Window" -msgstr "Aktuális új ablakba" - -#: lib\pwiki\PersonalWikiFrame.py:709 -msgid "Create new window for same wiki" -msgstr "Új ablak ugyanennek a wikinek" - -#: lib\pwiki\PersonalWikiFrame.py:714 -msgid "&Recent" -msgstr "Előzmények" - -#: lib\pwiki\PersonalWikiFrame.py:721 -msgid "F&avorites" -msgstr "Kedvencek" - -#: lib\pwiki\PersonalWikiFrame.py:727 -msgid "&Search Wiki..." -msgstr "keresés Wikiben..." - -#: lib\pwiki\PersonalWikiFrame.py:728 -msgid "Search whole wiki" -msgstr "Keresés teljes wikiben" - -#: lib\pwiki\PersonalWikiFrame.py:736 -msgid "Publish as HTML" -msgstr "Közzététel HTML-ként" - -#: lib\pwiki\PersonalWikiFrame.py:739 -msgid "Wiki as Single HTML Page" -msgstr "Wiki egyetlen HTML-lapként" - -#: lib\pwiki\PersonalWikiFrame.py:740 -msgid "Publish Wiki as Single HTML Page" -msgstr "Wiki közzététele egyetlen HTML lapként" - -#: lib\pwiki\PersonalWikiFrame.py:744 -msgid "Wiki as Set of HTML Pages" -msgstr "Wiki HTML-lapsorozatként" - -#: lib\pwiki\PersonalWikiFrame.py:745 -msgid "Publish Wiki as Set of HTML Pages" -msgstr "Wiki közzététele HTML-lapsorozatként" - -#: lib\pwiki\PersonalWikiFrame.py:749 -msgid "Current Wiki Word as HTML Page" -msgstr "Aktuális wikiszó HTML-lapként" - -#: lib\pwiki\PersonalWikiFrame.py:750 -msgid "Publish Current Wiki Word as HTML Page" -msgstr "Aktuális Wikiszó közzététele HTML-lapként" - -#: lib\pwiki\PersonalWikiFrame.py:754 -msgid "Sub-Tree as Single HTML Page" -msgstr "Alárendelt fa egyetlen HTML-lapként" - -#: lib\pwiki\PersonalWikiFrame.py:755 -msgid "Publish Sub-Tree as Single HTML Page" -msgstr "Alárendelt fa közzététele egyetlen HTML-lapként" - -#: lib\pwiki\PersonalWikiFrame.py:759 -msgid "Sub-Tree as Set of HTML Pages" -msgstr "Alárendelt fa HTML-lapsorozatként" - -#: lib\pwiki\PersonalWikiFrame.py:760 -msgid "Publish Sub-Tree as Set of HTML Pages" -msgstr "Alárendelt fa közzététele HTML-lapsorozatként" - -#: lib\pwiki\PersonalWikiFrame.py:768 -msgid "Other Export..." -msgstr "Export másként..." - -#: lib\pwiki\PersonalWikiFrame.py:769 -#: lib\pwiki\PersonalWikiFrame.py:1781 -msgid "Open general export dialog" -msgstr "Általános export párbeszédablak nyitása" - -#: lib\pwiki\PersonalWikiFrame.py:773 -msgid "Print..." -msgstr "Nyomtat..." - -#: lib\pwiki\PersonalWikiFrame.py:774 -msgid "Show the print dialog" -msgstr "Nyomtatás párbeszédablak megnyitása" - -#: lib\pwiki\PersonalWikiFrame.py:779 -msgid "&Properties..." -msgstr "Tulajdonságok......" - -#: lib\pwiki\PersonalWikiFrame.py:780 -msgid "Show general information about current wiki" -msgstr "Általános információk megjelenítése az aktuális wikiről" - -#: lib\pwiki\PersonalWikiFrame.py:784 -msgid "Maintenance" -msgstr "Karbantartás" - -#: lib\pwiki\PersonalWikiFrame.py:788 -msgid "&Rebuild Wiki..." -msgstr "Wiki újraépítése..." - -#: lib\pwiki\PersonalWikiFrame.py:789 -msgid "Rebuild this wiki and its cache completely" -msgstr "Aktuális wiki és caché teljes újraépítése" - -#: lib\pwiki\PersonalWikiFrame.py:803 -msgid "&Update cache..." -msgstr "Cache frissítése" - -#: lib\pwiki\PersonalWikiFrame.py:804 -msgid "Update cache where marked as not up to date" -msgstr "Elavultként jelőlt caché frissítése" - -#: lib\pwiki\PersonalWikiFrame.py:809 -msgid "&Initiate update..." -msgstr "Frissítés indítása..." - -#: lib\pwiki\PersonalWikiFrame.py:810 -msgid "Initiate full cache update which is done mainly in background" -msgstr "Alapvetően a háttérben történő cachéfrissítés indítása" - -#: lib\pwiki\PersonalWikiFrame.py:817 -msgid "Show job count..." -msgstr "Munkafolyamat-számláló megjelenítése..." - -#: lib\pwiki\PersonalWikiFrame.py:818 -msgid "Show how many update jobs are waiting in background" -msgstr "Mutassa a háttérben várakozó frissítési faladatok számát" - -#: lib\pwiki\PersonalWikiFrame.py:823 -msgid "Open as &Type..." -msgstr "Nyissa meg ... típusként" - -#: lib\pwiki\PersonalWikiFrame.py:824 -msgid "Open wiki with a specified wiki database type" -msgstr "Wiki megnyitása meghatározott wiki adatbázistípusként" - -#: lib\pwiki\PersonalWikiFrame.py:828 -msgid "Reconnect..." -msgstr "Újracsatol..." - -#: lib\pwiki\PersonalWikiFrame.py:829 -msgid "Reconnect to database after connection failure" -msgstr "Sikertelen kapcsolódás után az adatbázis újracsatolása" - -#: lib\pwiki\PersonalWikiFrame.py:835 -msgid "&Optimise Database" -msgstr "Adatbázis optimalizálása" - -#: lib\pwiki\PersonalWikiFrame.py:836 -msgid "Free unused space in database" -msgstr "Szabadítsa fel a használatonkívüli helyeket az adatbázisban" - -#: lib\pwiki\PersonalWikiFrame.py:843 -msgid "&Copy .wiki files to database" -msgstr ".wiki fájlok másolása adatbázisba" - -#: lib\pwiki\PersonalWikiFrame.py:844 -msgid "Copy .wiki files to database" -msgstr ".wiki fájlok másolása adatbázisba" - -#: lib\pwiki\PersonalWikiFrame.py:854 -msgid "E&xit" -msgstr "Kilép" - -#: lib\pwiki\PersonalWikiFrame.py:854 -#: lib\pwiki\PersonalWikiFrame.py:5642 -msgid "Exit" -msgstr "Kilép" - -#: lib\pwiki\PersonalWikiFrame.py:1022 -msgid "Reread text blocks" -msgstr "Szövegblokkok újraolvasása" - -#: lib\pwiki\PersonalWikiFrame.py:1023 -msgid "Reread the text block file(s) and recreate menu" -msgstr "Szövegblokkfájlok újraolvasása és a menű újraépítése" - -#: lib\pwiki\PersonalWikiFrame.py:1077 -msgid "Add wiki" -msgstr "Wiki hozzáadás" - -#: lib\pwiki\PersonalWikiFrame.py:1078 -msgid "Add a wiki to the favorites" -msgstr "Wiki a kedevencekhez" - -#: lib\pwiki\PersonalWikiFrame.py:1083 -#: lib\pwiki\PersonalWikiFrame.py:1084 -msgid "Manage favorites" -msgstr "Kedvencek kezelése" - -#: lib\pwiki\PersonalWikiFrame.py:1106 -#: lib\pwiki\PersonalWikiFrame.py:1831 -#: lib\pwiki\PersonalWikiFrame.py:2355 -#: lib\pwiki\PersonalWikiFrame.py:4901 -#: lib\pwiki\PersonalWikiFrame.py:5219 -msgid "Error while starting new WikidPad instance" -msgstr "Hiba új WikidPad példány indításakor" - -#: lib\pwiki\PersonalWikiFrame.py:1208 -msgid "&Undo" -msgstr "Visszavon" - -#: lib\pwiki\PersonalWikiFrame.py:1212 -msgid "&Redo" -msgstr "Ismét" - -#: lib\pwiki\PersonalWikiFrame.py:1220 -msgid "&Search and Replace..." -msgstr "Keresés és csere..." - -#: lib\pwiki\PersonalWikiFrame.py:1222 -msgid "Search and replace inside current page" -msgstr "Keresés és csere az aktuális lapon" - -#: lib\pwiki\PersonalWikiFrame.py:1228 -msgid "Cu&t" -msgstr "Kivág" - -#: lib\pwiki\PersonalWikiFrame.py:1233 -msgid "&Copy" -msgstr "Másol" - -#: lib\pwiki\PersonalWikiFrame.py:1237 -msgid "&Paste" -msgstr "Beilleszt" - -#: lib\pwiki\PersonalWikiFrame.py:1242 -msgid "Select &All" -msgstr "Mindent kiválaszt" - -#: lib\pwiki\PersonalWikiFrame.py:1248 -msgid "Copy to Sc&ratchPad" -msgstr "Másolás a Vázlatfüzetbe" - -#: lib\pwiki\PersonalWikiFrame.py:1250 -msgid "Copy selected text to ScratchPad" -msgstr "Kiválasztott szöveg másolása a Vázlatfüzetbe" - -#: lib\pwiki\PersonalWikiFrame.py:1256 -msgid "Paste T&extblock" -msgstr "Szövegblokkok" - -#: lib\pwiki\PersonalWikiFrame.py:1264 -msgid "C&lipboard Catcher" -msgstr "Vágólapbeolvasó" - -#: lib\pwiki\PersonalWikiFrame.py:1267 -#: lib\pwiki\PersonalWikiFrame.py:4125 -msgid "Set at Page" -msgstr "Elhelyezés lapon" - -#: lib\pwiki\PersonalWikiFrame.py:1269 -msgid "Text copied to clipboard is also appended to this page" -msgstr "A szöveg vágólapra másolva és a laphoz is hozzáfűzve" - -#: lib\pwiki\PersonalWikiFrame.py:1275 -msgid "Set at Cursor" -msgstr "Elhelyez a kurzornál" - -#: lib\pwiki\PersonalWikiFrame.py:1277 -msgid "Text copied to clipboard is also added to cursor position" -msgstr "A vágólapra másolt szöveg ehhez a kurzorpozícióhoz is hozzáfűzve" - -#: lib\pwiki\PersonalWikiFrame.py:1283 -msgid "Set Off" -msgstr "Kikapcsol" - -#: lib\pwiki\PersonalWikiFrame.py:1285 -msgid "Switch off clipboard catcher" -msgstr "Vágólapbeolvasó kikapcsolása" - -#: lib\pwiki\PersonalWikiFrame.py:1295 -msgid "Spell Check..." -msgstr "Helyesírásellenőrzés..." - -#: lib\pwiki\PersonalWikiFrame.py:1297 -msgid "Spell check current and possibly further pages" -msgstr "Helyesírásellenőrzés az aktuális és további lapokon" - -#: lib\pwiki\PersonalWikiFrame.py:1301 -msgid "Spell Check While Type" -msgstr "Helyesírás ellenőrzése gépelés közben" - -#: lib\pwiki\PersonalWikiFrame.py:1302 -msgid "Set if editor should do spell checking during typing" -msgstr "Állítsa be, ha a szerkesztő gépelés közben ellenőrizze a nyelvhelyességet" - -#: lib\pwiki\PersonalWikiFrame.py:1307 -msgid "Clear Ignore List" -msgstr "Kivételek lista törlése" - -#: lib\pwiki\PersonalWikiFrame.py:1309 -msgid "Clear the list of words to ignore for spell check while type" -msgstr "Gépelés közben a helyesírás-ellenőrzésből kizártak listáját törölje" - -#: lib\pwiki\PersonalWikiFrame.py:1318 -msgid "&Insert" -msgstr "Beszúr" - -#: lib\pwiki\PersonalWikiFrame.py:1320 -msgid "&File URL..." -msgstr "File URL..." - -#: lib\pwiki\PersonalWikiFrame.py:1321 -msgid "Use file dialog to add URL" -msgstr "Fájl párbeszédablak használata URL hozzáadásához" - -#: lib\pwiki\PersonalWikiFrame.py:1326 -msgid "Current &Date" -msgstr "Aktuális dátum" - -#: lib\pwiki\PersonalWikiFrame.py:1327 -msgid "Insert current date" -msgstr "Aktuális dátum beszúrása" - -#: lib\pwiki\PersonalWikiFrame.py:1336 -msgid "&Settings" -msgstr "Beállítások" - -#: lib\pwiki\PersonalWikiFrame.py:1339 -msgid "&Date Format..." -msgstr "Dátumformátum..." - -#: lib\pwiki\PersonalWikiFrame.py:1340 -msgid "Set date format for inserting current date" -msgstr "Dátumformátum beállítása az aktuális dátum beszúrásához" - -#: lib\pwiki\PersonalWikiFrame.py:1344 -msgid "Auto-&Wrap" -msgstr "Automatikus tördelés" - -#: lib\pwiki\PersonalWikiFrame.py:1345 -msgid "Set if editor should wrap long lines" -msgstr "Állítsa be, hogy a szerkesztő tördelje-e a hosszú sorokat" - -#: lib\pwiki\PersonalWikiFrame.py:1359 -msgid "Auto-&Indent" -msgstr "Automatikus behúzás" - -#: lib\pwiki\PersonalWikiFrame.py:1360 -msgid "Auto indentation" -msgstr "Automatikus behúzás" - -#: lib\pwiki\PersonalWikiFrame.py:1373 -msgid "Auto-&Bullets" -msgstr "Automatikus felsorolás" - -#: lib\pwiki\PersonalWikiFrame.py:1374 -msgid "Show bullet on next line if current has one" -msgstr "Következő sorban mutassa a felsorolásjelet, ha az aktuálisnak van" - -#: lib\pwiki\PersonalWikiFrame.py:1389 -msgid "Tabs to spaces" -msgstr "Tabulátorokat szóközökre" - -#: lib\pwiki\PersonalWikiFrame.py:1390 -msgid "Write spaces when hitting TAB key" -msgstr "TAB lenyomásakor szóközöket ír" - -#: lib\pwiki\PersonalWikiFrame.py:1408 -msgid "Show T&oolbar" -msgstr "Eszköztár megjelenítése" - -#: lib\pwiki\PersonalWikiFrame.py:1410 -msgid "Show toolbar" -msgstr "Eszköztár megjelenítése" - -#: lib\pwiki\PersonalWikiFrame.py:1417 -msgid "Show &Tree View" -msgstr "Fanézet megjelenítése" - -#: lib\pwiki\PersonalWikiFrame.py:1419 -msgid "Show Tree Control" -msgstr "Favezérlők megjelenítése" - -#: lib\pwiki\PersonalWikiFrame.py:1426 -msgid "Show &Chron. View" -msgstr "Időrendi nézet megjelenítése" - -#: lib\pwiki\PersonalWikiFrame.py:1428 -msgid "Show chronological view" -msgstr "Időrendi nézet megjelenítése" - -#: lib\pwiki\PersonalWikiFrame.py:1435 -msgid "Show &Page Structure" -msgstr "Lapszerkezet megjelenítése" - -#: lib\pwiki\PersonalWikiFrame.py:1437 -msgid "Show structure (headings) of the page" -msgstr "Lapszerkezet (fejlécek) megjelenítése" - -#: lib\pwiki\PersonalWikiFrame.py:1449 -msgid "Show &Indentation Guides" -msgstr "Behúzásjelölők megjelenítése" - -#: lib\pwiki\PersonalWikiFrame.py:1450 -msgid "Show indentation guides in editor" -msgstr "Behúzásjelölők megjelenítése a szerkesztőben" - -#: lib\pwiki\PersonalWikiFrame.py:1455 -msgid "Show Line &Numbers" -msgstr "Sorok számát mutatja" - -#: lib\pwiki\PersonalWikiFrame.py:1456 -msgid "Show line numbers in editor" -msgstr "Sorok számát mutatja a szerkesztőben" - -#: lib\pwiki\PersonalWikiFrame.py:1463 -msgid "Stay on Top" -msgstr "Maradjon felül" - -#: lib\pwiki\PersonalWikiFrame.py:1465 -msgid "Stay on Top of all other windows" -msgstr "Maradjon az összes többi ablak fölött" - -#: lib\pwiki\PersonalWikiFrame.py:1473 -msgid "&Zoom In" -msgstr "Nagyít" - -#: lib\pwiki\PersonalWikiFrame.py:1474 -#: lib\pwiki\PersonalWikiFrame.py:2037 -msgid "Zoom In" -msgstr "Nagyít" - -#: lib\pwiki\PersonalWikiFrame.py:1477 -msgid "Zoo&m Out" -msgstr "Kicsinyít" - -#: lib\pwiki\PersonalWikiFrame.py:1478 -#: lib\pwiki\PersonalWikiFrame.py:2042 -msgid "Zoom Out" -msgstr "Kicsinyít" - -#: lib\pwiki\PersonalWikiFrame.py:1500 -msgid "Toggle Ed./Prev" -msgstr "Váltás Szerk./előnéz." - -#: lib\pwiki\PersonalWikiFrame.py:1502 -#: lib\pwiki\PersonalWikiFrame.py:2033 -msgid "Switch between editor and preview" -msgstr "Váltás szerkesztő- és előnézeti mód között" - -#: lib\pwiki\PersonalWikiFrame.py:1506 -msgid "Enter Edit Mode" -msgstr "Szerkesztőmódba ugrás" - -#: lib\pwiki\PersonalWikiFrame.py:1507 -msgid "Show editor in tab" -msgstr "Szerkesztő megjelenítése a fülön" - -#: lib\pwiki\PersonalWikiFrame.py:1511 -msgid "Enter Preview Mode" -msgstr "Váltás előnézeti módba" - -#: lib\pwiki\PersonalWikiFrame.py:1513 -msgid "Show preview in tab" -msgstr "Előnézetet a fülön" - -#: lib\pwiki\PersonalWikiFrame.py:1537 -msgid "&Save" -msgstr "Ment" - -#: lib\pwiki\PersonalWikiFrame.py:1538 -msgid "Save all open pages" -msgstr "Összes nyitott lap mentése" - -#: lib\pwiki\PersonalWikiFrame.py:1545 -msgid "&Rename" -msgstr "Átnevez" - -#: lib\pwiki\PersonalWikiFrame.py:1546 -msgid "Rename current wiki word" -msgstr "Aktuális wikiszó átnevezése" - -#: lib\pwiki\PersonalWikiFrame.py:1552 -msgid "Delete current wiki word" -msgstr "Aktuális wikiszó törlése" - -#: lib\pwiki\PersonalWikiFrame.py:1559 -msgid "Set as Roo&t" -msgstr "Gyökérnek beállít" - -#: lib\pwiki\PersonalWikiFrame.py:1560 -msgid "Set current wiki word as tree root" -msgstr "Aktuális wikiszó beállítása fa gyökereként" - -#: lib\pwiki\PersonalWikiFrame.py:1564 -msgid "R&eset Root" -msgstr "Gyökér alaphelyzetbe" - -#: lib\pwiki\PersonalWikiFrame.py:1565 -msgid "Set home wiki word as tree root" -msgstr "Alap wikiszó beállítása fa gyökereként" - -#: lib\pwiki\PersonalWikiFrame.py:1569 -msgid "S&ynchronise Tree" -msgstr "Fa szinkronizálása" - -#: lib\pwiki\PersonalWikiFrame.py:1570 -msgid "Find the current wiki word in the tree" -msgstr "Aktuális wikiszó megkeresése a fában" - -#: lib\pwiki\PersonalWikiFrame.py:1575 -msgid "&Follow Link" -msgstr "Link követése" - -#: lib\pwiki\PersonalWikiFrame.py:1576 -msgid "Activate link/word" -msgstr "Szó/link érvényesítése" - -#: lib\pwiki\PersonalWikiFrame.py:1581 -msgid "Follow Link in &New Tab" -msgstr "Link követése új fülön" - -#: lib\pwiki\PersonalWikiFrame.py:1582 -msgid "Activate link/word in new tab" -msgstr "Szó/link érvényesítése új fülön" - -#: lib\pwiki\PersonalWikiFrame.py:1587 -msgid "Copy &URL to Clipboard" -msgstr "URL másolása vágólapra" - -#: lib\pwiki\PersonalWikiFrame.py:1589 -msgid "Copy full \"wiki:\" URL of the word to clipboard" -msgstr "A szó teljes \"wiki:\" URL-jének másolása a vágólapra" - -#: lib\pwiki\PersonalWikiFrame.py:1595 -msgid "&Add version" -msgstr "Verzió hozzáadása" - -#: lib\pwiki\PersonalWikiFrame.py:1596 -msgid "Add new version" -msgstr "Új verzió hozzáadása" - -#: lib\pwiki\PersonalWikiFrame.py:1604 -msgid "&Bold" -msgstr "Vastag" - -#: lib\pwiki\PersonalWikiFrame.py:1605 -#: lib\pwiki\PersonalWikiFrame.py:2018 -msgid "Bold" -msgstr "Vastag" - -#: lib\pwiki\PersonalWikiFrame.py:1611 -msgid "&Italic" -msgstr "Dőlt" - -#: lib\pwiki\PersonalWikiFrame.py:1612 -#: lib\pwiki\PersonalWikiFrame.py:2024 -msgid "Italic" -msgstr "Dőlt" - -#: lib\pwiki\PersonalWikiFrame.py:1618 -msgid "&Heading" -msgstr "Fejléc" - -#: lib\pwiki\PersonalWikiFrame.py:1619 -msgid "Add Heading" -msgstr "Fejléc hozzáadása" - -#: lib\pwiki\PersonalWikiFrame.py:1627 -msgid "&Rewrap Text" -msgstr "Szöveg újratördelése" - -#: lib\pwiki\PersonalWikiFrame.py:1629 -msgid "Rewrap Text" -msgstr "Szöveg újratördelése" - -#: lib\pwiki\PersonalWikiFrame.py:1634 -msgid "&Convert" -msgstr "Átalakítás" - -#: lib\pwiki\PersonalWikiFrame.py:1637 -msgid "Selection to &Link" -msgstr "Kijelölést link-ké" - -#: lib\pwiki\PersonalWikiFrame.py:1638 -msgid "Remove non-allowed characters and make sel. a wiki word link" -msgstr "Érvénytelen karakterek eltávolítása és kijelölésből wikiszóhivatkozást készítése" - -#: lib\pwiki\PersonalWikiFrame.py:1643 -msgid "Selection to &Wiki Word" -msgstr "Kijelölést Wikiszóra" - -#: lib\pwiki\PersonalWikiFrame.py:1645 -msgid "Put selected text in a new or existing wiki word" -msgstr "A kijelölt szöveget helyezze új, vagy meglévő wikiszóba" - -#: lib\pwiki\PersonalWikiFrame.py:1650 -msgid "Absolute/Relative &File URL" -msgstr "Abszolút/relatív fájl URL" - -#: lib\pwiki\PersonalWikiFrame.py:1652 -msgid "Convert file URL from absolute to relative and vice versa" -msgstr "Fájl URL átalakítása abszolútból relatívba és vissza" - -#: lib\pwiki\PersonalWikiFrame.py:1666 -msgid "&Icon Name" -msgstr "Ikon neve" - -#: lib\pwiki\PersonalWikiFrame.py:1678 -msgid "&Color Name" -msgstr "Szín neve" - -#: lib\pwiki\PersonalWikiFrame.py:1686 -msgid "&Add Attribute" -msgstr "Attribútum hozzáadása" - -#: lib\pwiki\PersonalWikiFrame.py:1695 -msgid "&Icon Attribute" -msgstr "Ikonjellemző" - -#: lib\pwiki\PersonalWikiFrame.py:1705 -msgid "&Color Attribute" -msgstr "Színjellemző" - -#: lib\pwiki\PersonalWikiFrame.py:1714 -msgid "&Back" -msgstr "Vissza" - -#: lib\pwiki\PersonalWikiFrame.py:1715 -msgid "Go backward" -msgstr "Visszalép" - -#: lib\pwiki\PersonalWikiFrame.py:1718 -msgid "&Forward" -msgstr "Előre" - -#: lib\pwiki\PersonalWikiFrame.py:1719 -msgid "Go forward" -msgstr "Előrelép" - -#: lib\pwiki\PersonalWikiFrame.py:1723 -msgid "&Wiki Home" -msgstr "Wiki indulólap" - -#: lib\pwiki\PersonalWikiFrame.py:1724 -msgid "Go to wiki homepage" -msgstr "Ugrás a wiki indulólaphoz" - -#: lib\pwiki\PersonalWikiFrame.py:1731 -msgid "Go to &Page..." -msgstr "Ugrás lapra..." - -#: lib\pwiki\PersonalWikiFrame.py:1732 -msgid "Open wiki word" -msgstr "Wikiszó megnyitása" - -#: lib\pwiki\PersonalWikiFrame.py:1737 -msgid "Go to P&arent..." -msgstr "Ugrás a felmenőhöz..." - -#: lib\pwiki\PersonalWikiFrame.py:1739 -msgid "List parents of current wiki word" -msgstr "Aktuális wikiszó felmenőinek listázása" - -#: lib\pwiki\PersonalWikiFrame.py:1742 -msgid "List &Children..." -msgstr "Leszármazottak listázása..." - -#: lib\pwiki\PersonalWikiFrame.py:1744 -msgid "List children of current wiki word" -msgstr "Aktuális wikiszó leszármazottainak listázása" - -#: lib\pwiki\PersonalWikiFrame.py:1747 -msgid "List Pa&rentless Pages" -msgstr "Felmenő nélküli lapok listázása" - -#: lib\pwiki\PersonalWikiFrame.py:1749 -msgid "List nodes with no parent relations" -msgstr "Felmenői kapcsolat nélküli csomópontok listázása" - -#: lib\pwiki\PersonalWikiFrame.py:1754 -msgid "Show &History..." -msgstr "Előzmények megjelenítése..." - -#: lib\pwiki\PersonalWikiFrame.py:1755 -msgid "View tab history" -msgstr "Fül előzményeinek megjelenítése" - -#: lib\pwiki\PersonalWikiFrame.py:1758 -msgid "&Up History..." -msgstr "Előzményekben fel..." - -#: lib\pwiki\PersonalWikiFrame.py:1759 -msgid "Up in tab history" -msgstr "Fel a fül előzményeiben" - -#: lib\pwiki\PersonalWikiFrame.py:1762 -msgid "&Down History..." -msgstr "Előzményekben le..." - -#: lib\pwiki\PersonalWikiFrame.py:1763 -msgid "Down in tab history" -msgstr "A fül előzményeiben le" - -#: lib\pwiki\PersonalWikiFrame.py:1768 -msgid "Add B&ookmark" -msgstr "Könyvjelző hozzáadása" - -#: lib\pwiki\PersonalWikiFrame.py:1769 -msgid "Add bookmark to page" -msgstr "Könyvjelző a laphoz" - -#: lib\pwiki\PersonalWikiFrame.py:1773 -msgid "Go to &Bookmark..." -msgstr "Ugrás a könyvjelzőhöz..." - -#: lib\pwiki\PersonalWikiFrame.py:1774 -msgid "List bookmarks" -msgstr "Könyvjelzők listázása" - -#: lib\pwiki\PersonalWikiFrame.py:1780 -msgid "&Export..." -msgstr "Export..." - -#: lib\pwiki\PersonalWikiFrame.py:1784 -msgid "&Continuous Export..." -msgstr "Folyamatos export..." - -#: lib\pwiki\PersonalWikiFrame.py:1785 -msgid "Open export dialog for continuous export of changes" -msgstr "Exportpárbeszéd nyitása a változások folyamatos exportjához" - -#: lib\pwiki\PersonalWikiFrame.py:1791 -msgid "&Import..." -msgstr "Import..." - -#: lib\pwiki\PersonalWikiFrame.py:1792 -msgid "Import dialog" -msgstr "Importpárbeszéd" - -#: lib\pwiki\PersonalWikiFrame.py:1798 -msgid "Scripts" -msgstr "Szkriptek" - -#: lib\pwiki\PersonalWikiFrame.py:1799 -msgid "Run scripts, evaluate expressions" -msgstr "Szkriptek futtatása, kifejezések kiértékelése" - -#: lib\pwiki\PersonalWikiFrame.py:1801 -msgid "&Eval" -msgstr "Kiértékelés" - -#: lib\pwiki\PersonalWikiFrame.py:1802 -msgid "Evaluate script blocks" -msgstr "Szkriptblokkok kiértékelése" - -#: lib\pwiki\PersonalWikiFrame.py:1807 -msgid "Run Function &%i" -msgstr "&%i függvény futtatása" - -#: lib\pwiki\PersonalWikiFrame.py:1808 -msgid "Run script function %i" -msgstr "%i szkriptfüggvény futtatása" - -#: lib\pwiki\PersonalWikiFrame.py:1813 -msgid "O&ptions..." -msgstr "Opciók..." - -#: lib\pwiki\PersonalWikiFrame.py:1814 -msgid "Set options" -msgstr "Opciók beállítása" - -#: lib\pwiki\PersonalWikiFrame.py:1835 -msgid "&Open help wiki" -msgstr "Wikihelp megnyitása" - -#: lib\pwiki\PersonalWikiFrame.py:1836 -msgid "Open WikidPadHelp, the help wiki" -msgstr "WikidPadHelp megnyitása, a wiki segítség" - -#: lib\pwiki\PersonalWikiFrame.py:1845 -msgid "&Visit Homepage" -msgstr "Honlap felkeresése" - -#: lib\pwiki\PersonalWikiFrame.py:1846 -msgid "Visit wikidPad homepage" -msgstr "WikidPad honlap felkeresése" - -#: lib\pwiki\PersonalWikiFrame.py:1856 -msgid "Show &License" -msgstr "Licensz mutatása" - -#: lib\pwiki\PersonalWikiFrame.py:1857 -msgid "Show license of WikidPad and used components" -msgstr "WikidPad és komponenseinek licensze" - -#: lib\pwiki\PersonalWikiFrame.py:1892 -msgid "&About" -msgstr "Névjegy" - -#: lib\pwiki\PersonalWikiFrame.py:1896 -msgid "&Wiki" -msgstr "Wiki" - -#: lib\pwiki\PersonalWikiFrame.py:1897 -msgid "&Edit" -msgstr "Szerkeszt" - -#: lib\pwiki\PersonalWikiFrame.py:1898 -msgid "&View" -msgstr "Nézet" - -#: lib\pwiki\PersonalWikiFrame.py:1899 -msgid "&Tabs" -msgstr "Fülek" - -#: lib\pwiki\PersonalWikiFrame.py:1900 -msgid "Wiki &Page" -msgstr "Wikilap" - -#: lib\pwiki\PersonalWikiFrame.py:1901 -msgid "&Format" -msgstr "Formázás" - -#: lib\pwiki\PersonalWikiFrame.py:1902 -msgid "&Navigate" -msgstr "Navigál" - -#: lib\pwiki\PersonalWikiFrame.py:1903 -msgid "E&xtra" -msgstr "Extra" - -#: lib\pwiki\PersonalWikiFrame.py:1919 -msgid "Pl&ugins" -msgstr "Beépülők" - -#: lib\pwiki\PersonalWikiFrame.py:1925 -msgid "&Help" -msgstr "Segítség" - -#: lib\pwiki\PersonalWikiFrame.py:1927 -msgid "He&lp" -msgstr "Segítség" - -#: lib\pwiki\PersonalWikiFrame.py:1953 -#: lib\pwiki\PersonalWikiFrame.py:1954 -msgid "Back" -msgstr "Vissza" - -#: lib\pwiki\PersonalWikiFrame.py:1959 -#: lib\pwiki\PersonalWikiFrame.py:1960 -msgid "Forward" -msgstr "Előre" - -#: lib\pwiki\PersonalWikiFrame.py:1965 -#: lib\pwiki\PersonalWikiFrame.py:1966 -msgid "Wiki Home" -msgstr "Wiki indulólap" - -#: lib\pwiki\PersonalWikiFrame.py:1980 -#: lib\pwiki\PersonalWikiFrame.py:1981 -msgid "Search" -msgstr "Keres" - -#: lib\pwiki\PersonalWikiFrame.py:1986 -#: lib\pwiki\PersonalWikiFrame.py:1987 -msgid "Find current word in tree" -msgstr "Aktuális szó megkeresése a fában" - -#: lib\pwiki\PersonalWikiFrame.py:1990 -#: lib\pwiki\PersonalWikiFrame.py:2008 -#: lib\pwiki\PersonalWikiFrame.py:2028 -msgid "Separator" -msgstr "Elválasztó" - -#: lib\pwiki\PersonalWikiFrame.py:1994 -#: lib\pwiki\PersonalWikiFrame.py:1995 -msgid "Save Wiki Word" -msgstr "Wikiszó mentése" - -#: lib\pwiki\PersonalWikiFrame.py:2005 -#: lib\pwiki\PersonalWikiFrame.py:4370 -msgid "Delete Wiki Word" -msgstr "Wikiszó törlése" - -#: lib\pwiki\PersonalWikiFrame.py:2012 -msgid "Heading" -msgstr "Fejléc" - -#: lib\pwiki\PersonalWikiFrame.py:2032 -msgid "Switch Editor/Preview" -msgstr "Szerkesztés/Előnézet váltás" - -#: lib\pwiki\PersonalWikiFrame.py:2053 -msgid "Wikize Selected Word " -msgstr "Kijelőlést Wikiszóvá" - -#: lib\pwiki\PersonalWikiFrame.py:2054 -msgid "Wikize Selected Word" -msgstr "Kijelőlést Wikiszóvá" - -#: lib\pwiki\PersonalWikiFrame.py:2123 -msgid "Line: 9999 Col: 9999 Pos: 9999999988888" -msgstr "Sor: 9999 Oszl: 9999 Poz: 9999999988888" - -#: lib\pwiki\PersonalWikiFrame.py:2292 -msgid "Regular expression error" -msgstr "Szabályos kifejezés hibája" - -#: lib\pwiki\PersonalWikiFrame.py:2301 -msgid "Are you sure you want to reconnect? You may lose some data by this process." -msgstr "Biztosan újracsatlakozik? Bizonyos adatok elveszthetnek a művelet során." - -#: lib\pwiki\PersonalWikiFrame.py:2303 -msgid "Reconnect database" -msgstr "Adatbázis újracsatolása" - -#: lib\pwiki\PersonalWikiFrame.py:2317 -#: lib\pwiki\PersonalWikiFrame.py:3345 -msgid "Error while trying to reconnect:\n" -msgstr "Hiba újracsatolás közben:\n" - -#: lib\pwiki\PersonalWikiFrame.py:2319 -msgid "" -"There was an error while reconnecting the database\n" -"\n" -"Would you like to try it again?\n" -"%s" -msgstr "" -"Hiba történt az adatbázis újracsatolása közben\n" -"\n" -"Ismét megpróbálja?\n" -"%s" - -#: lib\pwiki\PersonalWikiFrame.py:2322 -msgid "Error reconnecting!" -msgstr "Újracstolási hiba!" - -#: lib\pwiki\PersonalWikiFrame.py:2338 -#: lib\pwiki\PersonalWikiFrame.py:3392 -msgid "Error while trying to write:\n" -msgstr "Hiba írási kisérlet közben:\n" - -#: lib\pwiki\PersonalWikiFrame.py:2340 -msgid "" -"There was an error while writing to the database\n" -"\n" -"Would you like to try it again?\n" -"%s" -msgstr "" -"Hiba lépett fel az adatbáziba írás közben\n" -"\n" -"Ismét megpróbálja?\n" -"%s" - -#: lib\pwiki\PersonalWikiFrame.py:2343 -msgid "Error writing!" -msgstr "Íráshiba" - -#: lib\pwiki\PersonalWikiFrame.py:2460 -msgid "There was an error loading the icons for the tree control." -msgstr "Hiba lépett fel fa vezérlő ikonjainak betöltésekor" - -#: lib\pwiki\PersonalWikiFrame.py:2712 -msgid "No data handler available to create database." -msgstr "Nincs elérhető adatbáziskezelő az adatbázis létrehozásához" - -#: lib\pwiki\PersonalWikiFrame.py:2724 -msgid "A wiki already exists in '%s', overwrite? (This deletes everything in and below this directory!)" -msgstr "A wiki már létezik ebben: '%s'. Felülírja? (A művelet itt és az alatta lévő könyvtárakban mindent töröl)" - -#: lib\pwiki\PersonalWikiFrame.py:2726 -msgid "Warning" -msgstr "Vigyázat" - -#: lib\pwiki\PersonalWikiFrame.py:2766 -msgid "A wiki database already exists in this location, overwrite?" -msgstr "A wikiadatbázis itt már létezik. Felülírja?" - -#: lib\pwiki\PersonalWikiFrame.py:2768 -msgid "Wiki DB Exists" -msgstr "A wiki adatbázis létezik" - -#: lib\pwiki\PersonalWikiFrame.py:2779 -msgid "There was an error creating the wiki database." -msgstr "Hiba lépett fel a wikiadatbázis létrehozásakor" - -#: lib\pwiki\PersonalWikiFrame.py:2848 -#: lib\pwiki\PersonalWikiFrame.py:2849 -msgid "Choose database type" -msgstr "Válasszon adatbázistípust" - -#: lib\pwiki\PersonalWikiFrame.py:2886 -msgid "Invalid path or missing file '%s'" -msgstr "Érvénytelen útvonal, vagy hiányzó fájl '%s'" - -#: lib\pwiki\PersonalWikiFrame.py:2937 -#: lib\pwiki\PersonalWikiFrame.py:3017 -msgid "Error connecting to database in '%s'" -msgstr "Hiba az adatbázishoz csatlakozás közben itt: '%s'" - -#: lib\pwiki\PersonalWikiFrame.py:2942 -msgid "The wiki needs an update to work with this version of WikidPad. Older versions of WikidPad may be unable to read the wiki after an update." -msgstr "A WikidPad e verziójával történő munkához a wiki frissítése Szükséges. A WikidPad régebbi verziói a frissítés után esetleg képtelenek lesznek az olvasására." - -#: lib\pwiki\PersonalWikiFrame.py:2945 -msgid "Update database?" -msgstr "Adatbázis frissítése?" - -#: lib\pwiki\PersonalWikiFrame.py:2980 -msgid "" -"Wiki '%s' is probably in use by different\n" -"instance of WikidPad. Connect anyway (dangerous!)?" -msgstr "" -"'%s' wikit valószínűleg a WikidPad másik példánya\n" -" használja. Mindenképp csatlakozzon? (Veszélyes!)" - -#: lib\pwiki\PersonalWikiFrame.py:2982 -msgid "Wiki already in use" -msgstr "Wiki már használatban van" - -#: lib\pwiki\PersonalWikiFrame.py:2991 -msgid "" -"Configuration file '%s' is corrupted or missing.\n" -"You may have to change some settings in configuration page \"Current Wiki\" and below which were lost." -msgstr "" -"Konfigurációs fájl '%s' megsérült, vagy hiányzik.\n" -"Lehet, hogy meg kell változtatnia néhány beállítást a \"Current Wiki\" konfigurációs lapon és alatta, amelyek elvesztek." - -#: lib\pwiki\PersonalWikiFrame.py:3024 -msgid "Can't write to database '%s'" -msgstr "Nem tud írni az adatbázisba '%s'" - -#: lib\pwiki\PersonalWikiFrame.py:3229 -msgid "" -"There is no (write-)access to underlying wiki\n" -"Close anyway and loose possible changes?" -msgstr "" -"Nincs hozzáférés írásra az alanti wikire\n" -"Bezárja mindenképpen és elveszti a változásokat?" - -#: lib\pwiki\PersonalWikiFrame.py:3231 -msgid "Close anyway" -msgstr "Bezárja mindenképpen" - -#: lib\pwiki\PersonalWikiFrame.py:3315 -msgid "This operation requires an open database" -msgstr "A művelet nyitott adatbázist igényel" - -#: lib\pwiki\PersonalWikiFrame.py:3316 -msgid "No open database" -msgstr "Nincs nyitott adatbázis" - -#: lib\pwiki\PersonalWikiFrame.py:3328 -msgid "No connection to database. Try to reconnect?" -msgstr "Nincs kapcsolat az adatbázishoz. Probálja újracsatolni?" - -#: lib\pwiki\PersonalWikiFrame.py:3329 -msgid "Reconnect database?" -msgstr "Újracsatolja az adatbázishoz?" - -#: lib\pwiki\PersonalWikiFrame.py:3336 -msgid "Trying to reconnect database..." -msgstr "Próbálja újracsatolni az adatbázist..." - -#: lib\pwiki\PersonalWikiFrame.py:3348 -msgid "Error while reconnecting database" -msgstr "Hiba az adatbázis újracsatolása közben" - -#: lib\pwiki\PersonalWikiFrame.py:3373 -msgid "" -"This operation needs write access to database\n" -"Try to write?" -msgstr "" -"Ez a művelet írási hozzáférést igényel az adatbázishoz\n" -"Megpróbál írni?" - -#: lib\pwiki\PersonalWikiFrame.py:3374 -msgid "Try writing?" -msgstr "Megpróbál írni?" - -#: lib\pwiki\PersonalWikiFrame.py:3381 -msgid "Trying to write to database..." -msgstr "Megpróbál az adatbázisba írni..." - -#: lib\pwiki\PersonalWikiFrame.py:3395 -msgid "Error while writing to database" -msgstr "Hiba az adatbázisba íráskor" - -#: lib\pwiki\PersonalWikiFrame.py:3419 -msgid "" -"Database connection error: %s.\n" -"Try to re-establish, then run \"Wiki\"->\"Reconnect\"" -msgstr "" -"Adatbáziskapcsolati hiba: %s.\n" -"Próbálja újra létrehozni a kapcsolatot, majd futtassa \"Wiki\"->\"Reconnect\"" - -#: lib\pwiki\PersonalWikiFrame.py:3421 -#: lib\pwiki\PersonalWikiFrame.py:3438 -msgid "Connection lost" -msgstr "Kapcsolat elveszett" - -#: lib\pwiki\PersonalWikiFrame.py:3436 -msgid "" -"No write access to database: %s.\n" -" Try to re-establish, then run \"Wiki\"->\"Reconnect\"" -msgstr "" -"Az adatbázishoz nincs hozzáférés írásra: %s.\n" -"Próbáljon újracsatlakozni, majd futtassa \"Wiki\"->\"Reconnect\"" - -#: lib\pwiki\PersonalWikiFrame.py:3455 -msgid "Trying to reconnect ..." -msgstr "Megpróbál újracsatlakozni..." - -#: lib\pwiki\PersonalWikiFrame.py:3464 -msgid "Error while trying to reconnect:" -msgstr "Hiba az újracsatolási kísérlet közben:" - -#: lib\pwiki\PersonalWikiFrame.py:3714 -msgid "Couldn't start file" -msgstr "Nem tudja elindítani a fájlt" - -#: lib\pwiki\PersonalWikiFrame.py:3729 -msgid "Couldn't open wiki: %s" -msgstr "Nem tudja a wikit megnyitni: %s" - -#: lib\pwiki\PersonalWikiFrame.py:3759 -msgid "Mod.: %s" -msgstr "Mod.: %s" - -#: lib\pwiki\PersonalWikiFrame.py:3760 -msgid "; Crea.: %s" -msgstr "; Készít: %s" - -#: lib\pwiki\PersonalWikiFrame.py:3797 -msgid "Parent nodes of '%s'" -msgstr "'%s' felmenő csomópontjai" - -#: lib\pwiki\PersonalWikiFrame.py:3809 -msgid "Parentless nodes" -msgstr "Felmenő nélküli csomópontok" - -#: lib\pwiki\PersonalWikiFrame.py:3821 -msgid "Child nodes of '%s'" -msgstr "'%s' leszármazott csomóponjai" - -#: lib\pwiki\PersonalWikiFrame.py:3834 -msgid "Bookmarks" -msgstr "Könyvjelzők" - -#: lib\pwiki\PersonalWikiFrame.py:3981 -msgid "Wiki: %s" -msgstr "Wiki: %s" - -#: lib\pwiki\PersonalWikiFrame.py:4120 -msgid "Set at Page: %s\t%s" -msgstr "Beállítva erre a lapra: %s\t%s" - -#: lib\pwiki\PersonalWikiFrame.py:4136 -msgid "Error saving global configuration" -msgstr "Hiba a globális beállítások mentésekor" - -#: lib\pwiki\PersonalWikiFrame.py:4147 -msgid "Error saving current configuration" -msgstr "Hiba az aktuális beállítások mentésekor" - -#: lib\pwiki\PersonalWikiFrame.py:4169 -msgid "No real wiki word selected to rename" -msgstr "Nincs igazi wikiszó kiválasztva átnevezésre" - -#: lib\pwiki\PersonalWikiFrame.py:4173 -msgid "The scratch pad cannot be renamed." -msgstr "A Vázlatfüzetet nem lehet átnevezni" - -#: lib\pwiki\PersonalWikiFrame.py:4197 -msgid "Description:" -msgstr "Leírás:" - -#: lib\pwiki\PersonalWikiFrame.py:4198 -msgid "Store new version" -msgstr "Új verzió mentése" - -#: lib\pwiki\PersonalWikiFrame.py:4212 -msgid "Do you want to delete all stored versions?" -msgstr "Mindegyik tárolt verziót törli?" - -#: lib\pwiki\PersonalWikiFrame.py:4213 -msgid "Delete All Versions" -msgstr "Minden verzió törlése" - -#: lib\pwiki\PersonalWikiFrame.py:4358 -msgid "The scratch pad cannot be deleted" -msgstr "A Vázlatfüzetet nem lehet törölni" - -#: lib\pwiki\PersonalWikiFrame.py:4362 -msgid "No real wiki word to delete" -msgstr "Nincs törölhető valós wikiszó" - -#: lib\pwiki\PersonalWikiFrame.py:4369 -msgid "Are you sure you want to delete wiki word '%s'?" -msgstr "Biztosan törölni akarja a '%s' wikiszót?" - -#: lib\pwiki\PersonalWikiFrame.py:4398 -msgid "No real wiki word to modify" -msgstr "Nincs módosítható valós wikiszó" - -#: lib\pwiki\PersonalWikiFrame.py:4414 -msgid "Replace text by WikiWord:" -msgstr "Szöveg lecserélése wikiszóval:" - -#: lib\pwiki\PersonalWikiFrame.py:4415 -msgid "Replace by Wiki Word" -msgstr "Lecserélés wikiszóval" - -#: lib\pwiki\PersonalWikiFrame.py:4424 -msgid "'%s' is an invalid wiki word." -msgstr "'%s' érvénytelen wikiszó." - -#: lib\pwiki\PersonalWikiFrame.py:4439 -msgid "" -"Wiki word %s exists already\n" -"Would you like to append to the word?" -msgstr "" -"%s wikiszó már létezik\n" -"Hozzáfűzi a szóhoz?" - -#: lib\pwiki\PersonalWikiFrame.py:4442 -msgid "Word exists" -msgstr "Szó létezik" - -#: lib\pwiki\PersonalWikiFrame.py:4693 -msgid "Error on export" -msgstr "Hiba az exportban" - -#: lib\pwiki\PersonalWikiFrame.py:4723 -msgid "Choose a file to create URL for" -msgstr "Válassza ki a fájlt, amihez URL-t készít" - -#: lib\pwiki\PersonalWikiFrame.py:4791 -msgid "Are you sure you want to start a full rebuild of wiki in background?" -msgstr "Biztosan elindítja a wiki teljes újraépítését a háttérben?" - -#: lib\pwiki\PersonalWikiFrame.py:4793 -msgid "Initiate update" -msgstr "Frissítés indítása" - -#: lib\pwiki\PersonalWikiFrame.py:4800 -#: lib\pwiki\PersonalWikiFrame.py:4801 -msgid " Initiating update " -msgstr " Frissítés indítása " - -#: lib\pwiki\PersonalWikiFrame.py:4815 -msgid "Error initiating update" -msgstr "Hiba a frissítés indításakor" - -#: lib\pwiki\PersonalWikiFrame.py:4824 -msgid "Are you sure you want to rebuild this wiki? You may want to backup your data first!" -msgstr "Biztosan újraépítené ezt a wikit? Előbb talán menthetné az adatait!" - -#: lib\pwiki\PersonalWikiFrame.py:4826 -msgid "Rebuild wiki" -msgstr "Wiki újraépítése" - -#: lib\pwiki\PersonalWikiFrame.py:4833 -#: lib\pwiki\PersonalWikiFrame.py:4834 -msgid " Rebuilding wiki " -msgstr " Wiki újraépítése " - -#: lib\pwiki\PersonalWikiFrame.py:4849 -msgid "Error rebuilding wiki" -msgstr "Hiba a wiki újraépítésekor" - -#: lib\pwiki\PersonalWikiFrame.py:4911 -msgid "This could overwrite pages in the database. Continue?" -msgstr "Ez felülírhat lapokat az adatbázisban. Folytatja?" - -#: lib\pwiki\PersonalWikiFrame.py:4912 -msgid "Import pagefiles" -msgstr "Lapfájl importálása" - -#: lib\pwiki\PersonalWikiFrame.py:5025 -msgid "No list of strings passed to \"listmcstr\" dialog" -msgstr "Sztringlistát a \"listmcstr\" párbeszéd nem kapott" - -#: lib\pwiki\PersonalWikiFrame.py:5048 -msgid "Unknown dialog type" -msgstr "Ismeretlen párbeszédtípus" - -#: lib\pwiki\PersonalWikiFrame.py:5187 -#: lib\pwiki\PersonalWikiFrame.py:5205 -#: lib\pwiki\PersonalWikiFrame.py:5230 -msgid "Choose a Wiki to open" -msgstr "Válassz egy wikit megnyitásra" - -#: lib\pwiki\PersonalWikiFrame.py:5244 -msgid "Name for new wiki (must be in the form of a WikiWord):" -msgstr "Az új wiki neve (wikiszó fromátumú legyen):" - -#: lib\pwiki\PersonalWikiFrame.py:5245 -msgid "Create New Wiki" -msgstr "Új wiki készítése" - -#: lib\pwiki\PersonalWikiFrame.py:5260 -msgid "Directory to store new wiki" -msgstr "Könyvtár az új wiki tárolására" - -#: lib\pwiki\PersonalWikiFrame.py:5271 -#: lib\pwiki\wikidata\WikiDataManager.py:1282 -#: lib\pwiki\wikidata\WikiDataManager.py:1338 -msgid "'%s' is an invalid wiki word. %s" -msgstr "'%s' szabálytalan wikiszó. %s" - -#: lib\pwiki\PersonalWikiFrame.py:5575 -msgid "Clipboard Catcher at Cursor" -msgstr "Vágólapbeolvasó a kurzornál" - -#: lib\pwiki\PersonalWikiFrame.py:5579 -msgid "Clipboard Catcher off" -msgstr "Vágólapbeolvasó ki" - -#: lib\pwiki\PersonalWikiFrame.py:5640 -msgid "Restore" -msgstr "Helyreállít" - -#: lib\pwiki\PersonalWikiFrame.py:5641 -msgid "Save" -msgstr "Ment" - -#: lib\pwiki\Printing.py:325 -#: lib\pwiki\Printing.py:641 -msgid "Print Preview" -msgstr "Nyomtatási előnézet" - -#: lib\pwiki\Printing.py:338 -msgid "Printout" -msgstr "Nyomtatvány" - -#: lib\pwiki\SearchAndReplaceDialogs.py:149 -msgid "Searching... (click into field to abort)" -msgstr "Keres... (kattintás a mezőn megszakít)" - -#: lib\pwiki\SearchAndReplaceDialogs.py:151 -msgid "Not found" -msgstr "Nincs találat" - -#: lib\pwiki\SearchAndReplaceDialogs.py:709 -msgid "End of document reached. Continue at beginning?" -msgstr "Elérte a dokumentum végét. Folytatja az elejétől?" - -#: lib\pwiki\SearchAndReplaceDialogs.py:711 -msgid "Continue at beginning?" -msgstr "Folytatja az elejétől" - -#: lib\pwiki\SearchAndReplaceDialogs.py:722 -#: lib\pwiki\SearchAndReplaceDialogs.py:723 -msgid "No matches found" -msgstr "Nincs egyezés" - -#: lib\pwiki\SearchAndReplaceDialogs.py:737 -#: lib\pwiki\SearchAndReplaceDialogs.py:750 -#: lib\pwiki\SearchAndReplaceDialogs.py:989 -#: lib\pwiki\SearchAndReplaceDialogs.py:1193 -#: lib\pwiki\SearchAndReplaceDialogs.py:1219 -#: lib\pwiki\SearchAndReplaceDialogs.py:1276 -#: lib\pwiki\SearchAndReplaceDialogs.py:1290 -#: lib\pwiki\SearchAndReplaceDialogs.py:1369 -#: lib\pwiki\SearchAndReplaceDialogs.py:1418 -#: lib\pwiki\SearchAndReplaceDialogs.py:1579 -#: lib\pwiki\SearchAndReplaceDialogs.py:2200 -msgid "Error in regular expression" -msgstr "Hiba a szabályos kifejezésben" - -#: lib\pwiki\SearchAndReplaceDialogs.py:778 -#: lib\pwiki\SearchAndReplaceDialogs.py:1363 -msgid "%i replacements done" -msgstr "%i csere történt" - -#: lib\pwiki\SearchAndReplaceDialogs.py:779 -#: lib\pwiki\SearchAndReplaceDialogs.py:1303 -#: lib\pwiki\SearchAndReplaceDialogs.py:1364 -msgid "Replace All" -msgstr "Mindet cseréli" - -#: lib\pwiki\SearchAndReplaceDialogs.py:988 -msgid "" -"Bad regular expression '%s':\n" -"%s" -msgstr "" -"Hibás szabályos kifejezés '%s':\n" -"%s" - -#: lib\pwiki\SearchAndReplaceDialogs.py:1196 -#: lib\pwiki\SearchAndReplaceDialogs.py:1223 -#: lib\pwiki\SearchAndReplaceDialogs.py:1279 -#: lib\pwiki\SearchAndReplaceDialogs.py:1294 -#: lib\pwiki\SearchAndReplaceDialogs.py:1372 -#: lib\pwiki\SearchAndReplaceDialogs.py:1422 -#: lib\pwiki\SearchAndReplaceDialogs.py:1582 -#: lib\pwiki\SearchAndReplaceDialogs.py:2203 -msgid "Error in boolean expression" -msgstr "Hiba a boolean kifejezésben" - -#: lib\pwiki\SearchAndReplaceDialogs.py:1199 -#: lib\pwiki\SearchAndReplaceDialogs.py:1227 -#: lib\pwiki\SearchAndReplaceDialogs.py:1375 -#: lib\pwiki\SearchAndReplaceDialogs.py:1585 -#: lib\pwiki\SearchAndReplaceDialogs.py:2206 -msgid "Error. Maybe wiki rebuild is needed" -msgstr "Hiba. Szükség lehet a Wiki újjáépítésére" - -#: lib\pwiki\SearchAndReplaceDialogs.py:1303 -msgid "Replace all occurrences?" -msgstr "Minden előfordulást lecserél?" - -#: lib\pwiki\SearchAndReplaceDialogs.py:1430 -msgid "Choose search title" -msgstr "Válasszon keresési címet" - -#: lib\pwiki\SearchAndReplaceDialogs.py:1440 -msgid "Do you want to overwrite existing search '%s'?" -msgstr "Felülírja a meglévő '%s' keresést?" - -#: lib\pwiki\SearchAndReplaceDialogs.py:1441 -msgid "Overwrite search" -msgstr "Keresés felülírása" - -#: lib\pwiki\SearchAndReplaceDialogs.py:1456 -msgid "Invalid search string, can't save as view" -msgstr "Érvénytelen keresési karaktersor, nem menthető képként" - -#: lib\pwiki\SearchAndReplaceDialogs.py:1556 -msgid "Do you want to delete %i search(es)?" -msgstr "Töröl a(z) %i keresést?" - -#: lib\pwiki\SearchAndReplaceDialogs.py:1557 -msgid "Delete search" -msgstr "Keresés törlése" - -#: lib\pwiki\SearchAndReplaceDialogs.py:1657 -msgid "*Set page list*" -msgstr "Laplita beállítása" - -#: lib\pwiki\SearchAndReplaceDialogs.py:1941 -msgid "" -msgstr "" - -#: lib\pwiki\SearchAndReplaceDialogs.py:1991 -msgid "Fast Search" -msgstr "Gyorskeresés" - -#: lib\pwiki\SearchAndReplaceDialogs.py:2018 -msgid "As Tab" -msgstr "Fülként" - -#: lib\pwiki\SearchAndReplaceDialogs.py:2058 -msgid "Search: %s" -msgstr "Keres: %s" - -#: lib\pwiki\SearchAndReplaceDialogs.py:2307 -#: lib\pwiki\WikiHtmlView.py:710 -msgid "Activate" -msgstr "Aktivál" - -#: lib\pwiki\SearchAndReplaceDialogs.py:2309 -#: lib\pwiki\WikiHtmlView.py:712 -msgid "Activate New Tab Backgrd." -msgstr "Új fülháttér aktiválása" - -#: lib\pwiki\SpellChecker.py:117 -msgid "No wiki open or current page is a functional page" -msgstr "Nincs nyitott wiki, vagy az aktuális lap funkciólap" - -#: lib\pwiki\SpellChecker.py:131 -msgid "Current page is not modified yet" -msgstr "Jelenlegi lap még nincs módosítva" - -#: lib\pwiki\SpellChecker.py:138 -#: lib\pwiki\SpellChecker.py:172 -#: lib\pwiki\SpellChecker.py:180 -msgid "No (more) misspelled words found" -msgstr "További gépelési hibát nem talált" - -#: lib\pwiki\SpellChecker.py:148 -msgid "No dictionary found for this page" -msgstr "Nem talált szótárat ehhez a laphoz" - -#: lib\pwiki\StringOps.py:715 -msgid "Inval. timestamp" -msgstr "Érvénytelen dátumbélyegző" - -#: lib\pwiki\TextTree.py:205 -#: lib\pwiki\TextTree.py:240 -msgid "" -msgstr "" - -#: lib\pwiki\TextTree.py:361 -msgid "Select wiki for favorites" -msgstr "Wikit a kedvencekhez" - -#: lib\pwiki\Trashcan.py:183 -#: lib\pwiki\Trashcan.py:440 -#: lib\pwiki\Trashcan.py:452 -#: lib\pwiki\timeView\Versioning.py:175 -#: lib\pwiki\timeView\Versioning.py:442 -#: lib\pwiki\timeView\Versioning.py:454 -msgid "Versioning data damaged" -msgstr "Verziójelölő dátum megsérült" - -#: lib\pwiki\WikiExceptions.py:72 -msgid "Multiple words rename to same word" -msgstr "Több szó átnevezése ugyanarra a szóra" - -#: lib\pwiki\WikiHtmlView.py:597 -#: lib\pwiki\WikiTxtCtrl.py:2098 -msgid "Folder does not exist" -msgstr "Könyvtár nem létezik" - -#: lib\pwiki\WikiHtmlView.py:689 -#: lib\pwiki\WikiHtmlViewIE.py:479 -msgid "Link to page: %s" -msgstr "Link a(z) %s laphoz" - -#: lib\pwiki\WikiTreeCtrl.py:525 -msgid "Views" -msgstr "Nézet" - -#: lib\pwiki\WikiTreeCtrl.py:902 -msgid "searches" -msgstr "Keresések" - -#: lib\pwiki\WikiTreeCtrl.py:984 -msgid "modified-within" -msgstr "Módosítva ...belül" - -#: lib\pwiki\WikiTreeCtrl.py:1012 -msgid "1 day" -msgstr "1 nap" - -#: lib\pwiki\WikiTreeCtrl.py:1014 -msgid "%i days" -msgstr "%i napja" - -#: lib\pwiki\WikiTreeCtrl.py:1052 -msgid "parentless-nodes" -msgstr "Felmenő nélküli csomópontok" - -#: lib\pwiki\WikiTreeCtrl.py:1083 -msgid "undefined-nodes" -msgstr "nem deffiniált csomópontok" - -#: lib\pwiki\WikiTreeCtrl.py:1113 -msgid "Func. pages" -msgstr "Funkc.lapok" - -#: lib\pwiki\WikiTreeCtrl.py:1245 -msgid "Add icon attribute" -msgstr "Ikonattribútum hozzáadása" - -#: lib\pwiki\WikiTreeCtrl.py:1252 -msgid "Add color attribute" -msgstr "Színattribútum hozzáadása" - -#: lib\pwiki\WikiTreeCtrl.py:1856 -msgid "Append Wiki Word" -msgstr "Wikiszó hozzáfűzése" - -#: lib\pwiki\WikiTreeCtrl.py:1871 -msgid "Prepend Wiki Word" -msgstr "Wikiszó beszúrása előre" - -#: lib\pwiki\WikiTxtCtrl.py:1287 -msgid "Select Template" -msgstr "Sablon kiválasztása" - -#: lib\pwiki\WikiTxtCtrl.py:1289 -msgid "Select Template (deletes current content!)" -msgstr "Válassza ki a sablon (törli az aktuális tartalmat)" - -#: lib\pwiki\WikiTxtCtrl.py:1395 -msgid "Use Template" -msgstr "Használandó sablon" - -#: lib\pwiki\WikiTxtCtrl.py:2174 -msgid "" -"Set in menu \"Wiki\", item \"Options...\", options page \"Security\", \n" -"item \"Script security\" an appropriate value to execute a script." -msgstr "" -"Szkriptfuttatáshoz a \"Wiki\" menü, \"Opciók\" lap \"Biztonság\"\n" -"részben a \"Szkript biztonság\"-ot állítsa a megfelelő értékre! " - -#: lib\pwiki\WikiTxtCtrl.py:2177 -msgid "Script execution disabled" -msgstr "Szkriptfuttatás letiltva" - -#: lib\pwiki\WikiTxtCtrl.py:2249 -msgid "" -"\n" -"Exception: %s" -msgstr "" -"\n" -"Kivétel: %s" - -#: lib\pwiki\WikiTxtCtrl.py:2942 -msgid "Line: %d Col: %d Pos: %d" -msgstr "Sor: %d Oszl.: %d Poz: %d" - -#: lib\pwiki\WikiTxtCtrl.py:3122 -msgid "Couldn't copy file" -msgstr "Nem tudta másolni a fájlt" - -#: lib\pwiki\WikiTxtCtrl.py:3383 -msgid "Ignore" -msgstr "Elvet" - -#: lib\pwiki\WikiTxtCtrl.py:3384 -msgid "Add Globally" -msgstr "Hozzáad globálisan" - -#: lib\pwiki\WikiTxtCtrl.py:3385 -msgid "Add Locally" -msgstr "Hozzáad helyileg" - -#: lib\pwiki\WikiTxtCtrl.py:3395 -msgid "Follow Link" -msgstr "Link követése" - -#: lib\pwiki\WikiTxtCtrl.py:3396 -msgid "Follow Link New Tab" -msgstr "Hivatkozás követése új fülön" - -#: lib\pwiki\WikiTxtCtrl.py:3397 -msgid "Follow Link New Tab Backgrd." -msgstr "Hivatkozás követése új fülön háttérben" - -#: lib\pwiki\WikiTxtCtrl.py:3399 -msgid "Convert Absolute/Relative File URL" -msgstr "Abszolút/relatív fájl URL konvertálása" - -#: lib\pwiki\WikiTxtCtrl.py:3400 -msgid "Open Containing Folder" -msgstr "Nyissa meg tároló könyvtárat" - -#: lib\pwiki\WikiTxtCtrl.py:3402 -msgid "Copy anchor URL to clipboard" -msgstr "Horgony URL másolása vágólapra" - -#: lib\pwiki\WikiTxtCtrl.py:3404 -msgid "Other..." -msgstr "Más..." - -#: lib\pwiki\WikiTxtCtrl.py:3405 -msgid "Use Template..." -msgstr "Használja a sablont ..." - -#: lib\pwiki\WikiTxtCtrl.py:3409 -msgid "Show folding" -msgstr "Összecsukásjelek megjelenítése" - -#: lib\pwiki\WikiTxtCtrl.py:3410 -msgid "Show folding marks and allow folding" -msgstr "Összecsukásjelek megjelenítése és az összecsukás engedélyezése" - -#: lib\pwiki\WikiTxtCtrl.py:3411 -msgid "&Toggle current folding" -msgstr "Aktuális összecsukások váltása" - -#: lib\pwiki\WikiTxtCtrl.py:3412 -msgid "Toggle folding of the current line" -msgstr "Az összecsukás átállítása az aktuális sorban" - -#: lib\pwiki\WikiTxtCtrl.py:3413 -msgid "&Unfold All" -msgstr "Kibont mind" - -#: lib\pwiki\WikiTxtCtrl.py:3414 -msgid "Unfold everything in current editor" -msgstr "Aktuális szerkesztőben mindent kibont" - -#: lib\pwiki\WikiTxtCtrl.py:3415 -msgid "&Fold All" -msgstr "Összecsukja mindet" - -#: lib\pwiki\WikiTxtCtrl.py:3416 -msgid "Fold everything in current editor" -msgstr "Összecsuk mindent az aktuális szerkesztőben" - -#: lib\pwiki\WikiTxtDialogs.py:43 -msgid "Incremental search (ENTER/ESC to finish)" -msgstr "Karakterrészlet keresése (ENTER vagy ESC megszakításhoz)" - -#: lib\pwiki\WindowsHacks.py:299 -msgid "Copying from %s to %s failed. SHFileOperation result no. %s" -msgstr "Másolás %s-ből %s-be nem sikerült. SHFileOperation eredménye %s" - -#: lib\pwiki\WindowsHacks.py:303 -msgid "Moving from %s to %s failed. SHFileOperation result no. %s" -msgstr "Mozgatás %s-ből %s-be nem sikerült. SHFileOperation eredménye %s" - -#: lib\pwiki\timeView\DatedWikiWordFilters.py:131 -msgid "Modified" -msgstr "Módosítva" - -#: lib\pwiki\timeView\TimeViewCtrl.py:47 -msgid "Versions" -msgstr "Verzió" - -#: lib\pwiki\timeView\TimelinePanel.py:799 -msgid "Show dates without associated wiki words" -msgstr "Dátumokat hozzátartozó wikiszavak nélkül mutatja." - -#: lib\pwiki\timeView\TimelinePanel.py:801 -msgid "List dates ascending or descending" -msgstr "Dátumokat növekvő vagy csökkenő sorban mutatja" - -#: lib\pwiki\timeView\VersioningGui.py:225 -msgid "Deleting in-between versions is not supported yet" -msgstr "Verziók közötti törlés még nem támogatott" - -#: lib\pwiki\timeView\VersioningGui.py:228 -msgid "Do you want to delete this version?" -msgstr "Törli ezt a verziót?" - -#: lib\pwiki\timeView\VersioningGui.py:229 -#: lib\pwiki\timeView\VersioningGui.py:814 -msgid "Delete version" -msgstr "Verzió törlése" - -#: lib\pwiki\timeView\VersioningGui.py:241 -#: lib\pwiki\timeView\VersioningGui.py:273 -msgid "Do you want to delete all version data of this page?" -msgstr "Törli az összes verzióadatot ezen a lapon?" - -#: lib\pwiki\timeView\VersioningGui.py:242 -#: lib\pwiki\timeView\VersioningGui.py:274 -#: lib\pwiki\timeView\VersioningGui.py:819 -msgid "Delete all versions" -msgstr "Összes verzió törlése" - -#: lib\pwiki\timeView\VersioningGui.py:288 -#: lib\pwiki\timeView\VersioningGui.py:349 -#: lib\pwiki\timeView\VersioningGui.py:577 -msgid "Current" -msgstr "Aktuális" - -#: lib\pwiki\timeView\VersioningGui.py:806 -msgid "Diff inline" -msgstr "Jav. megjelen." - -#: lib\pwiki\timeView\VersioningGui.py:807 -msgid "Show the difference between two versions inline" -msgstr "Mutassa a két verzió közötti különbséget megnyitott állapotban" - -#: lib\pwiki\timeView\VersioningGui.py:809 -msgid "Set diff from" -msgstr "Dif.-et vegye innen" - -#: lib\pwiki\timeView\VersioningGui.py:810 -msgid "Set the \"from\" version in diff" -msgstr "Helyezze a \"ból\ verziót a dif.-be" - -#: lib\pwiki\timeView\VersioningGui.py:811 -msgid "Set diff to" -msgstr "Dif.-et tegye ide" - -#: lib\pwiki\timeView\VersioningGui.py:812 -msgid "Set the \"to\" version in diff" -msgstr "Helyezze a \"ba\" verziót a dif.-be" - -#: lib\pwiki\timeView\VersioningGui.py:815 -msgid "Delete selected version" -msgstr "Kiválasztott verziók törlése" - -#: lib\pwiki\timeView\VersioningGui.py:817 -msgid "Add version" -msgstr "Verzió hozzáadása" - -#: lib\pwiki\timeView\VersioningGui.py:818 -msgid "Add a new version" -msgstr "Új verzió hozzáadása" - -#: lib\pwiki\timeView\VersioningGui.py:820 -msgid "Delete all versions of current page" -msgstr "Aktuális lapon valamennyi verzió törlése" - -#: lib\pwiki\wikidata\FileStorage.py:194 -msgid "File compare error, file not readable or changed during compare" -msgstr "Fájl összehasonlítási hiba, a fájl nem olvasható, vagy összevetés közben megváltozott" - -#: lib\pwiki\wikidata\FileStorage.py:216 -msgid "Path '%s' must point to an existing file" -msgstr "'%s' útvonalnak létező fájlra kell mutatnia" - -#: lib\pwiki\wikidata\FileStorage.py:233 -msgid "Internal error: Bad source file name" -msgstr "Belső hiba. Rossz forrásfájlnév" - -#: lib\pwiki\wikidata\FileStorage.py:298 -msgid "Copy of file '%s' couldn't be created" -msgstr "'%s' fájl másolatát nem lehetett létrehozni " - -#: lib\pwiki\wikidata\WikiDataManager.py:58 -msgid "Database exists already and is currently in use" -msgstr "Az adatbázis már létezik és jelenleg használatban van" - -#: lib\pwiki\wikidata\WikiDataManager.py:63 -#: lib\pwiki\wikidata\WikiDataManager.py:1703 -msgid "Data handler %s not available" -msgstr "Adatkezelő %s elérhetetlen" - -#: lib\pwiki\wikidata\WikiDataManager.py:84 -msgid "Database is already in use with database handler \"%s\". Can't open with different handler." -msgstr "Adatbázist \"%s\" adatbáziskezelő már használja. Másik kezelővel nem nyitható meg." - -#: lib\pwiki\wikidata\WikiDataManager.py:90 -msgid "Database is already in use with wiki language handler \"%s\". Can't open with different handler." -msgstr "Adatbázist a wiki \"%s\" nyelvi kezelője már használja. Eltérő kezelővel nem nyitható meg." - -#: lib\pwiki\wikidata\WikiDataManager.py:285 -msgid "Wiki is probably already in use by other instance" -msgstr "A wikit egy másik példány már valószínűleg használja." - -#: lib\pwiki\wikidata\WikiDataManager.py:314 -msgid "Wiki configuration file is corrupted" -msgstr "Sérült a wiki konfigurációs fájlja" - -#: lib\pwiki\wikidata\WikiDataManager.py:350 -msgid "No data handler information found, probably \"Original Gadfly\" is right." -msgstr "Nics információ az adatkezelőről, valószínűleg az \"Original Gadfly\" megfelel." - -#: lib\pwiki\wikidata\WikiDataManager.py:356 -msgid "Required data handler \"%s\" unknown to WikidPad" -msgstr "A szükséges adatkezelőt \"%s\" a WikidPad nem ismeri" - -#: lib\pwiki\wikidata\WikiDataManager.py:362 -msgid "Error on initializing data handler \"%s\"" -msgstr "Hiba az adatkezelő indításakor \"%s\"" - -#: lib\pwiki\wikidata\WikiDataManager.py:373 -msgid "Required wiki language handler \"%s\" not available" -msgstr "A szükséges nyelvkezelő \"%s\" nem áll rendelkezésre" - -#: lib\pwiki\wikidata\WikiDataManager.py:820 -msgid "Word '%s' not in wiki" -msgstr "'%s' szó nincs a wikiben" - -#: lib\pwiki\wikidata\WikiDataManager.py:1126 -#: lib\pwiki\wikidata\WikiDataManager.py:1190 -msgid "Update basic link info" -msgstr "Alap hivatkozási információk frissítése" - -#: lib\pwiki\wikidata\WikiDataManager.py:1142 -msgid "Starting update thread" -msgstr "Frissítési folyamat indítása" - -#: lib\pwiki\wikidata\WikiDataManager.py:1210 -msgid "Update attributes of %s" -msgstr "%s attribútumainak frissítése" - -#: lib\pwiki\wikidata\WikiDataManager.py:1232 -msgid "Update page %s" -msgstr "%s lap frissítése" - -#: lib\pwiki\wikidata\WikiDataManager.py:1250 -msgid "Final cleanup" -msgstr "Végső tisztítás" - -#: lib\pwiki\wikidata\WikiDataManager.py:1343 -msgid "Cannot rename '%s' to '%s', '%s' already exists" -msgstr "'%s'-t to '%s'-ra nem tudja átnevezni, '%s' már létezik" - -#: lib\pwiki\wikidata\compact_sqlite\DbStructure.py:725 -#: lib\pwiki\wikidata\original_gadfly\DbStructure.py:723 -#: lib\pwiki\wikidata\original_sqlite\DbStructure.py:695 -msgid "database already exists at location: %s" -msgstr "adatbázis %s helyen már létezik." - -#: lib\pwiki\wikidata\compact_sqlite\DbStructure.py:913 -#: lib\pwiki\wikidata\original_gadfly\DbStructure.py:779 -msgid "Update needed" -msgstr "Frissítés szükséges" - -#: lib\pwiki\wikidata\compact_sqlite\DbStructure.py:916 -#: lib\pwiki\wikidata\original_gadfly\DbStructure.py:782 -#: lib\pwiki\wikidata\original_sqlite\DbStructure.py:825 -msgid "Database has unknown format branchtag='%s'" -msgstr "Az adatbázis '%s' kiterjesztése ismeretlen" - -#: lib\pwiki\wikidata\compact_sqlite\DbStructure.py:925 -#: lib\pwiki\wikidata\original_gadfly\DbStructure.py:791 -#: lib\pwiki\wikidata\original_sqlite\DbStructure.py:834 -msgid "Database has unknown format version='%i'" -msgstr "Adatbázis formátuma='%i', ismeretlen verzió" - -#: lib\pwiki\wikidata\compact_sqlite\DbStructure.py:929 -#: lib\pwiki\wikidata\original_gadfly\DbStructure.py:795 -#: lib\pwiki\wikidata\original_sqlite\DbStructure.py:838 -msgid "Update needed, current format version='%i'" -msgstr "Frissítés kell, a formátum jelenlegi verziója='%i'" - -#: lib\pwiki\wikidata\compact_sqlite\DbStructure.py:932 -#: lib\pwiki\wikidata\original_gadfly\DbStructure.py:798 -#: lib\pwiki\wikidata\original_sqlite\DbStructure.py:841 -msgid "Database format is up to date" -msgstr "Az adatbázis formátuma friss" - -#: lib\pwiki\wikidata\compact_sqlite\WikiData.py:215 -msgid "Wiki page not found: %s" -msgstr "%s wikilap nem taláható" - -#: lib\pwiki\wikidata\compact_sqlite\WikiData.py:479 -#: lib\pwiki\wikidata\original_gadfly\WikiData.py:459 -#: lib\pwiki\wikidata\original_sqlite\WikiData.py:521 -msgid "You cannot delete the root wiki node" -msgstr "Nem törölheti a gyökér wiki csomópontot" - -#: lib\pwiki\wikidata\original_gadfly\WikiData.py:1051 -#: lib\pwiki\wikidata\original_sqlite\WikiData.py:1160 -msgid "Wiki page not found (no path information) for word: %s" -msgstr "%s szóhoz wikilap nem található (nincs útvonalinformáció)" - -#: lib\pwiki\wikidata\original_gadfly\WikiData.py:1069 -#: lib\pwiki\wikidata\original_gadfly\WikiData.py:1083 -#: lib\pwiki\wikidata\original_sqlite\WikiData.py:1178 -#: lib\pwiki\wikidata\original_sqlite\WikiData.py:1192 -msgid "Wiki page not found (bad path information) for word: %s" -msgstr "%s szóhoz wikilap nem található (rossz útvonalinformáció)" - -#~ msgid "New Tab" -#~ msgstr "Új fül" -#~ msgid "Opened wiki word '%s'" -#~ msgstr "Nyitott wikiszó '%s'" -#~ msgid "Internal Error" -#~ msgstr "Belső hiba" -#~ msgid "'%s' is already alias for the wiki word(s): %s" -#~ msgstr "'%s' álnév már tartozik a wikiszó(k)hoz: %s" - +# This file is distributed under the same license as wikidPad. +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2010-09-25 11:38\n" +"PO-Revision-Date: 2010-10-26 17:30+0100\n" +"Last-Translator: Török Árpád \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5mod\n" +"X-Poedit-Language: Hungarian\n" +"X-Poedit-Country: HUNGARY\n" + +#: WikidPad.xrc:0 +msgid " Context characters" +msgstr " Szövegkörnyezet karakterek" + +#: WikidPad.xrc:0 +msgid " Defaults" +msgstr " Alapbeállítások" + +#: WikidPad.xrc:0 +msgid " &Delete " +msgstr "Törlés" + +#: WikidPad.xrc:0 +msgid " &Find " +msgstr "Keres" + +#: WikidPad.xrc:0 +msgid " &Replace " +msgstr "Csere" + +#: WikidPad.xrc:0 +msgid " &Test " +msgstr "Teszt" + +#: WikidPad.xrc:0 +msgid " Find &Next " +msgstr "Következő találat" + +#: WikidPad.xrc:0 +msgid " Replace &All " +msgstr "Cseréli mindet" + +#: WikidPad.xrc:0 +msgid "&Case sensitive" +msgstr "Nagybetűérzékeny" + +#: WikidPad.xrc:0 +msgid "&Create" +msgstr "Létrehoz" + +#: WikidPad.xrc:0 +msgid "&Delete Export(s)" +msgstr "Export törlése" + +#: WikidPad.xrc:0 +msgid "&Delete Search(es)" +msgstr "Keresés törlése" + +#: WikidPad.xrc:0 +msgid "&Ignore" +msgstr "Elvet" + +#: WikidPad.xrc:0 +msgid "&Load Export" +msgstr "Export betöltése" + +#: WikidPad.xrc:0 +msgid "&Load Search" +msgstr "Keresési eredmény betöltése" + +#: WikidPad.xrc:0 +msgid "&New Tab" +msgstr "Új fül" + +#: WikidPad.xrc:0 +msgid "&Options" +msgstr "Opciók" + +#: WikidPad.xrc:0 +msgid "&Page names" +msgstr "Oldalaknevek" + +#: WikidPad.xrc:0 +msgid "&Replace" +msgstr "Cserél" + +#: WikidPad.xrc:0 +msgid "&Replace by:" +msgstr "Cserél evvel:" + +#: WikidPad.xrc:0 +msgid "&Save Export" +msgstr "Export mentése" + +#: WikidPad.xrc:0 +msgid "&Save Search" +msgstr "Keresési eredmény mentése" + +#: WikidPad.xrc:0 +msgid "&Search:" +msgstr "Keresés:" + +#: WikidPad.xrc:0 +msgid "&Whole word" +msgstr "Teljes szó" + +#: WikidPad.xrc:0 +msgid "&beginning" +msgstr "előről" + +#: WikidPad.xrc:0 +msgid "(0: worst, 100: best)" +msgstr "(0: legrosszabb, 100: legjobb)" + +#: WikidPad.xrc:0 +msgid "(Use selected defaults if you are unsure)" +msgstr "(Az alapbeállításokat használd, ha nem vagy biztos)" + +#: WikidPad.xrc:0 +msgid "* Needs WikidPad restart" +msgstr "* WikidPad újraindítását igényeli" + +#: WikidPad.xrc:0 +msgid "0" +msgstr "0" + +#: WikidPad.xrc:0 +msgid "" +"0: Never complete version; 1: Always complete v.;\n" +"10: Every tenth v. is complete" +msgstr "" +"0: Sosem teljes verziót; 1: Mindig teljes v;\n" +"10: Minden tizedik verz. teljes" + +#: WikidPad.xrc:0 +msgid "1 (since 1.9beta6)" +msgstr "1 (1.9beta6 óta)" + +#: WikidPad.xrc:0 +msgid "Above" +msgstr "Fölött" + +#: WikidPad.xrc:0 +msgid "Absolute URL" +msgstr "Abszolut URL-cím" + +#: WikidPad.xrc:0 +msgid "Active link color:" +msgstr "Aktív link színe" + +#: WikidPad.xrc:0 +msgid "Add" +msgstr "Hozzáad" + +#: WikidPad.xrc:0 +msgid "Add &Globally" +msgstr "Hozzáad globálisan" + +#: WikidPad.xrc:0 +msgid "Add &Locally" +msgstr "Hozzáad helyben" + +#: WikidPad.xrc:0 +msgid "Add Wiki to Favorites" +msgstr "Wiki hozzáadása Kedvencekhez" + +#: WikidPad.xrc:0 +msgid "Add spaces" +msgstr "Szóközök hozzáadása" + +#: WikidPad.xrc:0 +msgid "After:" +msgstr "Utána:" + +#: WikidPad.xrc:0 +msgid "All Pages" +msgstr "Osszes lap" + +#: WikidPad.xrc:0 +msgid "Allow everything" +msgstr "Mindent engedélyez" + +#: WikidPad.xrc:0 +msgid "Alphabetically" +msgstr "Betűrendben" + +#: WikidPad.xrc:0 +msgid "Also rename subpages" +msgstr "Alárendelt lapokat is átnevezi" + +#: WikidPad.xrc:0 +msgid "Always" +msgstr "Mindig" + +#: WikidPad.xrc:0 +msgid "Always show import table" +msgstr "Mindig mutassa az importtáblát" + +#: WikidPad.xrc:0 +msgid "App-bound hotkey:" +msgstr "Alkamazást elrejtő gyorsillentyű" + +#: WikidPad.xrc:0 +msgid "Append after clipboard snippet:" +msgstr "A vágólaprészlet végéhez fűz:" + +#: WikidPad.xrc:0 +msgid "Append after links:" +msgstr "A linkek végéhez fűz:" + +#: WikidPad.xrc:0 +msgid "Append closing bracket on auto-complete" +msgstr "Csukó zárójel hozzáfűzése automatikus kiegészítéskor" + +#: WikidPad.xrc:0 +msgid "Append wiki word" +msgstr "Wikiszó hozzáfűzése" + +#: WikidPad.xrc:0 +msgid "As &Tab" +msgstr "Fülként" + +#: WikidPad.xrc:0 +msgid "As &is" +msgstr "Ahogy van" + +#: WikidPad.xrc:0 +msgid "As Res&ultlist" +msgstr "Eredménylistaként" + +#: WikidPad.xrc:0 +msgid "As Tree" +msgstr "Faként" + +#: WikidPad.xrc:0 +msgid "As is" +msgstr "Ahogy van" + +#: WikidPad.xrc:0 +msgid "As list" +msgstr "Listaként" + +#: WikidPad.xrc:0 +msgid "As tree" +msgstr "Faként" + +#: WikidPad.xrc:0 +msgid "Ask" +msgstr "Kérdés" + +#: WikidPad.xrc:0 +msgid "Ask settings on each paste" +msgstr "Beállításokat bekéri beillesztéskor" + +#: WikidPad.xrc:0 +msgid "At position:" +msgstr "Pozíciónál:" + +#: WikidPad.xrc:0 +msgid "Attribute/script color:" +msgstr "Attribútum/szkript színe" + +#: WikidPad.xrc:0 +msgid "Attrs. to" +msgstr "Attribútumok amiket " + +#: WikidPad.xrc:0 +msgid "Auto-hide" +msgstr "Automatikus elrejtés" + +#: WikidPad.xrc:0 +msgid "Auto-hide log window" +msgstr "Logablak automatikus elrejtése" + +#: WikidPad.xrc:0 +msgid "Auto-hide structure window" +msgstr "Struktúraablak automatikus elrejtése" + +#: WikidPad.xrc:0 +msgid "Auto-show log window" +msgstr "Logablak automatikus megjelenítése" + +#: WikidPad.xrc:0 +msgid "Auto-unbullets" +msgstr "Felsorolás automatikus kiszedése" + +#: WikidPad.xrc:0 +msgid "Autosave active" +msgstr "Automatikus mentés aktív" + +#: WikidPad.xrc:0 +msgid "Avoid doubled snippets" +msgstr "Töredékismétlés elkerülése" + +#: WikidPad.xrc:0 +msgid "Background color:" +msgstr "Háttérszín:" + +#: WikidPad.xrc:0 +msgid "Background image:" +msgstr "Háttérkép:" + +#: WikidPad.xrc:0 +msgid "Basic wiki settings" +msgstr "Alap wikibeállítások" + +#: WikidPad.xrc:0 +msgid "Before:" +msgstr "Előtte:" + +#: WikidPad.xrc:0 +msgid "Below" +msgstr "Alatta" + +#: WikidPad.xrc:0 +msgid "Between links:" +msgstr "Linkek közé:" + +#: WikidPad.xrc:0 +msgid "Bookmark" +msgstr "Könyvjelző" + +#: WikidPad.xrc:0 +msgid "Bool&ean regex" +msgstr "Boolean regex." + +#: WikidPad.xrc:0 +msgid "Bottom" +msgstr "Lent" + +#: WikidPad.xrc:0 +msgid "C" +msgstr "C" + +#: WikidPad.xrc:0 +msgid "Cancel" +msgstr "Elvet" + +#: WikidPad.xrc:0 +msgid "Change these only if you know what you are doing!" +msgstr "Csak akkor változtassa meg, ha tudja mit csinál!" + +#: WikidPad.xrc:0 +msgid "Choose Wiki Word" +msgstr "Wikiszó kiválasztása" + +#: WikidPad.xrc:0 +msgid "Choose date format" +msgstr "Dátumformátum kiválasztása" + +#: WikidPad.xrc:0 +msgid "Choose plain text font" +msgstr "Sima szöveg betűtípusának kiválasztása" + +#: WikidPad.xrc:0 +msgid "Chronological View" +msgstr "Időrendi nézet" + +#: WikidPad.xrc:0 +msgid "Clear" +msgstr "Törlés" + +#: WikidPad.xrc:0 +msgid "Clear List" +msgstr "Lista törlése" + +#: WikidPad.xrc:0 +msgid "Clone tab" +msgstr "Fül klónozása" + +#: WikidPad.xrc:0 +msgid "Close tab" +msgstr "Fül bezárása" + +#: WikidPad.xrc:0 +msgid "Collapse whole tree" +msgstr "A teljes fastruktúra összecsukása" + +#: WikidPad.xrc:0 +msgid "Compatible filenames" +msgstr "Kompatibilis fájlnevek" + +#: WikidPad.xrc:0 +msgid "Config. dir." +msgstr "Konfig. könyvt." + +#: WikidPad.xrc:0 +msgid "Copy URL to clipboard" +msgstr "URL másolása vágólapra" + +#: WikidPad.xrc:0 +msgid "Copy into file storage" +msgstr "Másolja a fájltárolóba" + +#: WikidPad.xrc:0 +msgid "Copy to clipboard:" +msgstr "Másolás vágólapra" + +#: WikidPad.xrc:0 +msgid "Count Occurrences up to max.:" +msgstr "Előfordulások számolása max.:" + +#: WikidPad.xrc:0 +msgid "Create wiki lock file" +msgstr "Wiki zárolásfájl létrehozása" + +#: WikidPad.xrc:0 +msgid "Current subtree" +msgstr "Jelenlegi alárendelt fa" + +#: WikidPad.xrc:0 +msgid "Current wiki page" +msgstr "Jelenlegi wiki lap" + +#: WikidPad.xrc:0 +msgid "Custom..." +msgstr "Igény szerinti..." + +#: WikidPad.xrc:0 +msgid "DOCTYPE:" +msgstr "Dok. típus:" + +#: WikidPad.xrc:0 +msgid "Database" +msgstr "Adatbázisban" + +#: WikidPad.xrc:0 +msgid "Database type:" +msgstr "Adatbázis típusa:" + +#: WikidPad.xrc:0 +msgid "Date format:" +msgstr "Dátumformátum" + +#: WikidPad.xrc:0 +msgid "Default export dir.:" +msgstr "Export alapértelmezett könyvtára:" + +#: WikidPad.xrc:0 +msgid "Default open/new dir.:" +msgstr "Megnyitás/új alapértelmezett könyvtára" + +#: WikidPad.xrc:0 +msgid "Defaults" +msgstr "Alapértelmezett értékek" + +#: WikidPad.xrc:0 +msgid "Del" +msgstr "Töröl" + +#: WikidPad.xrc:0 +msgid "Delay after key pressed:" +msgstr "Billentyűnyomás után várjon:" + +#: WikidPad.xrc:0 +msgid "Delay after page dirty:" +msgstr "Lap változtatása után várjon:" + +#: WikidPad.xrc:0 +msgid "Delay before auto-close:" +msgstr "Automatikus bezárás előtt várjon" + +#: WikidPad.xrc:0 +msgid "Details" +msgstr "Részletek" + +#: WikidPad.xrc:0 +msgid "Done" +msgstr "Kész" + +#: WikidPad.xrc:0 +msgid "Down" +msgstr "Le" + +#: WikidPad.xrc:0 +msgid "Dropping files with CTRL:" +msgstr "Fájlok ejtése CTRL-lel:" + +#: WikidPad.xrc:0 +msgid "Dropping files with SHIFT:" +msgstr "Fájlok ejtése SHIFT-tel:" + +#: WikidPad.xrc:0 +msgid "Dropping files:" +msgstr "Fájlok ejtése:" + +#: WikidPad.xrc:0 +msgid "Editor timing" +msgstr "Szerkesztő időzítése" + +#: WikidPad.xrc:0 +msgid "Encoding:" +msgstr "Kódolás:" + +#: WikidPad.xrc:0 +msgid "Export" +msgstr "Export" + +#: WikidPad.xrc:0 +msgid "Export to:" +msgstr "Exportálás ebbe:" + +#: WikidPad.xrc:0 +msgid "Fast search defaults" +msgstr "Gyorskeresés alapbeállításai" + +#: WikidPad.xrc:0 +msgid "File dropping" +msgstr "Fájl ejtése" + +#: WikidPad.xrc:0 +msgid "File storage identity check" +msgstr "Fájltároló azonosításának ellenőrzése" + +#: WikidPad.xrc:0 +msgid "File type:" +msgstr "Fájltípus:" + +#: WikidPad.xrc:0 +msgid "Filename must match" +msgstr "A fájlnévnek egyeznie kell!" + +#: WikidPad.xrc:0 +msgid "Filename prefix:" +msgstr "Fájlnév előtagja" + +#: WikidPad.xrc:0 +msgid "First word at startup:" +msgstr "Wikiszó indításkor" + +#: WikidPad.xrc:0 +msgid "Font name for HTML preview:" +msgstr "HTML-előnézet betűtípusa:" + +#: WikidPad.xrc:0 +msgid "Font:" +msgstr "Font:" + +#: WikidPad.xrc:0 +msgid "Forbid cancel on search" +msgstr "Megszakítás tiltása kereséskor" + +#: WikidPad.xrc:0 +msgid "Force ScratchPad visibility in tree" +msgstr "A vázlatfüzet mindenképpen jelenjen meg a fában" + +#: WikidPad.xrc:0 +msgid "Format version" +msgstr "Formátum verzió" + +#: WikidPad.xrc:0 +msgid "Given:" +msgstr "Adott:" + +#: WikidPad.xrc:0 +msgid "Go to next page" +msgstr "Ugrás a következő lapra" + +#: WikidPad.xrc:0 +msgid "Graceful handling of missing page files" +msgstr "Hiányzó fájlnevek óvatos kezelése" + +#: WikidPad.xrc:0 +msgid "HTML" +msgstr "HTML" + +#: WikidPad.xrc:0 +msgid "Heading of new pages" +msgstr "Új lapok fejléce" + +#: WikidPad.xrc:0 +msgid "Heading prefix:" +msgstr "Fejléc előtag" + +#: WikidPad.xrc:0 +msgid "Headings as aliases" +msgstr "Fejlécek álnevekként" + +#: WikidPad.xrc:0 +msgid "Hidden" +msgstr "Rejtett" + +#: WikidPad.xrc:0 +msgid "Hide Log" +msgstr "Log rejtése" + +#: WikidPad.xrc:0 +msgid "Hide undefined wiki words in Tree" +msgstr "A nem definiált wikiszavak rejtse el a fában" + +#: WikidPad.xrc:0 +msgid "Highlight start delay:" +msgstr "Kiemelés indításának késleltetése:" + +#: WikidPad.xrc:0 +msgid "IE" +msgstr "IE" + +#: WikidPad.xrc:0 +msgid "Icon:" +msgstr "Ikon:" + +#: WikidPad.xrc:0 +msgid "Ig&nore All" +msgstr "Mindet elvet" + +#: WikidPad.xrc:0 +msgid "Ignore wiki lock file" +msgstr "Wiki zárolásfájl mellőzése" + +#: WikidPad.xrc:0 +msgid "Image pasting" +msgstr "Kép beillesztése" + +#: WikidPad.xrc:0 +msgid "Import format:" +msgstr "Importálás formátuma:" + +#: WikidPad.xrc:0 +msgid "Import questions" +msgstr "Importálási kérdések" + +#: WikidPad.xrc:0 +msgid "Incremental search" +msgstr "Részletkeresés" + +#: WikidPad.xrc:0 +msgid "Internal" +msgstr "Belső" + +#: WikidPad.xrc:0 +msgid "Intersect" +msgstr "Összevet" + +#: WikidPad.xrc:0 +msgid "JPEG" +msgstr "JPEG" + +#: WikidPad.xrc:0 +msgid "Left" +msgstr "Bal" + +#: WikidPad.xrc:0 +msgid "Left double click in preview:" +msgstr "Bal duplakattintás előnézetben:" + +#: WikidPad.xrc:0 +msgid "Link color:" +msgstr "Link színe:" + +#: WikidPad.xrc:0 +msgid "Load and R&un Export" +msgstr "Export betöltése és futtatása" + +#: WikidPad.xrc:0 +msgid "Load and R&un Search" +msgstr "Keresés betöltése és futtatása" + +#: WikidPad.xrc:0 +msgid "Margin color:" +msgstr "Szegélyszín" + +#: WikidPad.xrc:0 +msgid "Max. characters on each tab:" +msgstr "Karakterek maximális száma a fül címében" + +#: WikidPad.xrc:0 +msgid "Middle button with CTRL:" +msgstr "Középső kattinás CTRL-lel:" + +#: WikidPad.xrc:0 +msgid "Middle button without CTRL:" +msgstr "Középső kattintás CTRL nélkül:" + +#: WikidPad.xrc:0 +msgid "Middle click on tab:" +msgstr "Középső kattintás a fülon" + +#: WikidPad.xrc:0 +msgid "Minimize on close button" +msgstr "Bezárás gombra összecsuk" + +#: WikidPad.xrc:0 +msgid "Minimize to Tray" +msgstr "Összecsuk tálcára" + +#: WikidPad.xrc:0 +msgid "Modif. date is enough" +msgstr "Módosítás dátuma elegendő" + +#: WikidPad.xrc:0 +msgid "Modif. date must match" +msgstr "Mód. dátumnak egyeznie kell" + +#: WikidPad.xrc:0 +msgid "Modify all links to the wiki word (unreliable!)" +msgstr "A wikiszó minden hivatkozását lecseréli (megbízhatatlan)" + +#: WikidPad.xrc:0 +msgid "Move into file storage" +msgstr "Áthelyezés a fájltárolóba" + +#: WikidPad.xrc:0 +msgid "Mozilla" +msgstr "Mozilla" + +#: WikidPad.xrc:0 +msgid "N. T. &Backgr." +msgstr "N. T. Háttér" + +#: WikidPad.xrc:0 +msgid "Natural" +msgstr "Természetes" + +#: WikidPad.xrc:0 +msgid "New tab in background" +msgstr "Új fül háttérben" + +#: WikidPad.xrc:0 +msgid "New tab in foreground" +msgstr "Új fül az előtérben" + +#: WikidPad.xrc:0 +msgid "New tab with edit" +msgstr "Előnézet: új fülön szerkeszt" + +#: WikidPad.xrc:0 +msgid "New window on wiki URL" +msgstr "Wiki URL nyitása új ablakban" + +#: WikidPad.xrc:0 +msgid "Newest visited" +msgstr "Legfrissebb látogatott" + +#: WikidPad.xrc:0 +msgid "No cycles in tree" +msgstr "Ciklus tiltása a Fában" + +#: WikidPad.xrc:0 +msgid "No global.import_scripts" +msgstr "Globális importszkriptek nem engedélyezettek" + +#: WikidPad.xrc:0 +msgid "No import_scripts" +msgstr "Importszkriptek nem engedélyezettek" + +#: WikidPad.xrc:0 +msgid "No scripts" +msgstr "Szkriptek nem engedélyezettek" + +#: WikidPad.xrc:0 +msgid "No title" +msgstr "Nincs cím" + +#: WikidPad.xrc:0 +msgid "None" +msgstr "Nincs" + +#: WikidPad.xrc:0 +msgid "Not at all" +msgstr "Egyáltalán ne" + +#: WikidPad.xrc:0 +msgid "Nothing" +msgstr "Semmi" + +#: WikidPad.xrc:0 +msgid "OK" +msgstr "Rendben" + +#: WikidPad.xrc:0 +msgid "Oldest visited" +msgstr "Legrégebbi látogatott" + +#: WikidPad.xrc:0 +msgid "Open in ..." +msgstr "Nyissa meg itt..." + +#: WikidPad.xrc:0 +msgid "Open in new window" +msgstr "Nyissa meg új ablakban" + +#: WikidPad.xrc:0 +msgid "Options" +msgstr "Opciók" + +#: WikidPad.xrc:0 +msgid "Options page:" +msgstr "Opciók lap:" + +#: WikidPad.xrc:0 +msgid "Order pages:" +msgstr "Parancslapok:" + +#: WikidPad.xrc:0 +msgid "Order:" +msgstr "Sorrend:" + +#: WikidPad.xrc:0 +msgid "PNG" +msgstr "PNG" + +#: WikidPad.xrc:0 +msgid "Page Setup" +msgstr "Oldalbeállítás" + +#: WikidPad.xrc:0 +msgid "Page file names ASCII only" +msgstr "Csak ASCII oldalfájl-nevek" + +#: WikidPad.xrc:0 +msgid "Page names matching regular expression:" +msgstr "Szabályos kifejezéssel egyező lapok neve" + +#: WikidPad.xrc:0 +msgid "Page separator:" +msgstr "Lapelválasztó:" + +#: WikidPad.xrc:0 +msgid "Pages in list and " +msgstr "Lapok a listában és " + +#: WikidPad.xrc:0 +msgid "Paste File" +msgstr "Fájl beillesztése" + +#: WikidPad.xrc:0 +msgid "Paste Image" +msgstr "Kép beillesztése" + +#: WikidPad.xrc:0 +msgid "Paste from clipboard:" +msgstr "Beillesztés vágólapról:" + +#: WikidPad.xrc:0 +msgid "Path or URL:" +msgstr "Elérési út vagy URL:" + +#: WikidPad.xrc:0 +msgid "Path to file launcher:" +msgstr "Fájlindító elérési útja:" + +#: WikidPad.xrc:0 +msgid "Plain text" +msgstr "Sima szöveg" + +#: WikidPad.xrc:0 +msgid "Plain text color:" +msgstr "Sima szöveg színe" + +#: WikidPad.xrc:0 +msgid "Position main tree:" +msgstr "Fa pozíció" + +#: WikidPad.xrc:0 +msgid "Position view tree:" +msgstr "Pozíció fanézetben" + +#: WikidPad.xrc:0 +msgid "Position:" +msgstr "Pozíció:" + +#: WikidPad.xrc:0 +msgid "Prefer memory where possible" +msgstr "Ahol lehet memóriát használja" + +#: WikidPad.xrc:0 +msgid "Prepend before clipboard snippet:" +msgstr "Vágolaprészlet elé illeszt" + +#: WikidPad.xrc:0 +msgid "Prepend before links:" +msgstr "Linkek elé illeszt" + +#: WikidPad.xrc:0 +msgid "Prepend wiki word" +msgstr "Wikiszó elévetés" + +#: WikidPad.xrc:0 +msgid "Preview" +msgstr "Előnézet" + +#: WikidPad.xrc:0 +msgid "Preview renderer:" +msgstr "Előnézet megjelenítő:" + +#: WikidPad.xrc:0 +msgid "Preview:" +msgstr "Előnézet:" + +#: WikidPad.xrc:0 +msgid "Print" +msgstr "Nyomtatás" + +#: WikidPad.xrc:0 +msgid "Print as:" +msgstr "Nyomtat mint:" + +#: WikidPad.xrc:0 +msgid "Process auto-generated areas" +msgstr "Automatikusan létrehozott területek feldolgozása" + +#: WikidPad.xrc:0 +msgid "Process insertion scripts" +msgstr "Beillesztett szkriptek feldolgozása" + +#: WikidPad.xrc:0 +msgid "Quality:" +msgstr "Minőség:" + +#: WikidPad.xrc:0 +msgid "R&egular expression" +msgstr "Szabályos kifejezés" + +#: WikidPad.xrc:0 +msgid "Read-only wiki" +msgstr "Csak olvasható Wiki" + +#: WikidPad.xrc:0 +msgid "Recent wikis list length:" +msgstr "Wiki előzménylista hossza:" + +#: WikidPad.xrc:0 +msgid "Relative URL" +msgstr "Releatív URL" + +#: WikidPad.xrc:0 +msgid "Remember expanded tree nodes:" +msgstr "Megnyitott facsomópontok megjegyzése:" + +#: WikidPad.xrc:0 +msgid "Rename" +msgstr "Átnevez" + +#: WikidPad.xrc:0 +msgid "Rename Word:" +msgstr "Wikiszó átnevezése" + +#: WikidPad.xrc:0 +msgid "Rename dialog defaults" +msgstr "Átnevezés párbeszédablak alapbeállításai" + +#: WikidPad.xrc:0 +msgid "Replace &All" +msgstr "Mindet cserél" + +#: WikidPad.xrc:0 +msgid "Replace By" +msgstr "Cseréli erre:" + +#: WikidPad.xrc:0 +msgid "Replace with:" +msgstr "Cseréli evvel:" + +#: WikidPad.xrc:0 +msgid "Result" +msgstr "Eredmény" + +#: WikidPad.xrc:0 +msgid "Reverse script search order (global imports first)" +msgstr "Fordított szkriptkeresési sorrend (globális importáltak elől)" + +#: WikidPad.xrc:0 +msgid "Right" +msgstr "Jobb" + +#: WikidPad.xrc:0 +msgid "Same Tab" +msgstr "Azonos fül" + +#: WikidPad.xrc:0 +msgid "Saved Exports:" +msgstr "Mentett exportok" + +#: WikidPad.xrc:0 +msgid "Saved Searches:" +msgstr "Mentett keresések:" + +#: WikidPad.xrc:0 +msgid "Script security:" +msgstr "Szkriptbiztonság:" + +#: WikidPad.xrc:0 +msgid "Search Page" +msgstr "Lapon keres" + +#: WikidPad.xrc:0 +msgid "Search Wiki" +msgstr "Wikiben keres" + +#: WikidPad.xrc:0 +msgid "Search from" +msgstr "Keresés ettől" + +#: WikidPad.xrc:0 +msgid "Search text" +msgstr "Keresett szöveg" + +#: WikidPad.xrc:0 +msgid "Select facename" +msgstr "Facename kiválasztása" + +#: WikidPad.xrc:0 +msgid "Selection bg. color:" +msgstr "Kiválasztás háttérszine" + +#: WikidPad.xrc:0 +msgid "Selection fg. color:" +msgstr "Kiválasztás előtérszíne" + +#: WikidPad.xrc:0 +msgid "Separate files" +msgstr "Önálló fáljloban" + +#: WikidPad.xrc:0 +msgid "Set As Root" +msgstr "Beállítás gyökérnek" + +#: WikidPad.xrc:0 +msgid "Set page &list" +msgstr "Laplista beállítása" + +#: WikidPad.xrc:0 +msgid "Short hint delay:" +msgstr "Rövidtipp késleltetése:" + +#: WikidPad.xrc:0 +msgid "Shortcut:" +msgstr "Billentyűparancs:" + +#: WikidPad.xrc:0 +msgid "Show in toolbar" +msgstr "Eszköztárban mutat" + +#: WikidPad.xrc:0 +msgid "Show pics as links in export" +msgstr "Exportban a képeket linkként mutassa" + +#: WikidPad.xrc:0 +msgid "Show pics as links in preview" +msgstr "Előnézetben a képeket linkként mutassa" + +#: WikidPad.xrc:0 +msgid "Show pictures as links" +msgstr "Képek mutatása linkként" + +#: WikidPad.xrc:0 +msgid "Show word list on hovering" +msgstr "Rámutatáskor a szavak listáját mutassa" + +#: WikidPad.xrc:0 +msgid "Show word list on select" +msgstr "Kiválasztáskor mutassa a szavak listáját" + +#: WikidPad.xrc:0 +msgid "Si&mple regex" +msgstr "Egyszerű regex" + +#: WikidPad.xrc:0 +msgid "Single page separator lines:" +msgstr "Önálló oldalakat elválasztó vonal:" + +#: WikidPad.xrc:0 +msgid "Single process per user*" +msgstr "Felhasználónként egy porcessz*" + +#: WikidPad.xrc:0 +msgid "Sort" +msgstr "Rendez" + +#: WikidPad.xrc:0 +msgid "Sort alphabetically" +msgstr "Betűrendbe rendez" + +#: WikidPad.xrc:0 +msgid "Sort order:" +msgstr "Rendezési sorrend:" + +#: WikidPad.xrc:0 +msgid "Sound" +msgstr "Hang" + +#: WikidPad.xrc:0 +msgid "Sound file:" +msgstr "Hangfájl:" + +#: WikidPad.xrc:0 +msgid "Spell Check" +msgstr "Helyesírásellenőrző" + +#: WikidPad.xrc:0 +msgid "Start browser after export" +msgstr "Böngésző indítása export után" + +#: WikidPad.xrc:0 +msgid "Statusbar time format:" +msgstr "Státuszsáv időformátuma" + +#: WikidPad.xrc:0 +msgid "Steps to complete version:" +msgstr "Változatok a teljes verzióig:" + +#: WikidPad.xrc:0 +msgid "Store relative pathes to wikis" +msgstr "Wikikben relatív útvonalak tárolása" + +#: WikidPad.xrc:0 +msgid "Structure window heading depth:" +msgstr "Struktúraablak fejlécmélysége" + +#: WikidPad.xrc:0 +msgid "Structure window position:" +msgstr "Struktúraablak pozíciója:" + +#: WikidPad.xrc:0 +msgid "Structure window selection auto-follow" +msgstr "Struktúraablak kiválasztás automatikus követése" + +#: WikidPad.xrc:0 +msgid "Swap From <-> To" +msgstr "Cserélje erről <-> erre" + +#: WikidPad.xrc:0 +msgid "Switch Ed./Prev" +msgstr "Szerk./Előnéz. vált." + +#: WikidPad.xrc:0 +msgid "Switch Tabs in MRU order" +msgstr "Fülek váltása időrenben" + +#: WikidPad.xrc:0 +msgid "Switch tab to edit" +msgstr "Előnézet: fülön szerkeszt" + +#: WikidPad.xrc:0 +msgid "Sync. highlighting limit:" +msgstr "Szinkr. kiemelési határérték:" + +#: WikidPad.xrc:0 +msgid "Synchronize editor by preview selection" +msgstr "Szerkesztő szinkronizálása az előnézeti kiválasztáshoz" + +#: WikidPad.xrc:0 +msgid "System" +msgstr "Rendszer" + +#: WikidPad.xrc:0 +msgid "Tab width:" +msgstr "Fül szélessége" + +#: WikidPad.xrc:0 +msgid "Table of contents:" +msgstr "Tartalomjegyzék:" + +#: WikidPad.xrc:0 +msgid "Template page names reg. exp.:" +msgstr "Sablonlapok reg.ex.:" + +#: WikidPad.xrc:0 +msgid "Temporary file directory:" +msgstr "Időszakos fájlok könyvtára:" + +#: WikidPad.xrc:0 +msgid "Temporary files" +msgstr "Időszakos fájlok" + +#: WikidPad.xrc:0 +msgid "Temporary path:" +msgstr "Időszakos útvonala:" + +#: WikidPad.xrc:0 +msgid "Test" +msgstr "Teszt" + +#: WikidPad.xrc:0 +msgid "Text color:" +msgstr "Szövegszin:" + +#: WikidPad.xrc:0 +msgid "Text cursor color:" +msgstr "Szövegkurzor szín:" + +#: WikidPad.xrc:0 +msgid "Timeline" +msgstr "Időrend" + +#: WikidPad.xrc:0 +msgid "Title of toc:" +msgstr "Tartalomjegyzék elnevezése:" + +#: WikidPad.xrc:0 +msgid "To Word:" +msgstr "Szóra:" + +#: WikidPad.xrc:0 +msgid "To check" +msgstr "Ellenőrzendő" + +#: WikidPad.xrc:0 +msgid "Top" +msgstr "Tetejére" + +#: WikidPad.xrc:0 +msgid "Tree auto-follow" +msgstr "Fa - automatikus követés" + +#: WikidPad.xrc:0 +msgid "Tree auto-hide" +msgstr "Fa automatikus rejtése" + +#: WikidPad.xrc:0 +msgid "Tree timing" +msgstr "Fa időzítése" + +#: WikidPad.xrc:0 +msgid "Tree update after save" +msgstr "Fa frissítése mentés után" + +#: WikidPad.xrc:0 +msgid "UI Language:" +msgstr "Kezelői felület nyelve" + +#: WikidPad.xrc:0 +msgid "UTF-8 with BOM" +msgstr "UTF-8 BOM-mal" + +#: WikidPad.xrc:0 +msgid "UTF-8 without BOM" +msgstr "UTF-8 BOM-nélkül" + +#: WikidPad.xrc:0 +msgid "Unordered" +msgstr "Rendezetlen" + +#: WikidPad.xrc:0 +msgid "Up" +msgstr "Fel" + +#: WikidPad.xrc:0 +msgid "Up to heading depth:" +msgstr "Fejléc mélységéig:" + +#: WikidPad.xrc:0 +msgid "Update step min. delay:" +msgstr "Frissítési lépések késleltetése min." + +#: WikidPad.xrc:0 +msgid "Uppercase first" +msgstr "Nagybetűsek előre" + +#: WikidPad.xrc:0 +msgid "Use IME workaround for editor input*" +msgstr "IME munkakörnyezet használata a szerkesztő bemeneteként*" + +#: WikidPad.xrc:0 +msgid "Use link title if present" +msgstr "Használja a hivatkozás elnevezését, ha létezik" + +#: WikidPad.xrc:0 +msgid "User notification:" +msgstr "Felhasználó értesítése:" + +#: WikidPad.xrc:0 +msgid "Version Import" +msgstr "Verzió import" + +#: WikidPad.xrc:0 +msgid "Visited link color:" +msgstr "Meglátogatott hivatkozások színe:" + +#: WikidPad.xrc:0 +msgid "Warn about other processes (Windows only)" +msgstr "Más folyamatokra figyelmeztet (csak Windows)" + +#: WikidPad.xrc:0 +msgid "What to export:" +msgstr "Exportálandó:" + +#: WikidPad.xrc:0 +msgid "What to print:" +msgstr "Nyomtatandó:" + +#: WikidPad.xrc:0 +msgid "Where to store:" +msgstr "Tárolás helye:" + +#: WikidPad.xrc:0 +msgid "While wiki open" +msgstr "Amíg a wiki nyitva:" + +#: WikidPad.xrc:0 +msgid "Whole wiki" +msgstr "Teljes Wiki" + +#: WikidPad.xrc:0 +msgid "Wiki Search" +msgstr "Wiki keresés" + +#: WikidPad.xrc:0 +msgid "Wiki Word" +msgstr "Wikiszó" + +#: WikidPad.xrc:0 +msgid "Wiki icon:" +msgstr "Wiki ikon:" + +#: WikidPad.xrc:0 +msgid "Wiki language:" +msgstr "Wiki nyelve" + +#: WikidPad.xrc:0 +msgid "Wiki word to heading:" +msgstr "Wikiszó fejlécbe" + +#: WikidPad.xrc:0 +msgid "Wiki-bound hotkey:" +msgstr "Wikit elrejtő gyorsbillentyű:" + +#: WikidPad.xrc:0 +msgid "Wiki-wide search" +msgstr "Wiki-szintű keresés:" + +#: WikidPad.xrc:0 +msgid "Write saved searches" +msgstr "Mentett keresések kiírása" + +#: WikidPad.xrc:0 +msgid "Write version data of pages" +msgstr "A lapok verzióadatai írja ki" + +#: WikidPad.xrc:0 +msgid "Write wiki func. pages" +msgstr "Wiki funkciólapok írása" + +#: WikidPad.xrc:0 +msgid "bytes" +msgstr "bájt" + +#: WikidPad.xrc:0 +msgid "cursor" +msgstr "kurzor" + +#: WikidPad.xrc:0 +msgid "include in export:" +msgstr "ellenőriz exportban:" + +#: WikidPad.xrc:0 +msgid "include in preview:" +msgstr "ellenőriz előnézetben:" + +#: WikidPad.xrc:0 +msgid "level(s) below" +msgstr "szinttel alatta" + +#: WikidPad.xrc:0 +msgid "milliseconds" +msgstr "ezredmásodperc" + +#: WikidPad.xrc:0 +msgid "not " +msgstr "nem " + +#: WikidPad.xrc:0 +msgid "second(s)" +msgstr "másodperc" + +#: WikidPad.xrc:0 +#: extensions\GraphvizStructureView.py:667 +#: extensions\GraphvizStructureView.py:691 +#: extensions\GraphvizStructureView.py:704 +#: extensions\GraphvizStructureView.py:717 +msgid "..." +msgstr "..." + +#: WikidPad.xrc:0 +#: lib\pwiki\AdditionalDialogs.py:793 +msgid " OK " +msgstr " OK " + +#: WikidPad.xrc:0 +#: lib\pwiki\AdditionalDialogs.py:797 +msgid " Cancel " +msgstr " Elvet " + +#: WikidPad.xrc:0 +#: lib\pwiki\AdditionalDialogs.py:1265 +msgid "Destination directory:" +msgstr "Célkönyvtár:" + +#: WikidPad.xrc:0 +#: lib\pwiki\AdditionalDialogs.py:1473 +#: lib\pwiki\SearchAndReplaceDialogs.py:1429 +msgid "Title:" +msgstr "Cím:" + +#: WikidPad.xrc:0 +#: lib\pwiki\AdditionalDialogs.py:1771 +msgid "Source directory:" +msgstr "Forráskönyvtár:" + +#: WikidPad.xrc:0 +#: lib\pwiki\MptImporterGui.py:168 +#: lib\pwiki\MptImporterGui.py:170 +msgid "Yes" +msgstr "Igen" + +#: WikidPad.xrc:0 +#: lib\pwiki\MptImporterGui.py:168 +#: lib\pwiki\MptImporterGui.py:170 +#: lib\pwiki\OptionsDialog.py:137 +#: lib\pwiki\OptionsDialog.py:854 +msgid "Default" +msgstr "Alapértelmezett" + +#: WikidPad.xrc:0 +#: lib\pwiki\MptImporterGui.py:169 +msgid "Overwrite" +msgstr "Felülír" + +#: WikidPad.xrc:0 +#: lib\pwiki\MptImporterGui.py:169 +#: lib\pwiki\MptImporterGui.py:170 +msgid "No" +msgstr "Nem" + +#: WikidPad.xrc:0 +#: lib\pwiki\MptImporterGui.py:193 +msgid "Import" +msgstr "Importálás" + +#: WikidPad.xrc:0 +#: lib\pwiki\OptionsDialog.py:670 +msgid "Versioning" +msgstr "Verziózás" + +#: WikidPad.xrc:0 +#: lib\pwiki\PersonalWikiFrame.py:1209 +#: lib\pwiki\WikiTxtCtrl.py:3387 +msgid "Undo" +msgstr "Visszavon" + +#: WikidPad.xrc:0 +#: lib\pwiki\PersonalWikiFrame.py:1213 +#: lib\pwiki\WikiTxtCtrl.py:3388 +msgid "Redo" +msgstr "Ismét" + +#: WikidPad.xrc:0 +#: lib\pwiki\PersonalWikiFrame.py:1229 +#: lib\pwiki\WikiTxtCtrl.py:3389 +msgid "Cut" +msgstr "Kivág" + +#: WikidPad.xrc:0 +#: lib\pwiki\PersonalWikiFrame.py:1234 +#: lib\pwiki\WikiTxtCtrl.py:3390 +msgid "Copy" +msgstr "Másol" + +#: WikidPad.xrc:0 +#: lib\pwiki\PersonalWikiFrame.py:1238 +#: lib\pwiki\WikiTxtCtrl.py:3391 +msgid "Paste" +msgstr "Beilleszt" + +#: WikidPad.xrc:0 +#: lib\pwiki\PersonalWikiFrame.py:1243 +#: lib\pwiki\WikiTxtCtrl.py:3393 +msgid "Select All" +msgstr "Kiválaszt mind" + +#: WikidPad.xrc:0 +#: lib\pwiki\PersonalWikiFrame.py:1551 +msgid "&Delete" +msgstr "Töröl" + +#: WikidPad.xrc:0 +#: lib\pwiki\PersonalWikiFrame.py:1974 +#: lib\pwiki\PersonalWikiFrame.py:1975 +msgid "Open Wiki Word" +msgstr "Wikiszó megnyitása" + +#: WikidPad.xrc:0 +#: lib\pwiki\PersonalWikiFrame.py:1999 +#: lib\pwiki\PersonalWikiFrame.py:2000 +msgid "Rename Wiki Word" +msgstr "Wikiszó átnevezése" + +#: WikidPad.xrc:0 +#: lib\pwiki\PersonalWikiFrame.py:2005 +#: lib\pwiki\WikiTxtCtrl.py:3392 +msgid "Delete" +msgstr "Töröl" + +#: WikidPad.xrc:0 +#: lib\pwiki\SearchAndReplaceDialogs.py:819 +msgid "Close" +msgstr "Bezár" + +#: WikidPad.xrc:0 +#: lib\pwiki\SearchAndReplaceDialogs.py:1655 +msgid "Set page list" +msgstr "Oldallista beállítása" + +#: WikidPad.xrc:0 +#: lib\pwiki\SearchAndReplaceDialogs.py:1903 +msgid "As Resultlist" +msgstr "Eredménylistaként" + +#: WikidPad.xrc:0 +#: lib\pwiki\SearchAndReplaceDialogs.py:1908 +#: lib\pwiki\SearchAndReplaceDialogs.py:2014 +msgid "As Full Search" +msgstr "Teljes keresésként" + +#: WikidPad.xrc:0 +#: lib\pwiki\SearchAndReplaceDialogs.py:2308 +#: lib\pwiki\WikiHtmlView.py:711 +msgid "Activate New Tab" +msgstr "Új Fül Aktiválása" + +#: WikidPad.xrc:0 +#: lib\pwiki\WikiTxtCtrl.py:3407 +msgid "Close Tab" +msgstr "Fül Bezárása" + +#: WikidPad.xrc:0 +#: lib\pwiki\timeView\TimelinePanel.py:798 +msgid "Show empty days" +msgstr "Üres napok megjelenítése" + +#: WikidPad.xrc:0 +#: lib\pwiki\timeView\TimelinePanel.py:800 +msgid "Sort dates ascending" +msgstr "Dátumokat emelkedő sorrendben" + +#: WikidPadStarter.py:196 +msgid "Error starting WikidPad" +msgstr "Hiba WikidPad indításakor" + +#: WikidPadStarter.py:197 +#: lib\pwiki\PersonalWikiFrame.py:5084 +#: lib\pwiki\SearchAndReplaceDialogs.py:688 +#: lib\pwiki\SearchAndReplaceDialogs.py:968 +msgid "Error!" +msgstr "Hiba!" + +#: WikidPadStarter.py:204 +#: lib\pwiki\MptImporterGui.py:197 +msgid "Error" +msgstr "Hiba" + +#: extensions\GnuplotClBridge.py:91 +msgid "[Please set path to Gnuplot executable]" +msgstr "[Állítsa be a futtatható Gnuplot útvonalát]" + +#: extensions\GnuplotClBridge.py:129 +msgid "[Gnuplot error: %s]" +msgstr "[Gnuplot hiba: %s]" + +#: extensions\GnuplotClBridge.py:180 +msgid "Path to Gnuplot:" +msgstr "Gnuplot útvonala:" + +#: extensions\GraphvizClBridge.py:106 +msgid "[Please set path to GraphViz executables]" +msgstr "[Állítsa be a futtatható GraphViz útvonalát]" + +#: extensions\GraphvizClBridge.py:137 +msgid "[%s Error: %s]" +msgstr "[%s Hiba: %s]" + +#: extensions\GraphvizClBridge.py:238 +msgid "Directory of executables:" +msgstr "Parancskönyvtár:" + +#: extensions\GraphvizClBridge.py:242 +msgid "Name of dot executable:" +msgstr "Dot parancs neve:" + +#: extensions\GraphvizClBridge.py:246 +msgid "Name of neato executable:" +msgstr "Neato parancsok neve:" + +#: extensions\GraphvizClBridge.py:250 +msgid "Name of twopi executable:" +msgstr "Twopi parancsok neve:" + +#: extensions\GraphvizClBridge.py:254 +msgid "Name of circo executable:" +msgstr "Circo parancsok neve:" + +#: extensions\GraphvizClBridge.py:258 +msgid "Name of fdp executable:" +msgstr "Fdp parancsok neve:" + +#: extensions\GraphvizStructureView.py:355 +msgid "%s Error: %s" +msgstr "%s Hiba: %s" + +#: extensions\GraphvizStructureView.py:421 +#: extensions\GraphvizStructureView.py:422 +msgid "Show relation graph" +msgstr "Mutassa a relációs ábrát" + +#: extensions\GraphvizStructureView.py:424 +msgid "Show rel. graph source" +msgstr "Mutassa a relációs ábra forrását" + +#: extensions\GraphvizStructureView.py:426 +msgid "Show relation graph source" +msgstr "Mutassa a relációs ábra forrását" + +#: extensions\GraphvizStructureView.py:428 +#: extensions\GraphvizStructureView.py:429 +msgid "Show child graph" +msgstr "Mutassa a leszármazott ábrát" + +#: extensions\GraphvizStructureView.py:431 +#: extensions\GraphvizStructureView.py:433 +msgid "Show child graph source" +msgstr "Mutassa a leszármaott ábra forrását" + +#: extensions\GraphvizStructureView.py:650 +msgid " GraphVizStructure" +msgstr " GraphVizStruktúra" + +#: extensions\GraphvizStructureView.py:670 +msgid "Node font name:" +msgstr "Csomópont betűtípusa:" + +#: extensions\GraphvizStructureView.py:682 +msgid "Node font size:" +msgstr "Csomópont betűmérete" + +#: extensions\GraphvizStructureView.py:694 +msgid "Node border color:" +msgstr "Csomópont határoló szín:" + +#: extensions\GraphvizStructureView.py:707 +msgid "Node background color:" +msgstr "Csomópont háttérszín:" + +#: extensions\GraphvizStructureView.py:720 +msgid "Edge color:" +msgstr "Nyílhegy szine:" + +#: extensions\MimeTexCGIBridge.py:92 +msgid "[Please set path to MimeTeX executable]" +msgstr "[Állítsa be a parancs Mimetex elérési útját]" + +#: extensions\MimeTexCGIBridge.py:111 +msgid "[Invalid response from MimeTeX]" +msgstr "[Érvénytelen válasz a MimeTeX-től]" + +#: extensions\MimeTexCGIBridge.py:172 +msgid "Path to MimeTeX:" +msgstr "MimeTex-útvonal:" + +#: extensions\PloticusClBridge.py:108 +msgid "[Please set path to Ploticus executable]" +msgstr "[Állítsa be a Ploticus parancs elérési útját]" + +#: extensions\PloticusClBridge.py:140 +msgid "[Ploticus error: %s]" +msgstr "[Ploticus hiba: %s]" + +#: extensions\PloticusClBridge.py:200 +msgid "Path to Ploticus:" +msgstr "Ploticus-útvonal" + +#: extensions\PloticusClBridge.py:206 +msgid "Output format:" +msgstr "Kimeneti formátum:" + +#: extensions\WikidPadParserStub.py:121 +msgid "Footnotes as wiki words" +msgstr "Lábjegyzetek wikiszóként" + +#: extensions\autoNew.py:58 +#: extensions\autoNew.py:59 +msgid "Create new page" +msgstr "Új lap" + +#: extensions\referrals.py:74 +#: extensions\referrals.py:75 +#: extensions\referrals.py:109 +#: extensions\referrals.py:129 +msgid "Insert referring pages" +msgstr "Hivatkozó lapok beillesztése" + +#: extensions\referrals.py:109 +#: extensions\referrals.py:129 +msgid "Referers" +msgstr "Hivatkozók" + +#: extensions\referrals.py:147 +msgid "*%s page(s) referring to* %s\n" +msgstr "*%s lap(ok) hivatkozi(na)k a* %s-ra\n" + +#: extensions\referrals.py:156 +msgid "*%s page(s) referred to by* %s\n" +msgstr "*%s lap(ok)ra a *%s hivatkozi(na)k \n" + +#: extensions\wikidPadParser\WikidPadParser.py:1580 +#: extensions\wikidPadParser\WikidPadParser.py:1607 +msgid "This is a footnote" +msgstr "Lábjegyzet" + +#: extensions\wikidPadParser\WikidPadParser.py:1585 +#: extensions\wikidPadParser\WikidPadParser.py:1612 +msgid "This is syntactically not a wiki word" +msgstr "Szintaktikailag nem wikiszó" + +#: extensions\wikidPadParser\WikidPadParser.py:2234 +msgid "" +"++ Wiki Settings\n" +"\n" +"These are your default global settings.\n" +"\n" +"[global.importance.low.color: grey]\n" +"[global.importance.high.bold: true]\n" +"[global.contact.icon: contact]\n" +"[global.wrap: 70]\n" +"\n" +"[icon: cog]\n" +msgstr "" +"++ Wiki beállítások\n" +"\n" +"Az Ön glogális wiki alapbeállításai.\n" +"\n" +"[global.importance.low.color: grey]\n" +"[global.importance.high.bold: true]\n" +"[global.contact.icon: contact]\n" +"[global.wrap: 70]\n" +"\n" +"[icon: cog]\n" + +#: lib\pwiki\AdditionalDialogs.py:145 +#: lib\pwiki\AdditionalDialogs.py:356 +msgid "Links to:" +msgstr "Link ehhez:" + +#: lib\pwiki\AdditionalDialogs.py:286 +#: lib\pwiki\AdditionalDialogs.py:398 +msgid "'%s' is an invalid WikiWord" +msgstr "'%s' szabálytalan Wikiszó" + +#: lib\pwiki\AdditionalDialogs.py:293 +msgid "'%s' is not an existing wikiword. Create?" +msgstr "'%s' nem létező wikiszó. Létrehozzam?" + +#: lib\pwiki\AdditionalDialogs.py:294 +msgid "Create" +msgstr "Létrehoz" + +#: lib\pwiki\AdditionalDialogs.py:404 +msgid "'%s' exists already" +msgstr "'%s' már létezik" + +#: lib\pwiki\AdditionalDialogs.py:418 +#: lib\pwiki\AdditionalDialogs.py:511 +msgid "Do you want to delete %i wiki page(s)?" +msgstr "Törölni akarja %i wikilap(oka)t?" + +#: lib\pwiki\AdditionalDialogs.py:670 +msgid "" +"Can't process renaming:\n" +"%s" +msgstr "" +"Az átnevezés nem folytatható:\n" +"%s" + +#: lib\pwiki\AdditionalDialogs.py:671 +msgid "Can't rename" +msgstr "Nem átnevezhető" + +#: lib\pwiki\AdditionalDialogs.py:709 +msgid "Can't rename to itself" +msgstr "Önmagára nem átnevezhető" + +#: lib\pwiki\AdditionalDialogs.py:713 +#: lib\pwiki\WikiExceptions.py:73 +msgid "Word already exists" +msgstr "Szó már létezik" + +#: lib\pwiki\AdditionalDialogs.py:766 +msgid "Select Icon" +msgstr "Ikon kiválasztás" + +#: lib\pwiki\AdditionalDialogs.py:775 +msgid "Icon" +msgstr "Ikon" + +#: lib\pwiki\AdditionalDialogs.py:926 +msgid "" +"\n" +"\n" +"\n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
DirectiveMeaning
%aLocale's abbreviated weekday name.
%ALocale's full weekday name.
%bLocale's abbreviated month name.
%BLocale's full month name.
%cLocale's appropriate date and time representation.
%dDay of the month as a decimal number [01,31].
%HHour (24-hour clock) as a decimal number [00,23].
%IHour (12-hour clock) as a decimal number [01,12].
%jDay of the year as a decimal number [001,366].
%mMonth as a decimal number [01,12].
%MMinute as a decimal number [00,59].
%pLocale's equivalent of either AM or PM.
%SSecond as a decimal number [00,61].
%uWeekday as a decimal number [1(Monday),7].
%UWeek number of the year (Sunday as the first day of the\n" +" week) as a decimal number [00,53]. All days in a new year\n" +" preceding the first Sunday are considered to be in week 0.
%wWeekday as a decimal number [0(Sunday),6].
%WWeek number of the year (Monday as the first day of the\n" +" week) as a decimal number [00,53]. All days in a new year\n" +" preceding the first Monday are considered to be in week 0.
%xLocale's appropriate date representation.
%XLocale's appropriate time representation.
%yYear without century as a decimal number [00,99].
%YYear with century as a decimal number.
%ZTime zone name (no characters if no time zone exists).
%%A literal \"%\" character.
\\n" +"A newline.
\\\\A literal \"\\\" character.
\n" +"\n" +"\n" +msgstr "" +"\n" +"\n" +"\n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
UtasításJelentés
%aA hét napjai rövidítve helyi nyelven
%AA hét napjai helyi nyelven.
%bA hónapok rövidítve helyi nyelven.
%BA hónapok neve helyi nyelven.
%cDátum és idő szabályos megjelenítése helyi nyelven.
%dA hónap napjai számokkal. [01,31].
%HÓra (24 órás megjelenítés) számokkal [00,23].
%IÓra (12 órás megjelenítés) számokkal. [01,12].
%jÉv napja számokkal [001,366].
%mHónapok számokkal. [01,12].
%MPercek, számokkal. [00,59].
%pDE DU megfelelője helyi nyelven.
%SMásodperc számokkal [00,61].
%uHét napja számokkal [1(Hétfő),7].
%UAz év hetének sorszáma (vasárnap az első nap\n" +" a héten) [00,53]. Az új évet következő napok\n" +" számítanak és az első vasárnap a 0. hétre esik.
%wA hét napja számokkal [0(vasárnap),6].
%WAz év hetének sorszáma (hétfő az első nap a\n" +" héten) [00,53]. Az új évet következő napok\n" +" számítanak és az első hétfő a 0. hétre esik.
%xA dátum szabályos megjelenítése helyi nyelven.
%XAz idő szabályos megjelenítése helyi nyelven.
%yÉvszám arabszámmal évszázad nélkül [00,99].
%YÉvszám arabszámmal évszázaddal .
%ZAz időzóna elnevezése (nincs betű nemlétező időzónánál).
%%Betű \"%\" karakter.
\\n" +"Új sor
\\\\Betű \"\\\" Karakter.
\n" +"\n" +"\n" + +#: lib\pwiki\AdditionalDialogs.py:1036 +msgid "" +msgstr "<érvénytelen>" + +#: lib\pwiki\AdditionalDialogs.py:1166 +msgid "Continuous Export" +msgstr "Folyamatos export" + +#: lib\pwiki\AdditionalDialogs.py:1268 +msgid "Destination file:" +msgstr "Célfájl:" + +#: lib\pwiki\AdditionalDialogs.py:1300 +msgid "Destination directory does not exist" +msgstr "Célkönyvtár nem létezik" + +#: lib\pwiki\AdditionalDialogs.py:1305 +msgid "Destination must be a directory" +msgstr "A cél, könyvtár kell legyen" + +#: lib\pwiki\AdditionalDialogs.py:1311 +msgid "Destination must be a file" +msgstr "A cél, fálj kell legyen" + +#: lib\pwiki\AdditionalDialogs.py:1321 +#: lib\pwiki\PersonalWikiFrame.py:4685 +msgid "Exporting" +msgstr "Exportál" + +#: lib\pwiki\AdditionalDialogs.py:1323 +#: lib\pwiki\PersonalWikiFrame.py:4687 +msgid "Preparing" +msgstr "Előkészítés" + +#: lib\pwiki\AdditionalDialogs.py:1343 +msgid "Error while exporting" +msgstr "Hiba exportáláskor" + +#: lib\pwiki\AdditionalDialogs.py:1359 +#: lib\pwiki\PersonalWikiFrame.py:4633 +msgid "Select Export Directory" +msgstr "Válassza ki ez exportkönyvtárat" + +#: lib\pwiki\AdditionalDialogs.py:1373 +#: lib\pwiki\AdditionalDialogs.py:1844 +msgid "All files (*.*)" +msgstr "Összes fájl (*.*)" + +#: lib\pwiki\AdditionalDialogs.py:1378 +msgid "Select Export File" +msgstr "Exportfájl Kiválasztása" + +#: lib\pwiki\AdditionalDialogs.py:1405 +#: lib\pwiki\PersonalWikiFrame.py:4656 +#: lib\pwiki\PersonalWikiFrame.py:4670 +#: lib\pwiki\Printing.py:182 +msgid "No real wiki word selected as root" +msgstr "A gyökérnek választott nem wikiszó" + +#: lib\pwiki\AdditionalDialogs.py:1474 +msgid "Choose export title" +msgstr "Export-cím kiválasztása" + +#: lib\pwiki\AdditionalDialogs.py:1483 +msgid "Do you want to overwrite existing export '%s'?" +msgstr "Felül akarja írni a létező exportot '%s'?" + +#: lib\pwiki\AdditionalDialogs.py:1484 +msgid "Overwrite export" +msgstr "Export felülírása" + +#: lib\pwiki\AdditionalDialogs.py:1531 +msgid "Do you want to delete %i export(s)?" +msgstr "Törölni akarja az %i exporto(ka)t?" + +#: lib\pwiki\AdditionalDialogs.py:1532 +msgid "Delete export" +msgstr "Export törlése" + +#: lib\pwiki\AdditionalDialogs.py:1569 +msgid "Selected export type does not support saving" +msgstr "A választot exporttípus nem támogatja a mentést" + +#: lib\pwiki\AdditionalDialogs.py:1613 +msgid "Export type '%s' of saved export is not supported" +msgstr "A(z) '%s' mentett export típusa nem támogatott " + +#: lib\pwiki\AdditionalDialogs.py:1624 +msgid "" +"Saved export uses different version for additional options than current export\n" +"Export type: '%s'\n" +"Saved export version: %i\n" +"Current export version: %i" +msgstr "" +"A mentett export a kiegészítő opciókra eltérő verziót használ mint a jelenlegi export\n" +"Exportport típusa: '%n'\n" +"Mentett export verziója: %i\n" +"Aktuális export verziója: %i" + +#: lib\pwiki\AdditionalDialogs.py:1632 +msgid "Type of additional option storage ('%s') is unknown" +msgstr "A további opciók tárolásának típusa ('%s') ismeretlen" + +#: lib\pwiki\AdditionalDialogs.py:1663 +msgid "Error during retrieving saved export: " +msgstr "Hiba a mentett export beolvasásakor:" + +#: lib\pwiki\AdditionalDialogs.py:1774 +msgid "Source file:" +msgstr "Forrásfájl:" + +#: lib\pwiki\AdditionalDialogs.py:1790 +msgid "Source does not exist" +msgstr "Forrás nem létezik" + +#: lib\pwiki\AdditionalDialogs.py:1800 +msgid "Source must be a directory" +msgstr "A forrás, könyvtár kell legyen" + +#: lib\pwiki\AdditionalDialogs.py:1805 +msgid "Source must be a file" +msgstr "A forrás, fálj kell legyen" + +#: lib\pwiki\AdditionalDialogs.py:1816 +msgid "Error while importing" +msgstr "Hiba importáláskor" + +#: lib\pwiki\AdditionalDialogs.py:1830 +msgid "Select Import Directory" +msgstr "Import könyvtár kiválasztás" + +#: lib\pwiki\AdditionalDialogs.py:1845 +msgid "*" +msgstr "*" + +#: lib\pwiki\AdditionalDialogs.py:1849 +msgid "Select Import File" +msgstr "Importfájlt kiválasztás" + +#: lib\pwiki\AdditionalDialogs.py:1962 +msgid "" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +"

%s

\n" +"\n" +"

\n" +"wikidPad is a Wiki-like notebook for storing your thoughts, ideas, todo lists, contacts, or anything else you can think of to write down.\n" +"What makes wikidPad different from other notepad applications is the ease with which you can cross-link your information.

\n" +"

\n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
Author:Michael Butscher
Email:mbutscher@gmx.de
URL:http://www.mbutscher.de/software.html
 
Author:Jason Horman
Email:wikidpad@jhorman.org
URL:http://www.jhorman.org/wikidPad/
 
Author:Gerhard Reitmayr
Email:gerhard.reitmayr@gmail.com
 
 
Translations:
Swedish:Stefan Berg
\n" +"
\n" +" \n" +" \n" +"\n" +" \n" +"
\n" +" \n" +"

Your configuration directory is: %s
\n" +" Sqlite version: %s
\n" +" wxPython version: %s\n" +"

\n" +" \n" +"\n" +"\n" +msgstr "" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +"

%s

\n" +"\n" +"

\n" +"A WikidPad egy Wikiszerű jegyzettömb, gondolatok, ötletek, tennivalók, kapcsolatok, vagy bármi más feljegyzésére, amit fontosnak gondol leírni.\n" +"Ami a WikidPad-ot megkülönbözteti más jegyzettömb alkalmazásoktól, az a könnyedség, amivel információhoz kereszthivatkozásokat készíthe.

\n" +"

\n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
Author:Michael Butscher
Email:mbutscher@gmx.de
URL:http://www.mbutscher.de/software.html
 
Author:Jason Horman
Email:wikidpad@jhorman.org
URL:http://www.jhorman.org/wikidPad/
 
Author:Gerhard Reitmayr
Email:gerhard.reitmayr@gmail.com
 
 
Fordítás:
Swedish:Stefan Berg
Magyar:Török Árpád
\n" +"
\n" +" \n" +" \n" +"\n" +" \n" +"
\n" +" \n" +"

Az ön konfigurációs könyvtára: %s
\n" +" Sqlite verzió: %s
\n" +" wxPython verzió: %s\n" +"

\n" +" \n" +"\n" +"\n" + +#: lib\pwiki\AdditionalDialogs.py:2010 +#: lib\pwiki\PersonalWikiFrame.py:1892 +msgid "About WikidPad" +msgstr "WikidPad névjegye" + +#: lib\pwiki\AdditionalDialogs.py:2014 +msgid "N/A" +msgstr "Nincs Adat" + +#: lib\pwiki\AdditionalDialogs.py:2024 +msgid "Okay" +msgstr "Rendben" + +#: lib\pwiki\AdditionalDialogs.py:2132 +msgid "No wiki loaded" +msgstr "Nincs betöltött Wikilap" + +#: lib\pwiki\AdditionalDialogs.py:2136 +msgid "Wiki config. path:" +msgstr "Wiki konfig. útvonal" + +#: lib\pwiki\AdditionalDialogs.py:2142 +msgid "Wiki database backend:" +msgstr "Wiki adatbázisfeldolgozó" + +#: lib\pwiki\AdditionalDialogs.py:2150 +msgid "Number of wiki pages:" +msgstr "Wiki lapok száma:" + +#: lib\pwiki\AdditionalDialogs.py:2159 +msgid "Wiki is read-only. Reason:" +msgstr "A wiki csak olvasható, mert:" + +#: lib\pwiki\AdditionalDialogs.py:2162 +msgid "Write access to database lost. Try \"Wiki\"->\"Reconnect\"" +msgstr "Az adatbázis írási elérése megszűnt. Próbálja a \"Wik\"->\"Újracsatlakozi\"" + +#: lib\pwiki\AdditionalDialogs.py:2164 +msgid "Wiki was set read-only in options dialog" +msgstr "A wiki az opcióknál csak olvashatóra lett állítva" + +#: lib\pwiki\AdditionalDialogs.py:2169 +#: lib\pwiki\AdditionalDialogs.py:2171 +msgid "Can't write wiki config.:" +msgstr "Nem tudja írni a wiki konfig.-ot " + +#: lib\pwiki\AdditionalDialogs.py:2169 +#: lib\pwiki\AdditionalDialogs.py:2173 +msgid "Unknown reason" +msgstr "Ismeretlen ok" + +#: lib\pwiki\AdditionalDialogs.py:2203 +msgid "Number of Jobs:" +msgstr "Job-ok száma:" + +#: lib\pwiki\AttributeHandling.py:32 +msgid "AQUAMARINE" +msgstr "KÉKESZÖLD" + +#: lib\pwiki\AttributeHandling.py:33 +msgid "BLACK" +msgstr "FEKETE" + +#: lib\pwiki\AttributeHandling.py:34 +msgid "BLUE VIOLET" +msgstr "IBOLYAKÉK" + +#: lib\pwiki\AttributeHandling.py:35 +msgid "BLUE" +msgstr "KÉK" + +#: lib\pwiki\AttributeHandling.py:36 +msgid "BROWN" +msgstr "BARNA" + +#: lib\pwiki\AttributeHandling.py:37 +msgid "CADET BLUE" +msgstr "ISKOLAKÉK" + +#: lib\pwiki\AttributeHandling.py:38 +msgid "CORAL" +msgstr "KORALL" + +#: lib\pwiki\AttributeHandling.py:39 +msgid "CORNFLOWER BLUE" +msgstr "BÚZAVIRÁGKÉK" + +#: lib\pwiki\AttributeHandling.py:40 +msgid "CYAN" +msgstr "CIÁN" + +#: lib\pwiki\AttributeHandling.py:41 +msgid "DARK GREEN" +msgstr "SÖTÉTZÖLD" + +#: lib\pwiki\AttributeHandling.py:42 +msgid "DARK GREY" +msgstr "SÖTÉTSZÜRKE" + +#: lib\pwiki\AttributeHandling.py:43 +msgid "DARK OLIVE GREEN" +msgstr "SÖTÉT OLIVAZÖLD" + +#: lib\pwiki\AttributeHandling.py:44 +msgid "DARK ORCHID" +msgstr "SÖTÉR ORCHIDEA" + +#: lib\pwiki\AttributeHandling.py:45 +msgid "DARK SLATE BLUE" +msgstr "SÖTÉT PALAKÉK" + +#: lib\pwiki\AttributeHandling.py:46 +msgid "DARK SLATE GREY" +msgstr "SÖTÉT PALASZÜRKE" + +#: lib\pwiki\AttributeHandling.py:47 +msgid "DARK TURQUOISE" +msgstr "SÖTÉT TÜRKIZ" + +#: lib\pwiki\AttributeHandling.py:48 +msgid "DIM GREY" +msgstr "HALVÁNY SZÜRKE" + +#: lib\pwiki\AttributeHandling.py:49 +msgid "FIREBRICK" +msgstr "TÉGLAVÖRÖS" + +#: lib\pwiki\AttributeHandling.py:50 +msgid "FOREST GREEN" +msgstr "ERDEI ZÖLD" + +#: lib\pwiki\AttributeHandling.py:51 +msgid "GOLD" +msgstr "ARANY" + +#: lib\pwiki\AttributeHandling.py:52 +msgid "GOLDENROD" +msgstr "ARANYRÚD" + +#: lib\pwiki\AttributeHandling.py:53 +msgid "GREEN YELLOW" +msgstr "ZÖLDESSÁRGA" + +#: lib\pwiki\AttributeHandling.py:54 +msgid "GREEN" +msgstr "ZÖLD" + +#: lib\pwiki\AttributeHandling.py:55 +msgid "GREY" +msgstr "SZÜRKE" + +#: lib\pwiki\AttributeHandling.py:56 +msgid "INDIAN RED" +msgstr "INDIÁNVÖRÖS" + +#: lib\pwiki\AttributeHandling.py:57 +msgid "KHAKI" +msgstr "KEKI" + +#: lib\pwiki\AttributeHandling.py:58 +msgid "LIGHT BLUE" +msgstr "VILÁGOSKÉK" + +#: lib\pwiki\AttributeHandling.py:59 +msgid "LIGHT GREY" +msgstr "VILÁGOSSZÜRKE" + +#: lib\pwiki\AttributeHandling.py:60 +msgid "LIGHT STEEL BLUE" +msgstr "VILÁGOS ACÉLKÉK" + +#: lib\pwiki\AttributeHandling.py:61 +msgid "LIME GREEN" +msgstr "LIME ZÖLD" + +#: lib\pwiki\AttributeHandling.py:62 +msgid "MAGENTA" +msgstr "BÍBORVÖRÖS" + +#: lib\pwiki\AttributeHandling.py:63 +msgid "MAROON" +msgstr "GESZTENYE" + +#: lib\pwiki\AttributeHandling.py:64 +msgid "MEDIUM AQUAMARINE" +msgstr "KÖZÉP KÉKESZÖLD" + +#: lib\pwiki\AttributeHandling.py:65 +msgid "MEDIUM BLUE" +msgstr "KÖZÉPKÉK" + +#: lib\pwiki\AttributeHandling.py:66 +msgid "MEDIUM FOREST GREEN" +msgstr "KÖZÉP ERDEI ZÖLD" + +#: lib\pwiki\AttributeHandling.py:67 +msgid "MEDIUM GOLDENROD" +msgstr "KŐZÉP ARANYRÚD" + +#: lib\pwiki\AttributeHandling.py:68 +msgid "MEDIUM ORCHID" +msgstr "KÖZÉP ORCHIDEA" + +#: lib\pwiki\AttributeHandling.py:69 +msgid "MEDIUM SEA GREEN" +msgstr "KÖZÉP TENGERÉSKÉK" + +#: lib\pwiki\AttributeHandling.py:70 +msgid "MEDIUM SLATE BLUE" +msgstr "KÖZÉP PALAKÉK" + +#: lib\pwiki\AttributeHandling.py:71 +msgid "MEDIUM SPRING GREEN" +msgstr "KÖZÉP TAVASZI ZÖLD" + +#: lib\pwiki\AttributeHandling.py:72 +msgid "MEDIUM TURQUOISE" +msgstr "KÖZÉP TÜRKIZ" + +#: lib\pwiki\AttributeHandling.py:73 +msgid "MEDIUM VIOLET RED" +msgstr "KÖZÉP VIOLAVÖRÖS" + +#: lib\pwiki\AttributeHandling.py:74 +msgid "MIDNIGHT BLUE" +msgstr "ÉJKÉK" + +#: lib\pwiki\AttributeHandling.py:75 +msgid "NAVY" +msgstr "TENGERÉSZKÉK" + +#: lib\pwiki\AttributeHandling.py:76 +msgid "ORANGE RED" +msgstr "NARANCSVÖRÖS" + +#: lib\pwiki\AttributeHandling.py:77 +msgid "ORANGE" +msgstr "NARANCSSÁRGA" + +#: lib\pwiki\AttributeHandling.py:78 +msgid "ORCHID" +msgstr "ORCHIDEA" + +#: lib\pwiki\AttributeHandling.py:79 +msgid "PALE GREEN" +msgstr "HALVÁNYZÖLD" + +#: lib\pwiki\AttributeHandling.py:80 +msgid "PINK" +msgstr "RÓZSSZÍN" + +#: lib\pwiki\AttributeHandling.py:81 +msgid "PLUM" +msgstr "SZILVAKÉK" + +#: lib\pwiki\AttributeHandling.py:82 +msgid "PURPLE" +msgstr "BORDÓ" + +#: lib\pwiki\AttributeHandling.py:83 +msgid "RED" +msgstr "VÖRÖS" + +#: lib\pwiki\AttributeHandling.py:84 +msgid "SALMON" +msgstr "LAZACVÖRÖS" + +#: lib\pwiki\AttributeHandling.py:85 +msgid "SEA GREEN" +msgstr "TENGERZÖLD" + +#: lib\pwiki\AttributeHandling.py:86 +msgid "SIENNA" +msgstr "SZIENNA" + +#: lib\pwiki\AttributeHandling.py:87 +msgid "SKY BLUE" +msgstr "ÉGKÉK" + +#: lib\pwiki\AttributeHandling.py:88 +msgid "SLATE BLUE" +msgstr "PALAKÉK" + +#: lib\pwiki\AttributeHandling.py:89 +msgid "SPRING GREEN" +msgstr "TAVASZI ZÖLD" + +#: lib\pwiki\AttributeHandling.py:90 +msgid "STEEL BLUE" +msgstr "ACÉLKÉK" + +#: lib\pwiki\AttributeHandling.py:91 +msgid "TAN" +msgstr "KREOL" + +#: lib\pwiki\AttributeHandling.py:92 +msgid "THISTLE" +msgstr "BOGÁNCS" + +#: lib\pwiki\AttributeHandling.py:93 +msgid "TURQUOISE" +msgstr "TÜRKIZ" + +#: lib\pwiki\AttributeHandling.py:94 +msgid "VIOLET RED" +msgstr "VIOLAVÖRÖS" + +#: lib\pwiki\AttributeHandling.py:95 +msgid "VIOLET" +msgstr "VIOLA" + +#: lib\pwiki\AttributeHandling.py:96 +msgid "WHEAT" +msgstr "BÚZA" + +#: lib\pwiki\AttributeHandling.py:97 +msgid "WHITE" +msgstr "FEHÉR" + +#: lib\pwiki\AttributeHandling.py:98 +msgid "YELLOW GREEN" +msgstr "SÁRGÁSZÖLD" + +#: lib\pwiki\AttributeHandling.py:99 +msgid "YELLOW" +msgstr "SÁRGA" + +#: lib\pwiki\AttributeHandling.py:286 +msgid "Alias value isn't a valid wikiword: [%s: %s], %s" +msgstr "Az álnév nem valós wikiszó: [%s: %s], %s" + +#: lib\pwiki\AttributeHandling.py:297 +msgid "A real wikiword with the alias name exists already: [%s: %s]" +msgstr "Wikiszó ezzel az álnévvel már létezik: [%s: %s]" + +#: lib\pwiki\AttributeHandling.py:348 +msgid "Template value isn't a valid wikiword: [%s: %s], %s" +msgstr "A sablonérték nem valós wikiszó: [%s: %s], %s" + +#: lib\pwiki\AttributeHandling.py:359 +msgid "Template value isn't an existing wikiword: [%s: %s]" +msgstr "A sablonérték nemlétező wikiszó: [%s: %s]" + +#: lib\pwiki\AttributeHandling.py:406 +msgid "The attribute %s was already set differently on this page" +msgstr "A(z) %s attribútumot már beállították másként a lapon" + +#: lib\pwiki\AttributeHandling.py:418 +msgid "Attribute '%s' is already defined on the wiki page(s): %s" +msgstr "A(z) %s attribútumot már beállították a wikilap(ok)on: %s" + +#: lib\pwiki\AttributeHandling.py:427 +msgid "Icon name doesn't exist: [%s: %s]" +msgstr "Nemlétező ikonnév: [%s: %s]" + +#: lib\pwiki\AttributeHandling.py:434 +msgid "Color name doesn't exist: [%s: %s]" +msgstr "Nemlétező szín: [%s: %s]" + +#: lib\pwiki\AttributeHandling.py:481 +msgid "The attribute 'global.graph.relations.exclude' (e.g. on page '%s') overrides the '...include' attribute" +msgstr "A 'global.graph.relations.exclude' attribútum (pl. a '%s' lapon) felülírja a(z) '...include' attribútumot" + +#: lib\pwiki\AttributeHandling.py:607 +msgid "Same attribute twice: [%s: %s]" +msgstr "Azonos attribútum kétszer: [%s: %s]" + +#: lib\pwiki\CmdLineAction.py:238 +msgid "" +"Options:\n" +"\n" +" -h, --help: Show this message box\n" +" -w, --wiki : set the wiki to open on startup\n" +" -p, --page : set the page to open on startup\n" +" -x, --exit: exit immediately after performing command line actions\n" +" --export-what : choose if you want to export page, subtree or wiki\n" +" --export-type : tag of the export type\n" +" --export-dest : path of destination directory for export\n" +" --export-compfn: Use compatible filenames on export\n" +" --rebuild: rebuild the Wiki database\n" +" --no-recent: Do not record opened wikis in recently opened wikis list\n" +" --preview: If no pages are given, all opened pages from previous session\n" +" are opened in preview mode. Otherwise all pages given after that\n" +" option are opened in preview mode.\n" +" --editor: Same as --preview but opens in text editor mode.\n" +"\n" +msgstr "" +"Opciók:\n" +"\n" +" -h, --help: Ezen üzenetdoboz megjelenítése \n" +" -w, --wiki : indításkor megnyitandó wiki beállítása\n" +" -p, --page : indításkor megnyitandó lap beállítása\n" +" -x, --exit: a parancssor végrehajtását követően azonnal kilép\n" +" --export-what : válassza, ha exportálni szeretne lapot, alárendelt fát, vagy wikit \n" +" --export-type : az export típus kiterjesztése\n" +" --export-dest : az exportálás célkönyvtára\n" +" --export-compfn: exportáláskor kompatibilis fájlnév alkalmazása\n" +" --rebuild: Wiki adatbázis újraépítése\n" +" --no-recent: Ne jegyezze meg a megnyitott wikiket az \"Előzményekben\"\n" +" --preview: Ha nincsenek lapok megadva, akkor az előző mentben megnyitott valamennyit\n" +" megnyitja előnézetben. Ellenkező esetben az opció után meghatározott lapokat\n" +" nyitja meg előnézetben.\n" +" --editor: Mint a --preview, de szerkesztő módban nyitja meg.\n" +"\n" + +#: lib\pwiki\CmdLineAction.py:261 +msgid "Usage information" +msgstr "Alkalmazási információ" + +#: lib\pwiki\Configuration.py:205 +#: lib\pwiki\Configuration.py:252 +#: lib\pwiki\Configuration.py:370 +#: lib\pwiki\Configuration.py:376 +#: lib\pwiki\Configuration.py:421 +msgid "Unknown option %s:%s" +msgstr "Ismeretlen opció %s:%s" + +#: lib\pwiki\Configuration.py:279 +msgid "Config file not found" +msgstr "Konfigurációs fájl nem található" + +#: lib\pwiki\Configuration.py:413 +msgid "Ambiguos option set %s:%s" +msgstr "Ambíciózus opciók beállítva %s:%s" + +#: lib\pwiki\DiffGui.py:155 +msgid "" +msgstr "" + +#: lib\pwiki\DocPagePresenter.py:231 +msgid "'%s' is an invalid wiki word. %s." +msgstr "'%s' érvénytelen wikiszó. %s." + +#: lib\pwiki\DocPagePresenter.py:265 +msgid "Wiki page not found, a new page will be created" +msgstr "Wiki lap nem található, új lap készül" + +#: lib\pwiki\DocPagePresenter.py:455 +#: lib\pwiki\DocPagePresenter.py:456 +msgid "History" +msgstr "Előzmények" + +#: lib\pwiki\DocPages.py:2064 +msgid "Func. tag %s does not exist" +msgstr "Parancstoldat %s nem létezik" + +#: lib\pwiki\DocPages.py:2325 +msgid "Global text blocks" +msgstr "Globális szövegblokkok" + +#: lib\pwiki\DocPages.py:2326 +msgid "Wiki text blocks" +msgstr "Wiki szövegblokkok" + +#: lib\pwiki\DocPages.py:2327 +msgid "Global spell list" +msgstr "Globális helyesírási lista" + +#: lib\pwiki\DocPages.py:2328 +msgid "Wiki spell list" +msgstr "Wiki helyesírási lista" + +#: lib\pwiki\DocPages.py:2329 +msgid "Global cc. blacklist" +msgstr "Globális cc. feketelista" + +#: lib\pwiki\DocPages.py:2330 +msgid "Wiki cc. blacklist" +msgstr "Wiki cc. feketelista" + +#: lib\pwiki\DocPages.py:2331 +msgid "Favorite wikis" +msgstr "Kedvenc wikik" + +#: lib\pwiki\Exporters.py:371 +msgid "One HTML page" +msgstr "Egy HTML lap" + +#: lib\pwiki\Exporters.py:372 +msgid "Set of HTML pages" +msgstr "Wiki HTML-lapsorozatként" + +#: lib\pwiki\Exporters.py:766 +#: lib\pwiki\Exporters.py:855 +msgid "Exporting %s" +msgstr "%s exportálása" + +#: lib\pwiki\Exporters.py:1623 +msgid "
[Allow evaluation of insertions in \"Options\", page \"Security\", option \"Process insertion scripts\"]
" +msgstr "
[Engedélyezi a beillesztések kiértékelését ebben \"Opciók\", page \"Biztonság\", option \"Beillesztett szkriptek feldolgozása\"]
" + +#: lib\pwiki\Exporters.py:2157 +msgid "[Unknown parser node with name \"%s\" found]" +msgstr "[Ismeretlen elemző csomópont \"%s\" ]" + +#: lib\pwiki\Exporters.py:2191 +msgid "Set of *.wiki files" +msgstr ".wiki fájlok sorozata" + +#: lib\pwiki\Exporters.py:2376 +#: lib\pwiki\Importers.py:61 +msgid "Multipage text" +msgstr "Többlapos szöveg" + +#: lib\pwiki\Exporters.py:2389 +#: lib\pwiki\Importers.py:74 +msgid "Multipage files (*.mpt)" +msgstr "Többlapos fájlok (*.mpt)" + +#: lib\pwiki\Exporters.py:2390 +#: lib\pwiki\Importers.py:75 +msgid "Text file (*.txt)" +msgstr "Szövegfájl (*.txt)" + +#: lib\pwiki\Exporters.py:2556 +msgid "No usable separator found" +msgstr "Nincs használható elválasztó" + +#: lib\pwiki\GtkHacks.py:290 +#: lib\pwiki\WindowsHacks.py:804 +msgid "Only a real wiki page can be a clipboard catcher" +msgstr "Csak érvényes wikilap lehet vágólapbeolvasó" + +#: lib\pwiki\Importers.py:171 +msgid "Opening import file failed" +msgstr "Importfájl megnyitása sikertelen" + +#: lib\pwiki\Importers.py:202 +#: lib\pwiki\Importers.py:217 +msgid "Bad file format, header not detected" +msgstr "Rossz fájlformátum, fejléc nem található" + +#: lib\pwiki\Importers.py:210 +msgid "File format number %i is not supported" +msgstr "%i fájlformátum-szám nem támogatott" + +#: lib\pwiki\Importers.py:465 +msgid "Bad wiki word: %s, %s" +msgstr "Rossz wikiszó %s" + +#: lib\pwiki\LogWindow.py:80 +msgid "Message" +msgstr "Üzenet" + +#: lib\pwiki\MainApp.py:139 +msgid "Error initializing environment, couldn't locate global config directory" +msgstr "Hiba a környezet inicializálásakor, a globális konfigurációs könyvtár nem található" + +#: lib\pwiki\MainApp.py:247 +msgid "" +"Invalid AppLock.lock file.\n" +"Ensure that WikidPad is not running,\n" +"then delete file \"%s\" if present yet.\n" +msgstr "" +"Érvénytelen AppLock.lock fájl.\n" +"Győződjön meg, hogy a WikidPad nem fut,\n" +"majd törölje a következő fájlt \"%s\" ha még létezik.\n" + +#: lib\pwiki\MainApp.py:305 +msgid "" +"Other WikidPad process(es) seem(s) to run already\n" +"Process identifier(s): %s\n" +"Continue?" +msgstr "" +"Úgy tűnik másik wiki processz(ek) már fut(nak)\n" +"Processz azonosító(k): %s\n" +"Folytatja?" + +#: lib\pwiki\MainApp.py:307 +msgid "Continue?" +msgstr "Folytatja?" + +#: lib\pwiki\MainApp.py:538 +msgid "" +"An error occurred during this session\n" +"See file %s" +msgstr "" +"Hiba következett be ebben a szakaszban\n" +"Lásd a(z) %s fájlt" + +#: lib\pwiki\MainApp.py:688 +msgid "Plugin options" +msgstr "Beépülő opciók" + +#: lib\pwiki\MainApp.py:737 +#: lib\pwiki\OptionsDialog.py:671 +msgid "Wiki language" +msgstr "Wikinyelv" + +#: lib\pwiki\MainAreaPanel.py:665 +#: lib\pwiki\WikiTxtCtrl.py:2152 +msgid "This can only be done for the page of a wiki word" +msgstr "Csak wikiszót tartalmazó lapra alkalmazható" + +#: lib\pwiki\MainAreaPanel.py:666 +#: lib\pwiki\WikiTxtCtrl.py:2153 +msgid "Not a wiki page" +msgstr "Nem Wikilap" + +#: lib\pwiki\MptImporterGui.py:172 +msgid "Wiki page" +msgstr "Wikilap" + +#: lib\pwiki\MptImporterGui.py:173 +msgid "Func. page" +msgstr "Funkc. lap" + +#: lib\pwiki\MptImporterGui.py:174 +msgid "Saved search" +msgstr "Mentett keresések" + +#: lib\pwiki\MptImporterGui.py:191 +msgid "Type" +msgstr "Típus" + +#: lib\pwiki\MptImporterGui.py:192 +msgid "Name" +msgstr "Név" + +#: lib\pwiki\MptImporterGui.py:194 +msgid "" +"Version\n" +"Import" +msgstr "" +"Verzió\n" +"Import" + +#: lib\pwiki\MptImporterGui.py:195 +msgid "" +"Rename\n" +"imported" +msgstr "" +"Átnevez\n" +"importált" + +#: lib\pwiki\MptImporterGui.py:196 +msgid "" +"Rename\n" +"present" +msgstr "" +"Átnevez\n" +"jelenlegi" + +#: lib\pwiki\MptImporterGui.py:381 +msgid "You can't rename imported and present item at the same time" +msgstr "Nem nevezhet át importált és megjelenített elemet egy időben" + +#: lib\pwiki\MptImporterGui.py:394 +msgid "Rename imported" +msgstr "Átnevez importált" + +#: lib\pwiki\MptImporterGui.py:403 +msgid "Rename present" +msgstr "Átnevez jelenlegi" + +#: lib\pwiki\MptImporterGui.py:414 +#: lib\pwiki\MptImporterGui.py:438 +msgid "Name collision: Item '%s' will be imported already" +msgstr "Névütközés: '%s' elem lesz importálva (már?)" + +#: lib\pwiki\MptImporterGui.py:420 +#: lib\pwiki\MptImporterGui.py:449 +msgid "Name collision: Item '%s' will already be created by renaming '%s'" +msgstr "Névütközés: '%s' készül (már?) '%s' átnevezéssel" + +#: lib\pwiki\MptImporterGui.py:429 +#: lib\pwiki\MptImporterGui.py:443 +msgid "Name collision: Item '%s' exists already in database" +msgstr "Névütközés: '%s' elem már szerepel az adatbázisban" + +#: lib\pwiki\OptionsDialog.py:652 +msgid "Application" +msgstr "Program" + +#: lib\pwiki\OptionsDialog.py:653 +msgid "User interface" +msgstr "Alkalmazói felület" + +#: lib\pwiki\OptionsDialog.py:654 +msgid "Security" +msgstr "Biztonság" + +#: lib\pwiki\OptionsDialog.py:655 +msgid "Tree" +msgstr "Fa" + +#: lib\pwiki\OptionsDialog.py:656 +msgid "HTML preview/export" +msgstr "HTML előnézet/export" + +#: lib\pwiki\OptionsDialog.py:657 +msgid "HTML header" +msgstr "HTML-fejléc" + +#: lib\pwiki\OptionsDialog.py:658 +msgid "Editor" +msgstr "Szerkesztő" + +#: lib\pwiki\OptionsDialog.py:659 +msgid "Editor Colors" +msgstr "A Szerkesztő színei" + +#: lib\pwiki\OptionsDialog.py:660 +msgid "Clipboard Catcher" +msgstr "Vágólapbeolvasó" + +#: lib\pwiki\OptionsDialog.py:661 +msgid "File Launcher" +msgstr "Fájlindító" + +#: lib\pwiki\OptionsDialog.py:662 +msgid "Mouse" +msgstr "Egér" + +#: lib\pwiki\OptionsDialog.py:663 +msgid "Chron. view" +msgstr "Időrendi nézet" + +#: lib\pwiki\OptionsDialog.py:664 +msgid "Searching" +msgstr "Keres" + +#: lib\pwiki\OptionsDialog.py:665 +#: lib\pwiki\OptionsDialog.py:673 +msgid "Advanced" +msgstr "Haladó" + +#: lib\pwiki\OptionsDialog.py:666 +msgid "Autosave" +msgstr "Automatikus mentés" + +#: lib\pwiki\OptionsDialog.py:668 +msgid "Current Wiki" +msgstr "Aktuális Wiki" + +#: lib\pwiki\OptionsDialog.py:669 +msgid "Headings" +msgstr "Fejlécek" + +#: lib\pwiki\OptionsDialog.py:938 +msgid "Wave files (*.wav)|*.wav" +msgstr "Hangfájlok (*.wav)|*wav" + +#: lib\pwiki\OptionsDialog.py:949 +#: lib\pwiki\OptionsDialog.py:1239 +msgid "All files (*.*)|*" +msgstr "Minden fájl (*.*)|*" + +#: lib\pwiki\OptionsDialog.py:1229 +msgid "Select Directory" +msgstr "Könyvtár kiválasztása" + +#: lib\pwiki\OptionsDialog.py:1237 +msgid "Select File" +msgstr "Fájl kiválasztása" + +#: lib\pwiki\PersonalWikiFrame.py:168 +msgid "Bad formatted command line." +msgstr "Rossz formátumú parancssor." + +#: lib\pwiki\PersonalWikiFrame.py:381 +msgid "Wiki doesn't exist: %s" +msgstr "Nemlétező Wiki: %s" + +#: lib\pwiki\PersonalWikiFrame.py:387 +msgid "Last wiki doesn't exist: %s" +msgstr "Az utolsó wiki nem létezik: %s" + +#: lib\pwiki\PersonalWikiFrame.py:693 +msgid "&New" +msgstr "Új" + +#: lib\pwiki\PersonalWikiFrame.py:694 +msgid "Create new wiki" +msgstr "Új wiki létrehozása" + +#: lib\pwiki\PersonalWikiFrame.py:697 +msgid "&Open" +msgstr "Megnyitás" + +#: lib\pwiki\PersonalWikiFrame.py:699 +msgid "In &This Window..." +msgstr "Ebben az ablakban..." + +#: lib\pwiki\PersonalWikiFrame.py:701 +msgid "Open wiki in this window" +msgstr "Wiki nyitása ebben az ablakban" + +#: lib\pwiki\PersonalWikiFrame.py:703 +msgid "In &New Window..." +msgstr "Új ablakban" + +#: lib\pwiki\PersonalWikiFrame.py:705 +msgid "Open wiki in a new window" +msgstr "Wiki megnyitása új ablakban" + +#: lib\pwiki\PersonalWikiFrame.py:707 +msgid "&Current in New Window" +msgstr "Aktuális új ablakba" + +#: lib\pwiki\PersonalWikiFrame.py:709 +msgid "Create new window for same wiki" +msgstr "Új ablak ugyanennek a wikinek" + +#: lib\pwiki\PersonalWikiFrame.py:714 +msgid "&Recent" +msgstr "Előzmények" + +#: lib\pwiki\PersonalWikiFrame.py:721 +msgid "F&avorites" +msgstr "Kedvencek" + +#: lib\pwiki\PersonalWikiFrame.py:727 +msgid "&Search Wiki..." +msgstr "keresés Wikiben..." + +#: lib\pwiki\PersonalWikiFrame.py:728 +msgid "Search whole wiki" +msgstr "Keresés teljes wikiben" + +#: lib\pwiki\PersonalWikiFrame.py:736 +msgid "Publish as HTML" +msgstr "Közzététel HTML-ként" + +#: lib\pwiki\PersonalWikiFrame.py:739 +msgid "Wiki as Single HTML Page" +msgstr "Wiki egyetlen HTML-lapként" + +#: lib\pwiki\PersonalWikiFrame.py:740 +msgid "Publish Wiki as Single HTML Page" +msgstr "Wiki közzététele egyetlen HTML lapként" + +#: lib\pwiki\PersonalWikiFrame.py:744 +msgid "Wiki as Set of HTML Pages" +msgstr "Wiki HTML-lapsorozatként" + +#: lib\pwiki\PersonalWikiFrame.py:745 +msgid "Publish Wiki as Set of HTML Pages" +msgstr "Wiki közzététele HTML-lapsorozatként" + +#: lib\pwiki\PersonalWikiFrame.py:749 +msgid "Current Wiki Word as HTML Page" +msgstr "Aktuális wikiszó HTML-lapként" + +#: lib\pwiki\PersonalWikiFrame.py:750 +msgid "Publish Current Wiki Word as HTML Page" +msgstr "Aktuális Wikiszó közzététele HTML-lapként" + +#: lib\pwiki\PersonalWikiFrame.py:754 +msgid "Sub-Tree as Single HTML Page" +msgstr "Alárendelt fa egyetlen HTML-lapként" + +#: lib\pwiki\PersonalWikiFrame.py:755 +msgid "Publish Sub-Tree as Single HTML Page" +msgstr "Alárendelt fa közzététele egyetlen HTML-lapként" + +#: lib\pwiki\PersonalWikiFrame.py:759 +msgid "Sub-Tree as Set of HTML Pages" +msgstr "Alárendelt fa HTML-lapsorozatként" + +#: lib\pwiki\PersonalWikiFrame.py:760 +msgid "Publish Sub-Tree as Set of HTML Pages" +msgstr "Alárendelt fa közzététele HTML-lapsorozatként" + +#: lib\pwiki\PersonalWikiFrame.py:768 +msgid "Other Export..." +msgstr "Export másként..." + +#: lib\pwiki\PersonalWikiFrame.py:769 +#: lib\pwiki\PersonalWikiFrame.py:1781 +msgid "Open general export dialog" +msgstr "Általános export párbeszédablak nyitása" + +#: lib\pwiki\PersonalWikiFrame.py:773 +msgid "Print..." +msgstr "Nyomtat..." + +#: lib\pwiki\PersonalWikiFrame.py:774 +msgid "Show the print dialog" +msgstr "Nyomtatás párbeszédablak megnyitása" + +#: lib\pwiki\PersonalWikiFrame.py:779 +msgid "&Properties..." +msgstr "Tulajdonságok......" + +#: lib\pwiki\PersonalWikiFrame.py:780 +msgid "Show general information about current wiki" +msgstr "Általános információk megjelenítése az aktuális wikiről" + +#: lib\pwiki\PersonalWikiFrame.py:784 +msgid "Maintenance" +msgstr "Karbantartás" + +#: lib\pwiki\PersonalWikiFrame.py:788 +msgid "&Rebuild Wiki..." +msgstr "Wiki újraépítése..." + +#: lib\pwiki\PersonalWikiFrame.py:789 +msgid "Rebuild this wiki and its cache completely" +msgstr "Aktuális wiki és caché teljes újraépítése" + +#: lib\pwiki\PersonalWikiFrame.py:803 +msgid "&Update cache..." +msgstr "Cache frissítése" + +#: lib\pwiki\PersonalWikiFrame.py:804 +msgid "Update cache where marked as not up to date" +msgstr "Elavultként jelőlt caché frissítése" + +#: lib\pwiki\PersonalWikiFrame.py:809 +msgid "&Initiate update..." +msgstr "Frissítés indítása..." + +#: lib\pwiki\PersonalWikiFrame.py:810 +msgid "Initiate full cache update which is done mainly in background" +msgstr "Alapvetően a háttérben történő cachéfrissítés indítása" + +#: lib\pwiki\PersonalWikiFrame.py:817 +msgid "Show job count..." +msgstr "Munkafolyamat-számláló megjelenítése..." + +#: lib\pwiki\PersonalWikiFrame.py:818 +msgid "Show how many update jobs are waiting in background" +msgstr "Mutassa a háttérben várakozó frissítési faladatok számát" + +#: lib\pwiki\PersonalWikiFrame.py:823 +msgid "Open as &Type..." +msgstr "Nyissa meg ... típusként" + +#: lib\pwiki\PersonalWikiFrame.py:824 +msgid "Open wiki with a specified wiki database type" +msgstr "Wiki megnyitása meghatározott wiki adatbázistípusként" + +#: lib\pwiki\PersonalWikiFrame.py:828 +msgid "Reconnect..." +msgstr "Újracsatol..." + +#: lib\pwiki\PersonalWikiFrame.py:829 +msgid "Reconnect to database after connection failure" +msgstr "Sikertelen kapcsolódás után az adatbázis újracsatolása" + +#: lib\pwiki\PersonalWikiFrame.py:835 +msgid "&Optimise Database" +msgstr "Adatbázis optimalizálása" + +#: lib\pwiki\PersonalWikiFrame.py:836 +msgid "Free unused space in database" +msgstr "Szabadítsa fel a használatonkívüli helyeket az adatbázisban" + +#: lib\pwiki\PersonalWikiFrame.py:843 +msgid "&Copy .wiki files to database" +msgstr ".wiki fájlok másolása adatbázisba" + +#: lib\pwiki\PersonalWikiFrame.py:844 +msgid "Copy .wiki files to database" +msgstr ".wiki fájlok másolása adatbázisba" + +#: lib\pwiki\PersonalWikiFrame.py:854 +msgid "E&xit" +msgstr "Kilép" + +#: lib\pwiki\PersonalWikiFrame.py:854 +#: lib\pwiki\PersonalWikiFrame.py:5642 +msgid "Exit" +msgstr "Kilép" + +#: lib\pwiki\PersonalWikiFrame.py:1022 +msgid "Reread text blocks" +msgstr "Szövegblokkok újraolvasása" + +#: lib\pwiki\PersonalWikiFrame.py:1023 +msgid "Reread the text block file(s) and recreate menu" +msgstr "Szövegblokkfájlok újraolvasása és a menű újraépítése" + +#: lib\pwiki\PersonalWikiFrame.py:1077 +msgid "Add wiki" +msgstr "Wiki hozzáadás" + +#: lib\pwiki\PersonalWikiFrame.py:1078 +msgid "Add a wiki to the favorites" +msgstr "Wiki a kedevencekhez" + +#: lib\pwiki\PersonalWikiFrame.py:1083 +#: lib\pwiki\PersonalWikiFrame.py:1084 +msgid "Manage favorites" +msgstr "Kedvencek kezelése" + +#: lib\pwiki\PersonalWikiFrame.py:1106 +#: lib\pwiki\PersonalWikiFrame.py:1831 +#: lib\pwiki\PersonalWikiFrame.py:2355 +#: lib\pwiki\PersonalWikiFrame.py:4901 +#: lib\pwiki\PersonalWikiFrame.py:5219 +msgid "Error while starting new WikidPad instance" +msgstr "Hiba új WikidPad példány indításakor" + +#: lib\pwiki\PersonalWikiFrame.py:1208 +msgid "&Undo" +msgstr "Visszavon" + +#: lib\pwiki\PersonalWikiFrame.py:1212 +msgid "&Redo" +msgstr "Ismét" + +#: lib\pwiki\PersonalWikiFrame.py:1220 +msgid "&Search and Replace..." +msgstr "Keresés és csere..." + +#: lib\pwiki\PersonalWikiFrame.py:1222 +msgid "Search and replace inside current page" +msgstr "Keresés és csere az aktuális lapon" + +#: lib\pwiki\PersonalWikiFrame.py:1228 +msgid "Cu&t" +msgstr "Kivág" + +#: lib\pwiki\PersonalWikiFrame.py:1233 +msgid "&Copy" +msgstr "Másol" + +#: lib\pwiki\PersonalWikiFrame.py:1237 +msgid "&Paste" +msgstr "Beilleszt" + +#: lib\pwiki\PersonalWikiFrame.py:1242 +msgid "Select &All" +msgstr "Mindent kiválaszt" + +#: lib\pwiki\PersonalWikiFrame.py:1248 +msgid "Copy to Sc&ratchPad" +msgstr "Másolás a Vázlatfüzetbe" + +#: lib\pwiki\PersonalWikiFrame.py:1250 +msgid "Copy selected text to ScratchPad" +msgstr "Kiválasztott szöveg másolása a Vázlatfüzetbe" + +#: lib\pwiki\PersonalWikiFrame.py:1256 +msgid "Paste T&extblock" +msgstr "Szövegblokkok" + +#: lib\pwiki\PersonalWikiFrame.py:1264 +msgid "C&lipboard Catcher" +msgstr "Vágólapbeolvasó" + +#: lib\pwiki\PersonalWikiFrame.py:1267 +#: lib\pwiki\PersonalWikiFrame.py:4125 +msgid "Set at Page" +msgstr "Elhelyezés lapon" + +#: lib\pwiki\PersonalWikiFrame.py:1269 +msgid "Text copied to clipboard is also appended to this page" +msgstr "A szöveg vágólapra másolva és a laphoz is hozzáfűzve" + +#: lib\pwiki\PersonalWikiFrame.py:1275 +msgid "Set at Cursor" +msgstr "Elhelyez a kurzornál" + +#: lib\pwiki\PersonalWikiFrame.py:1277 +msgid "Text copied to clipboard is also added to cursor position" +msgstr "A vágólapra másolt szöveg ehhez a kurzorpozícióhoz is hozzáfűzve" + +#: lib\pwiki\PersonalWikiFrame.py:1283 +msgid "Set Off" +msgstr "Kikapcsol" + +#: lib\pwiki\PersonalWikiFrame.py:1285 +msgid "Switch off clipboard catcher" +msgstr "Vágólapbeolvasó kikapcsolása" + +#: lib\pwiki\PersonalWikiFrame.py:1295 +msgid "Spell Check..." +msgstr "Helyesírásellenőrzés..." + +#: lib\pwiki\PersonalWikiFrame.py:1297 +msgid "Spell check current and possibly further pages" +msgstr "Helyesírásellenőrzés az aktuális és további lapokon" + +#: lib\pwiki\PersonalWikiFrame.py:1301 +msgid "Spell Check While Type" +msgstr "Helyesírás ellenőrzése gépelés közben" + +#: lib\pwiki\PersonalWikiFrame.py:1302 +msgid "Set if editor should do spell checking during typing" +msgstr "Állítsa be, ha a szerkesztő gépelés közben ellenőrizze a nyelvhelyességet" + +#: lib\pwiki\PersonalWikiFrame.py:1307 +msgid "Clear Ignore List" +msgstr "Kivételek lista törlése" + +#: lib\pwiki\PersonalWikiFrame.py:1309 +msgid "Clear the list of words to ignore for spell check while type" +msgstr "Gépelés közben a helyesírás-ellenőrzésből kizártak listáját törölje" + +#: lib\pwiki\PersonalWikiFrame.py:1318 +msgid "&Insert" +msgstr "Beszúr" + +#: lib\pwiki\PersonalWikiFrame.py:1320 +msgid "&File URL..." +msgstr "File URL..." + +#: lib\pwiki\PersonalWikiFrame.py:1321 +msgid "Use file dialog to add URL" +msgstr "Fájl párbeszédablak használata URL hozzáadásához" + +#: lib\pwiki\PersonalWikiFrame.py:1326 +msgid "Current &Date" +msgstr "Aktuális dátum" + +#: lib\pwiki\PersonalWikiFrame.py:1327 +msgid "Insert current date" +msgstr "Aktuális dátum beszúrása" + +#: lib\pwiki\PersonalWikiFrame.py:1336 +msgid "&Settings" +msgstr "Beállítások" + +#: lib\pwiki\PersonalWikiFrame.py:1339 +msgid "&Date Format..." +msgstr "Dátumformátum..." + +#: lib\pwiki\PersonalWikiFrame.py:1340 +msgid "Set date format for inserting current date" +msgstr "Dátumformátum beállítása az aktuális dátum beszúrásához" + +#: lib\pwiki\PersonalWikiFrame.py:1344 +msgid "Auto-&Wrap" +msgstr "Automatikus tördelés" + +#: lib\pwiki\PersonalWikiFrame.py:1345 +msgid "Set if editor should wrap long lines" +msgstr "Állítsa be, hogy a szerkesztő tördelje-e a hosszú sorokat" + +#: lib\pwiki\PersonalWikiFrame.py:1359 +msgid "Auto-&Indent" +msgstr "Automatikus behúzás" + +#: lib\pwiki\PersonalWikiFrame.py:1360 +msgid "Auto indentation" +msgstr "Automatikus behúzás" + +#: lib\pwiki\PersonalWikiFrame.py:1373 +msgid "Auto-&Bullets" +msgstr "Automatikus felsorolás" + +#: lib\pwiki\PersonalWikiFrame.py:1374 +msgid "Show bullet on next line if current has one" +msgstr "Következő sorban mutassa a felsorolásjelet, ha az aktuálisnak van" + +#: lib\pwiki\PersonalWikiFrame.py:1389 +msgid "Tabs to spaces" +msgstr "Tabulátorokat szóközökre" + +#: lib\pwiki\PersonalWikiFrame.py:1390 +msgid "Write spaces when hitting TAB key" +msgstr "TAB lenyomásakor szóközöket ír" + +#: lib\pwiki\PersonalWikiFrame.py:1408 +msgid "Show T&oolbar" +msgstr "Eszköztár megjelenítése" + +#: lib\pwiki\PersonalWikiFrame.py:1410 +msgid "Show toolbar" +msgstr "Eszköztár megjelenítése" + +#: lib\pwiki\PersonalWikiFrame.py:1417 +msgid "Show &Tree View" +msgstr "Fanézet megjelenítése" + +#: lib\pwiki\PersonalWikiFrame.py:1419 +msgid "Show Tree Control" +msgstr "Favezérlők megjelenítése" + +#: lib\pwiki\PersonalWikiFrame.py:1426 +msgid "Show &Chron. View" +msgstr "Időrendi nézet megjelenítése" + +#: lib\pwiki\PersonalWikiFrame.py:1428 +msgid "Show chronological view" +msgstr "Időrendi nézet megjelenítése" + +#: lib\pwiki\PersonalWikiFrame.py:1435 +msgid "Show &Page Structure" +msgstr "Lapszerkezet megjelenítése" + +#: lib\pwiki\PersonalWikiFrame.py:1437 +msgid "Show structure (headings) of the page" +msgstr "Lapszerkezet (fejlécek) megjelenítése" + +#: lib\pwiki\PersonalWikiFrame.py:1449 +msgid "Show &Indentation Guides" +msgstr "Behúzásjelölők megjelenítése" + +#: lib\pwiki\PersonalWikiFrame.py:1450 +msgid "Show indentation guides in editor" +msgstr "Behúzásjelölők megjelenítése a szerkesztőben" + +#: lib\pwiki\PersonalWikiFrame.py:1455 +msgid "Show Line &Numbers" +msgstr "Sorok számát mutatja" + +#: lib\pwiki\PersonalWikiFrame.py:1456 +msgid "Show line numbers in editor" +msgstr "Sorok számát mutatja a szerkesztőben" + +#: lib\pwiki\PersonalWikiFrame.py:1463 +msgid "Stay on Top" +msgstr "Maradjon felül" + +#: lib\pwiki\PersonalWikiFrame.py:1465 +msgid "Stay on Top of all other windows" +msgstr "Maradjon az összes többi ablak fölött" + +#: lib\pwiki\PersonalWikiFrame.py:1473 +msgid "&Zoom In" +msgstr "Nagyít" + +#: lib\pwiki\PersonalWikiFrame.py:1474 +#: lib\pwiki\PersonalWikiFrame.py:2037 +msgid "Zoom In" +msgstr "Nagyít" + +#: lib\pwiki\PersonalWikiFrame.py:1477 +msgid "Zoo&m Out" +msgstr "Kicsinyít" + +#: lib\pwiki\PersonalWikiFrame.py:1478 +#: lib\pwiki\PersonalWikiFrame.py:2042 +msgid "Zoom Out" +msgstr "Kicsinyít" + +#: lib\pwiki\PersonalWikiFrame.py:1500 +msgid "Toggle Ed./Prev" +msgstr "Váltás Szerk./előnéz." + +#: lib\pwiki\PersonalWikiFrame.py:1502 +#: lib\pwiki\PersonalWikiFrame.py:2033 +msgid "Switch between editor and preview" +msgstr "Váltás szerkesztő- és előnézeti mód között" + +#: lib\pwiki\PersonalWikiFrame.py:1506 +msgid "Enter Edit Mode" +msgstr "Szerkesztőmódba ugrás" + +#: lib\pwiki\PersonalWikiFrame.py:1507 +msgid "Show editor in tab" +msgstr "Szerkesztő megjelenítése a fülön" + +#: lib\pwiki\PersonalWikiFrame.py:1511 +msgid "Enter Preview Mode" +msgstr "Váltás előnézeti módba" + +#: lib\pwiki\PersonalWikiFrame.py:1513 +msgid "Show preview in tab" +msgstr "Előnézetet a fülön" + +#: lib\pwiki\PersonalWikiFrame.py:1537 +msgid "&Save" +msgstr "Ment" + +#: lib\pwiki\PersonalWikiFrame.py:1538 +msgid "Save all open pages" +msgstr "Összes nyitott lap mentése" + +#: lib\pwiki\PersonalWikiFrame.py:1545 +msgid "&Rename" +msgstr "Átnevez" + +#: lib\pwiki\PersonalWikiFrame.py:1546 +msgid "Rename current wiki word" +msgstr "Aktuális wikiszó átnevezése" + +#: lib\pwiki\PersonalWikiFrame.py:1552 +msgid "Delete current wiki word" +msgstr "Aktuális wikiszó törlése" + +#: lib\pwiki\PersonalWikiFrame.py:1559 +msgid "Set as Roo&t" +msgstr "Gyökérnek beállít" + +#: lib\pwiki\PersonalWikiFrame.py:1560 +msgid "Set current wiki word as tree root" +msgstr "Aktuális wikiszó beállítása fa gyökereként" + +#: lib\pwiki\PersonalWikiFrame.py:1564 +msgid "R&eset Root" +msgstr "Gyökér alaphelyzetbe" + +#: lib\pwiki\PersonalWikiFrame.py:1565 +msgid "Set home wiki word as tree root" +msgstr "Alap wikiszó beállítása fa gyökereként" + +#: lib\pwiki\PersonalWikiFrame.py:1569 +msgid "S&ynchronise Tree" +msgstr "Fa szinkronizálása" + +#: lib\pwiki\PersonalWikiFrame.py:1570 +msgid "Find the current wiki word in the tree" +msgstr "Aktuális wikiszó megkeresése a fában" + +#: lib\pwiki\PersonalWikiFrame.py:1575 +msgid "&Follow Link" +msgstr "Link követése" + +#: lib\pwiki\PersonalWikiFrame.py:1576 +msgid "Activate link/word" +msgstr "Szó/link érvényesítése" + +#: lib\pwiki\PersonalWikiFrame.py:1581 +msgid "Follow Link in &New Tab" +msgstr "Link követése új fülön" + +#: lib\pwiki\PersonalWikiFrame.py:1582 +msgid "Activate link/word in new tab" +msgstr "Szó/link érvényesítése új fülön" + +#: lib\pwiki\PersonalWikiFrame.py:1587 +msgid "Copy &URL to Clipboard" +msgstr "URL másolása vágólapra" + +#: lib\pwiki\PersonalWikiFrame.py:1589 +msgid "Copy full \"wiki:\" URL of the word to clipboard" +msgstr "A szó teljes \"wiki:\" URL-jének másolása a vágólapra" + +#: lib\pwiki\PersonalWikiFrame.py:1595 +msgid "&Add version" +msgstr "Verzió hozzáadása" + +#: lib\pwiki\PersonalWikiFrame.py:1596 +msgid "Add new version" +msgstr "Új verzió hozzáadása" + +#: lib\pwiki\PersonalWikiFrame.py:1604 +msgid "&Bold" +msgstr "Vastag" + +#: lib\pwiki\PersonalWikiFrame.py:1605 +#: lib\pwiki\PersonalWikiFrame.py:2018 +msgid "Bold" +msgstr "Vastag" + +#: lib\pwiki\PersonalWikiFrame.py:1611 +msgid "&Italic" +msgstr "Dőlt" + +#: lib\pwiki\PersonalWikiFrame.py:1612 +#: lib\pwiki\PersonalWikiFrame.py:2024 +msgid "Italic" +msgstr "Dőlt" + +#: lib\pwiki\PersonalWikiFrame.py:1618 +msgid "&Heading" +msgstr "Fejléc" + +#: lib\pwiki\PersonalWikiFrame.py:1619 +msgid "Add Heading" +msgstr "Fejléc hozzáadása" + +#: lib\pwiki\PersonalWikiFrame.py:1627 +msgid "&Rewrap Text" +msgstr "Szöveg újratördelése" + +#: lib\pwiki\PersonalWikiFrame.py:1629 +msgid "Rewrap Text" +msgstr "Szöveg újratördelése" + +#: lib\pwiki\PersonalWikiFrame.py:1634 +msgid "&Convert" +msgstr "Átalakítás" + +#: lib\pwiki\PersonalWikiFrame.py:1637 +msgid "Selection to &Link" +msgstr "Kijelölést link-ké" + +#: lib\pwiki\PersonalWikiFrame.py:1638 +msgid "Remove non-allowed characters and make sel. a wiki word link" +msgstr "Érvénytelen karakterek eltávolítása és kijelölésből wikiszóhivatkozást készítése" + +#: lib\pwiki\PersonalWikiFrame.py:1643 +msgid "Selection to &Wiki Word" +msgstr "Kijelölést Wikiszóra" + +#: lib\pwiki\PersonalWikiFrame.py:1645 +msgid "Put selected text in a new or existing wiki word" +msgstr "A kijelölt szöveget helyezze új, vagy meglévő wikiszóba" + +#: lib\pwiki\PersonalWikiFrame.py:1650 +msgid "Absolute/Relative &File URL" +msgstr "Abszolút/relatív fájl URL" + +#: lib\pwiki\PersonalWikiFrame.py:1652 +msgid "Convert file URL from absolute to relative and vice versa" +msgstr "Fájl URL átalakítása abszolútból relatívba és vissza" + +#: lib\pwiki\PersonalWikiFrame.py:1666 +msgid "&Icon Name" +msgstr "Ikon neve" + +#: lib\pwiki\PersonalWikiFrame.py:1678 +msgid "&Color Name" +msgstr "Szín neve" + +#: lib\pwiki\PersonalWikiFrame.py:1686 +msgid "&Add Attribute" +msgstr "Attribútum hozzáadása" + +#: lib\pwiki\PersonalWikiFrame.py:1695 +msgid "&Icon Attribute" +msgstr "Ikonjellemző" + +#: lib\pwiki\PersonalWikiFrame.py:1705 +msgid "&Color Attribute" +msgstr "Színjellemző" + +#: lib\pwiki\PersonalWikiFrame.py:1714 +msgid "&Back" +msgstr "Vissza" + +#: lib\pwiki\PersonalWikiFrame.py:1715 +msgid "Go backward" +msgstr "Visszalép" + +#: lib\pwiki\PersonalWikiFrame.py:1718 +msgid "&Forward" +msgstr "Előre" + +#: lib\pwiki\PersonalWikiFrame.py:1719 +msgid "Go forward" +msgstr "Előrelép" + +#: lib\pwiki\PersonalWikiFrame.py:1723 +msgid "&Wiki Home" +msgstr "Wiki indulólap" + +#: lib\pwiki\PersonalWikiFrame.py:1724 +msgid "Go to wiki homepage" +msgstr "Ugrás a wiki indulólaphoz" + +#: lib\pwiki\PersonalWikiFrame.py:1731 +msgid "Go to &Page..." +msgstr "Ugrás lapra..." + +#: lib\pwiki\PersonalWikiFrame.py:1732 +msgid "Open wiki word" +msgstr "Wikiszó megnyitása" + +#: lib\pwiki\PersonalWikiFrame.py:1737 +msgid "Go to P&arent..." +msgstr "Ugrás a felmenőhöz..." + +#: lib\pwiki\PersonalWikiFrame.py:1739 +msgid "List parents of current wiki word" +msgstr "Aktuális wikiszó felmenőinek listázása" + +#: lib\pwiki\PersonalWikiFrame.py:1742 +msgid "List &Children..." +msgstr "Leszármazottak listázása..." + +#: lib\pwiki\PersonalWikiFrame.py:1744 +msgid "List children of current wiki word" +msgstr "Aktuális wikiszó leszármazottainak listázása" + +#: lib\pwiki\PersonalWikiFrame.py:1747 +msgid "List Pa&rentless Pages" +msgstr "Felmenő nélküli lapok listázása" + +#: lib\pwiki\PersonalWikiFrame.py:1749 +msgid "List nodes with no parent relations" +msgstr "Felmenői kapcsolat nélküli csomópontok listázása" + +#: lib\pwiki\PersonalWikiFrame.py:1754 +msgid "Show &History..." +msgstr "Előzmények megjelenítése..." + +#: lib\pwiki\PersonalWikiFrame.py:1755 +msgid "View tab history" +msgstr "Fül előzményeinek megjelenítése" + +#: lib\pwiki\PersonalWikiFrame.py:1758 +msgid "&Up History..." +msgstr "Előzményekben fel..." + +#: lib\pwiki\PersonalWikiFrame.py:1759 +msgid "Up in tab history" +msgstr "Fel a fül előzményeiben" + +#: lib\pwiki\PersonalWikiFrame.py:1762 +msgid "&Down History..." +msgstr "Előzményekben le..." + +#: lib\pwiki\PersonalWikiFrame.py:1763 +msgid "Down in tab history" +msgstr "A fül előzményeiben le" + +#: lib\pwiki\PersonalWikiFrame.py:1768 +msgid "Add B&ookmark" +msgstr "Könyvjelző hozzáadása" + +#: lib\pwiki\PersonalWikiFrame.py:1769 +msgid "Add bookmark to page" +msgstr "Könyvjelző a laphoz" + +#: lib\pwiki\PersonalWikiFrame.py:1773 +msgid "Go to &Bookmark..." +msgstr "Ugrás a könyvjelzőhöz..." + +#: lib\pwiki\PersonalWikiFrame.py:1774 +msgid "List bookmarks" +msgstr "Könyvjelzők listázása" + +#: lib\pwiki\PersonalWikiFrame.py:1780 +msgid "&Export..." +msgstr "Export..." + +#: lib\pwiki\PersonalWikiFrame.py:1784 +msgid "&Continuous Export..." +msgstr "Folyamatos export..." + +#: lib\pwiki\PersonalWikiFrame.py:1785 +msgid "Open export dialog for continuous export of changes" +msgstr "Exportpárbeszéd nyitása a változások folyamatos exportjához" + +#: lib\pwiki\PersonalWikiFrame.py:1791 +msgid "&Import..." +msgstr "Import..." + +#: lib\pwiki\PersonalWikiFrame.py:1792 +msgid "Import dialog" +msgstr "Importpárbeszéd" + +#: lib\pwiki\PersonalWikiFrame.py:1798 +msgid "Scripts" +msgstr "Szkriptek" + +#: lib\pwiki\PersonalWikiFrame.py:1799 +msgid "Run scripts, evaluate expressions" +msgstr "Szkriptek futtatása, kifejezések kiértékelése" + +#: lib\pwiki\PersonalWikiFrame.py:1801 +msgid "&Eval" +msgstr "Kiértékelés" + +#: lib\pwiki\PersonalWikiFrame.py:1802 +msgid "Evaluate script blocks" +msgstr "Szkriptblokkok kiértékelése" + +#: lib\pwiki\PersonalWikiFrame.py:1807 +msgid "Run Function &%i" +msgstr "&%i függvény futtatása" + +#: lib\pwiki\PersonalWikiFrame.py:1808 +msgid "Run script function %i" +msgstr "%i szkriptfüggvény futtatása" + +#: lib\pwiki\PersonalWikiFrame.py:1813 +msgid "O&ptions..." +msgstr "Opciók..." + +#: lib\pwiki\PersonalWikiFrame.py:1814 +msgid "Set options" +msgstr "Opciók beállítása" + +#: lib\pwiki\PersonalWikiFrame.py:1835 +msgid "&Open help wiki" +msgstr "Wikihelp megnyitása" + +#: lib\pwiki\PersonalWikiFrame.py:1836 +msgid "Open WikidPadHelp, the help wiki" +msgstr "WikidPadHelp megnyitása, a wiki segítség" + +#: lib\pwiki\PersonalWikiFrame.py:1845 +msgid "&Visit Homepage" +msgstr "Honlap felkeresése" + +#: lib\pwiki\PersonalWikiFrame.py:1846 +msgid "Visit wikidPad homepage" +msgstr "WikidPad honlap felkeresése" + +#: lib\pwiki\PersonalWikiFrame.py:1856 +msgid "Show &License" +msgstr "Licensz mutatása" + +#: lib\pwiki\PersonalWikiFrame.py:1857 +msgid "Show license of WikidPad and used components" +msgstr "WikidPad és komponenseinek licensze" + +#: lib\pwiki\PersonalWikiFrame.py:1892 +msgid "&About" +msgstr "Névjegy" + +#: lib\pwiki\PersonalWikiFrame.py:1896 +msgid "&Wiki" +msgstr "Wiki" + +#: lib\pwiki\PersonalWikiFrame.py:1897 +msgid "&Edit" +msgstr "Szerkeszt" + +#: lib\pwiki\PersonalWikiFrame.py:1898 +msgid "&View" +msgstr "Nézet" + +#: lib\pwiki\PersonalWikiFrame.py:1899 +msgid "&Tabs" +msgstr "Fülek" + +#: lib\pwiki\PersonalWikiFrame.py:1900 +msgid "Wiki &Page" +msgstr "Wikilap" + +#: lib\pwiki\PersonalWikiFrame.py:1901 +msgid "&Format" +msgstr "Formázás" + +#: lib\pwiki\PersonalWikiFrame.py:1902 +msgid "&Navigate" +msgstr "Navigál" + +#: lib\pwiki\PersonalWikiFrame.py:1903 +msgid "E&xtra" +msgstr "Extra" + +#: lib\pwiki\PersonalWikiFrame.py:1919 +msgid "Pl&ugins" +msgstr "Beépülők" + +#: lib\pwiki\PersonalWikiFrame.py:1925 +msgid "&Help" +msgstr "Segítség" + +#: lib\pwiki\PersonalWikiFrame.py:1927 +msgid "He&lp" +msgstr "Segítség" + +#: lib\pwiki\PersonalWikiFrame.py:1953 +#: lib\pwiki\PersonalWikiFrame.py:1954 +msgid "Back" +msgstr "Vissza" + +#: lib\pwiki\PersonalWikiFrame.py:1959 +#: lib\pwiki\PersonalWikiFrame.py:1960 +msgid "Forward" +msgstr "Előre" + +#: lib\pwiki\PersonalWikiFrame.py:1965 +#: lib\pwiki\PersonalWikiFrame.py:1966 +msgid "Wiki Home" +msgstr "Wiki indulólap" + +#: lib\pwiki\PersonalWikiFrame.py:1980 +#: lib\pwiki\PersonalWikiFrame.py:1981 +msgid "Search" +msgstr "Keres" + +#: lib\pwiki\PersonalWikiFrame.py:1986 +#: lib\pwiki\PersonalWikiFrame.py:1987 +msgid "Find current word in tree" +msgstr "Aktuális szó megkeresése a fában" + +#: lib\pwiki\PersonalWikiFrame.py:1990 +#: lib\pwiki\PersonalWikiFrame.py:2008 +#: lib\pwiki\PersonalWikiFrame.py:2028 +msgid "Separator" +msgstr "Elválasztó" + +#: lib\pwiki\PersonalWikiFrame.py:1994 +#: lib\pwiki\PersonalWikiFrame.py:1995 +msgid "Save Wiki Word" +msgstr "Wikiszó mentése" + +#: lib\pwiki\PersonalWikiFrame.py:2005 +#: lib\pwiki\PersonalWikiFrame.py:4370 +msgid "Delete Wiki Word" +msgstr "Wikiszó törlése" + +#: lib\pwiki\PersonalWikiFrame.py:2012 +msgid "Heading" +msgstr "Fejléc" + +#: lib\pwiki\PersonalWikiFrame.py:2032 +msgid "Switch Editor/Preview" +msgstr "Szerkesztés/Előnézet váltás" + +#: lib\pwiki\PersonalWikiFrame.py:2053 +msgid "Wikize Selected Word " +msgstr "Kijelőlést Wikiszóvá" + +#: lib\pwiki\PersonalWikiFrame.py:2054 +msgid "Wikize Selected Word" +msgstr "Kijelőlést Wikiszóvá" + +#: lib\pwiki\PersonalWikiFrame.py:2123 +msgid "Line: 9999 Col: 9999 Pos: 9999999988888" +msgstr "Sor: 9999 Oszl: 9999 Poz: 9999999988888" + +#: lib\pwiki\PersonalWikiFrame.py:2292 +msgid "Regular expression error" +msgstr "Szabályos kifejezés hibája" + +#: lib\pwiki\PersonalWikiFrame.py:2301 +msgid "Are you sure you want to reconnect? You may lose some data by this process." +msgstr "Biztosan újracsatlakozik? Bizonyos adatok elveszthetnek a művelet során." + +#: lib\pwiki\PersonalWikiFrame.py:2303 +msgid "Reconnect database" +msgstr "Adatbázis újracsatolása" + +#: lib\pwiki\PersonalWikiFrame.py:2317 +#: lib\pwiki\PersonalWikiFrame.py:3345 +msgid "Error while trying to reconnect:\n" +msgstr "Hiba újracsatolás közben:\n" + +#: lib\pwiki\PersonalWikiFrame.py:2319 +msgid "" +"There was an error while reconnecting the database\n" +"\n" +"Would you like to try it again?\n" +"%s" +msgstr "" +"Hiba történt az adatbázis újracsatolása közben\n" +"\n" +"Ismét megpróbálja?\n" +"%s" + +#: lib\pwiki\PersonalWikiFrame.py:2322 +msgid "Error reconnecting!" +msgstr "Újracstolási hiba!" + +#: lib\pwiki\PersonalWikiFrame.py:2338 +#: lib\pwiki\PersonalWikiFrame.py:3392 +msgid "Error while trying to write:\n" +msgstr "Hiba írási kisérlet közben:\n" + +#: lib\pwiki\PersonalWikiFrame.py:2340 +msgid "" +"There was an error while writing to the database\n" +"\n" +"Would you like to try it again?\n" +"%s" +msgstr "" +"Hiba lépett fel az adatbáziba írás közben\n" +"\n" +"Ismét megpróbálja?\n" +"%s" + +#: lib\pwiki\PersonalWikiFrame.py:2343 +msgid "Error writing!" +msgstr "Íráshiba" + +#: lib\pwiki\PersonalWikiFrame.py:2460 +msgid "There was an error loading the icons for the tree control." +msgstr "Hiba lépett fel fa vezérlő ikonjainak betöltésekor" + +#: lib\pwiki\PersonalWikiFrame.py:2712 +msgid "No data handler available to create database." +msgstr "Nincs elérhető adatbáziskezelő az adatbázis létrehozásához" + +#: lib\pwiki\PersonalWikiFrame.py:2724 +msgid "A wiki already exists in '%s', overwrite? (This deletes everything in and below this directory!)" +msgstr "A wiki már létezik ebben: '%s'. Felülírja? (A művelet itt és az alatta lévő könyvtárakban mindent töröl)" + +#: lib\pwiki\PersonalWikiFrame.py:2726 +msgid "Warning" +msgstr "Vigyázat" + +#: lib\pwiki\PersonalWikiFrame.py:2766 +msgid "A wiki database already exists in this location, overwrite?" +msgstr "A wikiadatbázis itt már létezik. Felülírja?" + +#: lib\pwiki\PersonalWikiFrame.py:2768 +msgid "Wiki DB Exists" +msgstr "A wiki adatbázis létezik" + +#: lib\pwiki\PersonalWikiFrame.py:2779 +msgid "There was an error creating the wiki database." +msgstr "Hiba lépett fel a wikiadatbázis létrehozásakor" + +#: lib\pwiki\PersonalWikiFrame.py:2848 +#: lib\pwiki\PersonalWikiFrame.py:2849 +msgid "Choose database type" +msgstr "Válasszon adatbázistípust" + +#: lib\pwiki\PersonalWikiFrame.py:2886 +msgid "Invalid path or missing file '%s'" +msgstr "Érvénytelen útvonal, vagy hiányzó fájl '%s'" + +#: lib\pwiki\PersonalWikiFrame.py:2937 +#: lib\pwiki\PersonalWikiFrame.py:3017 +msgid "Error connecting to database in '%s'" +msgstr "Hiba az adatbázishoz csatlakozás közben itt: '%s'" + +#: lib\pwiki\PersonalWikiFrame.py:2942 +msgid "The wiki needs an update to work with this version of WikidPad. Older versions of WikidPad may be unable to read the wiki after an update." +msgstr "A WikidPad e verziójával történő munkához a wiki frissítése Szükséges. A WikidPad régebbi verziói a frissítés után esetleg képtelenek lesznek az olvasására." + +#: lib\pwiki\PersonalWikiFrame.py:2945 +msgid "Update database?" +msgstr "Adatbázis frissítése?" + +#: lib\pwiki\PersonalWikiFrame.py:2980 +msgid "" +"Wiki '%s' is probably in use by different\n" +"instance of WikidPad. Connect anyway (dangerous!)?" +msgstr "" +"'%s' wikit valószínűleg a WikidPad másik példánya\n" +" használja. Mindenképp csatlakozzon? (Veszélyes!)" + +#: lib\pwiki\PersonalWikiFrame.py:2982 +msgid "Wiki already in use" +msgstr "Wiki már használatban van" + +#: lib\pwiki\PersonalWikiFrame.py:2991 +msgid "" +"Configuration file '%s' is corrupted or missing.\n" +"You may have to change some settings in configuration page \"Current Wiki\" and below which were lost." +msgstr "" +"Konfigurációs fájl '%s' megsérült, vagy hiányzik.\n" +"Lehet, hogy meg kell változtatnia néhány beállítást a \"Current Wiki\" konfigurációs lapon és alatta, amelyek elvesztek." + +#: lib\pwiki\PersonalWikiFrame.py:3024 +msgid "Can't write to database '%s'" +msgstr "Nem tud írni az adatbázisba '%s'" + +#: lib\pwiki\PersonalWikiFrame.py:3229 +msgid "" +"There is no (write-)access to underlying wiki\n" +"Close anyway and loose possible changes?" +msgstr "" +"Nincs hozzáférés írásra az alanti wikire\n" +"Bezárja mindenképpen és elveszti a változásokat?" + +#: lib\pwiki\PersonalWikiFrame.py:3231 +msgid "Close anyway" +msgstr "Bezárja mindenképpen" + +#: lib\pwiki\PersonalWikiFrame.py:3315 +msgid "This operation requires an open database" +msgstr "A művelet nyitott adatbázist igényel" + +#: lib\pwiki\PersonalWikiFrame.py:3316 +msgid "No open database" +msgstr "Nincs nyitott adatbázis" + +#: lib\pwiki\PersonalWikiFrame.py:3328 +msgid "No connection to database. Try to reconnect?" +msgstr "Nincs kapcsolat az adatbázishoz. Probálja újracsatolni?" + +#: lib\pwiki\PersonalWikiFrame.py:3329 +msgid "Reconnect database?" +msgstr "Újracsatolja az adatbázishoz?" + +#: lib\pwiki\PersonalWikiFrame.py:3336 +msgid "Trying to reconnect database..." +msgstr "Próbálja újracsatolni az adatbázist..." + +#: lib\pwiki\PersonalWikiFrame.py:3348 +msgid "Error while reconnecting database" +msgstr "Hiba az adatbázis újracsatolása közben" + +#: lib\pwiki\PersonalWikiFrame.py:3373 +msgid "" +"This operation needs write access to database\n" +"Try to write?" +msgstr "" +"Ez a művelet írási hozzáférést igényel az adatbázishoz\n" +"Megpróbál írni?" + +#: lib\pwiki\PersonalWikiFrame.py:3374 +msgid "Try writing?" +msgstr "Megpróbál írni?" + +#: lib\pwiki\PersonalWikiFrame.py:3381 +msgid "Trying to write to database..." +msgstr "Megpróbál az adatbázisba írni..." + +#: lib\pwiki\PersonalWikiFrame.py:3395 +msgid "Error while writing to database" +msgstr "Hiba az adatbázisba íráskor" + +#: lib\pwiki\PersonalWikiFrame.py:3419 +msgid "" +"Database connection error: %s.\n" +"Try to re-establish, then run \"Wiki\"->\"Reconnect\"" +msgstr "" +"Adatbáziskapcsolati hiba: %s.\n" +"Próbálja újra létrehozni a kapcsolatot, majd futtassa \"Wiki\"->\"Reconnect\"" + +#: lib\pwiki\PersonalWikiFrame.py:3421 +#: lib\pwiki\PersonalWikiFrame.py:3438 +msgid "Connection lost" +msgstr "Kapcsolat elveszett" + +#: lib\pwiki\PersonalWikiFrame.py:3436 +msgid "" +"No write access to database: %s.\n" +" Try to re-establish, then run \"Wiki\"->\"Reconnect\"" +msgstr "" +"Az adatbázishoz nincs hozzáférés írásra: %s.\n" +"Próbáljon újracsatlakozni, majd futtassa \"Wiki\"->\"Reconnect\"" + +#: lib\pwiki\PersonalWikiFrame.py:3455 +msgid "Trying to reconnect ..." +msgstr "Megpróbál újracsatlakozni..." + +#: lib\pwiki\PersonalWikiFrame.py:3464 +msgid "Error while trying to reconnect:" +msgstr "Hiba az újracsatolási kísérlet közben:" + +#: lib\pwiki\PersonalWikiFrame.py:3714 +msgid "Couldn't start file" +msgstr "Nem tudja elindítani a fájlt" + +#: lib\pwiki\PersonalWikiFrame.py:3729 +msgid "Couldn't open wiki: %s" +msgstr "Nem tudja a wikit megnyitni: %s" + +#: lib\pwiki\PersonalWikiFrame.py:3759 +msgid "Mod.: %s" +msgstr "Mod.: %s" + +#: lib\pwiki\PersonalWikiFrame.py:3760 +msgid "; Crea.: %s" +msgstr "; Készít: %s" + +#: lib\pwiki\PersonalWikiFrame.py:3797 +msgid "Parent nodes of '%s'" +msgstr "'%s' felmenő csomópontjai" + +#: lib\pwiki\PersonalWikiFrame.py:3809 +msgid "Parentless nodes" +msgstr "Felmenő nélküli csomópontok" + +#: lib\pwiki\PersonalWikiFrame.py:3821 +msgid "Child nodes of '%s'" +msgstr "'%s' leszármazott csomóponjai" + +#: lib\pwiki\PersonalWikiFrame.py:3834 +msgid "Bookmarks" +msgstr "Könyvjelzők" + +#: lib\pwiki\PersonalWikiFrame.py:3981 +msgid "Wiki: %s" +msgstr "Wiki: %s" + +#: lib\pwiki\PersonalWikiFrame.py:4120 +msgid "Set at Page: %s\t%s" +msgstr "Beállítva erre a lapra: %s\t%s" + +#: lib\pwiki\PersonalWikiFrame.py:4136 +msgid "Error saving global configuration" +msgstr "Hiba a globális beállítások mentésekor" + +#: lib\pwiki\PersonalWikiFrame.py:4147 +msgid "Error saving current configuration" +msgstr "Hiba az aktuális beállítások mentésekor" + +#: lib\pwiki\PersonalWikiFrame.py:4169 +msgid "No real wiki word selected to rename" +msgstr "Nincs igazi wikiszó kiválasztva átnevezésre" + +#: lib\pwiki\PersonalWikiFrame.py:4173 +msgid "The scratch pad cannot be renamed." +msgstr "A Vázlatfüzetet nem lehet átnevezni" + +#: lib\pwiki\PersonalWikiFrame.py:4197 +msgid "Description:" +msgstr "Leírás:" + +#: lib\pwiki\PersonalWikiFrame.py:4198 +msgid "Store new version" +msgstr "Új verzió mentése" + +#: lib\pwiki\PersonalWikiFrame.py:4212 +msgid "Do you want to delete all stored versions?" +msgstr "Mindegyik tárolt verziót törli?" + +#: lib\pwiki\PersonalWikiFrame.py:4213 +msgid "Delete All Versions" +msgstr "Minden verzió törlése" + +#: lib\pwiki\PersonalWikiFrame.py:4358 +msgid "The scratch pad cannot be deleted" +msgstr "A Vázlatfüzetet nem lehet törölni" + +#: lib\pwiki\PersonalWikiFrame.py:4362 +msgid "No real wiki word to delete" +msgstr "Nincs törölhető valós wikiszó" + +#: lib\pwiki\PersonalWikiFrame.py:4369 +msgid "Are you sure you want to delete wiki word '%s'?" +msgstr "Biztosan törölni akarja a '%s' wikiszót?" + +#: lib\pwiki\PersonalWikiFrame.py:4398 +msgid "No real wiki word to modify" +msgstr "Nincs módosítható valós wikiszó" + +#: lib\pwiki\PersonalWikiFrame.py:4414 +msgid "Replace text by WikiWord:" +msgstr "Szöveg lecserélése wikiszóval:" + +#: lib\pwiki\PersonalWikiFrame.py:4415 +msgid "Replace by Wiki Word" +msgstr "Lecserélés wikiszóval" + +#: lib\pwiki\PersonalWikiFrame.py:4424 +msgid "'%s' is an invalid wiki word." +msgstr "'%s' érvénytelen wikiszó." + +#: lib\pwiki\PersonalWikiFrame.py:4439 +msgid "" +"Wiki word %s exists already\n" +"Would you like to append to the word?" +msgstr "" +"%s wikiszó már létezik\n" +"Hozzáfűzi a szóhoz?" + +#: lib\pwiki\PersonalWikiFrame.py:4442 +msgid "Word exists" +msgstr "Szó létezik" + +#: lib\pwiki\PersonalWikiFrame.py:4693 +msgid "Error on export" +msgstr "Hiba az exportban" + +#: lib\pwiki\PersonalWikiFrame.py:4723 +msgid "Choose a file to create URL for" +msgstr "Válassza ki a fájlt, amihez URL-t készít" + +#: lib\pwiki\PersonalWikiFrame.py:4791 +msgid "Are you sure you want to start a full rebuild of wiki in background?" +msgstr "Biztosan elindítja a wiki teljes újraépítését a háttérben?" + +#: lib\pwiki\PersonalWikiFrame.py:4793 +msgid "Initiate update" +msgstr "Frissítés indítása" + +#: lib\pwiki\PersonalWikiFrame.py:4800 +#: lib\pwiki\PersonalWikiFrame.py:4801 +msgid " Initiating update " +msgstr " Frissítés indítása " + +#: lib\pwiki\PersonalWikiFrame.py:4815 +msgid "Error initiating update" +msgstr "Hiba a frissítés indításakor" + +#: lib\pwiki\PersonalWikiFrame.py:4824 +msgid "Are you sure you want to rebuild this wiki? You may want to backup your data first!" +msgstr "Biztosan újraépítené ezt a wikit? Előbb talán menthetné az adatait!" + +#: lib\pwiki\PersonalWikiFrame.py:4826 +msgid "Rebuild wiki" +msgstr "Wiki újraépítése" + +#: lib\pwiki\PersonalWikiFrame.py:4833 +#: lib\pwiki\PersonalWikiFrame.py:4834 +msgid " Rebuilding wiki " +msgstr " Wiki újraépítése " + +#: lib\pwiki\PersonalWikiFrame.py:4849 +msgid "Error rebuilding wiki" +msgstr "Hiba a wiki újraépítésekor" + +#: lib\pwiki\PersonalWikiFrame.py:4911 +msgid "This could overwrite pages in the database. Continue?" +msgstr "Ez felülírhat lapokat az adatbázisban. Folytatja?" + +#: lib\pwiki\PersonalWikiFrame.py:4912 +msgid "Import pagefiles" +msgstr "Lapfájl importálása" + +#: lib\pwiki\PersonalWikiFrame.py:5025 +msgid "No list of strings passed to \"listmcstr\" dialog" +msgstr "Sztringlistát a \"listmcstr\" párbeszéd nem kapott" + +#: lib\pwiki\PersonalWikiFrame.py:5048 +msgid "Unknown dialog type" +msgstr "Ismeretlen párbeszédtípus" + +#: lib\pwiki\PersonalWikiFrame.py:5187 +#: lib\pwiki\PersonalWikiFrame.py:5205 +#: lib\pwiki\PersonalWikiFrame.py:5230 +msgid "Choose a Wiki to open" +msgstr "Válassz egy wikit megnyitásra" + +#: lib\pwiki\PersonalWikiFrame.py:5244 +msgid "Name for new wiki (must be in the form of a WikiWord):" +msgstr "Az új wiki neve (wikiszó fromátumú legyen):" + +#: lib\pwiki\PersonalWikiFrame.py:5245 +msgid "Create New Wiki" +msgstr "Új wiki készítése" + +#: lib\pwiki\PersonalWikiFrame.py:5260 +msgid "Directory to store new wiki" +msgstr "Könyvtár az új wiki tárolására" + +#: lib\pwiki\PersonalWikiFrame.py:5271 +#: lib\pwiki\wikidata\WikiDataManager.py:1282 +#: lib\pwiki\wikidata\WikiDataManager.py:1338 +msgid "'%s' is an invalid wiki word. %s" +msgstr "'%s' szabálytalan wikiszó. %s" + +#: lib\pwiki\PersonalWikiFrame.py:5575 +msgid "Clipboard Catcher at Cursor" +msgstr "Vágólapbeolvasó a kurzornál" + +#: lib\pwiki\PersonalWikiFrame.py:5579 +msgid "Clipboard Catcher off" +msgstr "Vágólapbeolvasó ki" + +#: lib\pwiki\PersonalWikiFrame.py:5640 +msgid "Restore" +msgstr "Helyreállít" + +#: lib\pwiki\PersonalWikiFrame.py:5641 +msgid "Save" +msgstr "Ment" + +#: lib\pwiki\Printing.py:325 +#: lib\pwiki\Printing.py:641 +msgid "Print Preview" +msgstr "Nyomtatási előnézet" + +#: lib\pwiki\Printing.py:338 +msgid "Printout" +msgstr "Nyomtatvány" + +#: lib\pwiki\SearchAndReplaceDialogs.py:149 +msgid "Searching... (click into field to abort)" +msgstr "Keres... (kattintás a mezőn megszakít)" + +#: lib\pwiki\SearchAndReplaceDialogs.py:151 +msgid "Not found" +msgstr "Nincs találat" + +#: lib\pwiki\SearchAndReplaceDialogs.py:709 +msgid "End of document reached. Continue at beginning?" +msgstr "Elérte a dokumentum végét. Folytatja az elejétől?" + +#: lib\pwiki\SearchAndReplaceDialogs.py:711 +msgid "Continue at beginning?" +msgstr "Folytatja az elejétől" + +#: lib\pwiki\SearchAndReplaceDialogs.py:722 +#: lib\pwiki\SearchAndReplaceDialogs.py:723 +msgid "No matches found" +msgstr "Nincs egyezés" + +#: lib\pwiki\SearchAndReplaceDialogs.py:737 +#: lib\pwiki\SearchAndReplaceDialogs.py:750 +#: lib\pwiki\SearchAndReplaceDialogs.py:989 +#: lib\pwiki\SearchAndReplaceDialogs.py:1193 +#: lib\pwiki\SearchAndReplaceDialogs.py:1219 +#: lib\pwiki\SearchAndReplaceDialogs.py:1276 +#: lib\pwiki\SearchAndReplaceDialogs.py:1290 +#: lib\pwiki\SearchAndReplaceDialogs.py:1369 +#: lib\pwiki\SearchAndReplaceDialogs.py:1418 +#: lib\pwiki\SearchAndReplaceDialogs.py:1579 +#: lib\pwiki\SearchAndReplaceDialogs.py:2200 +msgid "Error in regular expression" +msgstr "Hiba a szabályos kifejezésben" + +#: lib\pwiki\SearchAndReplaceDialogs.py:778 +#: lib\pwiki\SearchAndReplaceDialogs.py:1363 +msgid "%i replacements done" +msgstr "%i csere történt" + +#: lib\pwiki\SearchAndReplaceDialogs.py:779 +#: lib\pwiki\SearchAndReplaceDialogs.py:1303 +#: lib\pwiki\SearchAndReplaceDialogs.py:1364 +msgid "Replace All" +msgstr "Mindet cseréli" + +#: lib\pwiki\SearchAndReplaceDialogs.py:988 +msgid "" +"Bad regular expression '%s':\n" +"%s" +msgstr "" +"Hibás szabályos kifejezés '%s':\n" +"%s" + +#: lib\pwiki\SearchAndReplaceDialogs.py:1196 +#: lib\pwiki\SearchAndReplaceDialogs.py:1223 +#: lib\pwiki\SearchAndReplaceDialogs.py:1279 +#: lib\pwiki\SearchAndReplaceDialogs.py:1294 +#: lib\pwiki\SearchAndReplaceDialogs.py:1372 +#: lib\pwiki\SearchAndReplaceDialogs.py:1422 +#: lib\pwiki\SearchAndReplaceDialogs.py:1582 +#: lib\pwiki\SearchAndReplaceDialogs.py:2203 +msgid "Error in boolean expression" +msgstr "Hiba a boolean kifejezésben" + +#: lib\pwiki\SearchAndReplaceDialogs.py:1199 +#: lib\pwiki\SearchAndReplaceDialogs.py:1227 +#: lib\pwiki\SearchAndReplaceDialogs.py:1375 +#: lib\pwiki\SearchAndReplaceDialogs.py:1585 +#: lib\pwiki\SearchAndReplaceDialogs.py:2206 +msgid "Error. Maybe wiki rebuild is needed" +msgstr "Hiba. Szükség lehet a Wiki újjáépítésére" + +#: lib\pwiki\SearchAndReplaceDialogs.py:1303 +msgid "Replace all occurrences?" +msgstr "Minden előfordulást lecserél?" + +#: lib\pwiki\SearchAndReplaceDialogs.py:1430 +msgid "Choose search title" +msgstr "Válasszon keresési címet" + +#: lib\pwiki\SearchAndReplaceDialogs.py:1440 +msgid "Do you want to overwrite existing search '%s'?" +msgstr "Felülírja a meglévő '%s' keresést?" + +#: lib\pwiki\SearchAndReplaceDialogs.py:1441 +msgid "Overwrite search" +msgstr "Keresés felülírása" + +#: lib\pwiki\SearchAndReplaceDialogs.py:1456 +msgid "Invalid search string, can't save as view" +msgstr "Érvénytelen keresési karaktersor, nem menthető képként" + +#: lib\pwiki\SearchAndReplaceDialogs.py:1556 +msgid "Do you want to delete %i search(es)?" +msgstr "Töröl a(z) %i keresést?" + +#: lib\pwiki\SearchAndReplaceDialogs.py:1557 +msgid "Delete search" +msgstr "Keresés törlése" + +#: lib\pwiki\SearchAndReplaceDialogs.py:1657 +msgid "*Set page list*" +msgstr "Laplita beállítása" + +#: lib\pwiki\SearchAndReplaceDialogs.py:1941 +msgid "" +msgstr "" + +#: lib\pwiki\SearchAndReplaceDialogs.py:1991 +msgid "Fast Search" +msgstr "Gyorskeresés" + +#: lib\pwiki\SearchAndReplaceDialogs.py:2018 +msgid "As Tab" +msgstr "Fülként" + +#: lib\pwiki\SearchAndReplaceDialogs.py:2058 +msgid "Search: %s" +msgstr "Keres: %s" + +#: lib\pwiki\SearchAndReplaceDialogs.py:2307 +#: lib\pwiki\WikiHtmlView.py:710 +msgid "Activate" +msgstr "Aktivál" + +#: lib\pwiki\SearchAndReplaceDialogs.py:2309 +#: lib\pwiki\WikiHtmlView.py:712 +msgid "Activate New Tab Backgrd." +msgstr "Új fülháttér aktiválása" + +#: lib\pwiki\SpellChecker.py:117 +msgid "No wiki open or current page is a functional page" +msgstr "Nincs nyitott wiki, vagy az aktuális lap funkciólap" + +#: lib\pwiki\SpellChecker.py:131 +msgid "Current page is not modified yet" +msgstr "Jelenlegi lap még nincs módosítva" + +#: lib\pwiki\SpellChecker.py:138 +#: lib\pwiki\SpellChecker.py:172 +#: lib\pwiki\SpellChecker.py:180 +msgid "No (more) misspelled words found" +msgstr "További gépelési hibát nem talált" + +#: lib\pwiki\SpellChecker.py:148 +msgid "No dictionary found for this page" +msgstr "Nem talált szótárat ehhez a laphoz" + +#: lib\pwiki\StringOps.py:715 +msgid "Inval. timestamp" +msgstr "Érvénytelen dátumbélyegző" + +#: lib\pwiki\TextTree.py:205 +#: lib\pwiki\TextTree.py:240 +msgid "" +msgstr "" + +#: lib\pwiki\TextTree.py:361 +msgid "Select wiki for favorites" +msgstr "Wikit a kedvencekhez" + +#: lib\pwiki\Trashcan.py:183 +#: lib\pwiki\Trashcan.py:440 +#: lib\pwiki\Trashcan.py:452 +#: lib\pwiki\timeView\Versioning.py:175 +#: lib\pwiki\timeView\Versioning.py:442 +#: lib\pwiki\timeView\Versioning.py:454 +msgid "Versioning data damaged" +msgstr "Verziójelölő dátum megsérült" + +#: lib\pwiki\WikiExceptions.py:72 +msgid "Multiple words rename to same word" +msgstr "Több szó átnevezése ugyanarra a szóra" + +#: lib\pwiki\WikiHtmlView.py:597 +#: lib\pwiki\WikiTxtCtrl.py:2098 +msgid "Folder does not exist" +msgstr "Könyvtár nem létezik" + +#: lib\pwiki\WikiHtmlView.py:689 +#: lib\pwiki\WikiHtmlViewIE.py:479 +msgid "Link to page: %s" +msgstr "Link a(z) %s laphoz" + +#: lib\pwiki\WikiTreeCtrl.py:525 +msgid "Views" +msgstr "Nézet" + +#: lib\pwiki\WikiTreeCtrl.py:902 +msgid "searches" +msgstr "Keresések" + +#: lib\pwiki\WikiTreeCtrl.py:984 +msgid "modified-within" +msgstr "Módosítva ...belül" + +#: lib\pwiki\WikiTreeCtrl.py:1012 +msgid "1 day" +msgstr "1 nap" + +#: lib\pwiki\WikiTreeCtrl.py:1014 +msgid "%i days" +msgstr "%i napja" + +#: lib\pwiki\WikiTreeCtrl.py:1052 +msgid "parentless-nodes" +msgstr "Felmenő nélküli csomópontok" + +#: lib\pwiki\WikiTreeCtrl.py:1083 +msgid "undefined-nodes" +msgstr "nem deffiniált csomópontok" + +#: lib\pwiki\WikiTreeCtrl.py:1113 +msgid "Func. pages" +msgstr "Funkc.lapok" + +#: lib\pwiki\WikiTreeCtrl.py:1245 +msgid "Add icon attribute" +msgstr "Ikonattribútum hozzáadása" + +#: lib\pwiki\WikiTreeCtrl.py:1252 +msgid "Add color attribute" +msgstr "Színattribútum hozzáadása" + +#: lib\pwiki\WikiTreeCtrl.py:1856 +msgid "Append Wiki Word" +msgstr "Wikiszó hozzáfűzése" + +#: lib\pwiki\WikiTreeCtrl.py:1871 +msgid "Prepend Wiki Word" +msgstr "Wikiszó beszúrása előre" + +#: lib\pwiki\WikiTxtCtrl.py:1287 +msgid "Select Template" +msgstr "Sablon kiválasztása" + +#: lib\pwiki\WikiTxtCtrl.py:1289 +msgid "Select Template (deletes current content!)" +msgstr "Válassza ki a sablon (törli az aktuális tartalmat)" + +#: lib\pwiki\WikiTxtCtrl.py:1395 +msgid "Use Template" +msgstr "Használandó sablon" + +#: lib\pwiki\WikiTxtCtrl.py:2174 +msgid "" +"Set in menu \"Wiki\", item \"Options...\", options page \"Security\", \n" +"item \"Script security\" an appropriate value to execute a script." +msgstr "" +"Szkriptfuttatáshoz a \"Wiki\" menü, \"Opciók\" lap \"Biztonság\"\n" +"részben a \"Szkript biztonság\"-ot állítsa a megfelelő értékre! " + +#: lib\pwiki\WikiTxtCtrl.py:2177 +msgid "Script execution disabled" +msgstr "Szkriptfuttatás letiltva" + +#: lib\pwiki\WikiTxtCtrl.py:2249 +msgid "" +"\n" +"Exception: %s" +msgstr "" +"\n" +"Kivétel: %s" + +#: lib\pwiki\WikiTxtCtrl.py:2942 +msgid "Line: %d Col: %d Pos: %d" +msgstr "Sor: %d Oszl.: %d Poz: %d" + +#: lib\pwiki\WikiTxtCtrl.py:3122 +msgid "Couldn't copy file" +msgstr "Nem tudta másolni a fájlt" + +#: lib\pwiki\WikiTxtCtrl.py:3383 +msgid "Ignore" +msgstr "Elvet" + +#: lib\pwiki\WikiTxtCtrl.py:3384 +msgid "Add Globally" +msgstr "Hozzáad globálisan" + +#: lib\pwiki\WikiTxtCtrl.py:3385 +msgid "Add Locally" +msgstr "Hozzáad helyileg" + +#: lib\pwiki\WikiTxtCtrl.py:3395 +msgid "Follow Link" +msgstr "Link követése" + +#: lib\pwiki\WikiTxtCtrl.py:3396 +msgid "Follow Link New Tab" +msgstr "Hivatkozás követése új fülön" + +#: lib\pwiki\WikiTxtCtrl.py:3397 +msgid "Follow Link New Tab Backgrd." +msgstr "Hivatkozás követése új fülön háttérben" + +#: lib\pwiki\WikiTxtCtrl.py:3399 +msgid "Convert Absolute/Relative File URL" +msgstr "Abszolút/relatív fájl URL konvertálása" + +#: lib\pwiki\WikiTxtCtrl.py:3400 +msgid "Open Containing Folder" +msgstr "Nyissa meg tároló könyvtárat" + +#: lib\pwiki\WikiTxtCtrl.py:3402 +msgid "Copy anchor URL to clipboard" +msgstr "Horgony URL másolása vágólapra" + +#: lib\pwiki\WikiTxtCtrl.py:3404 +msgid "Other..." +msgstr "Más..." + +#: lib\pwiki\WikiTxtCtrl.py:3405 +msgid "Use Template..." +msgstr "Használja a sablont ..." + +#: lib\pwiki\WikiTxtCtrl.py:3409 +msgid "Show folding" +msgstr "Összecsukásjelek megjelenítése" + +#: lib\pwiki\WikiTxtCtrl.py:3410 +msgid "Show folding marks and allow folding" +msgstr "Összecsukásjelek megjelenítése és az összecsukás engedélyezése" + +#: lib\pwiki\WikiTxtCtrl.py:3411 +msgid "&Toggle current folding" +msgstr "Aktuális összecsukások váltása" + +#: lib\pwiki\WikiTxtCtrl.py:3412 +msgid "Toggle folding of the current line" +msgstr "Az összecsukás átállítása az aktuális sorban" + +#: lib\pwiki\WikiTxtCtrl.py:3413 +msgid "&Unfold All" +msgstr "Kibont mind" + +#: lib\pwiki\WikiTxtCtrl.py:3414 +msgid "Unfold everything in current editor" +msgstr "Aktuális szerkesztőben mindent kibont" + +#: lib\pwiki\WikiTxtCtrl.py:3415 +msgid "&Fold All" +msgstr "Összecsukja mindet" + +#: lib\pwiki\WikiTxtCtrl.py:3416 +msgid "Fold everything in current editor" +msgstr "Összecsuk mindent az aktuális szerkesztőben" + +#: lib\pwiki\WikiTxtDialogs.py:43 +msgid "Incremental search (ENTER/ESC to finish)" +msgstr "Karakterrészlet keresése (ENTER vagy ESC megszakításhoz)" + +#: lib\pwiki\WindowsHacks.py:299 +msgid "Copying from %s to %s failed. SHFileOperation result no. %s" +msgstr "Másolás %s-ből %s-be nem sikerült. SHFileOperation eredménye %s" + +#: lib\pwiki\WindowsHacks.py:303 +msgid "Moving from %s to %s failed. SHFileOperation result no. %s" +msgstr "Mozgatás %s-ből %s-be nem sikerült. SHFileOperation eredménye %s" + +#: lib\pwiki\timeView\DatedWikiWordFilters.py:131 +msgid "Modified" +msgstr "Módosítva" + +#: lib\pwiki\timeView\TimeViewCtrl.py:47 +msgid "Versions" +msgstr "Verzió" + +#: lib\pwiki\timeView\TimelinePanel.py:799 +msgid "Show dates without associated wiki words" +msgstr "Dátumokat hozzátartozó wikiszavak nélkül mutatja." + +#: lib\pwiki\timeView\TimelinePanel.py:801 +msgid "List dates ascending or descending" +msgstr "Dátumokat növekvő vagy csökkenő sorban mutatja" + +#: lib\pwiki\timeView\VersioningGui.py:225 +msgid "Deleting in-between versions is not supported yet" +msgstr "Verziók közötti törlés még nem támogatott" + +#: lib\pwiki\timeView\VersioningGui.py:228 +msgid "Do you want to delete this version?" +msgstr "Törli ezt a verziót?" + +#: lib\pwiki\timeView\VersioningGui.py:229 +#: lib\pwiki\timeView\VersioningGui.py:814 +msgid "Delete version" +msgstr "Verzió törlése" + +#: lib\pwiki\timeView\VersioningGui.py:241 +#: lib\pwiki\timeView\VersioningGui.py:273 +msgid "Do you want to delete all version data of this page?" +msgstr "Törli az összes verzióadatot ezen a lapon?" + +#: lib\pwiki\timeView\VersioningGui.py:242 +#: lib\pwiki\timeView\VersioningGui.py:274 +#: lib\pwiki\timeView\VersioningGui.py:819 +msgid "Delete all versions" +msgstr "Összes verzió törlése" + +#: lib\pwiki\timeView\VersioningGui.py:288 +#: lib\pwiki\timeView\VersioningGui.py:349 +#: lib\pwiki\timeView\VersioningGui.py:577 +msgid "Current" +msgstr "Aktuális" + +#: lib\pwiki\timeView\VersioningGui.py:806 +msgid "Diff inline" +msgstr "Jav. megjelen." + +#: lib\pwiki\timeView\VersioningGui.py:807 +msgid "Show the difference between two versions inline" +msgstr "Mutassa a két verzió közötti különbséget megnyitott állapotban" + +#: lib\pwiki\timeView\VersioningGui.py:809 +msgid "Set diff from" +msgstr "Dif.-et vegye innen" + +#: lib\pwiki\timeView\VersioningGui.py:810 +msgid "Set the \"from\" version in diff" +msgstr "Helyezze a \"ból\ verziót a dif.-be" + +#: lib\pwiki\timeView\VersioningGui.py:811 +msgid "Set diff to" +msgstr "Dif.-et tegye ide" + +#: lib\pwiki\timeView\VersioningGui.py:812 +msgid "Set the \"to\" version in diff" +msgstr "Helyezze a \"ba\" verziót a dif.-be" + +#: lib\pwiki\timeView\VersioningGui.py:815 +msgid "Delete selected version" +msgstr "Kiválasztott verziók törlése" + +#: lib\pwiki\timeView\VersioningGui.py:817 +msgid "Add version" +msgstr "Verzió hozzáadása" + +#: lib\pwiki\timeView\VersioningGui.py:818 +msgid "Add a new version" +msgstr "Új verzió hozzáadása" + +#: lib\pwiki\timeView\VersioningGui.py:820 +msgid "Delete all versions of current page" +msgstr "Aktuális lapon valamennyi verzió törlése" + +#: lib\pwiki\wikidata\FileStorage.py:194 +msgid "File compare error, file not readable or changed during compare" +msgstr "Fájl összehasonlítási hiba, a fájl nem olvasható, vagy összevetés közben megváltozott" + +#: lib\pwiki\wikidata\FileStorage.py:216 +msgid "Path '%s' must point to an existing file" +msgstr "'%s' útvonalnak létező fájlra kell mutatnia" + +#: lib\pwiki\wikidata\FileStorage.py:233 +msgid "Internal error: Bad source file name" +msgstr "Belső hiba. Rossz forrásfájlnév" + +#: lib\pwiki\wikidata\FileStorage.py:298 +msgid "Copy of file '%s' couldn't be created" +msgstr "'%s' fájl másolatát nem lehetett létrehozni " + +#: lib\pwiki\wikidata\WikiDataManager.py:58 +msgid "Database exists already and is currently in use" +msgstr "Az adatbázis már létezik és jelenleg használatban van" + +#: lib\pwiki\wikidata\WikiDataManager.py:63 +#: lib\pwiki\wikidata\WikiDataManager.py:1703 +msgid "Data handler %s not available" +msgstr "Adatkezelő %s elérhetetlen" + +#: lib\pwiki\wikidata\WikiDataManager.py:84 +msgid "Database is already in use with database handler \"%s\". Can't open with different handler." +msgstr "Adatbázist \"%s\" adatbáziskezelő már használja. Másik kezelővel nem nyitható meg." + +#: lib\pwiki\wikidata\WikiDataManager.py:90 +msgid "Database is already in use with wiki language handler \"%s\". Can't open with different handler." +msgstr "Adatbázist a wiki \"%s\" nyelvi kezelője már használja. Eltérő kezelővel nem nyitható meg." + +#: lib\pwiki\wikidata\WikiDataManager.py:285 +msgid "Wiki is probably already in use by other instance" +msgstr "A wikit egy másik példány már valószínűleg használja." + +#: lib\pwiki\wikidata\WikiDataManager.py:314 +msgid "Wiki configuration file is corrupted" +msgstr "Sérült a wiki konfigurációs fájlja" + +#: lib\pwiki\wikidata\WikiDataManager.py:350 +msgid "No data handler information found, probably \"Original Gadfly\" is right." +msgstr "Nics információ az adatkezelőről, valószínűleg az \"Original Gadfly\" megfelel." + +#: lib\pwiki\wikidata\WikiDataManager.py:356 +msgid "Required data handler \"%s\" unknown to WikidPad" +msgstr "A szükséges adatkezelőt \"%s\" a WikidPad nem ismeri" + +#: lib\pwiki\wikidata\WikiDataManager.py:362 +msgid "Error on initializing data handler \"%s\"" +msgstr "Hiba az adatkezelő indításakor \"%s\"" + +#: lib\pwiki\wikidata\WikiDataManager.py:373 +msgid "Required wiki language handler \"%s\" not available" +msgstr "A szükséges nyelvkezelő \"%s\" nem áll rendelkezésre" + +#: lib\pwiki\wikidata\WikiDataManager.py:820 +msgid "Word '%s' not in wiki" +msgstr "'%s' szó nincs a wikiben" + +#: lib\pwiki\wikidata\WikiDataManager.py:1126 +#: lib\pwiki\wikidata\WikiDataManager.py:1190 +msgid "Update basic link info" +msgstr "Alap hivatkozási információk frissítése" + +#: lib\pwiki\wikidata\WikiDataManager.py:1142 +msgid "Starting update thread" +msgstr "Frissítési folyamat indítása" + +#: lib\pwiki\wikidata\WikiDataManager.py:1210 +msgid "Update attributes of %s" +msgstr "%s attribútumainak frissítése" + +#: lib\pwiki\wikidata\WikiDataManager.py:1232 +msgid "Update page %s" +msgstr "%s lap frissítése" + +#: lib\pwiki\wikidata\WikiDataManager.py:1250 +msgid "Final cleanup" +msgstr "Végső tisztítás" + +#: lib\pwiki\wikidata\WikiDataManager.py:1343 +msgid "Cannot rename '%s' to '%s', '%s' already exists" +msgstr "'%s'-t to '%s'-ra nem tudja átnevezni, '%s' már létezik" + +#: lib\pwiki\wikidata\compact_sqlite\DbStructure.py:725 +#: lib\pwiki\wikidata\original_gadfly\DbStructure.py:723 +#: lib\pwiki\wikidata\original_sqlite\DbStructure.py:695 +msgid "database already exists at location: %s" +msgstr "adatbázis %s helyen már létezik." + +#: lib\pwiki\wikidata\compact_sqlite\DbStructure.py:913 +#: lib\pwiki\wikidata\original_gadfly\DbStructure.py:779 +msgid "Update needed" +msgstr "Frissítés szükséges" + +#: lib\pwiki\wikidata\compact_sqlite\DbStructure.py:916 +#: lib\pwiki\wikidata\original_gadfly\DbStructure.py:782 +#: lib\pwiki\wikidata\original_sqlite\DbStructure.py:825 +msgid "Database has unknown format branchtag='%s'" +msgstr "Az adatbázis '%s' kiterjesztése ismeretlen" + +#: lib\pwiki\wikidata\compact_sqlite\DbStructure.py:925 +#: lib\pwiki\wikidata\original_gadfly\DbStructure.py:791 +#: lib\pwiki\wikidata\original_sqlite\DbStructure.py:834 +msgid "Database has unknown format version='%i'" +msgstr "Adatbázis formátuma='%i', ismeretlen verzió" + +#: lib\pwiki\wikidata\compact_sqlite\DbStructure.py:929 +#: lib\pwiki\wikidata\original_gadfly\DbStructure.py:795 +#: lib\pwiki\wikidata\original_sqlite\DbStructure.py:838 +msgid "Update needed, current format version='%i'" +msgstr "Frissítés kell, a formátum jelenlegi verziója='%i'" + +#: lib\pwiki\wikidata\compact_sqlite\DbStructure.py:932 +#: lib\pwiki\wikidata\original_gadfly\DbStructure.py:798 +#: lib\pwiki\wikidata\original_sqlite\DbStructure.py:841 +msgid "Database format is up to date" +msgstr "Az adatbázis formátuma friss" + +#: lib\pwiki\wikidata\compact_sqlite\WikiData.py:215 +msgid "Wiki page not found: %s" +msgstr "%s wikilap nem taláható" + +#: lib\pwiki\wikidata\compact_sqlite\WikiData.py:479 +#: lib\pwiki\wikidata\original_gadfly\WikiData.py:459 +#: lib\pwiki\wikidata\original_sqlite\WikiData.py:521 +msgid "You cannot delete the root wiki node" +msgstr "Nem törölheti a gyökér wiki csomópontot" + +#: lib\pwiki\wikidata\original_gadfly\WikiData.py:1051 +#: lib\pwiki\wikidata\original_sqlite\WikiData.py:1160 +msgid "Wiki page not found (no path information) for word: %s" +msgstr "%s szóhoz wikilap nem található (nincs útvonalinformáció)" + +#: lib\pwiki\wikidata\original_gadfly\WikiData.py:1069 +#: lib\pwiki\wikidata\original_gadfly\WikiData.py:1083 +#: lib\pwiki\wikidata\original_sqlite\WikiData.py:1178 +#: lib\pwiki\wikidata\original_sqlite\WikiData.py:1192 +msgid "Wiki page not found (bad path information) for word: %s" +msgstr "%s szóhoz wikilap nem található (rossz útvonalinformáció)" + +#~ msgid "New Tab" +#~ msgstr "Új fül" +#~ msgid "Opened wiki word '%s'" +#~ msgstr "Nyitott wikiszó '%s'" +#~ msgid "Internal Error" +#~ msgstr "Belső hiba" +#~ msgid "'%s' is already alias for the wiki word(s): %s" +#~ msgstr "'%s' álnév már tartozik a wikiszó(k)hoz: %s" + diff --git a/WikidPad_ru_RU.po b/WikidPad_ru_RU.po index 2a8fac46..c47cc30b 100644 --- a/WikidPad_ru_RU.po +++ b/WikidPad_ru_RU.po @@ -1,8 +1,11 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# msgid "" -msgstr "" -"Project-Id-Version: WIKIDPAD INTERNATIONALISATION. RUSSIAN. VERSION 1.0\n" +msgstr "Project-Id-Version: WIKIDPAD INTERNATIONALISATION. RUSSIAN. VERSION 1.0\n" "POT-Creation-Date: 2010-11-14 20:59\n" -"PO-Revision-Date: 2011-05-03 16:57+0600\n" +"PO-Revision-Date: 2011-05-29 17:36+0600\n" "Last-Translator: Oleg Domanov \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -129,11 +132,9 @@ msgid "0" msgstr "0" #: WikidPad.xrc:0 -msgid "" -"0: Never complete version; 1: Always complete v.;\n" +msgid "0: Never complete version; 1: Always complete v.;\n" "10: Every tenth v. is complete" -msgstr "" -"0: Никогда не завершать версию; 1: Всегда завершать версию;\n" +msgstr "0: Никогда не завершать версию; 1: Всегда завершать версию;\n" "10: Завершать каждую десятую версию" #: WikidPad.xrc:0 @@ -608,6 +609,10 @@ msgstr "Префикс заголовка:" msgid "Headings as aliases" msgstr "Заголовки как псевдонимы" +#: WikidPad.xrc:0 +msgid "Height:" +msgstr "Высота:" + #: WikidPad.xrc:0 msgid "Hidden" msgstr "Скрыть" @@ -644,6 +649,10 @@ msgstr "Игнорировать файл блокировки" msgid "Image pasting" msgstr "Вставка картинок" +#: WikidPad.xrc:0 +msgid "Image preview tooltips for local URLs" +msgstr "Всплывающий предпросмотр для локальных URL" + #: WikidPad.xrc:0 msgid "Import format:" msgstr "Формат импорта:" @@ -929,8 +938,8 @@ msgid "Preview" msgstr "Просмотр" #: WikidPad.xrc:0 -msgid "Preview renderer:" -msgstr "Браузер для режима просмотра:" +msgid "Preview renderer*:" +msgstr "Просмотр с помощью*:" #: WikidPad.xrc:0 msgid "Preview:" @@ -1082,7 +1091,7 @@ msgstr "Быстрая клавиша:" #: WikidPad.xrc:0 msgid "Show iframes inside IE preview" -msgstr "Показывать iframe при IE просмотре" +msgstr "Показывать iframe при просмотре в IE" #: WikidPad.xrc:0 msgid "Show in toolbar" @@ -1320,6 +1329,10 @@ msgstr "Использовать \"заплатку\" IME для ввода в msgid "Use link title if present" msgstr "Использовать название ссылки, если есть" +#: WikidPad.xrc:0 +msgid "Use vi keys in Webkit preview" +msgstr "Использовать клавиши vi при просмотре в Webkit" + #: WikidPad.xrc:0 msgid "User notification:" msgstr "Предупреждение пользователя:" @@ -1356,6 +1369,10 @@ msgstr "Пока открыта вики" msgid "Whole wiki" msgstr "Всю вики" +#: WikidPad.xrc:0 +msgid "Width:" +msgstr "Ширина:" + #: WikidPad.xrc:0 msgid "Wiki Search" msgstr "Найти в вики" @@ -1428,198 +1445,154 @@ msgstr "не " msgid "second(s)" msgstr "(секунды)" -#: WikidPad.xrc:0 -#: extensions\GraphvizStructureView.py:678 +#: WikidPad.xrc:0 extensions\GraphvizStructureView.py:678 #: extensions\GraphvizStructureView.py:702 #: extensions\GraphvizStructureView.py:715 #: extensions\GraphvizStructureView.py:728 msgid "..." msgstr "..." -#: WikidPad.xrc:0 -#: lib\pwiki\AdditionalDialogs.py:792 +#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:792 msgid " OK " msgstr " OK" -#: WikidPad.xrc:0 -#: lib\pwiki\AdditionalDialogs.py:796 +#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:796 msgid " Cancel " msgstr " Отмена" -#: WikidPad.xrc:0 -#: lib\pwiki\AdditionalDialogs.py:1183 +#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:1183 msgid "Destination directory:" msgstr "Папка для сохранения:" -#: WikidPad.xrc:0 -#: lib\pwiki\AdditionalDialogs.py:1377 +#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:1377 #: lib\pwiki\SearchAndReplaceDialogs.py:1485 msgid "Title:" msgstr "Заголовок:" -#: WikidPad.xrc:0 -#: lib\pwiki\AdditionalDialogs.py:1659 +#: WikidPad.xrc:0 lib\pwiki\AdditionalDialogs.py:1659 msgid "Source directory:" msgstr "Папка источника:" -#: WikidPad.xrc:0 -#: lib\pwiki\DiffGui.py:436 -#: lib\pwiki\PersonalWikiFrame.py:1320 -#: lib\pwiki\WikiTxtCtrl.py:3601 +#: WikidPad.xrc:0 lib\pwiki\DiffGui.py:436 lib\pwiki\PersonalWikiFrame.py:1338 +#: lib\pwiki\WikiTxtCtrl.py:3820 msgid "Copy" msgstr "Копировать" -#: WikidPad.xrc:0 -#: lib\pwiki\DiffGui.py:437 -#: lib\pwiki\PersonalWikiFrame.py:1329 -#: lib\pwiki\WikiTxtCtrl.py:3604 +#: WikidPad.xrc:0 lib\pwiki\DiffGui.py:437 lib\pwiki\PersonalWikiFrame.py:1347 +#: lib\pwiki\WikiTxtCtrl.py:3823 msgid "Select All" msgstr "Выделить всё" -#: WikidPad.xrc:0 -#: lib\pwiki\DiffGui.py:439 -#: lib\pwiki\WikiTxtCtrl.py:3618 +#: WikidPad.xrc:0 lib\pwiki\DiffGui.py:439 lib\pwiki\WikiTxtCtrl.py:3839 msgid "Close Tab" msgstr "Закрыть вкладку" -#: WikidPad.xrc:0 -#: lib\pwiki\FileCleanup.py:528 -#: lib\pwiki\FileCleanup.py:530 -#: lib\pwiki\FileCleanup.py:686 -#: lib\pwiki\FileCleanup.py:1264 +#: WikidPad.xrc:0 lib\pwiki\FileCleanup.py:528 lib\pwiki\FileCleanup.py:530 +#: lib\pwiki\FileCleanup.py:686 lib\pwiki\FileCleanup.py:1264 msgid "Keep" msgstr "Оставить" -#: WikidPad.xrc:0 -#: lib\pwiki\FileCleanup.py:528 -#: lib\pwiki\FileCleanup.py:616 -#: lib\pwiki\FileCleanup.py:686 -#: lib\pwiki\FileCleanup.py:775 -#: lib\pwiki\MptImporterGui.py:170 -#: lib\pwiki\MptImporterGui.py:172 -#: lib\pwiki\OptionsDialog.py:139 -#: lib\pwiki\OptionsDialog.py:885 +#: WikidPad.xrc:0 lib\pwiki\FileCleanup.py:528 lib\pwiki\FileCleanup.py:616 +#: lib\pwiki\FileCleanup.py:686 lib\pwiki\FileCleanup.py:775 +#: lib\pwiki\MptImporterGui.py:172 lib\pwiki\MptImporterGui.py:174 +#: lib\pwiki\OptionsDialog.py:139 lib\pwiki\OptionsDialog.py:893 msgid "Default" msgstr "По умолчанию" -#: WikidPad.xrc:0 -#: lib\pwiki\FileCleanup.py:529 -#: lib\pwiki\FileCleanup.py:531 -#: lib\pwiki\FileCleanup.py:687 -#: lib\pwiki\FileCleanup.py:1265 -#: lib\pwiki\WikiTxtCtrl.py:3603 +#: WikidPad.xrc:0 lib\pwiki\FileCleanup.py:529 lib\pwiki\FileCleanup.py:531 +#: lib\pwiki\FileCleanup.py:687 lib\pwiki\FileCleanup.py:1265 +#: lib\pwiki\WikiTxtCtrl.py:3822 msgid "Delete" msgstr "Удалить" -#: WikidPad.xrc:0 -#: lib\pwiki\FileCleanup.py:529 -#: lib\pwiki\FileCleanup.py:531 +#: WikidPad.xrc:0 lib\pwiki\FileCleanup.py:529 lib\pwiki\FileCleanup.py:531 #: lib\pwiki\FileCleanup.py:1265 msgid "Collect" msgstr "Собрать" -#: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:170 -#: lib\pwiki\MptImporterGui.py:172 +#: WikidPad.xrc:0 lib\pwiki\MptImporterGui.py:172 +#: lib\pwiki\MptImporterGui.py:174 msgid "Yes" msgstr "Да" -#: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:171 +#: WikidPad.xrc:0 lib\pwiki\MptImporterGui.py:173 msgid "Overwrite" msgstr "Заменить" -#: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:171 -#: lib\pwiki\MptImporterGui.py:172 +#: WikidPad.xrc:0 lib\pwiki\MptImporterGui.py:173 +#: lib\pwiki\MptImporterGui.py:174 msgid "No" msgstr "Нет" -#: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:195 +#: WikidPad.xrc:0 lib\pwiki\MptImporterGui.py:197 msgid "Import" msgstr "Импорт" -#: WikidPad.xrc:0 -#: lib\pwiki\OptionsDialog.py:680 +#: WikidPad.xrc:0 lib\pwiki\OptionsDialog.py:688 msgid "Versioning" msgstr "Версии" -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1295 -#: lib\pwiki\WikiTxtCtrl.py:3598 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1313 +#: lib\pwiki\WikiTxtCtrl.py:3817 msgid "Undo" msgstr "Отменить" -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1299 -#: lib\pwiki\WikiTxtCtrl.py:3599 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1317 +#: lib\pwiki\WikiTxtCtrl.py:3818 msgid "Redo" msgstr "Вернуть" -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1315 -#: lib\pwiki\WikiTxtCtrl.py:3600 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1333 +#: lib\pwiki\WikiTxtCtrl.py:3819 msgid "Cut" msgstr "Вырезать" -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1324 -#: lib\pwiki\WikiTxtCtrl.py:3602 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1342 +#: lib\pwiki\WikiTxtCtrl.py:3821 msgid "Paste" msgstr "Вставить" -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1617 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:1635 msgid "&Delete" msgstr "&Удалить" -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:2031 -#: lib\pwiki\PersonalWikiFrame.py:2032 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:2049 +#: lib\pwiki\PersonalWikiFrame.py:2050 msgid "Open Wiki Word" msgstr "Открыть вики-слово" -#: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:2062 -#: lib\pwiki\PersonalWikiFrame.py:2063 +#: WikidPad.xrc:0 lib\pwiki\PersonalWikiFrame.py:2080 +#: lib\pwiki\PersonalWikiFrame.py:2081 msgid "Rename Wiki Word" msgstr "Переименовать вики-слово" -#: WikidPad.xrc:0 -#: lib\pwiki\SearchAndReplaceDialogs.py:855 +#: WikidPad.xrc:0 lib\pwiki\SearchAndReplaceDialogs.py:855 msgid "Close" msgstr "Закрыть" -#: WikidPad.xrc:0 -#: lib\pwiki\SearchAndReplaceDialogs.py:1711 +#: WikidPad.xrc:0 lib\pwiki\SearchAndReplaceDialogs.py:1711 msgid "Set page list" msgstr "Задать фильтр страниц" -#: WikidPad.xrc:0 -#: lib\pwiki\SearchAndReplaceDialogs.py:1962 +#: WikidPad.xrc:0 lib\pwiki\SearchAndReplaceDialogs.py:1962 msgid "As Resultlist" msgstr "Как список" -#: WikidPad.xrc:0 -#: lib\pwiki\SearchAndReplaceDialogs.py:1967 +#: WikidPad.xrc:0 lib\pwiki\SearchAndReplaceDialogs.py:1967 #: lib\pwiki\SearchAndReplaceDialogs.py:2073 msgid "As Full Search" msgstr "Как полный поиск" -#: WikidPad.xrc:0 -#: lib\pwiki\SearchAndReplaceDialogs.py:2367 +#: WikidPad.xrc:0 lib\pwiki\SearchAndReplaceDialogs.py:2367 #: lib\pwiki\WikiHtmlView.py:672 msgid "Activate New Tab" msgstr "Открыть в новой вкладке" -#: WikidPad.xrc:0 -#: lib\pwiki\timeView\TimelinePanel.py:798 +#: WikidPad.xrc:0 lib\pwiki\timeView\TimelinePanel.py:798 msgid "Show empty days" msgstr "Показать пустые дни" -#: WikidPad.xrc:0 -#: lib\pwiki\timeView\TimelinePanel.py:800 +#: WikidPad.xrc:0 lib\pwiki\timeView\TimelinePanel.py:800 msgid "Sort dates ascending" msgstr "Сортировать даты по возрастанию" @@ -1627,15 +1600,13 @@ msgstr "Сортировать даты по возрастанию" msgid "Error starting WikidPad" msgstr "Ошибка при старте WikidPad" -#: WikidPadStarter.py:207 -#: lib\pwiki\PersonalWikiFrame.py:5097 +#: WikidPadStarter.py:207 lib\pwiki\PersonalWikiFrame.py:5139 #: lib\pwiki\SearchAndReplaceDialogs.py:724 #: lib\pwiki\SearchAndReplaceDialogs.py:1008 msgid "Error!" msgstr "Ошибка!" -#: WikidPadStarter.py:214 -#: lib\pwiki\MptImporterGui.py:199 +#: WikidPadStarter.py:214 lib\pwiki\MptImporterGui.py:201 msgid "Error" msgstr "Ошибка" @@ -1742,8 +1713,7 @@ msgstr "Одна HTML страница" msgid "Set of HTML pages" msgstr "Множество HTML страниц" -#: extensions\HtmlExporter.py:682 -#: extensions\HtmlExporter.py:771 +#: extensions\HtmlExporter.py:682 extensions\HtmlExporter.py:771 msgid "Exporting %s" msgstr "Экспортируется: %s" @@ -1751,7 +1721,7 @@ msgstr "Экспортируется: %s" msgid "
[Allow evaluation of insertions in \"Options\", page \"Security\", option \"Process insertion scripts\"]
" msgstr "
[Разрешите обработку вставок в меню \"Настройки\", страница \"Безопасность\", опция \"Обрабатывать вставные скрипты\"]
" -#: extensions\HtmlExporter.py:2151 +#: extensions\HtmlExporter.py:2202 msgid "[Unknown parser node with name \"%s\" found]" msgstr "[Найден неизвестный узел с именем \"%s\" ]" @@ -1783,24 +1753,20 @@ msgstr "Путь к Ploticus:" msgid "Output format:" msgstr "Формат вывода:" -#: extensions\WikidPadParserStub.py:121 +#: extensions\WikidPadParserStub.py:124 msgid "Footnotes as wiki words" msgstr "Сноски как вики-слова" -#: extensions\autoNew.py:58 -#: extensions\autoNew.py:59 +#: extensions\autoNew.py:58 extensions\autoNew.py:59 msgid "Create new page" msgstr "Создать новую страницу" -#: extensions\referrals.py:74 -#: extensions\referrals.py:75 -#: extensions\referrals.py:109 -#: extensions\referrals.py:129 +#: extensions\referrals.py:74 extensions\referrals.py:75 +#: extensions\referrals.py:109 extensions\referrals.py:129 msgid "Insert referring pages" msgstr "Вставить ссылки для текущей страницы" -#: extensions\referrals.py:109 -#: extensions\referrals.py:129 +#: extensions\referrals.py:109 extensions\referrals.py:129 msgid "Referers" msgstr "Ссылки страницы" @@ -1812,19 +1778,18 @@ msgstr "*Всего %s стр. ссылается на страницу* %s:\n" msgid "*%s page(s) referred to by* %s\n" msgstr "*Всего на %s стр. ссылается страница* %s:\n" -#: extensions\wikidPadParser\WikidPadParser.py:1625 -#: extensions\wikidPadParser\WikidPadParser.py:1652 +#: extensions\wikidPadParser\WikidPadParser.py:1649 +#: extensions\wikidPadParser\WikidPadParser.py:1676 msgid "This is a footnote" msgstr "Это сноска" -#: extensions\wikidPadParser\WikidPadParser.py:1630 -#: extensions\wikidPadParser\WikidPadParser.py:1657 +#: extensions\wikidPadParser\WikidPadParser.py:1654 +#: extensions\wikidPadParser\WikidPadParser.py:1681 msgid "This is syntactically not a wiki word" msgstr "Синтаксическая ошибка в вики-слове" -#: extensions\wikidPadParser\WikidPadParser.py:2274 -msgid "" -"++ Wiki Settings\n" +#: extensions\wikidPadParser\WikidPadParser.py:2298 +msgid "++ Wiki Settings\n" "\n" "These are your default global settings.\n" "\n" @@ -1834,8 +1799,7 @@ msgid "" "[global.wrap: 70]\n" "\n" "[icon: cog]\n" -msgstr "" -"++ Wiki Settings\n" +msgstr "++ Wiki Settings\n" "\n" "Это ваши глобальные установки по умолчанию.\n" "\n" @@ -1846,13 +1810,11 @@ msgstr "" "\n" "[icon: cog]\n" -#: lib\pwiki\AdditionalDialogs.py:144 -#: lib\pwiki\AdditionalDialogs.py:360 +#: lib\pwiki\AdditionalDialogs.py:144 lib\pwiki\AdditionalDialogs.py:360 msgid "Links to:" msgstr "Ссылки на:" -#: lib\pwiki\AdditionalDialogs.py:290 -#: lib\pwiki\AdditionalDialogs.py:402 +#: lib\pwiki\AdditionalDialogs.py:290 lib\pwiki\AdditionalDialogs.py:402 msgid "'%s' is an invalid WikiWord" msgstr "'%s' - неверное вики-слово" @@ -1868,17 +1830,14 @@ msgstr "Создать" msgid "'%s' exists already" msgstr "'%s' уже существует" -#: lib\pwiki\AdditionalDialogs.py:422 -#: lib\pwiki\AdditionalDialogs.py:515 +#: lib\pwiki\AdditionalDialogs.py:422 lib\pwiki\AdditionalDialogs.py:515 msgid "Do you want to delete %i wiki page(s)?" msgstr "Выбрано страниц для удаления: %i. Удалить?" #: lib\pwiki\AdditionalDialogs.py:674 -msgid "" -"Can't process renaming:\n" +msgid "Can't process renaming:\n" "%s" -msgstr "" -"Невозможно переименовать:\n" +msgstr "Невозможно переименовать:\n" "%s" #: lib\pwiki\AdditionalDialogs.py:675 @@ -1889,8 +1848,7 @@ msgstr "Невозможно переименовать" msgid "Can't rename to itself" msgstr "Невозможно переименовать в себя" -#: lib\pwiki\AdditionalDialogs.py:716 -#: lib\pwiki\WikiExceptions.py:73 +#: lib\pwiki\AdditionalDialogs.py:716 lib\pwiki\WikiExceptions.py:73 msgid "Word already exists" msgstr "Слово уже существует" @@ -1903,8 +1861,7 @@ msgid "Icon" msgstr "Иконка" #: lib\pwiki\AdditionalDialogs.py:840 -msgid "" -"\n" +msgid "\n" "\n" "\n" "\n" @@ -1960,8 +1917,7 @@ msgid "" " \n" " \n" " \n" -" \n" +" \n" " \n" " \n" " \n" @@ -1969,8 +1925,7 @@ msgid "" "
Time zone name (no characters if no time zone exists).
%%A literal \"%\" character.
\\n" -"
\\nA newline.
\\\\A literal \"\\\" character.
\n" "\n" "\n" -msgstr "" -"\n" +msgstr "\n" "\n" "\n" "\n" @@ -2026,8 +1981,7 @@ msgstr "" " \n" " \n" " \n" -" \n" +" \n" " \n" " \n" " \n" @@ -2060,13 +2014,11 @@ msgstr "Цель должна быть папкой" msgid "Destination must be a file" msgstr "Цель должна быть файлом" -#: lib\pwiki\AdditionalDialogs.py:1241 -#: lib\pwiki\PersonalWikiFrame.py:4683 +#: lib\pwiki\AdditionalDialogs.py:1241 lib\pwiki\PersonalWikiFrame.py:4725 msgid "Exporting" msgstr "Экспорт" -#: lib\pwiki\AdditionalDialogs.py:1243 -#: lib\pwiki\PersonalWikiFrame.py:4685 +#: lib\pwiki\AdditionalDialogs.py:1243 lib\pwiki\PersonalWikiFrame.py:4727 msgid "Preparing" msgstr "Подготовка" @@ -2074,13 +2026,11 @@ msgstr "Подготовка" msgid "Error while exporting" msgstr "Ошибка при экспорте" -#: lib\pwiki\AdditionalDialogs.py:1279 -#: lib\pwiki\PersonalWikiFrame.py:4626 +#: lib\pwiki\AdditionalDialogs.py:1279 lib\pwiki\PersonalWikiFrame.py:4668 msgid "Select Export Directory" msgstr "Выберите папку для экспорта" -#: lib\pwiki\AdditionalDialogs.py:1293 -#: lib\pwiki\AdditionalDialogs.py:1732 +#: lib\pwiki\AdditionalDialogs.py:1293 lib\pwiki\AdditionalDialogs.py:1732 msgid "All files (*.*)" msgstr "Все файлы (*.*)" @@ -2088,10 +2038,8 @@ msgstr "Все файлы (*.*)" msgid "Select Export File" msgstr "Выберите файл для экспорта" -#: lib\pwiki\AdditionalDialogs.py:1325 -#: lib\pwiki\PersonalWikiFrame.py:4650 -#: lib\pwiki\PersonalWikiFrame.py:4666 -#: lib\pwiki\Printing.py:183 +#: lib\pwiki\AdditionalDialogs.py:1325 lib\pwiki\PersonalWikiFrame.py:4692 +#: lib\pwiki\PersonalWikiFrame.py:4708 lib\pwiki\Printing.py:183 msgid "No real wiki word selected as root" msgstr "Нет реального вики-слова, выбранного как корневое" @@ -2119,31 +2067,25 @@ msgstr "Удалить Экспорт" msgid "Selected export type does not support saving" msgstr "Выбранный тип экспорта не поддерживает сохранение" -#: lib\pwiki\AdditionalDialogs.py:1506 -#: lib\pwiki\CmdLineAction.py:213 +#: lib\pwiki\AdditionalDialogs.py:1506 lib\pwiki\CmdLineAction.py:213 msgid "Export type '%s' of saved export is not supported" msgstr "Тип экспорта '%s' для сохранённого Экспорта не поддерживается" -#: lib\pwiki\AdditionalDialogs.py:1517 -#: lib\pwiki\CmdLineAction.py:224 -msgid "" -"Saved export uses different version for additional options than current export\n" +#: lib\pwiki\AdditionalDialogs.py:1517 lib\pwiki\CmdLineAction.py:224 +msgid "Saved export uses different version for additional options than current export\n" "Export type: '%s'\n" "Saved export version: %i\n" "Current export version: %i" -msgstr "" -"Сохранённый Экспорт использует версию дополнительных опций, отличную от текущего Экспорта\n" +msgstr "Сохранённый Экспорт использует версию дополнительных опций, отличную от текущего Экспорта\n" "Тип экспорта: '%s'\n" "Версия сохранённого экспорта: %i\n" "Версия текущего экспорта: %i" -#: lib\pwiki\AdditionalDialogs.py:1525 -#: lib\pwiki\CmdLineAction.py:233 +#: lib\pwiki\AdditionalDialogs.py:1525 lib\pwiki\CmdLineAction.py:233 msgid "Type of additional option storage ('%s') is unknown" msgstr "" -#: lib\pwiki\AdditionalDialogs.py:1549 -#: lib\pwiki\CmdLineAction.py:255 +#: lib\pwiki\AdditionalDialogs.py:1549 lib\pwiki\CmdLineAction.py:255 msgid "Error during retrieving saved export: " msgstr "Ошибка при чтении сохранённого экспорта:" @@ -2180,8 +2122,7 @@ msgid "Select Import File" msgstr "Выберите файл импорта" #: lib\pwiki\AdditionalDialogs.py:1853 -msgid "" -"\n" +msgid "\n" "\n" "\n" "
\n" @@ -2229,8 +2170,7 @@ msgid "" " \n" "\n" "\n" -msgstr "" -"\n" +msgstr "\n" "\n" "\n" "
\n" @@ -2282,8 +2222,7 @@ msgstr "" "\n" "\n" -#: lib\pwiki\AdditionalDialogs.py:1904 -#: lib\pwiki\PersonalWikiFrame.py:1949 +#: lib\pwiki\AdditionalDialogs.py:1904 lib\pwiki\PersonalWikiFrame.py:1967 msgid "About WikidPad" msgstr "О программе WikidPad" @@ -2327,13 +2266,11 @@ msgstr "Доступ на запись к базе данных утерян. П msgid "Wiki was set read-only in options dialog" msgstr "В диалоге настройки вики установлено \"Только для чтения\"" -#: lib\pwiki\AdditionalDialogs.py:2063 -#: lib\pwiki\AdditionalDialogs.py:2065 +#: lib\pwiki\AdditionalDialogs.py:2063 lib\pwiki\AdditionalDialogs.py:2065 msgid "Can't write wiki config.:" msgstr "Сбой записи конфигурации вики:" -#: lib\pwiki\AdditionalDialogs.py:2063 -#: lib\pwiki\AdditionalDialogs.py:2067 +#: lib\pwiki\AdditionalDialogs.py:2063 lib\pwiki\AdditionalDialogs.py:2067 msgid "Unknown reason" msgstr "Неизвестная причина" @@ -2674,11 +2611,9 @@ msgid "Value for --export-what can be 'page', 'subtree' or 'wiki'." msgstr "Значением --export-what могут быть 'page', 'subtree' или 'wiki'." #: lib\pwiki\CmdLineAction.py:339 -msgid "" -"Value for --export-type can be one of:\n" +msgid "Value for --export-type can be one of:\n" "%s" -msgstr "" -"Значением --export-type может быть одно из:\n" +msgstr "Значением --export-type может быть одно из:\n" "%s" #: lib\pwiki\CmdLineAction.py:360 @@ -2686,8 +2621,7 @@ msgid "Combination of --exit and --continuous-export-saved isn't allowed" msgstr "Комбинация --exit и --continuous-export-saved недопустима" #: lib\pwiki\CmdLineAction.py:368 -msgid "" -"Options:\n" +msgid "Options:\n" "\n" " -h, --help: Show this message box\n" " -w, --wiki : set the wiki to open on startup\n" @@ -2706,8 +2640,7 @@ msgid "" " option are opened in preview mode.\n" " --editor: Same as --preview but opens in text editor mode.\n" "\n" -msgstr "" -"Опции:\n" +msgstr "Опции:\n" "\n" " -h, --help: Показать эту справку\n" " -w, --wiki : установить вики, которая открывается при старте\n" @@ -2731,10 +2664,8 @@ msgstr "" msgid "Usage information" msgstr "Информация об использовании" -#: lib\pwiki\Configuration.py:149 -#: lib\pwiki\Configuration.py:196 -#: lib\pwiki\Configuration.py:314 -#: lib\pwiki\Configuration.py:320 +#: lib\pwiki\Configuration.py:149 lib\pwiki\Configuration.py:196 +#: lib\pwiki\Configuration.py:314 lib\pwiki\Configuration.py:320 #: lib\pwiki\Configuration.py:365 msgid "Unknown option %s:%s" msgstr "Неизвестная опция %s:%s" @@ -2759,40 +2690,39 @@ msgstr "'%s' не является допустимым вики-словом. % msgid "Wiki page not found, a new page will be created" msgstr "Вики-страница не найдена, будет создана новая страница" -#: lib\pwiki\DocPagePresenter.py:457 -#: lib\pwiki\DocPagePresenter.py:458 +#: lib\pwiki\DocPagePresenter.py:457 lib\pwiki\DocPagePresenter.py:458 msgid "History" msgstr "История" -#: lib\pwiki\DocPages.py:2210 +#: lib\pwiki\DocPages.py:2226 msgid "Func. tag %s does not exist" msgstr "Функц. метка %s не существует" -#: lib\pwiki\DocPages.py:2471 +#: lib\pwiki\DocPages.py:2487 msgid "Global text blocks" msgstr "Глобальные текстовые блоки" -#: lib\pwiki\DocPages.py:2472 +#: lib\pwiki\DocPages.py:2488 msgid "Wiki text blocks" msgstr "Текстовые блоки этой вики" -#: lib\pwiki\DocPages.py:2473 +#: lib\pwiki\DocPages.py:2489 msgid "Global spell list" msgstr "Глобальный список правописания" -#: lib\pwiki\DocPages.py:2474 +#: lib\pwiki\DocPages.py:2490 msgid "Wiki spell list" msgstr "Список правописания этой вики" -#: lib\pwiki\DocPages.py:2475 +#: lib\pwiki\DocPages.py:2491 msgid "Global cc. blacklist" msgstr "Глобальный чёрный список" -#: lib\pwiki\DocPages.py:2476 +#: lib\pwiki\DocPages.py:2492 msgid "Wiki cc. blacklist" msgstr "Чёрный список этой вики" -#: lib\pwiki\DocPages.py:2477 +#: lib\pwiki\DocPages.py:2493 msgid "Favorite wikis" msgstr "Избранные вики" @@ -2800,18 +2730,15 @@ msgstr "Избранные вики" msgid "Set of *.wiki files" msgstr "Множество файлов *.wiki" -#: lib\pwiki\Exporters.py:561 -#: lib\pwiki\Importers.py:61 +#: lib\pwiki\Exporters.py:561 lib\pwiki\Importers.py:61 msgid "Multipage text" msgstr "Многостраничный текст" -#: lib\pwiki\Exporters.py:595 -#: lib\pwiki\Importers.py:74 +#: lib\pwiki\Exporters.py:595 lib\pwiki\Importers.py:74 msgid "Multipage files (*.mpt)" msgstr "Многостраничные файлы (*.mpt)" -#: lib\pwiki\Exporters.py:596 -#: lib\pwiki\Importers.py:75 +#: lib\pwiki\Exporters.py:596 lib\pwiki\Importers.py:75 msgid "Text file (*.txt)" msgstr "Текстовый файл (*.txt)" @@ -2823,13 +2750,25 @@ msgstr "Не найдено подходящего разделителя" msgid "Scan links in %s" msgstr "Сканирование ссылок в %s" -#: lib\pwiki\FileCleanup.py:554 -#: lib\pwiki\FileCleanup.py:710 +#: lib\pwiki\FileCleanup.py:530 +msgid "" +msgstr "Project-Id-Version: WIKIDPAD INTERNATIONALISATION. RUSSIAN. VERSION 1.0\n" +"POT-Creation-Date: 2010-11-14 20:59\n" +"PO-Revision-Date: 2011-05-29 17:36+0600\n" +"Last-Translator: Oleg Domanov \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5mod\n" +"X-Poedit-Language: Russian\n" +"X-Poedit-Country: RUSSIAN FEDERATION\n" + +#: lib\pwiki\FileCleanup.py:554 lib\pwiki\FileCleanup.py:710 msgid "Path" msgstr "Путь" -#: lib\pwiki\FileCleanup.py:555 -#: lib\pwiki\MptImporterGui.py:193 +#: lib\pwiki\FileCleanup.py:555 lib\pwiki\MptImporterGui.py:195 msgid "Type" msgstr "Тип" @@ -2839,8 +2778,7 @@ msgstr "Действие" #: lib\pwiki\FileCleanup.py:557 msgid "Calc. action" -msgstr "" -"Предпол.\n" +msgstr "Предпол.\n" "действие" #: lib\pwiki\FileCleanup.py:617 @@ -2851,8 +2789,7 @@ msgstr "Директория" msgid "File" msgstr "Файл" -#: lib\pwiki\GtkHacks.py:289 -#: lib\pwiki\WindowsHacks.py:891 +#: lib\pwiki\GtkHacks.py:289 lib\pwiki\WindowsHacks.py:891 msgid "Only a real wiki page can be a clipboard catcher" msgstr "Для захвата буфера может использоваться только реальная вики-страница" @@ -2860,8 +2797,7 @@ msgstr "Для захвата буфера может использоватьс msgid "Opening import file failed" msgstr "Ошибка открытия файла импорта" -#: lib\pwiki\Importers.py:202 -#: lib\pwiki\Importers.py:217 +#: lib\pwiki\Importers.py:202 lib\pwiki\Importers.py:217 msgid "Bad file format, header not detected" msgstr "Неверный формат файла, заголовок не найден" @@ -2882,247 +2818,221 @@ msgid "Error initializing environment, couldn't locate global config directory" msgstr "Ошибка инициализации, не найдена глобальная папка конфигурации" #: lib\pwiki\MainApp.py:274 -msgid "" -"Invalid AppLock.lock file.\n" +msgid "Invalid AppLock.lock file.\n" "Ensure that WikidPad is not running,\n" "then delete file \"%s\" if present yet.\n" -msgstr "" -"Неверный файл AppLock.lock.\n" +msgstr "Неверный файл AppLock.lock.\n" "Убедитесь, что WikidPad не запущен,\n" "и удалите файл \"%s\", если он ещё имеется.\n" #: lib\pwiki\MainApp.py:332 -msgid "" -"Other WikidPad process(es) seem(s) to run already\n" +msgid "Other WikidPad process(es) seem(s) to run already\n" "Process identifier(s): %s\n" "Continue?" -msgstr "" -"Кажется, другой(ие) процесс(ы) WikidPad уже запущены\n" +msgstr "Кажется, другой(ие) процесс(ы) WikidPad уже запущены\n" "Идентификатор(ы) процесса(ов): %s\n" "Продолжать?" -#: lib\pwiki\MainApp.py:334 -#: lib\pwiki\MainApp.py:357 -#: lib\pwiki\MainApp.py:367 -#: lib\pwiki\PersonalWikiFrame.py:3080 +#: lib\pwiki\MainApp.py:334 lib\pwiki\MainApp.py:357 lib\pwiki\MainApp.py:367 +#: lib\pwiki\PersonalWikiFrame.py:3099 msgid "Continue?" msgstr "Продолжить?" #: lib\pwiki\MainApp.py:355 -msgid "" -"WikidPad couldn't detect if other processes are already running.\n" +msgid "WikidPad couldn't detect if other processes are already running.\n" "Continue anyway?" -msgstr "" -"WikidPad не смого определить, запущены ли другие процессы.\n" +msgstr "WikidPad не смого определить, запущены ли другие процессы.\n" "Продолжать всё равно?" #: lib\pwiki\MainApp.py:365 -msgid "" -"WikidPad couldn't detect if other processes are already running.\n" +msgid "WikidPad couldn't detect if other processes are already running.\n" "Socket error: %s\n" "Continue anyway?" -msgstr "" -"WikidPad не смого определить, запущены ли другие процессы.\n" +msgstr "WikidPad не смого определить, запущены ли другие процессы.\n" "Ошибка сокета: %s\n" "Продолжать всё равно?" #: lib\pwiki\MainApp.py:631 -msgid "" -"An error occurred during this session\n" +msgid "An error occurred during this session\n" "See file %s" -msgstr "" -"В этой сессии произошла ошибка.\n" +msgstr "В этой сессии произошла ошибка.\n" "См. файл %s" -#: lib\pwiki\MainApp.py:786 +#: lib\pwiki\MainApp.py:796 msgid "Plugin options" msgstr "Настройки плагинов" -#: lib\pwiki\MainApp.py:835 -#: lib\pwiki\OptionsDialog.py:681 +#: lib\pwiki\MainApp.py:845 lib\pwiki\OptionsDialog.py:689 msgid "Wiki language" msgstr "Синтаксис вики" -#: lib\pwiki\MainApp.py:858 +#: lib\pwiki\MainApp.py:868 msgid "Plugins" msgstr "Плагины" -#: lib\pwiki\MainAreaPanel.py:665 -#: lib\pwiki\WikiTxtCtrl.py:2212 +#: lib\pwiki\MainAreaPanel.py:665 lib\pwiki\WikiTxtCtrl.py:2341 msgid "This can only be done for the page of a wiki word" msgstr "Это можно сделать только со страницей вики-слова" -#: lib\pwiki\MainAreaPanel.py:666 -#: lib\pwiki\WikiTxtCtrl.py:2213 +#: lib\pwiki\MainAreaPanel.py:666 lib\pwiki\WikiTxtCtrl.py:2342 msgid "Not a wiki page" msgstr "Не вики-страница" -#: lib\pwiki\MptImporterGui.py:174 +#: lib\pwiki\MptImporterGui.py:176 msgid "Wiki page" msgstr "Вики-страница" -#: lib\pwiki\MptImporterGui.py:175 +#: lib\pwiki\MptImporterGui.py:177 msgid "Func. page" msgstr "Функ. страница" -#: lib\pwiki\MptImporterGui.py:176 +#: lib\pwiki\MptImporterGui.py:178 msgid "Saved search" msgstr "Сохранённый Поиск" -#: lib\pwiki\MptImporterGui.py:194 +#: lib\pwiki\MptImporterGui.py:196 msgid "Name" msgstr "Название" -#: lib\pwiki\MptImporterGui.py:196 -msgid "" -"Version\n" +#: lib\pwiki\MptImporterGui.py:198 +msgid "Version\n" "Import" -msgstr "" -"Импорт\n" +msgstr "Импорт\n" "Версий" -#: lib\pwiki\MptImporterGui.py:197 -msgid "" -"Rename\n" +#: lib\pwiki\MptImporterGui.py:199 +msgid "Rename\n" "imported" -msgstr "" -"Переимен.\n" +msgstr "Переимен.\n" "новое" -#: lib\pwiki\MptImporterGui.py:198 -msgid "" -"Rename\n" +#: lib\pwiki\MptImporterGui.py:200 +msgid "Rename\n" "present" -msgstr "" -"Переимен.\n" +msgstr "Переимен.\n" "старое" -#: lib\pwiki\MptImporterGui.py:383 +#: lib\pwiki\MptImporterGui.py:385 msgid "You can't rename imported and present item at the same time" msgstr "Вы не можете переименовать импортируемую и имеющуюся одновременно" -#: lib\pwiki\MptImporterGui.py:396 +#: lib\pwiki\MptImporterGui.py:398 msgid "Rename imported" msgstr "Переименовать импортируемую" -#: lib\pwiki\MptImporterGui.py:405 +#: lib\pwiki\MptImporterGui.py:407 msgid "Rename present" msgstr "Переименовать имеющуюся" -#: lib\pwiki\MptImporterGui.py:416 -#: lib\pwiki\MptImporterGui.py:440 +#: lib\pwiki\MptImporterGui.py:418 lib\pwiki\MptImporterGui.py:442 msgid "Name collision: Item '%s' will be imported already" msgstr "Конфликт имён: '%s' уже будет импортирована" -#: lib\pwiki\MptImporterGui.py:422 -#: lib\pwiki\MptImporterGui.py:451 +#: lib\pwiki\MptImporterGui.py:424 lib\pwiki\MptImporterGui.py:453 msgid "Name collision: Item '%s' will already be created by renaming '%s'" msgstr "Конфликт имён: '%s' уже будет создана путём переименования '%s'" -#: lib\pwiki\MptImporterGui.py:431 -#: lib\pwiki\MptImporterGui.py:445 +#: lib\pwiki\MptImporterGui.py:433 lib\pwiki\MptImporterGui.py:447 msgid "Name collision: Item '%s' exists already in database" msgstr "Конфликт имён: '%s' уже существует в базе данных" -#: lib\pwiki\OptionsDialog.py:661 +#: lib\pwiki\OptionsDialog.py:669 msgid "Application" msgstr "Общие" -#: lib\pwiki\OptionsDialog.py:662 +#: lib\pwiki\OptionsDialog.py:670 msgid "User interface" msgstr "Интерфейс пользователя" -#: lib\pwiki\OptionsDialog.py:663 +#: lib\pwiki\OptionsDialog.py:671 msgid "Security" msgstr "Безопасность" -#: lib\pwiki\OptionsDialog.py:664 +#: lib\pwiki\OptionsDialog.py:672 msgid "Tree" msgstr "Дерево" -#: lib\pwiki\OptionsDialog.py:665 +#: lib\pwiki\OptionsDialog.py:673 msgid "HTML preview/export" msgstr "Просмотр/экспорт HTML" -#: lib\pwiki\OptionsDialog.py:666 +#: lib\pwiki\OptionsDialog.py:674 msgid "HTML header" msgstr "Заголовок HTML" -#: lib\pwiki\OptionsDialog.py:667 +#: lib\pwiki\OptionsDialog.py:675 msgid "Editor" msgstr "Редактор" -#: lib\pwiki\OptionsDialog.py:668 +#: lib\pwiki\OptionsDialog.py:676 msgid "Editor Colors" msgstr "Цвета редактора" -#: lib\pwiki\OptionsDialog.py:669 +#: lib\pwiki\OptionsDialog.py:677 msgid "Clipboard Catcher" msgstr "Захват буфера" -#: lib\pwiki\OptionsDialog.py:670 +#: lib\pwiki\OptionsDialog.py:678 msgid "File Launcher" msgstr "Программа запуска файлов" -#: lib\pwiki\OptionsDialog.py:671 +#: lib\pwiki\OptionsDialog.py:679 msgid "Mouse" msgstr "Мышь" -#: lib\pwiki\OptionsDialog.py:672 +#: lib\pwiki\OptionsDialog.py:680 msgid "Chron. view" msgstr "Хронология" -#: lib\pwiki\OptionsDialog.py:673 +#: lib\pwiki\OptionsDialog.py:681 msgid "Searching" msgstr "Поиск" -#: lib\pwiki\OptionsDialog.py:674 -#: lib\pwiki\OptionsDialog.py:683 +#: lib\pwiki\OptionsDialog.py:682 lib\pwiki\OptionsDialog.py:691 msgid "Advanced" msgstr "Дополнительно" -#: lib\pwiki\OptionsDialog.py:675 +#: lib\pwiki\OptionsDialog.py:683 msgid "Timing" msgstr "Задержки" -#: lib\pwiki\OptionsDialog.py:676 +#: lib\pwiki\OptionsDialog.py:684 msgid "Autosave" msgstr "Автосохранение" -#: lib\pwiki\OptionsDialog.py:678 +#: lib\pwiki\OptionsDialog.py:686 msgid "Current Wiki" msgstr "Текущая вики" -#: lib\pwiki\OptionsDialog.py:679 +#: lib\pwiki\OptionsDialog.py:687 msgid "Headings" msgstr "Заголовки" -#: lib\pwiki\OptionsDialog.py:823 +#: lib\pwiki\OptionsDialog.py:831 msgid "IE" msgstr "IE" -#: lib\pwiki\OptionsDialog.py:825 +#: lib\pwiki\OptionsDialog.py:833 msgid "Mozilla" msgstr "Mozilla" -#: lib\pwiki\OptionsDialog.py:829 +#: lib\pwiki\OptionsDialog.py:837 msgid "Webkit" -msgstr "" +msgstr "Webkit" -#: lib\pwiki\OptionsDialog.py:963 +#: lib\pwiki\OptionsDialog.py:971 msgid "Wave files (*.wav)|*.wav" msgstr "Звуковые файлы (*.wav)|*.wav" -#: lib\pwiki\OptionsDialog.py:974 -#: lib\pwiki\OptionsDialog.py:1285 +#: lib\pwiki\OptionsDialog.py:982 lib\pwiki\OptionsDialog.py:1303 msgid "All files (*.*)|*" msgstr "Все файлы (*.*)|*" -#: lib\pwiki\OptionsDialog.py:1275 +#: lib\pwiki\OptionsDialog.py:1293 msgid "Select Directory" msgstr "Выберите папку" -#: lib\pwiki\OptionsDialog.py:1283 +#: lib\pwiki\OptionsDialog.py:1301 msgid "Select File" msgstr "Выберите файл" @@ -3130,8 +3040,7 @@ msgstr "Выберите файл" msgid "Choose a file to create URL for" msgstr "Выберите файл, чтобы создать URL для" -#: lib\pwiki\PWikiNonCore.py:70 -#: lib\pwiki\PWikiNonCore.py:71 +#: lib\pwiki\PWikiNonCore.py:70 lib\pwiki\PWikiNonCore.py:71 msgid " Scanning " msgstr " Сканирование " @@ -3163,1358 +3072,1310 @@ msgstr "Удаление потерянных файлов и пустых сс msgid "Bad formatted command line." msgstr "Неверная командная строка" -#: lib\pwiki\PersonalWikiFrame.py:381 -#: lib\pwiki\PersonalWikiFrame.py:389 -#: lib\pwiki\PersonalWikiFrame.py:1190 +#: lib\pwiki\PersonalWikiFrame.py:399 lib\pwiki\PersonalWikiFrame.py:407 +#: lib\pwiki\PersonalWikiFrame.py:1208 msgid "Wiki doesn't exist: %s" msgstr "Вики не существует: '%s' " -#: lib\pwiki\PersonalWikiFrame.py:751 +#: lib\pwiki\PersonalWikiFrame.py:769 msgid "&New" msgstr "&Новая" -#: lib\pwiki\PersonalWikiFrame.py:752 +#: lib\pwiki\PersonalWikiFrame.py:770 msgid "Create new wiki" msgstr "Создать новую вики" -#: lib\pwiki\PersonalWikiFrame.py:755 +#: lib\pwiki\PersonalWikiFrame.py:773 msgid "&Open" msgstr "&Открыть" -#: lib\pwiki\PersonalWikiFrame.py:757 +#: lib\pwiki\PersonalWikiFrame.py:775 msgid "In &This Window..." msgstr "В &этом окне..." -#: lib\pwiki\PersonalWikiFrame.py:759 +#: lib\pwiki\PersonalWikiFrame.py:777 msgid "Open wiki in this window" msgstr "Открыть вики в этом окне" -#: lib\pwiki\PersonalWikiFrame.py:761 +#: lib\pwiki\PersonalWikiFrame.py:779 msgid "In &New Window..." msgstr "В &новом окне..." -#: lib\pwiki\PersonalWikiFrame.py:763 +#: lib\pwiki\PersonalWikiFrame.py:781 msgid "Open wiki in a new window" msgstr "Открыть вики в новом окне" -#: lib\pwiki\PersonalWikiFrame.py:765 +#: lib\pwiki\PersonalWikiFrame.py:783 msgid "&Current in New Window" msgstr "&Текущую в новом окне" -#: lib\pwiki\PersonalWikiFrame.py:767 +#: lib\pwiki\PersonalWikiFrame.py:785 msgid "Create new window for same wiki" msgstr "Создать новое окно с этим же вики" -#: lib\pwiki\PersonalWikiFrame.py:772 +#: lib\pwiki\PersonalWikiFrame.py:790 msgid "&Recent" msgstr "&Недавние" -#: lib\pwiki\PersonalWikiFrame.py:779 +#: lib\pwiki\PersonalWikiFrame.py:797 msgid "F&avorites" msgstr "&Избранное" -#: lib\pwiki\PersonalWikiFrame.py:785 +#: lib\pwiki\PersonalWikiFrame.py:803 msgid "&Search Wiki..." msgstr "&Найти в вики..." -#: lib\pwiki\PersonalWikiFrame.py:786 +#: lib\pwiki\PersonalWikiFrame.py:804 msgid "Search whole wiki" msgstr "Поиск по всей вики" -#: lib\pwiki\PersonalWikiFrame.py:794 +#: lib\pwiki\PersonalWikiFrame.py:812 msgid "Publish as HTML" msgstr "Опубликовать как HTML" -#: lib\pwiki\PersonalWikiFrame.py:797 +#: lib\pwiki\PersonalWikiFrame.py:815 msgid "Wiki as Single HTML Page" msgstr "Вики как одна страница HTML" -#: lib\pwiki\PersonalWikiFrame.py:798 +#: lib\pwiki\PersonalWikiFrame.py:816 msgid "Publish Wiki as Single HTML Page" msgstr "Опубликовать вики как одну страницу HTML" -#: lib\pwiki\PersonalWikiFrame.py:802 +#: lib\pwiki\PersonalWikiFrame.py:820 msgid "Wiki as Set of HTML Pages" msgstr "Вики как набор страниц HTML" -#: lib\pwiki\PersonalWikiFrame.py:803 +#: lib\pwiki\PersonalWikiFrame.py:821 msgid "Publish Wiki as Set of HTML Pages" msgstr "Опубликовать вики как набор страниц HTML" -#: lib\pwiki\PersonalWikiFrame.py:807 +#: lib\pwiki\PersonalWikiFrame.py:825 msgid "Current Wiki Word as HTML Page" msgstr "Текущее вики-слово как страница HTML" -#: lib\pwiki\PersonalWikiFrame.py:808 +#: lib\pwiki\PersonalWikiFrame.py:826 msgid "Publish Current Wiki Word as HTML Page" msgstr "Опубликовать текущее вики-слово как страницу HTML" -#: lib\pwiki\PersonalWikiFrame.py:812 +#: lib\pwiki\PersonalWikiFrame.py:830 msgid "Sub-Tree as Single HTML Page" msgstr "Поддерево как одна страница HTML" -#: lib\pwiki\PersonalWikiFrame.py:813 +#: lib\pwiki\PersonalWikiFrame.py:831 msgid "Publish Sub-Tree as Single HTML Page" msgstr "Опубликовать поддерево как одну страницу HTML" -#: lib\pwiki\PersonalWikiFrame.py:817 +#: lib\pwiki\PersonalWikiFrame.py:835 msgid "Sub-Tree as Set of HTML Pages" msgstr "Поддерево как набор страниц HTML" -#: lib\pwiki\PersonalWikiFrame.py:818 +#: lib\pwiki\PersonalWikiFrame.py:836 msgid "Publish Sub-Tree as Set of HTML Pages" msgstr "Опубликовать поддерево как набор страниц HTML" -#: lib\pwiki\PersonalWikiFrame.py:826 +#: lib\pwiki\PersonalWikiFrame.py:844 msgid "Other Export..." msgstr "Другой экспорт..." -#: lib\pwiki\PersonalWikiFrame.py:827 -#: lib\pwiki\PersonalWikiFrame.py:1856 +#: lib\pwiki\PersonalWikiFrame.py:845 lib\pwiki\PersonalWikiFrame.py:1874 msgid "Open general export dialog" msgstr "Открыть общий диалог экспорта" -#: lib\pwiki\PersonalWikiFrame.py:831 +#: lib\pwiki\PersonalWikiFrame.py:849 msgid "Print..." msgstr "Печать..." -#: lib\pwiki\PersonalWikiFrame.py:832 +#: lib\pwiki\PersonalWikiFrame.py:850 msgid "Show the print dialog" msgstr "Открыть диалог печати" -#: lib\pwiki\PersonalWikiFrame.py:836 +#: lib\pwiki\PersonalWikiFrame.py:854 msgid "&Properties..." msgstr "&Свойства..." -#: lib\pwiki\PersonalWikiFrame.py:837 +#: lib\pwiki\PersonalWikiFrame.py:855 msgid "Show general information about current wiki" msgstr "Показать общую информацию о текущей вики" -#: lib\pwiki\PersonalWikiFrame.py:841 +#: lib\pwiki\PersonalWikiFrame.py:859 msgid "Maintenance" msgstr "Обслуживание" -#: lib\pwiki\PersonalWikiFrame.py:845 +#: lib\pwiki\PersonalWikiFrame.py:863 msgid "&Rebuild Wiki..." msgstr "&Перестроить вики..." -#: lib\pwiki\PersonalWikiFrame.py:846 +#: lib\pwiki\PersonalWikiFrame.py:864 msgid "Rebuild this wiki and its cache completely" msgstr "Полностью перестроить эту вики и её кэш" -#: lib\pwiki\PersonalWikiFrame.py:860 +#: lib\pwiki\PersonalWikiFrame.py:878 msgid "&Update cache..." msgstr "&Обновить кэш..." -#: lib\pwiki\PersonalWikiFrame.py:861 +#: lib\pwiki\PersonalWikiFrame.py:879 msgid "Update cache where marked as not up to date" msgstr "Обновить в кэше устаревшие сведения" -#: lib\pwiki\PersonalWikiFrame.py:866 +#: lib\pwiki\PersonalWikiFrame.py:884 msgid "&Initiate update..." msgstr "&Запустить обновление..." -#: lib\pwiki\PersonalWikiFrame.py:867 +#: lib\pwiki\PersonalWikiFrame.py:885 msgid "Initiate full cache update which is done mainly in background" msgstr "Запустить полное обновление кэша (в основном, происходит в фоновом режиме)" -#: lib\pwiki\PersonalWikiFrame.py:887 +#: lib\pwiki\PersonalWikiFrame.py:905 msgid "Show job count..." msgstr "Показать число заданий..." -#: lib\pwiki\PersonalWikiFrame.py:888 +#: lib\pwiki\PersonalWikiFrame.py:906 msgid "Show how many update jobs are waiting in background" msgstr "Показать число заданий обновления, ожидающих в фоновом режиме" -#: lib\pwiki\PersonalWikiFrame.py:893 +#: lib\pwiki\PersonalWikiFrame.py:911 msgid "Open as &Type..." msgstr "Открыть &как..." -#: lib\pwiki\PersonalWikiFrame.py:894 +#: lib\pwiki\PersonalWikiFrame.py:912 msgid "Open wiki with a specified wiki database type" msgstr "Открыть вики с указанным типом базы данных" -#: lib\pwiki\PersonalWikiFrame.py:898 +#: lib\pwiki\PersonalWikiFrame.py:916 msgid "Reconnect..." msgstr "Подключить заново..." -#: lib\pwiki\PersonalWikiFrame.py:899 +#: lib\pwiki\PersonalWikiFrame.py:917 msgid "Reconnect to database after connection failure" msgstr "Заново подключить базу данных после ошибки" -#: lib\pwiki\PersonalWikiFrame.py:905 +#: lib\pwiki\PersonalWikiFrame.py:923 msgid "&Optimise Database" msgstr "&Оптимизировать базу данных" -#: lib\pwiki\PersonalWikiFrame.py:906 +#: lib\pwiki\PersonalWikiFrame.py:924 msgid "Free unused space in database" msgstr "Очистить неиспользуемое место в базе данных" -#: lib\pwiki\PersonalWikiFrame.py:913 +#: lib\pwiki\PersonalWikiFrame.py:931 msgid "&Copy .wiki files to database" msgstr "&Копировать файлы .wiki в базу данных" -#: lib\pwiki\PersonalWikiFrame.py:914 +#: lib\pwiki\PersonalWikiFrame.py:932 msgid "Copy .wiki files to database" msgstr "Копировать файлы .wiki в базу данных" -#: lib\pwiki\PersonalWikiFrame.py:924 +#: lib\pwiki\PersonalWikiFrame.py:942 msgid "E&xit" msgstr "&Выход" -#: lib\pwiki\PersonalWikiFrame.py:924 -#: lib\pwiki\PersonalWikiFrame.py:5663 +#: lib\pwiki\PersonalWikiFrame.py:942 lib\pwiki\PersonalWikiFrame.py:5705 msgid "Exit" msgstr "Выход" -#: lib\pwiki\PersonalWikiFrame.py:1092 +#: lib\pwiki\PersonalWikiFrame.py:1110 msgid "Reread text blocks" msgstr "Обновить текстовые блоки" -#: lib\pwiki\PersonalWikiFrame.py:1093 +#: lib\pwiki\PersonalWikiFrame.py:1111 msgid "Reread the text block file(s) and recreate menu" msgstr "Заново прочитать файл(ы) текстовых блоков и обновить меню" -#: lib\pwiki\PersonalWikiFrame.py:1147 +#: lib\pwiki\PersonalWikiFrame.py:1165 msgid "Add wiki" msgstr "Добавить вики" -#: lib\pwiki\PersonalWikiFrame.py:1148 +#: lib\pwiki\PersonalWikiFrame.py:1166 msgid "Add a wiki to the favorites" msgstr "Добавить вики в Избранное" -#: lib\pwiki\PersonalWikiFrame.py:1153 -#: lib\pwiki\PersonalWikiFrame.py:1154 +#: lib\pwiki\PersonalWikiFrame.py:1171 lib\pwiki\PersonalWikiFrame.py:1172 msgid "Manage favorites" msgstr "Упорядочить Избранное" -#: lib\pwiki\PersonalWikiFrame.py:1176 -#: lib\pwiki\PersonalWikiFrame.py:1906 -#: lib\pwiki\PersonalWikiFrame.py:2424 -#: lib\pwiki\PersonalWikiFrame.py:4903 -#: lib\pwiki\PersonalWikiFrame.py:5240 +#: lib\pwiki\PersonalWikiFrame.py:1194 lib\pwiki\PersonalWikiFrame.py:1924 +#: lib\pwiki\PersonalWikiFrame.py:2443 lib\pwiki\PersonalWikiFrame.py:3859 +#: lib\pwiki\PersonalWikiFrame.py:4945 lib\pwiki\PersonalWikiFrame.py:5282 msgid "Error while starting new WikidPad instance" msgstr "Ошибка при старте новой копии WikidPad" -#: lib\pwiki\PersonalWikiFrame.py:1283 +#: lib\pwiki\PersonalWikiFrame.py:1301 msgid "W&iki" msgstr "&Вики" -#: lib\pwiki\PersonalWikiFrame.py:1294 +#: lib\pwiki\PersonalWikiFrame.py:1312 msgid "&Undo" msgstr "&Отменить" -#: lib\pwiki\PersonalWikiFrame.py:1298 +#: lib\pwiki\PersonalWikiFrame.py:1316 msgid "&Redo" msgstr "&Вернуть" -#: lib\pwiki\PersonalWikiFrame.py:1306 +#: lib\pwiki\PersonalWikiFrame.py:1324 msgid "&Search and Replace..." msgstr "&Найти и заменить..." -#: lib\pwiki\PersonalWikiFrame.py:1308 +#: lib\pwiki\PersonalWikiFrame.py:1326 msgid "Search and replace inside current page" msgstr "Поиск и замена в пределах текущей страницы" -#: lib\pwiki\PersonalWikiFrame.py:1314 +#: lib\pwiki\PersonalWikiFrame.py:1332 msgid "Cu&t" msgstr "&Вырезать" -#: lib\pwiki\PersonalWikiFrame.py:1319 +#: lib\pwiki\PersonalWikiFrame.py:1337 msgid "&Copy" msgstr "&Копировать" -#: lib\pwiki\PersonalWikiFrame.py:1323 +#: lib\pwiki\PersonalWikiFrame.py:1341 msgid "&Paste" msgstr "В&ставить" -#: lib\pwiki\PersonalWikiFrame.py:1328 +#: lib\pwiki\PersonalWikiFrame.py:1346 msgid "Select &All" msgstr "Выбрать &всё" -#: lib\pwiki\PersonalWikiFrame.py:1334 +#: lib\pwiki\PersonalWikiFrame.py:1352 msgid "Copy to Sc&ratchPad" msgstr "Копировать в &Черновик (ScratchPad)" -#: lib\pwiki\PersonalWikiFrame.py:1336 +#: lib\pwiki\PersonalWikiFrame.py:1354 msgid "Copy selected text to ScratchPad" msgstr "Копировать выбранный текст в Черновик (ScratchPad)" -#: lib\pwiki\PersonalWikiFrame.py:1342 +#: lib\pwiki\PersonalWikiFrame.py:1360 msgid "Paste T&extblock" msgstr "Вставить &текстовый блок" -#: lib\pwiki\PersonalWikiFrame.py:1350 +#: lib\pwiki\PersonalWikiFrame.py:1368 msgid "C&lipboard Catcher" msgstr "Захват &буфера" -#: lib\pwiki\PersonalWikiFrame.py:1353 -#: lib\pwiki\PersonalWikiFrame.py:4214 +#: lib\pwiki\PersonalWikiFrame.py:1371 lib\pwiki\PersonalWikiFrame.py:4256 msgid "Set at Page" msgstr "Записывать в конец страницы" -#: lib\pwiki\PersonalWikiFrame.py:1355 +#: lib\pwiki\PersonalWikiFrame.py:1373 msgid "Text copied to clipboard is also appended to this page" msgstr "Текст, копируемый в буфер, будет также добавляться в конец этой страницы" -#: lib\pwiki\PersonalWikiFrame.py:1361 +#: lib\pwiki\PersonalWikiFrame.py:1379 msgid "Set at Cursor" msgstr "Записывать после позиции курсора" -#: lib\pwiki\PersonalWikiFrame.py:1363 +#: lib\pwiki\PersonalWikiFrame.py:1381 msgid "Text copied to clipboard is also added to cursor position" msgstr "Текст, копируемый в буфер, будет также добавляться после позиции курсора" -#: lib\pwiki\PersonalWikiFrame.py:1369 +#: lib\pwiki\PersonalWikiFrame.py:1387 msgid "Set Off" msgstr "Отключить" -#: lib\pwiki\PersonalWikiFrame.py:1371 +#: lib\pwiki\PersonalWikiFrame.py:1389 msgid "Switch off clipboard catcher" msgstr "Отключить захват буфера" -#: lib\pwiki\PersonalWikiFrame.py:1381 +#: lib\pwiki\PersonalWikiFrame.py:1399 msgid "Spell Check..." msgstr "Правописание..." -#: lib\pwiki\PersonalWikiFrame.py:1383 +#: lib\pwiki\PersonalWikiFrame.py:1401 msgid "Spell check current and possibly further pages" msgstr "Проверить правописание текущей и, возможно, следующих страниц" -#: lib\pwiki\PersonalWikiFrame.py:1387 +#: lib\pwiki\PersonalWikiFrame.py:1405 msgid "Spell Check While Type" msgstr "Правописание при наборе" -#: lib\pwiki\PersonalWikiFrame.py:1388 +#: lib\pwiki\PersonalWikiFrame.py:1406 msgid "Set if editor should do spell checking during typing" msgstr "Проверять правописание в ходе набора текста" -#: lib\pwiki\PersonalWikiFrame.py:1393 +#: lib\pwiki\PersonalWikiFrame.py:1411 msgid "Clear Ignore List" msgstr "Очистить список игнорирования" -#: lib\pwiki\PersonalWikiFrame.py:1395 +#: lib\pwiki\PersonalWikiFrame.py:1413 msgid "Clear the list of words to ignore for spell check while type" msgstr "Очистить список слов, игнорируемых проверкой правописания при наборе" -#: lib\pwiki\PersonalWikiFrame.py:1404 +#: lib\pwiki\PersonalWikiFrame.py:1422 msgid "&Insert" msgstr "&Вставить" -#: lib\pwiki\PersonalWikiFrame.py:1428 +#: lib\pwiki\PersonalWikiFrame.py:1446 msgid "&Settings" msgstr "&Настройки" -#: lib\pwiki\PersonalWikiFrame.py:1431 +#: lib\pwiki\PersonalWikiFrame.py:1449 msgid "&Date Format..." msgstr "Формат &даты..." -#: lib\pwiki\PersonalWikiFrame.py:1432 +#: lib\pwiki\PersonalWikiFrame.py:1450 msgid "Set date format for inserting current date" msgstr "Установить формат даты при вставке текущей даты" -#: lib\pwiki\PersonalWikiFrame.py:1436 +#: lib\pwiki\PersonalWikiFrame.py:1454 msgid "Auto-&Wrap" msgstr "Авто&перенос" -#: lib\pwiki\PersonalWikiFrame.py:1437 +#: lib\pwiki\PersonalWikiFrame.py:1455 msgid "Set if editor should wrap long lines" msgstr "Установите, чтобы редактор переносил длинные строки" -#: lib\pwiki\PersonalWikiFrame.py:1451 +#: lib\pwiki\PersonalWikiFrame.py:1469 msgid "Auto-&Indent" msgstr "Авто&отступ" -#: lib\pwiki\PersonalWikiFrame.py:1452 +#: lib\pwiki\PersonalWikiFrame.py:1470 msgid "Auto indentation" msgstr "Автоматический отступ" -#: lib\pwiki\PersonalWikiFrame.py:1458 +#: lib\pwiki\PersonalWikiFrame.py:1476 msgid "Auto-&Bullets" msgstr "Авто&маркёры списков" -#: lib\pwiki\PersonalWikiFrame.py:1459 +#: lib\pwiki\PersonalWikiFrame.py:1477 msgid "Show bullet on next line if current has one" msgstr "Создаётся маркёр на следующей строке, если он есть на текущей" -#: lib\pwiki\PersonalWikiFrame.py:1465 +#: lib\pwiki\PersonalWikiFrame.py:1483 msgid "Tabs to spaces" msgstr "Табуляторы в пробелы" -#: lib\pwiki\PersonalWikiFrame.py:1466 +#: lib\pwiki\PersonalWikiFrame.py:1484 msgid "Write spaces when hitting TAB key" msgstr "Записывать пробелы при нажатии на клавишу TAB" -#: lib\pwiki\PersonalWikiFrame.py:1474 +#: lib\pwiki\PersonalWikiFrame.py:1492 msgid "Show T&oolbar" msgstr "Панель &инструментов" -#: lib\pwiki\PersonalWikiFrame.py:1476 +#: lib\pwiki\PersonalWikiFrame.py:1494 msgid "Show toolbar" msgstr "Показать панель инструментов" -#: lib\pwiki\PersonalWikiFrame.py:1483 +#: lib\pwiki\PersonalWikiFrame.py:1501 msgid "Show &Tree View" msgstr "&Дерево" -#: lib\pwiki\PersonalWikiFrame.py:1485 +#: lib\pwiki\PersonalWikiFrame.py:1503 msgid "Show Tree Control" msgstr "Показать дерево" -#: lib\pwiki\PersonalWikiFrame.py:1492 +#: lib\pwiki\PersonalWikiFrame.py:1510 msgid "Show &Chron. View" msgstr "&Хронология" -#: lib\pwiki\PersonalWikiFrame.py:1494 +#: lib\pwiki\PersonalWikiFrame.py:1512 msgid "Show chronological view" msgstr "Показать хронологию" -#: lib\pwiki\PersonalWikiFrame.py:1501 +#: lib\pwiki\PersonalWikiFrame.py:1519 msgid "Show &Page Structure" msgstr "Структура &страницы" -#: lib\pwiki\PersonalWikiFrame.py:1503 +#: lib\pwiki\PersonalWikiFrame.py:1521 msgid "Show structure (headings) of the page" msgstr "Показать структуру страницы (заголовки)" -#: lib\pwiki\PersonalWikiFrame.py:1515 +#: lib\pwiki\PersonalWikiFrame.py:1533 msgid "Show &Indentation Guides" msgstr "Разметка &отступов" -#: lib\pwiki\PersonalWikiFrame.py:1516 +#: lib\pwiki\PersonalWikiFrame.py:1534 msgid "Show indentation guides in editor" msgstr "Показать разметку отступов в редакторе" -#: lib\pwiki\PersonalWikiFrame.py:1521 +#: lib\pwiki\PersonalWikiFrame.py:1539 msgid "Show Line &Numbers" msgstr "&Номера строк" -#: lib\pwiki\PersonalWikiFrame.py:1522 +#: lib\pwiki\PersonalWikiFrame.py:1540 msgid "Show line numbers in editor" msgstr "Показать номера строк в редакторе" -#: lib\pwiki\PersonalWikiFrame.py:1529 +#: lib\pwiki\PersonalWikiFrame.py:1547 msgid "Stay on Top" msgstr "Поверх всех окон" -#: lib\pwiki\PersonalWikiFrame.py:1531 +#: lib\pwiki\PersonalWikiFrame.py:1549 msgid "Stay on Top of all other windows" msgstr "Оставаться поверх всех других окон" -#: lib\pwiki\PersonalWikiFrame.py:1539 +#: lib\pwiki\PersonalWikiFrame.py:1557 msgid "&Zoom In" msgstr "У&величить" -#: lib\pwiki\PersonalWikiFrame.py:1540 -#: lib\pwiki\PersonalWikiFrame.py:2101 +#: lib\pwiki\PersonalWikiFrame.py:1558 lib\pwiki\PersonalWikiFrame.py:2119 msgid "Zoom In" msgstr "Увеличить" -#: lib\pwiki\PersonalWikiFrame.py:1543 +#: lib\pwiki\PersonalWikiFrame.py:1561 msgid "Zoo&m Out" msgstr "У&меньшить" -#: lib\pwiki\PersonalWikiFrame.py:1544 -#: lib\pwiki\PersonalWikiFrame.py:2106 +#: lib\pwiki\PersonalWikiFrame.py:1562 lib\pwiki\PersonalWikiFrame.py:2124 msgid "Zoom Out" msgstr "Уменьшить" -#: lib\pwiki\PersonalWikiFrame.py:1566 +#: lib\pwiki\PersonalWikiFrame.py:1584 msgid "Toggle Ed./Prev" msgstr "Переключить Редактор/Просмотр" -#: lib\pwiki\PersonalWikiFrame.py:1568 -#: lib\pwiki\PersonalWikiFrame.py:2097 +#: lib\pwiki\PersonalWikiFrame.py:1586 lib\pwiki\PersonalWikiFrame.py:2115 msgid "Switch between editor and preview" msgstr "Переключает между режимами редактора и просмотра" -#: lib\pwiki\PersonalWikiFrame.py:1572 +#: lib\pwiki\PersonalWikiFrame.py:1590 msgid "Enter Edit Mode" msgstr "Войти в режим редактора" -#: lib\pwiki\PersonalWikiFrame.py:1573 +#: lib\pwiki\PersonalWikiFrame.py:1591 msgid "Show editor in tab" msgstr "Показывать вкладку в режиме редактора" -#: lib\pwiki\PersonalWikiFrame.py:1577 +#: lib\pwiki\PersonalWikiFrame.py:1595 msgid "Enter Preview Mode" msgstr "Войти в режим просмотра" -#: lib\pwiki\PersonalWikiFrame.py:1579 +#: lib\pwiki\PersonalWikiFrame.py:1597 msgid "Show preview in tab" msgstr "Показывать вкладку в режиме просмотра" -#: lib\pwiki\PersonalWikiFrame.py:1603 +#: lib\pwiki\PersonalWikiFrame.py:1621 msgid "&Save" msgstr "&Сохранить" -#: lib\pwiki\PersonalWikiFrame.py:1604 +#: lib\pwiki\PersonalWikiFrame.py:1622 msgid "Save all open pages" msgstr "Сохранить все открытые страницы" -#: lib\pwiki\PersonalWikiFrame.py:1611 +#: lib\pwiki\PersonalWikiFrame.py:1629 msgid "&Rename" msgstr "&Переименовать" -#: lib\pwiki\PersonalWikiFrame.py:1612 +#: lib\pwiki\PersonalWikiFrame.py:1630 msgid "Rename current wiki word" msgstr "Переименовать текущее вики-слово" -#: lib\pwiki\PersonalWikiFrame.py:1618 +#: lib\pwiki\PersonalWikiFrame.py:1636 msgid "Delete current wiki word" msgstr "Удалить текущее вики-слово" -#: lib\pwiki\PersonalWikiFrame.py:1625 +#: lib\pwiki\PersonalWikiFrame.py:1643 msgid "Set as Roo&t" msgstr "Установить как &корневое" -#: lib\pwiki\PersonalWikiFrame.py:1626 +#: lib\pwiki\PersonalWikiFrame.py:1644 msgid "Set current wiki word as tree root" msgstr "Установить текущее вики-слово в качестве корня дерева" -#: lib\pwiki\PersonalWikiFrame.py:1630 +#: lib\pwiki\PersonalWikiFrame.py:1648 msgid "R&eset Root" msgstr "&Востановить корневое слово" -#: lib\pwiki\PersonalWikiFrame.py:1631 +#: lib\pwiki\PersonalWikiFrame.py:1649 msgid "Set home wiki word as tree root" msgstr "Восстановить домашнюю страницу в качестве корня дерева" -#: lib\pwiki\PersonalWikiFrame.py:1635 +#: lib\pwiki\PersonalWikiFrame.py:1653 msgid "S&ynchronise Tree" msgstr "&Синхронизировать дерево" -#: lib\pwiki\PersonalWikiFrame.py:1636 +#: lib\pwiki\PersonalWikiFrame.py:1654 msgid "Find the current wiki word in the tree" msgstr "Найти текущее вики-слово в дереве" -#: lib\pwiki\PersonalWikiFrame.py:1641 +#: lib\pwiki\PersonalWikiFrame.py:1659 msgid "&Follow Link" msgstr "&Открыть ссылку" -#: lib\pwiki\PersonalWikiFrame.py:1642 +#: lib\pwiki\PersonalWikiFrame.py:1660 msgid "Activate link/word" msgstr "Открыть ссылку/слово" -#: lib\pwiki\PersonalWikiFrame.py:1647 +#: lib\pwiki\PersonalWikiFrame.py:1665 msgid "Follow Link in &New Tab" msgstr "Открыть ссылку в &новой вкладке " -#: lib\pwiki\PersonalWikiFrame.py:1648 +#: lib\pwiki\PersonalWikiFrame.py:1666 msgid "Activate link/word in new tab" msgstr "Открыть ссылку/слово в новой вкладке" -#: lib\pwiki\PersonalWikiFrame.py:1653 +#: lib\pwiki\PersonalWikiFrame.py:1671 msgid "Copy &URL to Clipboard" msgstr "&Копировать URL в буфер" -#: lib\pwiki\PersonalWikiFrame.py:1655 +#: lib\pwiki\PersonalWikiFrame.py:1673 msgid "Copy full \"wiki:\" URL of the word to clipboard" msgstr "Копировать полный \"wiki:\" URL слова в буфер" -#: lib\pwiki\PersonalWikiFrame.py:1661 +#: lib\pwiki\PersonalWikiFrame.py:1679 msgid "&Add version" msgstr "&Добавить версию" -#: lib\pwiki\PersonalWikiFrame.py:1662 +#: lib\pwiki\PersonalWikiFrame.py:1680 msgid "Add new version" msgstr "Добавить новую версию" -#: lib\pwiki\PersonalWikiFrame.py:1670 +#: lib\pwiki\PersonalWikiFrame.py:1688 msgid "&Bold" msgstr "&Полужирный" -#: lib\pwiki\PersonalWikiFrame.py:1671 -#: lib\pwiki\PersonalWikiFrame.py:2082 +#: lib\pwiki\PersonalWikiFrame.py:1689 lib\pwiki\PersonalWikiFrame.py:2100 msgid "Bold" msgstr "Полужирный" -#: lib\pwiki\PersonalWikiFrame.py:1677 +#: lib\pwiki\PersonalWikiFrame.py:1695 msgid "&Italic" msgstr "&Курсив" -#: lib\pwiki\PersonalWikiFrame.py:1678 -#: lib\pwiki\PersonalWikiFrame.py:2088 +#: lib\pwiki\PersonalWikiFrame.py:1696 lib\pwiki\PersonalWikiFrame.py:2106 msgid "Italic" msgstr "Курсив" -#: lib\pwiki\PersonalWikiFrame.py:1684 +#: lib\pwiki\PersonalWikiFrame.py:1702 msgid "&Heading" msgstr "&Заголовок" -#: lib\pwiki\PersonalWikiFrame.py:1685 +#: lib\pwiki\PersonalWikiFrame.py:1703 msgid "Add Heading" msgstr "Добавить заголовок" -#: lib\pwiki\PersonalWikiFrame.py:1693 +#: lib\pwiki\PersonalWikiFrame.py:1711 msgid "&Rewrap Text" msgstr "Обновить &переносы" -#: lib\pwiki\PersonalWikiFrame.py:1695 +#: lib\pwiki\PersonalWikiFrame.py:1713 msgid "Rewrap Text" msgstr "Обновить переносы текста" -#: lib\pwiki\PersonalWikiFrame.py:1700 +#: lib\pwiki\PersonalWikiFrame.py:1718 msgid "&Convert" msgstr "&Преобразовать" -#: lib\pwiki\PersonalWikiFrame.py:1703 +#: lib\pwiki\PersonalWikiFrame.py:1721 msgid "Selection to &Link" msgstr "Выделенное в ссылку (&викифицировать)" -#: lib\pwiki\PersonalWikiFrame.py:1704 +#: lib\pwiki\PersonalWikiFrame.py:1722 msgid "Remove non-allowed characters and make sel. a wiki word link" msgstr "Удалить недопустимые символы и преобразовать выделенный фрагмент в вики-ссылку" -#: lib\pwiki\PersonalWikiFrame.py:1709 +#: lib\pwiki\PersonalWikiFrame.py:1727 msgid "Selection to &Wiki Word" msgstr "Выделенное в вики-&слово" -#: lib\pwiki\PersonalWikiFrame.py:1711 +#: lib\pwiki\PersonalWikiFrame.py:1729 msgid "Put selected text in a new or existing wiki word" msgstr "Переместить выделенный фрагмент в новое или существующее вики-слово" -#: lib\pwiki\PersonalWikiFrame.py:1716 +#: lib\pwiki\PersonalWikiFrame.py:1734 msgid "Absolute/Relative &File URL" msgstr "&Абсолютный/Относительный URL файла" -#: lib\pwiki\PersonalWikiFrame.py:1718 +#: lib\pwiki\PersonalWikiFrame.py:1736 msgid "Convert file URL from absolute to relative and vice versa" msgstr "Преобразовать URL файла из абсолютного в относительный и обратно" -#: lib\pwiki\PersonalWikiFrame.py:1732 +#: lib\pwiki\PersonalWikiFrame.py:1750 msgid "&Icon Name" msgstr "&Иконка" -#: lib\pwiki\PersonalWikiFrame.py:1744 +#: lib\pwiki\PersonalWikiFrame.py:1762 msgid "&Color Name" msgstr "&Цвет" -#: lib\pwiki\PersonalWikiFrame.py:1752 +#: lib\pwiki\PersonalWikiFrame.py:1770 msgid "&Add Attribute" msgstr "&Добавить атрибут" -#: lib\pwiki\PersonalWikiFrame.py:1761 +#: lib\pwiki\PersonalWikiFrame.py:1779 msgid "&Icon Attribute" msgstr "Атрибут &иконки" -#: lib\pwiki\PersonalWikiFrame.py:1771 +#: lib\pwiki\PersonalWikiFrame.py:1789 msgid "&Color Attribute" msgstr "Атрибут &цвета" -#: lib\pwiki\PersonalWikiFrame.py:1780 +#: lib\pwiki\PersonalWikiFrame.py:1798 msgid "&Back" msgstr "&Назад" -#: lib\pwiki\PersonalWikiFrame.py:1781 +#: lib\pwiki\PersonalWikiFrame.py:1799 msgid "Go backward" msgstr "Перейти назад" -#: lib\pwiki\PersonalWikiFrame.py:1786 +#: lib\pwiki\PersonalWikiFrame.py:1804 msgid "&Forward" msgstr "&Вперёд" -#: lib\pwiki\PersonalWikiFrame.py:1787 +#: lib\pwiki\PersonalWikiFrame.py:1805 msgid "Go forward" msgstr "Перейти вперёд" -#: lib\pwiki\PersonalWikiFrame.py:1792 +#: lib\pwiki\PersonalWikiFrame.py:1810 msgid "&Wiki Home" msgstr "&Домашняя страница" -#: lib\pwiki\PersonalWikiFrame.py:1793 +#: lib\pwiki\PersonalWikiFrame.py:1811 msgid "Go to wiki homepage" msgstr "Перейти на домашнюю страницу этой вики" -#: lib\pwiki\PersonalWikiFrame.py:1799 +#: lib\pwiki\PersonalWikiFrame.py:1817 msgid "Up&ward" msgstr "В&верх" -#: lib\pwiki\PersonalWikiFrame.py:1801 -#: lib\pwiki\PersonalWikiFrame.py:2049 -#: lib\pwiki\PersonalWikiFrame.py:2050 +#: lib\pwiki\PersonalWikiFrame.py:1819 lib\pwiki\PersonalWikiFrame.py:2067 +#: lib\pwiki\PersonalWikiFrame.py:2068 msgid "Go upward from a subpage" msgstr "Перейти вверх от подстраницы" -#: lib\pwiki\PersonalWikiFrame.py:1806 +#: lib\pwiki\PersonalWikiFrame.py:1824 msgid "Go to &Page..." msgstr "Перейти на &страницу..." -#: lib\pwiki\PersonalWikiFrame.py:1807 +#: lib\pwiki\PersonalWikiFrame.py:1825 msgid "Open wiki word" msgstr "Открыть вики-слово" -#: lib\pwiki\PersonalWikiFrame.py:1812 +#: lib\pwiki\PersonalWikiFrame.py:1830 msgid "Go to P&arent..." msgstr "Перейти к &родителю..." -#: lib\pwiki\PersonalWikiFrame.py:1814 +#: lib\pwiki\PersonalWikiFrame.py:1832 msgid "List parents of current wiki word" msgstr "Список родителей текущего вики-слова" -#: lib\pwiki\PersonalWikiFrame.py:1817 +#: lib\pwiki\PersonalWikiFrame.py:1835 msgid "List &Children..." msgstr "Перейти к &потомку..." -#: lib\pwiki\PersonalWikiFrame.py:1819 +#: lib\pwiki\PersonalWikiFrame.py:1837 msgid "List children of current wiki word" msgstr "Список потомков текущего вики-слова" -#: lib\pwiki\PersonalWikiFrame.py:1822 +#: lib\pwiki\PersonalWikiFrame.py:1840 msgid "List Pa&rentless Pages" msgstr "Список страниц &без родителей" -#: lib\pwiki\PersonalWikiFrame.py:1824 +#: lib\pwiki\PersonalWikiFrame.py:1842 msgid "List nodes with no parent relations" msgstr "Показать список узлов, не имеющих родителей" -#: lib\pwiki\PersonalWikiFrame.py:1829 +#: lib\pwiki\PersonalWikiFrame.py:1847 msgid "Show &History..." msgstr "Показать &историю..." -#: lib\pwiki\PersonalWikiFrame.py:1830 +#: lib\pwiki\PersonalWikiFrame.py:1848 msgid "View tab history" msgstr "Посмотреть историю вкладки" -#: lib\pwiki\PersonalWikiFrame.py:1833 +#: lib\pwiki\PersonalWikiFrame.py:1851 msgid "&Up History..." msgstr "&Назад по истории..." -#: lib\pwiki\PersonalWikiFrame.py:1834 +#: lib\pwiki\PersonalWikiFrame.py:1852 msgid "Up in tab history" msgstr "Назад по истории вкладки" -#: lib\pwiki\PersonalWikiFrame.py:1837 +#: lib\pwiki\PersonalWikiFrame.py:1855 msgid "&Down History..." msgstr "&Вперёд по истории..." -#: lib\pwiki\PersonalWikiFrame.py:1838 +#: lib\pwiki\PersonalWikiFrame.py:1856 msgid "Down in tab history" msgstr "Вперёд по истории вкладки" -#: lib\pwiki\PersonalWikiFrame.py:1843 +#: lib\pwiki\PersonalWikiFrame.py:1861 msgid "Add B&ookmark" msgstr "Добавить &закладку" -#: lib\pwiki\PersonalWikiFrame.py:1844 +#: lib\pwiki\PersonalWikiFrame.py:1862 msgid "Add bookmark to page" msgstr "Добавить закладку на эту страницу" -#: lib\pwiki\PersonalWikiFrame.py:1848 +#: lib\pwiki\PersonalWikiFrame.py:1866 msgid "Go to &Bookmark..." msgstr "Перейти &к закладке..." -#: lib\pwiki\PersonalWikiFrame.py:1849 +#: lib\pwiki\PersonalWikiFrame.py:1867 msgid "List bookmarks" msgstr "Список закладок" -#: lib\pwiki\PersonalWikiFrame.py:1855 +#: lib\pwiki\PersonalWikiFrame.py:1873 msgid "&Export..." msgstr "&Экспорт..." -#: lib\pwiki\PersonalWikiFrame.py:1859 +#: lib\pwiki\PersonalWikiFrame.py:1877 msgid "&Continuous Export..." msgstr "&Непрерывный экспорт..." -#: lib\pwiki\PersonalWikiFrame.py:1860 +#: lib\pwiki\PersonalWikiFrame.py:1878 msgid "Open export dialog for continuous export of changes" msgstr "Открыть диалог непрерывного экспорта изменений" -#: lib\pwiki\PersonalWikiFrame.py:1866 +#: lib\pwiki\PersonalWikiFrame.py:1884 msgid "&Import..." msgstr "&Импорт..." -#: lib\pwiki\PersonalWikiFrame.py:1867 +#: lib\pwiki\PersonalWikiFrame.py:1885 msgid "Import dialog" msgstr "Диалог импорта" -#: lib\pwiki\PersonalWikiFrame.py:1873 +#: lib\pwiki\PersonalWikiFrame.py:1891 msgid "Scripts" msgstr "Скрипты" -#: lib\pwiki\PersonalWikiFrame.py:1874 +#: lib\pwiki\PersonalWikiFrame.py:1892 msgid "Run scripts, evaluate expressions" msgstr "Запустить скрипты, вычислить значения выражений" -#: lib\pwiki\PersonalWikiFrame.py:1876 +#: lib\pwiki\PersonalWikiFrame.py:1894 msgid "&Eval" msgstr "&Вычислить (Eval)" -#: lib\pwiki\PersonalWikiFrame.py:1877 +#: lib\pwiki\PersonalWikiFrame.py:1895 msgid "Evaluate script blocks" msgstr "Обработать блоки скриптов" -#: lib\pwiki\PersonalWikiFrame.py:1882 +#: lib\pwiki\PersonalWikiFrame.py:1900 msgid "Run Function &%i\tCtrl-%i" msgstr "Запустить функцию &%i\tCtrl-%i" -#: lib\pwiki\PersonalWikiFrame.py:1883 +#: lib\pwiki\PersonalWikiFrame.py:1901 msgid "Run script function %i" msgstr "Запустить функцию скрипта %i" -#: lib\pwiki\PersonalWikiFrame.py:1888 +#: lib\pwiki\PersonalWikiFrame.py:1906 msgid "O&ptions..." msgstr "&Настройки..." -#: lib\pwiki\PersonalWikiFrame.py:1889 +#: lib\pwiki\PersonalWikiFrame.py:1907 msgid "Set options" msgstr "Изменить настройки" -#: lib\pwiki\PersonalWikiFrame.py:1910 +#: lib\pwiki\PersonalWikiFrame.py:1928 msgid "&Open help wiki" msgstr "Открыть &справку " -#: lib\pwiki\PersonalWikiFrame.py:1911 +#: lib\pwiki\PersonalWikiFrame.py:1929 msgid "Open WikidPadHelp, the help wiki" msgstr "Открыть WikidPadHelp, вики справки" -#: lib\pwiki\PersonalWikiFrame.py:1920 +#: lib\pwiki\PersonalWikiFrame.py:1938 msgid "&Visit Homepage" msgstr "&Перейти на страницу WikidPad в интернете" -#: lib\pwiki\PersonalWikiFrame.py:1921 +#: lib\pwiki\PersonalWikiFrame.py:1939 msgid "Visit wikidPad homepage" msgstr "Перейти на домашнюю страницу WikidPad в интернете" -#: lib\pwiki\PersonalWikiFrame.py:1931 +#: lib\pwiki\PersonalWikiFrame.py:1949 msgid "Show &License" msgstr "Показать &лицензию" -#: lib\pwiki\PersonalWikiFrame.py:1932 +#: lib\pwiki\PersonalWikiFrame.py:1950 msgid "Show license of WikidPad and used components" msgstr "Показать лицензии WikidPad и использованных компонентов" -#: lib\pwiki\PersonalWikiFrame.py:1949 +#: lib\pwiki\PersonalWikiFrame.py:1967 msgid "&About" msgstr "&О программе WikidPad" -#: lib\pwiki\PersonalWikiFrame.py:1953 +#: lib\pwiki\PersonalWikiFrame.py:1971 msgid "&Wiki" msgstr "&Вики" -#: lib\pwiki\PersonalWikiFrame.py:1954 +#: lib\pwiki\PersonalWikiFrame.py:1972 msgid "&Edit" msgstr "&Правка" -#: lib\pwiki\PersonalWikiFrame.py:1955 +#: lib\pwiki\PersonalWikiFrame.py:1973 msgid "&View" msgstr "В&ид" -#: lib\pwiki\PersonalWikiFrame.py:1956 +#: lib\pwiki\PersonalWikiFrame.py:1974 msgid "&Tabs" msgstr "В&кладки" -#: lib\pwiki\PersonalWikiFrame.py:1957 +#: lib\pwiki\PersonalWikiFrame.py:1975 msgid "Wiki &Page" msgstr "Вики-&страница" -#: lib\pwiki\PersonalWikiFrame.py:1958 +#: lib\pwiki\PersonalWikiFrame.py:1976 msgid "&Format" msgstr "&Формат" -#: lib\pwiki\PersonalWikiFrame.py:1959 +#: lib\pwiki\PersonalWikiFrame.py:1977 msgid "&Navigate" msgstr "&Навигация" -#: lib\pwiki\PersonalWikiFrame.py:1960 +#: lib\pwiki\PersonalWikiFrame.py:1978 msgid "E&xtra" msgstr "&Дополнительно" -#: lib\pwiki\PersonalWikiFrame.py:1976 +#: lib\pwiki\PersonalWikiFrame.py:1994 msgid "Pl&ugins" msgstr "&Плагины" -#: lib\pwiki\PersonalWikiFrame.py:1982 +#: lib\pwiki\PersonalWikiFrame.py:2000 msgid "&Help" msgstr "&Справка" -#: lib\pwiki\PersonalWikiFrame.py:1984 +#: lib\pwiki\PersonalWikiFrame.py:2002 msgid "He&lp" msgstr "&Справка" -#: lib\pwiki\PersonalWikiFrame.py:2010 -#: lib\pwiki\PersonalWikiFrame.py:2011 +#: lib\pwiki\PersonalWikiFrame.py:2028 lib\pwiki\PersonalWikiFrame.py:2029 msgid "Back" msgstr "Назад" -#: lib\pwiki\PersonalWikiFrame.py:2016 -#: lib\pwiki\PersonalWikiFrame.py:2017 +#: lib\pwiki\PersonalWikiFrame.py:2034 lib\pwiki\PersonalWikiFrame.py:2035 msgid "Forward" msgstr "Вперёд" -#: lib\pwiki\PersonalWikiFrame.py:2022 -#: lib\pwiki\PersonalWikiFrame.py:2023 +#: lib\pwiki\PersonalWikiFrame.py:2040 lib\pwiki\PersonalWikiFrame.py:2041 msgid "Wiki Home" msgstr "Домашняя страница" -#: lib\pwiki\PersonalWikiFrame.py:2037 -#: lib\pwiki\PersonalWikiFrame.py:2038 +#: lib\pwiki\PersonalWikiFrame.py:2055 lib\pwiki\PersonalWikiFrame.py:2056 msgid "Search" msgstr "Найти" -#: lib\pwiki\PersonalWikiFrame.py:2043 -#: lib\pwiki\PersonalWikiFrame.py:2044 +#: lib\pwiki\PersonalWikiFrame.py:2061 lib\pwiki\PersonalWikiFrame.py:2062 msgid "Find current word in tree" msgstr "Найти текущее слово в дереве" -#: lib\pwiki\PersonalWikiFrame.py:2053 -#: lib\pwiki\PersonalWikiFrame.py:2072 -#: lib\pwiki\PersonalWikiFrame.py:2092 +#: lib\pwiki\PersonalWikiFrame.py:2071 lib\pwiki\PersonalWikiFrame.py:2090 +#: lib\pwiki\PersonalWikiFrame.py:2110 msgid "Separator" msgstr "Разделитель" -#: lib\pwiki\PersonalWikiFrame.py:2057 -#: lib\pwiki\PersonalWikiFrame.py:2058 +#: lib\pwiki\PersonalWikiFrame.py:2075 lib\pwiki\PersonalWikiFrame.py:2076 msgid "Save Wiki Word" msgstr "Сохранить вики-слово" -#: lib\pwiki\PersonalWikiFrame.py:2068 -#: lib\pwiki\PersonalWikiFrame.py:2069 -#: lib\pwiki\PersonalWikiFrame.py:4359 +#: lib\pwiki\PersonalWikiFrame.py:2086 lib\pwiki\PersonalWikiFrame.py:2087 +#: lib\pwiki\PersonalWikiFrame.py:4401 msgid "Delete Wiki Word" msgstr "Удалить вики-слово" -#: lib\pwiki\PersonalWikiFrame.py:2076 +#: lib\pwiki\PersonalWikiFrame.py:2094 msgid "Heading" msgstr "Заголовки" -#: lib\pwiki\PersonalWikiFrame.py:2096 +#: lib\pwiki\PersonalWikiFrame.py:2114 msgid "Switch Editor/Preview" msgstr "Переключить Редактор/Просмотр" -#: lib\pwiki\PersonalWikiFrame.py:2117 +#: lib\pwiki\PersonalWikiFrame.py:2135 msgid "Wikize Selected Word " msgstr "Викифицировать" -#: lib\pwiki\PersonalWikiFrame.py:2118 +#: lib\pwiki\PersonalWikiFrame.py:2136 msgid "Wikize Selected Word" msgstr "Преобразовать выделенный фрагмент в вики-слово" -#: lib\pwiki\PersonalWikiFrame.py:2189 +#: lib\pwiki\PersonalWikiFrame.py:2208 msgid "Line: 9999 Col: 9999 Pos: 9999999988888" msgstr "Строка: 9999 Колонка: 9999 Позиция: 9999999988888" -#: lib\pwiki\PersonalWikiFrame.py:2361 +#: lib\pwiki\PersonalWikiFrame.py:2380 msgid "Regular expression error" msgstr "Ошибка в регулярном выражении" -#: lib\pwiki\PersonalWikiFrame.py:2370 +#: lib\pwiki\PersonalWikiFrame.py:2389 msgid "Are you sure you want to reconnect? You may lose some data by this process." msgstr "Вы уверены, что хотите подключиться заново? Вы можете потерять некоторые данные." -#: lib\pwiki\PersonalWikiFrame.py:2372 +#: lib\pwiki\PersonalWikiFrame.py:2391 msgid "Reconnect database" msgstr "Подключить базу данных заново" -#: lib\pwiki\PersonalWikiFrame.py:2386 -#: lib\pwiki\PersonalWikiFrame.py:3440 +#: lib\pwiki\PersonalWikiFrame.py:2405 lib\pwiki\PersonalWikiFrame.py:3466 msgid "Error while trying to reconnect:\n" msgstr "Ошибка при переподключении:\n" -#: lib\pwiki\PersonalWikiFrame.py:2388 -msgid "" -"There was an error while reconnecting the database\n" +#: lib\pwiki\PersonalWikiFrame.py:2407 +msgid "There was an error while reconnecting the database\n" "\n" "Would you like to try it again?\n" "%s" -msgstr "" -"Произошла ошибка при переподключении базы данных\n" +msgstr "Произошла ошибка при переподключении базы данных\n" "\n" "Хотите попробовать ещё раз?\n" "%s" -#: lib\pwiki\PersonalWikiFrame.py:2391 +#: lib\pwiki\PersonalWikiFrame.py:2410 msgid "Error reconnecting!" msgstr "Ошибка переподключения!" -#: lib\pwiki\PersonalWikiFrame.py:2407 -#: lib\pwiki\PersonalWikiFrame.py:3487 +#: lib\pwiki\PersonalWikiFrame.py:2426 lib\pwiki\PersonalWikiFrame.py:3513 msgid "Error while trying to write:\n" msgstr "Ошибка при записи:\n" -#: lib\pwiki\PersonalWikiFrame.py:2409 -msgid "" -"There was an error while writing to the database\n" +#: lib\pwiki\PersonalWikiFrame.py:2428 +msgid "There was an error while writing to the database\n" "\n" "Would you like to try it again?\n" "%s" -msgstr "" -"Произошла ошибка при записи в базу данных\n" +msgstr "Произошла ошибка при записи в базу данных\n" "\n" "Хотите попробовать ещё раз?\n" "%s" -#: lib\pwiki\PersonalWikiFrame.py:2412 +#: lib\pwiki\PersonalWikiFrame.py:2431 msgid "Error writing!" msgstr "Ошибка записи!" -#: lib\pwiki\PersonalWikiFrame.py:2531 +#: lib\pwiki\PersonalWikiFrame.py:2550 msgid "There was an error loading the icons for the tree control." msgstr "Произошла ошибка при загрузке иконок для дерева." -#: lib\pwiki\PersonalWikiFrame.py:2797 +#: lib\pwiki\PersonalWikiFrame.py:2816 msgid "No data handler available to create database." msgstr "Нет доступного обработчика данных для создания базы данных" -#: lib\pwiki\PersonalWikiFrame.py:2809 +#: lib\pwiki\PersonalWikiFrame.py:2828 msgid "A wiki already exists in '%s', overwrite? (This deletes everything in and below this directory!)" msgstr "Вики в '%s' уже существует, переписать? (Это уничтожит всё в этой папке и её подпапках!)" -#: lib\pwiki\PersonalWikiFrame.py:2811 +#: lib\pwiki\PersonalWikiFrame.py:2830 msgid "Warning" msgstr "Предупреждение" -#: lib\pwiki\PersonalWikiFrame.py:2852 +#: lib\pwiki\PersonalWikiFrame.py:2871 msgid "A wiki database already exists in this location, overwrite?" msgstr "База данных в этом месте уже существует, переписать?" -#: lib\pwiki\PersonalWikiFrame.py:2854 +#: lib\pwiki\PersonalWikiFrame.py:2873 msgid "Wiki DB Exists" msgstr "БД вики существует" -#: lib\pwiki\PersonalWikiFrame.py:2865 +#: lib\pwiki\PersonalWikiFrame.py:2884 msgid "There was an error creating the wiki database." msgstr "Произошла ошибка при создании базы данных вики." -#: lib\pwiki\PersonalWikiFrame.py:2934 -#: lib\pwiki\PersonalWikiFrame.py:2935 +#: lib\pwiki\PersonalWikiFrame.py:2953 lib\pwiki\PersonalWikiFrame.py:2954 msgid "Choose database type" msgstr "Выберите тип базы данных" -#: lib\pwiki\PersonalWikiFrame.py:2972 +#: lib\pwiki\PersonalWikiFrame.py:2991 msgid "Inaccessible or missing file: %s" msgstr "Файл недоступен или отсутствует: %s" -#: lib\pwiki\PersonalWikiFrame.py:3023 -#: lib\pwiki\PersonalWikiFrame.py:3108 +#: lib\pwiki\PersonalWikiFrame.py:3042 lib\pwiki\PersonalWikiFrame.py:3127 msgid "Error connecting to database in '%s'" msgstr "Ошибка подключения к базе данных в '%s'" -#: lib\pwiki\PersonalWikiFrame.py:3028 +#: lib\pwiki\PersonalWikiFrame.py:3047 msgid "The wiki needs an update to work with this version of WikidPad. Older versions of WikidPad may be unable to read the wiki after an update." msgstr "Чтобы работать с этой версией WikidPad, вики требует обновления. После обновления более старые версии WikidPad могут отказаться читать эту вики." -#: lib\pwiki\PersonalWikiFrame.py:3031 +#: lib\pwiki\PersonalWikiFrame.py:3050 msgid "Update database?" msgstr "Обновить базу данных?" -#: lib\pwiki\PersonalWikiFrame.py:3066 -msgid "" -"Wiki '%s' is probably in use by different\n" +#: lib\pwiki\PersonalWikiFrame.py:3085 +msgid "Wiki '%s' is probably in use by different\n" "instance of WikidPad. Connect anyway (dangerous!)?" -msgstr "" -"Вики '%s', вероятно, используется другой\n" +msgstr "Вики '%s', вероятно, используется другой\n" "копией WikidPad. Всё равно подключить (опасно!)?" -#: lib\pwiki\PersonalWikiFrame.py:3068 +#: lib\pwiki\PersonalWikiFrame.py:3087 msgid "Wiki already in use" msgstr "Вики уже используется" -#: lib\pwiki\PersonalWikiFrame.py:3077 -msgid "" -"Configuration file '%s' is corrupted or missing.\n" +#: lib\pwiki\PersonalWikiFrame.py:3096 +msgid "Configuration file '%s' is corrupted or missing.\n" "You may have to change some settings in configuration page \"Current Wiki\" and below which were lost." -msgstr "" -"Файл конфигурации '%s' повреждён или отсутствует.\n" +msgstr "Файл конфигурации '%s' повреждён или отсутствует.\n" "Вероятно, вам нужно изменить некоторые утерянные установки на странице настроек \"Текущая вики\" и ниже." -#: lib\pwiki\PersonalWikiFrame.py:3115 +#: lib\pwiki\PersonalWikiFrame.py:3134 msgid "Can't write to database '%s'" msgstr "Невозможно записать в базу данных '%s'" -#: lib\pwiki\PersonalWikiFrame.py:3324 -msgid "" -"There is no (write-)access to underlying wiki\n" +#: lib\pwiki\PersonalWikiFrame.py:3343 +msgid "There is no (write-)access to underlying wiki\n" "Close anyway and loose possible changes?" -msgstr "" -"Нет доступа (для записи) к вики\n" +msgstr "Нет доступа (для записи) к вики\n" "Всё равно закрыть и потерять возможные изменения?" -#: lib\pwiki\PersonalWikiFrame.py:3326 +#: lib\pwiki\PersonalWikiFrame.py:3345 msgid "Close anyway" msgstr "Всё равно закрыть" -#: lib\pwiki\PersonalWikiFrame.py:3410 +#: lib\pwiki\PersonalWikiFrame.py:3436 msgid "This operation requires an open database" msgstr "Эта операция требует открытой базы данных" -#: lib\pwiki\PersonalWikiFrame.py:3411 +#: lib\pwiki\PersonalWikiFrame.py:3437 msgid "No open database" msgstr "Нет открытой базы данных" -#: lib\pwiki\PersonalWikiFrame.py:3423 +#: lib\pwiki\PersonalWikiFrame.py:3449 msgid "No connection to database. Try to reconnect?" msgstr "Нет подключения к базе данных. Попытаться подключиться заново?" -#: lib\pwiki\PersonalWikiFrame.py:3424 +#: lib\pwiki\PersonalWikiFrame.py:3450 msgid "Reconnect database?" msgstr "Подключить базу данных заново?" -#: lib\pwiki\PersonalWikiFrame.py:3431 +#: lib\pwiki\PersonalWikiFrame.py:3457 msgid "Trying to reconnect database..." msgstr "Подключаюсь заново к базе данных..." -#: lib\pwiki\PersonalWikiFrame.py:3443 +#: lib\pwiki\PersonalWikiFrame.py:3469 msgid "Error while reconnecting database" msgstr "Ошибка при переподключении к базе данных" -#: lib\pwiki\PersonalWikiFrame.py:3468 -msgid "" -"This operation needs write access to database\n" +#: lib\pwiki\PersonalWikiFrame.py:3494 +msgid "This operation needs write access to database\n" "Try to write?" -msgstr "" -"Эта операция требует разрешения на запись в базу данных\n" +msgstr "Эта операция требует разрешения на запись в базу данных\n" "Попытаться записать?" -#: lib\pwiki\PersonalWikiFrame.py:3469 +#: lib\pwiki\PersonalWikiFrame.py:3495 msgid "Try writing?" msgstr "Попытаться записать?" -#: lib\pwiki\PersonalWikiFrame.py:3476 +#: lib\pwiki\PersonalWikiFrame.py:3502 msgid "Trying to write to database..." msgstr "Пытаюсь записать в базу данных..." -#: lib\pwiki\PersonalWikiFrame.py:3490 +#: lib\pwiki\PersonalWikiFrame.py:3516 msgid "Error while writing to database" msgstr "Ошибка при записи в базу данных" -#: lib\pwiki\PersonalWikiFrame.py:3514 -msgid "" -"Database connection error: %s.\n" +#: lib\pwiki\PersonalWikiFrame.py:3540 +msgid "Database connection error: %s.\n" "Try to re-establish, then run \"Wiki\"->\"Reconnect\"" -msgstr "" -"Ошибка подключения к базе данных: %s.\n" +msgstr "Ошибка подключения к базе данных: %s.\n" "Попытайтесь исправить и затем запустить \"Вики\"->\"Подключить заново\"" -#: lib\pwiki\PersonalWikiFrame.py:3516 -#: lib\pwiki\PersonalWikiFrame.py:3533 +#: lib\pwiki\PersonalWikiFrame.py:3542 lib\pwiki\PersonalWikiFrame.py:3559 msgid "Connection lost" msgstr "Подключение потеряно" -#: lib\pwiki\PersonalWikiFrame.py:3531 -msgid "" -"No write access to database: %s.\n" +#: lib\pwiki\PersonalWikiFrame.py:3557 +msgid "No write access to database: %s.\n" " Try to re-establish, then run \"Wiki\"->\"Reconnect\"" -msgstr "" -"Нет доступа на запись к базе данных: %s.\n" +msgstr "Нет доступа на запись к базе данных: %s.\n" "Попытайтесь исправить и затем запустить \"Вики\"->\"Подключить заново\"" -#: lib\pwiki\PersonalWikiFrame.py:3550 +#: lib\pwiki\PersonalWikiFrame.py:3576 msgid "Trying to reconnect ..." msgstr "Пытаюсь подключить заново..." -#: lib\pwiki\PersonalWikiFrame.py:3559 +#: lib\pwiki\PersonalWikiFrame.py:3585 msgid "Error while trying to reconnect:" msgstr "Ошибка при переподключении:" -#: lib\pwiki\PersonalWikiFrame.py:3803 +#: lib\pwiki\PersonalWikiFrame.py:3831 msgid "Couldn't start file" msgstr "Не могу запустить файл" -#: lib\pwiki\PersonalWikiFrame.py:3818 +#: lib\pwiki\PersonalWikiFrame.py:3839 msgid "Couldn't open wiki: %s" msgstr "Не могу открыть вики: %s" -#: lib\pwiki\PersonalWikiFrame.py:3848 +#: lib\pwiki\PersonalWikiFrame.py:3890 msgid "Mod.: %s" msgstr "Модиф.: %s" -#: lib\pwiki\PersonalWikiFrame.py:3849 +#: lib\pwiki\PersonalWikiFrame.py:3891 msgid "; Crea.: %s" msgstr "; Созд.: %s" -#: lib\pwiki\PersonalWikiFrame.py:3886 +#: lib\pwiki\PersonalWikiFrame.py:3928 msgid "Parent nodes of '%s'" msgstr "Родительские узлы для '%s'" -#: lib\pwiki\PersonalWikiFrame.py:3898 +#: lib\pwiki\PersonalWikiFrame.py:3940 msgid "Parentless nodes" msgstr "Узлы без родителей" -#: lib\pwiki\PersonalWikiFrame.py:3910 +#: lib\pwiki\PersonalWikiFrame.py:3952 msgid "Child nodes of '%s'" msgstr "Узлы-потомки для '%s'" -#: lib\pwiki\PersonalWikiFrame.py:3923 +#: lib\pwiki\PersonalWikiFrame.py:3965 msgid "Bookmarks" msgstr "Закладки" -#: lib\pwiki\PersonalWikiFrame.py:4070 +#: lib\pwiki\PersonalWikiFrame.py:4112 msgid "Wiki: %s" msgstr "Вики: %s" -#: lib\pwiki\PersonalWikiFrame.py:4209 +#: lib\pwiki\PersonalWikiFrame.py:4251 msgid "Set at Page: %s\t%s" msgstr "Установить на странице: %s\t%s" -#: lib\pwiki\PersonalWikiFrame.py:4225 +#: lib\pwiki\PersonalWikiFrame.py:4267 msgid "Error saving global configuration" msgstr "Ошибка при сохранении глобальной конфигурации" -#: lib\pwiki\PersonalWikiFrame.py:4236 +#: lib\pwiki\PersonalWikiFrame.py:4278 msgid "Error saving current configuration" msgstr "Ошибка при сохранении текущей конфигурации" -#: lib\pwiki\PersonalWikiFrame.py:4258 +#: lib\pwiki\PersonalWikiFrame.py:4300 msgid "No real wiki word selected to rename" msgstr "Не выбрано реального вики-слова для переименования" -#: lib\pwiki\PersonalWikiFrame.py:4262 +#: lib\pwiki\PersonalWikiFrame.py:4304 msgid "The scratch pad cannot be renamed." msgstr "Черновик (ScratchPad) нельзя переименовать" -#: lib\pwiki\PersonalWikiFrame.py:4286 +#: lib\pwiki\PersonalWikiFrame.py:4328 msgid "Description:" msgstr "Описание:" -#: lib\pwiki\PersonalWikiFrame.py:4287 +#: lib\pwiki\PersonalWikiFrame.py:4329 msgid "Store new version" msgstr "Сохранить новую версию" -#: lib\pwiki\PersonalWikiFrame.py:4301 +#: lib\pwiki\PersonalWikiFrame.py:4343 msgid "Do you want to delete all stored versions?" msgstr "Вы хотите удалить все сохранённые версии?" -#: lib\pwiki\PersonalWikiFrame.py:4302 +#: lib\pwiki\PersonalWikiFrame.py:4344 msgid "Delete All Versions" msgstr "Удалить все версии" -#: lib\pwiki\PersonalWikiFrame.py:4347 +#: lib\pwiki\PersonalWikiFrame.py:4389 msgid "The scratch pad cannot be deleted" msgstr "Черновик (ScratchPad) нельзя удалить" -#: lib\pwiki\PersonalWikiFrame.py:4351 +#: lib\pwiki\PersonalWikiFrame.py:4393 msgid "No real wiki word to delete" msgstr "Нет реального вики-слова для удаления" -#: lib\pwiki\PersonalWikiFrame.py:4358 +#: lib\pwiki\PersonalWikiFrame.py:4400 msgid "Are you sure you want to delete wiki word '%s'?" msgstr "Вы уверены, что хотите удалить вики-слово '%s'?" -#: lib\pwiki\PersonalWikiFrame.py:4389 +#: lib\pwiki\PersonalWikiFrame.py:4431 msgid "No real wiki word to modify" msgstr "Нет реального вики-слова для изменения" -#: lib\pwiki\PersonalWikiFrame.py:4405 +#: lib\pwiki\PersonalWikiFrame.py:4447 msgid "Replace text by WikiWord:" msgstr "Заменить выделенный текст вики-словом:" -#: lib\pwiki\PersonalWikiFrame.py:4406 +#: lib\pwiki\PersonalWikiFrame.py:4448 msgid "Replace by Wiki Word" msgstr "Заменить вики-словом" -#: lib\pwiki\PersonalWikiFrame.py:4415 +#: lib\pwiki\PersonalWikiFrame.py:4457 msgid "'%s' is an invalid wiki word." msgstr "'%s' - неверное вики-слово" -#: lib\pwiki\PersonalWikiFrame.py:4430 -msgid "" -"Wiki word %s exists already\n" +#: lib\pwiki\PersonalWikiFrame.py:4472 +msgid "Wiki word %s exists already\n" "Would you like to append to the word?" -msgstr "" -"Вики-слово %s уже существует\n" +msgstr "Вики-слово %s уже существует\n" "Вы хотите добавить в конец этого слова?" -#: lib\pwiki\PersonalWikiFrame.py:4433 +#: lib\pwiki\PersonalWikiFrame.py:4475 msgid "Word exists" msgstr "Слово существует" -#: lib\pwiki\PersonalWikiFrame.py:4691 +#: lib\pwiki\PersonalWikiFrame.py:4733 msgid "Error on export" msgstr "Ошибка при экспорте" -#: lib\pwiki\PersonalWikiFrame.py:4763 +#: lib\pwiki\PersonalWikiFrame.py:4805 msgid "Are you sure you want to start a full rebuild of wiki in background?" msgstr "Вы уверены, что хотите запустить полную перестройку вики в фоновом режиме?" -#: lib\pwiki\PersonalWikiFrame.py:4765 +#: lib\pwiki\PersonalWikiFrame.py:4807 msgid "Initiate update" msgstr "Запустить обновление" -#: lib\pwiki\PersonalWikiFrame.py:4772 -#: lib\pwiki\PersonalWikiFrame.py:4773 +#: lib\pwiki\PersonalWikiFrame.py:4814 lib\pwiki\PersonalWikiFrame.py:4815 msgid " Initiating update " msgstr " Запуск обновления" -#: lib\pwiki\PersonalWikiFrame.py:4787 +#: lib\pwiki\PersonalWikiFrame.py:4829 msgid "Error initiating update" msgstr "Ошибка при запуске обновления" -#: lib\pwiki\PersonalWikiFrame.py:4796 +#: lib\pwiki\PersonalWikiFrame.py:4838 msgid "Are you sure you want to rebuild this wiki? You may want to backup your data first!" msgstr "Вы уверены, что хотите перестроить вики? Возможно, вам сначала нужно сделать резервную копию!" -#: lib\pwiki\PersonalWikiFrame.py:4798 +#: lib\pwiki\PersonalWikiFrame.py:4840 msgid "Rebuild wiki" msgstr "Перестроить вики" -#: lib\pwiki\PersonalWikiFrame.py:4805 -#: lib\pwiki\PersonalWikiFrame.py:4806 +#: lib\pwiki\PersonalWikiFrame.py:4847 lib\pwiki\PersonalWikiFrame.py:4848 msgid " Rebuilding wiki " msgstr " Перестройка вики" -#: lib\pwiki\PersonalWikiFrame.py:4821 +#: lib\pwiki\PersonalWikiFrame.py:4863 msgid "Error rebuilding wiki" msgstr "Ошибка перестройки вики" -#: lib\pwiki\PersonalWikiFrame.py:4913 +#: lib\pwiki\PersonalWikiFrame.py:4955 msgid "This could overwrite pages in the database. Continue?" msgstr "Страницы в базе данных могут быть заменены. Продолжить?" -#: lib\pwiki\PersonalWikiFrame.py:4914 +#: lib\pwiki\PersonalWikiFrame.py:4956 msgid "Import pagefiles" msgstr "Импортировать файлы страниц" -#: lib\pwiki\PersonalWikiFrame.py:5016 +#: lib\pwiki\PersonalWikiFrame.py:5058 msgid "No list of strings passed to \"listmcstr\" dialog" msgstr "Нет списка строк, переданных в диалог \"listmcstr\"" -#: lib\pwiki\PersonalWikiFrame.py:5039 +#: lib\pwiki\PersonalWikiFrame.py:5081 msgid "Unknown dialog type" msgstr "Неизвестный тип диалога" -#: lib\pwiki\PersonalWikiFrame.py:5208 -#: lib\pwiki\PersonalWikiFrame.py:5226 -#: lib\pwiki\PersonalWikiFrame.py:5251 +#: lib\pwiki\PersonalWikiFrame.py:5250 lib\pwiki\PersonalWikiFrame.py:5268 +#: lib\pwiki\PersonalWikiFrame.py:5293 msgid "Choose a Wiki to open" msgstr "Выберите вики для открытия" -#: lib\pwiki\PersonalWikiFrame.py:5265 +#: lib\pwiki\PersonalWikiFrame.py:5307 msgid "Name for new wiki (must be in the form of a WikiWord):" msgstr "Имя новой вики (должно быть вида 'ВикиСлово'):" -#: lib\pwiki\PersonalWikiFrame.py:5266 +#: lib\pwiki\PersonalWikiFrame.py:5308 msgid "Create New Wiki" msgstr "Создать новую вики" -#: lib\pwiki\PersonalWikiFrame.py:5281 +#: lib\pwiki\PersonalWikiFrame.py:5323 msgid "Directory to store new wiki" msgstr "Папка для сохранения новой вики" -#: lib\pwiki\PersonalWikiFrame.py:5292 -#: lib\pwiki\wikidata\WikiDataManager.py:1406 -#: lib\pwiki\wikidata\WikiDataManager.py:1462 +#: lib\pwiki\PersonalWikiFrame.py:5334 +#: lib\pwiki\wikidata\WikiDataManager.py:1420 +#: lib\pwiki\wikidata\WikiDataManager.py:1476 msgid "'%s' is an invalid wiki word. %s" msgstr "Слово '%s' - неверное вики-слово. %s" -#: lib\pwiki\PersonalWikiFrame.py:5596 +#: lib\pwiki\PersonalWikiFrame.py:5638 msgid "Clipboard Catcher at Cursor" msgstr "Захват буфера в позиции курсора" -#: lib\pwiki\PersonalWikiFrame.py:5600 +#: lib\pwiki\PersonalWikiFrame.py:5642 msgid "Clipboard Catcher off" msgstr "Захват буфера отключен" -#: lib\pwiki\PersonalWikiFrame.py:5661 +#: lib\pwiki\PersonalWikiFrame.py:5703 msgid "Restore" msgstr "Восстановить" -#: lib\pwiki\PersonalWikiFrame.py:5662 +#: lib\pwiki\PersonalWikiFrame.py:5704 msgid "Save" msgstr "Сохранить" -#: lib\pwiki\Printing.py:328 -#: lib\pwiki\Printing.py:645 +#: lib\pwiki\Printing.py:328 lib\pwiki\Printing.py:645 msgid "Print Preview" msgstr "Предварительный просмотр печати" @@ -4569,11 +4430,9 @@ msgid "Replace All" msgstr "Заменить всё" #: lib\pwiki\SearchAndReplaceDialogs.py:1028 -msgid "" -"Bad regular expression '%s':\n" +msgid "Bad regular expression '%s':\n" "%s" -msgstr "" -"Неверное регулярное выражение '%s':\n" +msgstr "Неверное регулярное выражение '%s':\n" "%s" #: lib\pwiki\SearchAndReplaceDialogs.py:1252 @@ -4643,13 +4502,11 @@ msgstr "Как вкладку" msgid "Search: %s" msgstr "Найти: %s" -#: lib\pwiki\SearchAndReplaceDialogs.py:2366 -#: lib\pwiki\WikiHtmlView.py:671 +#: lib\pwiki\SearchAndReplaceDialogs.py:2366 lib\pwiki\WikiHtmlView.py:671 msgid "Activate" msgstr "Открыть в текущей вкладке" -#: lib\pwiki\SearchAndReplaceDialogs.py:2368 -#: lib\pwiki\WikiHtmlView.py:673 +#: lib\pwiki\SearchAndReplaceDialogs.py:2368 lib\pwiki\WikiHtmlView.py:673 msgid "Activate New Tab Backgrd." msgstr "Открыть новую вкладку на заднем плане" @@ -4661,8 +4518,7 @@ msgstr "Нет открытой вики или текущая страница msgid "Current page is not modified yet" msgstr "Текущая страница ещё не изменялась" -#: lib\pwiki\SpellChecker.py:138 -#: lib\pwiki\SpellChecker.py:172 +#: lib\pwiki\SpellChecker.py:138 lib\pwiki\SpellChecker.py:172 #: lib\pwiki\SpellChecker.py:180 msgid "No (more) misspelled words found" msgstr "Слов с ошибками (больше) нет " @@ -4675,8 +4531,7 @@ msgstr "Словарь для этой страницы не найден" msgid "Inval. timestamp" msgstr "Неверная временная метка" -#: lib\pwiki\TextTree.py:205 -#: lib\pwiki\TextTree.py:240 +#: lib\pwiki\TextTree.py:205 lib\pwiki\TextTree.py:240 msgid "" msgstr "<Без заголовка>" @@ -4688,30 +4543,32 @@ msgstr "Выберите вики для Избранного" msgid "Multiple words rename to same word" msgstr "Несколько слов переименовываются в одно и то же слово" -#: lib\pwiki\WikiHtmlView.py:558 -#: lib\pwiki\WikiTxtCtrl.py:2159 +#: lib\pwiki\WikiHtmlView.py:558 lib\pwiki\WikiTxtCtrl.py:2178 msgid "Folder does not exist" msgstr "Папка не существует" -#: lib\pwiki\WikiHtmlView.py:650 -#: lib\pwiki\WikiHtmlViewIE.py:494 -#: lib\pwiki\WikiHtmlViewWK.py:486 +#: lib\pwiki\WikiHtmlView.py:650 lib\pwiki\WikiHtmlViewIE.py:494 +#: lib\pwiki\WikiHtmlViewWK.py:968 lib\pwiki\WikiTxtCtrl.py:3364 msgid "Link to page: %s" msgstr "Ссылка на страницу: %s" -#: lib\pwiki\WikiHtmlViewWK.py:358 +#: lib\pwiki\WikiHtmlViewWK.py:73 lib\pwiki\WikiTxtDialogs.py:47 +msgid "Incremental search (ENTER/ESC to finish)" +msgstr "Пошаговый поиск (ENTER/ESC - остановка)" + +#: lib\pwiki\WikiHtmlViewWK.py:759 msgid "Open Link (External)" msgstr "Открыть ссылку (внешн.)" -#: lib\pwiki\WikiHtmlViewWK.py:361 +#: lib\pwiki\WikiHtmlViewWK.py:762 msgid "Follow &Link" msgstr "Перейти по &ссылке" -#: lib\pwiki\WikiHtmlViewWK.py:373 +#: lib\pwiki\WikiHtmlViewWK.py:774 msgid "Follow Link in New &Tab" msgstr "Перейти по ссылке в новой &вкладке" -#: lib\pwiki\WikiHtmlViewWK.py:380 +#: lib\pwiki\WikiHtmlViewWK.py:781 msgid "Follow Link in New Back&ground Tab" msgstr "Перейти по ссылке в новой вкладке в &фоне" @@ -4763,130 +4620,166 @@ msgstr "Добавить вики-слово в конец" msgid "Prepend Wiki Word" msgstr "Добавить вики-слово в начало" -#: lib\pwiki\WikiTxtCtrl.py:1252 +#: lib\pwiki\WikiTxtCtrl.py:1270 msgid "Select Template" msgstr "Выберите шаблон" -#: lib\pwiki\WikiTxtCtrl.py:1254 +#: lib\pwiki\WikiTxtCtrl.py:1272 msgid "Select Template (deletes current content!)" msgstr "Выберите шаблон (удалит текущее содержание!)" -#: lib\pwiki\WikiTxtCtrl.py:1360 +#: lib\pwiki\WikiTxtCtrl.py:1378 msgid "Use Template" msgstr "Использовать шаблон" -#: lib\pwiki\WikiTxtCtrl.py:2234 -msgid "" -"Set in menu \"Wiki\", item \"Options...\", options page \"Security\", \n" +#: lib\pwiki\WikiTxtCtrl.py:2208 lib\pwiki\WikiTxtCtrl.py:2247 +msgid "File does not exist" +msgstr "Файл не существует" + +#: lib\pwiki\WikiTxtCtrl.py:2214 +msgid "Are you sure you want to delete the file: %s" +msgstr "Вы уверены, что хотите удалить файл: %s" + +#: lib\pwiki\WikiTxtCtrl.py:2215 +msgid "Delete File" +msgstr "Удалить Файл" + +#: lib\pwiki\WikiTxtCtrl.py:2255 +msgid "Enter new name" +msgstr "Введите новое имя" + +#: lib\pwiki\WikiTxtCtrl.py:2256 +msgid "Rename File" +msgstr "Переименовать Файл" + +#: lib\pwiki\WikiTxtCtrl.py:2266 +msgid "Target is not a file" +msgstr "Целевой объект не является файлом" + +#: lib\pwiki\WikiTxtCtrl.py:2270 +msgid "Target file exists already. Overwrite?" +msgstr "Целевой файл уже существует. Заменить?" + +#: lib\pwiki\WikiTxtCtrl.py:2271 +msgid "Overwrite File" +msgstr "Заменить Файл" + +#: lib\pwiki\WikiTxtCtrl.py:2363 +msgid "Set in menu \"Wiki\", item \"Options...\", options page \"Security\", \n" "item \"Script security\" an appropriate value to execute a script." -msgstr "" -"Чтобы выполнить скрипт, установите соответствующее значение в меню \"Вики\", пункт \"Настройки...\", \n" +msgstr "Чтобы выполнить скрипт, установите соответствующее значение в меню \"Вики\", пункт \"Настройки...\", \n" "страница настроек \"Безопасность\", пункт \"Безопасность скриптов\"." -#: lib\pwiki\WikiTxtCtrl.py:2237 +#: lib\pwiki\WikiTxtCtrl.py:2366 msgid "Script execution disabled" msgstr "Выполнение скрипта запрещено" -#: lib\pwiki\WikiTxtCtrl.py:2309 -msgid "" -"\n" +#: lib\pwiki\WikiTxtCtrl.py:2438 +msgid "\n" "Exception: %s" -msgstr "" -"\n" +msgstr "\n" "Исключение: %s" -#: lib\pwiki\WikiTxtCtrl.py:3017 +#: lib\pwiki\WikiTxtCtrl.py:3146 msgid "No more fields in this 'form' page" msgstr "Больше нет полей на странице 'form'" -#: lib\pwiki\WikiTxtCtrl.py:3176 +#: lib\pwiki\WikiTxtCtrl.py:3315 msgid "Line: %d Col: %d Pos: %d" msgstr "Строка: %d Колонка: %d Позиция: %d" -#: lib\pwiki\WikiTxtCtrl.py:3352 +#: lib\pwiki\WikiTxtCtrl.py:3420 +msgid "Not a valid image" +msgstr "Неверное изображение" + +#: lib\pwiki\WikiTxtCtrl.py:3569 msgid "Couldn't copy file" msgstr "Ошибка копирования файла" -#: lib\pwiki\WikiTxtCtrl.py:3594 +#: lib\pwiki\WikiTxtCtrl.py:3813 msgid "Ignore" msgstr "Игнорировать" -#: lib\pwiki\WikiTxtCtrl.py:3595 +#: lib\pwiki\WikiTxtCtrl.py:3814 msgid "Add Globally" msgstr "Добавить глобально" -#: lib\pwiki\WikiTxtCtrl.py:3596 +#: lib\pwiki\WikiTxtCtrl.py:3815 msgid "Add Locally" msgstr "Добавить локально" -#: lib\pwiki\WikiTxtCtrl.py:3606 +#: lib\pwiki\WikiTxtCtrl.py:3825 msgid "Follow Link" msgstr "Открыть ссылку" -#: lib\pwiki\WikiTxtCtrl.py:3607 +#: lib\pwiki\WikiTxtCtrl.py:3826 msgid "Follow Link New Tab" msgstr "Открыть ссылку в новой вкладке" -#: lib\pwiki\WikiTxtCtrl.py:3608 +#: lib\pwiki\WikiTxtCtrl.py:3827 msgid "Follow Link New Tab Backgrd." msgstr "Открыть ссылку в новой вкладке на заднем фоне" -#: lib\pwiki\WikiTxtCtrl.py:3610 +#: lib\pwiki\WikiTxtCtrl.py:3829 msgid "Convert Absolute/Relative File URL" msgstr "Преобразовать Абсолютный/Относительный URL файла" -#: lib\pwiki\WikiTxtCtrl.py:3611 +#: lib\pwiki\WikiTxtCtrl.py:3830 msgid "Open Containing Folder" msgstr "Открыть папку" -#: lib\pwiki\WikiTxtCtrl.py:3613 +#: lib\pwiki\WikiTxtCtrl.py:3831 +msgid "Rename file" +msgstr "Переименовать файл" + +#: lib\pwiki\WikiTxtCtrl.py:3832 +msgid "Delete file" +msgstr "Удалить файл" + +#: lib\pwiki\WikiTxtCtrl.py:3834 msgid "Copy anchor URL to clipboard" msgstr "Копировать anchor URL в буфер" -#: lib\pwiki\WikiTxtCtrl.py:3615 +#: lib\pwiki\WikiTxtCtrl.py:3836 msgid "Other..." msgstr "Другие..." -#: lib\pwiki\WikiTxtCtrl.py:3616 +#: lib\pwiki\WikiTxtCtrl.py:3837 msgid "Use Template..." msgstr "Использовать шаблон..." -#: lib\pwiki\WikiTxtCtrl.py:3620 +#: lib\pwiki\WikiTxtCtrl.py:3841 msgid "Show folding" msgstr "Показать свёртку фрагментов" -#: lib\pwiki\WikiTxtCtrl.py:3621 +#: lib\pwiki\WikiTxtCtrl.py:3842 msgid "Show folding marks and allow folding" msgstr "Показать значки свёртки и разрешить свёртку фрагментов текста" -#: lib\pwiki\WikiTxtCtrl.py:3622 +#: lib\pwiki\WikiTxtCtrl.py:3843 msgid "&Toggle current folding" msgstr "&Свернуть/Развернуть текущий фрагмент" -#: lib\pwiki\WikiTxtCtrl.py:3623 +#: lib\pwiki\WikiTxtCtrl.py:3844 msgid "Toggle folding of the current line" msgstr "Свернуть/Развернуть фрагмент текста, соответствующего текущей строке" -#: lib\pwiki\WikiTxtCtrl.py:3624 +#: lib\pwiki\WikiTxtCtrl.py:3845 msgid "&Unfold All" msgstr "&Развернуть всё" -#: lib\pwiki\WikiTxtCtrl.py:3625 +#: lib\pwiki\WikiTxtCtrl.py:3846 msgid "Unfold everything in current editor" msgstr "Развернуть все фрагменты в текущем редакторе" -#: lib\pwiki\WikiTxtCtrl.py:3626 +#: lib\pwiki\WikiTxtCtrl.py:3847 msgid "&Fold All" msgstr "&Свернуть всё" -#: lib\pwiki\WikiTxtCtrl.py:3627 +#: lib\pwiki\WikiTxtCtrl.py:3848 msgid "Fold everything in current editor" msgstr "Свернуть все фрагменты в текущем редакторе" -#: lib\pwiki\WikiTxtDialogs.py:43 -msgid "Incremental search (ENTER/ESC to finish)" -msgstr "Пошаговый поиск (ENTER/ESC - остановка)" - #: lib\pwiki\WindowsHacks.py:317 msgid "Copying from %s to %s failed. SHFileOperation result no. %s" msgstr "Ошибка копирования из %s в %s. Код результата SHFileOperation %s" @@ -4915,8 +4808,7 @@ msgstr "Показать даты без соответствующих вики msgid "List dates ascending or descending" msgstr "Список дат в восходящем или нисходящем порядке" -#: lib\pwiki\timeView\Versioning.py:175 -#: lib\pwiki\timeView\Versioning.py:442 +#: lib\pwiki\timeView\Versioning.py:175 lib\pwiki\timeView\Versioning.py:442 #: lib\pwiki\timeView\Versioning.py:454 msgid "Versioning data damaged" msgstr "Данные о версиях повреждены" @@ -5012,7 +4904,7 @@ msgid "Database exists already and is currently in use" msgstr "База данных уже существует и сейчас используется" #: lib\pwiki\wikidata\WikiDataManager.py:69 -#: lib\pwiki\wikidata\WikiDataManager.py:2035 +#: lib\pwiki\wikidata\WikiDataManager.py:2052 msgid "Data handler %s not available" msgstr "Обработчик данных %s недоступен" @@ -5049,36 +4941,36 @@ msgstr "Ошибка при инициализации обработчика \" msgid "Required wiki language handler \"%s\" not available" msgstr "Требуемый обработчик синтаксиса \"%s\" не доступен" -#: lib\pwiki\wikidata\WikiDataManager.py:915 +#: lib\pwiki\wikidata\WikiDataManager.py:929 msgid "Word '%s' not in wiki" msgstr "Слова '%s' нет в вики" -#: lib\pwiki\wikidata\WikiDataManager.py:1220 -#: lib\pwiki\wikidata\WikiDataManager.py:1291 +#: lib\pwiki\wikidata\WikiDataManager.py:1234 +#: lib\pwiki\wikidata\WikiDataManager.py:1305 msgid "Update basic link info" msgstr "Обновление осн.информации о связях" -#: lib\pwiki\wikidata\WikiDataManager.py:1236 +#: lib\pwiki\wikidata\WikiDataManager.py:1250 msgid "Starting update thread" msgstr "Запуск процесса обновления" -#: lib\pwiki\wikidata\WikiDataManager.py:1311 +#: lib\pwiki\wikidata\WikiDataManager.py:1325 msgid "Update attributes of %s" msgstr "Обновление атрибутов: %s" -#: lib\pwiki\wikidata\WikiDataManager.py:1333 +#: lib\pwiki\wikidata\WikiDataManager.py:1347 msgid "Update syntax of %s" msgstr "Обновление синтаксиса: %s" -#: lib\pwiki\wikidata\WikiDataManager.py:1354 +#: lib\pwiki\wikidata\WikiDataManager.py:1368 msgid "Update index of %s" msgstr "Обновление индекса: %s" -#: lib\pwiki\wikidata\WikiDataManager.py:1374 +#: lib\pwiki\wikidata\WikiDataManager.py:1388 msgid "Final cleanup" msgstr "Окончательно прибираемся" -#: lib\pwiki\wikidata\WikiDataManager.py:1467 +#: lib\pwiki\wikidata\WikiDataManager.py:1481 msgid "Cannot rename '%s' to '%s', '%s' already exists" msgstr "Невозможно переименовать '%s' в '%s', '%s' уже существует" diff --git a/WikidPad_sv.po b/WikidPad_sv.po index 45ee36d0..524204b4 100644 --- a/WikidPad_sv.po +++ b/WikidPad_sv.po @@ -2,9 +2,9 @@ # This file is distributed under the same license as wikidPad. msgid "" msgstr "" -"Project-Id-Version: WikidPad 2.2beta03\n" +"Project-Id-Version: WikidPad 2.2beta04\n" "POT-Creation-Date: \n" -"PO-Revision-Date: 2011-05-14 13:00+0100\n" +"PO-Revision-Date: 2011-05-30 21:03+0100\n" "Last-Translator: Stefan Berg \n" "Language-Team: \n" "Language: \n" @@ -612,6 +612,10 @@ msgstr "Rubrikprefix:" msgid "Headings as aliases" msgstr "Rubriker som alias" +#: WikidPad.xrc:0 +msgid "Height:" +msgstr "Höjd:" + #: WikidPad.xrc:0 msgid "Hidden" msgstr "Dold" @@ -648,6 +652,10 @@ msgstr "Ignorera låsningsfil för wikin" msgid "Image pasting" msgstr "Bildinklistring" +#: WikidPad.xrc:0 +msgid "Image preview tooltips for local URLs" +msgstr "Verktygstips vid bildgranskning för lokala urler" + #: WikidPad.xrc:0 msgid "Import format:" msgstr "Importformat:" @@ -933,8 +941,8 @@ msgid "Preview" msgstr "Förhandsgranskning" #: WikidPad.xrc:0 -msgid "Preview renderer:" -msgstr "Förhandsgranskningsrenderare:" +msgid "Preview renderer*:" +msgstr "Förhandsgranskningsrenderare*:" #: WikidPad.xrc:0 msgid "Preview:" @@ -1324,6 +1332,10 @@ msgstr "Använd IME workaround för indata till redigeraren*" msgid "Use link title if present" msgstr "Använd länktitel när sådan finns" +#: WikidPad.xrc:0 +msgid "Use vi keys in Webkit preview" +msgstr "Använd vi-nycklar i webkit förhandsgranskning" + #: WikidPad.xrc:0 msgid "User notification:" msgstr "Användarmeddelande:" @@ -1360,6 +1372,10 @@ msgstr "När wikin är öppen" msgid "Whole wiki" msgstr "Hela wikin" +#: WikidPad.xrc:0 +msgid "Width:" +msgstr "Bredd:" + #: WikidPad.xrc:0 msgid "Wiki Search" msgstr "Wikisökning" @@ -1468,21 +1484,21 @@ msgstr "Källkatalog:" #: WikidPad.xrc:0 #: lib\pwiki\DiffGui.py:436 -#: lib\pwiki\PersonalWikiFrame.py:1320 -#: lib\pwiki\WikiTxtCtrl.py:3601 +#: lib\pwiki\PersonalWikiFrame.py:1338 +#: lib\pwiki\WikiTxtCtrl.py:3811 msgid "Copy" msgstr "Kopiera" #: WikidPad.xrc:0 #: lib\pwiki\DiffGui.py:437 -#: lib\pwiki\PersonalWikiFrame.py:1329 -#: lib\pwiki\WikiTxtCtrl.py:3604 +#: lib\pwiki\PersonalWikiFrame.py:1347 +#: lib\pwiki\WikiTxtCtrl.py:3814 msgid "Select All" msgstr "Markera allt" #: WikidPad.xrc:0 #: lib\pwiki\DiffGui.py:439 -#: lib\pwiki\WikiTxtCtrl.py:3618 +#: lib\pwiki\WikiTxtCtrl.py:3830 msgid "Close Tab" msgstr "Stäng flik" @@ -1499,10 +1515,10 @@ msgstr "Spara" #: lib\pwiki\FileCleanup.py:616 #: lib\pwiki\FileCleanup.py:686 #: lib\pwiki\FileCleanup.py:775 -#: lib\pwiki\MptImporterGui.py:170 #: lib\pwiki\MptImporterGui.py:172 +#: lib\pwiki\MptImporterGui.py:174 #: lib\pwiki\OptionsDialog.py:139 -#: lib\pwiki\OptionsDialog.py:885 +#: lib\pwiki\OptionsDialog.py:893 msgid "Default" msgstr "Standard" @@ -1511,7 +1527,7 @@ msgstr "Standard" #: lib\pwiki\FileCleanup.py:531 #: lib\pwiki\FileCleanup.py:687 #: lib\pwiki\FileCleanup.py:1265 -#: lib\pwiki\WikiTxtCtrl.py:3603 +#: lib\pwiki\WikiTxtCtrl.py:3813 msgid "Delete" msgstr "Radera" @@ -1523,70 +1539,70 @@ msgid "Collect" msgstr "Samla" #: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:170 #: lib\pwiki\MptImporterGui.py:172 +#: lib\pwiki\MptImporterGui.py:174 msgid "Yes" msgstr "Ja" #: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:171 +#: lib\pwiki\MptImporterGui.py:173 msgid "Overwrite" msgstr "Skriv över" #: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:171 -#: lib\pwiki\MptImporterGui.py:172 +#: lib\pwiki\MptImporterGui.py:173 +#: lib\pwiki\MptImporterGui.py:174 msgid "No" msgstr "Nej" #: WikidPad.xrc:0 -#: lib\pwiki\MptImporterGui.py:195 +#: lib\pwiki\MptImporterGui.py:197 msgid "Import" msgstr "Importera" #: WikidPad.xrc:0 -#: lib\pwiki\OptionsDialog.py:680 +#: lib\pwiki\OptionsDialog.py:688 msgid "Versioning" msgstr "Versionshantering" #: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1295 -#: lib\pwiki\WikiTxtCtrl.py:3598 +#: lib\pwiki\PersonalWikiFrame.py:1313 +#: lib\pwiki\WikiTxtCtrl.py:3808 msgid "Undo" msgstr "Ångra" #: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1299 -#: lib\pwiki\WikiTxtCtrl.py:3599 +#: lib\pwiki\PersonalWikiFrame.py:1317 +#: lib\pwiki\WikiTxtCtrl.py:3809 msgid "Redo" msgstr "Gör om" #: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1315 -#: lib\pwiki\WikiTxtCtrl.py:3600 +#: lib\pwiki\PersonalWikiFrame.py:1333 +#: lib\pwiki\WikiTxtCtrl.py:3810 msgid "Cut" msgstr "Klipp ut" #: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1324 -#: lib\pwiki\WikiTxtCtrl.py:3602 +#: lib\pwiki\PersonalWikiFrame.py:1342 +#: lib\pwiki\WikiTxtCtrl.py:3812 msgid "Paste" msgstr "Klistra in" #: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:1617 +#: lib\pwiki\PersonalWikiFrame.py:1635 msgid "&Delete" msgstr "Ra&dera" #: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:2031 -#: lib\pwiki\PersonalWikiFrame.py:2032 +#: lib\pwiki\PersonalWikiFrame.py:2049 +#: lib\pwiki\PersonalWikiFrame.py:2050 msgid "Open Wiki Word" msgstr "Öppna wikiord" #: WikidPad.xrc:0 -#: lib\pwiki\PersonalWikiFrame.py:2062 -#: lib\pwiki\PersonalWikiFrame.py:2063 +#: lib\pwiki\PersonalWikiFrame.py:2080 +#: lib\pwiki\PersonalWikiFrame.py:2081 msgid "Rename Wiki Word" msgstr "Byt namn på wikiord" @@ -1632,14 +1648,14 @@ msgid "Error starting WikidPad" msgstr "Fel vid uppstart av WikidPad" #: WikidPadStarter.py:207 -#: lib\pwiki\PersonalWikiFrame.py:5097 +#: lib\pwiki\PersonalWikiFrame.py:5136 #: lib\pwiki\SearchAndReplaceDialogs.py:724 #: lib\pwiki\SearchAndReplaceDialogs.py:1008 msgid "Error!" msgstr "Fel!" #: WikidPadStarter.py:214 -#: lib\pwiki\MptImporterGui.py:199 +#: lib\pwiki\MptImporterGui.py:201 msgid "Error" msgstr "Fel" @@ -1755,7 +1771,7 @@ msgstr "Exporterar %s" msgid "
[Allow evaluation of insertions in \"Options\", page \"Security\", option \"Process insertion scripts\"]
" msgstr "
[Tillåt evaluering av infogningsskript i \"Alternativ\", sidan \"Säkerhet\", alternativet \"Kör infogningsskript\"]
" -#: extensions\HtmlExporter.py:2151 +#: extensions\HtmlExporter.py:2202 msgid "[Unknown parser node with name \"%s\" found]" msgstr "[Okänd tolkningsnod med namnet \"%s\" hittades]" @@ -1787,7 +1803,7 @@ msgstr "Sökväg till Ploticus:" msgid "Output format:" msgstr "Visningsformat:" -#: extensions\WikidPadParserStub.py:121 +#: extensions\WikidPadParserStub.py:124 msgid "Footnotes as wiki words" msgstr "Fotnoter som wikiord" @@ -1816,17 +1832,17 @@ msgstr "*%s sidor hänvisar till* %s\n" msgid "*%s page(s) referred to by* %s\n" msgstr "*%s sidor hänvisade till av * %s\n" -#: extensions\wikidPadParser\WikidPadParser.py:1625 -#: extensions\wikidPadParser\WikidPadParser.py:1652 +#: extensions\wikidPadParser\WikidPadParser.py:1627 +#: extensions\wikidPadParser\WikidPadParser.py:1654 msgid "This is a footnote" msgstr "Detta är en fotnot" -#: extensions\wikidPadParser\WikidPadParser.py:1630 -#: extensions\wikidPadParser\WikidPadParser.py:1657 +#: extensions\wikidPadParser\WikidPadParser.py:1632 +#: extensions\wikidPadParser\WikidPadParser.py:1659 msgid "This is syntactically not a wiki word" msgstr "Detta är syntaktiskt inte ett wikiord" -#: extensions\wikidPadParser\WikidPadParser.py:2274 +#: extensions\wikidPadParser\WikidPadParser.py:2276 msgid "" "++ Wiki Settings\n" "\n" @@ -2066,12 +2082,12 @@ msgid "Destination must be a file" msgstr "Målet måste vara en fil" #: lib\pwiki\AdditionalDialogs.py:1241 -#: lib\pwiki\PersonalWikiFrame.py:4683 +#: lib\pwiki\PersonalWikiFrame.py:4722 msgid "Exporting" msgstr "Exporterar" #: lib\pwiki\AdditionalDialogs.py:1243 -#: lib\pwiki\PersonalWikiFrame.py:4685 +#: lib\pwiki\PersonalWikiFrame.py:4724 msgid "Preparing" msgstr "Förbereder" @@ -2080,7 +2096,7 @@ msgid "Error while exporting" msgstr "Fel vid export" #: lib\pwiki\AdditionalDialogs.py:1279 -#: lib\pwiki\PersonalWikiFrame.py:4626 +#: lib\pwiki\PersonalWikiFrame.py:4665 msgid "Select Export Directory" msgstr "Välj exportkatalog" @@ -2094,8 +2110,8 @@ msgid "Select Export File" msgstr "Välj exportfil" #: lib\pwiki\AdditionalDialogs.py:1325 -#: lib\pwiki\PersonalWikiFrame.py:4650 -#: lib\pwiki\PersonalWikiFrame.py:4666 +#: lib\pwiki\PersonalWikiFrame.py:4689 +#: lib\pwiki\PersonalWikiFrame.py:4705 #: lib\pwiki\Printing.py:183 msgid "No real wiki word selected as root" msgstr "Inget riktigt wikiord har valts som rot" @@ -2216,7 +2232,7 @@ msgid "" "
\n" " \n" " \n" -" \n" +" \n" " \n" " \n" "
Временной пояс (ничего, если пояс не существует).
%%Символ \"%\".
\\n" -"
\\nНовая строка.
\\\\Символ \"\\\".
 
Translations:
Chinese:yuxiaoxu@msn.com
Hungarian:Trk rpd
Hungarian:Török Árpád
Russian:Oleg Domanov
Swedish:Stefan Berg
\n" @@ -2282,7 +2298,7 @@ msgstr "" "\n" #: lib\pwiki\AdditionalDialogs.py:1904 -#: lib\pwiki\PersonalWikiFrame.py:1949 +#: lib\pwiki\PersonalWikiFrame.py:1967 msgid "About WikidPad" msgstr "Om WikidPad" @@ -2763,35 +2779,35 @@ msgstr "Wikisidan hittades inte, en ny sida skapas" msgid "History" msgstr "Historik" -#: lib\pwiki\DocPages.py:2210 +#: lib\pwiki\DocPages.py:2226 msgid "Func. tag %s does not exist" msgstr "Funkt. taggen %s finns inte" -#: lib\pwiki\DocPages.py:2471 +#: lib\pwiki\DocPages.py:2487 msgid "Global text blocks" msgstr "Globala textblock" -#: lib\pwiki\DocPages.py:2472 +#: lib\pwiki\DocPages.py:2488 msgid "Wiki text blocks" msgstr "Wiki textblock" -#: lib\pwiki\DocPages.py:2473 +#: lib\pwiki\DocPages.py:2489 msgid "Global spell list" msgstr "Global stavningslista" -#: lib\pwiki\DocPages.py:2474 +#: lib\pwiki\DocPages.py:2490 msgid "Wiki spell list" msgstr "Wiki stavningslista" -#: lib\pwiki\DocPages.py:2475 +#: lib\pwiki\DocPages.py:2491 msgid "Global cc. blacklist" msgstr "Global cc-svartlista" -#: lib\pwiki\DocPages.py:2476 +#: lib\pwiki\DocPages.py:2492 msgid "Wiki cc. blacklist" msgstr "Wikins cc-svartlista" -#: lib\pwiki\DocPages.py:2477 +#: lib\pwiki\DocPages.py:2493 msgid "Favorite wikis" msgstr "Favoritwikis" @@ -2828,7 +2844,7 @@ msgid "Path" msgstr "Sökväg" #: lib\pwiki\FileCleanup.py:555 -#: lib\pwiki\MptImporterGui.py:193 +#: lib\pwiki\MptImporterGui.py:195 msgid "Type" msgstr "Typ" @@ -2901,7 +2917,7 @@ msgstr "" #: lib\pwiki\MainApp.py:334 #: lib\pwiki\MainApp.py:357 #: lib\pwiki\MainApp.py:367 -#: lib\pwiki\PersonalWikiFrame.py:3080 +#: lib\pwiki\PersonalWikiFrame.py:3099 msgid "Continue?" msgstr "Fortsätt?" @@ -2931,46 +2947,46 @@ msgstr "" "Ett fel uppstod under denna session\n" "Se filen %s" -#: lib\pwiki\MainApp.py:786 +#: lib\pwiki\MainApp.py:796 msgid "Plugin options" msgstr "Insticksprogram" -#: lib\pwiki\MainApp.py:835 -#: lib\pwiki\OptionsDialog.py:681 +#: lib\pwiki\MainApp.py:845 +#: lib\pwiki\OptionsDialog.py:689 msgid "Wiki language" msgstr "Gränssnittsspråk" -#: lib\pwiki\MainApp.py:858 +#: lib\pwiki\MainApp.py:868 msgid "Plugins" msgstr "Insticksprogram" #: lib\pwiki\MainAreaPanel.py:665 -#: lib\pwiki\WikiTxtCtrl.py:2212 +#: lib\pwiki\WikiTxtCtrl.py:2342 msgid "This can only be done for the page of a wiki word" msgstr "Detta kan bara utföras på en sida i ett wikiord" #: lib\pwiki\MainAreaPanel.py:666 -#: lib\pwiki\WikiTxtCtrl.py:2213 +#: lib\pwiki\WikiTxtCtrl.py:2343 msgid "Not a wiki page" msgstr "Inte en wikisida" -#: lib\pwiki\MptImporterGui.py:174 +#: lib\pwiki\MptImporterGui.py:176 msgid "Wiki page" msgstr "Wikisida" -#: lib\pwiki\MptImporterGui.py:175 +#: lib\pwiki\MptImporterGui.py:177 msgid "Func. page" msgstr "Funktionssida" -#: lib\pwiki\MptImporterGui.py:176 +#: lib\pwiki\MptImporterGui.py:178 msgid "Saved search" msgstr "Sparad sökning" -#: lib\pwiki\MptImporterGui.py:194 +#: lib\pwiki\MptImporterGui.py:196 msgid "Name" msgstr "Namn" -#: lib\pwiki\MptImporterGui.py:196 +#: lib\pwiki\MptImporterGui.py:198 msgid "" "Version\n" "Import" @@ -2978,7 +2994,7 @@ msgstr "" "Versions-\n" "import" -#: lib\pwiki\MptImporterGui.py:197 +#: lib\pwiki\MptImporterGui.py:199 msgid "" "Rename\n" "imported" @@ -2986,7 +3002,7 @@ msgstr "" "Byt namn\n" "på importerat" -#: lib\pwiki\MptImporterGui.py:198 +#: lib\pwiki\MptImporterGui.py:200 msgid "" "Rename\n" "present" @@ -2994,132 +3010,132 @@ msgstr "" "Byt namn\n" "på befintligt" -#: lib\pwiki\MptImporterGui.py:383 +#: lib\pwiki\MptImporterGui.py:385 msgid "You can't rename imported and present item at the same time" msgstr "Du kan inte byta namn på importerad och befintlig post samtidigt" -#: lib\pwiki\MptImporterGui.py:396 +#: lib\pwiki\MptImporterGui.py:398 msgid "Rename imported" msgstr "Byt namn på importerat" -#: lib\pwiki\MptImporterGui.py:405 +#: lib\pwiki\MptImporterGui.py:407 msgid "Rename present" msgstr "Byt namn på befintligt" -#: lib\pwiki\MptImporterGui.py:416 -#: lib\pwiki\MptImporterGui.py:440 +#: lib\pwiki\MptImporterGui.py:418 +#: lib\pwiki\MptImporterGui.py:442 msgid "Name collision: Item '%s' will be imported already" msgstr "Namnkollision: Posten '%s' kommer att importeras" -#: lib\pwiki\MptImporterGui.py:422 -#: lib\pwiki\MptImporterGui.py:451 +#: lib\pwiki\MptImporterGui.py:424 +#: lib\pwiki\MptImporterGui.py:453 msgid "Name collision: Item '%s' will already be created by renaming '%s'" msgstr "Namnkonflikt: Posten '%s' kommer att skapas genom att döpa om '%s'" -#: lib\pwiki\MptImporterGui.py:431 -#: lib\pwiki\MptImporterGui.py:445 +#: lib\pwiki\MptImporterGui.py:433 +#: lib\pwiki\MptImporterGui.py:447 msgid "Name collision: Item '%s' exists already in database" msgstr "Namnkonflikt: : Posten '%s' finns redan i databasen" -#: lib\pwiki\OptionsDialog.py:661 +#: lib\pwiki\OptionsDialog.py:669 msgid "Application" msgstr "Applikation" -#: lib\pwiki\OptionsDialog.py:662 +#: lib\pwiki\OptionsDialog.py:670 msgid "User interface" msgstr "Användargränssnitt" -#: lib\pwiki\OptionsDialog.py:663 +#: lib\pwiki\OptionsDialog.py:671 msgid "Security" msgstr "Säkerhet" -#: lib\pwiki\OptionsDialog.py:664 +#: lib\pwiki\OptionsDialog.py:672 msgid "Tree" msgstr "Träd" -#: lib\pwiki\OptionsDialog.py:665 +#: lib\pwiki\OptionsDialog.py:673 msgid "HTML preview/export" msgstr "HTML förhandsgranskning/export" -#: lib\pwiki\OptionsDialog.py:666 +#: lib\pwiki\OptionsDialog.py:674 msgid "HTML header" msgstr "HTML header" -#: lib\pwiki\OptionsDialog.py:667 +#: lib\pwiki\OptionsDialog.py:675 msgid "Editor" msgstr "Redigerare" -#: lib\pwiki\OptionsDialog.py:668 +#: lib\pwiki\OptionsDialog.py:676 msgid "Editor Colors" msgstr " Redigerarfärger" -#: lib\pwiki\OptionsDialog.py:669 +#: lib\pwiki\OptionsDialog.py:677 msgid "Clipboard Catcher" msgstr "Urklippsfångare" -#: lib\pwiki\OptionsDialog.py:670 +#: lib\pwiki\OptionsDialog.py:678 msgid "File Launcher" msgstr "Filstartare" -#: lib\pwiki\OptionsDialog.py:671 +#: lib\pwiki\OptionsDialog.py:679 msgid "Mouse" msgstr "Mus" -#: lib\pwiki\OptionsDialog.py:672 +#: lib\pwiki\OptionsDialog.py:680 msgid "Chron. view" msgstr "Tidsvy" -#: lib\pwiki\OptionsDialog.py:673 +#: lib\pwiki\OptionsDialog.py:681 msgid "Searching" msgstr "Sökning" -#: lib\pwiki\OptionsDialog.py:674 -#: lib\pwiki\OptionsDialog.py:683 +#: lib\pwiki\OptionsDialog.py:682 +#: lib\pwiki\OptionsDialog.py:691 msgid "Advanced" msgstr "Avancerat" -#: lib\pwiki\OptionsDialog.py:675 +#: lib\pwiki\OptionsDialog.py:683 msgid "Timing" msgstr "Tider" -#: lib\pwiki\OptionsDialog.py:676 +#: lib\pwiki\OptionsDialog.py:684 msgid "Autosave" msgstr "Spara automatiskt" -#: lib\pwiki\OptionsDialog.py:678 +#: lib\pwiki\OptionsDialog.py:686 msgid "Current Wiki" msgstr "Aktuell wiki" -#: lib\pwiki\OptionsDialog.py:679 +#: lib\pwiki\OptionsDialog.py:687 msgid "Headings" msgstr "Rubriker" -#: lib\pwiki\OptionsDialog.py:823 +#: lib\pwiki\OptionsDialog.py:831 msgid "IE" msgstr "IE" -#: lib\pwiki\OptionsDialog.py:825 +#: lib\pwiki\OptionsDialog.py:833 msgid "Mozilla" msgstr "Mozilla" -#: lib\pwiki\OptionsDialog.py:829 +#: lib\pwiki\OptionsDialog.py:837 msgid "Webkit" msgstr "Webkit" -#: lib\pwiki\OptionsDialog.py:963 +#: lib\pwiki\OptionsDialog.py:971 msgid "Wave files (*.wav)|*.wav" msgstr "Wave-filer (*.wav)|*.wav" -#: lib\pwiki\OptionsDialog.py:974 -#: lib\pwiki\OptionsDialog.py:1285 +#: lib\pwiki\OptionsDialog.py:982 +#: lib\pwiki\OptionsDialog.py:1303 msgid "All files (*.*)|*" msgstr "Alla filer (*.*)|*" -#: lib\pwiki\OptionsDialog.py:1275 +#: lib\pwiki\OptionsDialog.py:1293 msgid "Select Directory" msgstr "Välj katalog" -#: lib\pwiki\OptionsDialog.py:1283 +#: lib\pwiki\OptionsDialog.py:1301 msgid "Select File" msgstr "Välj fil" @@ -3160,955 +3176,956 @@ msgstr "Radera föräldralösa filer och döda länkar" msgid "Bad formatted command line." msgstr "Felaktigt formaterad kommandorad" -#: lib\pwiki\PersonalWikiFrame.py:381 -#: lib\pwiki\PersonalWikiFrame.py:389 -#: lib\pwiki\PersonalWikiFrame.py:1190 +#: lib\pwiki\PersonalWikiFrame.py:399 +#: lib\pwiki\PersonalWikiFrame.py:407 +#: lib\pwiki\PersonalWikiFrame.py:1208 msgid "Wiki doesn't exist: %s" msgstr "Wikin finns inte: %s" -#: lib\pwiki\PersonalWikiFrame.py:751 +#: lib\pwiki\PersonalWikiFrame.py:769 msgid "&New" msgstr "&Ny" -#: lib\pwiki\PersonalWikiFrame.py:752 +#: lib\pwiki\PersonalWikiFrame.py:770 msgid "Create new wiki" msgstr "Skapa ny wiki" -#: lib\pwiki\PersonalWikiFrame.py:755 +#: lib\pwiki\PersonalWikiFrame.py:773 msgid "&Open" msgstr "Öppna" -#: lib\pwiki\PersonalWikiFrame.py:757 +#: lib\pwiki\PersonalWikiFrame.py:775 msgid "In &This Window..." msgstr "I de&tta fönster..." -#: lib\pwiki\PersonalWikiFrame.py:759 +#: lib\pwiki\PersonalWikiFrame.py:777 msgid "Open wiki in this window" msgstr "Öppna wiki i detta fönster" -#: lib\pwiki\PersonalWikiFrame.py:761 +#: lib\pwiki\PersonalWikiFrame.py:779 msgid "In &New Window..." msgstr "I &nytt fönster..." -#: lib\pwiki\PersonalWikiFrame.py:763 +#: lib\pwiki\PersonalWikiFrame.py:781 msgid "Open wiki in a new window" msgstr "Öppna wiki i nytt fönster" -#: lib\pwiki\PersonalWikiFrame.py:765 +#: lib\pwiki\PersonalWikiFrame.py:783 msgid "&Current in New Window" msgstr "Aktuell i nytt fönster" -#: lib\pwiki\PersonalWikiFrame.py:767 +#: lib\pwiki\PersonalWikiFrame.py:785 msgid "Create new window for same wiki" msgstr "Skapa nytt fönster för samma wiki" -#: lib\pwiki\PersonalWikiFrame.py:772 +#: lib\pwiki\PersonalWikiFrame.py:790 msgid "&Recent" msgstr "Tidiga&re" -#: lib\pwiki\PersonalWikiFrame.py:779 +#: lib\pwiki\PersonalWikiFrame.py:797 msgid "F&avorites" msgstr "F&avoriter" -#: lib\pwiki\PersonalWikiFrame.py:785 +#: lib\pwiki\PersonalWikiFrame.py:803 msgid "&Search Wiki..." msgstr "&Sök i wikin..." -#: lib\pwiki\PersonalWikiFrame.py:786 +#: lib\pwiki\PersonalWikiFrame.py:804 msgid "Search whole wiki" msgstr "Sök i hela wikin" -#: lib\pwiki\PersonalWikiFrame.py:794 +#: lib\pwiki\PersonalWikiFrame.py:812 msgid "Publish as HTML" msgstr "Publicera som HTML" -#: lib\pwiki\PersonalWikiFrame.py:797 +#: lib\pwiki\PersonalWikiFrame.py:815 msgid "Wiki as Single HTML Page" msgstr "Wiki som en HTML-sida" -#: lib\pwiki\PersonalWikiFrame.py:798 +#: lib\pwiki\PersonalWikiFrame.py:816 msgid "Publish Wiki as Single HTML Page" msgstr "Publicera wiki som en HTML-sida" -#: lib\pwiki\PersonalWikiFrame.py:802 +#: lib\pwiki\PersonalWikiFrame.py:820 msgid "Wiki as Set of HTML Pages" msgstr "Wiki som flera HTML-sidor" -#: lib\pwiki\PersonalWikiFrame.py:803 +#: lib\pwiki\PersonalWikiFrame.py:821 msgid "Publish Wiki as Set of HTML Pages" msgstr "Publicera wiki som flera HTML-sidor" -#: lib\pwiki\PersonalWikiFrame.py:807 +#: lib\pwiki\PersonalWikiFrame.py:825 msgid "Current Wiki Word as HTML Page" msgstr "Aktuellt wikiord som HTML-sida" -#: lib\pwiki\PersonalWikiFrame.py:808 +#: lib\pwiki\PersonalWikiFrame.py:826 msgid "Publish Current Wiki Word as HTML Page" msgstr "Publicera aktuellt wikiord som HTML-sida" -#: lib\pwiki\PersonalWikiFrame.py:812 +#: lib\pwiki\PersonalWikiFrame.py:830 msgid "Sub-Tree as Single HTML Page" msgstr "Underträd som en HTML-sida" -#: lib\pwiki\PersonalWikiFrame.py:813 +#: lib\pwiki\PersonalWikiFrame.py:831 msgid "Publish Sub-Tree as Single HTML Page" msgstr "Publicera underträd som en HTML-sida" -#: lib\pwiki\PersonalWikiFrame.py:817 +#: lib\pwiki\PersonalWikiFrame.py:835 msgid "Sub-Tree as Set of HTML Pages" msgstr "Underträd som flera HTML-sidor" -#: lib\pwiki\PersonalWikiFrame.py:818 +#: lib\pwiki\PersonalWikiFrame.py:836 msgid "Publish Sub-Tree as Set of HTML Pages" msgstr "Publicera underträd som flera HTML-sidor" -#: lib\pwiki\PersonalWikiFrame.py:826 +#: lib\pwiki\PersonalWikiFrame.py:844 msgid "Other Export..." msgstr "Annan export..." -#: lib\pwiki\PersonalWikiFrame.py:827 -#: lib\pwiki\PersonalWikiFrame.py:1856 +#: lib\pwiki\PersonalWikiFrame.py:845 +#: lib\pwiki\PersonalWikiFrame.py:1874 msgid "Open general export dialog" msgstr "Öppna huvudexportdialog" -#: lib\pwiki\PersonalWikiFrame.py:831 +#: lib\pwiki\PersonalWikiFrame.py:849 msgid "Print..." msgstr "Skriv ut..." -#: lib\pwiki\PersonalWikiFrame.py:832 +#: lib\pwiki\PersonalWikiFrame.py:850 msgid "Show the print dialog" msgstr "Visa utskriftsdialog" -#: lib\pwiki\PersonalWikiFrame.py:836 +#: lib\pwiki\PersonalWikiFrame.py:854 msgid "&Properties..." msgstr "Egenska&per..." -#: lib\pwiki\PersonalWikiFrame.py:837 +#: lib\pwiki\PersonalWikiFrame.py:855 msgid "Show general information about current wiki" msgstr "Visa allmän information om aktuell wiki" -#: lib\pwiki\PersonalWikiFrame.py:841 +#: lib\pwiki\PersonalWikiFrame.py:859 msgid "Maintenance" msgstr "Underhåll" -#: lib\pwiki\PersonalWikiFrame.py:845 +#: lib\pwiki\PersonalWikiFrame.py:863 msgid "&Rebuild Wiki..." msgstr "Bygg om wiki..." -#: lib\pwiki\PersonalWikiFrame.py:846 +#: lib\pwiki\PersonalWikiFrame.py:864 msgid "Rebuild this wiki and its cache completely" msgstr "Bygg om helt denna wiki och dess cache" -#: lib\pwiki\PersonalWikiFrame.py:860 +#: lib\pwiki\PersonalWikiFrame.py:878 msgid "&Update cache..." msgstr "Uppdatera cache..." -#: lib\pwiki\PersonalWikiFrame.py:861 +#: lib\pwiki\PersonalWikiFrame.py:879 msgid "Update cache where marked as not up to date" msgstr "Uppdatera cache där den är markerad som inaktuell" -#: lib\pwiki\PersonalWikiFrame.py:866 +#: lib\pwiki\PersonalWikiFrame.py:884 msgid "&Initiate update..." msgstr "&Initiera uppdatering..." -#: lib\pwiki\PersonalWikiFrame.py:867 +#: lib\pwiki\PersonalWikiFrame.py:885 msgid "Initiate full cache update which is done mainly in background" msgstr "Initiera full cacheuppdatering som huvudsakligen utförs i bakgrunden" -#: lib\pwiki\PersonalWikiFrame.py:887 +#: lib\pwiki\PersonalWikiFrame.py:905 msgid "Show job count..." msgstr "Visa antal jobb..." -#: lib\pwiki\PersonalWikiFrame.py:888 +#: lib\pwiki\PersonalWikiFrame.py:906 msgid "Show how many update jobs are waiting in background" msgstr "Visa hur många uppdateringsjobb som väntar i bakgrunden" -#: lib\pwiki\PersonalWikiFrame.py:893 +#: lib\pwiki\PersonalWikiFrame.py:911 msgid "Open as &Type..." msgstr "Öppna som &typ..." -#: lib\pwiki\PersonalWikiFrame.py:894 +#: lib\pwiki\PersonalWikiFrame.py:912 msgid "Open wiki with a specified wiki database type" msgstr "Öppna wiki med en specifik databastyp" -#: lib\pwiki\PersonalWikiFrame.py:898 +#: lib\pwiki\PersonalWikiFrame.py:916 msgid "Reconnect..." msgstr "Återanslut..." -#: lib\pwiki\PersonalWikiFrame.py:899 +#: lib\pwiki\PersonalWikiFrame.py:917 msgid "Reconnect to database after connection failure" msgstr "Återanslut till databasen efter misslyckad anslutning" -#: lib\pwiki\PersonalWikiFrame.py:905 +#: lib\pwiki\PersonalWikiFrame.py:923 msgid "&Optimise Database" msgstr "&Optimera databasen" -#: lib\pwiki\PersonalWikiFrame.py:906 +#: lib\pwiki\PersonalWikiFrame.py:924 msgid "Free unused space in database" msgstr "Ledigt oanvänt utrymme i databasen" -#: lib\pwiki\PersonalWikiFrame.py:913 +#: lib\pwiki\PersonalWikiFrame.py:931 msgid "&Copy .wiki files to database" msgstr "Kopiera .wiki-filer till databasen" -#: lib\pwiki\PersonalWikiFrame.py:914 +#: lib\pwiki\PersonalWikiFrame.py:932 msgid "Copy .wiki files to database" msgstr "Kopiera .wiki-filer till databasen" -#: lib\pwiki\PersonalWikiFrame.py:924 +#: lib\pwiki\PersonalWikiFrame.py:942 msgid "E&xit" msgstr "Avsluta" -#: lib\pwiki\PersonalWikiFrame.py:924 -#: lib\pwiki\PersonalWikiFrame.py:5663 +#: lib\pwiki\PersonalWikiFrame.py:942 +#: lib\pwiki\PersonalWikiFrame.py:5702 msgid "Exit" msgstr "Avsluta" -#: lib\pwiki\PersonalWikiFrame.py:1092 +#: lib\pwiki\PersonalWikiFrame.py:1110 msgid "Reread text blocks" msgstr "Läs om textblock" -#: lib\pwiki\PersonalWikiFrame.py:1093 +#: lib\pwiki\PersonalWikiFrame.py:1111 msgid "Reread the text block file(s) and recreate menu" msgstr "Läs om textblocksfiler och återskapa meny" -#: lib\pwiki\PersonalWikiFrame.py:1147 +#: lib\pwiki\PersonalWikiFrame.py:1165 msgid "Add wiki" msgstr "Lägg till wiki" -#: lib\pwiki\PersonalWikiFrame.py:1148 +#: lib\pwiki\PersonalWikiFrame.py:1166 msgid "Add a wiki to the favorites" msgstr "Lägg till en wiki till favoriter" -#: lib\pwiki\PersonalWikiFrame.py:1153 -#: lib\pwiki\PersonalWikiFrame.py:1154 +#: lib\pwiki\PersonalWikiFrame.py:1171 +#: lib\pwiki\PersonalWikiFrame.py:1172 msgid "Manage favorites" msgstr "Hantera favoriter" -#: lib\pwiki\PersonalWikiFrame.py:1176 -#: lib\pwiki\PersonalWikiFrame.py:1906 -#: lib\pwiki\PersonalWikiFrame.py:2424 -#: lib\pwiki\PersonalWikiFrame.py:4903 -#: lib\pwiki\PersonalWikiFrame.py:5240 +#: lib\pwiki\PersonalWikiFrame.py:1194 +#: lib\pwiki\PersonalWikiFrame.py:1924 +#: lib\pwiki\PersonalWikiFrame.py:2443 +#: lib\pwiki\PersonalWikiFrame.py:3856 +#: lib\pwiki\PersonalWikiFrame.py:4942 +#: lib\pwiki\PersonalWikiFrame.py:5279 msgid "Error while starting new WikidPad instance" msgstr "Fel när en ny instans av WikidPad skulle startas" -#: lib\pwiki\PersonalWikiFrame.py:1283 +#: lib\pwiki\PersonalWikiFrame.py:1301 msgid "W&iki" msgstr "W&iki" -#: lib\pwiki\PersonalWikiFrame.py:1294 +#: lib\pwiki\PersonalWikiFrame.py:1312 msgid "&Undo" msgstr "Ångra" -#: lib\pwiki\PersonalWikiFrame.py:1298 +#: lib\pwiki\PersonalWikiFrame.py:1316 msgid "&Redo" msgstr "Gö&r om" -#: lib\pwiki\PersonalWikiFrame.py:1306 +#: lib\pwiki\PersonalWikiFrame.py:1324 msgid "&Search and Replace..." msgstr "Sök och ersätt..." -#: lib\pwiki\PersonalWikiFrame.py:1308 +#: lib\pwiki\PersonalWikiFrame.py:1326 msgid "Search and replace inside current page" msgstr "Sök och ersätt på aktuell sida" -#: lib\pwiki\PersonalWikiFrame.py:1314 +#: lib\pwiki\PersonalWikiFrame.py:1332 msgid "Cu&t" msgstr "Klipp u&t" -#: lib\pwiki\PersonalWikiFrame.py:1319 +#: lib\pwiki\PersonalWikiFrame.py:1337 msgid "&Copy" msgstr "Kopiera" -#: lib\pwiki\PersonalWikiFrame.py:1323 +#: lib\pwiki\PersonalWikiFrame.py:1341 msgid "&Paste" msgstr "Klistra in" -#: lib\pwiki\PersonalWikiFrame.py:1328 +#: lib\pwiki\PersonalWikiFrame.py:1346 msgid "Select &All" msgstr "Markera &allt" -#: lib\pwiki\PersonalWikiFrame.py:1334 +#: lib\pwiki\PersonalWikiFrame.py:1352 msgid "Copy to Sc&ratchPad" msgstr "Kopiera till Sc&ratchPad" -#: lib\pwiki\PersonalWikiFrame.py:1336 +#: lib\pwiki\PersonalWikiFrame.py:1354 msgid "Copy selected text to ScratchPad" msgstr "Kopiera markerad text till ScratchPad" -#: lib\pwiki\PersonalWikiFrame.py:1342 +#: lib\pwiki\PersonalWikiFrame.py:1360 msgid "Paste T&extblock" msgstr "Lägg till t&extblock" -#: lib\pwiki\PersonalWikiFrame.py:1350 +#: lib\pwiki\PersonalWikiFrame.py:1368 msgid "C&lipboard Catcher" msgstr "Urk&lippsfångare" -#: lib\pwiki\PersonalWikiFrame.py:1353 -#: lib\pwiki\PersonalWikiFrame.py:4214 +#: lib\pwiki\PersonalWikiFrame.py:1371 +#: lib\pwiki\PersonalWikiFrame.py:4253 msgid "Set at Page" msgstr "På sida" -#: lib\pwiki\PersonalWikiFrame.py:1355 +#: lib\pwiki\PersonalWikiFrame.py:1373 msgid "Text copied to clipboard is also appended to this page" msgstr "Text kopierad till urklipp läggs även till på denna sida" -#: lib\pwiki\PersonalWikiFrame.py:1361 +#: lib\pwiki\PersonalWikiFrame.py:1379 msgid "Set at Cursor" msgstr "Vid markör" -#: lib\pwiki\PersonalWikiFrame.py:1363 +#: lib\pwiki\PersonalWikiFrame.py:1381 msgid "Text copied to clipboard is also added to cursor position" msgstr "Text kopierad till urklipp läggs även till vid markörens position" -#: lib\pwiki\PersonalWikiFrame.py:1369 +#: lib\pwiki\PersonalWikiFrame.py:1387 msgid "Set Off" msgstr "Av" -#: lib\pwiki\PersonalWikiFrame.py:1371 +#: lib\pwiki\PersonalWikiFrame.py:1389 msgid "Switch off clipboard catcher" msgstr "Stäng av urklippsfångaren" -#: lib\pwiki\PersonalWikiFrame.py:1381 +#: lib\pwiki\PersonalWikiFrame.py:1399 msgid "Spell Check..." msgstr "Stavningskontroll..." -#: lib\pwiki\PersonalWikiFrame.py:1383 +#: lib\pwiki\PersonalWikiFrame.py:1401 msgid "Spell check current and possibly further pages" msgstr "Stavningskontrollera aktuell och eventuellt ytterligare sidor" -#: lib\pwiki\PersonalWikiFrame.py:1387 +#: lib\pwiki\PersonalWikiFrame.py:1405 msgid "Spell Check While Type" msgstr "Stavningskontroll under skrivning" -#: lib\pwiki\PersonalWikiFrame.py:1388 +#: lib\pwiki\PersonalWikiFrame.py:1406 msgid "Set if editor should do spell checking during typing" msgstr "Ånge om redigeraren ska stavningskontrollera under skrivning" -#: lib\pwiki\PersonalWikiFrame.py:1393 +#: lib\pwiki\PersonalWikiFrame.py:1411 msgid "Clear Ignore List" msgstr "Rensa ignoreringslista" -#: lib\pwiki\PersonalWikiFrame.py:1395 +#: lib\pwiki\PersonalWikiFrame.py:1413 msgid "Clear the list of words to ignore for spell check while type" msgstr "Rensa listan över ord som ska ignoreras vid stavningskontroll under skrivning" -#: lib\pwiki\PersonalWikiFrame.py:1404 +#: lib\pwiki\PersonalWikiFrame.py:1422 msgid "&Insert" msgstr "&Infoga" -#: lib\pwiki\PersonalWikiFrame.py:1428 +#: lib\pwiki\PersonalWikiFrame.py:1446 msgid "&Settings" msgstr "In&ställningar" -#: lib\pwiki\PersonalWikiFrame.py:1431 +#: lib\pwiki\PersonalWikiFrame.py:1449 msgid "&Date Format..." msgstr "&Datumformat..." -#: lib\pwiki\PersonalWikiFrame.py:1432 +#: lib\pwiki\PersonalWikiFrame.py:1450 msgid "Set date format for inserting current date" msgstr "Ange datumformat för infogning av aktuellt datum" -#: lib\pwiki\PersonalWikiFrame.py:1436 +#: lib\pwiki\PersonalWikiFrame.py:1454 msgid "Auto-&Wrap" msgstr "Automatisk radbrytning" -#: lib\pwiki\PersonalWikiFrame.py:1437 +#: lib\pwiki\PersonalWikiFrame.py:1455 msgid "Set if editor should wrap long lines" msgstr "Ånge om redigeraren ska bryta långa rader" -#: lib\pwiki\PersonalWikiFrame.py:1451 +#: lib\pwiki\PersonalWikiFrame.py:1469 msgid "Auto-&Indent" msgstr "Automatiskt &indrag" -#: lib\pwiki\PersonalWikiFrame.py:1452 +#: lib\pwiki\PersonalWikiFrame.py:1470 msgid "Auto indentation" msgstr "Automatiskt indrag" -#: lib\pwiki\PersonalWikiFrame.py:1458 +#: lib\pwiki\PersonalWikiFrame.py:1476 msgid "Auto-&Bullets" msgstr "Automatisk punktlista" -#: lib\pwiki\PersonalWikiFrame.py:1459 +#: lib\pwiki\PersonalWikiFrame.py:1477 msgid "Show bullet on next line if current has one" msgstr "Visa punkt på nästa rad om den aktuella har en" -#: lib\pwiki\PersonalWikiFrame.py:1465 +#: lib\pwiki\PersonalWikiFrame.py:1483 msgid "Tabs to spaces" msgstr "Tabbar till mellanslag" -#: lib\pwiki\PersonalWikiFrame.py:1466 +#: lib\pwiki\PersonalWikiFrame.py:1484 msgid "Write spaces when hitting TAB key" msgstr "Skriv mellanslag när tab-tangenten används" -#: lib\pwiki\PersonalWikiFrame.py:1474 +#: lib\pwiki\PersonalWikiFrame.py:1492 msgid "Show T&oolbar" msgstr "Visa verktygsfält" -#: lib\pwiki\PersonalWikiFrame.py:1476 +#: lib\pwiki\PersonalWikiFrame.py:1494 msgid "Show toolbar" msgstr "Visa verktygsfält" -#: lib\pwiki\PersonalWikiFrame.py:1483 +#: lib\pwiki\PersonalWikiFrame.py:1501 msgid "Show &Tree View" msgstr "Visa &trädvy" -#: lib\pwiki\PersonalWikiFrame.py:1485 +#: lib\pwiki\PersonalWikiFrame.py:1503 msgid "Show Tree Control" msgstr "Visa trädfältet" -#: lib\pwiki\PersonalWikiFrame.py:1492 +#: lib\pwiki\PersonalWikiFrame.py:1510 msgid "Show &Chron. View" msgstr "Visa tidsvy" -#: lib\pwiki\PersonalWikiFrame.py:1494 +#: lib\pwiki\PersonalWikiFrame.py:1512 msgid "Show chronological view" msgstr "Visa tidsvy" -#: lib\pwiki\PersonalWikiFrame.py:1501 +#: lib\pwiki\PersonalWikiFrame.py:1519 msgid "Show &Page Structure" msgstr "Visa sidstruktur" -#: lib\pwiki\PersonalWikiFrame.py:1503 +#: lib\pwiki\PersonalWikiFrame.py:1521 msgid "Show structure (headings) of the page" msgstr "Visa sidans struktur (rubriker)" -#: lib\pwiki\PersonalWikiFrame.py:1515 +#: lib\pwiki\PersonalWikiFrame.py:1533 msgid "Show &Indentation Guides" msgstr "Visa &indragsmarkeringar" -#: lib\pwiki\PersonalWikiFrame.py:1516 +#: lib\pwiki\PersonalWikiFrame.py:1534 msgid "Show indentation guides in editor" msgstr "Visa indragsmarkeringar i redigeraren" -#: lib\pwiki\PersonalWikiFrame.py:1521 +#: lib\pwiki\PersonalWikiFrame.py:1539 msgid "Show Line &Numbers" msgstr "Visa rad&nummer" -#: lib\pwiki\PersonalWikiFrame.py:1522 +#: lib\pwiki\PersonalWikiFrame.py:1540 msgid "Show line numbers in editor" msgstr "Visa radnummer i redigeraren" -#: lib\pwiki\PersonalWikiFrame.py:1529 +#: lib\pwiki\PersonalWikiFrame.py:1547 msgid "Stay on Top" msgstr "Alltid överst" -#: lib\pwiki\PersonalWikiFrame.py:1531 +#: lib\pwiki\PersonalWikiFrame.py:1549 msgid "Stay on Top of all other windows" msgstr "Stanna överst av alla fönster" -#: lib\pwiki\PersonalWikiFrame.py:1539 +#: lib\pwiki\PersonalWikiFrame.py:1557 msgid "&Zoom In" msgstr "&Zooma in" -#: lib\pwiki\PersonalWikiFrame.py:1540 -#: lib\pwiki\PersonalWikiFrame.py:2101 +#: lib\pwiki\PersonalWikiFrame.py:1558 +#: lib\pwiki\PersonalWikiFrame.py:2119 msgid "Zoom In" msgstr "Zooma in" -#: lib\pwiki\PersonalWikiFrame.py:1543 +#: lib\pwiki\PersonalWikiFrame.py:1561 msgid "Zoo&m Out" msgstr "Zoo&ma ut" -#: lib\pwiki\PersonalWikiFrame.py:1544 -#: lib\pwiki\PersonalWikiFrame.py:2106 +#: lib\pwiki\PersonalWikiFrame.py:1562 +#: lib\pwiki\PersonalWikiFrame.py:2124 msgid "Zoom Out" msgstr "Zooma ut" -#: lib\pwiki\PersonalWikiFrame.py:1566 +#: lib\pwiki\PersonalWikiFrame.py:1584 msgid "Toggle Ed./Prev" msgstr "Växla Red./Förh." -#: lib\pwiki\PersonalWikiFrame.py:1568 -#: lib\pwiki\PersonalWikiFrame.py:2097 +#: lib\pwiki\PersonalWikiFrame.py:1586 +#: lib\pwiki\PersonalWikiFrame.py:2115 msgid "Switch between editor and preview" msgstr "Växla mellan redigerare och förhandsgranskning" -#: lib\pwiki\PersonalWikiFrame.py:1572 +#: lib\pwiki\PersonalWikiFrame.py:1590 msgid "Enter Edit Mode" msgstr "Gå till redigeringsläge" -#: lib\pwiki\PersonalWikiFrame.py:1573 +#: lib\pwiki\PersonalWikiFrame.py:1591 msgid "Show editor in tab" msgstr "Visa redigerare i flik" -#: lib\pwiki\PersonalWikiFrame.py:1577 +#: lib\pwiki\PersonalWikiFrame.py:1595 msgid "Enter Preview Mode" msgstr "Gå till förhandsgranskning " -#: lib\pwiki\PersonalWikiFrame.py:1579 +#: lib\pwiki\PersonalWikiFrame.py:1597 msgid "Show preview in tab" msgstr "Visa förhandsgranskning i flik" -#: lib\pwiki\PersonalWikiFrame.py:1603 +#: lib\pwiki\PersonalWikiFrame.py:1621 msgid "&Save" msgstr "&Spara" -#: lib\pwiki\PersonalWikiFrame.py:1604 +#: lib\pwiki\PersonalWikiFrame.py:1622 msgid "Save all open pages" msgstr "Spara alla öppna sidor" -#: lib\pwiki\PersonalWikiFrame.py:1611 +#: lib\pwiki\PersonalWikiFrame.py:1629 msgid "&Rename" msgstr "Byt namn" -#: lib\pwiki\PersonalWikiFrame.py:1612 +#: lib\pwiki\PersonalWikiFrame.py:1630 msgid "Rename current wiki word" msgstr "Byt namn på aktuellt wikiord" -#: lib\pwiki\PersonalWikiFrame.py:1618 +#: lib\pwiki\PersonalWikiFrame.py:1636 msgid "Delete current wiki word" msgstr "Radera aktuellt wikiord" -#: lib\pwiki\PersonalWikiFrame.py:1625 +#: lib\pwiki\PersonalWikiFrame.py:1643 msgid "Set as Roo&t" msgstr "Sätt som ro&t" -#: lib\pwiki\PersonalWikiFrame.py:1626 +#: lib\pwiki\PersonalWikiFrame.py:1644 msgid "Set current wiki word as tree root" msgstr "Sätt aktuellt wikiord som trädrot" -#: lib\pwiki\PersonalWikiFrame.py:1630 +#: lib\pwiki\PersonalWikiFrame.py:1648 msgid "R&eset Root" msgstr "Åt&erställ rot" -#: lib\pwiki\PersonalWikiFrame.py:1631 +#: lib\pwiki\PersonalWikiFrame.py:1649 msgid "Set home wiki word as tree root" msgstr "Sätt wikins huvudsida som trädrot" -#: lib\pwiki\PersonalWikiFrame.py:1635 +#: lib\pwiki\PersonalWikiFrame.py:1653 msgid "S&ynchronise Tree" msgstr "S&ynkronisera träd" -#: lib\pwiki\PersonalWikiFrame.py:1636 +#: lib\pwiki\PersonalWikiFrame.py:1654 msgid "Find the current wiki word in the tree" msgstr "Hitta det aktuella wikiord i trädet" -#: lib\pwiki\PersonalWikiFrame.py:1641 +#: lib\pwiki\PersonalWikiFrame.py:1659 msgid "&Follow Link" msgstr "Öppna länk" -#: lib\pwiki\PersonalWikiFrame.py:1642 +#: lib\pwiki\PersonalWikiFrame.py:1660 msgid "Activate link/word" msgstr "Aktivera länk/ord" -#: lib\pwiki\PersonalWikiFrame.py:1647 +#: lib\pwiki\PersonalWikiFrame.py:1665 msgid "Follow Link in &New Tab" msgstr "Öppna länk i ny flik" -#: lib\pwiki\PersonalWikiFrame.py:1648 +#: lib\pwiki\PersonalWikiFrame.py:1666 msgid "Activate link/word in new tab" msgstr "Aktivera länk/ord i ny flik" -#: lib\pwiki\PersonalWikiFrame.py:1653 +#: lib\pwiki\PersonalWikiFrame.py:1671 msgid "Copy &URL to Clipboard" msgstr "Kopiera &URL till urklipp" -#: lib\pwiki\PersonalWikiFrame.py:1655 +#: lib\pwiki\PersonalWikiFrame.py:1673 msgid "Copy full \"wiki:\" URL of the word to clipboard" msgstr "Kopiera ordets hela \"wiki:\" URL till urklipp" -#: lib\pwiki\PersonalWikiFrame.py:1661 +#: lib\pwiki\PersonalWikiFrame.py:1679 msgid "&Add version" msgstr "Lägg till version" -#: lib\pwiki\PersonalWikiFrame.py:1662 +#: lib\pwiki\PersonalWikiFrame.py:1680 msgid "Add new version" msgstr "Lägg till ny version" -#: lib\pwiki\PersonalWikiFrame.py:1670 +#: lib\pwiki\PersonalWikiFrame.py:1688 msgid "&Bold" msgstr "Fet" -#: lib\pwiki\PersonalWikiFrame.py:1671 -#: lib\pwiki\PersonalWikiFrame.py:2082 +#: lib\pwiki\PersonalWikiFrame.py:1689 +#: lib\pwiki\PersonalWikiFrame.py:2100 msgid "Bold" msgstr "Fet" -#: lib\pwiki\PersonalWikiFrame.py:1677 +#: lib\pwiki\PersonalWikiFrame.py:1695 msgid "&Italic" msgstr "Kurs&iv" -#: lib\pwiki\PersonalWikiFrame.py:1678 -#: lib\pwiki\PersonalWikiFrame.py:2088 +#: lib\pwiki\PersonalWikiFrame.py:1696 +#: lib\pwiki\PersonalWikiFrame.py:2106 msgid "Italic" msgstr "Kursiv" -#: lib\pwiki\PersonalWikiFrame.py:1684 +#: lib\pwiki\PersonalWikiFrame.py:1702 msgid "&Heading" msgstr "Rubrik" -#: lib\pwiki\PersonalWikiFrame.py:1685 +#: lib\pwiki\PersonalWikiFrame.py:1703 msgid "Add Heading" msgstr "Lägg till rubrik" -#: lib\pwiki\PersonalWikiFrame.py:1693 +#: lib\pwiki\PersonalWikiFrame.py:1711 msgid "&Rewrap Text" msgstr "Sammanfoga text" -#: lib\pwiki\PersonalWikiFrame.py:1695 +#: lib\pwiki\PersonalWikiFrame.py:1713 msgid "Rewrap Text" msgstr "Sammanfoga text" -#: lib\pwiki\PersonalWikiFrame.py:1700 +#: lib\pwiki\PersonalWikiFrame.py:1718 msgid "&Convert" msgstr "Konvertera" -#: lib\pwiki\PersonalWikiFrame.py:1703 +#: lib\pwiki\PersonalWikiFrame.py:1721 msgid "Selection to &Link" msgstr "Markering till &länk" -#: lib\pwiki\PersonalWikiFrame.py:1704 +#: lib\pwiki\PersonalWikiFrame.py:1722 msgid "Remove non-allowed characters and make sel. a wiki word link" msgstr "Radera otillåtna tecken och gör markering till ett länkat wikiord" -#: lib\pwiki\PersonalWikiFrame.py:1709 +#: lib\pwiki\PersonalWikiFrame.py:1727 msgid "Selection to &Wiki Word" msgstr "Markering till &wikiord" -#: lib\pwiki\PersonalWikiFrame.py:1711 +#: lib\pwiki\PersonalWikiFrame.py:1729 msgid "Put selected text in a new or existing wiki word" msgstr "Lägg den markerade texten i ett nytt eller existerande wikiord" -#: lib\pwiki\PersonalWikiFrame.py:1716 +#: lib\pwiki\PersonalWikiFrame.py:1734 msgid "Absolute/Relative &File URL" msgstr "Absolut/relativ sökväg till &fil" -#: lib\pwiki\PersonalWikiFrame.py:1718 +#: lib\pwiki\PersonalWikiFrame.py:1736 msgid "Convert file URL from absolute to relative and vice versa" msgstr "Konvertera sökväg till fil från absolut till relativ eller tvärtom" -#: lib\pwiki\PersonalWikiFrame.py:1732 +#: lib\pwiki\PersonalWikiFrame.py:1750 msgid "&Icon Name" msgstr "&Ikonnamn" -#: lib\pwiki\PersonalWikiFrame.py:1744 +#: lib\pwiki\PersonalWikiFrame.py:1762 msgid "&Color Name" msgstr "Färgnamn" -#: lib\pwiki\PersonalWikiFrame.py:1752 +#: lib\pwiki\PersonalWikiFrame.py:1770 msgid "&Add Attribute" msgstr "Lägg till &attribut" -#: lib\pwiki\PersonalWikiFrame.py:1761 +#: lib\pwiki\PersonalWikiFrame.py:1779 msgid "&Icon Attribute" msgstr "&Ikonattribut" -#: lib\pwiki\PersonalWikiFrame.py:1771 +#: lib\pwiki\PersonalWikiFrame.py:1789 msgid "&Color Attribute" msgstr "Färgattribut" -#: lib\pwiki\PersonalWikiFrame.py:1780 +#: lib\pwiki\PersonalWikiFrame.py:1798 msgid "&Back" msgstr "&Bakåt" -#: lib\pwiki\PersonalWikiFrame.py:1781 +#: lib\pwiki\PersonalWikiFrame.py:1799 msgid "Go backward" msgstr "Gå bakåt" -#: lib\pwiki\PersonalWikiFrame.py:1786 +#: lib\pwiki\PersonalWikiFrame.py:1804 msgid "&Forward" msgstr "&Framåt" -#: lib\pwiki\PersonalWikiFrame.py:1787 +#: lib\pwiki\PersonalWikiFrame.py:1805 msgid "Go forward" msgstr "Gå framåt" -#: lib\pwiki\PersonalWikiFrame.py:1792 +#: lib\pwiki\PersonalWikiFrame.py:1810 msgid "&Wiki Home" msgstr "&Wikins startsida" -#: lib\pwiki\PersonalWikiFrame.py:1793 +#: lib\pwiki\PersonalWikiFrame.py:1811 msgid "Go to wiki homepage" msgstr "Gå till wikins startsida" -#: lib\pwiki\PersonalWikiFrame.py:1799 +#: lib\pwiki\PersonalWikiFrame.py:1817 msgid "Up&ward" msgstr "&Uppåt" -#: lib\pwiki\PersonalWikiFrame.py:1801 -#: lib\pwiki\PersonalWikiFrame.py:2049 -#: lib\pwiki\PersonalWikiFrame.py:2050 +#: lib\pwiki\PersonalWikiFrame.py:1819 +#: lib\pwiki\PersonalWikiFrame.py:2067 +#: lib\pwiki\PersonalWikiFrame.py:2068 msgid "Go upward from a subpage" msgstr "Gå uppåt från en undersida" -#: lib\pwiki\PersonalWikiFrame.py:1806 +#: lib\pwiki\PersonalWikiFrame.py:1824 msgid "Go to &Page..." msgstr "Gå till sida..." -#: lib\pwiki\PersonalWikiFrame.py:1807 +#: lib\pwiki\PersonalWikiFrame.py:1825 msgid "Open wiki word" msgstr "Öppna wikiord" -#: lib\pwiki\PersonalWikiFrame.py:1812 +#: lib\pwiki\PersonalWikiFrame.py:1830 msgid "Go to P&arent..." msgstr "Lista föräldranoder..." -#: lib\pwiki\PersonalWikiFrame.py:1814 +#: lib\pwiki\PersonalWikiFrame.py:1832 msgid "List parents of current wiki word" msgstr "Visa föräldrar till aktuellt wikiord" -#: lib\pwiki\PersonalWikiFrame.py:1817 +#: lib\pwiki\PersonalWikiFrame.py:1835 msgid "List &Children..." msgstr "Lista barnnoder..." -#: lib\pwiki\PersonalWikiFrame.py:1819 +#: lib\pwiki\PersonalWikiFrame.py:1837 msgid "List children of current wiki word" msgstr "Visa barnnoder till aktuellt wikiord" -#: lib\pwiki\PersonalWikiFrame.py:1822 +#: lib\pwiki\PersonalWikiFrame.py:1840 msgid "List Pa&rentless Pages" msgstr "Lista föräldralösa sidor " -#: lib\pwiki\PersonalWikiFrame.py:1824 +#: lib\pwiki\PersonalWikiFrame.py:1842 msgid "List nodes with no parent relations" msgstr "Visa noder som saknar förälderrelationer" -#: lib\pwiki\PersonalWikiFrame.py:1829 +#: lib\pwiki\PersonalWikiFrame.py:1847 msgid "Show &History..." msgstr "Visa &historik..." -#: lib\pwiki\PersonalWikiFrame.py:1830 +#: lib\pwiki\PersonalWikiFrame.py:1848 msgid "View tab history" msgstr "Visa flikhistorik" -#: lib\pwiki\PersonalWikiFrame.py:1833 +#: lib\pwiki\PersonalWikiFrame.py:1851 msgid "&Up History..." msgstr "&Upp i historik..." -#: lib\pwiki\PersonalWikiFrame.py:1834 +#: lib\pwiki\PersonalWikiFrame.py:1852 msgid "Up in tab history" msgstr "Upp i flikhistorik" -#: lib\pwiki\PersonalWikiFrame.py:1837 +#: lib\pwiki\PersonalWikiFrame.py:1855 msgid "&Down History..." msgstr "Ner i historik..." -#: lib\pwiki\PersonalWikiFrame.py:1838 +#: lib\pwiki\PersonalWikiFrame.py:1856 msgid "Down in tab history" msgstr "Ner i flikhistorik" -#: lib\pwiki\PersonalWikiFrame.py:1843 +#: lib\pwiki\PersonalWikiFrame.py:1861 msgid "Add B&ookmark" msgstr "Lägg till b&okmärke" -#: lib\pwiki\PersonalWikiFrame.py:1844 +#: lib\pwiki\PersonalWikiFrame.py:1862 msgid "Add bookmark to page" msgstr "Lägg till bokmärke till sida" -#: lib\pwiki\PersonalWikiFrame.py:1848 +#: lib\pwiki\PersonalWikiFrame.py:1866 msgid "Go to &Bookmark..." msgstr "Gå till &bokmärke..." -#: lib\pwiki\PersonalWikiFrame.py:1849 +#: lib\pwiki\PersonalWikiFrame.py:1867 msgid "List bookmarks" msgstr "Lista bokmärken" -#: lib\pwiki\PersonalWikiFrame.py:1855 +#: lib\pwiki\PersonalWikiFrame.py:1873 msgid "&Export..." msgstr "Exportera..." -#: lib\pwiki\PersonalWikiFrame.py:1859 +#: lib\pwiki\PersonalWikiFrame.py:1877 msgid "&Continuous Export..." msgstr "Kontinuerlig export..." -#: lib\pwiki\PersonalWikiFrame.py:1860 +#: lib\pwiki\PersonalWikiFrame.py:1878 msgid "Open export dialog for continuous export of changes" msgstr "Öppna exportdialog för kontinuerlig export av ändringar" -#: lib\pwiki\PersonalWikiFrame.py:1866 +#: lib\pwiki\PersonalWikiFrame.py:1884 msgid "&Import..." msgstr "&Importera..." -#: lib\pwiki\PersonalWikiFrame.py:1867 +#: lib\pwiki\PersonalWikiFrame.py:1885 msgid "Import dialog" msgstr "Importdialog" -#: lib\pwiki\PersonalWikiFrame.py:1873 +#: lib\pwiki\PersonalWikiFrame.py:1891 msgid "Scripts" msgstr "Skript" -#: lib\pwiki\PersonalWikiFrame.py:1874 +#: lib\pwiki\PersonalWikiFrame.py:1892 msgid "Run scripts, evaluate expressions" msgstr "Kör skript, evaluera uttryck" -#: lib\pwiki\PersonalWikiFrame.py:1876 +#: lib\pwiki\PersonalWikiFrame.py:1894 msgid "&Eval" msgstr "&Evaluera" -#: lib\pwiki\PersonalWikiFrame.py:1877 +#: lib\pwiki\PersonalWikiFrame.py:1895 msgid "Evaluate script blocks" msgstr "Evaluera skriptblock" -#: lib\pwiki\PersonalWikiFrame.py:1882 +#: lib\pwiki\PersonalWikiFrame.py:1900 msgid "Run Function &%i\tCtrl-%i" msgstr "Kör funktion &%i\tCtrl-%i" -#: lib\pwiki\PersonalWikiFrame.py:1883 +#: lib\pwiki\PersonalWikiFrame.py:1901 msgid "Run script function %i" msgstr "Kör skriptfunktion %i" -#: lib\pwiki\PersonalWikiFrame.py:1888 +#: lib\pwiki\PersonalWikiFrame.py:1906 msgid "O&ptions..." msgstr "Alternativ..." -#: lib\pwiki\PersonalWikiFrame.py:1889 +#: lib\pwiki\PersonalWikiFrame.py:1907 msgid "Set options" msgstr "Ange alternativ" -#: lib\pwiki\PersonalWikiFrame.py:1910 +#: lib\pwiki\PersonalWikiFrame.py:1928 msgid "&Open help wiki" msgstr "Öppna hjälpwiki" -#: lib\pwiki\PersonalWikiFrame.py:1911 +#: lib\pwiki\PersonalWikiFrame.py:1929 msgid "Open WikidPadHelp, the help wiki" msgstr "Öppna WikidPadHelp, hjälpwikin" -#: lib\pwiki\PersonalWikiFrame.py:1920 +#: lib\pwiki\PersonalWikiFrame.py:1938 msgid "&Visit Homepage" msgstr "Besök webbplatsen" -#: lib\pwiki\PersonalWikiFrame.py:1921 +#: lib\pwiki\PersonalWikiFrame.py:1939 msgid "Visit wikidPad homepage" msgstr "Besök wikidPads webbplats" -#: lib\pwiki\PersonalWikiFrame.py:1931 +#: lib\pwiki\PersonalWikiFrame.py:1949 msgid "Show &License" msgstr "Visa &licens" -#: lib\pwiki\PersonalWikiFrame.py:1932 +#: lib\pwiki\PersonalWikiFrame.py:1950 msgid "Show license of WikidPad and used components" msgstr "Visa wikidPads licens och komponenter som används" -#: lib\pwiki\PersonalWikiFrame.py:1949 +#: lib\pwiki\PersonalWikiFrame.py:1967 msgid "&About" msgstr "Om" -#: lib\pwiki\PersonalWikiFrame.py:1953 +#: lib\pwiki\PersonalWikiFrame.py:1971 msgid "&Wiki" msgstr "&Wiki" -#: lib\pwiki\PersonalWikiFrame.py:1954 +#: lib\pwiki\PersonalWikiFrame.py:1972 msgid "&Edit" msgstr "R&edigera" -#: lib\pwiki\PersonalWikiFrame.py:1955 +#: lib\pwiki\PersonalWikiFrame.py:1973 msgid "&View" msgstr "&Visa" -#: lib\pwiki\PersonalWikiFrame.py:1956 +#: lib\pwiki\PersonalWikiFrame.py:1974 msgid "&Tabs" msgstr "Flikar" -#: lib\pwiki\PersonalWikiFrame.py:1957 +#: lib\pwiki\PersonalWikiFrame.py:1975 msgid "Wiki &Page" msgstr "Wikisida" -#: lib\pwiki\PersonalWikiFrame.py:1958 +#: lib\pwiki\PersonalWikiFrame.py:1976 msgid "&Format" msgstr "&Format" -#: lib\pwiki\PersonalWikiFrame.py:1959 +#: lib\pwiki\PersonalWikiFrame.py:1977 msgid "&Navigate" msgstr "&Navigera" -#: lib\pwiki\PersonalWikiFrame.py:1960 +#: lib\pwiki\PersonalWikiFrame.py:1978 msgid "E&xtra" msgstr "Tillbehör" -#: lib\pwiki\PersonalWikiFrame.py:1976 +#: lib\pwiki\PersonalWikiFrame.py:1994 msgid "Pl&ugins" msgstr "Insticksprogram" -#: lib\pwiki\PersonalWikiFrame.py:1982 +#: lib\pwiki\PersonalWikiFrame.py:2000 msgid "&Help" msgstr "&Hjälp" -#: lib\pwiki\PersonalWikiFrame.py:1984 +#: lib\pwiki\PersonalWikiFrame.py:2002 msgid "He&lp" msgstr "Hjä&lp" -#: lib\pwiki\PersonalWikiFrame.py:2010 -#: lib\pwiki\PersonalWikiFrame.py:2011 +#: lib\pwiki\PersonalWikiFrame.py:2028 +#: lib\pwiki\PersonalWikiFrame.py:2029 msgid "Back" msgstr "Bakåt" -#: lib\pwiki\PersonalWikiFrame.py:2016 -#: lib\pwiki\PersonalWikiFrame.py:2017 +#: lib\pwiki\PersonalWikiFrame.py:2034 +#: lib\pwiki\PersonalWikiFrame.py:2035 msgid "Forward" msgstr "Framåt" -#: lib\pwiki\PersonalWikiFrame.py:2022 -#: lib\pwiki\PersonalWikiFrame.py:2023 +#: lib\pwiki\PersonalWikiFrame.py:2040 +#: lib\pwiki\PersonalWikiFrame.py:2041 msgid "Wiki Home" msgstr "Wikins startsida" -#: lib\pwiki\PersonalWikiFrame.py:2037 -#: lib\pwiki\PersonalWikiFrame.py:2038 +#: lib\pwiki\PersonalWikiFrame.py:2055 +#: lib\pwiki\PersonalWikiFrame.py:2056 msgid "Search" msgstr "Sök" -#: lib\pwiki\PersonalWikiFrame.py:2043 -#: lib\pwiki\PersonalWikiFrame.py:2044 +#: lib\pwiki\PersonalWikiFrame.py:2061 +#: lib\pwiki\PersonalWikiFrame.py:2062 msgid "Find current word in tree" msgstr "Hitta aktuellt wikiord i träd" -#: lib\pwiki\PersonalWikiFrame.py:2053 -#: lib\pwiki\PersonalWikiFrame.py:2072 -#: lib\pwiki\PersonalWikiFrame.py:2092 +#: lib\pwiki\PersonalWikiFrame.py:2071 +#: lib\pwiki\PersonalWikiFrame.py:2090 +#: lib\pwiki\PersonalWikiFrame.py:2110 msgid "Separator" msgstr "Fältavskiljare" -#: lib\pwiki\PersonalWikiFrame.py:2057 -#: lib\pwiki\PersonalWikiFrame.py:2058 +#: lib\pwiki\PersonalWikiFrame.py:2075 +#: lib\pwiki\PersonalWikiFrame.py:2076 msgid "Save Wiki Word" msgstr "Spara wikiord" -#: lib\pwiki\PersonalWikiFrame.py:2068 -#: lib\pwiki\PersonalWikiFrame.py:2069 -#: lib\pwiki\PersonalWikiFrame.py:4359 +#: lib\pwiki\PersonalWikiFrame.py:2086 +#: lib\pwiki\PersonalWikiFrame.py:2087 +#: lib\pwiki\PersonalWikiFrame.py:4398 msgid "Delete Wiki Word" msgstr "Radera wikiord" -#: lib\pwiki\PersonalWikiFrame.py:2076 +#: lib\pwiki\PersonalWikiFrame.py:2094 msgid "Heading" msgstr "Rubrik" -#: lib\pwiki\PersonalWikiFrame.py:2096 +#: lib\pwiki\PersonalWikiFrame.py:2114 msgid "Switch Editor/Preview" msgstr "Växla redigerare/förhandsgranskning" -#: lib\pwiki\PersonalWikiFrame.py:2117 +#: lib\pwiki\PersonalWikiFrame.py:2135 msgid "Wikize Selected Word " msgstr "Wikifiera markerat ord " -#: lib\pwiki\PersonalWikiFrame.py:2118 +#: lib\pwiki\PersonalWikiFrame.py:2136 msgid "Wikize Selected Word" msgstr "Wikifiera markerat ord " -#: lib\pwiki\PersonalWikiFrame.py:2189 +#: lib\pwiki\PersonalWikiFrame.py:2208 msgid "Line: 9999 Col: 9999 Pos: 9999999988888" msgstr "Rad: 9999 Kol: 9999 Pos: 9999999988888" -#: lib\pwiki\PersonalWikiFrame.py:2361 +#: lib\pwiki\PersonalWikiFrame.py:2380 msgid "Regular expression error" msgstr "Fel i reguljärt uttryck" -#: lib\pwiki\PersonalWikiFrame.py:2370 +#: lib\pwiki\PersonalWikiFrame.py:2389 msgid "Are you sure you want to reconnect? You may lose some data by this process." msgstr "Är du säker på att du vill återansluta? Du kan förlora data genom denna åtgärd." -#: lib\pwiki\PersonalWikiFrame.py:2372 +#: lib\pwiki\PersonalWikiFrame.py:2391 msgid "Reconnect database" msgstr "Återanslut databas" -#: lib\pwiki\PersonalWikiFrame.py:2386 -#: lib\pwiki\PersonalWikiFrame.py:3440 +#: lib\pwiki\PersonalWikiFrame.py:2405 +#: lib\pwiki\PersonalWikiFrame.py:3466 msgid "Error while trying to reconnect:\n" msgstr "Fel vid försök att återansluta:\n" -#: lib\pwiki\PersonalWikiFrame.py:2388 +#: lib\pwiki\PersonalWikiFrame.py:2407 msgid "" "There was an error while reconnecting the database\n" "\n" @@ -4120,16 +4137,16 @@ msgstr "" "Vill du försöka igen?\n" "%s" -#: lib\pwiki\PersonalWikiFrame.py:2391 +#: lib\pwiki\PersonalWikiFrame.py:2410 msgid "Error reconnecting!" msgstr "Fel vid återanslutning!" -#: lib\pwiki\PersonalWikiFrame.py:2407 -#: lib\pwiki\PersonalWikiFrame.py:3487 +#: lib\pwiki\PersonalWikiFrame.py:2426 +#: lib\pwiki\PersonalWikiFrame.py:3513 msgid "Error while trying to write:\n" msgstr "Fel vid försök att skriva:\n" -#: lib\pwiki\PersonalWikiFrame.py:2409 +#: lib\pwiki\PersonalWikiFrame.py:2428 msgid "" "There was an error while writing to the database\n" "\n" @@ -4141,61 +4158,61 @@ msgstr "" "Vill du försöka igen?\n" "%s" -#: lib\pwiki\PersonalWikiFrame.py:2412 +#: lib\pwiki\PersonalWikiFrame.py:2431 msgid "Error writing!" msgstr "Fel vid skrivning!" -#: lib\pwiki\PersonalWikiFrame.py:2531 +#: lib\pwiki\PersonalWikiFrame.py:2550 msgid "There was an error loading the icons for the tree control." msgstr "Det uppstod ett fel när ikoner skulle laddas i trädfältet." -#: lib\pwiki\PersonalWikiFrame.py:2797 +#: lib\pwiki\PersonalWikiFrame.py:2816 msgid "No data handler available to create database." msgstr "Ingen datahanterare finns tillgänglig för att skapa databasen." -#: lib\pwiki\PersonalWikiFrame.py:2809 +#: lib\pwiki\PersonalWikiFrame.py:2828 msgid "A wiki already exists in '%s', overwrite? (This deletes everything in and below this directory!)" msgstr "En wiki finns redan i '%s', skriva över? (Detta raderar allt i och under denna katalog!)" -#: lib\pwiki\PersonalWikiFrame.py:2811 +#: lib\pwiki\PersonalWikiFrame.py:2830 msgid "Warning" msgstr "Varning" -#: lib\pwiki\PersonalWikiFrame.py:2852 +#: lib\pwiki\PersonalWikiFrame.py:2871 msgid "A wiki database already exists in this location, overwrite?" msgstr "En wikidatabas finns redan på denna plats, skriva över?" -#: lib\pwiki\PersonalWikiFrame.py:2854 +#: lib\pwiki\PersonalWikiFrame.py:2873 msgid "Wiki DB Exists" msgstr "Wikidatabasen finns" -#: lib\pwiki\PersonalWikiFrame.py:2865 +#: lib\pwiki\PersonalWikiFrame.py:2884 msgid "There was an error creating the wiki database." msgstr "Det uppstod ett fel då databasen skulle skapas." -#: lib\pwiki\PersonalWikiFrame.py:2934 -#: lib\pwiki\PersonalWikiFrame.py:2935 +#: lib\pwiki\PersonalWikiFrame.py:2953 +#: lib\pwiki\PersonalWikiFrame.py:2954 msgid "Choose database type" msgstr "Välj databastyp" -#: lib\pwiki\PersonalWikiFrame.py:2972 +#: lib\pwiki\PersonalWikiFrame.py:2991 msgid "Inaccessible or missing file: %s" msgstr "Oåtkomlig eller saknad fil: '%s'" -#: lib\pwiki\PersonalWikiFrame.py:3023 -#: lib\pwiki\PersonalWikiFrame.py:3108 +#: lib\pwiki\PersonalWikiFrame.py:3042 +#: lib\pwiki\PersonalWikiFrame.py:3127 msgid "Error connecting to database in '%s'" msgstr "Fel vid anslutning till databasen i '%s'" -#: lib\pwiki\PersonalWikiFrame.py:3028 +#: lib\pwiki\PersonalWikiFrame.py:3047 msgid "The wiki needs an update to work with this version of WikidPad. Older versions of WikidPad may be unable to read the wiki after an update." msgstr "Wikin behöver uppdateras för att fungera med denna version av WikidPad. Äldre versioner av WikidPad kan kanske inte läsa wikin efter en uppdatering." -#: lib\pwiki\PersonalWikiFrame.py:3031 +#: lib\pwiki\PersonalWikiFrame.py:3050 msgid "Update database?" msgstr "Uppdatera databasen?" -#: lib\pwiki\PersonalWikiFrame.py:3066 +#: lib\pwiki\PersonalWikiFrame.py:3085 msgid "" "Wiki '%s' is probably in use by different\n" "instance of WikidPad. Connect anyway (dangerous!)?" @@ -4203,11 +4220,11 @@ msgstr "" "Wikin '%s' används troligen av en annan\n" "instans av WikidPad. Fortsätta ändå (riskabelt!)?" -#: lib\pwiki\PersonalWikiFrame.py:3068 +#: lib\pwiki\PersonalWikiFrame.py:3087 msgid "Wiki already in use" msgstr "Wikin används redan" -#: lib\pwiki\PersonalWikiFrame.py:3077 +#: lib\pwiki\PersonalWikiFrame.py:3096 msgid "" "Configuration file '%s' is corrupted or missing.\n" "You may have to change some settings in configuration page \"Current Wiki\" and below which were lost." @@ -4215,11 +4232,11 @@ msgstr "" "Konfigurationsfilen '%s' är korrupt eller saknas.\n" "Du kan behöva ändra förlorade inställningar på konfigurationssidan \"Aktuell wiki\" och nedåt." -#: lib\pwiki\PersonalWikiFrame.py:3115 +#: lib\pwiki\PersonalWikiFrame.py:3134 msgid "Can't write to database '%s'" msgstr "Kan inte skriva till databasen '%s'" -#: lib\pwiki\PersonalWikiFrame.py:3324 +#: lib\pwiki\PersonalWikiFrame.py:3343 msgid "" "There is no (write-)access to underlying wiki\n" "Close anyway and loose possible changes?" @@ -4227,35 +4244,35 @@ msgstr "" "Det finns inte (skriv-)rättigheter till den underliggande wikin.\n" "Stänga ändå och förlora eventuella ändringar?" -#: lib\pwiki\PersonalWikiFrame.py:3326 +#: lib\pwiki\PersonalWikiFrame.py:3345 msgid "Close anyway" msgstr "Stäng ändå" -#: lib\pwiki\PersonalWikiFrame.py:3410 +#: lib\pwiki\PersonalWikiFrame.py:3436 msgid "This operation requires an open database" msgstr "Denna åtgärd kräver en öppen databas" -#: lib\pwiki\PersonalWikiFrame.py:3411 +#: lib\pwiki\PersonalWikiFrame.py:3437 msgid "No open database" msgstr "Ingen öppen databas" -#: lib\pwiki\PersonalWikiFrame.py:3423 +#: lib\pwiki\PersonalWikiFrame.py:3449 msgid "No connection to database. Try to reconnect?" msgstr "Ingen anslutning till databasen. Försöka att återansluta?" -#: lib\pwiki\PersonalWikiFrame.py:3424 +#: lib\pwiki\PersonalWikiFrame.py:3450 msgid "Reconnect database?" msgstr "Återansluta databasen?" -#: lib\pwiki\PersonalWikiFrame.py:3431 +#: lib\pwiki\PersonalWikiFrame.py:3457 msgid "Trying to reconnect database..." msgstr "Försöker att återansluta till databasen..." -#: lib\pwiki\PersonalWikiFrame.py:3443 +#: lib\pwiki\PersonalWikiFrame.py:3469 msgid "Error while reconnecting database" msgstr "Fel vid återanslutning till databasen" -#: lib\pwiki\PersonalWikiFrame.py:3468 +#: lib\pwiki\PersonalWikiFrame.py:3494 msgid "" "This operation needs write access to database\n" "Try to write?" @@ -4263,19 +4280,19 @@ msgstr "" "Denna åtgärd kräver skrivrättigheter i databasen\n" "Försöka att skriva?" -#: lib\pwiki\PersonalWikiFrame.py:3469 +#: lib\pwiki\PersonalWikiFrame.py:3495 msgid "Try writing?" msgstr "Försöka att skriva?" -#: lib\pwiki\PersonalWikiFrame.py:3476 +#: lib\pwiki\PersonalWikiFrame.py:3502 msgid "Trying to write to database..." msgstr "Försöker att skriva till databasen..." -#: lib\pwiki\PersonalWikiFrame.py:3490 +#: lib\pwiki\PersonalWikiFrame.py:3516 msgid "Error while writing to database" msgstr "Fel vid försök att skriva till databasen" -#: lib\pwiki\PersonalWikiFrame.py:3514 +#: lib\pwiki\PersonalWikiFrame.py:3540 msgid "" "Database connection error: %s.\n" "Try to re-establish, then run \"Wiki\"->\"Reconnect\"" @@ -4283,12 +4300,12 @@ msgstr "" "Fel i förbindelsen med databasen: %s.\n" "Försök att återställa, kör sedan \"Wiki/Underhåll\"->\"Återanslut\"" -#: lib\pwiki\PersonalWikiFrame.py:3516 -#: lib\pwiki\PersonalWikiFrame.py:3533 +#: lib\pwiki\PersonalWikiFrame.py:3542 +#: lib\pwiki\PersonalWikiFrame.py:3559 msgid "Connection lost" msgstr "Anslutning avbruten" -#: lib\pwiki\PersonalWikiFrame.py:3531 +#: lib\pwiki\PersonalWikiFrame.py:3557 msgid "" "No write access to database: %s.\n" " Try to re-establish, then run \"Wiki\"->\"Reconnect\"" @@ -4296,115 +4313,115 @@ msgstr "" "Inga skrivrättigheter till databas: %s.\n" " Försök att återställa och klicka sedan på \"Wiki/Underhåll\"->\"Återanslut\"" -#: lib\pwiki\PersonalWikiFrame.py:3550 +#: lib\pwiki\PersonalWikiFrame.py:3576 msgid "Trying to reconnect ..." msgstr "Försöker att återansluta..." -#: lib\pwiki\PersonalWikiFrame.py:3559 +#: lib\pwiki\PersonalWikiFrame.py:3585 msgid "Error while trying to reconnect:" msgstr "Fel vid försök att återansluta:" -#: lib\pwiki\PersonalWikiFrame.py:3803 +#: lib\pwiki\PersonalWikiFrame.py:3828 msgid "Couldn't start file" msgstr "Kunde inte starta fil" -#: lib\pwiki\PersonalWikiFrame.py:3818 +#: lib\pwiki\PersonalWikiFrame.py:3836 msgid "Couldn't open wiki: %s" msgstr "Kunde inte öppna wiki: %s" -#: lib\pwiki\PersonalWikiFrame.py:3848 +#: lib\pwiki\PersonalWikiFrame.py:3887 msgid "Mod.: %s" msgstr "Ändrad: %s" -#: lib\pwiki\PersonalWikiFrame.py:3849 +#: lib\pwiki\PersonalWikiFrame.py:3888 msgid "; Crea.: %s" msgstr "; Skapad: %s" -#: lib\pwiki\PersonalWikiFrame.py:3886 +#: lib\pwiki\PersonalWikiFrame.py:3925 msgid "Parent nodes of '%s'" msgstr "Föräldranoder till '%s'" -#: lib\pwiki\PersonalWikiFrame.py:3898 +#: lib\pwiki\PersonalWikiFrame.py:3937 msgid "Parentless nodes" msgstr "Föräldralösa noder" -#: lib\pwiki\PersonalWikiFrame.py:3910 +#: lib\pwiki\PersonalWikiFrame.py:3949 msgid "Child nodes of '%s'" msgstr "Barnnoder till '%s'" -#: lib\pwiki\PersonalWikiFrame.py:3923 +#: lib\pwiki\PersonalWikiFrame.py:3962 msgid "Bookmarks" msgstr "Bokmärken" -#: lib\pwiki\PersonalWikiFrame.py:4070 +#: lib\pwiki\PersonalWikiFrame.py:4109 msgid "Wiki: %s" msgstr "Wiki: %s" -#: lib\pwiki\PersonalWikiFrame.py:4209 +#: lib\pwiki\PersonalWikiFrame.py:4248 msgid "Set at Page: %s\t%s" msgstr "Ange på sida: %s\t%s" -#: lib\pwiki\PersonalWikiFrame.py:4225 +#: lib\pwiki\PersonalWikiFrame.py:4264 msgid "Error saving global configuration" msgstr "Fel då global konfiguration skulle sparas" -#: lib\pwiki\PersonalWikiFrame.py:4236 +#: lib\pwiki\PersonalWikiFrame.py:4275 msgid "Error saving current configuration" msgstr "Fel då aktuell konfiguration skulle sparas" -#: lib\pwiki\PersonalWikiFrame.py:4258 +#: lib\pwiki\PersonalWikiFrame.py:4297 msgid "No real wiki word selected to rename" msgstr "Inget riktigt wikiord har valts att döpas om" -#: lib\pwiki\PersonalWikiFrame.py:4262 +#: lib\pwiki\PersonalWikiFrame.py:4301 msgid "The scratch pad cannot be renamed." msgstr "ScratchPad kan inte byta namn" -#: lib\pwiki\PersonalWikiFrame.py:4286 +#: lib\pwiki\PersonalWikiFrame.py:4325 msgid "Description:" msgstr "Beskrivning:" -#: lib\pwiki\PersonalWikiFrame.py:4287 +#: lib\pwiki\PersonalWikiFrame.py:4326 msgid "Store new version" msgstr "Spara ny version" -#: lib\pwiki\PersonalWikiFrame.py:4301 +#: lib\pwiki\PersonalWikiFrame.py:4340 msgid "Do you want to delete all stored versions?" msgstr "Vill du radera alla sparade versioner?" -#: lib\pwiki\PersonalWikiFrame.py:4302 +#: lib\pwiki\PersonalWikiFrame.py:4341 msgid "Delete All Versions" msgstr "Radera alla versioner" -#: lib\pwiki\PersonalWikiFrame.py:4347 +#: lib\pwiki\PersonalWikiFrame.py:4386 msgid "The scratch pad cannot be deleted" msgstr "Scratchpad kan inte raderas" -#: lib\pwiki\PersonalWikiFrame.py:4351 +#: lib\pwiki\PersonalWikiFrame.py:4390 msgid "No real wiki word to delete" msgstr "Inte ett riktigt wikiord att radera" -#: lib\pwiki\PersonalWikiFrame.py:4358 +#: lib\pwiki\PersonalWikiFrame.py:4397 msgid "Are you sure you want to delete wiki word '%s'?" msgstr "Är du säker på att du vill radera wikiordet '%s'?" -#: lib\pwiki\PersonalWikiFrame.py:4389 +#: lib\pwiki\PersonalWikiFrame.py:4428 msgid "No real wiki word to modify" msgstr "Inte att riktigt wikiord att ändra" -#: lib\pwiki\PersonalWikiFrame.py:4405 +#: lib\pwiki\PersonalWikiFrame.py:4444 msgid "Replace text by WikiWord:" msgstr "Ersätt text med wikiord:" -#: lib\pwiki\PersonalWikiFrame.py:4406 +#: lib\pwiki\PersonalWikiFrame.py:4445 msgid "Replace by Wiki Word" msgstr "Ersätt med wikiord" -#: lib\pwiki\PersonalWikiFrame.py:4415 +#: lib\pwiki\PersonalWikiFrame.py:4454 msgid "'%s' is an invalid wiki word." msgstr "'%s' är ett ogiltigt wikiord" -#: lib\pwiki\PersonalWikiFrame.py:4430 +#: lib\pwiki\PersonalWikiFrame.py:4469 msgid "" "Wiki word %s exists already\n" "Would you like to append to the word?" @@ -4412,101 +4429,101 @@ msgstr "" "Wikiordet %s finns redan\n" "Vill du bifoga till det ordet?" -#: lib\pwiki\PersonalWikiFrame.py:4433 +#: lib\pwiki\PersonalWikiFrame.py:4472 msgid "Word exists" msgstr "Ordet finns redan" -#: lib\pwiki\PersonalWikiFrame.py:4691 +#: lib\pwiki\PersonalWikiFrame.py:4730 msgid "Error on export" msgstr "Fel vid export" -#: lib\pwiki\PersonalWikiFrame.py:4763 +#: lib\pwiki\PersonalWikiFrame.py:4802 msgid "Are you sure you want to start a full rebuild of wiki in background?" msgstr "Är du säker på att du vill bygga om hela wikin i bakgrunden?" -#: lib\pwiki\PersonalWikiFrame.py:4765 +#: lib\pwiki\PersonalWikiFrame.py:4804 msgid "Initiate update" msgstr "Initierar uppdatering" -#: lib\pwiki\PersonalWikiFrame.py:4772 -#: lib\pwiki\PersonalWikiFrame.py:4773 +#: lib\pwiki\PersonalWikiFrame.py:4811 +#: lib\pwiki\PersonalWikiFrame.py:4812 msgid " Initiating update " msgstr " Initierar uppdatering" -#: lib\pwiki\PersonalWikiFrame.py:4787 +#: lib\pwiki\PersonalWikiFrame.py:4826 msgid "Error initiating update" msgstr "Fel då uppdatering skulle påbörjas" -#: lib\pwiki\PersonalWikiFrame.py:4796 +#: lib\pwiki\PersonalWikiFrame.py:4835 msgid "Are you sure you want to rebuild this wiki? You may want to backup your data first!" msgstr "Är du säker på att du vill bygga om wikin? Du bör ta backup på dina datafiler först!" -#: lib\pwiki\PersonalWikiFrame.py:4798 +#: lib\pwiki\PersonalWikiFrame.py:4837 msgid "Rebuild wiki" msgstr "Bygg om wiki" -#: lib\pwiki\PersonalWikiFrame.py:4805 -#: lib\pwiki\PersonalWikiFrame.py:4806 +#: lib\pwiki\PersonalWikiFrame.py:4844 +#: lib\pwiki\PersonalWikiFrame.py:4845 msgid " Rebuilding wiki " msgstr " Bygger om wiki" -#: lib\pwiki\PersonalWikiFrame.py:4821 +#: lib\pwiki\PersonalWikiFrame.py:4860 msgid "Error rebuilding wiki" msgstr "Fel då wikin skulle byggas om" -#: lib\pwiki\PersonalWikiFrame.py:4913 +#: lib\pwiki\PersonalWikiFrame.py:4952 msgid "This could overwrite pages in the database. Continue?" msgstr "Detta kan skriva över sidor i databasen. Fortsätta?" -#: lib\pwiki\PersonalWikiFrame.py:4914 +#: lib\pwiki\PersonalWikiFrame.py:4953 msgid "Import pagefiles" msgstr "Importera sidfiler" -#: lib\pwiki\PersonalWikiFrame.py:5016 +#: lib\pwiki\PersonalWikiFrame.py:5055 msgid "No list of strings passed to \"listmcstr\" dialog" msgstr "Ingen lista över strängar skickades till \"listmcstr\"-dialogen" -#: lib\pwiki\PersonalWikiFrame.py:5039 +#: lib\pwiki\PersonalWikiFrame.py:5078 msgid "Unknown dialog type" msgstr "Okänd dialogtyp" -#: lib\pwiki\PersonalWikiFrame.py:5208 -#: lib\pwiki\PersonalWikiFrame.py:5226 -#: lib\pwiki\PersonalWikiFrame.py:5251 +#: lib\pwiki\PersonalWikiFrame.py:5247 +#: lib\pwiki\PersonalWikiFrame.py:5265 +#: lib\pwiki\PersonalWikiFrame.py:5290 msgid "Choose a Wiki to open" msgstr "Välj wiki att öppna" -#: lib\pwiki\PersonalWikiFrame.py:5265 +#: lib\pwiki\PersonalWikiFrame.py:5304 msgid "Name for new wiki (must be in the form of a WikiWord):" msgstr "Namn på ny wiki (måste vara i WikiOrd-format):" -#: lib\pwiki\PersonalWikiFrame.py:5266 +#: lib\pwiki\PersonalWikiFrame.py:5305 msgid "Create New Wiki" msgstr "Skapa ny wiki" -#: lib\pwiki\PersonalWikiFrame.py:5281 +#: lib\pwiki\PersonalWikiFrame.py:5320 msgid "Directory to store new wiki" msgstr "Katalog för att lagra ny wiki" -#: lib\pwiki\PersonalWikiFrame.py:5292 -#: lib\pwiki\wikidata\WikiDataManager.py:1406 -#: lib\pwiki\wikidata\WikiDataManager.py:1462 +#: lib\pwiki\PersonalWikiFrame.py:5331 +#: lib\pwiki\wikidata\WikiDataManager.py:1420 +#: lib\pwiki\wikidata\WikiDataManager.py:1476 msgid "'%s' is an invalid wiki word. %s" msgstr "'%s' är ett ogiltigt wikiord. %s" -#: lib\pwiki\PersonalWikiFrame.py:5596 +#: lib\pwiki\PersonalWikiFrame.py:5635 msgid "Clipboard Catcher at Cursor" msgstr "Urklippsfångare vid markör" -#: lib\pwiki\PersonalWikiFrame.py:5600 +#: lib\pwiki\PersonalWikiFrame.py:5639 msgid "Clipboard Catcher off" msgstr "Urklippsfångare av" -#: lib\pwiki\PersonalWikiFrame.py:5661 +#: lib\pwiki\PersonalWikiFrame.py:5700 msgid "Restore" msgstr "Återskapa" -#: lib\pwiki\PersonalWikiFrame.py:5662 +#: lib\pwiki\PersonalWikiFrame.py:5701 msgid "Save" msgstr "Spara" @@ -4686,29 +4703,35 @@ msgid "Multiple words rename to same word" msgstr "Omdöpning av flera ord till samma namn" #: lib\pwiki\WikiHtmlView.py:558 -#: lib\pwiki\WikiTxtCtrl.py:2159 +#: lib\pwiki\WikiTxtCtrl.py:2179 msgid "Folder does not exist" msgstr "Katalogen finns inte" #: lib\pwiki\WikiHtmlView.py:650 #: lib\pwiki\WikiHtmlViewIE.py:494 -#: lib\pwiki\WikiHtmlViewWK.py:486 +#: lib\pwiki\WikiHtmlViewWK.py:968 +#: lib\pwiki\WikiTxtCtrl.py:3355 msgid "Link to page: %s" msgstr "Länk till sida: %s" -#: lib\pwiki\WikiHtmlViewWK.py:358 +#: lib\pwiki\WikiHtmlViewWK.py:73 +#: lib\pwiki\WikiTxtDialogs.py:47 +msgid "Incremental search (ENTER/ESC to finish)" +msgstr "Inkrementell sökning (ENTER/ESC för att avsluta)" + +#: lib\pwiki\WikiHtmlViewWK.py:759 msgid "Open Link (External)" msgstr "Öppna länk (externt)" -#: lib\pwiki\WikiHtmlViewWK.py:361 +#: lib\pwiki\WikiHtmlViewWK.py:762 msgid "Follow &Link" msgstr "Öppna &länk" -#: lib\pwiki\WikiHtmlViewWK.py:373 +#: lib\pwiki\WikiHtmlViewWK.py:774 msgid "Follow Link in New &Tab" msgstr "Öppna länk i ny flik" -#: lib\pwiki\WikiHtmlViewWK.py:380 +#: lib\pwiki\WikiHtmlViewWK.py:781 msgid "Follow Link in New Back&ground Tab" msgstr "Öppna länk i ny bakgrundsflik" @@ -4760,19 +4783,52 @@ msgstr "Lägg till wikiord sist" msgid "Prepend Wiki Word" msgstr "Lägg till wikiord först" -#: lib\pwiki\WikiTxtCtrl.py:1252 +#: lib\pwiki\WikiTxtCtrl.py:1271 msgid "Select Template" msgstr "Välj mall" -#: lib\pwiki\WikiTxtCtrl.py:1254 +#: lib\pwiki\WikiTxtCtrl.py:1273 msgid "Select Template (deletes current content!)" msgstr "Välj mall (raderar aktuellt innehåll!)" -#: lib\pwiki\WikiTxtCtrl.py:1360 +#: lib\pwiki\WikiTxtCtrl.py:1379 msgid "Use Template" msgstr "Använd mall" -#: lib\pwiki\WikiTxtCtrl.py:2234 +#: lib\pwiki\WikiTxtCtrl.py:2209 +#: lib\pwiki\WikiTxtCtrl.py:2248 +msgid "File does not exist" +msgstr "Filen finns inte" + +#: lib\pwiki\WikiTxtCtrl.py:2215 +msgid "Are you sure you want to delete the file: %s" +msgstr "Är du säker på att du vill radera filen: %s" + +#: lib\pwiki\WikiTxtCtrl.py:2216 +msgid "Delete File" +msgstr "Radera fil" + +#: lib\pwiki\WikiTxtCtrl.py:2256 +msgid "Enter new name" +msgstr "Ange nytt namn" + +#: lib\pwiki\WikiTxtCtrl.py:2257 +msgid "Rename File" +msgstr "Byt namn på fil" + +#: lib\pwiki\WikiTxtCtrl.py:2267 +msgid "Target is not a file" +msgstr "Målet är inte en fil" + +#: lib\pwiki\WikiTxtCtrl.py:2271 +msgid "Target file exists already. Overwrite?" +msgstr "Målfilen finns redan. Skriva över?" + +#: lib\pwiki\WikiTxtCtrl.py:2272 +msgid "Overwrite File" +msgstr "Skriv över fil" + +#: lib\pwiki\WikiTxtCtrl.py:2364 msgid "" "Set in menu \"Wiki\", item \"Options...\", options page \"Security\", \n" "item \"Script security\" an appropriate value to execute a script." @@ -4780,11 +4836,11 @@ msgstr "" "Ange i \"Tillbehör/Alternativ...\", inställningssidan \"Säkerhet\", \n" "vid \"Skriptsäkerhet\", ett lämpligt alternativ för att köra skriptet." -#: lib\pwiki\WikiTxtCtrl.py:2237 +#: lib\pwiki\WikiTxtCtrl.py:2367 msgid "Script execution disabled" msgstr "Körning av skript avaktiverad" -#: lib\pwiki\WikiTxtCtrl.py:2309 +#: lib\pwiki\WikiTxtCtrl.py:2439 msgid "" "\n" "Exception: %s" @@ -4792,98 +4848,106 @@ msgstr "" "\n" "Undantag: %s" -#: lib\pwiki\WikiTxtCtrl.py:3017 +#: lib\pwiki\WikiTxtCtrl.py:3147 msgid "No more fields in this 'form' page" msgstr "Inge fler fält i denna formulärsida" -#: lib\pwiki\WikiTxtCtrl.py:3176 +#: lib\pwiki\WikiTxtCtrl.py:3306 msgid "Line: %d Col: %d Pos: %d" msgstr "Rad: %d Kol: %d Pos: %d" -#: lib\pwiki\WikiTxtCtrl.py:3352 +#: lib\pwiki\WikiTxtCtrl.py:3411 +msgid "Not a valid image" +msgstr "Inte en tillåten bild" + +#: lib\pwiki\WikiTxtCtrl.py:3560 msgid "Couldn't copy file" msgstr "Kunde inte kopiera fil" -#: lib\pwiki\WikiTxtCtrl.py:3594 +#: lib\pwiki\WikiTxtCtrl.py:3804 msgid "Ignore" msgstr "Ignorera" -#: lib\pwiki\WikiTxtCtrl.py:3595 +#: lib\pwiki\WikiTxtCtrl.py:3805 msgid "Add Globally" msgstr "Lägg till globalt" -#: lib\pwiki\WikiTxtCtrl.py:3596 +#: lib\pwiki\WikiTxtCtrl.py:3806 msgid "Add Locally" msgstr "Lägg till lokalt" -#: lib\pwiki\WikiTxtCtrl.py:3606 +#: lib\pwiki\WikiTxtCtrl.py:3816 msgid "Follow Link" msgstr "Öppna länk" -#: lib\pwiki\WikiTxtCtrl.py:3607 +#: lib\pwiki\WikiTxtCtrl.py:3817 msgid "Follow Link New Tab" msgstr "Öppna länk i ny flik" -#: lib\pwiki\WikiTxtCtrl.py:3608 +#: lib\pwiki\WikiTxtCtrl.py:3818 msgid "Follow Link New Tab Backgrd." msgstr "Öppna länk i ny fliks bakgrund" -#: lib\pwiki\WikiTxtCtrl.py:3610 +#: lib\pwiki\WikiTxtCtrl.py:3820 msgid "Convert Absolute/Relative File URL" msgstr "Konvertera absolut/relativ sökväg till fil" -#: lib\pwiki\WikiTxtCtrl.py:3611 +#: lib\pwiki\WikiTxtCtrl.py:3821 msgid "Open Containing Folder" msgstr "Öppna aktuell katalog" -#: lib\pwiki\WikiTxtCtrl.py:3613 +#: lib\pwiki\WikiTxtCtrl.py:3822 +msgid "Rename file" +msgstr "Byt namn på fil" + +#: lib\pwiki\WikiTxtCtrl.py:3823 +msgid "Delete file" +msgstr "Radera fil" + +#: lib\pwiki\WikiTxtCtrl.py:3825 msgid "Copy anchor URL to clipboard" msgstr "Kopiera sökväg till urklipp" -#: lib\pwiki\WikiTxtCtrl.py:3615 +#: lib\pwiki\WikiTxtCtrl.py:3827 msgid "Other..." msgstr "Annat..." -#: lib\pwiki\WikiTxtCtrl.py:3616 +#: lib\pwiki\WikiTxtCtrl.py:3828 msgid "Use Template..." msgstr "Använd mall..." -#: lib\pwiki\WikiTxtCtrl.py:3620 +#: lib\pwiki\WikiTxtCtrl.py:3832 msgid "Show folding" msgstr "Visa vikning" -#: lib\pwiki\WikiTxtCtrl.py:3621 +#: lib\pwiki\WikiTxtCtrl.py:3833 msgid "Show folding marks and allow folding" msgstr "Visa vikningsmarkeringar och tillåt vikning" -#: lib\pwiki\WikiTxtCtrl.py:3622 +#: lib\pwiki\WikiTxtCtrl.py:3834 msgid "&Toggle current folding" msgstr "Växla ak&tuell vikning" -#: lib\pwiki\WikiTxtCtrl.py:3623 +#: lib\pwiki\WikiTxtCtrl.py:3835 msgid "Toggle folding of the current line" msgstr "Växla vikning av den aktuella raden" -#: lib\pwiki\WikiTxtCtrl.py:3624 +#: lib\pwiki\WikiTxtCtrl.py:3836 msgid "&Unfold All" msgstr "Vik &ut allt" -#: lib\pwiki\WikiTxtCtrl.py:3625 +#: lib\pwiki\WikiTxtCtrl.py:3837 msgid "Unfold everything in current editor" msgstr "Vik ut allt i aktuell redigerare" -#: lib\pwiki\WikiTxtCtrl.py:3626 +#: lib\pwiki\WikiTxtCtrl.py:3838 msgid "&Fold All" msgstr "Vik in allt" -#: lib\pwiki\WikiTxtCtrl.py:3627 +#: lib\pwiki\WikiTxtCtrl.py:3839 msgid "Fold everything in current editor" msgstr "Vik in allt i aktuell redigerare" -#: lib\pwiki\WikiTxtDialogs.py:43 -msgid "Incremental search (ENTER/ESC to finish)" -msgstr "Inkrementell sökning (ENTER/ESC för att avsluta)" - #: lib\pwiki\WindowsHacks.py:317 msgid "Copying from %s to %s failed. SHFileOperation result no. %s" msgstr "Kopiering från %s till %s misslyckades. SHFileOperation resultat nr. %s" @@ -5009,7 +5073,7 @@ msgid "Database exists already and is currently in use" msgstr "Databasen finns redan och används nu" #: lib\pwiki\wikidata\WikiDataManager.py:69 -#: lib\pwiki\wikidata\WikiDataManager.py:2035 +#: lib\pwiki\wikidata\WikiDataManager.py:2052 msgid "Data handler %s not available" msgstr "Datahanteraren %s är inte tillgänglig" @@ -5046,36 +5110,36 @@ msgstr "Fel vid anslutning till databasen \"%s\"" msgid "Required wiki language handler \"%s\" not available" msgstr "Den begärda wikispråkhanteraren \"%s\" är inte tillgänglig" -#: lib\pwiki\wikidata\WikiDataManager.py:915 +#: lib\pwiki\wikidata\WikiDataManager.py:929 msgid "Word '%s' not in wiki" msgstr "Ordet '%s' finns inte i wikin" -#: lib\pwiki\wikidata\WikiDataManager.py:1220 -#: lib\pwiki\wikidata\WikiDataManager.py:1291 +#: lib\pwiki\wikidata\WikiDataManager.py:1234 +#: lib\pwiki\wikidata\WikiDataManager.py:1305 msgid "Update basic link info" msgstr "Uppdaterar grundläggande länkinformation" -#: lib\pwiki\wikidata\WikiDataManager.py:1236 +#: lib\pwiki\wikidata\WikiDataManager.py:1250 msgid "Starting update thread" msgstr "Startar uppdateringstråd" -#: lib\pwiki\wikidata\WikiDataManager.py:1311 +#: lib\pwiki\wikidata\WikiDataManager.py:1325 msgid "Update attributes of %s" msgstr "Uppdatera %s attribut" -#: lib\pwiki\wikidata\WikiDataManager.py:1333 +#: lib\pwiki\wikidata\WikiDataManager.py:1347 msgid "Update syntax of %s" msgstr "Uppdatera syntaxen för %s" -#: lib\pwiki\wikidata\WikiDataManager.py:1354 +#: lib\pwiki\wikidata\WikiDataManager.py:1368 msgid "Update index of %s" msgstr "Uppdatera index för %s" -#: lib\pwiki\wikidata\WikiDataManager.py:1374 +#: lib\pwiki\wikidata\WikiDataManager.py:1388 msgid "Final cleanup" msgstr "Städar upp" -#: lib\pwiki\wikidata\WikiDataManager.py:1467 +#: lib\pwiki\wikidata\WikiDataManager.py:1481 msgid "Cannot rename '%s' to '%s', '%s' already exists" msgstr "Kan inte byta namn på '%s' till '%s', '%s' finns redan" diff --git a/docs/dev-setup.txt b/docs/dev-setup.txt index b6a0c95a..98aff97e 100644 --- a/docs/dev-setup.txt +++ b/docs/dev-setup.txt @@ -2,8 +2,8 @@ Sorry, these instructions are very Windows specific. --- Setup for dev --- -Install Python (recommended: 2.6, should also work: 2.5, may work: 2.7, - will not work: 2.4 or less, 3.0 or later) +Install Python (recommended: 2.6, may work: 2.7, + will not work: 2.5 or less, 3.0 or later) http://www.python.org/ @@ -81,7 +81,7 @@ The "dist" directory contains the final product, "build" contains intermediate cache files and is not needed for the actual program. ---- To create a WikidPad.exe distribution --- +--- To create a WikidPad.exe Windows binary installer --- Modify version number and version string of WikidPad (additionally to above adjustments) in wikidpad_unicode.iss. Find diff --git a/extensions/HtmlExporter.py b/extensions/HtmlExporter.py index 5c07154b..0474aaa4 100644 --- a/extensions/HtmlExporter.py +++ b/extensions/HtmlExporter.py @@ -445,7 +445,8 @@ def export(self, wikiDocument, wordList, exportType, exportDest, elif exportType == u"html_single": browserFile = self._exportHtmlSingleFiles(self.wordList) - # Other supported types: html_previewWX, html_previewIE, html_previewMOZ + # Other supported types: html_previewWX, html_previewIE, html_previewMOZ, + # html_previewWK # are not handled in this function wx.GetApp().getInsertionPluginManager().taskEnd() @@ -1510,6 +1511,55 @@ def _actualProcessInsertion(self, fullContent, astNode): searchOp.setPackedSettings(datablock) searchOp.replaceOp = False wordList = self.wikiDocument.searchWiki(searchOp) + elif key == u"search": + searchOp = SearchReplaceOperation() + searchOp.replaceOp = False + searchOp.searchStr = value + + searchType = Consts.SEARCHTYPE_BOOLEANREGEX + removeSelf = False + # Based on + # SearchAndReplaceDialogs.SearchWikiDialog._buildSearchReplaceOperation + for ap in appendices: + if ap == "type boolean" or ap == "type bool": + searchType = Consts.SEARCHTYPE_BOOLEANREGEX + elif ap == "type regex": + searchType = Consts.SEARCHTYPE_REGEX + elif ap == "type asis" or ap == "type plain": + searchType = Consts.SEARCHTYPE_ASIS + elif ap == "type index" and \ + self.wikiDocument.isSearchIndexEnabled(): + searchType = Consts.SEARCHTYPE_INDEX + elif ap == "casesensitive": + searchOp.caseSensitive = True + elif ap == "wholewordsonly": + searchOp.wholeWord = True + elif ap == "removeself" or ap == "removethis": + removeSelf = True + + searchOp.booleanOp = searchType == Consts.SEARCHTYPE_BOOLEANREGEX + searchOp.indexSearch = 'no' if searchType != Consts.SEARCHTYPE_INDEX \ + else 'default' + searchOp.wildCard = 'regex' if searchType != Consts.SEARCHTYPE_ASIS \ + else 'no' + + wordList = self.wikiDocument.searchWiki(searchOp) + + # Because a simple search for "foo" includes the page containing + # the insertion searching for "foo" there is option "removeself" + # to remove the page containing the insertion from the list + if removeSelf and self.optsStack["innermostPageUnifName"]\ + .startswith(u"wikipage/"): + + selfPageName = self.wikiDocument.getUnAliasedWikiWordOrAsIs( + self.optsStack["innermostPageUnifName"][9:]) + try: + wordList.remove(selfPageName) + except ValueError: + pass + + + elif key == u"toc" and value == u"": pageAst = self.getBasePageAst() # pageAst = self.optsStack["innermostFullPageAst"] diff --git a/extensions/KeyBindings.py b/extensions/KeyBindings.py index 6b28550f..c9d243d8 100644 --- a/extensions/KeyBindings.py +++ b/extensions/KeyBindings.py @@ -13,6 +13,7 @@ OpenWikiWord="Ctrl-O" Save="Ctrl-S" Print="Ctrl-P" +OpenTrashcan="" Rename="Ctrl-Alt-R" Delete="Ctrl-D" AddBookmark="Ctrl-Alt-B" @@ -38,6 +39,7 @@ ViewHistory="Ctrl-H" ClipboardCopyUrlToCurrentWikiword="" AddVersion="" +TogglePageReadOnly="" SetAsRoot="Ctrl-Shift-Q" ResetRoot="" UpHistory="Ctrl-Alt-Up" diff --git a/extensions/wikidPadParser/WikidPadParser.py b/extensions/wikidPadParser/WikidPadParser.py index 85afa639..842fe69a 100644 --- a/extensions/wikidPadParser/WikidPadParser.py +++ b/extensions/wikidPadParser/WikidPadParser.py @@ -2,7 +2,7 @@ ## _prof = hotshot.Profile("hotshot.prf") # Official parser plugin for wiki language "WikidPad default 2.0" -# Last modified (format YYYY-MM-DD): 2011-06-04 +# Last modified (format YYYY-MM-DD): 2011-06-22 import locale, pprint, time, sys, string, traceback @@ -642,11 +642,11 @@ def actionPreHtmlTag(s, l, st, t): ur"]+)") -UrlPAT = ur'(?:(?:https?|ftp|rel)://|mailto:|Outlook:\S|wiki:/|file:/)'\ +UrlPAT = ur'(?:(?:https?|ftp|rel|wikirel)://|mailto:|Outlook:\S|wiki:/|file:/)'\ ur'(?:(?![.,;:!?)]+(?:["\s]|$))[^"\s|\]<>])*' -UrlInBracketsPAT = ur'(?:(?:https?|ftp|rel)://|mailto:|Outlook:\S|wiki:/|file:/)'\ +UrlInBracketsPAT = ur'(?:(?:https?|ftp|rel|wikirel)://|mailto:|Outlook:\S|wiki:/|file:/)'\ ur'(?:(?![ \t]+[|\]])(?: |[^"\s|\]<>]))*' @@ -699,12 +699,17 @@ def resolveWikiWordLink(link, basePage): def actionWikiWordNcc(s, l, st, t): t.wikiWord = t.findFlatByName("word") if t.wikiWord is not None: + wikiFormatDetails = st.dictStack["wikiFormatDetails"] + t.wikiWord = resolveWikiWordLink(t.wikiWord.getString(), st.dictStack["wikiFormatDetails"].basePage) if t.wikiWord == u"": raise ParseException(s, l, "Subpage resolution of wikiword failed") + if t.wikiWord in wikiFormatDetails.wikiDocument.getNccWordBlacklist(): + raise ParseException(s, l, "Non-CamelCase word is in blacklist") + t.titleNode = t.findFlatByName("title") t.fragmentNode = t.findFlatByName("searchFragment") @@ -1880,21 +1885,29 @@ def createRelativeLinkFromWikiWord(word, baseWord, downwardOnly=True): return relPath.getLinkCore() @staticmethod - def createUrlLinkFromPath(wikiDocument, path, relative=False, bracketed=False): + def createUrlLinkFromPath(wikiDocument, path, relative=False, + bracketed=False, protocol=None): if bracketed: addSafe = ' ' else: addSafe = '' - if relative: + if relative: url = wikiDocument.makeAbsPathRelUrl(path, addSafe=addSafe) if url is None: # Relative not possible -> absolute instead + relative = False + else: + if protocol == "wiki": + url = u"wiki" + url # Combines to "wikirel://" + + if not relative: + if protocol == "wiki": + url = u"wiki:" + urlFromPathname(path, addSafe=addSafe) + else: url = u"file:" + urlFromPathname(path, addSafe=addSafe) - else: - url = u"file:" + urlFromPathname(path, addSafe=addSafe) - + if bracketed: url = BracketStart + url + BracketEnd diff --git a/lib/pwiki/AdditionalDialogs.py b/lib/pwiki/AdditionalDialogs.py index 00bc6138..78e36bd7 100644 --- a/lib/pwiki/AdditionalDialogs.py +++ b/lib/pwiki/AdditionalDialogs.py @@ -209,6 +209,7 @@ def __init__(self, pWiki, parent, ID, title=None, wx.EVT_TEXT(self, ID, self.OnText) wx.EVT_CHAR(self.ctrls.text, self.OnCharText) wx.EVT_CHAR(self.ctrls.lb, self.OnCharListBox) + wx.EVT_KEY_DOWN(self.ctrls.lb, self.OnKeyDownListBox) wx.EVT_LISTBOX(self, ID, self.OnListBox) wx.EVT_LISTBOX_DCLICK(self, GUI_ID.lb, self.OnOk) wx.EVT_BUTTON(self, wx.ID_OK, self.OnOk) @@ -387,6 +388,14 @@ def OnCharListBox(self, evt): else: evt.Skip() + def OnKeyDownListBox(self, evt): + accP = getAccelPairFromKeyDown(evt) + if accP in ((wx.ACCEL_NORMAL, wx.WXK_DELETE), + (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_DELETE)): + self.OnDelete(evt) + else: + evt.Skip() + def OnCreate(self, evt): """ @@ -418,13 +427,14 @@ def OnCreate(self, evt): def OnDelete(self, evt): sellen = len(self.ctrls.lb.GetSelections()) if sellen > 0: - answer = wx.MessageBox( - _(u"Do you want to delete %i wiki page(s)?") % sellen, - (u"Delete Wiki Page(s)"), - wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION, self) - - if answer != wx.YES: - return + if self.pWiki.getConfig().getboolean("main", "trashcan_askOnDelete", + True): + answer = wx.MessageBox( + _(u"Do you want to delete %i wiki page(s)?") % sellen, + (u"Delete Wiki Page(s)"), + wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION, self) + if answer != wx.YES: + return self.pWiki.saveAllDocPages() for s in self.ctrls.lb.GetSelections(): @@ -434,7 +444,7 @@ def OnDelete(self, evt): if delword is not None: page = self.pWiki.getWikiDocument().getWikiPage(delword) - page.deletePage() + page.deletePageToTrashcan() # self.pWiki.getWikiData().deleteWord(delword) @@ -511,13 +521,15 @@ def __init__(self, pWiki, ID, words, motionType, title=None, def OnDelete(self, evt): sellen = len(self.ctrls.lb.GetSelections()) if sellen > 0: - answer = wx.MessageBox( - _(u"Do you want to delete %i wiki page(s)?") % sellen, - (u"Delete Wiki Page(s)"), - wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION, self) - - if answer != wx.YES: - return + if self.pWiki.getConfig().getboolean("main", "trashcan_askOnDelete", + True): + answer = wx.MessageBox( + _(u"Do you want to delete %i wiki page(s)?") % sellen, + (u"Delete Wiki Page(s)"), + wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION, self) + + if answer != wx.YES: + return self.pWiki.saveAllDocPages() for s in self.ctrls.lb.GetSelections(): @@ -527,7 +539,7 @@ def OnDelete(self, evt): if delword is not None: page = self.pWiki.getWikiDocument().getWikiPage(delword) - page.deletePage() + page.deletePageToTrashcan() # self.pWiki.getWikiData().deleteWord(delword) diff --git a/lib/pwiki/Configuration.py b/lib/pwiki/Configuration.py index ed6a898a..e0f9d345 100644 --- a/lib/pwiki/Configuration.py +++ b/lib/pwiki/Configuration.py @@ -514,7 +514,6 @@ def informChanged(self, oldSettings): ("main", "wikiOpenNew_defaultDir"): u"", # Default directory to show when opening # or creating a wiki. If entry is empty, a built-in default is used. - ("main", "wikiLockFile_ignore"): u"False", # Ignore the lock file created by another instance # when opening a wiki? ("main", "wikiLockFile_create"): u"True", # Create a lock file when opening a wiki? @@ -761,6 +760,14 @@ def informChanged(self, oldSettings): ("main", "tabs_maxCharacters"): u"0", # Maximum number of characters to show on a tab (0: inifinite) ("main", "template_pageNamesRE"): u"^template/", # Regular expression pattern for pages which should be seen as templates # Especially they will be listed in text editor context menu on new pages + + ("main", "trashcan_maxNoOfBags"): u"200", # Maximum number of trashbags. If more are present + # oldest are removed + ("main", "trashcan_askOnDelete"): u"True", # When deleting an element ask before it is + # put to trashbag + ("main", "trashcan_storageLocation"): "0", # Where to store trashcan data? 0: Intern in database; + # 1: extern in files (not supported for Compact Sqlite DB) + ("main", "tree_expandedNodes_descriptorPathes_main"): u"", # ";"-delimited sequence of node descriptor pathes of expanded nodes in tree. # Descriptors of a path are delimited by ','. This config. entry applies to main tree diff --git a/lib/pwiki/DocPages.py b/lib/pwiki/DocPages.py index 7cea92e7..c4e83fa8 100644 --- a/lib/pwiki/DocPages.py +++ b/lib/pwiki/DocPages.py @@ -273,6 +273,9 @@ def writeToDatabase(self, text=None, fireEvent=True): Write current text to database and initiate update of meta-data. """ with self.textOperationLock: + if self.isReadOnlyEffect(): + return + s, u = self.getDirty() if s: if text is None: @@ -282,7 +285,7 @@ def writeToDatabase(self, text=None, fireEvent=True): elif u: self.initiateUpdate(fireEvent=fireEvent) else: - if self.getMetaDataState(self.wikiWord) < \ + if self.getMetaDataState() < \ self.getWikiDocument().getFinalMetaDataState(): self.updateDirtySince = time.time() self.initiateUpdate(fireEvent=fireEvent) @@ -439,6 +442,9 @@ def getDirtySince(self): def setEditorText(self, text, dirty=True): with self.textOperationLock: + if self.isReadOnlyEffect(): + return + super(DataCarryingPage, self).setEditorText(text) if text is None: if self.saveDirtySince is not None: @@ -969,12 +975,6 @@ def getSpellCheckerUnknownWords(self, dieOnChange=False, # with self.livePageAstBuildLock: # TODO: Timeout? threadstop.testRunning() - spellSession = self.getWikiDocument().createOnlineSpellCheckerSessionClone() - if spellSession is None: - return - - spellSession.setCurrentDocPage(self) - with self.textOperationLock: text = self.getLiveText() liveTextPlaceHold = self.liveTextPlaceHold @@ -994,12 +994,20 @@ def getSpellCheckerUnknownWords(self, dieOnChange=False, lambda: origThreadstop.isRunning() and liveTextPlaceHold is self.liveTextPlaceHold) + spellSession = self.getWikiDocument().createOnlineSpellCheckerSessionClone() + if spellSession is None: + return + + spellSession.setCurrentDocPage(self) + if len(text) == 0: unknownWords = [] else: unknownWords = spellSession.buildUnknownWordList(text, threadstop=threadstop) + spellSession.close() + with self.textOperationLock: threadstop.testRunning() @@ -1109,6 +1117,7 @@ def __init__(self, wikiDocument, wikiWord): AbstractWikiPage.__init__(self, wikiDocument, wikiWord) self.versionOverview = UNDEFINED + self.pageReadOnly = None def getVersionOverview(self): @@ -1172,7 +1181,7 @@ def setPresentation(self, data, startPos): startPos -- start position in the presentation tuple which should be overwritten with data. """ - if self.isReadOnlyEffect(): + if self.wikiDocument.isReadOnlyEffect(): return if self.wikiDocument.getReadAccessFailed() or \ @@ -1397,7 +1406,7 @@ def pseudoDeletePage(self): ("pseudo-deleted page", "pseudo-deleted wiki page")) - def deletePage(self): + def deletePage(self, fireEvent=True): """ Deletes the page from database """ @@ -1415,8 +1424,26 @@ def deletePage(self): self.queueRemoveFromSearchIndex() # TODO: Check for (dead-)locks - wx.CallAfter(self.fireMiscEventKeys, - ("deleted page", "deleted wiki page")) + if fireEvent: + wx.CallAfter(self.fireMiscEventKeys, + ("deleted page", "deleted wiki page")) + + + def deletePageToTrashcan(self, fireEvent=True): + with self.textOperationLock: + if self.isReadOnlyEffect(): + return + + if not self.isDefined(): + return + + wikiDoc = self.getWikiDocument() + if wikiDoc.getWikiConfig().getint("main", "trashcan_maxNoOfBags", + 200) > 0: + # Trashcan is enabled + wikiDoc.getTrashcan().storeWikiWord(self.getWikiWord()) + + self.deletePage(fireEvent=fireEvent) def renameVersionData(self, newWord): @@ -1543,7 +1570,7 @@ def refreshSyncUpdateMatchTerms(self): """ Refresh those match terms which must be refreshed synchronously """ - if self.isReadOnlyEffect(): + if self.wikiDocument.isReadOnlyEffect(): return WORD_TYPE = Consts.WIKIWORDMATCHTERMS_TYPE_ASLINK | \ @@ -1560,7 +1587,7 @@ def refreshAttributesFromPageAst(self, pageAst, threadstop=DUMBTHREADSTOP): Update properties (aka attributes) only. This is step one in update/rebuild process. """ - if self.isReadOnlyEffect(): + if self.wikiDocument.isReadOnlyEffect(): return True # TODO Error? langHelper = wx.GetApp().createWikiLanguageHelper( @@ -1620,7 +1647,7 @@ def refreshMainDbCacheFromPageAst(self, pageAst, fireEvent=True, Update everything else (todos, relations). This is step two in update/rebuild process. """ - if self.isReadOnlyEffect(): + if self.wikiDocument.isReadOnlyEffect(): return True # return True or False? todos = [] @@ -1825,7 +1852,7 @@ def runDatabaseUpdate(self, step=-1, threadstop=DUMBTHREADSTOP): with self.textOperationLock: if not self.isDefined(): return False - if self.isReadOnlyEffect(): + if self.wikiDocument.isReadOnlyEffect(): return False liveTextPlaceHold = self.liveTextPlaceHold @@ -1929,6 +1956,45 @@ def _save(self, text, fireEvent=True): self.modified = None + def getPageReadOnly(self): + if self.pageReadOnly is None: + wikiWordReadOnly = self.getWikiData().getWikiWordReadOnly( + self.getNonAliasPage().getWikiWord()) + + if wikiWordReadOnly is None: + self.pageReadOnly = False + else: + self.pageReadOnly = bool(wikiWordReadOnly & 1) + + return self.pageReadOnly + + + def setPageReadOnly(self, readOnly): + if self.wikiDocument.isReadOnlyEffect() or \ + self.pageReadOnly == readOnly or \ + not self.isDefined(): + return + + if readOnly: + self.writeToDatabase() # Write to db before becoming read only + self.getWikiData().setWikiWordReadOnly(self.getNonAliasPage() + .getWikiWord(), 1) + else: + self.getWikiData().setWikiWordReadOnly(self.getNonAliasPage() + .getWikiWord(), 0) + + self.pageReadOnly = None + self.fireMiscEventKeys(("changed read only flag",)) + + + + def isReadOnlyEffect(self): + """ + Return true if page is effectively read-only, this means + "for any reason", regardless if error or intention. + """ + return self.getPageReadOnly() or super(WikiPage, self).isReadOnlyEffect() + # ----- Advanced functions ----- @@ -2292,10 +2358,11 @@ def getLiveTextNoTemplate(self): def getContent(self): if self.funcTag in (u"global/TextBlocks", u"global/PWL", - u"global/CCBlacklist", u"global/FavoriteWikis"): + u"global/CCBlacklist", u"global/NCCBlacklist", + u"global/FavoriteWikis"): return self._loadGlobalPage(self.funcTag[7:]) elif self.funcTag in (u"wiki/TextBlocks", u"wiki/PWL", - u"wiki/CCBlacklist"): + u"wiki/CCBlacklist", u"wiki/NCCBlacklist"): return self._loadDbSpecificPage(self.funcTag) @@ -2380,10 +2447,11 @@ def _save(self, text, fireEvent=True): # text = self.getLiveText() if self.funcTag in (u"global/TextBlocks", u"global/PWL", - u"global/CCBlacklist", u"global/FavoriteWikis"): + u"global/CCBlacklist", u"global/NCCBlacklist", + u"global/FavoriteWikis"): self._saveGlobalPage(text, self.funcTag[7:]) elif self.funcTag in (u"wiki/TextBlocks", u"wiki/PWL", - u"wiki/CCBlacklist"): + u"wiki/CCBlacklist", u"wiki/NCCBlacklist"): self._saveDbSpecificPage(text, self.funcTag) self.saveDirtySince = None @@ -2396,7 +2464,8 @@ def initiateUpdate(self, fireEvent=True): Here it is done directly in initiateUpdate() because it doesn't need much work. """ - if self.isReadOnlyEffect(): + # For "global/*" functional pages self.wikiDocument is None + if self.wikiDocument is not None and self.wikiDocument.isReadOnlyEffect(): return with self.textOperationLock: @@ -2423,6 +2492,11 @@ def initiateUpdate(self, fireEvent=True): # was updated evtSource.fireMiscEventKeys(("updated func page", "updated page", "reread cc blacklist needed")) + elif self.funcTag in (u"global/NCCBlacklist", u"wiki/NCCBlacklist"): + # The blacklist of non-camelcase words not to mark as wiki links + # was updated + evtSource.fireMiscEventKeys(("updated func page", "updated page", + "reread ncc blacklist needed")) elif self.funcTag == u"global/FavoriteWikis": # The list of favorite wikis was updated (there is no # wiki-bound version of favorite wikis @@ -2490,6 +2564,8 @@ def _cmpNumbersItem1Rev(a, b): u"wiki/PWL": N_(u"Wiki spell list"), u"global/CCBlacklist": N_(u"Global cc. blacklist"), u"wiki/CCBlacklist": N_(u"Wiki cc. blacklist"), + u"global/NCCBlacklist": N_(u"Global ncc. blacklist"), + u"wiki/NCCBlacklist": N_(u"Wiki ncc. blacklist"), u"global/FavoriteWikis": N_(u"Favorite wikis"), } diff --git a/lib/pwiki/Exporters.py b/lib/pwiki/Exporters.py index 785523be..2dd81e84 100644 --- a/lib/pwiki/Exporters.py +++ b/lib/pwiki/Exporters.py @@ -530,6 +530,37 @@ def exportWikiWord(self, word): self._writeHintedDatablock(unifName, True) +def getSingleWikiWordPacket(wikiDocument, word, writeVersionData=True, + formatVer=1): + """ + Helper function for the trashcan to create a trash bag packet for a + single wiki word (including versions). Returns it as a utf8-encoded + bytestring. + """ + for tryNumber in range(35): + stream = StringIO() + stream.write(BOM_UTF8) + separator = u"-----%s-----" % createRandomString(25) + exportFile = _SeparatorWatchUtf8Writer(stream, separator) + wikiPageWriter = MultiPageTextWikiPageWriter(wikiDocument, exportFile, + writeVersionData, formatVer) + + exportFile.write(u"Multipage text format %i\n" % formatVer) + # Separator line + exportFile.write(u"Separator: %s\n" % separator) + + try: + wikiPageWriter.exportWikiWord(word) + exportFile.checkAndClearBuffer() + + return stream.getvalue() + except _SeparatorFoundException: + continue + else: + raise ExportException(_(u"No usable separator found")) + + + class MultiPageTextExporter(AbstractExporter): """ diff --git a/lib/pwiki/Importers.py b/lib/pwiki/Importers.py index b94ce608..6ec047ae 100644 --- a/lib/pwiki/Importers.py +++ b/lib/pwiki/Importers.py @@ -3,6 +3,7 @@ from codecs import BOM_UTF8 from os.path import join, exists, splitext from calendar import timegm +from cStringIO import StringIO import urllib_red as urllib import wx, wx.xrc @@ -154,21 +155,27 @@ def _skipContent(self): def doImport(self, wikiDocument, importType, importSrc, - compatFilenames, addOpt): + compatFilenames, addOpt, importData=None): """ Run import operation. wikiDocument -- WikiDataManager object importType -- string tag to identify how to import - importDest -- Path to source directory or file to import from + importSrc -- Path to source directory or file to import from compatFilenames -- Should the filenames be decoded from the lowest level compatible? addOpt -- additional options returned by getAddOpt() + importData -- if not None contains data to import as bytestring. + importSrc is ignored in this case. Needed for trashcan. + returns True if import was done (needed for trashcan) """ - try: - self.rawImportFile = open(pathEnc(importSrc), "rU") - except IOError: - raise ImportException(_(u"Opening import file failed")) + if importData is not None: + self.rawImportFile = StringIO(importData) + else: + try: + self.rawImportFile = open(pathEnc(importSrc), "rU") + except IOError: + raise ImportException(_(u"Opening import file failed")) self.wikiDocument = wikiDocument self.tempDb = None @@ -193,11 +200,11 @@ def doImport(self, wikiDocument, importType, importSrc, decode = utf8Dec line = decode(self.rawImportFile.readline())[0] - if line.startswith("#!"): + if line.startswith(u"#!"): # Skip initial line with #! to allow execution as shell script line = decode(self.rawImportFile.readline())[0] - if not line.startswith("Multipage text format "): + if not line.startswith(u"Multipage text format "): raise ImportException( _(u"Bad file format, header not detected")) @@ -212,7 +219,7 @@ def doImport(self, wikiDocument, importType, importSrc, # Next is the separator line line = decode(self.rawImportFile.readline())[0] - if not line.startswith("Separator: "): + if not line.startswith(u"Separator: "): raise ImportException( _(u"Bad file format, header not detected")) @@ -274,11 +281,9 @@ def doImport(self, wikiDocument, importType, importSrc, # Now ask user if necessary if showImportTableAlways or self._isUserNeeded(): - if not MultiPageTextImporterDialog.runModal( - self.mainControl, self.tempDb, - self.mainControl): + if not self._doUserDecision(): # Canceled by user - return + return False # Further logical processing after possible user editing self._markNonImportedVersionsData() @@ -291,7 +296,8 @@ def doImport(self, wikiDocument, importType, importSrc, self.rawImportFile.seek(startPos) self.importFile = decodingReader(self.rawImportFile, "replace") self._doImportVer1Pass2() - + + return True finally: self.tempDb.close() self.tempDb = None @@ -425,6 +431,17 @@ def _propagateRenames(self): """, (newName, depUnifName)) + def _doUserDecision(self): + """ + Called to present GUI to user for deciding what to do. + This method is overwritten for trashcan GUI. + Returns False if user canceled operation + """ + return MultiPageTextImporterDialog.runModal( + self.mainControl, self.tempDb, + self.mainControl) + + def _isUserNeeded(self): """ Decide if a dialog must be shown to ask user how to proceed. @@ -620,11 +637,11 @@ def _doImportVer1Pass2(self): """ select substr(unifName, 10) from entries where unifName glob 'wikipage/*' and - renamePresentTo == '' and not dontImport and importVersionData + renameImportTo == '' and not dontImport and importVersionData union - select substr(renamePresentTo, 10) + select substr(renameImportTo, 10) from entries where unifName glob 'wikipage/*' and - renamePresentTo glob 'wikipage/*' and not dontImport and + renameImportTo glob 'wikipage/*' and not dontImport and importVersionData """): if not wikiDoc.isDefinedWikiPage(wikiWord): @@ -780,7 +797,8 @@ def _importTextDatablockVer1Pass2(self, unifName): def _importHintedDatablockVer1Pass2(self, unifName): """ A hinted datablock starts with an extra line defining encoding - (text or B64) and storage hint + (text or B64) and storage hint. It was introduced later therefore + only versioning packets use this while saved searches don't. """ hintStrings, content = self._readHintedDatablockVer1() if hintStrings is None: diff --git a/lib/pwiki/MainApp.py b/lib/pwiki/MainApp.py index 1b4375c9..e7c516bf 100644 --- a/lib/pwiki/MainApp.py +++ b/lib/pwiki/MainApp.py @@ -231,7 +231,7 @@ def initStep2(self, cmdLine): OptionsDialog.OptionsDialog.DEFAULT_PANEL_LIST) self.globalConfig.getMiscEvent().addListener(KeyFunctionSink(( - ("changed configuration", self.onGlobalConfigurationChanged), + ("changed configuration", self.onChangedGlobalConfiguration), )), False) Localization.loadLangList(self.wikiAppDir) @@ -568,7 +568,7 @@ def pauseBackgroundThreads(self): def resumeBackgroundThreads(self): self.fireMiscEventKeys(("resume background threads",)) - def onGlobalConfigurationChanged(self, miscevt): + def onChangedGlobalConfiguration(self, miscevt): self._rereadGlobalConfig() diff --git a/lib/pwiki/MainAreaPanel.py b/lib/pwiki/MainAreaPanel.py index f1dcf842..3cde6ae6 100644 --- a/lib/pwiki/MainAreaPanel.py +++ b/lib/pwiki/MainAreaPanel.py @@ -369,7 +369,7 @@ def switchDocPagePresenterTabEditorPreview(self, presenter): if self.mainControl.getConfig().getboolean("main", "editor_sync_byPreviewSelection", False) and \ presenter.getCurrentSubControlName() == "preview": - selText = presenter.getCurrentSubControl().getSelectedText() + selText = presenter.getCurrentSubControl().GetSelectedText() presenter.switchSubControl("textedit", gainFocus=True) diff --git a/lib/pwiki/MiscEvent.py b/lib/pwiki/MiscEvent.py index 712222fb..2a354df9 100644 --- a/lib/pwiki/MiscEvent.py +++ b/lib/pwiki/MiscEvent.py @@ -25,7 +25,7 @@ def removeMiscEvent(self): del self._MiscEventSourceMixin__miscevent - def fireMiscEventProps(self, props, first=None): + def fireMiscEventProps(self, props, first=None, shareListenerList=False): """ props -- Dictionary {key: value} with properties first -- first object to call its miscEventHappened method @@ -33,10 +33,11 @@ def fireMiscEventProps(self, props, first=None): return: create clone event """ - return self.getMiscEvent().createCloneAddProps(props).processSend(first) + return self.getMiscEvent().createCloneAddProps(props, + shareListenerList=shareListenerList).processSend(first) - def fireMiscEventKeys(self, keys, first=None): + def fireMiscEventKeys(self, keys, first=None, shareListenerList=False): """ keys -- Sequence with key strings first -- first object to call its miscEventHappened method @@ -44,18 +45,32 @@ def fireMiscEventKeys(self, keys, first=None): return: create clone event """ - return self.getMiscEvent().createCloneAddKeys(keys).processSend(first) + return self.getMiscEvent().createCloneAddKeys(keys, + shareListenerList=shareListenerList).processSend(first) class ListenerList(object): - __slots__ = ("__weakref__", "listeners", "userCount", "cleanupFlag") + __slots__ = ("__weakref__", "listeners", "userCount", "cleanupFlag", + "parentList") def __init__(self): self.listeners = [] self.userCount = 0 self.cleanupFlag = False + self.parentList = None # Don't know yet what it's good for + def clone(self): + result = ListenerList() + result.listeners = self.listeners[:] + result.userCount = 0 + result.parentList = self + if self.cleanupFlag: + result.cleanDeadRefs() + + return result + + def addListener(self, listener, isWeak=True): """ isWeak -- Iff true, store weak reference to listener instead @@ -244,15 +259,18 @@ def getParent(self): """ return self.parent - def clone(self): + def clone(self, shareListenerList=False): """ Normally you shouldn't call this method directly, call createClone() instead """ result = MiscEvent() - result.listenerList = self.listenerList - + if shareListenerList: + result.listenerList = self.listenerList + else: + result.listenerList = self.listenerList.clone() + if self.properties is not None: result.properties = self.properties.copy() @@ -311,7 +329,7 @@ def cleanDeadRefs(self): # parent.cleanDeadRefs() - def processSend(self, first = None): + def processSend(self, first=None): """ Called on the clone to dispatch itself to first, then to all listeners. Can't be called on an original MiscEvent, must be a clone. @@ -356,7 +374,7 @@ def processSend(self, first = None): return self - def createClone(self): + def createClone(self, shareListenerList=False): """ Creates a clone with the appropriate data, so dispatching can be done later.
Some methods can be called only on a cloned MiscEvent. @@ -364,7 +382,7 @@ def createClone(self): _source -- The object which will dispatch the event """ - event = self.clone() + event = self.clone(shareListenerList=shareListenerList) if event.properties is None: event.properties = {} @@ -404,25 +422,25 @@ def addKeys(self, addkeys): return self - def createCloneAddProps(self, addprops): + def createCloneAddProps(self, addprops, shareListenerList=False): """ Creates a clone with the appropriate data, so dispatching can be done later.
Some methods can be called only on a cloned MiscEvent. @param addprops Dictionary with additional properties """ - event = self.createClone() + event = self.createClone(shareListenerList=shareListenerList) event.properties.update(addprops) return event - def createCloneAddKeys(self, addkeys): + def createCloneAddKeys(self, addkeys, shareListenerList=False): """ Creates a clone with the appropriate data, so dispatching can be done later.
Some methods can be called only on a cloned MiscEvent. @param addkeys Sequence with additional keys for properties """ - event = self.createClone() + event = self.createClone(shareListenerList=shareListenerList) for k in addkeys: event.properties[k] = True return event diff --git a/lib/pwiki/OptionsDialog.py b/lib/pwiki/OptionsDialog.py index e0b8c993..b4f8950b 100644 --- a/lib/pwiki/OptionsDialog.py +++ b/lib/pwiki/OptionsDialog.py @@ -632,7 +632,11 @@ class OptionsDialog(wx.Dialog): ("wiki_icon", "tfWikiIcon", "t"), ("hotKey_showHide_byWiki", "tfHotKeyShowHideByWiki", "t"), - ("wiki_wikiLanguage", "chWikiWikiLanguage", "wikilang"), + + ("trashcan_maxNoOfBags", "tfTrashcanMaxNoOfBags", "i0+"), + ("trashcan_askOnDelete", "cbTrashcanAskOnDelete", "b"), + ("trashcan_storageLocation", "chTrashcanStorageLocation", "seli"), + ("wikiPageTitlePrefix", "tfWikiPageTitlePrefix", "t"), ("wikiPageTitle_creationMode", "chWikiPageTitleCreationMode", "seli"), @@ -642,6 +646,8 @@ class OptionsDialog(wx.Dialog): ("versioning_storageLocation", "chVersioningStorageLocation", "seli"), ("versioning_completeSteps", "tfVersioningCompleteSteps", "i0+"), + ("wiki_wikiLanguage", "chWikiWikiLanguage", "wikilang"), + ("fileStorage_identity_modDateMustMatch", "cbFsModDateMustMatch", "b"), ("fileStorage_identity_filenameMustMatch", "cbFsFilenameMustMatch", "b"), ("fileStorage_identity_modDateIsEnough", "cbFsModDateIsEnough", "b") @@ -938,9 +944,12 @@ def __init__(self, pWiki, ID, startPanelName=None, title="Options", self.ctrls.cbWikiPageFilesAsciiOnly.Enable(fppCap is not None) self.ctrls.cbWikiPageFilesGracefulOutsideAddAndRemove.Enable( fppCap is not None) + self.ctrls.chTrashcanStorageLocation.Enable( + fppCap is not None) self.ctrls.chVersioningStorageLocation.Enable( fppCap is not None) + self.OnUpdateUiAfterChange(None) diff --git a/lib/pwiki/PWikiNonCore.py b/lib/pwiki/PWikiNonCore.py index 68d93b1c..8f8e1812 100644 --- a/lib/pwiki/PWikiNonCore.py +++ b/lib/pwiki/PWikiNonCore.py @@ -13,6 +13,9 @@ from . import wxHelper, StringOps +from . import Trashcan + + class PWikiNonCore: def __init__(self, mainControl, dlgParent): @@ -74,6 +77,35 @@ def OnShowFileCleanup(self, evt): progresshandler) + def OnTogglePageReadOnly(self, evt): + docPage = self.mainControl.getCurrentDocPage() + if docPage is None: + return + + docPage.setPageReadOnly(evt.IsChecked()) + + + def OnTogglePageReadOnlyUpdate(self, evt): + if not evt.GetEnabled(): + evt.Check(False) + return + + docPage = self.mainControl.getCurrentDocPage() + if docPage is None or not docPage.isDefined(): + evt.Enable(False) + evt.Check(False) + return + + evt.Check(docPage.getPageReadOnly()) + + + def OnOpenTrashcan(self, evt): + from .TrashcanGui import TrashcanDialog + + self.mainControl.saveAllDocPages() + TrashcanDialog.runModal(self.mainControl, self.mainControl) + + def _buildDescriptorDict(self): """ Builds and returns a dictionary of tuples to describe the menu items, @@ -111,10 +143,23 @@ def _buildDescriptorDict(self): kb.InsertDate, "date", None, (mc.OnUpdateDisReadOnlyPage, mc.OnUpdateDisNotTextedit, mc.OnUpdateDisNotWikiPage)), + "showFileCleanupDialog": (self.OnShowFileCleanup, _(u'File cleanup...'), _(u'Remove orphaned files and dead links'), kb.FileCleanup, None, None, None), + + "togglePageReadOnly": (self.OnTogglePageReadOnly, + _(u'Page read only'), _(u'Set current page read only'), + kb.TogglePageReadOnly, None, None, + (mc.OnUpdateDisReadOnlyWiki, mc.OnUpdateDisNotWikiPage, + self.OnTogglePageReadOnlyUpdate), + wx.ITEM_CHECK), + + "openTrashcan": (self.OnOpenTrashcan, _(u'Open trashcan'), + _(u'Open trashcan'), + kb.OpenTrashcan, None, None, + None), } return descriptorDict diff --git a/lib/pwiki/PersonalWikiFrame.py b/lib/pwiki/PersonalWikiFrame.py index 35d39a91..085394b3 100644 --- a/lib/pwiki/PersonalWikiFrame.py +++ b/lib/pwiki/PersonalWikiFrame.py @@ -849,6 +849,12 @@ def buildWikiMenu(self): self.addMenuItem(wikiMenu, _(u'Print...') + u'\t' + self.keyBindings.Print, _(u'Show the print dialog'), self.OnShowPrintMainDialog) + self.addMenuItemByUnifNameTable(wikiMenu, + """ + menuItem/mainControl/builtin/openTrashcan + """ + ) + wikiMenu.AppendSeparator() self.addMenuItem(wikiMenu, _(u'&Properties...'), @@ -864,7 +870,7 @@ def buildWikiMenu(self): _(u'Rebuild this wiki and its cache completely'), lambda evt: self.rebuildWiki(onlyDirty=False), menuID=GUI_ID.MENU_REBUILD_WIKI, - updatefct=self.OnUpdateDisReadOnlyWiki) + updatefct=(self.OnUpdateDisReadOnlyWiki,)) # if wikiData.checkCapability("filePerPage") == 1: # self.addMenuItem(maintenanceMenu, @@ -879,14 +885,14 @@ def buildWikiMenu(self): _(u'Update cache where marked as not up to date'), lambda evt: self.rebuildWiki(onlyDirty=True), menuID=GUI_ID.MENU_UPDATE_WIKI_CACHE, - updatefct=self.OnUpdateDisReadOnlyWiki) + updatefct=(self.OnUpdateDisReadOnlyWiki,)) self.addMenuItem(maintenanceMenu, _(u'&Initiate update...'), _(u'Initiate full cache update which is done mainly ' u'in background'), lambda evt: self.initiateFullUpdate(), menuID=GUI_ID.MENU_INITATE_UPDATE_WIKI_CACHE, - updatefct=self.OnUpdateDisReadOnlyWiki) + updatefct=(self.OnUpdateDisReadOnlyWiki,)) self.addMenuItemByUnifNameTable(maintenanceMenu, """ @@ -894,6 +900,8 @@ def buildWikiMenu(self): """ ) + + # TODO: Test for wikiDocument.isSearchIndexEnabled() # self.addMenuItem(maintenanceMenu, _(u'Re&index Wiki...'), # _(u'Rebuild the reverse index for fulltext search'), @@ -908,6 +916,7 @@ def buildWikiMenu(self): maintenanceMenu.AppendSeparator() + self.addMenuItem(maintenanceMenu, _(u'Open as &Type...'), _(u'Open wiki with a specified wiki database type'), self.OnWikiOpenAsType) @@ -924,14 +933,14 @@ def buildWikiMenu(self): _(u'Free unused space in database'), lambda evt: self.vacuumWiki(), menuID=GUI_ID.MENU_VACUUM_WIKI, - updatefct=self.OnUpdateDisReadOnlyWiki) + updatefct=(self.OnUpdateDisReadOnlyWiki,)) if wikiData.checkCapability("plain text import") == 1: self.addMenuItem(maintenanceMenu, _(u'&Copy .wiki files to database'), _(u'Copy .wiki files to database'), self.OnImportFromPagefiles, - updatefct=self.OnUpdateDisReadOnlyWiki) + updatefct=(self.OnUpdateDisReadOnlyWiki,)) wikiMenu.AppendSeparator() # TODO May have two separators without anything between @@ -1352,7 +1361,7 @@ def buildMainMenu(self): self.addMenuItem(editMenu, _(u'Copy to Sc&ratchPad') + u'\t' + \ self.keyBindings.CopyToScratchPad, _(u'Copy selected text to ScratchPad'), lambda evt: self.getActiveEditor().snip(), - "tb_copy", updatefct=self.OnUpdateDisReadOnlyWiki) + "tb_copy", updatefct=(self.OnUpdateDisReadOnlyWiki,)) self.textBlocksMenu = wx.Menu() self.fillTextBlocksMenu(self.textBlocksMenu) @@ -1360,7 +1369,7 @@ def buildMainMenu(self): editMenu.AppendMenu(GUI_ID.MENU_TEXT_BLOCKS, _(u'Paste T&extblock'), self.textBlocksMenu) wx.EVT_UPDATE_UI(self, GUI_ID.MENU_TEXT_BLOCKS, - self.OnUpdateDisReadOnlyPage) + _buildChainedUpdateEventFct((self.OnUpdateDisReadOnlyPage,))) if self.clipboardInterceptor is not None: @@ -1623,9 +1632,9 @@ def buildMainMenu(self): lambda evt: (self.saveAllDocPages(), self.getWikiData().commit()), "tb_save", menuID=GUI_ID.CMD_SAVE_WIKI, - updatefct=self.OnUpdateDisReadOnlyWiki) + updatefct=(self.OnUpdateDisReadOnlyWiki,)) - # TODO More fine grained check for en-/disabling of rename and delete? + # TODO: More fine grained check for en-/disabling of rename and delete? self.addMenuItem(wikiPageMenu, _(u'&Rename') + u'\t' + self.keyBindings.Rename, _(u'Rename current wiki word'), lambda evt: self.showWikiWordRenameDialog(), "tb_rename", @@ -1682,6 +1691,12 @@ def buildMainMenu(self): updatefct=(self.OnUpdateDisNotTextedit, self.OnUpdateDisNotWikiPage) ) + self.addMenuItemByUnifNameTable(wikiPageMenu, + """ + menuItem/mainControl/builtin/togglePageReadOnly + """ + ) + formatMenu = wx.Menu() @@ -1712,7 +1727,7 @@ def buildMainMenu(self): self.keyBindings.RewrapText, _(u'Rewrap Text'), lambda evt: self.getActiveEditor().rewrapText(), - updatefct=self.OnUpdateDisReadOnlyPage) + updatefct=(self.OnUpdateDisReadOnlyPage,)) convertMenu = wx.Menu() formatMenu.AppendMenu(wx.NewId(), _(u'&Convert'), convertMenu) @@ -1728,7 +1743,7 @@ def buildMainMenu(self): self.keyBindings.ReplaceTextByWikiword, _(u'Put selected text in a new or existing wiki word'), lambda evt: self.showReplaceTextByWikiwordDialog(), - updatefct=self.OnUpdateDisReadOnlyPage) + updatefct=(self.OnUpdateDisReadOnlyPage,)) self.addMenuItem(convertMenu, _(u'Absolute/Relative &File URL') + u'\t' + @@ -1749,7 +1764,7 @@ def buildMainMenu(self): formatMenu.AppendMenu(GUI_ID.MENU_ADD_ICON_NAME, _(u'&Icon Name'), iconsMenu) wx.EVT_UPDATE_UI(self, GUI_ID.MENU_ADD_ICON_NAME, - self.OnUpdateDisReadOnlyPage) + _buildChainedUpdateEventFct((self.OnUpdateDisReadOnlyPage,))) self.cmdIdToInsertString = cmdIdToIconName @@ -1761,7 +1776,7 @@ def buildMainMenu(self): formatMenu.AppendMenu(GUI_ID.MENU_ADD_STRING_NAME, _(u'&Color Name'), colorsMenu) wx.EVT_UPDATE_UI(self, GUI_ID.MENU_ADD_STRING_NAME, - self.OnUpdateDisReadOnlyPage) + _buildChainedUpdateEventFct((self.OnUpdateDisReadOnlyPage,))) self.cmdIdToInsertString.update(cmdIdToColorName) @@ -1778,7 +1793,7 @@ def buildMainMenu(self): addAttributeMenu.AppendMenu(GUI_ID.MENU_ADD_ICON_ATTRIBUTE, _(u'&Icon Attribute'), iconsMenu) wx.EVT_UPDATE_UI(self, GUI_ID.MENU_ADD_ICON_ATTRIBUTE, - self.OnUpdateDisReadOnlyPage) + _buildChainedUpdateEventFct((self.OnUpdateDisReadOnlyPage,))) # Build submenu for color attributes colorsMenu, self.cmdIdToColorNameForAttribute = AttributeHandling.buildColorsSubmenu() @@ -1788,7 +1803,7 @@ def buildMainMenu(self): addAttributeMenu.AppendMenu(GUI_ID.MENU_ADD_COLOR_ATTRIBUTE, _(u'&Color Attribute'), colorsMenu) wx.EVT_UPDATE_UI(self, GUI_ID.MENU_ADD_COLOR_ATTRIBUTE, - self.OnUpdateDisReadOnlyPage) + _buildChainedUpdateEventFct((self.OnUpdateDisReadOnlyPage,))) # TODO: Bold attribute @@ -1872,7 +1887,7 @@ def buildMainMenu(self): self.addMenuItem(extraMenu, _(u'&Export...'), _(u'Open general export dialog'), self.OnCmdExportDialog, - updatefct=self.OnUpdateDisNoWiki) + updatefct=(self.OnUpdateDisNoWiki,)) self.addMenuItem(extraMenu, _(u'&Continuous Export...'), _(u'Open export dialog for continuous export of changes'), @@ -1883,7 +1898,7 @@ def buildMainMenu(self): self.addMenuItem(extraMenu, _(u'&Import...'), _(u'Import dialog'), self.OnCmdImportDialog, - updatefct=self.OnUpdateDisReadOnlyWiki) + updatefct=(self.OnUpdateDisReadOnlyWiki,)) extraMenu.AppendSeparator() @@ -3725,11 +3740,13 @@ def saveDocPage(self, page, text=None): def deleteWikiWord(self, wikiWord): + wikiDoc = self.getWikiDocument() + if wikiWord and self.requireWriteAccess(): try: - if self.getWikiDocument().isDefinedWikiLink(wikiWord): - page = self.getWikiDocument().getWikiPage(wikiWord) - page.deletePage() + if wikiDoc.isDefinedWikiLink(wikiWord): + page = wikiDoc.getWikiPage(wikiWord) + page.deletePageToTrashcan() except (IOError, OSError, DbAccessError), e: self.lostAccess(e) raise @@ -3818,11 +3835,14 @@ def makeAbsPathRelUrl(self, absPath, addSafe=''): def launchUrl(self, link): + if link.startswith(u"wikirel://"): + # Relative wiki link + link = self.getWikiDocument().makeRelUrlAbsolute(link) + elif link.startswith(u"rel://"): + # Relative link + link = self.getWikiDocument().makeRelUrlAbsolute(link) + if not link.startswith(u"wiki:"): - if link.startswith(u"rel://"): - # This is a relative link - link = self.getWikiDocument().makeRelUrlAbsolute(link) - try: OsAbstract.startFile(self, link) return True @@ -4396,21 +4416,23 @@ def showWikiWordDeleteDialog(self, wikiWord=None): if self.isReadOnlyPage(): return # TODO Error message - dlg = wx.MessageDialog(self, - uniToGui(_(u"Are you sure you want to delete wiki word '%s'?") % wikiWord), - _(u'Delete Wiki Word'), wx.YES_NO | wx.NO_DEFAULT) - result = dlg.ShowModal() - if result == wx.ID_YES: - try: - self.saveAllDocPages() - self.deleteWikiWord(wikiWord) - except (IOError, OSError, DbAccessError), e: - self.lostAccess(e) - raise - except WikiDataException, e: - self.displayErrorMessage(unicode(e)) + if self.getConfig().getboolean("main", "trashcan_askOnDelete", True): + result = wx.MessageBox( + _(u"Are you sure you want to delete wiki word '%s'?") % + wikiWord, _(u'Delete Wiki Word'), + wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION, self) - dlg.Destroy() + if result == wx.NO: + return + + try: + self.saveAllDocPages() + self.deleteWikiWord(wikiWord) + except (IOError, OSError, DbAccessError), e: + self.lostAccess(e) + raise + except WikiDataException, e: + self.displayErrorMessage(unicode(e)) def showSearchReplaceDialog(self): @@ -5387,24 +5409,32 @@ def isReadOnlyPage(self): return (docPage is None) or docPage.isReadOnlyEffect() + # All OnUpdateDis* methods only disable a menu/toolbar item, they + # never enable. This allows to build chains of them where each + # condition is checked which may disable the item (before running the + # chain the item is enabled by _buildChainedUpdateEventFct() + def OnUpdateDisNoWiki(self, evt): """ Called for ui-update to disable menu item if no wiki loaded. """ - evt.Enable(self.isWikiLoaded()) - + if not self.isWikiLoaded(): + evt.Enable(False) def OnUpdateDisReadOnlyWiki(self, evt): """ Called for ui-update to disable menu item if wiki is read-only. """ - evt.Enable(not self.isReadOnlyWiki()) + if self.isReadOnlyWiki(): + evt.Enable(False) + def OnUpdateDisReadOnlyPage(self, evt): """ Called for ui-update to disable menu item if page is read-only. """ - evt.Enable(not self.isReadOnlyPage()) + if self.isReadOnlyPage(): + evt.Enable(False) def OnUpdateDisNotTextedit(self, evt): """ diff --git a/lib/pwiki/SearchAndReplace.py b/lib/pwiki/SearchAndReplace.py index a6b407e4..695ba5f7 100644 --- a/lib/pwiki/SearchAndReplace.py +++ b/lib/pwiki/SearchAndReplace.py @@ -1759,6 +1759,8 @@ def _buildBooleanSearchTree(self, parseExpr): # return AttributeValueNode(self, node.prefixedTerm) elif tname == "todoTerm": return TodoNode(self, node.key, node.value) + elif tname == "pageTerm": + return RegexWikiPageNode(self, node.pageName) diff --git a/lib/pwiki/SearchAndReplaceBoolLang.py b/lib/pwiki/SearchAndReplaceBoolLang.py index ac8c0a22..cb475e8a 100644 --- a/lib/pwiki/SearchAndReplaceBoolLang.py +++ b/lib/pwiki/SearchAndReplaceBoolLang.py @@ -100,6 +100,10 @@ def actionAttributeTerm(s, l, st, t): t.value = ptt.parameterTerm +def actionPageTerm(s, l, st, t): + t.pageName = t.findFlatByName("pageName").parameterTerm + + def actionOneOp(s, l, st, t): t.op = t.findFlatByName("op") @@ -131,10 +135,11 @@ def actionTwoOpsLeft(s, l, st, t): # keyPrefixValue = buildRegex(ur"val(?:ue)?:") keyPrefixAtt = buildRegex(ur"att(?:r)?:") keyPrefixTodo = buildRegex(ur"todo:") +keyPrefixPage = buildRegex(ur"page:") keyWord = keyParenOpen | keyParenClose | keyNot | keyAnd | keyOr | \ - keyPrefixAtt | keyPrefixTodo + keyPrefixAtt | keyPrefixTodo | keyPrefixPage valueQuote = buildRegex(u"\"+|'+|/+", name=u"quoting") @@ -171,23 +176,32 @@ def actionTwoOpsLeft(s, l, st, t): attributeTerm = keyPrefixAtt + buildRegex(ur" ?") + \ - parameterTermOpt.setResultsNameNoCopy("key") + optWhitespace + \ + parameterTermOpt.setResultsName("key") + optWhitespace + \ Optional( buildRegex(ur": ?") + - parameterTerm.setResultsNameNoCopy("value") + optWhitespace ) + parameterTerm.setResultsName("value") + optWhitespace ) attributeTerm = attributeTerm.setResultsNameNoCopy("attributeTerm")\ .setParseAction(actionAttributeTerm) todoTerm = keyPrefixTodo + buildRegex(ur" ?") + \ - parameterTermOpt.setResultsNameNoCopy("key") + optWhitespace + \ + parameterTermOpt.setResultsName("key") + optWhitespace + \ Optional( buildRegex(ur": ?") + - parameterTerm.setResultsNameNoCopy("value") + optWhitespace ) + parameterTerm.setResultsName("value") + optWhitespace ) todoTerm = todoTerm.setResultsNameNoCopy("todoTerm")\ .setParseAction(actionAttributeTerm) +pageTerm = keyPrefixPage + buildRegex(ur" ?") + \ + parameterTerm.setResultsName("pageName") + optWhitespace + +pageTerm = pageTerm.setResultsNameNoCopy("pageTerm")\ + .setParseAction(actionPageTerm) + + + + # todoTerm = keyPrefixTodo + buildRegex(ur" ?") + \ # (quotedTerm | nonQuotedTermSnippet) + optWhitespace # todoTerm = todoTerm.setResultsNameNoCopy("todoTerm")\ @@ -213,7 +227,7 @@ def actionTwoOpsLeft(s, l, st, t): .setParseAction(actionOneOp) exprLevel1 << (notExpression | parensExpression | attributeTerm | todoTerm | - regexTerm) + pageTerm | regexTerm) orExpression = exprLevel1.setResultsName("op1") + keyOr + Group(searchExpression).setResultsName("op2") diff --git a/lib/pwiki/SqliteThin3.py b/lib/pwiki/SqliteThin3.py index 663fa9f7..ec31413c 100644 --- a/lib/pwiki/SqliteThin3.py +++ b/lib/pwiki/SqliteThin3.py @@ -656,7 +656,7 @@ class _Context: """ def __init__(self, ptr): - self._contextpointer = ptr + self._contextpointer = c_void_p(ptr) def result_blob(self, data): """ @@ -722,7 +722,7 @@ class _Value: """ def __init__(self, ptr): - self._valuepointer = ptr + self._valuepointer = c_void_p(ptr) def value_blob(self): @@ -788,8 +788,7 @@ def %s(self): _dll.sqlite3_user_data.restype = c_uint def _pyFuncCallback(contextptr, nValues, valueptrptr): - - realfunc = _sqliteTransObjects[_dll.sqlite3_user_data(contextptr)] + realfunc = _sqliteTransObjects[_dll.sqlite3_user_data(c_void_p(contextptr))] values = [_Value(valueptrptr[i]) for i in xrange(nValues)] # print "_pyFuncCallback", repr(realfunc), repr(values), id(realfunc), sys.getrefcount(realfunc) try: diff --git a/lib/pwiki/StringOps.py b/lib/pwiki/StringOps.py index 9225f963..b37bdbd6 100644 --- a/lib/pwiki/StringOps.py +++ b/lib/pwiki/StringOps.py @@ -1048,7 +1048,7 @@ def ntPathnameFromUrl(url, testFileType=True): throw RuntimeError if not. """ import string - if url.startswith("file:"): + if url.startswith("file:") or url.startswith("wiki:"): url = url[5:] elif testFileType: raise RuntimeError, 'Cannot convert non-local URL to pathname' @@ -1104,7 +1104,7 @@ def macPathnameFromUrl(url, testFileType=True): # XXXX The .. handling should be fixed... # tp = urllib.splittype(url)[0] - if tp and tp != 'file': + if tp and tp != 'file' and tp != 'wiki': raise RuntimeError, 'Cannot convert non-local URL to pathname' # Turn starting /// into /, an empty hostname means current host if url[:3] == '///': @@ -1149,9 +1149,9 @@ def elsePathnameFromUrl(url, testFileType=True): # # XXXX The .. handling should be fixed... # - if url.startswith("file:///"): + if url.startswith("file:///") or url.startswith("wiki:///"): url = url[7:] # Third '/' remains - elif url.startswith("file:"): + elif url.startswith("file:") or url.startswith("wiki:"): url = url[5:] elif testFileType: raise RuntimeError, 'Cannot convert non-local URL to pathname' diff --git a/lib/pwiki/Trashcan.py b/lib/pwiki/Trashcan.py new file mode 100644 index 00000000..3acbfae3 --- /dev/null +++ b/lib/pwiki/Trashcan.py @@ -0,0 +1,637 @@ +""" +""" + +import time, zlib, re +from calendar import timegm + +from .rtlibRepl import minidom + +import wx + +from WikiExceptions import * +import Consts + +from .MiscEvent import MiscEventSourceMixin, KeyFunctionSink + +from . import Exporters, Serialization + +from . import StringOps + +# from ..StringOps import applyBinCompact, getBinCompactForDiff, \ +# fileContentToUnicode, BOM_UTF8, formatWxDate +# +# from ..Serialization import serToXmlUnicode, serFromXmlUnicode, serToXmlInt, \ +# serFromXmlInt, iterXmlElementFlat +# +# from ..DocPages import AbstractWikiPage + + + +DAMAGED = object() + + +class TrashBag(object): + """ + A trash bag contains all parts of a wikiword, wikipage content itself and + dependent datablocks (e.g. old versions). It provides also a small subset + of WikiData API to allow read-only access to the items in the bag. + """ + __slots__ = ("trashcan", "trashTimeStamp", "bagId", + "contentStorageMode", "originalUnifiedName", "xmlNode") + + def __init__(self, trashcan): # , contentStorageMode = u"single" + self.trashcan = trashcan + self.trashTimeStamp = time.time() + + self.bagId = 0 # Invalid, numbers start with 1 +# self.contentStorageMode = contentStorageMode + + # Unified name of main content before it was trashed. + # Normally this is a "wikipage/..." but the trash bag can contain + # additional items + self.originalUnifiedName = None + + self.xmlNode = None + + + def getTrashcan(self): + return self.trashcan + + + def getFormattedTrashDate(self, formatStr): + return StringOps.formatWxDate(formatStr, wx.DateTimeFromTimeT( + self.trashTimeStamp)) + + + def serializeOverviewToXmlProd(self, xmlDoc): + """ + Create XML node to contain all overview information (not content) + about this object. + """ + xmlNode = self.xmlNode + if xmlNode is None: + xmlNode = xmlDoc.createElement(u"trashBag") + + self.serializeOverviewToXml(xmlNode, xmlDoc) + + return xmlNode + + + def serializeOverviewToXml(self, xmlNode, xmlDoc): + """ + Create XML node to contain all overview information (not content) + about this object. + """ + Serialization.serToXmlInt(xmlNode, xmlDoc, u"bagId", self.bagId, + replace=True) + + Serialization.serToXmlUnicode(xmlNode, xmlDoc, u"originalUnifiedName", + self.originalUnifiedName, replace=True) + + Serialization.serToXmlUnicode(xmlNode, xmlDoc, u"trashTime", unicode(time.strftime( + "%Y-%m-%d/%H:%M:%S", time.gmtime(self.trashTimeStamp))), + replace=True) + + +# Serialization.serToXmlUnicode(xmlNode, xmlDoc, u"contentStorageMode", +# self.contentStorageMode, replace=True) + + + def serializeOverviewFromXml(self, xmlNode): + """ + Set object state from data in xmlNode) + """ + self.xmlNode = xmlNode + + self.bagId = Serialization.serFromXmlInt(xmlNode, u"bagId") + + self.originalUnifiedName = Serialization.serFromXmlUnicode(xmlNode, + u"originalUnifiedName") + + timeStr = Serialization.serFromXmlUnicode(xmlNode, u"trashTime") + self.trashTimeStamp = timegm(time.strptime(timeStr, + "%Y-%m-%d/%H:%M:%S")) + + + def getPacketUnifiedName(self): + if self.bagId == 0: + return None + else: + return u"trashcan/trashBag/packet/bagId/%s" % self.bagId + + + def getPacketData(self): + unifName = self.getPacketUnifiedName() + if unifName is None: + return None + + return self.trashcan.getWikiDocument().retrieveDataBlock(unifName, None) + + def deletePacket(self): + """ + Delete associated packet (if any). Should only be called from Trashcan + """ + unifName = self.getPacketUnifiedName() + if unifName is None: + return + + self.trashcan.getWikiDocument().deleteDataBlock(unifName) + self.bagId = 0 + + + +class Trashcan(MiscEventSourceMixin): + def __init__(self, wikiDocument): + MiscEventSourceMixin.__init__(self) + + self.wikiDocument = wikiDocument + self.trashBags = [] + self.trashBagIds = set() + + self.xmlNode = None + + self.__sinkWikiDoc = KeyFunctionSink(( + ("changed wiki configuration", self.onChangedWikiConfiguration), + )) + + self.wikiDocument.getMiscEvent().addListener(self.__sinkWikiDoc) + + + def getWikiDocument(self): + return self.wikiDocument + + + def close(self): + self.wikiDocument.getMiscEvent().removeListener(self.__sinkWikiDoc) + + + def isInDatabase(self): + """ + Can be called before readOverview() to check if the version overview + is already in database. + """ + unifName = u"trashcan/overview" + return self.wikiDocument.retrieveDataBlock(unifName) is not None + + + def onChangedWikiConfiguration(self, miscEvt): + self._removeOldest() + + + def _removeOldest(self): + """ + Remove oldest trashbags if there are more in the can than configuration + setting allows + """ + remCount = len(self.trashBags) - self.wikiDocument.getWikiConfig()\ + .getint("main", "trashcan_maxNoOfBags", 200) + + if remCount <= 0: + return + + for bag in self.trashBags[:remCount]: + self.trashBagIds.discard(bag.bagId) + + del self.trashBags[:remCount] + + + def _addTrashBag(self, trashBag): + """ + Adds bag to trashcan. Also checks if there are too many bags according + to settings and removes the oldest one(s). The bag must already have a + unique bagId + """ + assert trashBag.bagId > 0 + + self.trashBags.append(trashBag) + self.trashBagIds.add(trashBag.bagId) + self._removeOldest() + self.writeOverview() + + + def storeWikiWord(self, word): + """ + Store wikiword (including versions) in a trash bag and return bag id + """ + bag = TrashBag(self) + + for bagId in xrange(1, len(self.trashBagIds) + 2): + if not bagId in self.trashBagIds: + break + else: + raise InternalError(u"Trashcan: No free bagId???") + + bag.bagId = bagId + + data = Exporters.getSingleWikiWordPacket(self.wikiDocument, word) + self.wikiDocument.storeDataBlock(bag.getPacketUnifiedName(), + data, storeHint=self.getStorageHint()) + + bag.originalUnifiedName = u"wikipage/" + word + self._addTrashBag(bag) + + return bagId + + def deleteBag(self, bag): + """ + Deletes bag from trashcan. Only the bagId of the bag parameter + is used so to delete a bag with a particular bagId just create + a "fake" bag and set bagId accordingly. + """ + bagId = bag.bagId + if bag.bagId == 0: + return + + for i, tb in enumerate(self.trashBags): + if tb.bagId == bagId: + del self.trashBags[i] + self.trashBagIds.discard(bagId) + tb.deletePacket() + return + + + def readOverview(self): + """ + Read and decode overview from database. Most functions can be called + only after this was called (exception: isInDatabase()) + """ + unifName = u"trashcan/overview" + + content = self.wikiDocument.retrieveDataBlock(unifName, default=DAMAGED) + if content is DAMAGED: + raise Exception(_(u"Trashcan data damaged")) # TODO: Specific exception + elif content is None: + self.trashBags = [] + self.trashBagIds = set() + self.xmlNode = None + return + + xmlDoc = minidom.parseString(content) + xmlNode = xmlDoc.firstChild + self.serializeFromXml(xmlNode) + + +# def getDependentDataBlocks(self): +# assert not self.isInvalid() +# +# unifiedPageName = self.basePage.getUnifiedPageName() +# +# result = [u"versioning/overview/" + unifiedPageName] +# +# for entry in self.versionEntries: +# result.append(u"versioning/packet/versionNo/%s/%s" % (entry.versionNumber, +# unifiedPageName)) +# +# return result + + + + + @staticmethod + def deleteBrokenData(wikiDocument): + """ + Delete all trashcan data in case existing data is broken and can't + be deleted in regular ways. + """ + dataBlocks = wikiDocument.getDataBlockUnifNamesStartingWith( + u"trashcan/") + + for db in dataBlocks: + wikiDocument.deleteDataBlock(db) + + + def writeOverview(self): + unifName = u"trashcan/overview" + + if len(self.trashBags) == 0: + self.wikiDocument.deleteDataBlock(unifName) + return + + xmlDoc = minidom.getDOMImplementation().createDocument(None, None, None) + xmlNode = self.serializeToXmlProd(xmlDoc) + + xmlDoc.appendChild(xmlNode) + content = xmlDoc.toxml("utf-8") + + self.wikiDocument.storeDataBlock(unifName, content, + storeHint=self.getStorageHint()) + + + def clear(self): + """ + Delete all data from trashcan (called when user empties trashcan) + """ + self.trashBags = [] + self.trashBagIds = set() + + self.xmlNode = None + self.deleteBrokenData(self.wikiDocument) + + + def getTrashBags(self): + return self.trashBags + + + def getStorageHint(self): + """ + Return appropriate storage hint according to option settings. + """ + if self.wikiDocument.getWikiConfig().getint("main", + "trashcan_storageLocation", 0) != 1: + return Consts.DATABLOCK_STOREHINT_INTERN + else: + return Consts.DATABLOCK_STOREHINT_EXTERN + + +# @staticmethod +# def decodeContent(encContent, encoding): +# if encoding is None: +# return encContent +# if encoding == "zlib": +# return zlib.decompress(encContent) +# +# @staticmethod +# def encodeContent(content, encoding): +# if encoding is None: +# return content +# if encoding == "zlib": +# return zlib.compress(content) + + + def serializeToXmlProd(self, xmlDoc): + """ + Create XML node to contain all information about this object. + """ + xmlNode = self.xmlNode + if xmlNode is None: + xmlNode = xmlDoc.createElement(u"trashcanOverview") + + self.serializeToXml(xmlNode, xmlDoc) + + return xmlNode + + + def serializeToXml(self, xmlNode, xmlDoc): + """ + Modify XML node to contain all information about this object. + """ + xmlNode.setAttribute(u"formatVersion", u"0") + xmlNode.setAttribute(u"readCompatVersion", u"0") + xmlNode.setAttribute(u"writeCompatVersion", u"0") + + for xmlEntry in Serialization.iterXmlElementFlat(xmlNode, u"trashBag"): + xmlNode.removeChild(xmlEntry) + + for entry in self.trashBags: + entryNode = entry.serializeOverviewToXmlProd(xmlDoc) + xmlNode.appendChild(entryNode) + + + def serializeFromXml(self, xmlNode): + """ + Set object state from data in xmlNode. + """ + formatVer = int(xmlNode.getAttribute(u"writeCompatVersion")) + if formatVer > 0: + SerializationException("Wrong version no. %s for trashcan overview" % + formatVer) + + self.xmlNode = xmlNode + + trashBags = [] + trashBagIds = set() + + for xmlEntry in Serialization.iterXmlElementFlat(xmlNode, u"trashBag"): + entry = TrashBag(self) + entry.serializeOverviewFromXml(xmlEntry) + + trashBags.append(entry) + trashBagIds.add(entry.bagId) + + # Order trash bags by trash date + trashBags.sort(key=lambda entry: entry.trashTimeStamp) + + self.trashBags = trashBags + self.trashBagIds = trashBagIds + + +# def getVersionContentRaw(self, versionNumber): +# if len(self.trashBags) == 0: +# raise InternalError(u"Tried to retrieve non-existing " +# u"version number %s from empty list." % versionNumber) +# +# if versionNumber == -1: +# versionNumber = self.trashBags[-1].versionNumber +# +# base = None +# workList = [] +# for i in xrange(len(self.trashBags) - 1, -1, -1): +# entry = self.trashBags[i] +# if entry.contentDifferencing == u"complete": +# workList = [] +# base = entry +# else: +# workList.append(entry) +# +# if entry.versionNumber == versionNumber: +# break +# else: +# raise InternalError(u"Tried to retrieve non-existing " +# u"version number %s." % versionNumber) +# +# if base is None: +# raise InternalError(u"No base version found for getVersionContent(%s)" % +# versionNumber) +# +# unifName = u"versioning/packet/versionNo/%s/%s" % (base.versionNumber, +# self.basePage.getUnifiedPageName()) +# +# content = self.wikiDocument.retrieveDataBlock(unifName, default=DAMAGED) +# if content is DAMAGED: +# raise VersioningException(_(u"Versioning data damaged")) +# elif content is None: +# raise InternalError(u"Tried to retrieve non-existing " +# u"packet for version number %s" % versionNumber) +# +# content = self.decodeContent(content, entry.contentEncoding) +# +# for entry in workList: +# unifName = u"versioning/packet/versionNo/%s/%s" % (entry.versionNumber, +# self.basePage.getUnifiedPageName()) +# packet = self.wikiDocument.retrieveDataBlock(unifName, default=None) +# if content is DAMAGED: +# raise VersioningException(_(u"Versioning data damaged")) +# elif content is None: +# raise InternalError(u"Tried to retrieve non-existing " +# u"packet for version number %s" % versionNumber) +# +# +# content = applyBinCompact(content, packet) +# +# return content +# +# +# def getVersionContent(self, versionNumber): +# return fileContentToUnicode(self.getVersionContentRaw(versionNumber)) +# +# +# def addVersion(self, content, entry): +# """ +# entry.versionNumber is assumed invalid and will be filled by this function. +# """ +# if isinstance(content, unicode): +# content = BOM_UTF8 + content.encode("utf-8") +# assert isinstance(content, str) +# +# completeStep = max(self.wikiDocument.getWikiConfig().getint("main", +# "versioning_completeSteps", 10), 0) +# +# if completeStep == 0: +# asRevDiff = True +# else: +# if len(self.trashBags) < completeStep: +# asRevDiff = True +# else: +# asRevDiff = False +# for e in reversed(self.trashBags[-completeStep:-1]): +# if e.contentDifferencing == "complete": +# asRevDiff = True +# break +# +# self.maxVersionNumber += 1 +# newHeadVerNo = self.maxVersionNumber +# +# newHeadUnifName = u"versioning/packet/versionNo/%s/%s" % \ +# (newHeadVerNo, self.basePage.getUnifiedPageName()) +# +# self.wikiDocument.storeDataBlock(newHeadUnifName, content, +# storeHint=self.getStorageHint()) +# +# entry.versionNumber = newHeadVerNo +# entry.contentDifferencing = "complete" +# entry.contentEncoding = None +# self.trashBags.append(entry) +# +# if len(self.trashBags) > 1: +# if asRevDiff: +# prevHeadEntry = self.trashBags[-2] +# prevHeadContent = self.getVersionContentRaw(prevHeadEntry.versionNumber) +# +# unifName = u"versioning/packet/versionNo/%s/%s" % (prevHeadEntry.versionNumber, +# self.basePage.getUnifiedPageName()) +# diffPacket = getBinCompactForDiff(content, prevHeadContent) +# +# if len(diffPacket) < len(prevHeadContent): +# prevHeadEntry.contentDifferencing = "revdiff" +# prevHeadEntry.contentEncoding = None +# self.wikiDocument.storeDataBlock(unifName, diffPacket, +# storeHint=self.getStorageHint()) +# +# self.fireMiscEventKeys(("appended version", "changed version overview")) +# +# +# def deleteVersion(self, versionNumber): +# if len(self.trashBags) == 0: +# raise InternalError("Non-existing version %s to delete (empty list)." % +# versionNumber) +# +# if versionNumber == -1: +# versionNumber = self.trashBags[-1].versionNumber +# +# if versionNumber == self.trashBags[0].versionNumber: +# # Delete oldest +# unifName = u"versioning/packet/versionNo/%s/%s" % (versionNumber, +# self.basePage.getUnifiedPageName()) +# +# self.wikiDocument.deleteDataBlock(unifName) +# del self.trashBags[0] +# self.fireMiscEventKeys(("deleted version", "changed version overview")) +# +# return +# +# if versionNumber == self.trashBags[-1].versionNumber: +# # Delete newest +# +# # We can assume here that len(self.trashBags) >= 2 otherwise +# # previous "if" would have been true. +# +# prevHeadEntry = self.trashBags[-2] +# newContent = self.getVersionContentRaw(prevHeadEntry.versionNumber) +# +# unifName = u"versioning/packet/versionNo/%s/%s" % (prevHeadEntry.versionNumber, +# self.basePage.getUnifiedPageName()) +# prevHeadEntry.contentDifferencing = "complete" +# self.wikiDocument.storeDataBlock(unifName, newContent, +# storeHint=self.getStorageHint()) +# +# unifName = u"versioning/packet/versionNo/%s/%s" % (versionNumber, +# self.basePage.getUnifiedPageName()) +# self.wikiDocument.deleteDataBlock(unifName) +# del self.trashBags[-1] +# self.fireMiscEventKeys(("deleted version", "changed version overview")) +# +# return +# +# # Delete some version in-between: Not supported yet. +# raise InternalError("In-between version %s to delete." % +# versionNumber) + + + + + + +# class WikiPageSnapshot(AbstractWikiPage): +# def __init__(self, wikiDocument, baseWikiPage, versionNo): +# AbstractWikiPage.__init__(self, wikiDocument, baseWikiPage.getWikiWord()) +# +# self.baseWikiPage = baseWikiPage +# self.versionNumber = versionNo +# +# self.content = self.baseWikiPage.getVersionOverview().getVersionContent( +# versionNo) +# +# +# def getSnapshotBaseDocPage(self): +# return self.baseWikiPage +# +# def getSnapshotVersionNumber(self): +# return self.versionNumber +# +# +# def getContent(self): +# return self.content +# +# +# def getUnifiedPageName(self): +# if self.versionNumber == 0: +# return None +# +# return u"versioning/version/versionNo/%s/%s" % (self.versionNumber, +# self.baseWikiPage.getWikiWord()) +# +# +# def isReadOnlyEffect(self): +# """ +# Return true if page is effectively read-only, this means +# "for any reason", regardless if error or intention. +# """ +# return True +# +# +# def getVersionOverview(self): +# return self.baseWikiPage.getVersionOverview() +# +# def getExistingVersionOverview(self): +# return self.baseWikiPage.getExistingVersionOverview() +# +# def setPresentation(self, data, startPos): +# """ +# Set (a part of) the presentation tuple. This is silently ignored +# if the "write access failed" or "read access failed" flags are +# set in the wiki document. +# data -- tuple with new presentation data +# startPos -- start position in the presentation tuple which should be +# overwritten with data. +# """ +# pass # TODO? diff --git a/lib/pwiki/ViHelper.py b/lib/pwiki/ViHelper.py new file mode 100644 index 00000000..7113e5d8 --- /dev/null +++ b/lib/pwiki/ViHelper.py @@ -0,0 +1,615 @@ +import wx +import SystemInfo +from wxHelper import GUI_ID, getAccelPairFromKeyDown +from collections import defaultdict + +#TODO: Multiple registers + +class ViHelper(): + """ + Base class for ViHandlers to inherit from. + + Contains code and functions that are relevent to VI emulation in + both editor and preview mode. + """ + # Modes + # Current these are only (partly) implemented for the editor + NORMAL, INSERT, VISUAL, REPLACE = range(4) + + MODE_TEXT = { 0 : u"", 1 : u"--INSERT--", 2 : u"--VISUAL--", 3 : u"REPLACE" } + + def __init__(self, ctrl): + # ctrl is WikiTxtCtrl in the case of the editor, + # WikiHtmlViewWk for the preview mode. + self.ctrl = ctrl + + self.mode = 0 + + self.pre_motion_key = None + self.pre_key = None + + # When true pre_keys are ignored, as happens if a non pre modifier + # is active + self.block_pre_keys = False + + self.key_modifier = [] + self.key_number_modifier = [] # holds the count + + self.count = 1 + self.true_count = False + + self.hintDialog = None + + self.marks = defaultdict(dict) + + self.last_forward_find_cmd = None + self.last_backward_find_cmd = None + + self.last_cmd = None + self.insert_action = [] + + def SetCount(self): + self.count = 1 + self.true_count = False # True if count is specified + if len(self.key_number_modifier) > 0: + self.count = int("".join(map(str, self.key_number_modifier))) + + # Set a max count + if self.count > 10000: + self.count = 10000 + self.true_count = True + + def GetCharFromCode(self, keycode): + """ + Converts keykeycode to unikeycode character. If no keykeycode is specified + returns an empty string + + @param keycode: raw keycode value + @return: unicode character or empty string + """ + if keycode is not None: + return unichr(keycode) + else: + return u"" + + def Mark(self, code): + """ + Set marks can be any alpha charcter (a-zA-Z) + + @param code: keycode of mark to be set + + # TODO: save marks across sessions? + """ + char = self.GetCharFromCode(code) + if char is not None and char.isalpha(): + self._SetMark(code) + self.visualBell("BLUE") + else: + self.visualBell("RED") + self.updateViStatus() + + def _SetMark(): + """ + Dummy function to be overridden + """ + + def SetDefaultCaretColour(self): + self.default_caret_colour = self.ctrl.GetCaretForeground() + + def GenerateKeyModifiers(self, keys): + """ + Takes dictionary of key combinations and returns those that + can be used to start a two key sequences + + @param keys: see self.keys in derived classes + """ + key_mods = defaultdict(dict) + for j in keys: + key_mods[j] = {} + key_mods[j] = set([i[0] for i in keys[j] if isinstance(i, tuple)]) + return key_mods + + def Repeat(self, func, count=None, arg=None): + """ + Base function called if a command needs to be repeated + a number of times. + + @param func: function to be run + @param count: number of times to run the function, if not specified + manually will use the input count (which defaults to 1) + @param arg: argument to run function with, can be single or multiple + arguments in the form of a dict + """ + if count is None: + count = self.count + for i in range(count): + if arg is not None: + if type(arg) == dict: + func(**arg) + else: + func(arg) + else: + func() + + def RunFunction(self, key, pre_motion_key=None): + """ + Called when a key command is run + + keys is a dictionary which holds the "key" and its + respective function. + + """ + keys = self.keys[self.mode] + + com_type, command, repeatable = keys[key] + func, args = command + + # If a "pre motion" key has been pressed it must be followed by a motion. + if pre_motion_key is not None: + if com_type != 1: # Not a motion + return False + # Is it repeatable? + repeatable = self.pre_keys[self.mode][pre_motion_key][2] + + # If in visual mode we don't want to change the selection start point + if self.mode != ViHelper.VISUAL: + # Otherwise the "pre motion" commands work by setting a start point + # at the current positions, running the motion command and + # finishing with a "post motion" command, i.e. deleting the + # text that was selected. + self.StartSelection() + + # Run the actual function + if type(args) == dict: + ret = func(**args) + elif args is not None: + ret = func(args) + else: + ret = func() + + # If "pre motion" key set run the "post motion" command + if pre_motion_key is not None: + # post motion function + self.pre_keys[self.mode][pre_motion_key][1]() + + # If the command is repeatable save its type and any other settings + if repeatable > 0: + self.last_cmd = repeatable, key, self.count, pre_motion_key + + # Some commands should cause the mode to revert back to normal if run + # from visual mode, others shouldn't. + if self.mode == ViHelper.VISUAL: + if com_type not in [1, 2]: + self.SetMode(ViHelper.NORMAL) + else: + self.SelectSelection() + + # Is this ever used? + if ret is True: + return True + + self.FlushBuffers() + + def FlushBuffers(self): + """ + Clear modifiers and start keys so next input will be fresh + + Should be called after (most?) successful inputs and all failed + ones. + """ + self.pre_motion_key = None + self.pre_key = None + self.block_pre_keys = False + self.key_modifier = [] + self.key_number_modifier = [] + self.updateViStatus() + + def minmax(self, a, b): + return min(a, b), max(a, b) + + def updateViStatus(self, force=False): + # can this be right aligned? + mode = self.mode + text = u"" + if mode in self.keys: + if (len(self.key_modifier) == 1 and \ + self.key_modifier[0] in self.key_mods[mode]) or \ + tuple(self.key_modifier) in self.keys[mode] or force: + mode = ViHelper.MODE_TEXT[self.mode] + count = u"".join(map(str, self.key_number_modifier)) + pre_motion_key = self.GetCharFromCode(self.pre_motion_key) + pre_key = self.GetCharFromCode(self.pre_key) + key_modifier = u"".join(self.GetCharFromCode(i) \ + for i in self.key_modifier) + + text = u"{0}{1}{2}{3}{4}".format( + mode, count, pre_motion_key, pre_key, key_modifier) + + self.ctrl.presenter.getMainControl().statusBar.SetStatusText(text , 0) + +#----------------------------------------------------------------------------- +# The following functions are common to both preview and editor mode +# ----------------------------------------------------------------------------- + + def SwitchEditorPreview(self, scName=None): + mainControl = self.ctrl.presenter.getMainControl() + mainControl.setDocPagePresenterSubControl(scName) + + def StartSearch(self): + # TODO: customise the search to make it more vim-like + text = self.ctrl.GetSelectedText() + text = text.split("\n", 1)[0] + text = text[:30] + self.ctrl.startIncrementalSearch(text) + + def GoForwardInHistory(self): + pageHistDeepness = self.ctrl.presenter.getPageHistory().getDeepness()[1] + if pageHistDeepness == 0: + self.visualBell() + return + self.ctrl.presenter.getPageHistory().goInHistory(self.count) + + def GoBackwardInHistory(self): + pageHistDeepness = self.ctrl.presenter.getPageHistory().getDeepness()[0] + if pageHistDeepness == 0: + self.visualBell() + return + self.ctrl.presenter.getPageHistory().goInHistory(-self.count) + + def ViewParents(self, direct=False): + """ + Note: the way this works may change in the future + """ + presenter = self.ctrl.presenter + word = self.ctrl.presenter.getWikiWord() + + # If no parents give a notification and exit + if len(presenter.getMainControl().getWikiData(). \ + getParentRelationships(word)) == 0: + + self.visualBell() + return + + path = [] + if direct: + # Is it better to open each page? (slower but history recorded) + for n in range(self.count): + parents = presenter.getMainControl().getWikiData().getParentRelationships(word) + + if len(parents) == 1: + + # No need to loop if two pages are each others parents + # Loop will end as soon as we are about to go back to + # a page we were just on (3+ page loops can still occur) + if n > 0 and path[n-1] == word: + n = self.count-1 + else: + word = parents[0] + path.append(word) + + if n == self.count-1: + presenter.openWikiPage(word, forceTreeSyncFromRoot=True) + presenter.getMainControl().getMainAreaPanel().\ + showPresenter(presenter) + presenter.SetFocus() + return + else: + presenter.openWikiPage(word, forceTreeSyncFromRoot=True) + presenter.getMainControl().getMainAreaPanel().\ + showPresenter(presenter) + presenter.SetFocus() + break + + presenter.getMainControl().viewParents(word) + + def CopyWikiWord(self): + """ + Copy current wikiword to clipboard + """ + + def SwitchTabs(self, left=False): + """ + Switch to n(th) tab. + Positive numbers go right, negative left. + + If tab end is reached will wrap around + """ + n = self.count + + if left: n = -n + + mainAreaPanel = self.ctrl.presenter.getMainControl().getMainAreaPanel() + pageCount = mainAreaPanel.GetPageCount() + currentTabNum = mainAreaPanel.GetSelection() + 1 + + if currentTabNum + n > pageCount: + newTabNum = currentTabNum + n % pageCount + if newTabNum > pageCount: + newTabNum -= pageCount + elif currentTabNum + n < 1: + newTabNum = currentTabNum - (pageCount - n % pageCount) + if newTabNum < 1: + newTabNum += pageCount + else: + newTabNum = currentTabNum + n + + # Switch tab + mainAreaPanel.SetSelection(newTabNum-1) + mainAreaPanel.presenters[mainAreaPanel.GetSelection()].SetFocus() + + def CloseCurrentTab(self): + """ + Closes currently focused tab + """ + mainAreaPanel = self.ctrl.presenter.getMainControl().getMainAreaPanel() + mainAreaPanel.closePresenterTab(mainAreaPanel.getCurrentPresenter()) + return True + + def OpenHomePage(self, inNewTab=False): + """ + Opens home page. + + If inNewTab=True opens in a new forground tab + """ + presenter = self.ctrl.presenter + + wikiword = presenter.getMainControl().getWikiDocument().getWikiName() + + if inNewTab: + presenter = self.ctrl.presenter.getMainControl().\ + createNewDocPagePresenterTab() + presenter.switchSubControl("preview", False) + + + # Now open wiki + presenter.openWikiPage(wikiword, forceTreeSyncFromRoot=True) + presenter.getMainControl().getMainAreaPanel().\ + showPresenter(presenter) + presenter.SetFocus() + +#-------------------------------------------------------------------- +# Misc commands +#-------------------------------------------------------------------- + def visualBell(self, colour="RED"): + """ + Display a visual sign to alert user input has been + recieved + """ + sb = self.ctrl.presenter.getMainControl().GetStatusBar() + + rect = sb.GetFieldRect(0) + if SystemInfo.isOSX(): + # needed on Mac OSX to avoid cropped text + rect = wx._core.Rect(rect.x, rect.y - 2, rect.width, rect.height + 4) + + rect.SetPosition(sb.ClientToScreen(rect.GetPosition())) + + bell = ViVisualBell(self.ctrl, -1, rect, colour) + + +# I will move this to wxHelper later (MB) +try: + class wxPopupOrFrame(wx.PopupWindow): + def __init__(self, parent, id=-1, style=None): + wx.PopupWindow.__init__(self, parent) + +except AttributeError: + class wxPopupOrFrame(wx.Frame): + def __init__(self, parent, id=-1, + style=wx.NO_BORDER|wx.FRAME_NO_TASKBAR|wx.FRAME_FLOAT_ON_PARENT): + wx.Frame.__init__(self, parent, id, style=style) + + +# NOTE: is popup window available on macs yet? +class ViVisualBell(wxPopupOrFrame): + """ + Popupwindow designed to cover the status bar. + + Its intention is to give visual feedback that a command has + been received in cases where no other visual change is observed + """ + + COLOURS = { + "RED" : wx.Colour(255, 0, 0), + "GREEN" : wx.Colour(0, 255, 0), + "YELLOW" : wx.Colour(255, 255, 0), + "BLUE" : wx.Colour(0, 0, 255), + } + + + def __init__(self, parent, id, rect, colour="RED", close_delay=100): + wxPopupOrFrame.__init__(self, parent) + self.SetPosition(rect.GetPosition()) + self.SetSize(rect.GetSize()) + self.SetBackgroundColour(ViVisualBell.COLOURS[colour]) + self.Show() + + wx.EVT_TIMER(self, GUI_ID.TIMER_VISUAL_BELL_CLOSE, + self.OnClose) + + self.closeTimer = wx.Timer(self, GUI_ID.TIMER_VISUAL_BELL_CLOSE) + self.closeTimer.Start(close_delay, True) + + + def OnClose(self, evt): + #self.timer.Stop() + self.Destroy() + + + +class ViHintDialog(wx.Frame): + + COLOR_YELLOW = wx.Colour(255, 255, 0); + COLOR_GREEN = wx.Colour(0, 255, 0); + COLOR_DARK_GREEN = wx.Colour(0, 100, 0); + COLOR_RED = wx.Colour(255, 0, 0); + + def __init__(self, parent, id, viCtrl, rect, font, \ + mainControl, tabMode=0, primary_link=None): + # Frame title is invisible but is helpful for workarounds with + # third-party tools + wx.Frame.__init__(self, parent, id, u"WikidPad Hints", + rect.GetPosition(), rect.GetSize(), + wx.NO_BORDER | wx.FRAME_FLOAT_ON_PARENT) + + self.tabMode = tabMode + + self.parent = parent + + self.primary_link = primary_link + + self.viCtrl = viCtrl + self.mainControl = mainControl + self.tfInput = wx.TextCtrl(self, GUI_ID.INC_SEARCH_TEXT_FIELD, + _(u"Follow Hint:"), style=wx.TE_PROCESS_ENTER | wx.TE_RICH) + + self.tfInput.SetFont(font) + + # Use a different colour if links are being opened in a different tab + if tabMode == 0: + self.colour = ViHintDialog.COLOR_GREEN + else: + self.colour = ViHintDialog.COLOR_DARK_GREEN + + self.tfInput.SetBackgroundColour(self.colour) + mainsizer = wx.BoxSizer(wx.HORIZONTAL) + mainsizer.Add(self.tfInput, 1, wx.ALL | wx.EXPAND, 0) + + self.SetSizer(mainsizer) + self.Layout() + self.tfInput.SelectAll() #added for Mac compatibility + self.tfInput.SetFocus() + + config = self.mainControl.getConfig() + + # Just use the same delays as incSearch + self.closeDelay = 1000 * config.getint("main", "incSearch_autoOffDelay", + 0) # Milliseconds to close or 0 to deactivate + + wx.EVT_TEXT(self, GUI_ID.INC_SEARCH_TEXT_FIELD, self.OnText) + wx.EVT_KEY_DOWN(self.tfInput, self.OnKeyDownInput) + wx.EVT_KILL_FOCUS(self.tfInput, self.OnKillFocus) + wx.EVT_TIMER(self, GUI_ID.TIMER_INC_SEARCH_CLOSE, + self.OnTimerIncSearchClose) + wx.EVT_MOUSE_EVENTS(self.tfInput, self.OnMouseAnyInput) + + if self.closeDelay: + self.closeTimer = wx.Timer(self, GUI_ID.TIMER_INC_SEARCH_CLOSE) + self.closeTimer.Start(self.closeDelay, True) + +# def Close(self): +# wx.Frame.Close(self) +# self.txtCtrl.SetFocus() + + + def OnKillFocus(self, evt): + self.viCtrl.forgetFollowHint() + self.Close() + + def OnText(self, evt): + self.viCtrl.searchStr = self.tfInput.GetValue() + link_number, link = self.viCtrl.executeFollowHint(self.tfInput.GetValue()) + + if link_number < 1: + # Nothing found + self.tfInput.SetBackgroundColour(ViHintDialog.COLOR_RED) + elif link_number == 1: + # Single link found + # launch it and finish + self.tfInput.SetBackgroundColour(self.colour) + self.parent._activateLink(link, tabMode=self.tabMode) + self.Close() + else: + # Multiple links found + self.tfInput.SetBackgroundColour(self.colour) + + self.primary_link = link + def OnMouseAnyInput(self, evt): +# if evt.Button(wx.MOUSE_BTN_ANY) and self.closeDelay: + + # Workaround for name clash in wx.MouseEvent.Button: + if wx._core_.MouseEvent_Button(evt, wx.MOUSE_BTN_ANY) and self.closeDelay: + # If a mouse button was pressed/released, restart timer + self.closeTimer.Start(self.closeDelay, True) + + evt.Skip() + + + def OnKeyDownInput(self, evt): + if self.closeDelay: + self.closeTimer.Start(self.closeDelay, True) + + key = evt.GetKeyCode() + accP = getAccelPairFromKeyDown(evt) + matchesAccelPair = self.mainControl.keyBindings.matchesAccelPair + + searchString = self.tfInput.GetValue() + + foundPos = -2 + if accP in ((wx.ACCEL_NORMAL, wx.WXK_NUMPAD_ENTER), + (wx.ACCEL_NORMAL, wx.WXK_RETURN)): + # Return pressed + self.viCtrl.endFollowHint() + if self.primary_link is not None: + self.parent._activateLink(self.primary_link, tabMode=self.tabMode) + + self.Close() + elif accP == (wx.ACCEL_NORMAL, wx.WXK_ESCAPE): + # Esc -> Abort inc. search, go back to start + self.viCtrl.resetFollowHint() + self.Close() + elif matchesAccelPair("ContinueSearch", accP): + foundPos = self.viCtrl.executeFollowHint(searchString) + # do the next search on another ctrl-f + elif matchesAccelPair("StartFollowHint", accP): + foundPos = self.viCtrl.executeFollowHint(searchString) + elif accP in ((wx.ACCEL_NORMAL, wx.WXK_DOWN), + (wx.ACCEL_NORMAL, wx.WXK_PAGEDOWN), + (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_DOWN), + (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_PAGEDOWN), + (wx.ACCEL_NORMAL, wx.WXK_NEXT)): + foundPos = self.viCtrl.executeFollowHint(searchString) + elif matchesAccelPair("ActivateLink", accP): + # ActivateLink is normally Ctrl-L + self.viCtrl.endFollowHint() + self.Close() + self.viCtrl.OnKeyDown(evt) + elif matchesAccelPair("ActivateLinkNewTab", accP): + # ActivateLinkNewTab is normally Ctrl-Alt-L + self.viCtrl.endFollowHint() + self.Close() + self.viCtrl.OnKeyDown(evt) + elif matchesAccelPair("ActivateLink2", accP): + # ActivateLink2 is normally Ctrl-Return + self.viCtrl.endFollowHint() + self.Close() + self.viCtrl.OnKeyDown(evt) + elif matchesAccelPair("ActivateLinkBackground", accP): + # ActivateLinkNewTab is normally Ctrl-Alt-L + self.viCtrl.endFollowHint() + self.Close() + self.viCtrl.OnKeyDown(evt) + # handle the other keys + else: + evt.Skip() + + if foundPos == False: + # Nothing found + self.tfInput.SetBackgroundColour(ViHintDialog.COLOR_YELLOW) + else: + # Found + self.tfInput.SetBackgroundColour(self.colour) + + # Else don't change + + if SystemInfo.isOSX(): + # Fix focus handling after close + def Close(self): + wx.Frame.Close(self) + wx.CallAfter(self.viCtrl.SetFocus) + + def OnTimerIncSearchClose(self, evt): + self.viCtrl.endFollowHint() # TODO forgetFollowHint() instead? + self.Close() + + diff --git a/lib/pwiki/WikiHtmlView.py b/lib/pwiki/WikiHtmlView.py index 6018415c..18ff444a 100644 --- a/lib/pwiki/WikiHtmlView.py +++ b/lib/pwiki/WikiHtmlView.py @@ -295,7 +295,7 @@ def gotoAnchor(self, anchor): self.refresh() - def getSelectedText(self): + def GetSelectedText(self): return self.SelectionToText() diff --git a/lib/pwiki/WikiHtmlViewIE.py b/lib/pwiki/WikiHtmlViewIE.py index e6effc5d..0b43f7a2 100644 --- a/lib/pwiki/WikiHtmlViewIE.py +++ b/lib/pwiki/WikiHtmlViewIE.py @@ -301,7 +301,7 @@ def gotoAnchor(self, anchor): # self.outOfSync = True self.refresh() - def getSelectedText(self): + def GetSelectedText(self): return self.GetStringSelection(False) diff --git a/lib/pwiki/WikiHtmlViewWK.py b/lib/pwiki/WikiHtmlViewWK.py index 80967fa2..1f636b7f 100644 --- a/lib/pwiki/WikiHtmlViewWK.py +++ b/lib/pwiki/WikiHtmlViewWK.py @@ -30,6 +30,8 @@ # pywebkitgtk (http://code.google.com/p/pywebkitgtk/) import webkit +from ViHelper import ViHintDialog, ViHelper + # used in search import SystemInfo @@ -425,6 +427,7 @@ def realizeIfNeeded(self): self.scrollDeferred(lx, ly) self.keyProcessWxWindow.realize() + self.html.ctrl.grab_focus() def __on_button_press_event(self, view, gtkEvent): @@ -466,7 +469,9 @@ def __on_key_press_event(self, view, gtkEvent): self.html.ctrl.grab_focus() finally: self.html.ctrl.handler_unblock(self.focusEventConn) - return True #False? + # Return False if not handled otherwise no default gtk + # bindings (such as arrow keys) will word + return False def __on_key_press_event_vi(self, view, gtkEvent): @@ -486,6 +491,7 @@ def __on_key_press_event_vi(self, view, gtkEvent): Current implemented commands: + NOTE: this list is not upto date key(s) : action -------------------------------------------------------- @@ -498,6 +504,14 @@ def __on_key_press_event_vi(self, view, gtkEvent): defaults to bottom of page (count) gg : scroll to (count)% of page defaults to top of page + {count} % : scroll to {count}% of page + + 0 : scrolls to absolute left of document + (count) ^ : scrolls to (count)% of document + horizontally. Defaults to 0% (same as + 0) + (count) $ : scrolls to (count)% of document + horizontally. Defaults to 100% gh : goto home page gH : open home in new forground tab @@ -511,6 +525,11 @@ def __on_key_press_event_vi(self, view, gtkEvent): (count) L : go forward in history count times gu : display parents dialog (Ctrl + Up) + (count) gU : same as gu but if only one parent defined + skip the dialog and open it directly. If + count keep going until no or more than one + parent reached (intervening pages are not + recorded in history). o : open wikipage (Ctrl + o) @@ -519,12 +538,13 @@ def __on_key_press_event_vi(self, view, gtkEvent): dd : close current tab (as opposed to single "d" in pentadactyl) - return : follow link (if selected) + Return : follow link (if selected) Ctrl+[ or Esc : clear selection (and/or count/modifier) + + y : copy selected wikiword to clipboard (not implemented) + Y : copy selected text to clipboard - TODO: link navigation on f and F - move functions out of here!!! """ self.realizeIfNeeded() @@ -539,40 +559,31 @@ def __on_key_press_event_vi(self, view, gtkEvent): if gtkEvent.state & gtk.gdk.CONTROL_MASK: # Ctrl control_mask = True -# if control_mask: -# if key == 102: # f -# self.startIncrementalSearch() - if key == 65307: # Escape self.html.ClearSelection() - vi.key_modifier = [] - vi.key_number_modifier = [] + vi.FlushBuffers() + m = vi.mode if 48 <= key <= 57: # Normal - self.vi.SetNumber(key-48) - return True + if self.vi.SetNumber(key-48): + return True elif 65456 <= key <= 65465: # Numpad - self.vi.SetNumber(key-65456) - return True + if self.vi.SetNumber(key-65456): + return True + # If control add it to keys + if control_mask: + key = ("ctrl", key) # Currently only supports single modifier (i.e. 2 key commands) - if len(vi.key_modifier) == 0 and key in vi.key_mods: + if len(vi.key_modifier) == 0 and key in vi.key_mods[m]: vi.key_modifier.append(key) vi.updateViStatus() return True # Set count to be used - vi.count = 1 - vi.true_count = False # True if count is specified - if len(vi.key_number_modifier) > 0: - vi.count = int("".join(map(str, vi.key_number_modifier))) - - # Set a max count - if vi.count > 10000: - vi.count = 10000 - vi.true_count = True + vi.SetCount() # Double key commands, e.g. gg, gU if len(vi.key_modifier) == 1: @@ -583,11 +594,8 @@ def __on_key_press_event_vi(self, view, gtkEvent): vi.updateViStatus() - # If control add it to keys - if control_mask: - key = ("ctrl", key) - if key in vi.keys: + if key in vi.keys[m]: vi.RunFunction(key) return True @@ -600,23 +608,27 @@ def __on_key_press_event_vi(self, view, gtkEvent): # Now send to self.keyProcessWxWindow to let wxPython translate # and process the key event if self.scrolled_window: - gtkEvent = gtkEvent.copy() - self.html.ctrl.handler_block(self.focusEventConn) - try: - self.keyProcessWxWindow.gtkMyself.grab_focus() - self.keyProcessWxWindow.gtkMyself.emit("key-press-event", gtkEvent) - self.html.ctrl.grab_focus() - finally: - self.html.ctrl.handler_unblock(self.focusEventConn) - return True #False? - +# # +# # NOTE: The following code causes the webkit ctrl to loose focus +# # and I can't work out how to gain it again +# # +# gtkEvent = gtkEvent.copy() +# self.html.ctrl.handler_block(self.focusEventConn) +# try: +# self.keyProcessWxWindow.gtkMyself.grab_focus() +# self.keyProcessWxWindow.gtkMyself.emit("key-press-event", gtkEvent) +# # Seems to work but doesn't always according to Ross (MB): +# self.html.ctrl.grab_focus() +# finally: +# self.html.ctrl.handler_unblock(self.focusEventConn) + return False def FollowLinkIfSelected(self): """ - A bit of a hack (there must be a better way to achieve - the same results) - - Should be able to select link (as with tab navigation) + Only necessary if we want custom keybindings (e.g. ctrl-l) + to be able to follow link. May be better to remap them to + activate selected element (like return does - I don't know + how to do this at the moment) or to rewrite with javascript? Gets selected html, parses it and opens the first link found. @@ -644,8 +656,10 @@ def __on_navigate(self, view, frame, request, action, decision): if frame.get_parent() is not None and \ self.presenter.getConfig().getboolean("main", "html_preview_ieShowIframes", True): - return False - + # Yeah this is probably not the best way to do this... + if str(action.get_reason()) == "": + return False + # Return True to block navigation, i.e. launching a url return self._activateLink(uri, tabMode=0) @@ -740,7 +754,7 @@ def __on_populate_popup(self, view, menu): ## Add item to copy selection (doesn't normally exist in ## webkit apparently) - #if self.getSelectedText() is not None: + #if self.GetSelectedText() is not None: # copy_menu_item = gtk.ImageMenuItem(gtk.STOCK_COPY) # copy_menu_item.get_children()[0].set_label( # wxToGtkLabel(_('Copy &Selected Text'))) @@ -809,6 +823,11 @@ def __on_populate_popup(self, view, menu): menu.show_all() def __on_scroll_event(self, widget, evt): + + if self.vi is not None and self.vi.hintDialog is not None: + self.vi.clearHints() + self.vi.hintDialog.Close() + # If ctrl is pressed if evt.state & gtk.gdk.CONTROL_MASK: if evt.direction == gtk.gdk.SCROLL_UP: @@ -834,10 +853,6 @@ def __on_expose_event(self, view, *params): return self.freezeCount > 0 def startIncrementalSearch(self, initSearch=None): -# # Should this be defined here or ealier? -# selected_text = self.getSelectedText() -# if len(selected_text) > 0: -# initSearch = selected_text sb = self.presenter.getMainControl().GetStatusBar() @@ -864,10 +879,26 @@ def executeIncrementalSearch(self, text): search_text arguments: (string to search for, case_sensitive, forward, wrap) + + Focuses selected elements """ if len(text) > 0: - return self.html.getWebkitWebView().search_text(text, False, True, True) + result = self.html.getWebkitWebView().search_text(text, False, True, True) + + # Focus selected elements + # Might this fail is some cases? + self.html.getWebkitWebView().execute_script(''' + var selectionRange = window.getSelection (); + + if (selectionRange.rangeCount > 0) { + var range = selectionRange.getRangeAt (0); + container = range.commonAncestorContainer; + } + + container.parentNode.focus(); + ''') + return result else: self.html.ClearSelection() @@ -1133,7 +1164,6 @@ def refresh(self): if self.scrolled_window != None and not self.html.ctrl.is_focus(): self.SetFocus() self.html.ctrl.grab_focus() - #self.grab_focus() ## _prof.stop() @@ -1153,7 +1183,8 @@ def getSelectedHTML(self): ret = self.html.getWebkitWebView().selection_convert("PRIMARY", "text/html") return self.selectedHTML - def getSelectedText(self): + # NOTE: capitalized to maintain consistency with WikiTxtCtrl + def GetSelectedText(self): """ Returns the currently selected text (plain) @@ -1427,6 +1458,7 @@ def _activateLink(self, href, tabMode=0): # N_(u"Activate New Tab Backgrd.") class ExtractUrlFromHTML(HTMLParser): + """HTML Parser designed to extract urls from html""" def __init__(self): HTMLParser.__init__(self) self.urls = [] @@ -1506,193 +1538,115 @@ def ClearSelection(self): self.ctrl.execute_script('window.getSelection().removeAllRanges()') -class ViFunctions(): +class ViFunctions(ViHelper): def __init__(self, view): - self.view = view - - self.key_modifier = [] - self.key_number_modifier = [] - - # Should probably automate this - self.key_map = { # used to display modifiers - 72 : "H", - 84 : "T", - 90 : "Z", - 100 : "d", - 103 : "g", - 104 : "h", - 116 : "t", - 117 : "u" - } - - # Holds keys which can be used as primary modifiers (e.g. g) - self.key_mods = [90, 103, 100] - - self.keys = { - # Format is - # key combination : (function to call, function arguments) - (103, 103) : (self.DocumentNavigation, (103, 103)), # gg - (103, 117) : (self.ViewParents, None), # gu - (103, 116) : (self.SwitchTabs, None), # gt - (103, 84) : (self.SwitchTabs, True), # gT - (103, 104) : (self.OpenHomePage, False), # gh - (103, 72) : (self.OpenHomePage, True), # gH - - (100, 100) : (self.CloseCurrentTab, None), # dd - - (90, 90) : (self.view.presenter.getMainControl().exitWiki, None), # ZZ - - # ctrl + - - ("ctrl", 91) : (self.view.html.ClearSelection, None), # Ctrl + [ - ("ctrl", 100) : (self.HalfPageJumpDown, None), # Ctrl + d - ("ctrl", 117) : (self.HalfPageJumpUp, None), # Ctrl + u - - # single keys - (47) : (self.view.startIncrementalSearch, None), # / - (72) : (self.GoBackwardInHistory, None), # H - (76) : (self.GoForwardInHistory, None), # L - (111) : (self.view.presenter.getMainControl().showWikiWordOpenDialog, None), # o - (79) : (self.view.presenter.getMainControl().showWikiWordOpenDialog, None), # O - (106) : (self.DocumentNavigation, 106), # j - (107) : (self.DocumentNavigation, 107), # k - (104) : (self.DocumentNavigation, 104), # h - (108) : (self.DocumentNavigation, 108), # l - (71) : (self.DocumentNavigation, 71), # G - (65293) : (self.view.FollowLinkIfSelected, None), # return + ViHelper.__init__(self, view) + + # Currently we only have one mode whilst in preview + self.keys = { 0 : { + (103, 117) : (0, (self.ViewParents, False), 0), # gu + (103, 85) : (0, (self.ViewParents, True), 0), # gU + (103, 116) : (0, (self.SwitchTabs, None), 0), # gt + (103, 84) : (0, (self.SwitchTabs, True), 0), # gT + (103, 114) : (0, (self.OpenHomePage, False), 0), # gr + (103, 82) : (0, (self.OpenHomePage, True), 0), # gR + + (100, 100) : (0, (self.CloseCurrentTab, None), 0), # dd + + (90, 90) : (0, (self.ctrl.presenter.getMainControl().exitWiki, None), 0), # ZZ + + # ctrl + + + ("ctrl", 91) : (0, (self.ctrl.html.ClearSelection, None), 0), # Ctrl + [ + ("ctrl", 100) : (0, (self.HalfPageJumpDown, None), 0), # Ctrl + d + ("ctrl", 117) : (0, (self.HalfPageJumpUp, None), 0), # Ctrl + u + ("ctrl", 108) : (0, (self.ctrl.FollowLinkIfSelected, None), 0), # Ctrl + l + + 47 : (0, (self.StartSearch, None), 0), # / + # H and L are equivelent to gh and gl in preview mode + 72 : (0, (self.GoBackwardInHistory, None), 0), # H + 76 : (0, (self.GoForwardInHistory, None), 0), # L + (103, 72) : (0, (self.GoBackwardInHistory, None), 0), # gH + (103, 76) : (0, (self.GoForwardInHistory, None), 0), # gL + (103, 104) : (0, (self.GoBackwardInHistory, None), 0), # gh + (103, 108) : (0, (self.GoForwardInHistory, None), 0), # gl + + 72 : (0, (self.GoBackwardInHistory, None), 0), # H + 76 : (0, (self.GoForwardInHistory, None), 0), # L + 111 : (0, (self.ctrl.presenter.getMainControl()). \ + showWikiWordOpenDialog, None, 0), # o + 79 : (0, (self.ctrl.presenter.getMainControl()). \ + showWikiWordOpenDialog, None, 0), # O + 106 : (0, (self.DocumentNavigation, 106), 0), # j + 107 : (0, (self.DocumentNavigation, 107), 0), # k + 104 : (0, (self.DocumentNavigation, 104), 0), # h + 108 : (0, (self.DocumentNavigation, 108), 0), # l + (103, 103) : (0, (self.DocumentNavigation, (103, 103)), 0), # gg + 71 : (0, (self.DocumentNavigation, 71), 0), # G + 37 : (0, (self.DocumentNavigation, 37), 0), # % + 36 : (0, (self.DocumentNavigation, 36), 0), # $ + 94 : (0, (self.DocumentNavigation, 94), 0), # ^ + 48 : (0, (self.DocumentNavigation, 48), 0), # 0 + 102 : (0, (self.startFollowHint, 0), 0), # f + 70 : (0, (self.startFollowHint, 2), 0), # F + 89 : (0, (self.ctrl.OnClipboardCopy, None), 0), # Y + 117 : (0, (self.CopyWikiWord, None), 0), # y + #65293 : (self.ctrl.FollowLinkIfSelected, None), # return + + (103, 115) : (0, (self.SwitchEditorPreview, None), 0), # gs + (103, 101) : (0, (self.SwitchEditorPreview, "textedit"), 0), # ge + (103, 112) : (0, (self.SwitchEditorPreview, "preview"), 0), # gp + (65470) : (0, (self.SwitchEditorPreview, "textedit"), 0), # F1 + (65471) : (0, (self.SwitchEditorPreview, "preview"), 0), # F2 + + } } - self.count = 1 - self.true_count = False - + # Generate possible key modifiers + self.key_mods = self.GenerateKeyModifiers(self.keys) # Vi Helper functions - def RunFunction(self, key): + def SetMode(self, mode): """ - Called when a key command is run - - keys is a dictionary which holds the "key" and its - respective function. + Dummy function for now - key and count buffers are always cleared + May be used in the future """ - keys = self.keys - func, args = keys[key] - - if type(args) == dict: - ret = func(**args) - elif args is not None: - ret = func(args) - else: - ret = func() - - if ret is True: - return True - - self.key_modifier = [] - self.key_number_modifier = [] - self.updateViStatus() - - def GoForwardInHistory(self): - self.view.presenter.getPageHistory().goInHistory(self.count) - - def GoBackwardInHistory(self): - self.view.presenter.getPageHistory().goInHistory(-self.count) - - def ViewParents(self): - self.view.presenter.getMainControl().viewParents( - self.view.currentLoadedWikiWord) - - def SwitchTabs(self, left=False): - """ - Switch to n(th) tab. - Positive numbers go right, negative left. - - If tab end is reached will wrap around - """ - n = self.count - - if left: n = -n - - mainAreaPanel = self.view.presenter.getMainControl().getMainAreaPanel() - pageCount = mainAreaPanel.GetPageCount() - currentTabNum = mainAreaPanel.GetSelection() + 1 - - if currentTabNum + n > pageCount: - newTabNum = currentTabNum + n % pageCount - if newTabNum > pageCount: - newTabNum -= pageCount - elif currentTabNum + n < 1: - newTabNum = currentTabNum - (pageCount - n % pageCount) - if newTabNum < 1: - newTabNum += pageCount - else: - newTabNum = currentTabNum + n - - # Switch tab - mainAreaPanel.SetSelection(newTabNum-1) - mainAreaPanel.presenters[mainAreaPanel.GetSelection()].SetFocus() - - def CloseCurrentTab(self): - """ - Closes currently focused tab - """ - mainAreaPanel = self.view.presenter.getMainControl().getMainAreaPanel() - mainAreaPanel.closePresenterTab(mainAreaPanel.getCurrentPresenter()) - return True - - def OpenHomePage(self, inNewTab=False): - """ - Opens home page. - - If inNewTab=True opens in a new forground tab - """ - presenter = self.view.presenter - - wikiword = presenter.getMainControl().getWikiDocument().getWikiName() - - if inNewTab: - presenter = self.view.presenter.getMainControl().\ - createNewDocPagePresenterTab() - presenter.switchSubControl("preview", False) - - - # Now open wiki - presenter.openWikiPage(wikiword, forceTreeSyncFromRoot=True) - presenter.getMainControl().getMainAreaPanel().\ - showPresenter(presenter) - presenter.SetFocus() # Document navigation def HalfPageJumpDown(self): - adj = self.view.scrolled_window.get_vadjustment() + adj = self.ctrl.scrolled_window.get_vadjustment() y = adj.get_value() - if y+adj.get_page_size()/2 < adj.upper(): - self.view.adj.set_value(y+adj.get_page_size()/2) + jump = self.count*adj.get_page_size()/2 + + if y+jump < adj.get_upper(): + adj.set_value(y+jump) else: - self.view.adj.set_value(adj.upper()-adj.get_page_size()) + adj.set_value(adj.get_upper()-adj.get_page_size()) def HalfPageJumpUp(self): - adj = self.view.scrolled_window.get_vadjustment() + adj = self.ctrl.scrolled_window.get_vadjustment() y = adj.get_value() - if y-adj.get_page_size()/2 > adj.upper(): - self.view.adj.set_value(y-adj.get_page_size()/2) + jump = self.count*adj.get_page_size()/2 + + if y-jump > adj.get_lower(): + adj.set_value(y-jump) else: - self.view.adj.set_value(adj.upper()+adj.get_page_size()) + adj.set_value(adj.get_lower()) def DocumentNavigation(self, key): """ function to handle most navigation commonds - currently handles: j, k, h, l, G + currently handles: j, k, h, l, G, gg, 0, $ """ - step_incr = self.view.scrolled_window.get_vadjustment().get_step_increment() - page_incr = self.view.scrolled_window.get_vadjustment().get_page_increment() + step_incr = self.ctrl.scrolled_window.get_vadjustment().get_step_increment() + page_incr = self.ctrl.scrolled_window.get_vadjustment().get_page_increment() - vadj = self.view.scrolled_window.get_vadjustment() - hadj = self.view.scrolled_window.get_hadjustment() + vadj = self.ctrl.scrolled_window.get_vadjustment() + hadj = self.ctrl.scrolled_window.get_hadjustment() y = vadj.get_value() x = hadj.get_value() @@ -1730,7 +1684,7 @@ def DocumentNavigation(self, key): hadj.set_value(mod) # If count is specified go to (count)% of page - elif (key == 71 or key == (103, 103)) and self.true_count: # gg or G + elif (key == 71 or key == (103, 103) or key == 37) and self.true_count: # gg or G if c > 100: c == 100 # 100% is the max vadj.set_value(int((y_upper-y_page_size)/100*c)) @@ -1741,23 +1695,237 @@ def DocumentNavigation(self, key): # gg to 0% elif key == (103, 103): # gg vadj.set_value(y_lower) + + elif (key == 48 or key == 94) and self.true_count: # count + $ or ^ + if c > 100: c == 100 # 100% is the max + hadj.set_value(int((x_upper-x_page_size)/100*c)) + + elif key == 36: # $ + hadj.set_value(x_upper-x_page_size) + + elif key == 94: # ^ + hadj.set_value(x_lower) + + elif key == 48: # 0 + hadj.set_value(x_lower) - def updateViStatus(self, force=False): - # can this be right aligned? - # TODO: sort this out - text = u"" - if (len(self.key_modifier) == 1 and self.key_modifier[0] in self.key_mods) \ - or tuple(self.key_modifier) in self.keys or force: - text = uniToGui(u"{0}{1}".format(u"".join( - map(str, self.key_number_modifier)), - u"".join(self.key_map[i] for i in self.key_modifier))) - - self.view.presenter.getMainControl().statusBar.SetStatusText(text , 0) # Numbers def SetNumber(self, n): + + # If 0 is first modifier it is a command + if len(self.key_number_modifier) < 1 and n == 0: + return False self.key_number_modifier.append(n) self.key_modifier = [] self.updateViStatus(True) + return True + + def clearHints(self): + """ + Loop through all links and remove any custom formating + + Currently if a background was previously set it will be lost + """ + + self.ctrl.html.getWebkitWebView().execute_script( + ''' + //START JAVASCRIPT CODE + var all_links = document.links; + + for (var i=0; i window.pageYOffset && + (left + width) > window.pageXOffset + ); + }} + +var all_links = document.links; + +var visible_links=new Array(); +var links_selected=new Array(); + +for (var i=0; i" + visible_links[i].innerHTML; + + if (!primary) {{ + visible_links[i].style.backgroundColor = "rgb(0,255,0)"; + primary = true; + }} else {{ + visible_links[i].style.backgroundColor = "#FDFF47"; + }} + + }} + }} + +hints = document.getElementsByName("quick-hints") + +//Format the hints +for (var i=0; i 0: + primary_link = self.ctrl.html.getWebkitWebView().get_main_frame().get_title() + else: + primary_link = None + + self.ctrl.html.getWebkitWebView().execute_script('document.title=oldtitle;') + + return link_number, primary_link + + def startFollowHint(self, tabMode=0): + """ + Called to start hint mode. + + Creates the hint dialog and calls highlightLinks() to format + the links. + + If not links are present in the viewport the dialog is not opened, + if a single is present it is activated automatically + """ + + link_number, link = self.highlightLinks() + # If only a single link is present we can launch that and finish + if link_number == 1: + self.ctrl._activateLink(link, tabMode=tabMode) + return + # Or if no links visible on page + elif link_number < 1: + self.visualBell() + return + + sb = self.ctrl.presenter.getMainControl().GetStatusBar() + + rect = sb.GetFieldRect(0) + if SystemInfo.isOSX(): + # needed on Mac OSX to avoid cropped text + rect = wx._core.Rect(rect.x, rect.y - 2, rect.width, rect.height + 4) + + rect.SetPosition(sb.ClientToScreen(rect.GetPosition())) + + self.hintDialog = ViHintDialog(self.ctrl, -1, self, rect, + sb.GetFont(), self.ctrl.presenter.getMainControl(), tabMode, link) + self.hintDialog.Show() + + + def executeFollowHint(self, text): + """ + Processes input text + """ + + return self.highlightLinks(None, text) + + def forgetFollowHint(self): + """ + Called if user just leaves the hint field. + """ + self.clearHints() + self.hintDialog = None + + def resetFollowHint(self): + """ + Called by WebkitSearchDialog before aborting an inc. search. + Called when search was explicitly aborted by user (with escape key) + TODO: Make vi keybinding "Ctrl + [" call this as well + """ + #self.ctrl.html.ClearSelection() + self.clearHints() + self.hintDialog = None + + def endFollowHint(self): + """ + Called if incremental search ended successfully. + """ + self.clearHints() + self.hintDialog = None class KeyProcessWxWindow(wx.Panel): @@ -1826,10 +1994,11 @@ def OnKeyDown(self, evt): # ContinueSearch is normally F3 # Start incremental search # First get selected text and prepare it as default value - text = self.htmlView.getSelectedText() + text = self.htmlView.GetSelectedText() text = text.split("\n", 1)[0] # text = re.escape(text[:30]) text = text[:30] self.htmlView.startIncrementalSearch(text) else: evt.Skip() + diff --git a/lib/pwiki/WikiTreeCtrl.py b/lib/pwiki/WikiTreeCtrl.py index f62a6a54..ddbebb5b 100644 --- a/lib/pwiki/WikiTreeCtrl.py +++ b/lib/pwiki/WikiTreeCtrl.py @@ -69,20 +69,24 @@ class AbstractNode(object): """ __slots__ = ("__weakref__", # just in case... - "treeCtrl", "parentNode", "unifiedName") + "treeCtrl", "wxItemId", "parentNode", "unifiedName") def __init__(self, tree, parentNode): self.treeCtrl = tree self.parentNode = parentNode + self.wxItemId = -1 # self.unifiedName = None - + def setRoot(self, flag = True): """ Sets if this node is a logical root of the tree or not (currently the physical root is the one and only logical root) """ pass - + + def setWxItemId(self, wxItemId): + self.wxItemId = wxItemId + def getParentNode(self): return self.parentNode @@ -113,12 +117,19 @@ def listChildren(self): """ return () - def onActivate(self): + def onSelected(self): """ - React on activation + React on selection of the item """ pass + def onActivated(self): + """ + React on activation (double click). Returns True if event should be + consumed (not handled further) + """ + return False + def prepareContextMenu(self, menu): """ Return a context menu for this item or None @@ -413,7 +424,7 @@ def listChildren(self): return result - def onActivate(self): + def onSelected(self): # tracer.runctx('self.treeCtrl.pWiki.openWikiPage(self.wikiWord)', globals(), locals()) self.treeCtrl.pWiki.openWikiPage(self.wikiWord) @@ -501,9 +512,9 @@ def __init__(self, tree, parentNode, wikiWord, newLabel = None, self.searchOp = searchOp - def onActivate(self): - # WikiWordNode.onActivate(self) - WikiWordRelabelNode.onActivate(self) + def onSelected(self): + # WikiWordNode.onSelected(self) + WikiWordRelabelNode.onSelected(self) if self.searchOp: self.treeCtrl.pWiki.getActiveEditor().executeSearch(self.searchOp, 0) # TODO @@ -607,6 +618,19 @@ def getNodePresentation(self): return style + def onActivated(self): + children = self.listChildren() + if len(children) == 1 and \ + isinstance(children[0], WikiWordTodoSearchNode) and \ + not self.treeCtrl.IsExpanded(self.wxItemId): + children[0].onSelected() + +# if self.treeCtrl.IsExpanded(self.wxItemId): +# return True + + return super(TodoNode, self).onActivated() + + def listChildren(self): """ Returns a sequence of Nodes for the children of this node. @@ -705,8 +729,8 @@ def __init__(self, tree, parentNode, wikiWord, todoName, todoValue): self.todoValue = todoValue - def onActivate(self): - super(WikiWordTodoSearchNode, self).onActivate() + def onSelected(self): + super(WikiWordTodoSearchNode, self).onSelected() editor = self.treeCtrl.pWiki.getActiveEditor() try: @@ -792,11 +816,22 @@ def listChildren(self): result[0].getValue().lower() == u"true": result = result[0].listChildren() - - return result + def onActivated(self): + children = self.listChildren() + if len(children) == 1 and \ + isinstance(children[0], WikiWordAttributeSearchNode) and \ + not self.treeCtrl.IsExpanded(self.wxItemId): + children[0].onSelected() + +# if self.treeCtrl.IsExpanded(self.wxItemId): +# return True + + return super(AttrCategoryNode, self).onActivated() + + def nodeEquality(self, other): """ Test for node equality @@ -842,6 +877,15 @@ def listChildren(self): key, self.value) for w in words] + def onActivated(self): + children = self.listChildren() + if len(children) == 1 and \ + not self.treeCtrl.IsExpanded(self.wxItemId): + children[0].onSelected() + + return super(AttrValueNode, self).onActivated() + + def nodeEquality(self, other): """ Test for node equality @@ -867,8 +911,8 @@ def __init__(self, tree, parentNode, wikiWord, propName, propValue): self.propValue = propValue - def onActivate(self): - super(WikiWordAttributeSearchNode, self).onActivate() + def onSelected(self): + super(WikiWordAttributeSearchNode, self).onSelected() editor = self.treeCtrl.pWiki.getActiveEditor() try: @@ -1123,6 +1167,8 @@ def listChildren(self): FuncPageNode(self.treeCtrl, self, u"wiki/PWL"), FuncPageNode(self.treeCtrl, self, u"global/CCBlacklist"), FuncPageNode(self.treeCtrl, self, u"wiki/CCBlacklist"), + FuncPageNode(self.treeCtrl, self, u"global/NCCBlacklist"), + FuncPageNode(self.treeCtrl, self, u"wiki/NCCBlacklist"), FuncPageNode(self.treeCtrl, self, u"global/FavoriteWikis") ] @@ -1151,9 +1197,9 @@ def getNodePresentation(self): return style - def onActivate(self): + def onSelected(self): """ - React on activation + React on selection """ self.treeCtrl.pWiki.openFuncPage(self.funcTag) @@ -1196,6 +1242,8 @@ def __init__(self, pWiki, parent, ID, treeType): self.refreshGeneratorLastCallTime = time.clock() # Initial value self.refreshGeneratorLastCallMinDelay = 0.1 self.refreshExecutor = Utilities.SingleThreadExecutor(1) + self.refreshStartLock = False # Disallows starting of refresh (mainly + # during wiki rebuild # self.refreshCheckChildren = [] # List of nodes to check for new/deleted children self.sizeVisible = True # Descriptor pathes of all expanded nodes to remember or None @@ -1205,17 +1253,15 @@ def __init__(self, pWiki, parent, ID, treeType): self.onOptionsChanged(None) - - # EVT_TREE_ITEM_ACTIVATED(self, ID, self.OnTreeItemActivated) - # EVT_TREE_SEL_CHANGED(self, ID, self.OnTreeItemActivated) + wx.EVT_TREE_ITEM_ACTIVATED(self, ID, self.OnTreeItemActivated) wx.EVT_RIGHT_DOWN(self, self.OnRightButtonDown) wx.EVT_RIGHT_UP(self, self.OnRightButtonUp) wx.EVT_MIDDLE_DOWN(self, self.OnMiddleButtonDown) wx.EVT_SIZE(self, self.OnSize) - self._bindActivation() + self._bindSelection() - wx.EVT_TREE_BEGIN_RDRAG(self, ID, self.OnTreeBeginRDrag) +# wx.EVT_TREE_BEGIN_RDRAG(self, ID, self.OnTreeBeginRDrag) wx.EVT_TREE_ITEM_EXPANDING(self, ID, self.OnTreeItemExpand) wx.EVT_TREE_ITEM_COLLAPSED(self, ID, self.OnTreeItemCollapse) wx.EVT_TREE_BEGIN_DRAG(self, ID, self.OnTreeBeginDrag) @@ -1302,7 +1348,9 @@ def __init__(self, pWiki, parent, ID, treeType): ("renamed wiki page", self.onRenamedWikiPage), ("deleted wiki page", self.onDeletedWikiPage), ("updated wiki page", self.onWikiPageUpdated), - ("changed wiki configuration", self.onChangedWikiConfiguration) + ("changed wiki configuration", self.onChangedWikiConfiguration), + ("begin foreground update", self.onBeginForegroundUpdate), + ("end foreground update", self.onEndForegroundUpdate), ), self.pWiki.getCurrentWikiDocumentProxyEvent(), self) self.__sinkApp = wxKeyFunctionSink(( @@ -1313,12 +1361,12 @@ def __init__(self, pWiki, parent, ID, treeType): self.refreshExecutor.start() - def _bindActivation(self): - self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemActivated) - self.Bind(wx.EVT_TREE_SEL_CHANGING, self.OnTreeItemSelChanging) + def _bindSelection(self): + self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemSelected) +# self.Bind(wx.EVT_TREE_SEL_CHANGING, self.OnTreeItemSelChanging) - def _unbindActivation(self): - self.Unbind(wx.EVT_TREE_SEL_CHANGING) + def _unbindSelection(self): +# self.Unbind(wx.EVT_TREE_SEL_CHANGING) self.Unbind(wx.EVT_TREE_SEL_CHANGED) def close(self): @@ -1413,9 +1461,9 @@ def onLoadingCurrentWikiPage(self, miscevt): if parentnode.representsWikiWord() and \ (parentnode.getWikiWord() == \ self.pWiki.getCurrentWikiWord()): - self._unbindActivation() + self._unbindSelection() self.SelectItem(parentnodeid) - self._bindActivation() + self._bindSelection() self._storeMainTreePositionHint(currentDpp, parentnodeid) return @@ -1430,9 +1478,9 @@ def onLoadingCurrentWikiPage(self, miscevt): child = self.findChildTreeNodeByWikiWord(currentNode, self.pWiki.getCurrentWikiWord()) if child: - self._unbindActivation() + self._unbindSelection() self.SelectItem(child) - self._bindActivation() + self._bindSelection() self._storeMainTreePositionHint(currentDpp, child) return # else: @@ -1525,15 +1573,41 @@ def _selectNodeByNodePathRecurs(self, parentNodeId, nodePath): return False + def joinItemIdToNode(self, treeItemId, nodeObj): + self.SetPyData(treeItemId, nodeObj) + nodeObj.setWxItemId(treeItemId) - def onWikiPageUpdated(self, miscevt): - if not self.pWiki.getConfig().getboolean("main", "tree_update_after_save"): + def _startBackgroundRefresh(self): + if self.refreshStartLock: return + self.refreshGenerator = self._generatorRefreshNodeAndChildren( + self.GetRootItem()) + self.Bind(wx.EVT_IDLE, self.OnIdle) + + def _stopBackgroundRefresh(self): + self.Unbind(wx.EVT_IDLE) + self.refreshGenerator = None + + def onEndForegroundUpdate(self, miscEvt): + self.refreshStartLock = False self.refreshGenerator = self._generatorRefreshNodeAndChildren( self.GetRootItem()) self.Bind(wx.EVT_IDLE, self.OnIdle) + def onBeginForegroundUpdate(self, miscEvt): + self._stopBackgroundRefresh() + self.refreshStartLock = True + + + + + def onWikiPageUpdated(self, miscevt): + if not self.pWiki.getConfig().getboolean("main", "tree_update_after_save"): + return + + self._startBackgroundRefresh() + def onDeletedWikiPage(self, miscevt): # TODO May be called multiple times if @@ -1541,9 +1615,7 @@ def onDeletedWikiPage(self, miscevt): # TODO May be called multiple times if if not self.pWiki.getConfig().getboolean("main", "tree_update_after_save"): return - self.refreshGenerator = self._generatorRefreshNodeAndChildren( - self.GetRootItem()) - self.Bind(wx.EVT_IDLE, self.OnIdle) + self._startBackgroundRefresh() def _addExpandedNodesToPathSet(self, parentNodeId): @@ -1588,9 +1660,7 @@ def onOptionsChanged(self, miscevt): self.refreshGeneratorLastCallMinDelay = config.getfloat("main", "tree_updateGenerator_minDelay", 0.1) - self.refreshGenerator = self._generatorRefreshNodeAndChildren( - self.GetRootItem()) - self.Bind(wx.EVT_IDLE, self.OnIdle) + self._startBackgroundRefresh() @@ -1716,7 +1786,7 @@ def _generatorRefreshNodeAndChildren(self, parentnodeid): # Old and new don't match -> Insert new child newnodeid = self.InsertItemBefore(parentnodeid, tci, "") tci += 1 - self.SetPyData(newnodeid, c) + self.joinItemIdToNode(newnodeid, c) retObj = self.refreshExecutor.executeAsync(0, c.getNodePresentation) @@ -1733,7 +1803,7 @@ def _generatorRefreshNodeAndChildren(self, parentnodeid): # No more nodes in tree, but some in new children list # -> append one to tree newnodeid = self.AppendItem(parentnodeid, "") - self.SetPyData(newnodeid, c) + self.joinItemIdToNode(newnodeid, c) retObj = self.refreshExecutor.executeAsync(0, c.getNodePresentation) @@ -1803,9 +1873,7 @@ def onRenamedWikiPage(self, miscevt): if not self.pWiki.getConfig().getboolean("main", "tree_update_after_save"): return - self.refreshGenerator = self._generatorRefreshNodeAndChildren( - self.GetRootItem()) - self.Bind(wx.EVT_IDLE, self.OnIdle) + self._startBackgroundRefresh() # self.collapse() # TODO? @@ -1829,15 +1897,14 @@ def onClosingCurrentWiki(self, miscevt): "tree_expandedNodes_descriptorPathes_" + self.treeType, u"") - self.refreshGenerator = None + self._stopBackgroundRefresh() self.refreshExecutor.end(hardEnd=True) self.refreshExecutor.start() def onClosedCurrentWiki(self, miscevt): # self.refreshExecutor.end(hardEnd=True) -# self.refreshGenerator = None - self.Unbind(wx.EVT_IDLE) + self._stopBackgroundRefresh() if self.expandedNodePathes is not None: self.expandedNodePathes = StringPathSet() @@ -1986,12 +2053,12 @@ def buildTreeForWord(self, wikiWord, selectNode=False, doexpand=False, if doexpand: self.EnsureVisible(currentNode) if selectNode: - self._unbindActivation() + self._unbindSelection() self.SelectItem(currentNode, send_events=False) currentDpp = self.pWiki.getCurrentDocPagePresenter() self._storeMainTreePositionHint(currentDpp, currentNode) self.EnsureVisible(currentNode) - self._bindActivation() + self._bindSelection() return True @@ -2031,7 +2098,7 @@ def setRootByUnifiedName(self, unifName): nodeobj = self.createNodeObjectByUnifiedName(unifName) nodeobj.setRoot(True) root = self.AddRoot(u"") - self.SetPyData(root, nodeobj) + self.joinItemIdToNode(root, nodeobj) self.setNodePresentation(root, nodeobj.getNodePresentation()) if self.expandedNodePathes is not None: self.expandedNodePathes = StringPathSet() @@ -2130,12 +2197,12 @@ def _sendSelectionEvents(self, oldNode, newNode): event.SetEventType(wx.wxEVT_COMMAND_TREE_SEL_CHANGED) self.GetEventHandler().ProcessEvent(event) - - def OnTreeItemActivated(self, event): + + def OnTreeItemSelected(self, event): item = event.GetItem() if item is not None and item.IsOk(): node = self.GetPyData(item) - node.onActivate() + node.onSelected() self._storeMainTreePositionHint( self.pWiki.getCurrentDocPagePresenter(), item) @@ -2148,12 +2215,23 @@ def OnTreeItemActivated(self, event): self.Refresh() - def OnTreeItemSelChanging(self, evt): - pass + def OnTreeItemActivated(self, event): + item = event.GetItem() + if item is not None and item.IsOk(): + node = self.GetPyData(item) + if not node.onActivated(): + event.Skip() + # Is said to fix a selection redraw problem + self.Refresh() - def OnTreeBeginRDrag(self, evt): - pass + +# def OnTreeItemSelChanging(self, evt): +# pass + + +# def OnTreeBeginRDrag(self, evt): +# pass def OnTreeItemExpand(self, event): ## _prof.start() @@ -2171,7 +2249,7 @@ def OnTreeItemExpand(self, event): try: for ch in childnodes: newit = self.AppendItem(item, u"") - self.SetPyData(newit, ch) + self.joinItemIdToNode(newit, ch) nodeStyle = ch.getNodePresentation() self.setNodePresentation(newit, nodeStyle) @@ -2261,20 +2339,20 @@ def OnRightButtonUp(self, event): if menu is not None: self.selectedNodeWhileContext = selnode - self._unbindActivation() + self._unbindSelection() self.SelectItem(item) - self._bindActivation() + self._bindSelection() self.PopupMenuXY(menu, event.GetX(), event.GetY()) selnode = self.selectedNodeWhileContext - self._unbindActivation() + self._unbindSelection() if selnode is None: self.Unselect() else: self.SelectItem(selnode, expand_if_necessary=False) - self._bindActivation() + self._bindSelection() newsel = self.GetSelection() if selnode != newsel: @@ -2332,7 +2410,6 @@ def OnMiddleButtonDown(self, event): # self.SelectItem(item) - def OnIdle(self, event): gen = self.refreshGenerator if gen is not None: @@ -2341,6 +2418,7 @@ def OnIdle(self, event): # May happen under special circumstances (e.g. after hibernation) self.refreshGeneratorLastCallTime = cl elif (cl - self.refreshGeneratorLastCallTime) < self.refreshGeneratorLastCallMinDelay: + event.Skip() return try: @@ -2352,6 +2430,9 @@ def OnIdle(self, event): if self.refreshGenerator == gen: self.refreshGenerator = None self.Unbind(wx.EVT_IDLE) + else: + event.Skip() + self.Unbind(wx.EVT_IDLE) def isVisibleEffect(self): diff --git a/lib/pwiki/WikiTxtCtrl.py b/lib/pwiki/WikiTxtCtrl.py index 3271182b..d110a2da 100644 --- a/lib/pwiki/WikiTxtCtrl.py +++ b/lib/pwiki/WikiTxtCtrl.py @@ -62,6 +62,9 @@ # wikiWordToLabel, revStr, lineendToInternal, lineendToOs +from ViHelper import ViHintDialog, ViHelper +from collections import defaultdict + try: import WindowsHacks except: @@ -140,6 +143,7 @@ def __init__(self, presenter, parent, ID): self.dwellLockCounter = 0 # Don't process dwell start messages if >0 self.wikiLanguageHelper = None self.templateIdRecycler = wxHelper.IdRecycler() + self.vi = None # Contains ViHandler instance if vi key handling enabled # If autocompletion word was choosen, how many bytes to delete backward # before inserting word @@ -279,7 +283,8 @@ def __init__(self, presenter, parent, ID): self.wikiPageSink = wxKeyFunctionSink(( ("updated wiki page", self.onWikiPageUpdated), # fired by a WikiPage - ("modified spell checker session", self.OnStyleNeeded) # ??? + ("modified spell checker session", self.OnStyleNeeded), # ??? + ("changed read only flag", self.onPageChangedReadOnlyFlag) )) @@ -295,7 +300,18 @@ def __init__(self, presenter, parent, ID): wx.EVT_MIDDLE_DOWN(self, self.OnMiddleDown) wx.EVT_LEFT_DCLICK(self, self.OnDoubleClick) - wx.EVT_KEY_DOWN(self, self.OnKeyDown) + # Not necessary, self.onOptionsChanged() is called below (MB) +# use_vi_navigation = self.presenter.getConfig().getboolean("main", +# "html_preview_webkitViKeys", False) +# +# if use_vi_navigation: +# self.vi = ViHandler(self) +# wx.EVT_KEY_DOWN(self, self.vi.OnViKeyDown) +# else: +# wx.EVT_KEY_DOWN(self, self.OnKeyDown) +# self.vi = None + + if config.getboolean("main", "editor_useImeWorkaround", False): wx.EVT_CHAR(self, self.OnChar_ImeWorkaround) @@ -399,8 +415,10 @@ def close(self): # wx.EVT_IDLE(self, self.OnIdle) - def Copy(self): - text = self.GetSelectedText() + def Copy(self, text=None): + if text is None: + text = self.GetSelectedText() + if len(text) == 0: return @@ -1014,6 +1032,10 @@ def _getColorFromOption(self, option, defColTuple): return wx.Colour(*coltuple) + def onPageChangedReadOnlyFlag(self, miscevt): + self._checkForReadOnly() + + def onOptionsChanged(self, miscevt): faces = self.presenter.getDefaultFontFaces().copy() @@ -1058,6 +1080,24 @@ def onOptionsChanged(self, miscevt): self.SetTabWidth(tabWidth) + # To allow switching vi keys on and off without restart + use_vi_navigation = self.presenter.getConfig().getboolean("main", + "html_preview_webkitViKeys", False) + + self.Unbind(wx.EVT_KEY_DOWN) + + if use_vi_navigation: + if self.vi is None: + self.vi = ViHandler(self) + + self.Bind(wx.EVT_KEY_DOWN, self.vi.OnViKeyDown) + else: + if self.vi is not None: + self.vi.TurnOff() + self.vi = None + self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + + def onChangedConfiguration(self, miscevt): """ Called when global configuration was changed. Most things are processed @@ -1314,6 +1354,7 @@ def OnContextMenu(self, evt): self.contextMenuTokens = nodes addActivateItem = False addFileUrlItem = False + addWikiUrlItem = False addUrlToClipboardItem = False unknownWord = None for node in nodes: @@ -1324,6 +1365,9 @@ def OnContextMenu(self, evt): if node.url.startswith(u"file:") or \ node.url.startswith(u"rel://"): addFileUrlItem = True + elif node.url.startswith(u"wiki:") or \ + node.url.startswith(u"wikirel://"): + addWikiUrlItem = True elif node.name == "insertion" and node.key == u"page": addActivateItem = True elif node.name == "anchorDef": @@ -1339,6 +1383,7 @@ def OnContextMenu(self, evt): if spellCheckerSession: # Show suggestions if available (up to first 5) suggestions = spellCheckerSession.suggest(unknownWord)[:5] + spellCheckerSession.close() if len(suggestions) > 0: for s, mid in zip(suggestions, self.SUGGESTION_CMD_IDS): @@ -1361,6 +1406,8 @@ def OnContextMenu(self, evt): if addFileUrlItem: appendToMenuByMenuDesc(menu, _CONTEXT_MENU_INTEXT_FILE_URL) + elif addWikiUrlItem: + appendToMenuByMenuDesc(menu, _CONTEXT_MENU_INTEXT_WIKI_URL) if addUrlToClipboardItem: appendToMenuByMenuDesc(menu, @@ -2166,11 +2213,11 @@ def OnOpenContainingFolderThis(self, evt): if node.name == "urlLink": link = node.url - if link.startswith(u"rel://"): + if link.startswith(u"rel://") or link.startswith(u"wikirel://"): link = self.presenter.getWikiDocument()\ .makeRelUrlAbsolute(link) - if link.startswith(u"file:"): + if link.startswith(u"file:") or link.startswith(u"wiki:"): try: path = dirname(StringOps.pathnameFromUrl(link)) if not exists(StringOps.longPathEnc(path)): @@ -2305,7 +2352,7 @@ def convertUrlAbsoluteRelative(self, tokenList): else: addSafe = '' - if link.startswith(u"rel://"): + if link.startswith(u"rel://") or link.startswith(u"wikirel://"): link = self.presenter.getWikiDocument()\ .makeRelUrlAbsolute(link, addSafe=addSafe) @@ -2315,6 +2362,15 @@ def convertUrlAbsoluteRelative(self, tokenList): link), addSafe=addSafe) if link is None: continue # TODO Message? + elif link.startswith(u"wiki:"): + link = self.presenter.getWikiDocument()\ + .makeAbsPathRelUrl(StringOps.pathnameFromUrl( + link), addSafe=addSafe) + if link is None: + continue # TODO Message? + else: + link = u"wiki" + link # Combines to "wikirel://" + else: continue @@ -2653,222 +2709,6 @@ def endIncrementalSearch(self): self.ensureSelectionExpanded() - - - -# def startIncrementalSearch(self, initSearch=None): -# sb = self.presenter.getStatusBar() -# -# self.incSearchCharStartPos = self.GetSelectionCharPos()[1] -# self.incSearchPreviousHiddenLines = None -# self.incSearchPreviousHiddenStartLine = -1 -# -# rect = sb.GetFieldRect(0) -# if isOSX(): -# # needed on Mac OSX to avoid cropped text -# rect = wx._core.Rect(rect.x, rect.y - 2, rect.width, rect.height + 4) -# -# rect.SetPosition(sb.ClientToScreen(rect.GetPosition())) -# -# dlg = WikiTxtDialogs.IncrementalSearchDialog(self, -1, self, rect, -# sb.GetFont(), self.presenter, initSearch) -# dlg.Show() -# -# -# def executeIncrementalSearch(self, next=False): -# """ -# Run incremental search, called only by IncrementalSearchDialog -# """ -# text = self.GetText() -# if len(self.searchStr) > 0: -# if next: -# charStartPos = self.GetSelectionCharPos()[1] -# else: -# charStartPos = self.incSearchCharStartPos -# -# regex = None -# try: -# regex = re.compile(self.searchStr, re.IGNORECASE | \ -# re.MULTILINE | re.UNICODE) -# except: -# # Regex error -# return charStartPos # ? -# -# match = regex.search(text, charStartPos, len(text)) -# if not match and charStartPos > 0: -# match = regex.search(text, 0, charStartPos) -# -# if match: -# # matchbytestart = self.bytelenSct(text[:match.start()]) -# # matchbyteend = matchbytestart + \ -# # self.bytelenSct(text[match.start():match.end()]) -# -# self.setSelectionForIncSearchByCharPos( -# match.start(), match.end()) -# -# return match.end() -# -# self.setSelectionForIncSearchByCharPos(-1, -1) -# self.GotoPos(self.bytelenSct(text[:self.incSearchCharStartPos])) -# -# return -1 -# -# -# def executeIncrementalSearchBackward(self): -# """ -# Run incremental search, called only by IncrementalSearchDialog -# """ -# text = self.GetText() -# if len(self.searchStr) > 0: -# charStartPos = self.GetSelectionCharPos()[0] -# -# regex = None -# try: -# regex = re.compile(self.searchStr, re.IGNORECASE | \ -# re.MULTILINE | re.UNICODE) -# except: -# # Regex error -# return charStartPos # ? -# -# match = regex.search(text, 0, len(text)) -# if match: -# if match.end() > charStartPos: -# # First match already reached -> find last -# while True: -# matchNext = regex.search(text, match.end(), len(text)) -# if not matchNext: -# break -# match = matchNext -# -# else: -# while True: -# matchNext = regex.search(text, match.end(), len(text)) -# if matchNext.end() > charStartPos: -# break -# match = matchNext -# -# self.setSelectionForIncSearchByCharPos(match.start(), match.end()) -# -# return match.start() -# -# self.setSelectionForIncSearchByCharPos(-1, -1) -# self.GotoPos(self.bytelenSct(text[:self.incSearchCharStartPos])) -# -# return -1 -# -# -# def forgetIncrementalSearch(self): -# """ -# Called by IncrementalSearchDialog if user just leaves the inc. search -# field. -# """ -# pass -# -# def resetIncrementalSearch(self): -# """ -# Called by IncrementalSearchDialog before aborting an inc. search. -# Called when search was explicitly aborted by user (with escape key) -# """ -# self.setSelectionForIncSearchByCharPos(-1, -1) -# self.GotoPos(self.bytelenSct(self.GetText()[:self.incSearchCharStartPos])) -# -# -# def endIncrementalSearch(self): -# """ -# Called if incremental search ended successfully. -# """ -# byteStart = self.GetSelectionStart() -# byteEnd = self.GetSelectionEnd() -# -# self.setSelectionForIncSearchByCharPos(-1, -1) -# -# self.SetSelection(byteStart, byteEnd) -# self.ensureSelectionExpanded() -# -# -# def getContinuePosForSearch(self, sarOp): -# """ -# Return the character position where to continue the given -# search operation sarOp. It always continues at beginning -# or end of current selection. -# -# If sarOp uses a regular expression, this function may throw -# a re.error exception. -# """ -# range = self.GetSelectionCharPos() -# -# # if sarOp.matchesPart(self.GetSelectedText()) is not None: -# if sarOp.matchesPart(self.GetText(), range) is not None: -# # currently selected text matches search operation -# # -> continue searching at the end of selection -# return range[1] -# else: -# # currently selected text does not match search -# # -> continue searching at the beginning of selection -# return range[0] -# -# -# def executeSearch(self, sarOp, searchCharStartPos=-1, next=False): -# """ -# Returns a tuple with a least two elements (, ) -# containing start and after end char positions of the found occurrence -# or (-1, -1) if not found. -# """ -# if sarOp.booleanOp: -# return (-1, -1) # Not possible -# -# if searchCharStartPos == -2: -# searchCharStartPos = self.getContinuePosForSearch(sarOp) -# -# text = self.GetText() -# if len(sarOp.searchStr) > 0: -# charStartPos = searchCharStartPos -# if next: -# charStartPos = len(self.GetTextRange(0, self.GetSelectionEnd())) -# try: -# found = sarOp.searchText(text, charStartPos) -# start, end = found[:2] -# except: -# # Regex error -# return (-1, -1) # (self.anchorCharPosition, self.anchorCharPosition) -# -# if start is not None: -# self.showSelectionByCharPos(start, end) -# -# return found # self.anchorCharPosition -# -# self.SetSelection(-1, -1) -# self.GotoPos(self.bytelenSct(text[:searchCharStartPos])) -# -# return (-1, -1) -# -# -# def executeReplace(self, sarOp): -# """ -# Returns char position after replacement or -1 if replacement wasn't -# possible -# """ -# # seltext = self.GetSelectedText() -# text = self.GetText() -# # found = sarOp.matchesPart(seltext) -# range = self.GetSelectionCharPos() -# -# # if sarOp.matchesPart(self.GetSelectedText()) is not None: -# found = sarOp.matchesPart(text, range) -# -# if found is None: -# return -1 -# -# replacement = sarOp.replace(text, found) -# bytestart = self.GetSelectionStart() -# self.ReplaceSelection(replacement) -# selByteEnd = bytestart + self.bytelenSct(replacement) -# selCharEnd = len(self.GetTextRange(0, selByteEnd)) -# -# return selCharEnd - - - def rewrapText(self): return self.wikiLanguageHelper.handleRewrapText(self, {}) @@ -3522,12 +3362,7 @@ def userActionPasteFiles(unifActionName, paramDict): elif unifActionName == u"action/editor/this/paste/files/insert/url/movetostorage": modeToStorage = True modeRelativeUrl = False - - -# elif unifActionName == u"action/editor/this/paste/files/insert/url/movetostorage": -# modeToStorage = True -# modeRelativeUrl = False -# moveToStorage = True + moveToStorage = True else: return @@ -3553,31 +3388,59 @@ def userActionPasteFiles(unifActionName, paramDict): urls = [] for fn in filenames: + protocol = None if fn.endswith(u".wiki"): - urls.append(u"wiki:%s" % StringOps.urlFromPathname(fn)) - else: - toStorage = False - if modeToStorage: - # Copy file into file storage - fs = editor.presenter.getWikiDocument().getFileStorage() - try: - fn = fs.createDestPath(fn, move=moveToStorage) - toStorage = True - except Exception, e: - traceback.print_exc() - editor.presenter.getMainControl().displayErrorMessage( - _(u"Couldn't copy file"), e) - return + protocol = "wiki" + + toStorage = False + if modeToStorage and protocol is None: + # Copy file into file storage + fs = editor.presenter.getWikiDocument().getFileStorage() + try: + fn = fs.createDestPath(fn, move=moveToStorage) + toStorage = True + except Exception, e: + traceback.print_exc() + editor.presenter.getMainControl().displayErrorMessage( + _(u"Couldn't copy file"), e) + return - urls.append(editor.wikiLanguageHelper.createUrlLinkFromPath( - editor.presenter.getWikiDocument(), fn, - relative=modeRelativeUrl or toStorage, - bracketed=dlgParams.bracketedUrl)) + urls.append(editor.wikiLanguageHelper.createUrlLinkFromPath( + editor.presenter.getWikiDocument(), fn, + relative=modeRelativeUrl or toStorage, + bracketed=dlgParams.bracketedUrl, protocol=protocol)) editor.handleDropText(x, y, prefix + middle.join(urls) + suffix) + def GetEOLChar(self): + """ + Gets the end of line char currently being used + """ + m_id = self.GetEOLMode() + if m_id == wx.stc.STC_EOL_CR: + return u'\r' + elif m_id == wx.stc.STC_EOL_CRLF: + return u'\r\n' + else: + return u'\n' + def GetLastVisibleLine(self): + """ + Returns line number of the first visible line in viewport + """ + return self.GetFirstVisibleLine() + self.LinesOnScreen() - 1 + + def GetMiddleVisibleLine(self): + """ + Returns line number of the middle visible line in viewport + """ + fl = self.GetFirstVisibleLine() + if self.LinesOnScreen() < self.GetLineCount(): + mid = (fl + (self.LinesOnScreen() // 2)) + else: + mid = (fl + (self.GetLineCount() // 2)) + return mid # TODO # def setMouseCursor(self): @@ -3607,7 +3470,6 @@ def userActionPasteFiles(unifActionName, paramDict): # return False - class WikiTxtCtrlDropTarget(wx.PyDropTarget): def __init__(self, editor): wx.PyDropTarget.__init__(self) @@ -3693,6 +3555,7 @@ def OnDropFiles(self, x, y, filenames): + # User actions to register # _ACTION_EDITOR_PASTE_FILES_ABSOLUTE = UserActionCoord.SimpleAction("", # u"action/editor/this/paste/files/insert/url/absolute", @@ -3762,6 +3625,13 @@ def OnDropFiles(self, x, y, filenames): Follow Link New Tab Backgrd.;CMD_ACTIVATE_NEW_TAB_BACKGROUND_THIS """ +_CONTEXT_MENU_INTEXT_WIKI_URL = \ +u""" +- +Convert Absolute/Relative File URL;CMD_CONVERT_URL_ABSOLUTE_RELATIVE_THIS +Open Containing Folder;CMD_OPEN_CONTAINING_FOLDER_THIS +""" + _CONTEXT_MENU_INTEXT_FILE_URL = \ u""" - @@ -3848,15 +3718,27 @@ def OnDropFiles(self, x, y, filenames): N_(u"Fold everything in current editor") +# I will move this to wxHelper later (MB) +try: + class wxPopupOrFrame(wx.PopupWindow): + def __init__(self, parent, id=-1, style=None): + wx.PopupWindow.__init__(self, parent) + +except AttributeError: + class wxPopupOrFrame(wx.Frame): + def __init__(self, parent, id=-1, + style=wx.NO_BORDER|wx.FRAME_NO_TASKBAR|wx.FRAME_FLOAT_ON_PARENT): + wx.Frame.__init__(self, parent, id, style=style) + -# TODO: Thumbnail size configurable -class ImageTooltipPanel(wx.Frame): +class ImageTooltipPanel(wxPopupOrFrame): """Quick panel for image tooltips""" def __init__(self, pWiki, filePath, maxWidth=200, maxHeight=200): - wx.Frame.__init__(self, pWiki, -1, style=wx.NO_BORDER|wx.FRAME_NO_TASKBAR|wx.FRAME_FLOAT_ON_PARENT) + wxPopupOrFrame.__init__(self, pWiki, -1) self.url = filePath self.pWiki = pWiki + self.firstMove = True img = wx.Image(filePath, wx.BITMAP_TYPE_ANY) @@ -3885,6 +3767,7 @@ def __init__(self, pWiki, filePath, maxWidth=200, maxHeight=200): self.bmp = wx.StaticBitmap(self, -1, img, (0, 0), (img.GetWidth(), img.GetHeight())) self.bmp.Bind(wx.EVT_LEFT_DOWN, self.OnLeftClick) self.bmp.Bind(wx.EVT_RIGHT_DOWN, self.OnRightClick) + self.bmp.Bind(wx.EVT_MOTION, self.OnMouseMotion) self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) mousePos = wx.GetMousePosition() @@ -3895,6 +3778,8 @@ def __init__(self, pWiki, filePath, maxWidth=200, maxHeight=200): WindowLayout.setWindowPos(self, mousePos, fullVisible=True) self.Show() + # Works for Windows (but not for GTK), maybe also for Mac (MB) + self.GetParent().SetFocus() def Close(self, event=None): self.Destroy() @@ -3913,8 +3798,1253 @@ def OnLeftClick(self, event=None): def OnRightClick(self, event=None): self.Close() + def OnMouseMotion(self, evt): + if self.firstMove: + self.firstMove = False + evt.Skip() + return + + self.Close() + + def OnKeyDown(self, event): kc = event.GetKeyCode() if kc == wx.WXK_ESCAPE: self.Close() + + +class ViHandler(ViHelper): + # TODO: Add search commands + # Fix long line inconsistency? + # Brace matching - finish % cmd + # Find a way to monitor selection (to enter visual mode from mouse selecttion) + + def __init__(self, stc): + ViHelper.__init__(self, stc) + + wx.CallAfter(self.SetDefaultCaretColour) + # Set default mode + wx.CallAfter(self.SetMode, ViHelper.NORMAL) + + """ + pre_keys holds all the keys that can be used as "power" modifiers + i.e. they can be followed by other commands (or a range of + inputs) + + In general there are three types, + 1. those that can be followed by any motion commands + e.g. dyc<> + 2. those that can be followed by a wide range of inputs + e.g. mr + 3. same as 2 but can be prefixed by commands in 1 (they + are motion commands themselves) + e.g. '`ft + """ + self.pre_keys = { + 0 : { + 60 : (1, self.DedentSingle, 1), # < + 62 : (1, self.IndentSingle, 1), # > + 99 : (1, self.EndDeleteInsert, 2), # c + 100 : (1, self.EndDelete, 1), # d + 121 : (1, self.Yank, 0), # y + 39 : (3, self.GotoMark, 0), # ' + 96 : (3, self.GotoMarkIndent, 0), # ` + 109 : (2, self.Mark, 0), # m + 102 : (3, self.FindNextChar, 0), # f + 70 : (3, self.FindNextCharBackwards, 0), # F + 116 : (3, self.FindUpToNextChar, 0), # t + 84 : (3, self.FindUpToNextCharBackwards, 0), # T + 114 : (2, self.ReplaceChar, 2), # r + }, + 2 : { + # In visual mode a number of keys no longer + # act as modifiers + 39 : (3, self.GotoMark, 0), # ' + 96 : (3, self.GotoMarkIndent, 0), # ` + 109 : (2, self.Mark, 0), # m + 102 : (3, self.FindNextChar, 0), # f + 70 : (3, self.FindNextCharBackwards, 0), # F + 116 : (3, self.FindUpToNextChar, 0), # t + 84 : (3, self.FindUpToNextCharBackwards, 0), # T + 114 : (2, self.ReplaceChar, 2), # r + } + } + + self.pre_motion_keys = defaultdict(dict) + for j in self.pre_keys: + self.pre_motion_keys[j] = \ + [i for i in self.pre_keys[j] if self.pre_keys[j][i][0] == 1] + + self.keys = { + 0 : { + # Normal mode + (105) : (0, (self.Insert, None), 2), # i + (97) : (0, (self.Append, None), 2), # a + (73) : (0, (self.InsertAtLineStart, None), 2), # I + (65) : (0, (self.AppendAtLineEnd, None), 2), # A + (111) : (0, (self.OpenNewLine, False), 2), # o + (79) : (0, (self.OpenNewLine, True), 2), # O + (67) : (0, (self.TruncateLineAndInsert, None), 2), # C + (68) : (0, (self.TruncateLine, None), 2), # D + + (120) : (0, (self.DeleteRight, None), 1), # x + (88) : (0, (self.DeleteLeft, None), 1), # X + + (115) : (0, (self.DeleteRightAndInsert, None), 2), # s + (83) : (0, (self.DeleteLinesAndIndentInsert, None), 2), # S + + (119) : (1, (self.MoveCaretNextWord, None), 0), # w + (87) : (1, (self.MoveCaretNextWORD, None), 0), # W + (101) : (1, (self.MoveCaretWordEnd, None), 0), # e + (69) : (1, (self.MoveCaretWordEND, None), 0), # E + (98) : (1, (self.MoveCaretBackWord, None), 0), # b + (66) : (1, (self.MoveCaretBackWORD, None), 0), # B + + (105, 119) : (1, (self.SelectInWord, None), 0), # iw + (105, 108) : (1, (self.SelectInLink, None), 0), # il + (105, 91) : (1, (self.SelectInLink, None), 0), # i[ + (105, 93) : (1, (self.SelectInLink, None), 0), # i] + + (123) : (1, (self.Repeat, self.ctrl.ParaUp), 0), # { + (125) : (1, (self.Repeat, self.ctrl.ParaDown), 0), # } + + # TODO: Technically search is a motion but it will require a custom + # implementation in order to achieve that. + 47 : (0, (self.StartSearch, None), 0), # / + # NOTE: at the moment searching does not mimic vi very well + 110 : (0, (self.StartSearch, None), 0), # n + # 78 : (0, (self.StartSearch, None)), # N + + # Basic movement + (104) : (1, (self.MoveCaretLeft, None), 0), # h + (107) : (1, (self.MoveCaretUp, None), 0), # k + (108) : (1, (self.MoveCaretRight, None), 0), # l + (106) : (1, (self.MoveCaretDown, None), 0), # j + # Arrow keys + (65361) : (1, (self.MoveCaretLeft, None), 0), # left + (65362) : (1, (self.MoveCaretUp, None), 0), # up + (65363) : (1, (self.MoveCaretRight, None), 0), # right + (65364) : (1, (self.MoveCaretDown, None), 0), # down + + (65293) : (1, (self.MoveCaretDownAndIndent, None), 0), # enter + (65293) : (1, (self.MoveCaretDownAndIndent, None), 0), # return + + # Line movement + (36) : (1, (self.GotoLineEnd, None), 0), # 0 + (48) : (1, (self.GotoLineStart, None), 0), # $ + (94) : (1, (self.GotoLineIndent, None), 0), # ^ + (124) : (1, (self.GotoColumn, None), 0), # | + + # Page scroll control + (103, 103) : (1, (self.DocumentNavigation, (103, 103)), 0), # gg + (71) : (1, (self.DocumentNavigation, 71), 0), # G + (37) : (1, (self.DocumentNavigation, 37), 0), # % + + (72) : (1, (self.GotoViewportTop, None), 0), # H + (76) : (1, (self.GotoViewportBottom, None), 0), # L + (77) : (1, (self.GotoViewportMiddle, None), 0), # M + + (122, 122) : (0, (self.ScrollViewportMiddle, None), 0), # zz + (122, 116) : (0, (self.ScrollViewportTop, None), 0), # zt + (122, 98) : (0, (self.ScrollViewportBottom, None), 0), # zb + + (90, 90) : (0, (self.ctrl.presenter.getMainControl().\ + exitWiki, None), 0), # ZZ + + (117) : (0, (self.Undo, None), 0), # u + ("ctrl", 114) : (0, (self.Redo, None), 0), # ctrl-r (already bound) + + # These two are motions + (59) : (1, (self.RepeatLastForwardFindCharCmd, None), 0), # ; + (44) : (1, (self.RepeatLastBackwardFindCharCmd, None), 0), # , + + # Replace ? + #(114) : (1, (self.ReplaceChar, None)), # r + # repeatable? + (82) : (0, (self.StartReplaceMode, None), 0), # R + + (118) : (2, (self.EnterVisualMode, None), 0), # v + (86) : (2, (self.EnterLineVisualMode, None), 0), # V + + (74) : (0, (self.JoinLines, None), 1), # J + + (126) : (0, (self.SwapCase, None), 0), # ~ + + (121, 121) : (0, (self.YankLine, None), 0), # yy + (89) : (0, (self.YankLine, None), 0), # Y + (112) : (0, (self.Put, False), 0), # p + (80) : (0, (self.Put, True), 0), # P + + (100, 100) : (0, (self.DeleteLine, None), 1), # dd + + (62, 62) : (0, (self.Indent, True), 1), # >> + (60, 60) : (0, (self.Indent, False), 1), # << + + (46) : (0, (self.RepeatCmd, None), 0), # . + + # Wikipage navigation + # As some command (e.g. HL) are already being used in most cases + # these navigation commands have been prefixed by "g". + # TODO: different repeat command for these? + (103, 102) : (0, (self.ctrl.activateLink, { "tabMode" : 0 }), 0), # gf + (103, 70) : (0, (self.ctrl.activateLink, { "tabMode" : 2 }), 0), # gF + (103, 98) : (0, (self.ctrl.activateLink, { "tabMode" : 3 }), 0), # gb + # This might be going a bit overboard with history nagivaiton! + (103, 72) : (0, (self.GoBackwardInHistory, None), 0), # gH + (103, 76) : (0, (self.GoForwardInHistory, None), 0), # gL + (103, 104) : (0, (self.GoBackwardInHistory, None), 0), # gh + (103, 108) : (0, (self.GoForwardInHistory, None), 0), # gl + (91) : (0, (self.GoBackwardInHistory, None), 0), # [ + (93) : (0, (self.GoForwardInHistory, None), 0), # ] + (103, 116) : (0, (self.SwitchTabs, None), 0), # gt + (103, 84) : (0, (self.SwitchTabs, True), 0), # gT + (103, 114) : (0, (self.OpenHomePage, False), 0), # gr + (103, 82) : (0, (self.OpenHomePage, True), 0), # gR + (103, 117) : (0, (self.ViewParents, False), 0), # gu + (103, 85) : (0, (self.ViewParents, True), 0), # gU + (103, 111) : (0, (self.ctrl.presenter.getMainControl()). \ + showWikiWordOpenDialog, None, 0), # go + # TODO: rewrite open dialog so it can be opened with new tab as default + (103, 79): (0, (self.ctrl.presenter.getMainControl()). \ + showWikiWordOpenDialog, None, 0), # gO + + (103, 115) : (0, (self.SwitchEditorPreview, None), 0), # gs + (103, 101) : (0, (self.SwitchEditorPreview, "textedit"), 0), # ge + (103, 112) : (0, (self.SwitchEditorPreview, "preview"), 0), # gp + (65470) : (0, (self.SwitchEditorPreview, "textedit"), 0), # F1 + (65471) : (0, (self.SwitchEditorPreview, "preview"), 0), # F2 + } + } + + # Rather than rewrite all the keys for other modes it is easier just + # to modify those that need to be changed + self.keys[2] = self.keys[0].copy() + self.keys[2].update({ + 99 : (0, (self.DeleteSelectionAndInsert, None), 2), # c + 100 : (0, (self.DeleteSelection, None), 1), # d + 120 : (0, (self.DeleteSelection, None), 1), # x + 121 : (0, (self.YankSelection, None), 0), # y + 60 : (0, (self.Indent, {"forward":False, "visual":True}), 0), # < + 62 : (0, (self.Indent, {"forward":True, "visual":True}), 0), # > + (117) : (0, (self.LowerCase, None), 0), # u + (85) : (0, (self.UpperCase, None), 0), # U + #(105, 119) : (1, (self.SelectInWord, None), 0), # iw + }) + # And delete a few so our key mods are correct + # These are keys that who do not serve the same function in visual mode + # as in normal mode (and it most cases are replaced by other function) + del self.keys[2][(100, 100)] # dd + del self.keys[2][(121, 121)] # yy + del self.keys[2][(105)] # i + + + self.key_mods = self.GenerateKeyModifiers(self.keys) + + def SetNumber(self, n): + # If 0 is first modifier it is a command + if len(self.key_number_modifier) < 1 and n == 0: + return False + self.key_number_modifier.append(n) + self.key_modifier = [] + self.updateViStatus(True) + return True + + + def SetMode(self, mode): + """ + It would be nice to set caret alpha but i don't think its + possible at the moment + """ + # If switching from insert mode vi does a few things + if self.mode == ViHelper.INSERT: + # Move back one pos if not at the start of a line + if self.ctrl.GetCurrentPos() != \ + self.GetLineStartPos(self.ctrl.GetCurrentLine()): + self.ctrl.CharLeft() + + if self.mode == ViHelper.INSERT: + # If current line only contains whitespace remove it + if self.ctrl.GetCurLine()[0].strip() == u"": + self.ctrl.LineDelete() + self.ctrl.AddText(self.ctrl.GetEOLChar()) + self.ctrl.CharLeft() + + self.mode = mode + + # Save caret position + self.ctrl.ChooseCaretX() + + if mode == ViHelper.NORMAL: + # Set block caret + #self.ctrl.SendMsg(2512, 2) + #self.ctrl.SetSelectionMode(0) + self.RemoveSelection() + self.ctrl.SetCaretForeground(wx.Colour(255, 0, 0)) + self.ctrl.SetCaretWidth(40) + self.ctrl.SetOvertype(False) + elif mode == ViHelper.VISUAL: + self.ctrl.SetCaretWidth(40) + self.ctrl.SetCaretForeground(wx.Colour(250, 250, 210)) + self.ctrl.SetOvertype(False) + elif mode == ViHelper.INSERT: + self.insert_action = [] + self.ctrl.SetCaretWidth(1) + self.ctrl.SetCaretForeground(self.default_caret_colour) + self.ctrl.SetOvertype(False) + elif mode == ViHelper.REPLACE: + self.ctrl.SetCaretWidth(1) + self.ctrl.SetCaretForeground(self.default_caret_colour) + self.ctrl.SetOvertype(True) + + def OnMouseLeftUp(self, evt): + if len(self.ctrl.GetSelectedText()) > 0: + self.EnterVisualMode() + + def OnViKeyDown(self, evt): + """ + Handle keypresses when in Vi mode + """ + + key = evt.GetRawKeyCode() + + print key, unichr(key) + + # Pass modifier keys on + if key in (65505, 65507, 65513): + return + + accP = getAccelPairFromKeyDown(evt) + + +# if control_mask: +# if key == 102: # f +# self.startIncrementalSearch() + + if key == 65307 or accP == (2, 91): # Escape + # TODO: Move into ViHandler? + self.SetMode(ViHandler.NORMAL) + self.FlushBuffers() + return + + # There might be a better way to monitor for selection changed + if len(self.ctrl.GetSelectedText()) > 0: + self.EnterVisualMode() + + if self.mode in [1, 3]: # Insert mode, replace mode, + # Store each keyevent + # NOTE: this may be terribly inefficient (i'm not sure) + # It would be possbile to just store the text that is inserted + # however then actions would be ignored + self.insert_action.append(key) + if key in [65362, 65362]: + self.insert_action = [] + evt.Skip() + return + + m = self.mode + + # As soon as a non-motion pre command has been set the next + # key must be valid or the input will be lost + # As with self. counts have to be specified prior to this + if self.pre_key is not None: + # TODO: make these commands repeatable + # If self.pre_motion_key is also set we need to prepare for the motion + if self.pre_motion_key is not None and self.pre_keys[m][self.pre_key][0] == 3: + self.StartSelection() + # Run the motion + self.pre_keys[m][self.pre_key][1](key) + # Post pre_motion_key action + self.pre_keys[m][self.pre_motion_key][1]() + self.FlushBuffers() + return + + # Otherwise we just run it normally + else: + self.pre_keys[m][self.pre_key][1](key) + self.FlushBuffers() + return + + control_mask = False + if accP[0] == 2: # Ctrl + control_mask = True + + if 48 <= key <= 57: # Normal + if self.SetNumber(key-48): + return + elif 65456 <= key <= 65465: # Numpad + if self.SetNumber(key-65456): + return + + self.SetCount() + + # First check if key is one one that can prefix motion commands + if not self.block_pre_keys: + if self.pre_motion_key is None and key in self.pre_motion_keys[m]: + self.pre_motion_key = key + self.updateViStatus(True) + return + # If not is it one of the other pre keys? + elif key in self.pre_keys[m] and key not in self.pre_motion_keys[m]: + self.pre_key = key + self.updateViStatus(True) + return + + self.key_modifier.append(key) + if len(self.key_modifier) > 1: + key = tuple(self.key_modifier) + + + + ## Double key commands, e.g. gg, gU + #if len(self.key_modifier) > 1: + + # self.key_modifier.append(key) + + # key = tuple(self.key_modifier) + + self.updateViStatus() + + if key in self.keys[m]: + self.RunFunction(key, self.pre_motion_key) + return + elif key in self.key_mods[m]: + self.block_pre_keys = True + + if self.pre_motion_key is not None: + temp_key = (self.pre_motion_key, key) + + if temp_key in self.keys[m]: + self.RunFunction(temp_key, None) + + return + + # If we've reached this point key hasn't been recogised so + # clear buffers + self.FlushBuffers() + + def TurnOff(self): + self.ctrl.SetCaretWidth(1) + + def GetChar(self, length=1): + """ + Retrieves text from under caret + @param length: the number of characters to get + """ + pos = self.ctrl.GetCurrentPos() + start, end = self.minmax(pos, pos + length) + start = max(0, start) + end = min(end, self.ctrl.GetLength()) + return self.ctrl.GetTextRange(start, end) + + def EmulateKeypresses(self, actions): + if len(actions) > 0: + + eol = self.ctrl.GetEOLChar() + + for i in actions: + if i == 65361: + self.ctrl.CharLeft() + elif i == 65363: + self.ctrl.CharRight() + elif i == 65288: + self.ctrl.DeleteBackNotLine() + elif i in [65535, 65439]: + self.ctrl.CharRight() + self.ctrl.DeleteBack() + elif i in [65293, 65421]: # enter, return + self.ctrl.InsertText(self.ctrl.GetCurrentPos(), eol) + self.ctrl.CharRight() + elif i == 65289: # tab + self.ctrl.InsertText(self.ctrl.GetCurrentPos(), "\t") + self.ctrl.CharRight() + else: + self.ctrl.InsertText(self.ctrl.GetCurrentPos(), unichr(i)) + self.ctrl.CharRight() + + + def RepeatCmd(self): + # TODO: clean this up + if self.last_cmd is not None: + self.visualBell("GREEN") + self.ctrl.BeginUndoAction() + cmd_type, key, count, pre_motion_key = self.last_cmd + + self.count = count + actions = self.insert_action + # NOTE: Is "." only going to repeat editable commands as in vim? + if cmd_type == 1: + self.RunFunction(key, pre_motion_key) + elif cmd_type == 2: # + insertion + self.RunFunction(key, pre_motion_key) + # Emulate keypresses + # Return to normal mode + self.EmulateKeypresses(actions) + self.SetMode(ViHandler.NORMAL) + elif cmd_type == 3: + self.ReplaceChar(key) + elif cmd_type == 4: # reverse repeat + self.pre_motion_key = pre_motion_key + self.StartSelection() + self.RepeatLastBackwardFindCharCmd() + pre_key_type = self.pre_keys[self.mode][pre_motion_key][2] + self.pre_keys[self.mode][pre_motion_key][1]() + if pre_key_type == 2: + self.EmulateKeypresses(actions) + self.SetMode(ViHandler.NORMAL) + elif cmd_type == 5: # forward repeat + self.pre_motion_key = pre_motion_key + self.StartSelection() + self.RepeatLastForwardFindCharCmd() + pre_key_type = self.pre_keys[self.mode][pre_motion_key][2] + self.pre_keys[self.mode][pre_motion_key][1]() + print "hello" + if pre_key_type == 2: + print self.insert_action + self.EmulateKeypresses(actions) + self.SetMode(ViHandler.NORMAL) + self.ctrl.EndUndoAction() + self.insert_action = actions + else: + self.visualBell("RED") + + +#-------------------------------------------------------------------- +# Misc stuff +#-------------------------------------------------------------------- + def ExtendSelectionToLineEnds(self): + start_pos = self.GetSelectionStart() + end_pos = self.GetSelectionEnd() + + def JoinLines(self): + text = self.ctrl.GetSelectedText() + start_line = self.ctrl.GetCurrentLine() + if len(text) < 1: + # We need at least 2 lines to be able to join + count = self.count if self.count > 1 else 2 + self.SelectLines(start_line, min(self.ctrl.GetLineCount(), \ + start_line - 1 + count)) + else: + start_line, end_line = self._GetSelectedLines() + self.SelectLines(start_line, end_line) + + text = self.ctrl.GetSelectedText() + + eol_char = self.ctrl.GetEOLChar() + + # Probably not the most efficient way to do this + # We need to lstrip every line except the first + lines = text.split(eol_char) + new_text = [] + for i in range(len(lines)): + if lines[i] == u"": # Leave out empty lines + continue + if i == 0: + new_text.append(lines[i]) + else: + new_text.append(lines[i].lstrip()) + + self.ctrl.ReplaceSelection(" ".join(new_text)) + + def DeleteSelection(self): + self.ctrl.Clear() + + def DeleteSelectionAndInsert(self): + self.DeleteSelection() + self.Insert() + + + def RemoveSelection(self): + """ + Removes the selection. + + TODO: don't goto selection start pos + """ + pos = self.ctrl.GetAnchor() + self.ctrl.SetSelection(pos,pos) + + def StartSelection(self): + """ Saves the current position to be used for selection start """ + self._anchor = self.ctrl.GetCurrentPos() + + def StartSelectionAtAnchor(self): + """ + Saves the current position to be used for selection start using + the anchor as the selection start. + """ + if len(self.ctrl.GetSelectedText()) > 0: + self._anchor = self.ctrl.GetAnchor() + else: + self._anchor = self.ctrl.GetCurrentPos() + + def SelectInWord(self): + self.MoveCaretBackWord() + self.StartSelection() + self.MoveCaretWordEnd() + self.SelectSelection() + + def SelectInLink(self): + pos = self.ctrl.GetCurrentPos() + start_pos = self.FindChar(91, True, 0, 1, False) + self.StartSelection() + end_pos = self.FindChar(93, False, -1, 1, False) + + if start_pos and end_pos: + self.SelectSelection() + + def SelectSelection(self): + self.ctrl.SetSelection(self._anchor, self.ctrl.GetCurrentPos()) + + def SelectionOnSingleLine(self): + """ + Assume that if an EOL char is present we have mutiple lines + """ + if self.ctrl.GetEOLChar() in self.ctrl.GetSelectedText(): + return False + else: + return True + + def DeleteSelection(self): + """Yank selection and delete it""" + start, end = self._GetSelectionRange() + self.ctrl.BeginUndoAction() + self.YankSelection() + self.ctrl.Clear() + self.ctrl.GotoPos(start) + self.ctrl.EndUndoAction() + + def _GetSelectionRange(self): + """Get the range of selection such that the start is the visual start + of the selection, not the logical start. + + """ + start, end = self.minmax(self.ctrl.GetSelectionStart(), + self.ctrl.GetSelectionEnd()) + return start, end + + def _GetSelectedLines(self): + """Get the first and last line (exclusive) of selection""" + start, end = self._GetSelectionRange() + start_line, end_line = (self.ctrl.LineFromPosition(start), + self.ctrl.LineFromPosition(end - 1) + 1) + return start_line, end_line + + def HasSelection(self): + """ + Detects if there's anything selected + @rtype: bool + """ + return len(self.ctrl.GetSelectedText()) > 0 + + def InsertText(self, text): + self.ctrl.InsertText(self.ctrl.GetCurrentPos(), text) + self.MoveCaretPos(len(text)) + + def SelectLines(self, start, end): + """ + Selects lines + + @param start: start line + @param end: end line + """ + start_pos = self.GetLineStartPos(start) + end_pos = self.ctrl.GetLineEndPosition(end) + + self.ctrl.SetSelection(start_pos, end_pos) + + def SwapCase(self): + self.ctrl.BeginUndoAction() + text = self.ctrl.GetSelectedText() + if len(text) == 0: + self.StartSelection() + self.MoveCaretRight() + self.SelectSelection() + text = self.ctrl.GetSelectedText() + self.ctrl.ReplaceSelection(text.swapcase()) + self.ctrl.EndUndoAction() + + def UpperCase(self): + self.ctrl.ReplaceSelection(self.ctrl.GetSelectedText().upper()) + + def LowerCase(self): + self.ctrl.ReplaceSelection(self.ctrl.GetSelectedText().lower()) + + def Indent(self, forward=True, repeat=1, visual=False): + if visual == True: + repeat = self.count + + self.ctrl.BeginUndoAction() + # If no selected text we work on lines as specified by count + if len(self.ctrl.GetSelectedText()) < 1: + start_line = self.ctrl.GetCurrentLine() + if self.count > 1: + self.SelectLines(start_line, min(self.ctrl.GetLineCount(), \ + start_line - 1 + self.count)) + else: + start_line, end = self._GetSelectedLines() + + if self.SelectionOnSingleLine(): + self.GotoLineIndent() + + for i in range(repeat): + if forward: + self.ctrl.Tab() + else: + self.ctrl.BackTab() + + self.ctrl.GotoLine(start_line) + self.GotoLineIndent() + self.ctrl.EndUndoAction() + + def _PositionViewport(self, n): + """ + Positions the viewport around caret position + """ + lines = self.ctrl.LinesOnScreen() - 1 + current = self.ctrl.GetCurrentLine() + diff = int(lines * n) + self.ctrl.ScrollToLine(current - diff) + + + def IndentSingle(self): + self.SelectSelection() + self.Indent(True) + + def DedentSingle(self): + self.SelectSelection() + self.Indent(False) + + #-------------------------------------------------------------------- + # Visual mode + #-------------------------------------------------------------------- + + def EnterLineVisualMode(self): + print "VISUAL" + #self.ctrl.SetSelectionMode(1) + print self.ctrl.GetSelectionMode() + #if self.mode != ViHelper.VISUAL: + # self.SetMode(ViHelper.VISUAL) + # self.StartSelectionAtAnchor() + + # if self.ctrl.GetSelectedText() > 0: + # self.MoveCaretRight() + self.ctrl.SetSelectionMode(1) + print self.ctrl.GetSelectionMode() + + def EnterVisualMode(self): + """ + Change to visual (selection) mode + + Will do nothing if already in visual mode + """ + if self.mode != ViHelper.VISUAL: + self.SetMode(ViHelper.VISUAL) + self.StartSelectionAtAnchor() + + if self.ctrl.GetSelectedText() > 0: + self.MoveCaretRight() + + #-------------------------------------------------------------------- + # Searching + #-------------------------------------------------------------------- + + def FindChar(self, code=None, reverse=False, offset=0, count=1, \ + repeat=True): + """ + Searches current line for specified character. Will move the + caret to this place (+/- any offset supplied). + """ + if code is None: + return + char = unichr(code) + text, cur_pos = self.ctrl.GetCurLine() + + pos = cur_pos + + if reverse: # Search backwards + if repeat: + # First save cmd so it can be repeated later + # Vim doesn't save the count so a new one can be used next time + self.last_backward_find_cmd = \ + {"code":code,"reverse":reverse,"offset":offset} + # Also save it for the "." repeats + if self.pre_motion_key is not None and \ + self.pre_keys[self.mode][self.pre_motion_key][2] in (1,2): + self.last_cmd = 4, code, self.count, self.pre_motion_key + for i in range(count): + pos = text.rfind(char, 0, pos) + # Vim won't move the caret if all occurences are not found + if pos == -1: + return + + else: # Search forwards + if repeat: + self.last_forward_find_cmd = {"code":code,"reverse":reverse,"offset":offset,"count":count} + if self.pre_motion_key is not None and \ + self.pre_keys[self.mode][self.pre_motion_key][2] in (1,2): + self.last_cmd = 5, code, self.count, self.pre_motion_key + for i in range(count): + pos = text.find(char, pos+1) + if pos == -1: + return + + new_pos = pos + offset + + # Hack to make deleting forward consistent with vim behavoir + # same thing if in visual mode + if self.pre_motion_key is not None and not reverse \ + or self.mode == ViHelper.VISUAL: + new_pos += 1 + + if -1 < new_pos < len(text): + to_move = new_pos - cur_pos + self.MoveCaretPos(to_move) + + # If in visual mode we need to select the text + if self.mode == ViHelper.VISUAL: + self.SelectSelection() + + return to_move + return False + + + def FindNextChar(self, keycode): + self.FindChar(keycode, count=self.count) + + def FindNextCharBackwards(self, keycode): + cmd = self.FindChar(keycode, count=self.count, reverse=True) + + def FindUpToNextChar(self, keycode): + cmd = self.FindChar(keycode, count=self.count, offset=-1) + + def FindUpToNextCharBackwards(self, keycode): + cmd = self.FindChar(keycode, count=self.count, reverse=True, offset=1) + + def GetLastForwardFindCharCmd(self): + return self.last_forward_find_cmd + + def GetLastBackwardFindCharCmd(self): + return self.last_backward_find_cmd + + def RepeatLastForwardFindCharCmd(self): + args = self.GetLastForwardFindCharCmd() + if args is not None: + # Set the new count + args["count"] = self.count + self.FindChar(**args) + # If no previous command has been found break any other functions + # that might be running + else: self.pre_motion_key = None + + def RepeatLastBackwardFindCharCmd(self): + args = self.GetLastBackwardFindCharCmd() + if args is not None: + args["count"] = self.count + self.FindChar(**args) + else: self.pre_motion_key = None + + def MatchBraces(self): + """ + Brace highlighting is possible though might be a bit + excessive + """ + pos = self.ctrl.GetCurrentPos() + char = self.ctrl.GetCharAt(pos) + + if char in (40, 41): + self.ctrl.GotoPos(self.ctrl.BraceMatch(pos)) + else: + # Vim appears to only search forward for a closing brace + self.FindChar(41, False, 0, 1, False) + #-------------------------------------------------------------------- + # Replace + #-------------------------------------------------------------------- + + def ReplaceChar(self, keycode): + """ + Replaces character under caret + + Contains some custom code to allow repeating + """ + # TODO: visual indication + char = unichr(keycode) + + # If in visual mode use the seletion we have (not the count) + if self.mode == ViHelper.VISUAL: + sel_start, sel_end = self._GetSelectionRange() + count = sel_end - sel_start + self.ctrl.GotoPos(self_start) + else: + count = self.count + + # Replace does not wrap lines and fails if you try and replace + # non existent chars + line, pos = self.ctrl.GetCurLine() + if pos + count > len(line)-1: + return + + self.last_cmd = 3, keycode, count, None + + self.ctrl.BeginUndoAction() + self.StartSelection() + self.ctrl.GotoPos(self.ctrl.GetCurrentPos()+count) + self.EndDelete() + self.Repeat(self.InsertText, arg=char) + self.ctrl.EndUndoAction() + self.MoveCaretPos(-1) + + def StartReplaceMode(self): + # TODO: visual indication + self.SetMode(ViHelper.REPLACE) + + #-------------------------------------------------------------------- + # Marks + #-------------------------------------------------------------------- + + def _SetMark(self, code): + """ + Not called directly (call self.Mark instead) + """ + page = self.ctrl.presenter.getWikiWord() + self.marks[page][code] = self.ctrl.GetCurrentPos() + + def GotoMark(self, char): + page = self.ctrl.presenter.getWikiWord() + if char in self.marks[page]: + pos = self.marks[page][char] + + # If mark is set past the end of the document just + # go to the end + pos = min(self.ctrl.GetLength(), pos) + + self.ctrl.GotoPos(pos) + self.visualBell("GREEN") + return True + + self.visualBell("RED") + return False + + def GotoMarkIndent(self, char): + if self.GotoMark(char): + self.GotoLineIndent() + + #-------------------------------------------------------------------- + # Copy and Paste commands + #-------------------------------------------------------------------- + + def YankLine(self): + """Copy the current line text to the clipboard""" + + line_no = self.ctrl.GetCurrentLine() + max_line = min(line_no+self.count-1, self.ctrl.GetLineCount()) + start = self.GetLineStartPos(line_no) + end = self.ctrl.GetLineEndPosition(max_line) + + text = self.ctrl.GetTextRange(start, end) + self.ctrl.GetEOLChar() + + self.ctrl.Copy(text) + + def YankSelection(self): + """Copy the current selection to the clipboard""" + self.ctrl.Copy() + + def Yank(self): + self.SelectSelection() + self.YankSelection() + start, end = self._GetSelectionRange() + self.ctrl.GotoPos(start) + + def Put(self, before, count=None): + text = getTextFromClipboard() + + # If its not text paste as normal for now + if not text: + self.ctrl.Paste() + + # Test for line as they are handled differently + eol = self.ctrl.GetEOLChar() + eol_len = len(eol) + if len(text) > eol_len: + is_line = text[-len(eol):] == eol + else: + is_line = False + + self.ctrl.BeginUndoAction() + + if is_line: + if not before: + # If pasting a line we have to goto the end before moving caret + # down to handle long lines correctly + self.GotoLineEnd() + self.MoveCaretDown(1) + self.GotoLineStart() + + if self.HasSelection(): + self.ctrl.Clear() + + self.Repeat(self.InsertText, arg=text) + + if is_line: + self.MoveCaretUp(1) + self.GotoLineIndent() + + self.ctrl.EndUndoAction() + + #-------------------------------------------------------------------- + # Deletion commands + #-------------------------------------------------------------------- + + def EndDelete(self): + self.SelectSelection() + self.DeleteSelection() + + def EndDeleteInsert(self): + self.SelectSelection() + self.DeleteSelection() + self.Insert() + + def DeleteRight(self): + # TODO: make this less complicated + self.ctrl.BeginUndoAction() + self.StartSelection() + self.MoveCaretRight() + self.SelectSelection() + self.DeleteSelection() + self.ctrl.EndUndoAction() + + def DeleteLeft(self): + self.ctrl.BeginUndoAction() + self.StartSelection() + self.MoveCaretLeft() + self.SelectSelection() + self.DeleteSelection() + self.ctrl.EndUndoAction() + + def DeleteRightAndInsert(self): + self.DeleteRight() + self.Insert() + + def DeleteLinesAndIndentInsert(self): + self.ctrl.BeginUndoAction() + indent = self.ctrl.GetLineIndentation(self.ctrl.GetCurrentLine()) + self.DeleteLine() + self.OpenNewLine(True, indent=indent) + self.ctrl.EndUndoAction() + + def DeleteLine(self): + line_no = self.ctrl.GetCurrentLine() + max_line = min(line_no+self.count-1, self.ctrl.GetLineCount()) + start = self.GetLineStartPos(line_no) + end = self.ctrl.GetLineEndPosition(max_line)+1 + self.ctrl.SetSelection(start, end) + self.DeleteSelection() + + + #-------------------------------------------------------------------- + # Movement commands + #-------------------------------------------------------------------- + + def GetLineStartPos(self, line): + return self.ctrl.GetLineIndentPosition(line) - \ + self.ctrl.GetLineIndentation(line) + + def GotoLineStart(self): + self.ctrl.Home() + + def GotoLineEnd(self): + self.ctrl.LineEnd() + + def GotoLineIndent(self, line=None): + if line is None: line = self.ctrl.GetCurrentLine() + self.ctrl.GotoPos(self.ctrl.GetLineIndentPosition(line)) + self.ctrl.ChooseCaretX() + + def GotoColumn(self): + line = self.ctrl.GetCurrentLine() + lstart = self.ctrl.PositionFromLine(line) + lend = self.ctrl.GetLineEndPosition(line) + line_len = lend - lstart + column = min(line_len, self.count) + self.ctrl.GotoPos(lstart + column) + + self.ctrl.ChooseCaretX() + + def MoveCaretRight(self): + self.MoveCaretPos(self.count) + + def MoveCaretUp(self, count=None): + self.Repeat(self.ctrl.LineUp, count) + + def MoveCaretDown(self, count=None): + self.Repeat(self.ctrl.LineDown, count) + + def MoveCaretDownAndIndent(self, count=None): + self.Repeat(self.ctrl.LineDown, count) + self.GotoLineIndent() + + def MoveCaretLeft(self): + self.MoveCaretPos(-self.count) + + def MoveCaretPos(self, offset): + """ + Move caret by a given offset + """ + #pos = max(self.ctrl.GetCurrentPos() + offset, 0) + #pos = min(pos, self.ctrl.GetLength()) + line, line_pos = self.ctrl.GetCurLine() + line_no = self.ctrl.GetCurrentLine() + pos = max(line_pos + offset, 0) + pos = min(pos, self.ctrl.LineLength(line_no)-1) + self.ctrl.GotoPos(self.GetLineStartPos(line_no) + pos) + self.ctrl.ChooseCaretX() + + def MoveCaretLinePos(self, offset): + """ + Move caret line position by a given offset + + Faster but does not maintain line position + """ + line = max(self.ctrl.GetCurrentLine() + offset, 0) + line = min(line, self.ctrl.GetLineCount()) + self.ctrl.GotoLine(line) + self.ctrl.ChooseCaretX() + + def MoveCaretToLinePos(self, line, index): + line = max(line, 0) + line = min(line, self.ctrl.GetLineCount()) + self.ctrl.GotoLine(line) + line_start_pos = self.ctrl.GetCurrentPos() + pos = max(index, 0) + pos = min(pos, self.ctrl.GetLineEndPosition(line)-line_start_pos) + self.ctrl.GotoPos(line_start_pos+pos) + +# word-motions + + def MoveCaretNextWord(self, count=None): + self.Repeat(self.ctrl.WordRight, count) + + def MoveCaretWordEnd(self, count=None): + self.Repeat(self.ctrl.WordRightEnd, count) + + def MoveCaretBackWord(self, count=None): + self.Repeat(self.ctrl.WordLeft, count) + + def MoveCaretNextWORD(self, count=None): + """Wordbreaks are spaces""" + def func(): + self.ctrl.WordRight() + while self.GetChar(-1) and not self.GetChar(-1).isspace(): + self.ctrl.WordRight() + self.Repeat(func, count) + + def MoveCaretWordEND(self, count=None): + def func(): + self.ctrl.WordRightEnd() + while self.GetChar(-1) and not self.GetChar(-1).isspace(): + self.ctrl.WordRightEnd() + self.Repeat(func, count) + + def MoveCaretBackWORD(self, count=None): + def func(): + self.ctrl.WordRightEnd() + while self.GetChar(-1) and not self.GetChar(-1).isspace(): + self.ctrl.WordRightEnd() + self.Repeat(func, count) + + def DocumentNavigation(self, key): + + # %, G or gg + if self.true_count: + if key in [71, (103, 103)]: + # Correct for line 0 + self.MoveCaretToLinePos(self.count-1, self.ctrl.GetCurLine()[1]) + elif key == 37: # % + max_lines = self.ctrl.GetLineCount() + # Same as int(self.count / 100 * max_lines) but needs only + # integer arithmetic + line_percentage = (self.count * max_lines) // 100 + self.MoveCaretToLinePos(line_percentage, self.ctrl.GetCurLine()[1]) + + elif key == 37: + # If key is % but no count it is used for brace matching + self.MatchBraces() + + elif key == (103, 103): + self.ctrl.GotoLine(0) + + elif key == (71): + self.ctrl.GotoLine(self.ctrl.GetLineCount()) + + def GotoViewportTop(self): + self.GotoLineIndent(self.ctrl.GetFirstVisibleLine()) + + def GotoViewportMiddle(self): + self.GotoLineIndent(self.ctrl.GetMiddleVisibleLine()) + + def GotoViewportBottom(self): + self.GotoLineIndent(self.ctrl.GetLastVisibleLine()) + + def ScrollViewportTop(self): + self._PositionViewport(0) + + def ScrollViewportMiddle(self): + self._PositionViewport(0.5) + + def ScrollViewportBottom(self): + self._PositionViewport(1) + + def Undo(self, count=None): + if self.ctrl.CanUndo(): + self.visualBell("GREEN") + self.Repeat(self.ctrl.Undo, count) + else: + self.visualBell("RED") + + def Redo(self, count=None): + if self.ctrl.CanRedo(): + self.visualBell("GREEN") + self.Repeat(self.ctrl.Redo, count) + else: + self.visualBell("RED") + +# The following commands are basic ways to enter insert mode + def Insert(self): + self.SetMode(ViHelper.INSERT) + + def Append(self): + if self.ctrl.GetCurrentPos() != self.ctrl.GetLineEndPosition(self.ctrl.GetCurrentLine()): + self.ctrl.CharRight() + self.Insert() + + def InsertAtLineStart(self): + # Goto line places the caret at the start of the line + self.GotoLineIndent(self.ctrl.GetCurrentLine()) + self.ctrl.ChooseCaretX() + self.Insert() + + def AppendAtLineEnd(self): + self.ctrl.GotoPos(self.ctrl.GetLineEndPosition( + self.ctrl.GetCurrentLine())) + self.Append() + + def OpenNewLine(self, above, indent=None): + self.ctrl.BeginUndoAction() + + if indent is None: + indent = self.ctrl.GetLineIndentation(self.ctrl.GetCurrentLine()) + + if above: + self.MoveCaretUp(1) + self.GotoLineEnd() + self.ctrl.AddText(self.ctrl.GetEOLChar()) + self.ctrl.SetLineIndentation(self.ctrl.GetCurrentLine(), indent) + self.ctrl.EndUndoAction() + self.AppendAtLineEnd() + + def TruncateLine(self): + self.ctrl.LineEndExtend() + self.DeleteSelection() + + def TruncateLineAndInsert(self): + self.TruncateLine() + self.Insert() diff --git a/lib/pwiki/wikidata/WikiDataManager.py b/lib/pwiki/wikidata/WikiDataManager.py index 6b848380..8606f641 100644 --- a/lib/pwiki/wikidata/WikiDataManager.py +++ b/lib/pwiki/wikidata/WikiDataManager.py @@ -29,6 +29,7 @@ from ..SearchAndReplace import SearchReplaceOperation from .. import SpellChecker +from .. import Trashcan import DbBackendUtils, FileStorage @@ -399,6 +400,7 @@ def __init__(self, wikiConfigFilename, dbtype, wikiLangName, ignoreLock=False, # Set of camelcase words not to see as wiki words self.ccWordBlacklist = None + self.nccWordBlacklist = None self.wikiData = WikiDataSynchronizedProxy(self.baseWikiData) self.wikiPageDict = WeakValueDictionary() self.funcPageDict = WeakValueDictionary() @@ -463,7 +465,8 @@ def connect(self): GetApp().getMiscEvent().addListener(self) self._updateCcWordBlacklist() - + self._updateNccWordBlacklist() + self.readAccessFailed = False self.writeAccessFailed = False self.noAutoSaveFlag = False # Flag is set (by PersonalWikiFrame), @@ -479,8 +482,11 @@ def connect(self): self.writeAccessFailed = True raise writeException - self.updateExecutor.start() - + self.trashcan = Trashcan.Trashcan(self) + + if self.trashcan.isInDatabase(): + self.trashcan.readOverview() + if self.isSearchIndexEnabled() and self.getWikiConfig().getint( "main", "indexSearch_formatNo", 1) != Consts.SEARCHINDEX_FORMAT_NO: # Search index rebuild needed @@ -500,7 +506,9 @@ def connect(self): self.removeSearchIndex() self.pushDirtyMetaDataUpdate() - + self.updateExecutor.start() + + # if not self.isReadOnlyEffect(): # words = self.getWikiData().getWikiWordsForMetaDataState(0) # for word in words: @@ -567,6 +575,10 @@ def release(self): self.refCount = 0 self.updateExecutor.end(hardEnd=True) # TODO Inform user as this may take some time + self.trashcan.writeOverview() + self.trashcan.close() + self.trashcan = None + # Invalidate all cached pages to prevent yet running threads from # using them for page in self.wikiPageDict.values(): @@ -625,6 +637,9 @@ def getDataDir(self): def getCollator(self): return GetApp().getCollator() + def getTrashcan(self): + return self.trashcan + def getWikiDefaultWikiLanguage(self): """ Returns the internal name of the default wiki language of this wiki. @@ -760,13 +775,22 @@ def makeRelUrlAbsolute(self, relurl, addSafe=''): """ Return the absolute file: URL for a rel: URL """ - relpath = pathnameFromUrl(relurl[6:], False) + if relurl.startswith(u"rel://"): + relpath = pathnameFromUrl(relurl[6:], False) + + url = u"file:" + urlFromPathname( + os.path.abspath(os.path.join(os.path.dirname( + self.getWikiConfigPath()), relpath)), addSafe=addSafe) - url = u"file:" + urlFromPathname( - os.path.abspath(os.path.join(os.path.dirname( - self.getWikiConfigPath()), relpath)), addSafe=addSafe) + return url + elif relurl.startswith(u"wikirel://"): + relpath = pathnameFromUrl(relurl[10:], False) - return url + url = u"wiki:" + urlFromPathname( + os.path.abspath(os.path.join(os.path.dirname( + self.getWikiConfigPath()), relpath)), addSafe=addSafe) + + return url def makeAbsPathRelUrl(self, absPath, addSafe=''): @@ -1289,6 +1313,8 @@ def rebuildWiki(self, progresshandler, onlyDirty): # progresshandler.update(0, _(u"Waiting for update thread to end")) + self.fireMiscEventKeys(("begin foreground update", "begin update")) + # re-save all of the pages try: step = 1 @@ -1394,6 +1420,7 @@ def rebuildWiki(self, progresshandler, onlyDirty): finally: progresshandler.close() + self.fireMiscEventKeys(("end foreground update",)) self.updateExecutor.start() @@ -1913,6 +1940,9 @@ def getWikiWordsModifiedLastDays(self, days): def getCcWordBlacklist(self): return self.ccWordBlacklist + def getNccWordBlacklist(self): + return self.nccWordBlacklist + def _updateCcWordBlacklist(self): """ Update the blacklist of camelcase words which should show up as normal @@ -1924,6 +1954,17 @@ def _updateCcWordBlacklist(self): bls.update(pg.getLiveText().split("\n")) self.ccWordBlacklist = bls + def _updateNccWordBlacklist(self): + """ + Update the blacklist of non-camelcase (=bracketed) words which should + show up as normal text. + """ + pg = self.getFuncPage("global/NCCBlacklist") + bls = set(pg.getLiveText().split("\n")) + pg = self.getFuncPage("wiki/NCCBlacklist") + bls.update(pg.getLiveText().split("\n")) + self.nccWordBlacklist = bls + def getUnAliasedWikiWord(self, word): """ @@ -2106,6 +2147,8 @@ def miscEventHappened(self, miscevt): elif miscevt.getSource() is GetApp(): if miscevt.has_key("reread cc blacklist needed"): self._updateCcWordBlacklist() + elif miscevt.has_key("reread ncc blacklist needed"): + self._updateNccWordBlacklist() elif miscevt.has_key("pause background threads"): self.updateExecutor.pause() elif miscevt.has_key("resume background threads"): @@ -2134,6 +2177,12 @@ def miscEventHappened(self, miscevt): elif miscevt.has_key("reread cc blacklist needed"): self._updateCcWordBlacklist() + attrs = miscevt.getProps().copy() + attrs["funcPage"] = miscevt.getSource() + self.fireMiscEventProps(attrs) + elif miscevt.has_key("reread ncc blacklist needed"): + self._updateNccWordBlacklist() + attrs = miscevt.getProps().copy() attrs["funcPage"] = miscevt.getSource() self.fireMiscEventProps(attrs) diff --git a/lib/pwiki/wikidata/compact_sqlite/WikiData.py b/lib/pwiki/wikidata/compact_sqlite/WikiData.py index 0d84c371..e16fe8e3 100644 --- a/lib/pwiki/wikidata/compact_sqlite/WikiData.py +++ b/lib/pwiki/wikidata/compact_sqlite/WikiData.py @@ -370,6 +370,46 @@ def setTimestamps(self, word, timestamps): raise DbWriteAccessError(e) + def setWikiWordReadOnly(self, word, flag): + """ + Set readonly flag of a wikiword. Warning: Methods in WikiData do not + respect this flag. + word -- wikiword to modify (must exist, aliases must be resolved + beforehand) + flag -- integer value. 0: Readwrite; &1: Readonly + """ + try: + data = self.connWrap.execSqlQuery("select word from wikiwordcontent " + "where word = ?", (word,)) + except (IOError, OSError, sqlite.Error), e: + traceback.print_exc() + raise DbReadAccessError(e) + + try: + if len(data) < 1: + raise WikiFileNotFoundException + else: + self.connWrap.execSql("update wikiwordcontent set readonly = ? " + "where word = ?", (flag, word)) + except (IOError, OSError, sqlite.Error), e: + traceback.print_exc() + raise DbWriteAccessError(e) + + + def getWikiWordReadOnly(self, word): + """ + Returns readonly flag of a wikiword. Warning: Methods in WikiData do not + respect this flag. + """ + try: + return self.connWrap.execSqlQuerySingleItem( + "select readonly from wikiwordcontent where word = ?", + (word,)) + except (IOError, OSError, sqlite.Error), e: + traceback.print_exc() + raise DbReadAccessError(e) + + def getExistingWikiWordInfo(self, wikiWord, withFields=()): """ Get information about an existing wiki word @@ -384,6 +424,7 @@ def getExistingWikiWordInfo(self, wikiWord, withFields=()): "modified": Modification date of page "created": Creation date of page "visited": Last visit date of page + "readonly": Read only flag "firstcharpos": Dummy returning very high value """ if withFields is None: @@ -402,6 +443,9 @@ def getExistingWikiWordInfo(self, wikiWord, withFields=()): elif field == "visited": addFields += ", visited" converters.append(float) + elif field == "readonly": + addFields += ", readonly" + converters.append(int) elif field == "firstcharpos": # Fake character position. TODO More elegantly addFields += ", 0" diff --git a/lib/pwiki/wikidata/original_gadfly/WikiData.py b/lib/pwiki/wikidata/original_gadfly/WikiData.py index c8f0b361..185d0a1e 100644 --- a/lib/pwiki/wikidata/original_gadfly/WikiData.py +++ b/lib/pwiki/wikidata/original_gadfly/WikiData.py @@ -341,6 +341,50 @@ def setTimestamps(self, word, timestamps): raise DbWriteAccessError(e) + def setWikiWordReadOnly(self, word, flag): + """ + Set readonly flag of a wikiword. Warning: Methods in WikiData do not + respect this flag. + word -- wikiword to modify (must exist, aliases must be resolved + beforehand) + flag -- integer value. 0: Readwrite; &1: Readonly + """ + try: + data = self.connWrap.execSqlQuery("select word from wikiwords " + "where word = ?", (word,)) + except (IOError, OSError, sqlite.Error), e: + traceback.print_exc() + raise DbReadAccessError(e) + + try: + if len(data) < 1: + raise WikiFileNotFoundException + else: + self.connWrap.execSql("update wikiwords set readonly = ? " + "where word = ?", (flag, word)) + except (IOError, OSError, ValueError), e: + traceback.print_exc() + raise DbWriteAccessError(e) + + + def getWikiWordReadOnly(self, word): + """ + Returns readonly flag of a wikiword. Warning: Methods in WikiData do not + respect this flag. + """ + try: + data = self.connWrap.execSqlQuerySingleItem( + "select readonly from wikiwords where word = ?", + (word,)) + if data is None: + return None + else: + return int(data) + except (IOError, OSError, ValueError), e: + traceback.print_exc() + raise DbReadAccessError(e) + + def getExistingWikiWordInfo(self, wikiWord, withFields=()): """ Get information about an existing wiki word @@ -355,6 +399,7 @@ def getExistingWikiWordInfo(self, wikiWord, withFields=()): "modified": Modification date of page "created": Creation date of page "visited": Last visit date of page + "readonly": Read only flag "firstcharpos": Dummy returning very high value """ if withFields is None: @@ -373,6 +418,9 @@ def getExistingWikiWordInfo(self, wikiWord, withFields=()): elif field == "visited": addFields += ", visited" converters.append(float) + elif field == "readonly": + addFields += ", readonly" + converters.append(int) elif field == "firstcharpos": # Fake character position. TODO More elegantly addFields += ", 0" diff --git a/lib/pwiki/wikidata/original_sqlite/WikiData.py b/lib/pwiki/wikidata/original_sqlite/WikiData.py index 27b76c78..081962df 100644 --- a/lib/pwiki/wikidata/original_sqlite/WikiData.py +++ b/lib/pwiki/wikidata/original_sqlite/WikiData.py @@ -408,6 +408,46 @@ def setTimestamps(self, word, timestamps): raise DbWriteAccessError(e) + def setWikiWordReadOnly(self, word, flag): + """ + Set readonly flag of a wikiword. Warning: Methods in WikiData do not + respect this flag. + word -- wikiword to modify (must exist, aliases must be resolved + beforehand) + flag -- integer value. 0: Readwrite; &1: Readonly + """ + try: + data = self.connWrap.execSqlQuery("select word from wikiwords " + "where word = ?", (word,)) + except (IOError, OSError, sqlite.Error), e: + traceback.print_exc() + raise DbReadAccessError(e) + + try: + if len(data) < 1: + raise WikiFileNotFoundException + else: + self.connWrap.execSql("update wikiwords set readonly = ? " + "where word = ?", (flag, word)) + except (IOError, OSError, sqlite.Error), e: + traceback.print_exc() + raise DbWriteAccessError(e) + + + def getWikiWordReadOnly(self, word): + """ + Returns readonly flag of a wikiword. Warning: Methods in WikiData do not + respect this flag. + """ + try: + return self.connWrap.execSqlQuerySingleItem( + "select readonly from wikiwords where word = ?", + (word,)) + except (IOError, OSError, sqlite.Error), e: + traceback.print_exc() + raise DbReadAccessError(e) + + def getExistingWikiWordInfo(self, wikiWord, withFields=()): """ Get information about an existing wiki word @@ -422,6 +462,7 @@ def getExistingWikiWordInfo(self, wikiWord, withFields=()): "modified": Modification date of page "created": Creation date of page "visited": Last visit date of page + "readonly": Read only flag "firstcharpos": Dummy returning very high value """ if withFields is None: @@ -440,6 +481,9 @@ def getExistingWikiWordInfo(self, wikiWord, withFields=()): elif field == "visited": addFields += ", visited" converters.append(float) + elif field == "readonly": + addFields += ", readonly" + converters.append(int) elif field == "firstcharpos": # Fake character position. TODO More elegantly addFields += ", 0" diff --git a/lib/pwiki/wxHelper.py b/lib/pwiki/wxHelper.py index 3b050742..8f25776b 100644 --- a/lib/pwiki/wxHelper.py +++ b/lib/pwiki/wxHelper.py @@ -8,7 +8,14 @@ from WikiExceptions import * from .MiscEvent import KeyFunctionSink -from . import SystemInfo +from . import SystemInfo, StringOps + + +# try: +# import gtk, gobject +# except: +# gtk = None +# gobject = None def _unescapeWithRe(text): @@ -188,23 +195,140 @@ def getTextFromClipboard(): else: return u"" return None - -# DO NOT DELETE! Useful later for retrieving HTML URL source -# dataob = wx.CustomDataObject(wx.CustomDataFormat("HTML Format")) -# -# print "--getTextFromClipboard5" -# if cb.GetData(dataob): -# print "--getTextFromClipboard6" -# if dataob.GetSize() > 0: -# print "--getTextFromClipboard7", repr(dataob.GetData()[:800]) -# return lineendToInternal(dataob.GetData()) -# else: -# return u"" -# return None finally: cb.Close() +if SystemInfo.isWindows(): + # Windows variant + def getHtmlFromClipboard(): + """ + Retrieve HTML source from clipboard. Returns a tuple (source, URL) + where source is the HTML sourcecode and URL is the URL where it came + from. Both or one of the items may be None. + """ + from StringOps import lineendToInternal, mbcsDec + + cb = wx.TheClipboard + cb.Open() + try: + dataob = wx.CustomDataObject(wx.CustomDataFormat("HTML Format")) + + if cb.GetData(dataob): + if dataob.GetSize() > 0: + raw = dataob.GetData() + + # Windows HTML clipboard format contains a header with additional + # information + start = None + end = None + sourceUrl = None + + canBreak = lambda : start is not None and end is not None \ + and sourceUrl is not None + + pos = 0 + try: + for line in raw.split("\r\n"): + if line.startswith("StartFragment:"): + start = int(line[14:]) + if canBreak(): + break + elif line.startswith("EndFragment:"): + end = int(line[14:]) + if canBreak(): + break + elif line.startswith("SourceURL:"): + sourceUrl = line[10:] + if canBreak(): + break + pos += len(line) + 2 + if start is not None and pos >= start: + break + + except ValueError: + return (None, None) + + if start is None or end is None: + return (None, None) + + return (lineendToInternal(dataob.GetData()[start:end]).decode( + "utf-8", "replace"), sourceUrl) + + return (None, None) + finally: + cb.Close() + +# elif gtk is not None and gobject is not None: +# # GTK variant +# def getHtmlFromClipboard(): +# """ +# Retrieve HTML source from clipboard. Returns a tuple (source, URL) +# where source is the HTML sourcecode and URL is the URL where it came +# from. Both or one of the items may be None. For GTK second item is always +# None. +# """ +# clipboard = gtk.Clipboard() +# targets = clipboard.wait_for_targets() +# +# if "text/html" in targets: +# contents = clipboard.wait_for_contents("text/html") +# if contents: +# +# # Firefox data needs to be formated first +# if "text/_moz_htmlinfo" in targets: +# d = contents.data.decode('utf_16').replace(u'\x00', u'').strip() +# else: +# d = contents.data.strip() +# +# text = d # getData(d) +# return (text, None) +# +# return (None, None) + + +else: + # GTK variant + def getHtmlFromClipboard(): + """ + Retrieve HTML source from clipboard. Returns a tuple (source, URL) + where source is the HTML sourcecode and URL is the URL where it came + from. Both or one of the items may be None. For GTK second item is always + None. + """ + from StringOps import lineendToInternal, mbcsDec + + cb = wx.TheClipboard + cb.Open() + try: + dataob = wx.CustomDataObject(wx.CustomDataFormat("text/html")) + if cb.GetData(dataob): + if dataob.GetSize() > 0: + raw = dataob.GetData() + return (lineendToInternal(StringOps.fileContentToUnicode( + raw)), None) + finally: + cb.Close() + + return (None, None) + +# else: +# # Dummy variant +# def getHtmlFromClipboard(): +# """ +# Retrieve HTML source from clipboard. Returns a tuple (source, URL) +# where source is the HTML sourcecode and URL is the URL where it came +# from. Both or one of the items may be None. For the dummy implementation +# both are always None. +# """ +# return (None, None) + + +# For testing +# getTextFromClipboard = lambda : getHtmlFromClipboard()[0] + + + def textToDataObject(text=None): """ Create data object for an unicode string diff --git a/lib/whoosh/classify.py b/lib/whoosh/classify.py index ed81f921..32b669b8 100644 --- a/lib/whoosh/classify.py +++ b/lib/whoosh/classify.py @@ -1,431 +1,431 @@ -#=============================================================================== -# Copyright 2008 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -"""Classes and functions for classifying and extracting information from -documents. -""" - -from __future__ import division -from collections import defaultdict -from math import log, sqrt - - -# Expansion models - -class ExpansionModel(object): - def __init__(self, doc_count, field_length): - self.N = doc_count - self.collection_total = field_length - self.mean_length = self.collection_total / self.N - - def normalizer(self, maxweight, top_total): - raise NotImplementedError - - def score(self, weight_in_top, weight_in_collection, top_total): - raise NotImplementedError - - -class Bo1Model(ExpansionModel): - def normalizer(self, maxweight, top_total): - f = maxweight / self.N - return (maxweight * log((1.0 + f) / f) + log(1.0 + f)) / log(2.0) - - def score(self, weight_in_top, weight_in_collection, top_total): - f = weight_in_collection / self.N - return weight_in_top * log((1.0 + f) / f, 2) + log(1.0 + f, 2) - - -class Bo2Model(ExpansionModel): - def normalizer(self, maxweight, top_total): - f = maxweight * self.N / self.collection_total - return (maxweight * log((1.0 + f) / f, 2) + log(1.0 + f, 2)) - - def score(self, weight_in_top, weight_in_collection, top_total): - f = weight_in_top * top_total / self.collection_total - return weight_in_top * log((1.0 + f) / f, 2) + log(1.0 + f, 2) - - -class KLModel(ExpansionModel): - def normalizer(self, maxweight, top_total): - return maxweight * log(self.collection_total / top_total) / log(2.0) * top_total - - def score(self, weight_in_top, weight_in_collection, top_total): - wit_over_tt = weight_in_top / top_total - wic_over_ct = weight_in_collection / self.collection_total - - if wit_over_tt < wic_over_ct: - return 0 - else: - return wit_over_tt * log((wit_over_tt) / (weight_in_top / self.collection_total), 2) - - -class Expander(object): - """Uses an ExpansionModel to expand the set of query terms based on the top - N result documents. - """ - - def __init__(self, ixreader, fieldname, model=Bo1Model): - """ - :param reader: A :class:whoosh.reading.IndexReader object. - :param fieldname: The name of the field in which to search. - :param model: (classify.ExpansionModel) The model to use for expanding - the query terms. If you omit this parameter, the expander uses - scoring.Bo1Model by default. - """ - - self.ixreader = ixreader - self.fieldname = fieldname - - if type(model) is type: - model = model(self.ixreader.doc_count_all(), - self.ixreader.field_length(fieldname)) - self.model = model - - # Cache the collection frequency of every term in this field. This - # turns out to be much faster than reading each individual weight - # from the term index as we add words. - self.collection_freq = dict((word, freq) for word, _, freq - in self.ixreader.iter_field(self.fieldname)) - - # Maps words to their weight in the top N documents. - self.topN_weight = defaultdict(float) - - # Total weight of all terms in the top N documents. - self.top_total = 0 - - def add(self, vector): - """Adds forward-index information about one of the "top N" documents. - - :param vector: A series of (text, weight) tuples, such as is - returned by Reader.vector_as("weight", docnum, fieldname). - """ - - total_weight = 0 - topN_weight = self.topN_weight - - for word, weight in vector: - total_weight += weight - topN_weight[word] += weight - - self.top_total += total_weight - - def add_document(self, docnum): - if self.ixreader.has_vector(docnum, self.fieldname): - self.add(self.ixreader.vector_as("weight", docnum, self.fieldname)) - elif self.ixreader.field(self.fieldname).stored: - self.add_text(self.ixreader.stored_fields(docnum).get(self.fieldname)) - else: - raise Exception("Field %r in document %s is not vectored or stored" % (self.fieldname, docnum)) - - def add_text(self, string): - field = self.ixreader.field(self.fieldname) - self.add((text, weight) for text, freq, weight, value - in field.index(string)) - - def expanded_terms(self, number, normalize=True): - """Returns the N most important terms in the vectors added so far. - - :param number: The number of terms to return. - :param normalize: Whether to normalize the weights. - :*returns*: A list of ("term", weight) tuples. - """ - - model = self.model - tlist = [] - maxweight = 0 - collection_freq = self.collection_freq - - for word, weight in self.topN_weight.iteritems(): - if word in collection_freq: - score = model.score(weight, collection_freq[word], self.top_total) - if score > maxweight: maxweight = score - tlist.append((score, word)) - - if normalize: - norm = model.normalizer(maxweight, self.top_total) - else: - norm = maxweight - tlist = [(weight / norm, t) for weight, t in tlist] - tlist.sort(reverse=True) - - return [(t, weight) for weight, t in tlist[:number]] - - -# Clustering - -def median(nums): - nums = sorted(nums) - l = len(nums) - if l % 2: # Odd - return nums[l // 2] - else: - return (nums[l // 2 - 1] + nums[l // 2]) / 2.0 - - -def mean(nums): - return sum(nums) / len(nums) - - -def minkowski_distance(x, y, p=2): - assert(len(y)==len(x)) - s = sum(abs(x[i] - y[i]) ** p for i in xrange(len(x))) - return s ** 1.0/p - - -def list_to_matrix(ls, f, symmetric=False, diagonal=None): - matrix = [] - for rownum, i1 in enumerate(ls): - row = [] - for colnum, i2 in enumerate(ls): - if diagonal is not None and rownum == colnum: - # Cell on the diagonal - row.append(diagonal) - elif symmetric and colnum < rownum: - # Matrix is symmetrical and we've already calculated this cell - # on the other side of the diagonal. - row.append(matrix[colnum][rownum]) - else: - row.append(f(i1, i2)) - matrix.append(row) - return matrix - - -def magnitude(v): - return sqrt(sum(v[i] ** 2 for i in xrange(len(v)))) - - -def dot_product(v1, v2): - assert len(v1) == len(v2) - return sum(v1[i] * v2[i] for i in xrange(len(v1))) - - -def centroid(points, method=median): - return tuple(method([point[i] for point in points]) - for i in xrange(len(points[0]))) - - -class Cluster(object): - def __init__(self, *items): - self.items = list(items) - - def __repr__(self): - return "" % (self.items, ) - - def __len__(self): - return len(self.items) - - def __add__(self, cluster): - return Cluster(self.items + cluster.items) - - def __iter__(self): - return iter(self.items) - - def __getitem__(self, n): - return self.items.__getitem__(n) - - def append(self, item): - self.items.append(item) - - def remove(self, item): - self.items.remove(item) - - def pop(self, i=None): - return self.items.pop(i) - - def flatten(self): - for item in self.items: - if isinstance(item, Cluster): - for i2 in item.flatten(): - yield i2 - else: - yield item - - def dump(self, tab=0): - print "%s-" % (" " * tab, ) - for item in self.items: - if isinstance(item, Cluster): - item.dump(tab+2) - else: - print "%s%r" % (" " * tab, item) - - -class HierarchicalClustering(object): - def __init__(self, distance_fn, linkage="uclus"): - self.distance = distance_fn - if linkage == "uclus": - self.linkage = self.uclus_dist - if linkage == "average": - self.linkage = self.average_linkage_dist - if linkage == "complete": - self.linkage = self.complete_linkage_dist - if linkage == "single": - self.linkage = self.single_linkage_dist - - def uclus_dist(self, x, y): - distances = [] - for xi in x.flatten(): - for yi in y.flatten(): - distances.append(self.distance(xi, yi)) - return median(distances) - - def average_linkage_dist(self, x, y): - distances = [] - for xi in x.flatten(): - for yi in y.flatten(): - distances.append(self.distance(xi, yi)) - return mean(distances) - - def complete_linkage_dist(self, x, y): - maxdist = self.distance(x[0], y[0]) - for xi in x.flatten(): - for yi in y.flatten(): - maxdist = max(maxdist, self.distance(xi, yi)) - return maxdist - - def single_linkage_dist(self, x, y): - mindist = self.distance(x[0], y[0]) - for xi in x.flatten(): - for yi in y.flatten(): - mindist = min(mindist, self.distance(xi, yi)) - return mindist - - def clusters(self, data): - data = [Cluster(x) for x in data] - linkage = self.linkage - matrix = None - sequence = 0 - while matrix is None or len(matrix) > 2: - matrix = list_to_matrix(data, linkage, True, 0) - lowrow, lowcol = None, None - mindist = None - for rownum, row in enumerate(matrix): - for colnum, cell in enumerate(row): - if rownum != colnum and (cell < mindist or lowrow is None): - lowrow, lowcol = rownum, colnum - mindist = cell - - sequence += 1 - cluster = Cluster(data[lowrow], data[lowcol]) - - data.remove(data[max(lowrow, lowcol)]) - data.remove(data[min(lowrow, lowcol)]) - data.append(cluster) - - if isinstance(data, list): - data = Cluster(*data) - return data - - -class KMeansClustering(object): - def __init__(self, distance_fn=None): - self.distance = distance_fn or minkowski_distance - - def clusters(self, data, count): - if len(data) > 1 and isinstance(data[0], (list, tuple)): - l = len(data[0]) - if not all(len(item) == l for item in data[1:]): - raise ValueError("All items in %r are not of the same dimension" % (data, )) - if count <= 1: - raise ValueError("You must ask for at least 2 clusters") - if not data or len(data) == 1 or count >= len(data): - return data - - - clusters = [Cluster() for _ in xrange(count)] - for i, item in enumerate(data): - clusters[i % count].append(item) - - def move_item(item, pos, origin): - closest = origin - for cluster in clusters: - if (self.distance(item, centroid(cluster)) - < self.distance(item, centroid(closest))): - closest = cluster - if closest is not origin: - closest.append(origin.pop(pos)) - return True - return False - - moved = True - while moved: - moved = False - for cluster in clusters: - for pos, item in enumerate(cluster): - moved = move_item(item, pos, cluster) or moved - - return clusters - - -# Similarity functions - -def shingles(input, size=2): - d = defaultdict(int) - for shingle in (input[i:i+size] for i in xrange(len(input)-(size-1))): - d[shingle] += 1 - return d.iteritems() - - -def simhash(features, hashbits=32): - if hashbits == 32: - hashfn = hash - else: - hashfn = lambda s: _hash(s, hashbits) - - vs = [0] * hashbits - for feature, weight in features: - h = hashfn(feature) - for i in xrange(hashbits): - if h & (1 << i): - vs[i] += weight - else: - vs[i] -= weight - - out = 0 - for i, v in enumerate(vs): - if v > 0: - out |= 1 << i - return out - - -def _hash(s, hashbits): - # A variable-length version of Python's builtin hash - if s == "": - return 0 - else: - x = ord(s[0])<<7 - m = 1000003 - mask = 2 ** hashbits-1 - for c in s: - x = ((x * m) ^ ord(c)) & mask - x ^= len(s) - if x == -1: - x = -2 - return x - - -def hamming_distance(first_hash, other_hash, hashbits=32): - x = (first_hash ^ other_hash) & ((1 << hashbits) - 1) - tot = 0 - while x: - tot += 1 - x &= x-1 - return tot - - - - - - +#=============================================================================== +# Copyright 2008 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +"""Classes and functions for classifying and extracting information from +documents. +""" + +from __future__ import division +from collections import defaultdict +from math import log, sqrt + + +# Expansion models + +class ExpansionModel(object): + def __init__(self, doc_count, field_length): + self.N = doc_count + self.collection_total = field_length + self.mean_length = self.collection_total / self.N + + def normalizer(self, maxweight, top_total): + raise NotImplementedError + + def score(self, weight_in_top, weight_in_collection, top_total): + raise NotImplementedError + + +class Bo1Model(ExpansionModel): + def normalizer(self, maxweight, top_total): + f = maxweight / self.N + return (maxweight * log((1.0 + f) / f) + log(1.0 + f)) / log(2.0) + + def score(self, weight_in_top, weight_in_collection, top_total): + f = weight_in_collection / self.N + return weight_in_top * log((1.0 + f) / f, 2) + log(1.0 + f, 2) + + +class Bo2Model(ExpansionModel): + def normalizer(self, maxweight, top_total): + f = maxweight * self.N / self.collection_total + return (maxweight * log((1.0 + f) / f, 2) + log(1.0 + f, 2)) + + def score(self, weight_in_top, weight_in_collection, top_total): + f = weight_in_top * top_total / self.collection_total + return weight_in_top * log((1.0 + f) / f, 2) + log(1.0 + f, 2) + + +class KLModel(ExpansionModel): + def normalizer(self, maxweight, top_total): + return maxweight * log(self.collection_total / top_total) / log(2.0) * top_total + + def score(self, weight_in_top, weight_in_collection, top_total): + wit_over_tt = weight_in_top / top_total + wic_over_ct = weight_in_collection / self.collection_total + + if wit_over_tt < wic_over_ct: + return 0 + else: + return wit_over_tt * log((wit_over_tt) / (weight_in_top / self.collection_total), 2) + + +class Expander(object): + """Uses an ExpansionModel to expand the set of query terms based on the top + N result documents. + """ + + def __init__(self, ixreader, fieldname, model=Bo1Model): + """ + :param reader: A :class:whoosh.reading.IndexReader object. + :param fieldname: The name of the field in which to search. + :param model: (classify.ExpansionModel) The model to use for expanding + the query terms. If you omit this parameter, the expander uses + scoring.Bo1Model by default. + """ + + self.ixreader = ixreader + self.fieldname = fieldname + + if type(model) is type: + model = model(self.ixreader.doc_count_all(), + self.ixreader.field_length(fieldname)) + self.model = model + + # Cache the collection frequency of every term in this field. This + # turns out to be much faster than reading each individual weight + # from the term index as we add words. + self.collection_freq = dict((word, freq) for word, _, freq + in self.ixreader.iter_field(self.fieldname)) + + # Maps words to their weight in the top N documents. + self.topN_weight = defaultdict(float) + + # Total weight of all terms in the top N documents. + self.top_total = 0 + + def add(self, vector): + """Adds forward-index information about one of the "top N" documents. + + :param vector: A series of (text, weight) tuples, such as is + returned by Reader.vector_as("weight", docnum, fieldname). + """ + + total_weight = 0 + topN_weight = self.topN_weight + + for word, weight in vector: + total_weight += weight + topN_weight[word] += weight + + self.top_total += total_weight + + def add_document(self, docnum): + if self.ixreader.has_vector(docnum, self.fieldname): + self.add(self.ixreader.vector_as("weight", docnum, self.fieldname)) + elif self.ixreader.field(self.fieldname).stored: + self.add_text(self.ixreader.stored_fields(docnum).get(self.fieldname)) + else: + raise Exception("Field %r in document %s is not vectored or stored" % (self.fieldname, docnum)) + + def add_text(self, string): + field = self.ixreader.field(self.fieldname) + self.add((text, weight) for text, freq, weight, value + in field.index(string)) + + def expanded_terms(self, number, normalize=True): + """Returns the N most important terms in the vectors added so far. + + :param number: The number of terms to return. + :param normalize: Whether to normalize the weights. + :*returns*: A list of ("term", weight) tuples. + """ + + model = self.model + tlist = [] + maxweight = 0 + collection_freq = self.collection_freq + + for word, weight in self.topN_weight.iteritems(): + if word in collection_freq: + score = model.score(weight, collection_freq[word], self.top_total) + if score > maxweight: maxweight = score + tlist.append((score, word)) + + if normalize: + norm = model.normalizer(maxweight, self.top_total) + else: + norm = maxweight + tlist = [(weight / norm, t) for weight, t in tlist] + tlist.sort(reverse=True) + + return [(t, weight) for weight, t in tlist[:number]] + + +# Clustering + +def median(nums): + nums = sorted(nums) + l = len(nums) + if l % 2: # Odd + return nums[l // 2] + else: + return (nums[l // 2 - 1] + nums[l // 2]) / 2.0 + + +def mean(nums): + return sum(nums) / len(nums) + + +def minkowski_distance(x, y, p=2): + assert(len(y)==len(x)) + s = sum(abs(x[i] - y[i]) ** p for i in xrange(len(x))) + return s ** 1.0/p + + +def list_to_matrix(ls, f, symmetric=False, diagonal=None): + matrix = [] + for rownum, i1 in enumerate(ls): + row = [] + for colnum, i2 in enumerate(ls): + if diagonal is not None and rownum == colnum: + # Cell on the diagonal + row.append(diagonal) + elif symmetric and colnum < rownum: + # Matrix is symmetrical and we've already calculated this cell + # on the other side of the diagonal. + row.append(matrix[colnum][rownum]) + else: + row.append(f(i1, i2)) + matrix.append(row) + return matrix + + +def magnitude(v): + return sqrt(sum(v[i] ** 2 for i in xrange(len(v)))) + + +def dot_product(v1, v2): + assert len(v1) == len(v2) + return sum(v1[i] * v2[i] for i in xrange(len(v1))) + + +def centroid(points, method=median): + return tuple(method([point[i] for point in points]) + for i in xrange(len(points[0]))) + + +class Cluster(object): + def __init__(self, *items): + self.items = list(items) + + def __repr__(self): + return "" % (self.items, ) + + def __len__(self): + return len(self.items) + + def __add__(self, cluster): + return Cluster(self.items + cluster.items) + + def __iter__(self): + return iter(self.items) + + def __getitem__(self, n): + return self.items.__getitem__(n) + + def append(self, item): + self.items.append(item) + + def remove(self, item): + self.items.remove(item) + + def pop(self, i=None): + return self.items.pop(i) + + def flatten(self): + for item in self.items: + if isinstance(item, Cluster): + for i2 in item.flatten(): + yield i2 + else: + yield item + + def dump(self, tab=0): + print "%s-" % (" " * tab, ) + for item in self.items: + if isinstance(item, Cluster): + item.dump(tab+2) + else: + print "%s%r" % (" " * tab, item) + + +class HierarchicalClustering(object): + def __init__(self, distance_fn, linkage="uclus"): + self.distance = distance_fn + if linkage == "uclus": + self.linkage = self.uclus_dist + if linkage == "average": + self.linkage = self.average_linkage_dist + if linkage == "complete": + self.linkage = self.complete_linkage_dist + if linkage == "single": + self.linkage = self.single_linkage_dist + + def uclus_dist(self, x, y): + distances = [] + for xi in x.flatten(): + for yi in y.flatten(): + distances.append(self.distance(xi, yi)) + return median(distances) + + def average_linkage_dist(self, x, y): + distances = [] + for xi in x.flatten(): + for yi in y.flatten(): + distances.append(self.distance(xi, yi)) + return mean(distances) + + def complete_linkage_dist(self, x, y): + maxdist = self.distance(x[0], y[0]) + for xi in x.flatten(): + for yi in y.flatten(): + maxdist = max(maxdist, self.distance(xi, yi)) + return maxdist + + def single_linkage_dist(self, x, y): + mindist = self.distance(x[0], y[0]) + for xi in x.flatten(): + for yi in y.flatten(): + mindist = min(mindist, self.distance(xi, yi)) + return mindist + + def clusters(self, data): + data = [Cluster(x) for x in data] + linkage = self.linkage + matrix = None + sequence = 0 + while matrix is None or len(matrix) > 2: + matrix = list_to_matrix(data, linkage, True, 0) + lowrow, lowcol = None, None + mindist = None + for rownum, row in enumerate(matrix): + for colnum, cell in enumerate(row): + if rownum != colnum and (cell < mindist or lowrow is None): + lowrow, lowcol = rownum, colnum + mindist = cell + + sequence += 1 + cluster = Cluster(data[lowrow], data[lowcol]) + + data.remove(data[max(lowrow, lowcol)]) + data.remove(data[min(lowrow, lowcol)]) + data.append(cluster) + + if isinstance(data, list): + data = Cluster(*data) + return data + + +class KMeansClustering(object): + def __init__(self, distance_fn=None): + self.distance = distance_fn or minkowski_distance + + def clusters(self, data, count): + if len(data) > 1 and isinstance(data[0], (list, tuple)): + l = len(data[0]) + if not all(len(item) == l for item in data[1:]): + raise ValueError("All items in %r are not of the same dimension" % (data, )) + if count <= 1: + raise ValueError("You must ask for at least 2 clusters") + if not data or len(data) == 1 or count >= len(data): + return data + + + clusters = [Cluster() for _ in xrange(count)] + for i, item in enumerate(data): + clusters[i % count].append(item) + + def move_item(item, pos, origin): + closest = origin + for cluster in clusters: + if (self.distance(item, centroid(cluster)) + < self.distance(item, centroid(closest))): + closest = cluster + if closest is not origin: + closest.append(origin.pop(pos)) + return True + return False + + moved = True + while moved: + moved = False + for cluster in clusters: + for pos, item in enumerate(cluster): + moved = move_item(item, pos, cluster) or moved + + return clusters + + +# Similarity functions + +def shingles(input, size=2): + d = defaultdict(int) + for shingle in (input[i:i+size] for i in xrange(len(input)-(size-1))): + d[shingle] += 1 + return d.iteritems() + + +def simhash(features, hashbits=32): + if hashbits == 32: + hashfn = hash + else: + hashfn = lambda s: _hash(s, hashbits) + + vs = [0] * hashbits + for feature, weight in features: + h = hashfn(feature) + for i in xrange(hashbits): + if h & (1 << i): + vs[i] += weight + else: + vs[i] -= weight + + out = 0 + for i, v in enumerate(vs): + if v > 0: + out |= 1 << i + return out + + +def _hash(s, hashbits): + # A variable-length version of Python's builtin hash + if s == "": + return 0 + else: + x = ord(s[0])<<7 + m = 1000003 + mask = 2 ** hashbits-1 + for c in s: + x = ((x * m) ^ ord(c)) & mask + x ^= len(s) + if x == -1: + x = -2 + return x + + +def hamming_distance(first_hash, other_hash, hashbits=32): + x = (first_hash ^ other_hash) & ((1 << hashbits) - 1) + tot = 0 + while x: + tot += 1 + x &= x-1 + return tot + + + + + + diff --git a/lib/whoosh/filedb/filestore.py b/lib/whoosh/filedb/filestore.py index d62ad633..812a4d36 100644 --- a/lib/whoosh/filedb/filestore.py +++ b/lib/whoosh/filedb/filestore.py @@ -1,195 +1,195 @@ -#=============================================================================== -# Copyright 2009 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -import os -from cStringIO import StringIO -from threading import Lock - -from whoosh.index import _DEF_INDEX_NAME -from whoosh.store import Storage -from whoosh.support.filelock import FileLock -from whoosh.filedb.structfile import StructFile - - -class FileStorage(Storage): - """Storage object that stores the index as files in a directory on disk. - """ - - def __init__(self, path, mapped=True): - self.folder = path - self.mapped = mapped - self.locks = {} - - if not os.path.exists(path): - raise IOError("Directory %s does not exist" % path) - - def __iter__(self): - return iter(self.list()) - - def create_index(self, schema, indexname=_DEF_INDEX_NAME): - from whoosh.filedb.fileindex import _create_index, FileIndex - _create_index(self, schema, indexname) - return FileIndex(self, schema, indexname) - - def open_index(self, indexname=_DEF_INDEX_NAME, schema=None): - from whoosh.filedb.fileindex import FileIndex - return FileIndex(self, schema=schema, indexname=indexname) - - def create_file(self, name): - f = StructFile(open(self._fpath(name), "wb"), name=name, - mapped=self.mapped) - return f - - def open_file(self, name, *args, **kwargs): - try: - f = StructFile(open(self._fpath(name), "rb"), name=name, *args, **kwargs) - except IOError: - print "Tried to open %r, files=%r" % (name, self.list()) - raise - return f - - def _fpath(self, fname): - return os.path.join(self.folder, fname) - - def clean(self): - path = self.folder - if not os.path.exists(path): - os.mkdir(path) - - files = self.list() - for file in files: - os.remove(os.path.join(path, file)) - - def list(self): - try: - files = os.listdir(self.folder) - except IOError: - files = [] - - return files - - def file_exists(self, name): - return os.path.exists(self._fpath(name)) - def file_modified(self, name): - return os.path.getmtime(self._fpath(name)) - def file_length(self, name): - return os.path.getsize(self._fpath(name)) - - def delete_file(self, name): - os.remove(self._fpath(name)) - - def rename_file(self, frm, to, safe=False): - if os.path.exists(self._fpath(to)): - if safe: - raise NameError("File %r exists" % to) - else: - os.remove(self._fpath(to)) - os.rename(self._fpath(frm), self._fpath(to)) - - def lock(self, name): - return FileLock(self._fpath(name)) - - def __repr__(self): - return "%s(%s)" % (self.__class__.__name__, repr(self.folder)) - - -class RamStorage(FileStorage): - """Storage object that keeps the index in memory. - """ - - def __init__(self): - self.files = {} - self.locks = {} - self.folder = '' - - def __iter__(self): - return iter(self.list()) - - def list(self): - return self.files.keys() - - def clean(self): - self.files = {} - - def total_size(self): - return sum(self.file_length(f) for f in self.list()) - - def file_exists(self, name): - return name in self.files - - def file_length(self, name): - if name not in self.files: - raise NameError - return len(self.files[name]) - - def delete_file(self, name): - if name not in self.files: - raise NameError - del self.files[name] - - def rename_file(self, name, newname, safe=False): - if name not in self.files: - raise NameError("File %r does not exist" % name) - if safe and newname in self.files: - raise NameError("File %r exists" % newname) - - content = self.files[name] - del self.files[name] - self.files[newname] = content - - def create_file(self, name): - def onclose_fn(sfile): - self.files[name] = sfile.file.getvalue() - f = StructFile(StringIO(), name=name, onclose=onclose_fn) - return f - - def open_file(self, name, *args, **kwargs): - if name not in self.files: - raise NameError("No such file %r" % name) - return StructFile(StringIO(self.files[name]), name=name, *args, **kwargs) - - def lock(self, name): - if name not in self.locks: - self.locks[name] = Lock() - return self.locks[name] - - -def copy_to_ram(storage): - """Copies the given FileStorage object into a new RamStorage object. - - :rtype: :class:`RamStorage` - """ - - import shutil #, time - #t = time.time() - ram = RamStorage() - for name in storage.list(): - f = storage.open_file(name) - r = ram.create_file(name) - shutil.copyfileobj(f.file, r.file) - f.close() - r.close() - #print time.time() - t, "to load index into ram" - return ram - - - - - - - - - +#=============================================================================== +# Copyright 2009 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +import os +from cStringIO import StringIO +from threading import Lock + +from whoosh.index import _DEF_INDEX_NAME +from whoosh.store import Storage +from whoosh.support.filelock import FileLock +from whoosh.filedb.structfile import StructFile + + +class FileStorage(Storage): + """Storage object that stores the index as files in a directory on disk. + """ + + def __init__(self, path, mapped=True): + self.folder = path + self.mapped = mapped + self.locks = {} + + if not os.path.exists(path): + raise IOError("Directory %s does not exist" % path) + + def __iter__(self): + return iter(self.list()) + + def create_index(self, schema, indexname=_DEF_INDEX_NAME): + from whoosh.filedb.fileindex import _create_index, FileIndex + _create_index(self, schema, indexname) + return FileIndex(self, schema, indexname) + + def open_index(self, indexname=_DEF_INDEX_NAME, schema=None): + from whoosh.filedb.fileindex import FileIndex + return FileIndex(self, schema=schema, indexname=indexname) + + def create_file(self, name): + f = StructFile(open(self._fpath(name), "wb"), name=name, + mapped=self.mapped) + return f + + def open_file(self, name, *args, **kwargs): + try: + f = StructFile(open(self._fpath(name), "rb"), name=name, *args, **kwargs) + except IOError: + print "Tried to open %r, files=%r" % (name, self.list()) + raise + return f + + def _fpath(self, fname): + return os.path.join(self.folder, fname) + + def clean(self): + path = self.folder + if not os.path.exists(path): + os.mkdir(path) + + files = self.list() + for file in files: + os.remove(os.path.join(path, file)) + + def list(self): + try: + files = os.listdir(self.folder) + except IOError: + files = [] + + return files + + def file_exists(self, name): + return os.path.exists(self._fpath(name)) + def file_modified(self, name): + return os.path.getmtime(self._fpath(name)) + def file_length(self, name): + return os.path.getsize(self._fpath(name)) + + def delete_file(self, name): + os.remove(self._fpath(name)) + + def rename_file(self, frm, to, safe=False): + if os.path.exists(self._fpath(to)): + if safe: + raise NameError("File %r exists" % to) + else: + os.remove(self._fpath(to)) + os.rename(self._fpath(frm), self._fpath(to)) + + def lock(self, name): + return FileLock(self._fpath(name)) + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self.folder)) + + +class RamStorage(FileStorage): + """Storage object that keeps the index in memory. + """ + + def __init__(self): + self.files = {} + self.locks = {} + self.folder = '' + + def __iter__(self): + return iter(self.list()) + + def list(self): + return self.files.keys() + + def clean(self): + self.files = {} + + def total_size(self): + return sum(self.file_length(f) for f in self.list()) + + def file_exists(self, name): + return name in self.files + + def file_length(self, name): + if name not in self.files: + raise NameError + return len(self.files[name]) + + def delete_file(self, name): + if name not in self.files: + raise NameError + del self.files[name] + + def rename_file(self, name, newname, safe=False): + if name not in self.files: + raise NameError("File %r does not exist" % name) + if safe and newname in self.files: + raise NameError("File %r exists" % newname) + + content = self.files[name] + del self.files[name] + self.files[newname] = content + + def create_file(self, name): + def onclose_fn(sfile): + self.files[name] = sfile.file.getvalue() + f = StructFile(StringIO(), name=name, onclose=onclose_fn) + return f + + def open_file(self, name, *args, **kwargs): + if name not in self.files: + raise NameError("No such file %r" % name) + return StructFile(StringIO(self.files[name]), name=name, *args, **kwargs) + + def lock(self, name): + if name not in self.locks: + self.locks[name] = Lock() + return self.locks[name] + + +def copy_to_ram(storage): + """Copies the given FileStorage object into a new RamStorage object. + + :rtype: :class:`RamStorage` + """ + + import shutil #, time + #t = time.time() + ram = RamStorage() + for name in storage.list(): + f = storage.open_file(name) + r = ram.create_file(name) + shutil.copyfileobj(f.file, r.file) + f.close() + r.close() + #print time.time() - t, "to load index into ram" + return ram + + + + + + + + + diff --git a/lib/whoosh/filedb/filetables.py b/lib/whoosh/filedb/filetables.py index 2e4ac4f3..a7e6806e 100644 --- a/lib/whoosh/filedb/filetables.py +++ b/lib/whoosh/filedb/filetables.py @@ -21,6 +21,9 @@ from sys import byteorder from array import array +import ctypes +from hashlib import md5 +# from zlib import crc32 from collections import defaultdict from cPickle import loads, dumps from struct import Struct @@ -57,7 +60,17 @@ #def _hash(value): # return abs(hash(value)) -_hash = hash + +def md5_hash(key): + # Very little bit faster than commented out variant + return ctypes.c_uint.from_buffer_copy(md5(key).digest()[:4]).value +# return int(md5(key).hexdigest(), 16) & 0xffffffff + + +# def crc32_hash(key): +# return crc32(key) & 0xffffffff + +_hash = md5_hash # Table classes diff --git a/lib/whoosh/filedb/multiproc.py b/lib/whoosh/filedb/multiproc.py index af1717ad..8219e962 100644 --- a/lib/whoosh/filedb/multiproc.py +++ b/lib/whoosh/filedb/multiproc.py @@ -1,229 +1,229 @@ -#=============================================================================== -# Copyright 2010 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -import os -from multiprocessing import Process, Queue - -from whoosh.filedb.filetables import LengthWriter, LengthReader -from whoosh.filedb.filewriting import SegmentWriter -from whoosh.filedb.pools import (imerge, PoolBase, read_run, TempfilePool, - write_postings) -from whoosh.filedb.structfile import StructFile -from whoosh.writing import IndexWriter -from whoosh.util import now - - -# Multiprocessing writer - -class SegmentWritingTask(Process): - def __init__(self, storage, indexname, segmentname, kwargs, postingqueue): - Process.__init__(self) - self.storage = storage - self.indexname = indexname - self.segmentname = segmentname - self.kwargs = kwargs - self.postingqueue = postingqueue - - self.segment = None - self.running = True - - def run(self): - pqueue = self.postingqueue - - index = self.storage.open_index(self.indexname) - writer = SegmentWriter(index, name=self.segmentname, lock=False, **self.kwargs) - - while self.running: - args = pqueue.get() - if args is None: - break - - writer.add_document(**args) - - if not self.running: - writer.cancel() - self.terminate() - else: - writer.pool.finish(writer.docnum, writer.lengthfile, - writer.termsindex, writer.postwriter) - self._segment = writer._getsegment() - - def get_segment(self): - return self._segment - - def cancel(self): - self.running = False - - -class MultiSegmentWriter(IndexWriter): - def __init__(self, index, procs=2, **writerargs): - self.index = index - self.lock = index.storage.lock(index.indexname + "_LOCK") - self.tasks = [] - self.postingqueue = Queue() - #self.resultqueue = Queue() - - names = [index._next_segment_name() for _ in xrange(procs)] - - self.tasks = [SegmentWritingTask(index.storage, index.indexname, - segname, writerargs, self.postingqueue) - for segname in names] - for task in self.tasks: - task.start() - - def add_document(self, **args): - self.postingqueue.put(args) - - def cancel(self): - for task in self.tasks: - task.cancel() - self.lock.release() - - def commit(self): - procs = len(self.tasks) - for _ in xrange(procs): - self.postingqueue.put(None) - for task in self.tasks: - print "Joining", task - task.join() - self.index.segments.append(task.get_segment()) - self.index.commit() - self.lock.release() - - -# Multiprocessing pool - -class PoolWritingTask(Process): - def __init__(self, schema, dir, postingqueue, resultqueue, limitmb): - Process.__init__(self) - self.schema = schema - self.dir = dir - self.postingqueue = postingqueue - self.resultqueue = resultqueue - self.limitmb = limitmb - - def run(self): - pqueue = self.postingqueue - rqueue = self.resultqueue - - subpool = TempfilePool(self.schema, limitmb=self.limitmb, dir=self.dir) - - while True: - code, args = pqueue.get() - - if code == -1: - doccount = args - break - if code == 0: - subpool.add_content(*args) - elif code == 1: - subpool.add_posting(*args) - elif code == 2: - subpool.add_field_length(*args) - - lenfilename = subpool.unique_name(".lengths") - subpool._write_lengths(StructFile(open(lenfilename, "wb")), doccount) - subpool.dump_run() - rqueue.put((subpool.runs, subpool.fieldlength_totals(), - subpool.fieldlength_maxes(), lenfilename)) - - -class MultiPool(PoolBase): - def __init__(self, schema, dir=None, procs=2, limitmb=32, **kw): - PoolBase.__init__(self, schema, dir=dir) - - self.procs = procs - self.limitmb = limitmb - - self.postingqueue = Queue() - self.resultsqueue = Queue() - - self.tasks = [PoolWritingTask(self.schema, self.dir, self.postingqueue, - self.resultsqueue, self.limitmb) - for _ in xrange(procs)] - for task in self.tasks: - task.start() - - def add_content(self, *args): - self.postingqueue.put((0, args)) - - def add_posting(self, *args): - self.postingqueue.put((1, args)) - - def add_field_length(self, *args): - self.postingqueue.put((2, args)) - - def cancel(self): - for task in self.tasks: - task.terminate() - self.cleanup() - - def cleanup(self): - pass - - def finish(self, doccount, lengthfile, termtable, postingwriter): - _fieldlength_totals = self._fieldlength_totals - if not self.tasks: - return - - pqueue = self.postingqueue - rqueue = self.resultsqueue - - for _ in xrange(self.procs): - pqueue.put((-1, doccount)) - - #print "Joining..." - t = now() - for task in self.tasks: - task.join() - #print "Join:", now() - t - - #print "Getting results..." - t = now() - runs = [] - lenfilenames = [] - for task in self.tasks: - taskruns, flentotals, flenmaxes, lenfilename = rqueue.get() - runs.extend(taskruns) - lenfilenames.append(lenfilename) - for fieldnum, total in flentotals.iteritems(): - _fieldlength_totals[fieldnum] += total - for fieldnum, length in flenmaxes.iteritems(): - if length > self._fieldlength_maxes.get(fieldnum, 0): - self._fieldlength_maxes[fieldnum] = length - #print "Results:", now() - t - - #print "Writing lengths..." - t = now() - lw = LengthWriter(lengthfile, doccount) - for lenfilename in lenfilenames: - sublengths = LengthReader(StructFile(open(lenfilename, "rb")), doccount) - lw.add_all(sublengths) - os.remove(lenfilename) - lw.close() - lengths = lw.reader() - #print "Lengths:", now() - t - - t = now() - iterator = imerge([read_run(runname, count) for runname, count in runs]) - total = sum(count for runname, count in runs) - write_postings(self.schema, termtable, lengths, postingwriter, iterator) - for runname, count in runs: - os.remove(runname) - #print "Merge:", now() - t - - self.cleanup() +#=============================================================================== +# Copyright 2010 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +import os +from multiprocessing import Process, Queue + +from whoosh.filedb.filetables import LengthWriter, LengthReader +from whoosh.filedb.filewriting import SegmentWriter +from whoosh.filedb.pools import (imerge, PoolBase, read_run, TempfilePool, + write_postings) +from whoosh.filedb.structfile import StructFile +from whoosh.writing import IndexWriter +from whoosh.util import now + + +# Multiprocessing writer + +class SegmentWritingTask(Process): + def __init__(self, storage, indexname, segmentname, kwargs, postingqueue): + Process.__init__(self) + self.storage = storage + self.indexname = indexname + self.segmentname = segmentname + self.kwargs = kwargs + self.postingqueue = postingqueue + + self.segment = None + self.running = True + + def run(self): + pqueue = self.postingqueue + + index = self.storage.open_index(self.indexname) + writer = SegmentWriter(index, name=self.segmentname, lock=False, **self.kwargs) + + while self.running: + args = pqueue.get() + if args is None: + break + + writer.add_document(**args) + + if not self.running: + writer.cancel() + self.terminate() + else: + writer.pool.finish(writer.docnum, writer.lengthfile, + writer.termsindex, writer.postwriter) + self._segment = writer._getsegment() + + def get_segment(self): + return self._segment + + def cancel(self): + self.running = False + + +class MultiSegmentWriter(IndexWriter): + def __init__(self, index, procs=2, **writerargs): + self.index = index + self.lock = index.storage.lock(index.indexname + "_LOCK") + self.tasks = [] + self.postingqueue = Queue() + #self.resultqueue = Queue() + + names = [index._next_segment_name() for _ in xrange(procs)] + + self.tasks = [SegmentWritingTask(index.storage, index.indexname, + segname, writerargs, self.postingqueue) + for segname in names] + for task in self.tasks: + task.start() + + def add_document(self, **args): + self.postingqueue.put(args) + + def cancel(self): + for task in self.tasks: + task.cancel() + self.lock.release() + + def commit(self): + procs = len(self.tasks) + for _ in xrange(procs): + self.postingqueue.put(None) + for task in self.tasks: + print "Joining", task + task.join() + self.index.segments.append(task.get_segment()) + self.index.commit() + self.lock.release() + + +# Multiprocessing pool + +class PoolWritingTask(Process): + def __init__(self, schema, dir, postingqueue, resultqueue, limitmb): + Process.__init__(self) + self.schema = schema + self.dir = dir + self.postingqueue = postingqueue + self.resultqueue = resultqueue + self.limitmb = limitmb + + def run(self): + pqueue = self.postingqueue + rqueue = self.resultqueue + + subpool = TempfilePool(self.schema, limitmb=self.limitmb, dir=self.dir) + + while True: + code, args = pqueue.get() + + if code == -1: + doccount = args + break + if code == 0: + subpool.add_content(*args) + elif code == 1: + subpool.add_posting(*args) + elif code == 2: + subpool.add_field_length(*args) + + lenfilename = subpool.unique_name(".lengths") + subpool._write_lengths(StructFile(open(lenfilename, "wb")), doccount) + subpool.dump_run() + rqueue.put((subpool.runs, subpool.fieldlength_totals(), + subpool.fieldlength_maxes(), lenfilename)) + + +class MultiPool(PoolBase): + def __init__(self, schema, dir=None, procs=2, limitmb=32, **kw): + PoolBase.__init__(self, schema, dir=dir) + + self.procs = procs + self.limitmb = limitmb + + self.postingqueue = Queue() + self.resultsqueue = Queue() + + self.tasks = [PoolWritingTask(self.schema, self.dir, self.postingqueue, + self.resultsqueue, self.limitmb) + for _ in xrange(procs)] + for task in self.tasks: + task.start() + + def add_content(self, *args): + self.postingqueue.put((0, args)) + + def add_posting(self, *args): + self.postingqueue.put((1, args)) + + def add_field_length(self, *args): + self.postingqueue.put((2, args)) + + def cancel(self): + for task in self.tasks: + task.terminate() + self.cleanup() + + def cleanup(self): + pass + + def finish(self, doccount, lengthfile, termtable, postingwriter): + _fieldlength_totals = self._fieldlength_totals + if not self.tasks: + return + + pqueue = self.postingqueue + rqueue = self.resultsqueue + + for _ in xrange(self.procs): + pqueue.put((-1, doccount)) + + #print "Joining..." + t = now() + for task in self.tasks: + task.join() + #print "Join:", now() - t + + #print "Getting results..." + t = now() + runs = [] + lenfilenames = [] + for task in self.tasks: + taskruns, flentotals, flenmaxes, lenfilename = rqueue.get() + runs.extend(taskruns) + lenfilenames.append(lenfilename) + for fieldnum, total in flentotals.iteritems(): + _fieldlength_totals[fieldnum] += total + for fieldnum, length in flenmaxes.iteritems(): + if length > self._fieldlength_maxes.get(fieldnum, 0): + self._fieldlength_maxes[fieldnum] = length + #print "Results:", now() - t + + #print "Writing lengths..." + t = now() + lw = LengthWriter(lengthfile, doccount) + for lenfilename in lenfilenames: + sublengths = LengthReader(StructFile(open(lenfilename, "rb")), doccount) + lw.add_all(sublengths) + os.remove(lenfilename) + lw.close() + lengths = lw.reader() + #print "Lengths:", now() - t + + t = now() + iterator = imerge([read_run(runname, count) for runname, count in runs]) + total = sum(count for runname, count in runs) + write_postings(self.schema, termtable, lengths, postingwriter, iterator) + for runname, count in runs: + os.remove(runname) + #print "Merge:", now() - t + + self.cleanup() \ No newline at end of file diff --git a/lib/whoosh/lang/lovins.py b/lib/whoosh/lang/lovins.py index f23aaac5..097665c6 100644 --- a/lib/whoosh/lang/lovins.py +++ b/lib/whoosh/lang/lovins.py @@ -1,542 +1,542 @@ -"""This module implements the Lovins stemming algorithm. Use the ``stem()`` -function:: - - stemmed_word = stem(word) -""" - -from collections import defaultdict - - -# Conditions - -def A(base): - # A No restrictions on stem - return True - -def B(base): - # B Minimum stem length = 3 - return len(base) > 2 - -def C(base): - # C Minimum stem length = 4 - return len(base) > 3 - -def D(base): - # D Minimum stem length = 5 - return len(base) > 4 - -def E(base): - # E Do not remove ending after e - return base[-1] != "e" - -def F(base): - # F Minimum stem length = 3 and do not remove ending after e - return len(base) > 2 and base[-1] != "e" - -def G(base): - # G Minimum stem length = 3 and remove ending only after f - return len(base) > 2 and base[-1] == "f" - -def H(base): - # H Remove ending only after t or ll - c1, c2 = base[-2:] - return c2 == "t" or (c2 == "l" and c1 == "l") - -def I(base): - # I Do not remove ending after o or e - c = base[-1] - return c != "o" and c != "e" - -def J(base): - # J Do not remove ending after a or e - c = base[-1] - return c != "a" and c != "e" - -def K(base): - # K Minimum stem length = 3 and remove ending only after l, i or u*e - c = base[-1] - cc = base[-3] - return len(base) > 2 and (c == "l" or c == "i" or (c == "e" and cc == "u")) - -def L(base): - # L Do not remove ending after u, x or s, unless s follows o - c1, c2 = base[-2:] - return c2 != "u" and c2 != "x" and (c2 != "s" or c1 == "o") - -def M(base): - # M Do not remove ending after a, c, e or m - c = base[-1] - return c != "a" and c!= "c" and c != "e" and c != "m" - -def N(base): - # N Minimum stem length = 4 after s**, elsewhere = 3 - return len(base) > 3 or (len(base) == 3 and base[-1] != "s") - -def O(base): - # O Remove ending only after l or i - c = base[-1] - return c == "l" or c == "i" - -def P(base): - # P Do not remove ending after c - return base[-1] != "c" - -def Q(base): - # Q Minimum stem length = 3 and do not remove ending after l or n - c = base[-1] - return len(base) > 2 and (c != "l" and c != "n") - -def R(base): - # R Remove ending only after n or r - c = base[-1] - return c == "n" or c == "r" - -def S(base): - # S Remove ending only after dr or t, unless t follows t - l2 = base[-2] - return l2 == "rd" or (base[-1] == "t" and l2 != "tt") - -def T(base): - # T Remove ending only after s or t, unless t follows o - c1, c2 = base[-2:] - return c2 == "s" or (c2 == "t" and c1 != "o") - -def U(base): - # U Remove ending only after l, m, n or r - c = base[-1] - return c == "l" or c == "m" or c == "n" or c == "r" - -def V(base): - # V Remove ending only after c - return base[-1] == "c" - -def W(base): - # W Do not remove ending after s or u - c = base[-1] - return c != "s" and c != "u" - -def X(base): - # X Remove ending only after l, i or u*e - c = base[-1] - cc = base[-3] - return c == "l" or c == "i" or (c == "e" and cc == "u") - -def Y(base): - # Y Remove ending only after in - return base[-2:] == "in" - -def Z(base): - # Z Do not remove ending after f - return base[-1] != "f" - -def a(base): - # a Remove ending only after d, f, ph, th, l, er, or, es or t - c = base[-1] - l2 = base[-2:] - return (c == "d" or c == "f" or l2 == "ph" or l2 == "th" or c == "l" - or l2 == "er" or l2 == "or" or l2 == "es" or c == "t") - -def b(base): - # b Minimum stem length = 3 and do not remove ending after met or ryst - return len(base) > 2 and not (base.endswith("met") - or base.endswith("ryst")) - -def c(base): - # c Remove ending only after l - return base[-1] == "l" - -# Endings - -m = [None] * 12 - -m[11] = dict(( - ("alistically", B), - ("arizability", A), - ("izationally", B))) -m[10] = dict(( - ("antialness", A), - ("arisations", A), - ("arizations", A), - ("entialness", A))) -m[9] = dict(( - ("allically", C), - ("antaneous", A), - ("antiality", A), - ("arisation", A), - ("arization", A), - ("ationally", B), - ("ativeness", A), - ("eableness", E), - ("entations", A), - ("entiality", A), - ("entialize", A), - ("entiation", A), - ("ionalness", A), - ("istically", A), - ("itousness", A), - ("izability", A), - ("izational", A))) -m[8] = dict(( - ("ableness", A), - ("arizable", A), - ("entation", A), - ("entially", A), - ("eousness", A), - ("ibleness", A), - ("icalness", A), - ("ionalism", A), - ("ionality", A), - ("ionalize", A), - ("iousness", A), - ("izations", A), - ("lessness", A))) -m[7] = dict(( - ("ability", A), - ("aically", A), - ("alistic", B), - ("alities", A), - ("ariness", E), - ("aristic", A), - ("arizing", A), - ("ateness", A), - ("atingly", A), - ("ational", B), - ("atively", A), - ("ativism", A), - ("elihood", E), - ("encible", A), - ("entally", A), - ("entials", A), - ("entiate", A), - ("entness", A), - ("fulness", A), - ("ibility", A), - ("icalism", A), - ("icalist", A), - ("icality", A), - ("icalize", A), - ("ication", G), - ("icianry", A), - ("ination", A), - ("ingness", A), - ("ionally", A), - ("isation", A), - ("ishness", A), - ("istical", A), - ("iteness", A), - ("iveness", A), - ("ivistic", A), - ("ivities", A), - ("ization", F), - ("izement", A), - ("oidally", A), - ("ousness", A))) -m[6] = dict(( - ("aceous", A), - ("acious", B), - ("action", G), - ("alness", A), - ("ancial", A), - ("ancies", A), - ("ancing", B), - ("ariser", A), - ("arized", A), - ("arizer", A), - ("atable", A), - ("ations", B), - ("atives", A), - ("eature", Z), - ("efully", A), - ("encies", A), - ("encing", A), - ("ential", A), - ("enting", C), - ("entist", A), - ("eously", A), - ("ialist", A), - ("iality", A), - ("ialize", A), - ("ically", A), - ("icance", A), - ("icians", A), - ("icists", A), - ("ifully", A), - ("ionals", A), - ("ionate", D), - ("ioning", A), - ("ionist", A), - ("iously", A), - ("istics", A), - ("izable", E), - ("lessly", A), - ("nesses", A), - ("oidism", A))) -m[5] = dict(( - ("acies", A), - ("acity", A), - ("aging", B), - ("aical", A), - ("alist", A), - ("alism", B), - ("ality", A), - ("alize", A), - ("allic", b), - ("anced", B), - ("ances", B), - ("antic", C), - ("arial", A), - ("aries", A), - ("arily", A), - ("arity", B), - ("arize", A), - ("aroid", A), - ("ately", A), - ("ating", I), - ("ation", B), - ("ative", A), - ("ators", A), - ("atory", A), - ("ature", E), - ("early", Y), - ("ehood", A), - ("eless", A), - ("elily", A), - ("ement", A), - ("enced", A), - ("ences", A), - ("eness", E), - ("ening", E), - ("ental", A), - ("ented", C), - ("ently", A), - ("fully", A), - ("ially", A), - ("icant", A), - ("ician", A), - ("icide", A), - ("icism", A), - ("icist", A), - ("icity", A), - ("idine", I), - ("iedly", A), - ("ihood", A), - ("inate", A), - ("iness", A), - ("ingly", B), - ("inism", J), - ("inity", c), - ("ional", A), - ("ioned", A), - ("ished", A), - ("istic", A), - ("ities", A), - ("itous", A), - ("ively", A), - ("ivity", A), - ("izers", F), - ("izing", F), - ("oidal", A), - ("oides", A), - ("otide", A), - ("ously", A))) -m[4] = dict(( - ("able", A), - ("ably", A), - ("ages", B), - ("ally", B), - ("ance", B), - ("ancy", B), - ("ants", B), - ("aric", A), - ("arly", K), - ("ated", I), - ("ates", A), - ("atic", B), - ("ator", A), - ("ealy", Y), - ("edly", E), - ("eful", A), - ("eity", A), - ("ence", A), - ("ency", A), - ("ened", E), - ("enly", E), - ("eous", A), - ("hood", A), - ("ials", A), - ("ians", A), - ("ible", A), - ("ibly", A), - ("ical", A), - ("ides", L), - ("iers", A), - ("iful", A), - ("ines", M), - ("ings", N), - ("ions", B), - ("ious", A), - ("isms", B), - ("ists", A), - ("itic", H), - ("ized", F), - ("izer", F), - ("less", A), - ("lily", A), - ("ness", A), - ("ogen", A), - ("ward", A), - ("wise", A), - ("ying", B), - ("yish", A))) -m[3] = dict(( - ("acy", A), - ("age", B), - ("aic", A), - ("als", b), - ("ant", B), - ("ars", O), - ("ary", F), - ("ata", A), - ("ate", A), - ("eal", Y), - ("ear", Y), - ("ely", E), - ("ene", E), - ("ent", C), - ("ery", E), - ("ese", A), - ("ful", A), - ("ial", A), - ("ian", A), - ("ics", A), - ("ide", L), - ("ied", A), - ("ier", A), - ("ies", P), - ("ily", A), - ("ine", M), - ("ing", N), - ("ion", Q), - ("ish", C), - ("ism", B), - ("ist", A), - ("ite", a), - ("ity", A), - ("ium", A), - ("ive", A), - ("ize", F), - ("oid", A), - ("one", R), - ("ous", A))) -m[2] = dict(( - ("ae", A), - ("al", b), - ("ar", X), - ("as", B), - ("ed", E), - ("en", F), - ("es", E), - ("ia", A), - ("ic", A), - ("is", A), - ("ly", B), - ("on", S), - ("or", T), - ("um", U), - ("us", V), - ("yl", R), - ("s'", A), - ("'s", A))) -m[1] = dict(( - ("a", A), - ("e", A), - ("i", A), - ("o", A), - ("s", W), - ("y", B))) - - -def remove_ending(word): - length = len(word) - el = 11 - while el > 0: - if length - el > 1: - ending = word[length-el:] - cond = m[el].get(ending) - if cond: - base = word[:length-el] - if cond(base): - return base - el -= 1 - return word - - -_endings = (("iev", "ief"), - ("uct", "uc"), - ("iev", "ief"), - ("uct", "uc"), - ("umpt", "um"), - ("rpt", "rb"), - ("urs", "ur"), - ("istr", "ister"), - ("metr", "meter"), - ("olv", "olut"), - ("ul", "l", "aoi"), - ("bex", "bic"), - ("dex", "dic"), - ("pex", "pic"), - ("tex", "tic"), - ("ax", "ac"), - ("ex", "ec"), - ("ix", "ic"), - ("lux", "luc"), - ("uad", "uas"), - ("vad", "vas"), - ("cid", "cis"), - ("lid", "lis"), - ("erid", "eris"), - ("pand", "pans"), - ("end", "ens", "s"), - ("ond", "ons"), - ("lud", "lus"), - ("rud", "rus"), - ("her", "hes", "pt"), - ("mit", "mis"), - ("ent", "ens", "m"), - ("ert", "ers"), - ("et", "es", "n"), - ("yt", "ys"), - ("yz", "ys")) - -# Hash the ending rules by the last letter of the target ending -_endingrules = defaultdict(list) -for rule in _endings: - _endingrules[rule[0][-1]].append(rule) - -_doubles = frozenset(("dd", "gg", "ll", "mm", "nn", "pp", "rr", "ss", "tt")) - - -def fix_ending(word): - if word[-2:] in _doubles: - word = word[:-1] - - for endingrule in _endingrules[word[-1]]: - target, newend = endingrule[:2] - if word.endswith(target): - if len(endingrule) > 2: - exceptafter = endingrule[2] - c = word[0-(len(target)+1)] - if c in exceptafter: return word - - return word[:0-len(target)] + newend - - return word - - -def stem(word): - """Returns the stemmed version of the argument string. - """ - return fix_ending(remove_ending(word)) - - - +"""This module implements the Lovins stemming algorithm. Use the ``stem()`` +function:: + + stemmed_word = stem(word) +""" + +from collections import defaultdict + + +# Conditions + +def A(base): + # A No restrictions on stem + return True + +def B(base): + # B Minimum stem length = 3 + return len(base) > 2 + +def C(base): + # C Minimum stem length = 4 + return len(base) > 3 + +def D(base): + # D Minimum stem length = 5 + return len(base) > 4 + +def E(base): + # E Do not remove ending after e + return base[-1] != "e" + +def F(base): + # F Minimum stem length = 3 and do not remove ending after e + return len(base) > 2 and base[-1] != "e" + +def G(base): + # G Minimum stem length = 3 and remove ending only after f + return len(base) > 2 and base[-1] == "f" + +def H(base): + # H Remove ending only after t or ll + c1, c2 = base[-2:] + return c2 == "t" or (c2 == "l" and c1 == "l") + +def I(base): + # I Do not remove ending after o or e + c = base[-1] + return c != "o" and c != "e" + +def J(base): + # J Do not remove ending after a or e + c = base[-1] + return c != "a" and c != "e" + +def K(base): + # K Minimum stem length = 3 and remove ending only after l, i or u*e + c = base[-1] + cc = base[-3] + return len(base) > 2 and (c == "l" or c == "i" or (c == "e" and cc == "u")) + +def L(base): + # L Do not remove ending after u, x or s, unless s follows o + c1, c2 = base[-2:] + return c2 != "u" and c2 != "x" and (c2 != "s" or c1 == "o") + +def M(base): + # M Do not remove ending after a, c, e or m + c = base[-1] + return c != "a" and c!= "c" and c != "e" and c != "m" + +def N(base): + # N Minimum stem length = 4 after s**, elsewhere = 3 + return len(base) > 3 or (len(base) == 3 and base[-1] != "s") + +def O(base): + # O Remove ending only after l or i + c = base[-1] + return c == "l" or c == "i" + +def P(base): + # P Do not remove ending after c + return base[-1] != "c" + +def Q(base): + # Q Minimum stem length = 3 and do not remove ending after l or n + c = base[-1] + return len(base) > 2 and (c != "l" and c != "n") + +def R(base): + # R Remove ending only after n or r + c = base[-1] + return c == "n" or c == "r" + +def S(base): + # S Remove ending only after dr or t, unless t follows t + l2 = base[-2] + return l2 == "rd" or (base[-1] == "t" and l2 != "tt") + +def T(base): + # T Remove ending only after s or t, unless t follows o + c1, c2 = base[-2:] + return c2 == "s" or (c2 == "t" and c1 != "o") + +def U(base): + # U Remove ending only after l, m, n or r + c = base[-1] + return c == "l" or c == "m" or c == "n" or c == "r" + +def V(base): + # V Remove ending only after c + return base[-1] == "c" + +def W(base): + # W Do not remove ending after s or u + c = base[-1] + return c != "s" and c != "u" + +def X(base): + # X Remove ending only after l, i or u*e + c = base[-1] + cc = base[-3] + return c == "l" or c == "i" or (c == "e" and cc == "u") + +def Y(base): + # Y Remove ending only after in + return base[-2:] == "in" + +def Z(base): + # Z Do not remove ending after f + return base[-1] != "f" + +def a(base): + # a Remove ending only after d, f, ph, th, l, er, or, es or t + c = base[-1] + l2 = base[-2:] + return (c == "d" or c == "f" or l2 == "ph" or l2 == "th" or c == "l" + or l2 == "er" or l2 == "or" or l2 == "es" or c == "t") + +def b(base): + # b Minimum stem length = 3 and do not remove ending after met or ryst + return len(base) > 2 and not (base.endswith("met") + or base.endswith("ryst")) + +def c(base): + # c Remove ending only after l + return base[-1] == "l" + +# Endings + +m = [None] * 12 + +m[11] = dict(( + ("alistically", B), + ("arizability", A), + ("izationally", B))) +m[10] = dict(( + ("antialness", A), + ("arisations", A), + ("arizations", A), + ("entialness", A))) +m[9] = dict(( + ("allically", C), + ("antaneous", A), + ("antiality", A), + ("arisation", A), + ("arization", A), + ("ationally", B), + ("ativeness", A), + ("eableness", E), + ("entations", A), + ("entiality", A), + ("entialize", A), + ("entiation", A), + ("ionalness", A), + ("istically", A), + ("itousness", A), + ("izability", A), + ("izational", A))) +m[8] = dict(( + ("ableness", A), + ("arizable", A), + ("entation", A), + ("entially", A), + ("eousness", A), + ("ibleness", A), + ("icalness", A), + ("ionalism", A), + ("ionality", A), + ("ionalize", A), + ("iousness", A), + ("izations", A), + ("lessness", A))) +m[7] = dict(( + ("ability", A), + ("aically", A), + ("alistic", B), + ("alities", A), + ("ariness", E), + ("aristic", A), + ("arizing", A), + ("ateness", A), + ("atingly", A), + ("ational", B), + ("atively", A), + ("ativism", A), + ("elihood", E), + ("encible", A), + ("entally", A), + ("entials", A), + ("entiate", A), + ("entness", A), + ("fulness", A), + ("ibility", A), + ("icalism", A), + ("icalist", A), + ("icality", A), + ("icalize", A), + ("ication", G), + ("icianry", A), + ("ination", A), + ("ingness", A), + ("ionally", A), + ("isation", A), + ("ishness", A), + ("istical", A), + ("iteness", A), + ("iveness", A), + ("ivistic", A), + ("ivities", A), + ("ization", F), + ("izement", A), + ("oidally", A), + ("ousness", A))) +m[6] = dict(( + ("aceous", A), + ("acious", B), + ("action", G), + ("alness", A), + ("ancial", A), + ("ancies", A), + ("ancing", B), + ("ariser", A), + ("arized", A), + ("arizer", A), + ("atable", A), + ("ations", B), + ("atives", A), + ("eature", Z), + ("efully", A), + ("encies", A), + ("encing", A), + ("ential", A), + ("enting", C), + ("entist", A), + ("eously", A), + ("ialist", A), + ("iality", A), + ("ialize", A), + ("ically", A), + ("icance", A), + ("icians", A), + ("icists", A), + ("ifully", A), + ("ionals", A), + ("ionate", D), + ("ioning", A), + ("ionist", A), + ("iously", A), + ("istics", A), + ("izable", E), + ("lessly", A), + ("nesses", A), + ("oidism", A))) +m[5] = dict(( + ("acies", A), + ("acity", A), + ("aging", B), + ("aical", A), + ("alist", A), + ("alism", B), + ("ality", A), + ("alize", A), + ("allic", b), + ("anced", B), + ("ances", B), + ("antic", C), + ("arial", A), + ("aries", A), + ("arily", A), + ("arity", B), + ("arize", A), + ("aroid", A), + ("ately", A), + ("ating", I), + ("ation", B), + ("ative", A), + ("ators", A), + ("atory", A), + ("ature", E), + ("early", Y), + ("ehood", A), + ("eless", A), + ("elily", A), + ("ement", A), + ("enced", A), + ("ences", A), + ("eness", E), + ("ening", E), + ("ental", A), + ("ented", C), + ("ently", A), + ("fully", A), + ("ially", A), + ("icant", A), + ("ician", A), + ("icide", A), + ("icism", A), + ("icist", A), + ("icity", A), + ("idine", I), + ("iedly", A), + ("ihood", A), + ("inate", A), + ("iness", A), + ("ingly", B), + ("inism", J), + ("inity", c), + ("ional", A), + ("ioned", A), + ("ished", A), + ("istic", A), + ("ities", A), + ("itous", A), + ("ively", A), + ("ivity", A), + ("izers", F), + ("izing", F), + ("oidal", A), + ("oides", A), + ("otide", A), + ("ously", A))) +m[4] = dict(( + ("able", A), + ("ably", A), + ("ages", B), + ("ally", B), + ("ance", B), + ("ancy", B), + ("ants", B), + ("aric", A), + ("arly", K), + ("ated", I), + ("ates", A), + ("atic", B), + ("ator", A), + ("ealy", Y), + ("edly", E), + ("eful", A), + ("eity", A), + ("ence", A), + ("ency", A), + ("ened", E), + ("enly", E), + ("eous", A), + ("hood", A), + ("ials", A), + ("ians", A), + ("ible", A), + ("ibly", A), + ("ical", A), + ("ides", L), + ("iers", A), + ("iful", A), + ("ines", M), + ("ings", N), + ("ions", B), + ("ious", A), + ("isms", B), + ("ists", A), + ("itic", H), + ("ized", F), + ("izer", F), + ("less", A), + ("lily", A), + ("ness", A), + ("ogen", A), + ("ward", A), + ("wise", A), + ("ying", B), + ("yish", A))) +m[3] = dict(( + ("acy", A), + ("age", B), + ("aic", A), + ("als", b), + ("ant", B), + ("ars", O), + ("ary", F), + ("ata", A), + ("ate", A), + ("eal", Y), + ("ear", Y), + ("ely", E), + ("ene", E), + ("ent", C), + ("ery", E), + ("ese", A), + ("ful", A), + ("ial", A), + ("ian", A), + ("ics", A), + ("ide", L), + ("ied", A), + ("ier", A), + ("ies", P), + ("ily", A), + ("ine", M), + ("ing", N), + ("ion", Q), + ("ish", C), + ("ism", B), + ("ist", A), + ("ite", a), + ("ity", A), + ("ium", A), + ("ive", A), + ("ize", F), + ("oid", A), + ("one", R), + ("ous", A))) +m[2] = dict(( + ("ae", A), + ("al", b), + ("ar", X), + ("as", B), + ("ed", E), + ("en", F), + ("es", E), + ("ia", A), + ("ic", A), + ("is", A), + ("ly", B), + ("on", S), + ("or", T), + ("um", U), + ("us", V), + ("yl", R), + ("s'", A), + ("'s", A))) +m[1] = dict(( + ("a", A), + ("e", A), + ("i", A), + ("o", A), + ("s", W), + ("y", B))) + + +def remove_ending(word): + length = len(word) + el = 11 + while el > 0: + if length - el > 1: + ending = word[length-el:] + cond = m[el].get(ending) + if cond: + base = word[:length-el] + if cond(base): + return base + el -= 1 + return word + + +_endings = (("iev", "ief"), + ("uct", "uc"), + ("iev", "ief"), + ("uct", "uc"), + ("umpt", "um"), + ("rpt", "rb"), + ("urs", "ur"), + ("istr", "ister"), + ("metr", "meter"), + ("olv", "olut"), + ("ul", "l", "aoi"), + ("bex", "bic"), + ("dex", "dic"), + ("pex", "pic"), + ("tex", "tic"), + ("ax", "ac"), + ("ex", "ec"), + ("ix", "ic"), + ("lux", "luc"), + ("uad", "uas"), + ("vad", "vas"), + ("cid", "cis"), + ("lid", "lis"), + ("erid", "eris"), + ("pand", "pans"), + ("end", "ens", "s"), + ("ond", "ons"), + ("lud", "lus"), + ("rud", "rus"), + ("her", "hes", "pt"), + ("mit", "mis"), + ("ent", "ens", "m"), + ("ert", "ers"), + ("et", "es", "n"), + ("yt", "ys"), + ("yz", "ys")) + +# Hash the ending rules by the last letter of the target ending +_endingrules = defaultdict(list) +for rule in _endings: + _endingrules[rule[0][-1]].append(rule) + +_doubles = frozenset(("dd", "gg", "ll", "mm", "nn", "pp", "rr", "ss", "tt")) + + +def fix_ending(word): + if word[-2:] in _doubles: + word = word[:-1] + + for endingrule in _endingrules[word[-1]]: + target, newend = endingrule[:2] + if word.endswith(target): + if len(endingrule) > 2: + exceptafter = endingrule[2] + c = word[0-(len(target)+1)] + if c in exceptafter: return word + + return word[:0-len(target)] + newend + + return word + + +def stem(word): + """Returns the stemmed version of the argument string. + """ + return fix_ending(remove_ending(word)) + + + diff --git a/lib/whoosh/lang/morph_en.py b/lib/whoosh/lang/morph_en.py index a56fc97b..66a9962a 100644 --- a/lib/whoosh/lang/morph_en.py +++ b/lib/whoosh/lang/morph_en.py @@ -1,941 +1,941 @@ -""" -Contains the variations() function for expanding an English word into multiple variations -by programmatically adding and removing suffixes. - -Translated to Python from the ``com.sun.labs.minion.lexmorph.LiteMorph_en`` class of -Sun's `Minion search engine `_. -""" - -import re - -# Rule exceptions - -exceptions = [ - "a", - "abandoner abandon abandons abandoned abandoning abandonings abandoners", - "abdomen abdomens", - "about", - "above", - "acid acids acidic acidity acidities", - "across", - "act acts acted acting actor actors", - "ad ads", - "add adds added adding addings addition additions adder adders", - "advertise advertises advertised advertising advertiser advertisers advertisement advertisements advertisings", - "after", - "again", - "against", - "ago", - "all", - "almost", - "along", - "already", - "also", - "although", - "alumna alumnae alumnus alumni", - "always", - "amen amens", - "amidships", - "amid amidst", - "among amongst", - "an", - "analysis analyses", - "and", - "another other others", - "antenna antennas antennae", - "antitheses antithesis", - "any", - "anyone anybody", - "anything", - "appendix appendixes appendices", - "apropos", - "aquarium aquariums aquaria", - "argument arguments argue argues argued arguing arguings arguer arguers", - "arise arises arose arisen ariser arisers arising arisings", - "around", - "as", - "asbestos", - "at", - "atlas atlases", - "auger augers augered augering augerings augerer augerers", - "augment augments augmented augmenting augmentings augmentation augmentations augmenter augmenters", - "automata automaton automatons", - "automation automating automate automates automated automatic", - "avoirdupois", - "awake awakes awoke awaked awoken awaker awakers awaking awakings awakening awakenings", - "away", - "awful awfully awfulness", - "axis axes axises", - "bacillus bacilli", - "bacterium bacteria", - "bad worse worst badly badness", - "bas", - "bases basis", - "bases base based basing basings basely baseness basenesses basement basements baseless basic basics", - "be am are is was were been being", - "bear bears bore borne bearing bearings bearer bearers", - "beat beats beaten beating beatings beater beaters", - "because", - "become becomes became becoming", - "beef beefs beeves beefed beefing", - "beer beers", - "before", - "begin begins began begun beginning beginnings beginner beginners", - "behalf behalves", - "being beings", - "bend bends bent bending bendings bender benders", - "bereave bereaves bereaved bereft bereaving bereavings bereavement bereavements", - "beside besides", - "best bests bested besting", - "bet bets betting bettor bettors", - "betimes", - "between", - "beyond", - "bid bids bade bidden bidding biddings bidder bidders", - "bier biers", - "bind binds bound binding bindings binder binders", - "bit bits", - "bite bites bit bitten biting bitings biter biters", - "blackfoot blackfeet", - "bleed bleeds bled bleeding bleedings bleeder bleeders", - "blow blows blew blown blowing blowings blower blowers", - "bookshelf bookshelves", - "both", - "bound bounds bounded bounding boundings bounder bounders boundless", - "bourgeois bourgeoisie", - "bra bras", - "brahman brahmans", - "break breaks broke broken breaking breakings breaker breakers", - "breed breeds bred breeding breedings breeder breeders", - "bring brings brought bringing bringings bringer bringers", - "build builds built building buildings builder builders", - "bus buses bused bussed busing bussing busings bussings buser busers busser bussers", - "buss busses bussed bussing bussings busser bussers", - "but", - "buy buys bought buying buyings buyer buyers", - "by", - "calf calves calved calving calvings calver calvers", - "can cans canned canning cannings canner canners", - "can could cannot", - "canoes canoe canoed canoeing canoeings canoer canoers", - "catch catches caught catching catchings catcher catchers", - "cement cements cemented cementing cementings cementer cementers", - "cent cents", - "center centers centered centering centerings centerless", - "child children childless childish childishly", - "choose chooses chose chosen choosing choosings chooser choosers", - "cling clings clung clinging clingings clinger clingers", - "colloquium colloquia colloquiums", - "come comes came coming comings comer comers", - "comment comments commented commenting commentings commenter commenters", - "compendium compendia compendiums", - "complement complements complemented complementing complementings complementer complementers complementary", - "compliment compliments complimented complimenting complimentings complimenter complimenters complimentary", - "concerto concertos concerti", - "condiment condiments", - "corps", - "cortex cortices cortexes cortical", - "couscous", - "creep creeps crept creeping creepings creeper creepers creepy", - "crisis crises", - "criterion criteria criterial", - "cryptanalysis cryptanalyses", - "curriculum curricula curriculums curricular", - "datum data", - "day days daily", - "deal deals dealt dealing dealings dealer dealers", - "decrement decrements decremented decrementing decrementings decrementer decrementers decremental", - "deer deers", - "demented dementia", - "desideratum desiderata", - "diagnosis diagnoses diagnose diagnosed diagnosing diagnostic", - "dialysis dialyses", - "dice dices diced dicing dicings dicer dicers", - "die dice", - "die dies died dying dyings", - "dig digs dug digging diggings digger diggers", - "dive dives diver divers dove dived diving divings", - "divest divests divester divesters divested divesting divestings divestment divestments", - "do does did done doing doings doer doers", - "document documents documented documenting documentings documenter documenters documentation documentations documentary", - "doe does", - "dove doves", - "downstairs", - "dozen", - "draw draws drew drawn drawing drawings drawer drawers", - "drink drinks drank drunk drinking drinkings drinker drinkers", - "drive drives drove driven driving drivings driver drivers driverless", - "due dues duly", - "during", - "e", - "each", - "eager eagerer eagerest eagerly eagerness eagernesses", - "early earlier earliest", - "easement easements", - "eat eats ate eaten eating eatings eater eaters", - "effluvium effluvia", - "either", - "element elements elementary", - "elf elves elfen", - "ellipse ellipses elliptic elliptical elliptically", - "ellipsis ellipses elliptic elliptical elliptically", - "else", - "embolus emboli embolic embolism", - "emolument emoluments", - "emphasis emphases", - "employ employs employed employing employer employers employee employees employment employments employable", - "enough", - "equilibrium equilibria equilibriums", - "erratum errata", - "ever", - "every", - "everything", - "exotic exotically exoticness exotica", - "experiment experiments experimented experimenting experimentings experimenter experimenters experimentation experimental", - "extra extras", - "fall falls fell fallen falling fallings faller fallers", - "far farther farthest", - "fee fees feeless", - "feed feeds fed feeding feedings feeder feeders", - "feel feels felt feeling feelings feeler feelers", - "ferment ferments fermented fermenting fermentings fermentation fermentations fermenter fermenters", - "few fewer fewest", - "fight fights fought fighting fightings fighter fighters", - "figment figments", - "filament filaments", - "find finds found finding findings finder finders", - "firmament firmaments", - "flee flees fled fleeing fleeings", - "fling flings flung flinging flingings flinger flingers", - "floe floes", - "fly flies flew flown flying flyings flier fliers flyer flyers", - "focus foci focuses focused focusing focusses focussed focussing focuser focal", - "foment foments fomented fomenting fomentings fomenter fomenters", - "foot feet", - "foot foots footed footing footer footers", - "footing footings footer footers", - "for", - "forbid forbids forbade forbidden forbidding forbiddings forbidder forbidders", - "foresee foresaw foreseen foreseeing foreseeings foreseer foreseers", - "forest forests forester foresting forestation forestations", - "forget forgets forgot forgotten forgetting forgettings forgetter forgetters forgetful", - "forsake forsakes forsook forsaken forsaking forsakings forsaker forsakers", - "found founds founded founding foundings founder founders", - "fragment fragments fragmented fragmenting fragmentings fragmentation fragmentations fragmenter fragmenters", - "free frees freer freest freed freeing freely freeness freenesses", - "freeze freezes froze frozen freezing freezings freezer freezers", - "from", - "full fully fuller fullest", - "fuller fullers full fulls fulled fulling fullings", - "fungus fungi funguses fungal", - "gallows", - "ganglion ganglia ganglions ganglionic", - "garment garments", - "gas gasses gassed gassing gassings gasser gassers", - "gas gases gasses gaseous gasless", - "gel gels gelled gelling gellings geller gellers", - "german germans germanic germany German Germans Germanic Germany", - "get gets got gotten getting gettings getter getters", - "give gives gave given giving givings giver givers", - "gladiolus gladioli gladioluses gladiola gladiolas gladiolae", - "glans glandes", - "gluiness gluey glue glues glued gluing gluings gluer gluers", - "go goes went gone going goings goer goers", - "godchild godchildren", - "good better best goodly goodness goodnesses", - "goods", - "goose geese", - "goose gooses goosed goosing goosings gooser goosers", - "grandchild grandchildren", - "grind grinds ground grinding grindings grinder grinders", - "ground grounds grounded grounding groundings grounder grounders groundless", - "grow grows grew grown growing growings grower growers growth", - "gum gums gummed gumming gummings gummer gummers", - "half halves", - "halve halves halved halving halvings halver halvers", - "hang hangs hung hanged hanging hangings hanger hangers", - "have has had having havings haver havers", - "he him his himself", - "hear hears heard hearing hearings hearer hearers", - "here", - "hide hides hid hidden hiding hidings hider hiders", - "hippopotamus hippopotami hippopotamuses", - "hold holds held holding holdings holder holders", - "honorarium honoraria honorariums", - "hoof hoofs hooves hoofed hoofing hoofer hoofers", - "how", - "hum hums hummed humming hummings hummer hummers", - "hymen hymens hymenal", - "hypotheses hypothesis hypothesize hypothesizes hypothesized hypothesizer hypothesizing hypothetical hypothetically", - "i", - "if iffy", - "impediment impediments", - "implement implements implemented implementing implementings implementation implementations implementer implementers", - "imply implies implied implying implyings implier impliers", - "in inner", - "inclement", - "increment increments incremented incrementing incrementings incrementer incrementers incremental incrementally", - "index indexes indexed indexing indexings indexer indexers", - "index indexes indices indexical indexicals", - "indoor indoors", - "instrument instruments instrumented instrumenting instrumentings instrumenter instrumenters instrumentation instrumentations instrumental", - "integument integumentary", - "into", - "it its itself", - "java", - "july julys", - "keep keeps kept keeping keepings keeper keepers", - "knife knifes knifed knifing knifings knifer knifers", - "knife knives", - "know knows knew known knowing knowings knower knowers knowledge", - "lament laments lamented lamenting lamentings lamentation lamentations lamenter lamenters lamentable lamentably", - "larva larvae larvas larval", - "late later latest lately lateness", - "latter latterly", - "lay lays laid laying layer layers", - "layer layers layered layering layerings", - "lead leads led leading leadings leader leaders leaderless", - "leaf leafs leafed leafing leafings leafer leafers", - "leaf leaves leafless", - "leave leaves left leaving leavings leaver leavers", - "lend lends lent lending lendings lender lenders", - "less lesser least", - "let lets letting lettings", - "lie lies lay lain lying lier liers", - "lie lies lied lying liar liars", - "life lives lifeless", - "light lights lit lighted lighting lightings lightly lighter lighters lightness lightnesses lightless", - "likely likelier likeliest", - "limen limens", - "lineament lineaments", - "liniment liniments", - "live alive living", - "live lives lived living livings", - "liver livers", - "loaf loafs loafed loafing loafings loafer loafers", - "loaf loaves", - "logic logics logical logically", - "lose loses lost losing loser losers loss losses", - "louse lice", - "lumen lumens", - "make makes made making makings maker makers", - "man mans manned manning mannings", - "man men", - "manly manlier manliest manliness manful manfulness manhood", - "manic manically", - "manner manners mannered mannerly mannerless mannerful", - "many", - "matrix matrices matrixes", - "may might", - "maximum maxima maximums maximal maximize maximizes maximized maximizing", - "mean means meant meaning meanings meaningless meaningful", - "mean meaner meanest meanly meanness meannesses", - "median medians medianly medial", - "medium media mediums", - "meet meets met meeting meetings", - "memorandum memoranda memorandums", - "mere merely", - "metal metals metallic", - "might mighty mightily", - "millenium millennia milleniums millennial", - "mine mines mined mining minings miner miners", - "mine my our ours", - "minimum minima minimums minimal", - "minus minuses", - "miscellaneous miscellanea miscellaneously miscellaneousness miscellany", - "molest molests molested molesting molestings molester molesters", - "moment moments", - "monument monuments monumental", - "more most", - "mouse mice mouseless", - "much", - "multiply multiplies multiplier multipliers multiple multiples multiplying multiplyings multiplication multiplications", - "mum mums mummed mumming mummings mummer mummers", - "must musts", - "neither", - "nemeses nemesis", - "neurosis neuroses neurotic neurotics", - "nomen", - "none", - "nos no noes", - "not", - "nothing nothings nothingness", - "now", - "nowadays", - "nucleus nuclei nucleuses nuclear", - "number numbers numbered numbering numberings numberless", - "nutriment nutriments nutrient nutrients nutrition nutritions", - "oasis oases", - "octopus octopi octopuses", - "of", - "off", - "offer offers offered offering offerings offerer offerers offeror offerors", - "often", - "oftentimes", - "ointment ointments", - "omen omens", - "on", - "once", - "only", - "ornament ornaments ornamented ornamenting ornamentings ornamentation ornamenter ornamenters ornamental", - "outdoor outdoors", - "outlay outlays", - "outlie outlies outlay outlied outlain outlying outlier outliers", - "ovum ova", - "ox oxen", - "parentheses parenthesis", - "parliament parliaments parliamentary", - "passerby passer-by passersby passers-by", - "past pasts", - "pay pays paid paying payings payer payers payee payees payment payments", - "per", - "perhaps", - "person persons people", - "phenomenon phenomena phenomenal", - "pi", - "picnic picnics picnicker picnickers picnicked picnicking picnickings", - "pigment pigments pigmented pigmenting pigmentings pigmenter pigmenters pigmentation pigmentations", - "please pleases pleased pleasing pleasings pleaser pleasers pleasure pleasures pleasuring pleasurings pleasant pleasantly pleasureless pleasureful", - "plus pluses plusses", - "polyhedra polyhedron polyhedral", - "priest priests priestly priestlier priestliest priestliness priestless", - "prognosis prognoses", - "prostheses prosthesis", - "prove proves proved proving provings proofs proof prover provers provable", - "psychosis psychoses psychotic psychotics", - "qed", - "quiz quizzes quizzed quizzing quizzings quizzer quizzers", - "raiment", - "rather", - "re", - "real really", - "redo redoes redid redone redoing redoings redoer redoers", - "regiment regiments regimented regimenting regimenter regimenters regimentation regimental", - "rendezvous", - "requiz requizzes requizzed requizzing requizzings requizzer requizzers", - "ride rides rode ridden riding ridings rider riders rideless", - "ring rings rang rung ringing ringings ringer ringers ringless", - "rise rises rose risen rising risings riser risers", - "rose roses", - "rudiment rudiments rudimentary", - "rum rums rummed rumming rummings rummer rummers", - "run runs ran running runnings runner runners", - "sacrament sacraments sacramental", - "same sameness", - "sans", - "saw saws sawed sawn sawing sawings sawyer sawyers", - "say says said saying sayings sayer sayers", - "scarf scarfs scarves scarfless", - "schema schemata schemas", - "sediment sediments sedimentary sedimentation sedimentations", - "see sees saw seen seeing seeings seer seers", - "seek seeks sought seeking seekings seeker seekers", - "segment segments segmented segmenting segmentings segmenter segmenters segmentation segmentations", - "self selves selfless", - "sell sells sold selling sellings seller sellers", - "semen", - "send sends sent sending sendings sender senders", - "sentiment sentiments sentimental", - "series", - "set sets setting settings", - "several severally", - "sew sews sewed sewn sewing sewings sewer sewers", - "sewer sewers sewerless", - "shake shakes shook shaken shaking shakings shaker shakers", - "shall should", - "shaman shamans", - "shave shaves shaved shaven shaving shavings shaver shavers shaveless", - "she her hers herself", - "sheaf sheaves sheafless", - "sheep", - "shelf shelves shelved shelfing shelvings shelver shelvers shelfless", - "shine shines shined shone shining shinings shiner shiners shineless", - "shoe shoes shoed shod shoeing shoeings shoer shoers shoeless", - "shoot shoots shot shooting shootings shooter shooters", - "shot shots", - "show shows showed shown showing showings shower showers", - "shower showers showery showerless", - "shrink shrinks shrank shrunk shrinking shrinkings shrinker shrinkers shrinkable", - "sideways", - "simply simple simpler simplest", - "since", - "sing sings sang sung singing singings singer singers singable", - "sink sinks sank sunk sinking sinkings sinker sinkers sinkable", - "sit sits sat sitting sittings sitter sitters", - "ski skis skied skiing skiings skier skiers skiless skiable", - "sky skies", - "slay slays slew slain slaying slayings slayer slayers", - "sleep sleeps slept sleeping sleepings sleeper sleepers sleepless", - "so", - "some", - "something", - "sometime sometimes", - "soon", - "spa spas", - "speak speaks spoke spoken speaking speakings speaker speakers", - "species specie", - "spectrum spectra spectrums", - "speed speeds sped speeded speeding speedings speeder speeders", - "spend spends spent spending spendings spender spenders spendable", - "spin spins spun spinning spinnings spinner spinners", - "spoke spokes", - "spring springs sprang sprung springing springings springer springers springy springiness", - "staff staffs staves staffed staffing staffings staffer staffers", - "stand stands stood standing standings", - "stasis stases", - "steal steals stole stolen stealing stealings stealer stealers", - "stick sticks stuck sticking stickings sticker stickers", - "stigma stigmata stigmas stigmatize stigmatizes stigmatized stigmatizing", - "stimulus stimuli", - "sting stings stung stinging stingings stinger stingers", - "stink stinks stank stunk stinking stinkings stinker stinkers", - "stomach stomachs", - "stratum strata stratums", - "stride strides strode stridden striding stridings strider striders", - "string strings strung stringing stringings stringer stringers stringless", - "strive strives strove striven striving strivings striver strivers", - "strum strums strummed strumming strummings strummer strummers strummable", - "such", - "suffer suffers suffered suffering sufferings sufferer sufferers sufferable", - "suggest suggests suggested suggesting suggestings suggester suggesters suggestor suggestors suggestive suggestion suggestions suggestible suggestable", - "sum sums summed summing summings summer summers", - "summer summers summered summering summerings", - "supplement supplements supplemented supplementing supplementings supplementation supplementer supplementers supplementary supplemental", - "supply supplies supplied supplying supplyings supplier suppliers", - "swear swears swore sworn swearing swearings swearer swearers", - "sweep sweeps swept sweeping sweepings sweeper sweepers", - "swell swells swelled swollen swelling swellings", - "swim swims swam swum swimming swimmings swimmer swimmers swimable", - "swine", - "swing swings swung swinging swingings swinger swingers", - "syllabus syllabi syllabuses", - "symposium symposia symposiums", - "synapse synapses", - "synapsis synapses", - "synopsis synopses", - "synthesis syntheses", - "tableau tableaux tableaus", - "take takes took taken taking takings taker takers takable", - "teach teaches taught teaching teachings teacher teachers teachable", - "tear tears tore torn tearing tearings tearer tearers tearable", - "tegument teguments", - "tell tells told telling tellings teller tellers tellable", - "temperament temperaments temperamental temperamentally", - "tenement tenements", - "the", - "there theres", - "theses thesis", - "they them their theirs themselves", - "thief thieves thieving thievings", - "think thinks thought thinking thinker thinkers thinkable", - "this that these those", - "thought thoughts thougtful thoughtless", - "throw throws threw thrown throwing throwings thrower throwers throwable", - "tic tics", - "tie ties tied tying tyings tier tiers tieable tieless", - "tier tiers tiered tiering tierings tierer tierers", - "to", - "toe toes toed toeing toeings toer toers toeless", - "together togetherness", - "too", - "tooth teeth toothless", - "topaz topazes", - "torment torments tormented tormenting tormentings tormenter tormenters tormentable", - "toward towards", - "tread treads trod trodden treading treadings treader treaders", - "tread treads treadless retread retreads", - "true truly trueness", - "two twos", - "u", - "under", - "underlay underlays underlaid underlaying underlayings underlayer underlayers", - "underlie underlies underlay underlain underlying underlier underliers", - "undo undoes undid undone undoing undoings undoer undoers undoable", - "unrest unrestful", - "until", - "unto", - "up", - "upon", - "upstairs", - "use uses user users used using useful useless", - "various variously", - "vehement vehemently vehemence", - "versus", - "very", - "visit visits visited visiting visitings visitor visitors", - "vortex vortexes vortices", - "wake wakes woke waked woken waking wakings waker wakers wakeful wakefulness wakefulnesses wakeable", - "wear wears wore worn wearing wearings wearer wearers wearable", - "weather weathers weathered weathering weatherly", - "weave weaves wove woven weaving weavings weaver weavers weaveable", - "weep weeps wept weeping weepings weeper weepers", - "wharf wharfs wharves", - "where wheres", - "whereas whereases", - "whether whethers", - "while whiles whilst whiled whiling", - "whiz whizzes whizzed whizzing whizzings whizzer whizzers", - "who whom whos whose whoses", - "why whys", - "wife wives wifeless", - "will wills willed willing willings willful", - "will would", - "win wins won winning winnings winner winners winnable", - "wind winds wound winding windings winder winders windable", - "wind winds windy windless", - "with", - "within", - "without", - "wolf wolves", - "woman women womanless womanly", - "wound wounds wounded wounding woundings", - "write writes wrote written writing writings writer writers writeable", - "yeses yes", - "yet yets", - "you your yours yourself" - ] - -_exdict = {} -for exlist in exceptions: - for ex in exlist.split(" "): - _exdict[ex] = exlist - -# Programmatic rules - -vowels = "aeiouy" -cons = "bcdfghjklmnpqrstvwxyz" - -rules = ( - # Words ending in S - - # (e.g., happiness, business) - (r"[%s].*[%s](iness)" % (vowels, cons), "y,ies,ier,iers,iest,ied,ying,yings,ily,inesses,iment,iments,iless,iful"), - # (e.g., baseless, shoeless) - (r"[%s].*(eless)" % vowels, "e,es,er,ers,est,ed,ing,ings,eing,eings,ely,eness,enesses,ement,ements,eness,enesses,eful"), - # (e.g., gutless, hatless, spotless) - (r"[%s][%s][bdgklmnprt]?(less)" % (cons, vowels), ",s,&er,&ers,&est,&ed,&ing,&ings,ly,ness,nesses,ment,ments,ful"), - # (e.g., thoughtless, worthless) - (r"[%s].*?(less)" % vowels, ",s,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,ful"), - # (e.g., baseness, toeness) - (r"[%s].*(eness)" % vowels, "e,es,er,ers,est,ed,ing,ings,eing,eings,ely,enesses,ement,ements,eless,eful"), - # (e.g., bluntness, grayness) - (r"[%s].*(ness)" % vowels, ",s,er,ers,est,ed,ing,ings,ly,nesses,ment,ments,less,ful"), - # (e.g., albatross, kiss) - (r"[%s]ss" % vowels, "es,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), - # (e.g., joyous, fractious, gaseous) - (r"[%s].*(ous)" % vowels, "ly,ness"), - # (e.g., tries, unties, jollies, beauties) - (r"(ies)", "y,ie,yer,yers,ier,iers,iest,ied,ying,yings,yness,iness,ieness,ynesses,inesses,ienesses,iment,iement,iments,iements,yless,iless,ieless,yful,iful,ieful"), - # (e.g., crisis, kinesis) - (r"[%s].*(sis)" % vowels, "ses,sises,sisness,sisment,sisments,sisless,sisful"), - # (e.g., bronchitis, bursitis) - (r"[%s].*(is)" % vowels, "es,ness,ment,ments,less,ful"), - (r"[%s].*[cs]h(es)" % vowels, ",e,er,ers,est,ed,ing,ings,ly,ely,ness,eness,nesses,enesses,ment,ement,ments,ements,less,eless,ful,eful"), - # (e.g., tokenizes) // adds British variations - (r"[%s].*[%s](izes)" % (vowels, cons), "ize,izes,izer,izers,ized,izing,izings,ization,izations,ise,iser,isers,ised,ising,isings,isation,isations"), - # (e.g., tokenises) // British variant // ~expertise - (r"[%s].*[%s](ises)" % (vowels, cons), "ize,izes,izer,izers,ized,izing,izings,ization,izations,ise,iser,isers,ised,ising,isings,isation,isations"), - # (e.g., aches, arches) - (r"[%s].*[jsxz](es)" % vowels, ",e,er,ers,est,ed,ing,ings,ly,ely,ness,eness,nesses,enesses,ment,ement,ments,ements,less,eless,ful,eful"), - # (e.g., judges, abridges) - (r"[%s].*dg(es)" % vowels, "e,er,ers,est,ed,ing,ings,ely,eness,enesses,ment,ments,ement,ements,eless,eful"), - # (e.g., trees, races, likes, agrees) covers all other -es words - (r"e(s)", ",*"), - # (e.g., segments, bisegments, cosegments) - (r"segment(s)", ",*"), - # (e.g., pigments, depigments, repigments) - (r"pigment(s)", ",*"), - # (e.g., judgments, abridgments) - (r"[%s].*dg(ments)" % vowels, "ment,*ments"), - # (e.g., merriments, embodiments) -iment in turn will generate y and *y (redo y) - (r"[%s].*[%s]iment(s)" % (vowels, cons), ",*"), - # (e.g., atonements, entrapments) - (r"[%s].*ment(s)" % vowels, ",*"), - # (e.g., viewers, meters, traders, transfers) - (r"[%s].*er(s)" % vowels, ",*"), - # (e.g., unflags) polysyllables - (r"[%s].*[%s][%s][bdglmnprt](s)" % (vowels, cons, vowels), ",*"), - # (e.g., frogs) monosyllables - (r"[%s][%s][bdglmnprt](s)" % (vowels, cons), ",*"), - # (e.g., killings, muggings) - (r"[%s].*ing(s)" % vowels, ",*"), - # (e.g., hulls, tolls) - (r"[%s].*ll(s)" % vowels, ",*"), - # e.g., boas, polkas, spas) don't generate latin endings - (r"a(s)", ",er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), - # (e.g., beads, toads) - (r"[%s].*[%s].*(s)" % (vowels, cons), ",*"), - # (e.g., boas, zoos) - (r"[%s].*[%s](s)" % (cons, vowels), ",er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), - # (e.g., ss, sss, ssss) no vowel (vowel case is already handled above) - (r"ss()", ""), - # (e.g., cds, lcds, m-16s) no vowel (can be a plural noun, but not verb) - (r"[%s].*[%s1234567890](s)" % (cons, cons), ""), - - # Words ending in E - - # (e.g., apple, so it doesn't include apply) - (r"appl(e)", "es,er,ers,est,ed,ing,ings,ely,eness,enesses,ement,ements,eless,eful"), - # (e.g., supple, so it doesn't include supply) - (r"suppl(e)", "es,er,ers,est,ed,ing,ings,ely,eness,enesses,ement,ements,eless,eful"), - # (e.g., able, abominable, fungible, table, enable, idle, subtle) - (r"[%s].*[%s]l(e)" % (vowels, cons), "es,er,ers,est,ed,ing,ings,y,ely,eness,enesses,ement,ements,eless,eful"), - # (e.g., bookie, magpie, vie) - (r"(ie)", "ies,ier,iers,iest,ied,ying,yings,iely,ieness,ienesses,iement,iements,ieless,ieful"), - # (e.g., dye, redye, redeye) - (r"ye()", "s,r,rs,st,d,ing,ings,ly,ness,nesses,ment,ments,less,ful"), - # (e.g., judge, abridge) - (r"[%s].*dg(e)" % vowels, "es,er,ers,est,ed,ing,ings,ely,eness,enesses,ment,ments,less,ful,ement,ements,eless,eful"), - # (e.g., true, due, imbue) - (r"u(e)", "es,er,ers,est,ed,ing,ings,eing,eings,ly,ely,eness,enesses,ment,ments,less,ful,ement,ements,eless,eful"), - # (e.g., tokenize) // adds British variations - (r"[%s].*[%s](ize)" % (vowels, cons), "izes,izer,izers,ized,izing,izings,ization,izations,ise,ises,iser,isers,ised,ising,isings,isation,isations"), - # (e.g., tokenise) // British variant // ~expertise - (r"[%s].*[%s](ise)" % (vowels, cons), "ize,izes,izer,izers,ized,izing,izings,ization,izations,ises,iser,isers,ised,ising,isings,isation,isations"), - # (e.g., tree, agree, rage, horse, hoarse) - (r"[%s].*[%s](e)" % (vowels, cons), "es,er,ers,est,ed,ing,ings,eing,eings,ely,eness,enesses,ement,ements,eless,eful"), - - # Words ending in -ED - - # (e.g., agreed, freed, decreed, treed) - (r"ree(d)", "ds,der,ders,ded,ding,dings,dly,dness,dnesses,dment,dments,dless,dful,,*"), - # (e.g., feed, seed, Xweed) - (r"ee(d)", "ds,der,ders,ded,ding,dings,dly,dness,dnesses,dment,dments,dless,dful"), - # (e.g., tried) - (r"[%s](ied)" % cons, "y,ie,ies,ier,iers,iest,ying,yings,ily,yly,iness,yness,inesses,ynesses,iment,iments,iless,iful,yment,yments,yless,yful"), - # (e.g., controlled, fulfilled, rebelled) - (r"[%s].*[%s].*l(led)" % (vowels, cons), ",s,er,ers,est,ing,ings,ly,ness,nesses,ment,ments,less,ful,&,&s,&er,&ers,&est,&ing,&ings,&y,&ness,&nesses,&ment,&ments,&ful"), - # (e.g., pulled, filled, fulled) - (r"[%s].*l(led)" % vowels, "&,&s,&er,&ers,&est,&ing,&ings,&y,&ness,&nesses,&ment,&ments,&ful"), - # (e.g., hissed, grossed) - (r"[%s].*s(sed)" % vowels, "&,&es,&er,&ers,&est,&ing,&ings,&ly,&ness,&nesses,&ment,&ments,&less,&ful"), - # (e.g., hugged, trekked) - (r"[%s][%s](?P[bdgklmnprt])((?P=ed1)ed)", ",s,&er,&ers,&est,&ing,&ings,ly,ness,nesses,ment,ments,less,ful"), - # (e.g., tokenize) // adds British variations - (r"[%s].*[%s](ized)" % (vowels, cons), "izes,izer,izers,ize,izing,izings,ization,izations,ise,ises,iser,isers,ised,ising,isings,isation,isations"), - # (e.g., tokenise) // British variant // ~expertise - (r"[%s].*[%s](ized)" % (vowels, cons), "ize,izes,izer,izers,ized,izing,izings,ization,izations,ises,iser,isers,ise,ising,isings,isation,isations"), - # (e.g., spoiled, tooled, tracked, roasted, atoned, abridged) - (r"[%s].*(ed)" % vowels, ",e,s,es,er,ers,est,ing,ings,ly,ely,ness,eness,nesses,enesses,ment,ement,ments,ements,less,eless,ful,eful"), - # (e.g., bed, sled) words with a single e as the only vowel - (r"ed()", "s,&er,&ers,&est,&ed,&ing,&ings,ly,ness,nesses,ment,ments,less,ful"), - - # Words ending in -ER - - # (e.g., altimeter, ammeter, odometer, perimeter) - (r"meter()", "s,er,ers,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), - # (e.g., agreer, beer, budgeteer, engineer, freer) - (r"eer()", "eers,eered,eering,eerings,eerly,eerness,eernesses,eerment,eerments,eerless,eerful,ee,ees,eest,eed,eeing,eeings,eely,eeness,eenesses,eement,eements,eeless,eeful,eerer,eerers,eerest"), - # (e.g., acidifier, saltier) - (r"[%s].*[%s](ier)" % (vowels, cons), "y,ie,ies,iest,ied,ying,yings,ily,yly,iness,yness,inesses,ynesses,yment,yments,yless,yful,iment,iments,iless,iful,iers,iered,iering,ierings,ierly,ierness,iernesses,ierment,ierments,ierless,ierful,ierer,ierers,ierest"), - # (e.g., puller, filler, fuller) - (r"[%s].*l(ler)" % vowels, "&,&s,&est,&ed,&ing,&ings,ly,lely,&ness,&nesses,&ment,&ments,&ful,&ers,&ered,&ering,&erings,&erly,&erness,&ernesses,&erments,&erless,&erful"), - # (e.g., hisser, grosser) - (r"[%s].*s(ser)" % vowels, "&,&es,&est,&ed,&ing,&ings,&ly,&ness,&nesses,&ment,&ments,&less,&ful,&ers,&ered,&ering,&erings,&erly,&erness,&ernesses,&erment,&erments,&erless,&erful"), - # (e.g., bigger, trekker, hitter) - (r"[%s][%s](?P[bdgkmnprt])((?P=er1)er)" % (cons, vowels), "s,&est,&ed,&ing,&ings,ly,ness,nesses,ment,ments,less,ful,&ers,&ered,&ering,&erings,&erly,&erness,&ernesses,&erments,&erless,&erful"), - # (e.g., tokenize) // adds British variations - (r"[%s].*[%s](izer)" % (vowels, cons), "izes,ize,izers,ized,izing,izings,ization,izations,ise,ises,iser,isers,ised,ising,isings,isation,isations"), - # (e.g., tokenise) // British variant // ~expertise - (r"[%s].*[%s](iser)" % (vowels, cons), "ize,izes,izer,izers,ized,izing,izings,ization,izations,ises,ise,isers,ised,ising,isings,isation,isations"), - #(e.g., actioner, atoner, icer, trader, accruer, churchgoer, prefer) - (r"[%s].*(er)" % vowels, ",e,s,es,est,ed,ing,ings,ly,ely,ness,eness,nesses,enesses,ment,ments,less,ful,ement,ements,eless,eful,ers,ered,erred,ering,erring,erings,errings,erly,erness,ernesses,erment,erments,erless,erful,erer,erers,erest,errer,errers,errest"), - - # Words ending in -EST - - # (e.g., sliest, happiest, wittiest) - (r"[%s](iest)" % cons, "y,ies,ier,iers,ied,ying,yings,ily,yly,iness,yness,inesses,ynesses,iment,iments,iless,iful"), - # (e.g., fullest) - (r"[%s].*l(lest)" % vowels, "&,&s,&er,&ers,&ed,&ing,&ings,ly,&ness,&nesses,&ment,&ments,&ful"), - # (e.g., grossest) - (r"[%s].*s(sest)" % vowels, "&,&es,&er,&ers,&ed,&ing,&ings,&ly,&ness,&nesses,&ment,&ments,&less,&ful"), - # (e.g., biggest) - (r"[%s][%s](?P[bdglmnprst])((?P=est1)est)" % (cons, vowels), ",s,&er,&ers,&ed,&ing,&ings,ly,ness,nesses,ment,ments,less,ful"), - # (e.g., basest, archest, rashest) - (r"[%s].*([cs]h|[jsxz])(est)" % vowels, "e,es,er,ers,ed,ing,ings,ly,ely,ness,eness,nesses,enesses,ment,ments,less,ful,ement,ements,eless,eful,ests,ester,esters,ested,esting,estings,estly,estness,estnesses,estment,estments,estless,estful"), - # (e.g., severest, Xinterest, merest) - (r"er(est)", "e,es,er,ers,ed,eing,eings,ely,eness,enesses,ement,ements,eless,eful,ests,ester,esters,ested,esting,estings,estly,estness,estnesses,estment,estments,estless,estful"), - # (e.g., slickest, coolest, ablest, amplest, protest, quest) - (r"[%s].*(est)" % vowels, ",e,s,es,er,ers,ed,ing,ings,ly,ely,ness,eness,nesses,enesses,ment,ments,less,ful,ement,ements,eless,eful,ests,ester,esters,ested,esting,estings,estly,estness,estnesses,estment,estments,estless,estful"), - # (e.g., rest, test) - (r"est", "s,er,ers,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), - - # Words ending in -FUL - - # (e.g., beautiful, plentiful) - (r"[%s].*[%s](iful)" % (vowels, cons), "ifully,ifulness,*y"), - # (e.g., hopeful, sorrowful) - (r"[%s].*(ful)" % vowels, "fully,fulness,,*"), - - # Words ending in -ICAL - - (r"[%s].*(ical)" % vowels, "ic,ics,ically"), - - # Words ending in -IC - - (r"[%s].*(ic)" % vowels, "ics,ical,ically"), - - # Words ending in -ING - - # (e.g., dying, crying, supplying) - (r"[%s](ying)" % cons, "yings,ie,y,ies,ier,iers,iest,ied,iely,yly,ieness,yness,ienesses,ynesses,iment,iments,iless,iful"), - # (e.g., pulling, filling, fulling) - (r"[%s].*l(ling)" % vowels, ",*,&,&s,&er,&ers,&est,&ed,&ings,&ness,&nesses,&ment,&ments,&ful"), - # (e.g., hissing, grossing, processing) - (r"[%s].*s(sing)" % vowels, "&,&s,&er,&ers,&est,&ed,&ings,&ly,&ness,&nesses,&ment,&ments,&less,&ful"), - # (e.g., hugging, trekking) - (r"[%s][%s](?P[bdgklmnprt])((?P=ing1)ing)" % (cons, vowels), ",s,&er,&ers,&est,&ed,&ings,ly,ness,nesses,ment,ments,less,ful"), - # (e.g., freeing, agreeing) - (r"eeing()", "ee,ees,eer,eers,eest,eed,eeings,eely,eeness,eenesses,eement,eements,eeless,eeful"), - # (e.g., ageing, aweing) - (r"[%s].*(eing)" % vowels, "e,es,er,ers,est,ed,eings,ely,eness,enesses,ement,ements,eless,eful"), - # (e.g., toying, playing) - (r"[%s].*y(ing)" % vowels, ",s,er,ers,est,ed,ings,ly,ingly,ness,nesses,ment,ments,less,ful"), - # (e.g., editing, crediting, expediting, siting, exciting) - (r"[%s].*[%s][eio]t(ing)" % (vowels, cons), ",*,*e,ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), - # (e.g., robing, siding, doling, translating, flaking) - (r"[%s][%s][bdgklmt](ing)" % (cons, vowels), "*e,ings,inger,ingers,ingest,inged,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), - # (e.g., tokenize) // adds British variations - (r"[%s].*[%s](izing)" % (vowels, cons), "izes,izer,izers,ized,ize,izings,ization,izations,ise,ises,iser,isers,ised,ising,isings,isation,isations"), - # (e.g., tokenise) // British variant // ~expertise - (r"[%s].*[%s](ising)" % (vowels, cons), "ize,izes,izer,izers,ized,izing,izings,ization,izations,ises,iser,isers,ised,ise,isings,isation,isations"), - # (e.g., icing, aging, achieving, amazing, housing) - (r"[%s][cgsvz](ing)" % vowels, "*e,ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), - # (e.g., dancing, troubling, arguing, bluing, carving) - (r"[%s][clsuv](ing)" % cons, "*e,ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), - # (e.g., charging, bulging) - (r"[%s].*[lr]g(ing)" % vowels, "*e,ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), - # (e.g., farming, harping, interesting, bedspring, redwing) - (r"[%s].*[%s][bdfjkmnpqrtwxz](ing)" % (vowels, cons), ",*,ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), - # (e.g., spoiling, reviling, autoing, egging, hanging, hingeing) - (r"[%s].*(ing)" % vowels, ",*,*e,ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), - # (e.g., wing, thing) monosyllables - (r"(ing)", "ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), - - # -LEAF rules omitted - - # Words ending in -MAN - # (e.g., policewomen, hatchetmen, dolmen) - (r"(man)", "man,mens,mener,meners,menest,mened,mening,menings,menly,menness,mennesses,menless,menful"), - - # Words ending in -MENT - - # (e.g., segment, bisegment, cosegment, pigment, depigment, repigment) - (r"segment|pigment", "s,ed,ing,ings,er,ers,ly,ness,nesses,less,ful"), - # (e.g., judgment, abridgment) - (r"[%s].*dg(ment)" % vowels, "*e"), - # (e.g., merriment, embodiment) - (r"[%s].*[%s](iment)" % (vowels, cons), "*y"), - # (e.g., atonement, entrapment) - (r"[%s].*[%s](ment)" % (vowels, cons), ",*"), - - # Words ending in -O - - # (e.g., taboo, rodeo) - (r"[%s]o()" % vowels, "s,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), - # (e.g., tomato, bonito) - (r"[%s].*o()" % vowels, "s,es,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), - - # Words ending in -UM - - # (e.g., datum, quantum, tedium, strum, [oil]drum, vacuum) - (r"[%s].*(um)" % vowels, "a,ums,umer,ummer,umers,ummers,umed,ummed,uming,umming,umings,ummings,umness,umments,umless,umful"), - - # Words ending in -Y - - # (e.g., ably, horribly, wobbly) - (r"[%s].*b(ly)" % vowels, "le,les,ler,lers,lest,led,ling,lings,leness,lenesses,lement,lements,leless,leful"), - # (e.g., happily, dizzily) - (r"[%s].*[%s](ily)" % (vowels, cons), "y,ies,ier,iers,iest,ied,ying,yings,yness,iness,ynesses,inesses,iment,iments,iless,iful"), - # (e.g., peaceful+ly) - (r"[%s].*ful(ly)" % vowels, ",*"), - # (e.g., fully, folly, coolly, fatally, dally) - (r"[%s].*l(ly)" % vowels, ",*,lies,lier,liers,liest,lied,lying,lyings,liness,linesses,liment,liments,liless,liful,*l"), - # (e.g., monopoly, Xcephaly, holy) - (r"[%s](ly)" % vowels, "lies,lier,liers,liest,lied,lying,lyings,liness,linesses,liment,liments,liless,liful"), - # (e.g., frequently, comely, deeply, apply, badly) - (r"[%s].*(ly)" % vowels, ",*,lies,lier,liers,liest,lied,lying,lyings,liness,linesses,lyless,lyful"), - # (e.g., happy, ply, spy, cry) - (r"[%s](y)" % cons, "ies,ier,iers,iest,ied,ying,yings,ily,yness,iness,ynesses,inesses,iment,iments,iless,iful,yment,yments,yless,yful"), - # (e.g., betray, gay, stay) - (r"[%s]y()" % vowels, "s,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), - - # Root rules - - # (e.g., fix, arch, rash) - (r"[%s].*(ch|sh|[jxz])()" % vowels, "es,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), - # (e.g., unflag, open, besot) - (r"[%s].*[%s][%s][bdglmnprt]()" % (vowels, cons, vowels), "s,er,ers,est,ed,ing,ings,&er,&ers,&est,&ed,&ing,&ings,ly,ness,nesses,ment,ments,less,ful"), - # (e.g., bed, cop) - (r"[%s][%s][bdglmnprt]()" % (cons, vowels), "s,&er,&ers,&est,&ed,&ing,&ings,ly,ness,nesses,ment,ments,less,ful"), - # (e.g., schemata, automata) - (r"[%s].*[%s][%s]ma(ta)" % (vowels, cons, vowels), ",s,tas,tum,tums,ton,tons,tic,tical"), - # (e.g., chordata, data, errata, sonata, toccata) - (r"[%s].*t(a)" % vowels, "as,ae,um,ums,on,ons,ic,ical"), - # (e.g., polka, spa, schema, ova, polyhedra) - (r"[%s].*[%s](a)" % (vowels, cons), "as,aed,aing,ae,ata,um,ums,on,ons,al,atic,atical"), - # (e.g., full) - (r"[%s].*ll()" % vowels, "s,er,ers,est,ed,ing,ings,y,ness,nesses,ment,ments,-less,ful"), - # (e.g., spoon, rhythm) - (r"[%s].*()", "s,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), - ) - -# There are a limited number of named groups available in a single -# regular expression, so we'll partition the list of rules into -# smaller chunks. - -_partition_size = 20 -_partitions = [] -for p in xrange(0, len(rules) // _partition_size + 1): - start = p * _partition_size - end = (p+1) * _partition_size - pattern = "|".join("(?P<_g%s>%s)$" % (i, r[0]) for i,r in enumerate(rules[start:end])) - _partitions.append(re.compile(pattern)) - -#print "\n".join(p.pattern for p in _partitions) - -def variations(word): - """Given an English word, returns a collection of morphological variations - on the word by algorithmically adding and removing suffixes. The variation - list may contain non-words (e.g. render -> renderment). - - >>> variations("pull") - set(['pull', 'pullings', 'pullnesses', 'pullful', 'pullment', 'puller', ... ]) - """ - - if word in _exdict: - return _exdict[word].split(" ") - - for i, p in enumerate(_partitions): - match = p.search(word) - if match: - # Get the named group that matched - num = int([k for k, v in match.groupdict().iteritems() - if v is not None and k.startswith("_g")][0][2:]) - # Get the positional groups for the matched group (all other - # positional groups are None) - groups = [g for g in match.groups() if g is not None] - ending = groups[-1] - root = word[:0-len(ending)] if ending else word - - out = set((word, )) - results = rules[i * _partition_size + num][1] - for result in results.split(","): - if result.startswith("&"): - out.add(root + root[-1] + result[1:]) - elif result.startswith("*"): - out.union(variations(root + result[1:])) - else: - out.add(root + result) - return set(out) - - return [word] - - -if __name__ == '__main__': - import time - t = time.clock() - s = variations("rendering") - print time.clock() - t - print len(s) - +""" +Contains the variations() function for expanding an English word into multiple variations +by programmatically adding and removing suffixes. + +Translated to Python from the ``com.sun.labs.minion.lexmorph.LiteMorph_en`` class of +Sun's `Minion search engine `_. +""" + +import re + +# Rule exceptions + +exceptions = [ + "a", + "abandoner abandon abandons abandoned abandoning abandonings abandoners", + "abdomen abdomens", + "about", + "above", + "acid acids acidic acidity acidities", + "across", + "act acts acted acting actor actors", + "ad ads", + "add adds added adding addings addition additions adder adders", + "advertise advertises advertised advertising advertiser advertisers advertisement advertisements advertisings", + "after", + "again", + "against", + "ago", + "all", + "almost", + "along", + "already", + "also", + "although", + "alumna alumnae alumnus alumni", + "always", + "amen amens", + "amidships", + "amid amidst", + "among amongst", + "an", + "analysis analyses", + "and", + "another other others", + "antenna antennas antennae", + "antitheses antithesis", + "any", + "anyone anybody", + "anything", + "appendix appendixes appendices", + "apropos", + "aquarium aquariums aquaria", + "argument arguments argue argues argued arguing arguings arguer arguers", + "arise arises arose arisen ariser arisers arising arisings", + "around", + "as", + "asbestos", + "at", + "atlas atlases", + "auger augers augered augering augerings augerer augerers", + "augment augments augmented augmenting augmentings augmentation augmentations augmenter augmenters", + "automata automaton automatons", + "automation automating automate automates automated automatic", + "avoirdupois", + "awake awakes awoke awaked awoken awaker awakers awaking awakings awakening awakenings", + "away", + "awful awfully awfulness", + "axis axes axises", + "bacillus bacilli", + "bacterium bacteria", + "bad worse worst badly badness", + "bas", + "bases basis", + "bases base based basing basings basely baseness basenesses basement basements baseless basic basics", + "be am are is was were been being", + "bear bears bore borne bearing bearings bearer bearers", + "beat beats beaten beating beatings beater beaters", + "because", + "become becomes became becoming", + "beef beefs beeves beefed beefing", + "beer beers", + "before", + "begin begins began begun beginning beginnings beginner beginners", + "behalf behalves", + "being beings", + "bend bends bent bending bendings bender benders", + "bereave bereaves bereaved bereft bereaving bereavings bereavement bereavements", + "beside besides", + "best bests bested besting", + "bet bets betting bettor bettors", + "betimes", + "between", + "beyond", + "bid bids bade bidden bidding biddings bidder bidders", + "bier biers", + "bind binds bound binding bindings binder binders", + "bit bits", + "bite bites bit bitten biting bitings biter biters", + "blackfoot blackfeet", + "bleed bleeds bled bleeding bleedings bleeder bleeders", + "blow blows blew blown blowing blowings blower blowers", + "bookshelf bookshelves", + "both", + "bound bounds bounded bounding boundings bounder bounders boundless", + "bourgeois bourgeoisie", + "bra bras", + "brahman brahmans", + "break breaks broke broken breaking breakings breaker breakers", + "breed breeds bred breeding breedings breeder breeders", + "bring brings brought bringing bringings bringer bringers", + "build builds built building buildings builder builders", + "bus buses bused bussed busing bussing busings bussings buser busers busser bussers", + "buss busses bussed bussing bussings busser bussers", + "but", + "buy buys bought buying buyings buyer buyers", + "by", + "calf calves calved calving calvings calver calvers", + "can cans canned canning cannings canner canners", + "can could cannot", + "canoes canoe canoed canoeing canoeings canoer canoers", + "catch catches caught catching catchings catcher catchers", + "cement cements cemented cementing cementings cementer cementers", + "cent cents", + "center centers centered centering centerings centerless", + "child children childless childish childishly", + "choose chooses chose chosen choosing choosings chooser choosers", + "cling clings clung clinging clingings clinger clingers", + "colloquium colloquia colloquiums", + "come comes came coming comings comer comers", + "comment comments commented commenting commentings commenter commenters", + "compendium compendia compendiums", + "complement complements complemented complementing complementings complementer complementers complementary", + "compliment compliments complimented complimenting complimentings complimenter complimenters complimentary", + "concerto concertos concerti", + "condiment condiments", + "corps", + "cortex cortices cortexes cortical", + "couscous", + "creep creeps crept creeping creepings creeper creepers creepy", + "crisis crises", + "criterion criteria criterial", + "cryptanalysis cryptanalyses", + "curriculum curricula curriculums curricular", + "datum data", + "day days daily", + "deal deals dealt dealing dealings dealer dealers", + "decrement decrements decremented decrementing decrementings decrementer decrementers decremental", + "deer deers", + "demented dementia", + "desideratum desiderata", + "diagnosis diagnoses diagnose diagnosed diagnosing diagnostic", + "dialysis dialyses", + "dice dices diced dicing dicings dicer dicers", + "die dice", + "die dies died dying dyings", + "dig digs dug digging diggings digger diggers", + "dive dives diver divers dove dived diving divings", + "divest divests divester divesters divested divesting divestings divestment divestments", + "do does did done doing doings doer doers", + "document documents documented documenting documentings documenter documenters documentation documentations documentary", + "doe does", + "dove doves", + "downstairs", + "dozen", + "draw draws drew drawn drawing drawings drawer drawers", + "drink drinks drank drunk drinking drinkings drinker drinkers", + "drive drives drove driven driving drivings driver drivers driverless", + "due dues duly", + "during", + "e", + "each", + "eager eagerer eagerest eagerly eagerness eagernesses", + "early earlier earliest", + "easement easements", + "eat eats ate eaten eating eatings eater eaters", + "effluvium effluvia", + "either", + "element elements elementary", + "elf elves elfen", + "ellipse ellipses elliptic elliptical elliptically", + "ellipsis ellipses elliptic elliptical elliptically", + "else", + "embolus emboli embolic embolism", + "emolument emoluments", + "emphasis emphases", + "employ employs employed employing employer employers employee employees employment employments employable", + "enough", + "equilibrium equilibria equilibriums", + "erratum errata", + "ever", + "every", + "everything", + "exotic exotically exoticness exotica", + "experiment experiments experimented experimenting experimentings experimenter experimenters experimentation experimental", + "extra extras", + "fall falls fell fallen falling fallings faller fallers", + "far farther farthest", + "fee fees feeless", + "feed feeds fed feeding feedings feeder feeders", + "feel feels felt feeling feelings feeler feelers", + "ferment ferments fermented fermenting fermentings fermentation fermentations fermenter fermenters", + "few fewer fewest", + "fight fights fought fighting fightings fighter fighters", + "figment figments", + "filament filaments", + "find finds found finding findings finder finders", + "firmament firmaments", + "flee flees fled fleeing fleeings", + "fling flings flung flinging flingings flinger flingers", + "floe floes", + "fly flies flew flown flying flyings flier fliers flyer flyers", + "focus foci focuses focused focusing focusses focussed focussing focuser focal", + "foment foments fomented fomenting fomentings fomenter fomenters", + "foot feet", + "foot foots footed footing footer footers", + "footing footings footer footers", + "for", + "forbid forbids forbade forbidden forbidding forbiddings forbidder forbidders", + "foresee foresaw foreseen foreseeing foreseeings foreseer foreseers", + "forest forests forester foresting forestation forestations", + "forget forgets forgot forgotten forgetting forgettings forgetter forgetters forgetful", + "forsake forsakes forsook forsaken forsaking forsakings forsaker forsakers", + "found founds founded founding foundings founder founders", + "fragment fragments fragmented fragmenting fragmentings fragmentation fragmentations fragmenter fragmenters", + "free frees freer freest freed freeing freely freeness freenesses", + "freeze freezes froze frozen freezing freezings freezer freezers", + "from", + "full fully fuller fullest", + "fuller fullers full fulls fulled fulling fullings", + "fungus fungi funguses fungal", + "gallows", + "ganglion ganglia ganglions ganglionic", + "garment garments", + "gas gasses gassed gassing gassings gasser gassers", + "gas gases gasses gaseous gasless", + "gel gels gelled gelling gellings geller gellers", + "german germans germanic germany German Germans Germanic Germany", + "get gets got gotten getting gettings getter getters", + "give gives gave given giving givings giver givers", + "gladiolus gladioli gladioluses gladiola gladiolas gladiolae", + "glans glandes", + "gluiness gluey glue glues glued gluing gluings gluer gluers", + "go goes went gone going goings goer goers", + "godchild godchildren", + "good better best goodly goodness goodnesses", + "goods", + "goose geese", + "goose gooses goosed goosing goosings gooser goosers", + "grandchild grandchildren", + "grind grinds ground grinding grindings grinder grinders", + "ground grounds grounded grounding groundings grounder grounders groundless", + "grow grows grew grown growing growings grower growers growth", + "gum gums gummed gumming gummings gummer gummers", + "half halves", + "halve halves halved halving halvings halver halvers", + "hang hangs hung hanged hanging hangings hanger hangers", + "have has had having havings haver havers", + "he him his himself", + "hear hears heard hearing hearings hearer hearers", + "here", + "hide hides hid hidden hiding hidings hider hiders", + "hippopotamus hippopotami hippopotamuses", + "hold holds held holding holdings holder holders", + "honorarium honoraria honorariums", + "hoof hoofs hooves hoofed hoofing hoofer hoofers", + "how", + "hum hums hummed humming hummings hummer hummers", + "hymen hymens hymenal", + "hypotheses hypothesis hypothesize hypothesizes hypothesized hypothesizer hypothesizing hypothetical hypothetically", + "i", + "if iffy", + "impediment impediments", + "implement implements implemented implementing implementings implementation implementations implementer implementers", + "imply implies implied implying implyings implier impliers", + "in inner", + "inclement", + "increment increments incremented incrementing incrementings incrementer incrementers incremental incrementally", + "index indexes indexed indexing indexings indexer indexers", + "index indexes indices indexical indexicals", + "indoor indoors", + "instrument instruments instrumented instrumenting instrumentings instrumenter instrumenters instrumentation instrumentations instrumental", + "integument integumentary", + "into", + "it its itself", + "java", + "july julys", + "keep keeps kept keeping keepings keeper keepers", + "knife knifes knifed knifing knifings knifer knifers", + "knife knives", + "know knows knew known knowing knowings knower knowers knowledge", + "lament laments lamented lamenting lamentings lamentation lamentations lamenter lamenters lamentable lamentably", + "larva larvae larvas larval", + "late later latest lately lateness", + "latter latterly", + "lay lays laid laying layer layers", + "layer layers layered layering layerings", + "lead leads led leading leadings leader leaders leaderless", + "leaf leafs leafed leafing leafings leafer leafers", + "leaf leaves leafless", + "leave leaves left leaving leavings leaver leavers", + "lend lends lent lending lendings lender lenders", + "less lesser least", + "let lets letting lettings", + "lie lies lay lain lying lier liers", + "lie lies lied lying liar liars", + "life lives lifeless", + "light lights lit lighted lighting lightings lightly lighter lighters lightness lightnesses lightless", + "likely likelier likeliest", + "limen limens", + "lineament lineaments", + "liniment liniments", + "live alive living", + "live lives lived living livings", + "liver livers", + "loaf loafs loafed loafing loafings loafer loafers", + "loaf loaves", + "logic logics logical logically", + "lose loses lost losing loser losers loss losses", + "louse lice", + "lumen lumens", + "make makes made making makings maker makers", + "man mans manned manning mannings", + "man men", + "manly manlier manliest manliness manful manfulness manhood", + "manic manically", + "manner manners mannered mannerly mannerless mannerful", + "many", + "matrix matrices matrixes", + "may might", + "maximum maxima maximums maximal maximize maximizes maximized maximizing", + "mean means meant meaning meanings meaningless meaningful", + "mean meaner meanest meanly meanness meannesses", + "median medians medianly medial", + "medium media mediums", + "meet meets met meeting meetings", + "memorandum memoranda memorandums", + "mere merely", + "metal metals metallic", + "might mighty mightily", + "millenium millennia milleniums millennial", + "mine mines mined mining minings miner miners", + "mine my our ours", + "minimum minima minimums minimal", + "minus minuses", + "miscellaneous miscellanea miscellaneously miscellaneousness miscellany", + "molest molests molested molesting molestings molester molesters", + "moment moments", + "monument monuments monumental", + "more most", + "mouse mice mouseless", + "much", + "multiply multiplies multiplier multipliers multiple multiples multiplying multiplyings multiplication multiplications", + "mum mums mummed mumming mummings mummer mummers", + "must musts", + "neither", + "nemeses nemesis", + "neurosis neuroses neurotic neurotics", + "nomen", + "none", + "nos no noes", + "not", + "nothing nothings nothingness", + "now", + "nowadays", + "nucleus nuclei nucleuses nuclear", + "number numbers numbered numbering numberings numberless", + "nutriment nutriments nutrient nutrients nutrition nutritions", + "oasis oases", + "octopus octopi octopuses", + "of", + "off", + "offer offers offered offering offerings offerer offerers offeror offerors", + "often", + "oftentimes", + "ointment ointments", + "omen omens", + "on", + "once", + "only", + "ornament ornaments ornamented ornamenting ornamentings ornamentation ornamenter ornamenters ornamental", + "outdoor outdoors", + "outlay outlays", + "outlie outlies outlay outlied outlain outlying outlier outliers", + "ovum ova", + "ox oxen", + "parentheses parenthesis", + "parliament parliaments parliamentary", + "passerby passer-by passersby passers-by", + "past pasts", + "pay pays paid paying payings payer payers payee payees payment payments", + "per", + "perhaps", + "person persons people", + "phenomenon phenomena phenomenal", + "pi", + "picnic picnics picnicker picnickers picnicked picnicking picnickings", + "pigment pigments pigmented pigmenting pigmentings pigmenter pigmenters pigmentation pigmentations", + "please pleases pleased pleasing pleasings pleaser pleasers pleasure pleasures pleasuring pleasurings pleasant pleasantly pleasureless pleasureful", + "plus pluses plusses", + "polyhedra polyhedron polyhedral", + "priest priests priestly priestlier priestliest priestliness priestless", + "prognosis prognoses", + "prostheses prosthesis", + "prove proves proved proving provings proofs proof prover provers provable", + "psychosis psychoses psychotic psychotics", + "qed", + "quiz quizzes quizzed quizzing quizzings quizzer quizzers", + "raiment", + "rather", + "re", + "real really", + "redo redoes redid redone redoing redoings redoer redoers", + "regiment regiments regimented regimenting regimenter regimenters regimentation regimental", + "rendezvous", + "requiz requizzes requizzed requizzing requizzings requizzer requizzers", + "ride rides rode ridden riding ridings rider riders rideless", + "ring rings rang rung ringing ringings ringer ringers ringless", + "rise rises rose risen rising risings riser risers", + "rose roses", + "rudiment rudiments rudimentary", + "rum rums rummed rumming rummings rummer rummers", + "run runs ran running runnings runner runners", + "sacrament sacraments sacramental", + "same sameness", + "sans", + "saw saws sawed sawn sawing sawings sawyer sawyers", + "say says said saying sayings sayer sayers", + "scarf scarfs scarves scarfless", + "schema schemata schemas", + "sediment sediments sedimentary sedimentation sedimentations", + "see sees saw seen seeing seeings seer seers", + "seek seeks sought seeking seekings seeker seekers", + "segment segments segmented segmenting segmentings segmenter segmenters segmentation segmentations", + "self selves selfless", + "sell sells sold selling sellings seller sellers", + "semen", + "send sends sent sending sendings sender senders", + "sentiment sentiments sentimental", + "series", + "set sets setting settings", + "several severally", + "sew sews sewed sewn sewing sewings sewer sewers", + "sewer sewers sewerless", + "shake shakes shook shaken shaking shakings shaker shakers", + "shall should", + "shaman shamans", + "shave shaves shaved shaven shaving shavings shaver shavers shaveless", + "she her hers herself", + "sheaf sheaves sheafless", + "sheep", + "shelf shelves shelved shelfing shelvings shelver shelvers shelfless", + "shine shines shined shone shining shinings shiner shiners shineless", + "shoe shoes shoed shod shoeing shoeings shoer shoers shoeless", + "shoot shoots shot shooting shootings shooter shooters", + "shot shots", + "show shows showed shown showing showings shower showers", + "shower showers showery showerless", + "shrink shrinks shrank shrunk shrinking shrinkings shrinker shrinkers shrinkable", + "sideways", + "simply simple simpler simplest", + "since", + "sing sings sang sung singing singings singer singers singable", + "sink sinks sank sunk sinking sinkings sinker sinkers sinkable", + "sit sits sat sitting sittings sitter sitters", + "ski skis skied skiing skiings skier skiers skiless skiable", + "sky skies", + "slay slays slew slain slaying slayings slayer slayers", + "sleep sleeps slept sleeping sleepings sleeper sleepers sleepless", + "so", + "some", + "something", + "sometime sometimes", + "soon", + "spa spas", + "speak speaks spoke spoken speaking speakings speaker speakers", + "species specie", + "spectrum spectra spectrums", + "speed speeds sped speeded speeding speedings speeder speeders", + "spend spends spent spending spendings spender spenders spendable", + "spin spins spun spinning spinnings spinner spinners", + "spoke spokes", + "spring springs sprang sprung springing springings springer springers springy springiness", + "staff staffs staves staffed staffing staffings staffer staffers", + "stand stands stood standing standings", + "stasis stases", + "steal steals stole stolen stealing stealings stealer stealers", + "stick sticks stuck sticking stickings sticker stickers", + "stigma stigmata stigmas stigmatize stigmatizes stigmatized stigmatizing", + "stimulus stimuli", + "sting stings stung stinging stingings stinger stingers", + "stink stinks stank stunk stinking stinkings stinker stinkers", + "stomach stomachs", + "stratum strata stratums", + "stride strides strode stridden striding stridings strider striders", + "string strings strung stringing stringings stringer stringers stringless", + "strive strives strove striven striving strivings striver strivers", + "strum strums strummed strumming strummings strummer strummers strummable", + "such", + "suffer suffers suffered suffering sufferings sufferer sufferers sufferable", + "suggest suggests suggested suggesting suggestings suggester suggesters suggestor suggestors suggestive suggestion suggestions suggestible suggestable", + "sum sums summed summing summings summer summers", + "summer summers summered summering summerings", + "supplement supplements supplemented supplementing supplementings supplementation supplementer supplementers supplementary supplemental", + "supply supplies supplied supplying supplyings supplier suppliers", + "swear swears swore sworn swearing swearings swearer swearers", + "sweep sweeps swept sweeping sweepings sweeper sweepers", + "swell swells swelled swollen swelling swellings", + "swim swims swam swum swimming swimmings swimmer swimmers swimable", + "swine", + "swing swings swung swinging swingings swinger swingers", + "syllabus syllabi syllabuses", + "symposium symposia symposiums", + "synapse synapses", + "synapsis synapses", + "synopsis synopses", + "synthesis syntheses", + "tableau tableaux tableaus", + "take takes took taken taking takings taker takers takable", + "teach teaches taught teaching teachings teacher teachers teachable", + "tear tears tore torn tearing tearings tearer tearers tearable", + "tegument teguments", + "tell tells told telling tellings teller tellers tellable", + "temperament temperaments temperamental temperamentally", + "tenement tenements", + "the", + "there theres", + "theses thesis", + "they them their theirs themselves", + "thief thieves thieving thievings", + "think thinks thought thinking thinker thinkers thinkable", + "this that these those", + "thought thoughts thougtful thoughtless", + "throw throws threw thrown throwing throwings thrower throwers throwable", + "tic tics", + "tie ties tied tying tyings tier tiers tieable tieless", + "tier tiers tiered tiering tierings tierer tierers", + "to", + "toe toes toed toeing toeings toer toers toeless", + "together togetherness", + "too", + "tooth teeth toothless", + "topaz topazes", + "torment torments tormented tormenting tormentings tormenter tormenters tormentable", + "toward towards", + "tread treads trod trodden treading treadings treader treaders", + "tread treads treadless retread retreads", + "true truly trueness", + "two twos", + "u", + "under", + "underlay underlays underlaid underlaying underlayings underlayer underlayers", + "underlie underlies underlay underlain underlying underlier underliers", + "undo undoes undid undone undoing undoings undoer undoers undoable", + "unrest unrestful", + "until", + "unto", + "up", + "upon", + "upstairs", + "use uses user users used using useful useless", + "various variously", + "vehement vehemently vehemence", + "versus", + "very", + "visit visits visited visiting visitings visitor visitors", + "vortex vortexes vortices", + "wake wakes woke waked woken waking wakings waker wakers wakeful wakefulness wakefulnesses wakeable", + "wear wears wore worn wearing wearings wearer wearers wearable", + "weather weathers weathered weathering weatherly", + "weave weaves wove woven weaving weavings weaver weavers weaveable", + "weep weeps wept weeping weepings weeper weepers", + "wharf wharfs wharves", + "where wheres", + "whereas whereases", + "whether whethers", + "while whiles whilst whiled whiling", + "whiz whizzes whizzed whizzing whizzings whizzer whizzers", + "who whom whos whose whoses", + "why whys", + "wife wives wifeless", + "will wills willed willing willings willful", + "will would", + "win wins won winning winnings winner winners winnable", + "wind winds wound winding windings winder winders windable", + "wind winds windy windless", + "with", + "within", + "without", + "wolf wolves", + "woman women womanless womanly", + "wound wounds wounded wounding woundings", + "write writes wrote written writing writings writer writers writeable", + "yeses yes", + "yet yets", + "you your yours yourself" + ] + +_exdict = {} +for exlist in exceptions: + for ex in exlist.split(" "): + _exdict[ex] = exlist + +# Programmatic rules + +vowels = "aeiouy" +cons = "bcdfghjklmnpqrstvwxyz" + +rules = ( + # Words ending in S + + # (e.g., happiness, business) + (r"[%s].*[%s](iness)" % (vowels, cons), "y,ies,ier,iers,iest,ied,ying,yings,ily,inesses,iment,iments,iless,iful"), + # (e.g., baseless, shoeless) + (r"[%s].*(eless)" % vowels, "e,es,er,ers,est,ed,ing,ings,eing,eings,ely,eness,enesses,ement,ements,eness,enesses,eful"), + # (e.g., gutless, hatless, spotless) + (r"[%s][%s][bdgklmnprt]?(less)" % (cons, vowels), ",s,&er,&ers,&est,&ed,&ing,&ings,ly,ness,nesses,ment,ments,ful"), + # (e.g., thoughtless, worthless) + (r"[%s].*?(less)" % vowels, ",s,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,ful"), + # (e.g., baseness, toeness) + (r"[%s].*(eness)" % vowels, "e,es,er,ers,est,ed,ing,ings,eing,eings,ely,enesses,ement,ements,eless,eful"), + # (e.g., bluntness, grayness) + (r"[%s].*(ness)" % vowels, ",s,er,ers,est,ed,ing,ings,ly,nesses,ment,ments,less,ful"), + # (e.g., albatross, kiss) + (r"[%s]ss" % vowels, "es,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), + # (e.g., joyous, fractious, gaseous) + (r"[%s].*(ous)" % vowels, "ly,ness"), + # (e.g., tries, unties, jollies, beauties) + (r"(ies)", "y,ie,yer,yers,ier,iers,iest,ied,ying,yings,yness,iness,ieness,ynesses,inesses,ienesses,iment,iement,iments,iements,yless,iless,ieless,yful,iful,ieful"), + # (e.g., crisis, kinesis) + (r"[%s].*(sis)" % vowels, "ses,sises,sisness,sisment,sisments,sisless,sisful"), + # (e.g., bronchitis, bursitis) + (r"[%s].*(is)" % vowels, "es,ness,ment,ments,less,ful"), + (r"[%s].*[cs]h(es)" % vowels, ",e,er,ers,est,ed,ing,ings,ly,ely,ness,eness,nesses,enesses,ment,ement,ments,ements,less,eless,ful,eful"), + # (e.g., tokenizes) // adds British variations + (r"[%s].*[%s](izes)" % (vowels, cons), "ize,izes,izer,izers,ized,izing,izings,ization,izations,ise,iser,isers,ised,ising,isings,isation,isations"), + # (e.g., tokenises) // British variant // ~expertise + (r"[%s].*[%s](ises)" % (vowels, cons), "ize,izes,izer,izers,ized,izing,izings,ization,izations,ise,iser,isers,ised,ising,isings,isation,isations"), + # (e.g., aches, arches) + (r"[%s].*[jsxz](es)" % vowels, ",e,er,ers,est,ed,ing,ings,ly,ely,ness,eness,nesses,enesses,ment,ement,ments,ements,less,eless,ful,eful"), + # (e.g., judges, abridges) + (r"[%s].*dg(es)" % vowels, "e,er,ers,est,ed,ing,ings,ely,eness,enesses,ment,ments,ement,ements,eless,eful"), + # (e.g., trees, races, likes, agrees) covers all other -es words + (r"e(s)", ",*"), + # (e.g., segments, bisegments, cosegments) + (r"segment(s)", ",*"), + # (e.g., pigments, depigments, repigments) + (r"pigment(s)", ",*"), + # (e.g., judgments, abridgments) + (r"[%s].*dg(ments)" % vowels, "ment,*ments"), + # (e.g., merriments, embodiments) -iment in turn will generate y and *y (redo y) + (r"[%s].*[%s]iment(s)" % (vowels, cons), ",*"), + # (e.g., atonements, entrapments) + (r"[%s].*ment(s)" % vowels, ",*"), + # (e.g., viewers, meters, traders, transfers) + (r"[%s].*er(s)" % vowels, ",*"), + # (e.g., unflags) polysyllables + (r"[%s].*[%s][%s][bdglmnprt](s)" % (vowels, cons, vowels), ",*"), + # (e.g., frogs) monosyllables + (r"[%s][%s][bdglmnprt](s)" % (vowels, cons), ",*"), + # (e.g., killings, muggings) + (r"[%s].*ing(s)" % vowels, ",*"), + # (e.g., hulls, tolls) + (r"[%s].*ll(s)" % vowels, ",*"), + # e.g., boas, polkas, spas) don't generate latin endings + (r"a(s)", ",er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), + # (e.g., beads, toads) + (r"[%s].*[%s].*(s)" % (vowels, cons), ",*"), + # (e.g., boas, zoos) + (r"[%s].*[%s](s)" % (cons, vowels), ",er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), + # (e.g., ss, sss, ssss) no vowel (vowel case is already handled above) + (r"ss()", ""), + # (e.g., cds, lcds, m-16s) no vowel (can be a plural noun, but not verb) + (r"[%s].*[%s1234567890](s)" % (cons, cons), ""), + + # Words ending in E + + # (e.g., apple, so it doesn't include apply) + (r"appl(e)", "es,er,ers,est,ed,ing,ings,ely,eness,enesses,ement,ements,eless,eful"), + # (e.g., supple, so it doesn't include supply) + (r"suppl(e)", "es,er,ers,est,ed,ing,ings,ely,eness,enesses,ement,ements,eless,eful"), + # (e.g., able, abominable, fungible, table, enable, idle, subtle) + (r"[%s].*[%s]l(e)" % (vowels, cons), "es,er,ers,est,ed,ing,ings,y,ely,eness,enesses,ement,ements,eless,eful"), + # (e.g., bookie, magpie, vie) + (r"(ie)", "ies,ier,iers,iest,ied,ying,yings,iely,ieness,ienesses,iement,iements,ieless,ieful"), + # (e.g., dye, redye, redeye) + (r"ye()", "s,r,rs,st,d,ing,ings,ly,ness,nesses,ment,ments,less,ful"), + # (e.g., judge, abridge) + (r"[%s].*dg(e)" % vowels, "es,er,ers,est,ed,ing,ings,ely,eness,enesses,ment,ments,less,ful,ement,ements,eless,eful"), + # (e.g., true, due, imbue) + (r"u(e)", "es,er,ers,est,ed,ing,ings,eing,eings,ly,ely,eness,enesses,ment,ments,less,ful,ement,ements,eless,eful"), + # (e.g., tokenize) // adds British variations + (r"[%s].*[%s](ize)" % (vowels, cons), "izes,izer,izers,ized,izing,izings,ization,izations,ise,ises,iser,isers,ised,ising,isings,isation,isations"), + # (e.g., tokenise) // British variant // ~expertise + (r"[%s].*[%s](ise)" % (vowels, cons), "ize,izes,izer,izers,ized,izing,izings,ization,izations,ises,iser,isers,ised,ising,isings,isation,isations"), + # (e.g., tree, agree, rage, horse, hoarse) + (r"[%s].*[%s](e)" % (vowels, cons), "es,er,ers,est,ed,ing,ings,eing,eings,ely,eness,enesses,ement,ements,eless,eful"), + + # Words ending in -ED + + # (e.g., agreed, freed, decreed, treed) + (r"ree(d)", "ds,der,ders,ded,ding,dings,dly,dness,dnesses,dment,dments,dless,dful,,*"), + # (e.g., feed, seed, Xweed) + (r"ee(d)", "ds,der,ders,ded,ding,dings,dly,dness,dnesses,dment,dments,dless,dful"), + # (e.g., tried) + (r"[%s](ied)" % cons, "y,ie,ies,ier,iers,iest,ying,yings,ily,yly,iness,yness,inesses,ynesses,iment,iments,iless,iful,yment,yments,yless,yful"), + # (e.g., controlled, fulfilled, rebelled) + (r"[%s].*[%s].*l(led)" % (vowels, cons), ",s,er,ers,est,ing,ings,ly,ness,nesses,ment,ments,less,ful,&,&s,&er,&ers,&est,&ing,&ings,&y,&ness,&nesses,&ment,&ments,&ful"), + # (e.g., pulled, filled, fulled) + (r"[%s].*l(led)" % vowels, "&,&s,&er,&ers,&est,&ing,&ings,&y,&ness,&nesses,&ment,&ments,&ful"), + # (e.g., hissed, grossed) + (r"[%s].*s(sed)" % vowels, "&,&es,&er,&ers,&est,&ing,&ings,&ly,&ness,&nesses,&ment,&ments,&less,&ful"), + # (e.g., hugged, trekked) + (r"[%s][%s](?P[bdgklmnprt])((?P=ed1)ed)", ",s,&er,&ers,&est,&ing,&ings,ly,ness,nesses,ment,ments,less,ful"), + # (e.g., tokenize) // adds British variations + (r"[%s].*[%s](ized)" % (vowels, cons), "izes,izer,izers,ize,izing,izings,ization,izations,ise,ises,iser,isers,ised,ising,isings,isation,isations"), + # (e.g., tokenise) // British variant // ~expertise + (r"[%s].*[%s](ized)" % (vowels, cons), "ize,izes,izer,izers,ized,izing,izings,ization,izations,ises,iser,isers,ise,ising,isings,isation,isations"), + # (e.g., spoiled, tooled, tracked, roasted, atoned, abridged) + (r"[%s].*(ed)" % vowels, ",e,s,es,er,ers,est,ing,ings,ly,ely,ness,eness,nesses,enesses,ment,ement,ments,ements,less,eless,ful,eful"), + # (e.g., bed, sled) words with a single e as the only vowel + (r"ed()", "s,&er,&ers,&est,&ed,&ing,&ings,ly,ness,nesses,ment,ments,less,ful"), + + # Words ending in -ER + + # (e.g., altimeter, ammeter, odometer, perimeter) + (r"meter()", "s,er,ers,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), + # (e.g., agreer, beer, budgeteer, engineer, freer) + (r"eer()", "eers,eered,eering,eerings,eerly,eerness,eernesses,eerment,eerments,eerless,eerful,ee,ees,eest,eed,eeing,eeings,eely,eeness,eenesses,eement,eements,eeless,eeful,eerer,eerers,eerest"), + # (e.g., acidifier, saltier) + (r"[%s].*[%s](ier)" % (vowels, cons), "y,ie,ies,iest,ied,ying,yings,ily,yly,iness,yness,inesses,ynesses,yment,yments,yless,yful,iment,iments,iless,iful,iers,iered,iering,ierings,ierly,ierness,iernesses,ierment,ierments,ierless,ierful,ierer,ierers,ierest"), + # (e.g., puller, filler, fuller) + (r"[%s].*l(ler)" % vowels, "&,&s,&est,&ed,&ing,&ings,ly,lely,&ness,&nesses,&ment,&ments,&ful,&ers,&ered,&ering,&erings,&erly,&erness,&ernesses,&erments,&erless,&erful"), + # (e.g., hisser, grosser) + (r"[%s].*s(ser)" % vowels, "&,&es,&est,&ed,&ing,&ings,&ly,&ness,&nesses,&ment,&ments,&less,&ful,&ers,&ered,&ering,&erings,&erly,&erness,&ernesses,&erment,&erments,&erless,&erful"), + # (e.g., bigger, trekker, hitter) + (r"[%s][%s](?P[bdgkmnprt])((?P=er1)er)" % (cons, vowels), "s,&est,&ed,&ing,&ings,ly,ness,nesses,ment,ments,less,ful,&ers,&ered,&ering,&erings,&erly,&erness,&ernesses,&erments,&erless,&erful"), + # (e.g., tokenize) // adds British variations + (r"[%s].*[%s](izer)" % (vowels, cons), "izes,ize,izers,ized,izing,izings,ization,izations,ise,ises,iser,isers,ised,ising,isings,isation,isations"), + # (e.g., tokenise) // British variant // ~expertise + (r"[%s].*[%s](iser)" % (vowels, cons), "ize,izes,izer,izers,ized,izing,izings,ization,izations,ises,ise,isers,ised,ising,isings,isation,isations"), + #(e.g., actioner, atoner, icer, trader, accruer, churchgoer, prefer) + (r"[%s].*(er)" % vowels, ",e,s,es,est,ed,ing,ings,ly,ely,ness,eness,nesses,enesses,ment,ments,less,ful,ement,ements,eless,eful,ers,ered,erred,ering,erring,erings,errings,erly,erness,ernesses,erment,erments,erless,erful,erer,erers,erest,errer,errers,errest"), + + # Words ending in -EST + + # (e.g., sliest, happiest, wittiest) + (r"[%s](iest)" % cons, "y,ies,ier,iers,ied,ying,yings,ily,yly,iness,yness,inesses,ynesses,iment,iments,iless,iful"), + # (e.g., fullest) + (r"[%s].*l(lest)" % vowels, "&,&s,&er,&ers,&ed,&ing,&ings,ly,&ness,&nesses,&ment,&ments,&ful"), + # (e.g., grossest) + (r"[%s].*s(sest)" % vowels, "&,&es,&er,&ers,&ed,&ing,&ings,&ly,&ness,&nesses,&ment,&ments,&less,&ful"), + # (e.g., biggest) + (r"[%s][%s](?P[bdglmnprst])((?P=est1)est)" % (cons, vowels), ",s,&er,&ers,&ed,&ing,&ings,ly,ness,nesses,ment,ments,less,ful"), + # (e.g., basest, archest, rashest) + (r"[%s].*([cs]h|[jsxz])(est)" % vowels, "e,es,er,ers,ed,ing,ings,ly,ely,ness,eness,nesses,enesses,ment,ments,less,ful,ement,ements,eless,eful,ests,ester,esters,ested,esting,estings,estly,estness,estnesses,estment,estments,estless,estful"), + # (e.g., severest, Xinterest, merest) + (r"er(est)", "e,es,er,ers,ed,eing,eings,ely,eness,enesses,ement,ements,eless,eful,ests,ester,esters,ested,esting,estings,estly,estness,estnesses,estment,estments,estless,estful"), + # (e.g., slickest, coolest, ablest, amplest, protest, quest) + (r"[%s].*(est)" % vowels, ",e,s,es,er,ers,ed,ing,ings,ly,ely,ness,eness,nesses,enesses,ment,ments,less,ful,ement,ements,eless,eful,ests,ester,esters,ested,esting,estings,estly,estness,estnesses,estment,estments,estless,estful"), + # (e.g., rest, test) + (r"est", "s,er,ers,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), + + # Words ending in -FUL + + # (e.g., beautiful, plentiful) + (r"[%s].*[%s](iful)" % (vowels, cons), "ifully,ifulness,*y"), + # (e.g., hopeful, sorrowful) + (r"[%s].*(ful)" % vowels, "fully,fulness,,*"), + + # Words ending in -ICAL + + (r"[%s].*(ical)" % vowels, "ic,ics,ically"), + + # Words ending in -IC + + (r"[%s].*(ic)" % vowels, "ics,ical,ically"), + + # Words ending in -ING + + # (e.g., dying, crying, supplying) + (r"[%s](ying)" % cons, "yings,ie,y,ies,ier,iers,iest,ied,iely,yly,ieness,yness,ienesses,ynesses,iment,iments,iless,iful"), + # (e.g., pulling, filling, fulling) + (r"[%s].*l(ling)" % vowels, ",*,&,&s,&er,&ers,&est,&ed,&ings,&ness,&nesses,&ment,&ments,&ful"), + # (e.g., hissing, grossing, processing) + (r"[%s].*s(sing)" % vowels, "&,&s,&er,&ers,&est,&ed,&ings,&ly,&ness,&nesses,&ment,&ments,&less,&ful"), + # (e.g., hugging, trekking) + (r"[%s][%s](?P[bdgklmnprt])((?P=ing1)ing)" % (cons, vowels), ",s,&er,&ers,&est,&ed,&ings,ly,ness,nesses,ment,ments,less,ful"), + # (e.g., freeing, agreeing) + (r"eeing()", "ee,ees,eer,eers,eest,eed,eeings,eely,eeness,eenesses,eement,eements,eeless,eeful"), + # (e.g., ageing, aweing) + (r"[%s].*(eing)" % vowels, "e,es,er,ers,est,ed,eings,ely,eness,enesses,ement,ements,eless,eful"), + # (e.g., toying, playing) + (r"[%s].*y(ing)" % vowels, ",s,er,ers,est,ed,ings,ly,ingly,ness,nesses,ment,ments,less,ful"), + # (e.g., editing, crediting, expediting, siting, exciting) + (r"[%s].*[%s][eio]t(ing)" % (vowels, cons), ",*,*e,ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), + # (e.g., robing, siding, doling, translating, flaking) + (r"[%s][%s][bdgklmt](ing)" % (cons, vowels), "*e,ings,inger,ingers,ingest,inged,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), + # (e.g., tokenize) // adds British variations + (r"[%s].*[%s](izing)" % (vowels, cons), "izes,izer,izers,ized,ize,izings,ization,izations,ise,ises,iser,isers,ised,ising,isings,isation,isations"), + # (e.g., tokenise) // British variant // ~expertise + (r"[%s].*[%s](ising)" % (vowels, cons), "ize,izes,izer,izers,ized,izing,izings,ization,izations,ises,iser,isers,ised,ise,isings,isation,isations"), + # (e.g., icing, aging, achieving, amazing, housing) + (r"[%s][cgsvz](ing)" % vowels, "*e,ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), + # (e.g., dancing, troubling, arguing, bluing, carving) + (r"[%s][clsuv](ing)" % cons, "*e,ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), + # (e.g., charging, bulging) + (r"[%s].*[lr]g(ing)" % vowels, "*e,ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), + # (e.g., farming, harping, interesting, bedspring, redwing) + (r"[%s].*[%s][bdfjkmnpqrtwxz](ing)" % (vowels, cons), ",*,ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), + # (e.g., spoiling, reviling, autoing, egging, hanging, hingeing) + (r"[%s].*(ing)" % vowels, ",*,*e,ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), + # (e.g., wing, thing) monosyllables + (r"(ing)", "ings,inger,ingers,ingest,inged,inging,ingings,ingly,ingness,ingnesses,ingment,ingments,ingless,ingful"), + + # -LEAF rules omitted + + # Words ending in -MAN + # (e.g., policewomen, hatchetmen, dolmen) + (r"(man)", "man,mens,mener,meners,menest,mened,mening,menings,menly,menness,mennesses,menless,menful"), + + # Words ending in -MENT + + # (e.g., segment, bisegment, cosegment, pigment, depigment, repigment) + (r"segment|pigment", "s,ed,ing,ings,er,ers,ly,ness,nesses,less,ful"), + # (e.g., judgment, abridgment) + (r"[%s].*dg(ment)" % vowels, "*e"), + # (e.g., merriment, embodiment) + (r"[%s].*[%s](iment)" % (vowels, cons), "*y"), + # (e.g., atonement, entrapment) + (r"[%s].*[%s](ment)" % (vowels, cons), ",*"), + + # Words ending in -O + + # (e.g., taboo, rodeo) + (r"[%s]o()" % vowels, "s,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), + # (e.g., tomato, bonito) + (r"[%s].*o()" % vowels, "s,es,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), + + # Words ending in -UM + + # (e.g., datum, quantum, tedium, strum, [oil]drum, vacuum) + (r"[%s].*(um)" % vowels, "a,ums,umer,ummer,umers,ummers,umed,ummed,uming,umming,umings,ummings,umness,umments,umless,umful"), + + # Words ending in -Y + + # (e.g., ably, horribly, wobbly) + (r"[%s].*b(ly)" % vowels, "le,les,ler,lers,lest,led,ling,lings,leness,lenesses,lement,lements,leless,leful"), + # (e.g., happily, dizzily) + (r"[%s].*[%s](ily)" % (vowels, cons), "y,ies,ier,iers,iest,ied,ying,yings,yness,iness,ynesses,inesses,iment,iments,iless,iful"), + # (e.g., peaceful+ly) + (r"[%s].*ful(ly)" % vowels, ",*"), + # (e.g., fully, folly, coolly, fatally, dally) + (r"[%s].*l(ly)" % vowels, ",*,lies,lier,liers,liest,lied,lying,lyings,liness,linesses,liment,liments,liless,liful,*l"), + # (e.g., monopoly, Xcephaly, holy) + (r"[%s](ly)" % vowels, "lies,lier,liers,liest,lied,lying,lyings,liness,linesses,liment,liments,liless,liful"), + # (e.g., frequently, comely, deeply, apply, badly) + (r"[%s].*(ly)" % vowels, ",*,lies,lier,liers,liest,lied,lying,lyings,liness,linesses,lyless,lyful"), + # (e.g., happy, ply, spy, cry) + (r"[%s](y)" % cons, "ies,ier,iers,iest,ied,ying,yings,ily,yness,iness,ynesses,inesses,iment,iments,iless,iful,yment,yments,yless,yful"), + # (e.g., betray, gay, stay) + (r"[%s]y()" % vowels, "s,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), + + # Root rules + + # (e.g., fix, arch, rash) + (r"[%s].*(ch|sh|[jxz])()" % vowels, "es,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), + # (e.g., unflag, open, besot) + (r"[%s].*[%s][%s][bdglmnprt]()" % (vowels, cons, vowels), "s,er,ers,est,ed,ing,ings,&er,&ers,&est,&ed,&ing,&ings,ly,ness,nesses,ment,ments,less,ful"), + # (e.g., bed, cop) + (r"[%s][%s][bdglmnprt]()" % (cons, vowels), "s,&er,&ers,&est,&ed,&ing,&ings,ly,ness,nesses,ment,ments,less,ful"), + # (e.g., schemata, automata) + (r"[%s].*[%s][%s]ma(ta)" % (vowels, cons, vowels), ",s,tas,tum,tums,ton,tons,tic,tical"), + # (e.g., chordata, data, errata, sonata, toccata) + (r"[%s].*t(a)" % vowels, "as,ae,um,ums,on,ons,ic,ical"), + # (e.g., polka, spa, schema, ova, polyhedra) + (r"[%s].*[%s](a)" % (vowels, cons), "as,aed,aing,ae,ata,um,ums,on,ons,al,atic,atical"), + # (e.g., full) + (r"[%s].*ll()" % vowels, "s,er,ers,est,ed,ing,ings,y,ness,nesses,ment,ments,-less,ful"), + # (e.g., spoon, rhythm) + (r"[%s].*()", "s,er,ers,est,ed,ing,ings,ly,ness,nesses,ment,ments,less,ful"), + ) + +# There are a limited number of named groups available in a single +# regular expression, so we'll partition the list of rules into +# smaller chunks. + +_partition_size = 20 +_partitions = [] +for p in xrange(0, len(rules) // _partition_size + 1): + start = p * _partition_size + end = (p+1) * _partition_size + pattern = "|".join("(?P<_g%s>%s)$" % (i, r[0]) for i,r in enumerate(rules[start:end])) + _partitions.append(re.compile(pattern)) + +#print "\n".join(p.pattern for p in _partitions) + +def variations(word): + """Given an English word, returns a collection of morphological variations + on the word by algorithmically adding and removing suffixes. The variation + list may contain non-words (e.g. render -> renderment). + + >>> variations("pull") + set(['pull', 'pullings', 'pullnesses', 'pullful', 'pullment', 'puller', ... ]) + """ + + if word in _exdict: + return _exdict[word].split(" ") + + for i, p in enumerate(_partitions): + match = p.search(word) + if match: + # Get the named group that matched + num = int([k for k, v in match.groupdict().iteritems() + if v is not None and k.startswith("_g")][0][2:]) + # Get the positional groups for the matched group (all other + # positional groups are None) + groups = [g for g in match.groups() if g is not None] + ending = groups[-1] + root = word[:0-len(ending)] if ending else word + + out = set((word, )) + results = rules[i * _partition_size + num][1] + for result in results.split(","): + if result.startswith("&"): + out.add(root + root[-1] + result[1:]) + elif result.startswith("*"): + out.union(variations(root + result[1:])) + else: + out.add(root + result) + return set(out) + + return [word] + + +if __name__ == '__main__': + import time + t = time.clock() + s = variations("rendering") + print time.clock() - t + print len(s) + diff --git a/lib/whoosh/lang/paicehusk.py b/lib/whoosh/lang/paicehusk.py index c9f7eb25..644903fd 100644 --- a/lib/whoosh/lang/paicehusk.py +++ b/lib/whoosh/lang/paicehusk.py @@ -1,246 +1,246 @@ -"""This module contains an object that implements the Paice-Husk stemming -algorithm. - -If you just want to use the standard Paice-Husk stemming rules, use the -module's ``stem()`` function:: - - stemmed_word = stem(word) - -If you want to use a custom rule set, read the rules into a string where the -rules are separated by newlines, and instantiate the object with the string, -then use the object's stem method to stem words:: - - stemmer = PaiceHuskStemmer(my_rules_string) - stemmed_word = stemmer.stem(word) -""" - -import re -from collections import defaultdict - - -class PaiceHuskStemmer(object): - """Implements the Paice-Husk stemming algorithm. - """ - - rule_expr = re.compile(r""" - ^(?P\w+) - (?P[*]?) - (?P\d+) - (?P\w*) - (?P[.>]) - """, re.UNICODE | re.VERBOSE) - - stem_expr = re.compile("^\w+", re.UNICODE) - - def __init__(self, ruletable): - """ - :param ruletable: a string containing the rule data, separated - by newlines. - """ - self.rules = defaultdict(list) - self.read_rules(ruletable) - - def read_rules(self, ruletable): - rule_expr = self.rule_expr - rules = self.rules - - for line in ruletable.split("\n"): - line = line.strip() - if not line: - continue - - match = rule_expr.match(line) - if match: - ending = match.group("ending")[::-1] - lastchar = ending[-1] - intact = match.group("intact") == "*" - num = int(match.group("num")) - append = match.group("append") - cont = match.group("cont") == ">" - - rules[lastchar].append((ending, intact, num, append, cont)) - else: - raise Exception("Bad rule: %r" % line) - - def first_vowel(self, word): - vp = min([p for p in [word.find(v) for v in "aeiou"] - if p > -1]) - yp = word.find("y") - if yp > 0 and yp < vp: - return yp - return vp - - def strip_prefix(self, word): - for prefix in ("kilo", "micro", "milli", "intra", "ultra", "mega", - "nano", "pico", "pseudo"): - if word.startswith(prefix): - return word[len(prefix):] - return word - - def stem(self, word): - """Returns a stemmed version of the argument string. - """ - - rules = self.rules - match = self.stem_expr.match(word) - if not match: return word - stem = self.strip_prefix(match.group(0)) - - is_intact = True - continuing = True - while continuing: - pfv = self.first_vowel(stem) - rulelist = rules.get(stem[-1]) - if not rulelist: break - - continuing = False - for ending, intact, num, append, cont in rulelist: - if stem.endswith(ending): - if intact and not is_intact: continue - newlen = len(stem) - num + len(append) - - if ((pfv == 0 and newlen < 2) - or (pfv > 0 and newlen < 3)): - # If word starts with vowel, minimum stem length is 2. - # If word starts with consonant, minimum stem length is - # 3. - continue - - is_intact = False - stem = stem[:0-num] + append - - continuing = cont - break - - return stem - -# The default rules for the Paice-Husk stemming algorithm - -defaultrules = """ -ai*2. { -ia > - if intact } -a*1. { -a > - if intact } -bb1. { -bb > -b } -city3s. { -ytic > -ys } -ci2> { -ic > - } -cn1t> { -nc > -nt } -dd1. { -dd > -d } -dei3y> { -ied > -y } -deec2ss. { -ceed > -cess } -dee1. { -eed > -ee } -de2> { -ed > - } -dooh4> { -hood > - } -e1> { -e > - } -feil1v. { -lief > -liev } -fi2> { -if > - } -gni3> { -ing > - } -gai3y. { -iag > -y } -ga2> { -ag > - } -gg1. { -gg > -g } -ht*2. { -th > - if intact } -hsiug5ct. { -guish > -ct } -hsi3> { -ish > - } -i*1. { -i > - if intact } -i1y> { -i > -y } -ji1d. { -ij > -id -- see nois4j> & vis3j> } -juf1s. { -fuj > -fus } -ju1d. { -uj > -ud } -jo1d. { -oj > -od } -jeh1r. { -hej > -her } -jrev1t. { -verj > -vert } -jsim2t. { -misj > -mit } -jn1d. { -nj > -nd } -j1s. { -j > -s } -lbaifi6. { -ifiabl > - } -lbai4y. { -iabl > -y } -lba3> { -abl > - } -lbi3. { -ibl > - } -lib2l> { -bil > -bl } -lc1. { -cl > c } -lufi4y. { -iful > -y } -luf3> { -ful > - } -lu2. { -ul > - } -lai3> { -ial > - } -lau3> { -ual > - } -la2> { -al > - } -ll1. { -ll > -l } -mui3. { -ium > - } -mu*2. { -um > - if intact } -msi3> { -ism > - } -mm1. { -mm > -m } -nois4j> { -sion > -j } -noix4ct. { -xion > -ct } -noi3> { -ion > - } -nai3> { -ian > - } -na2> { -an > - } -nee0. { protect -een } -ne2> { -en > - } -nn1. { -nn > -n } -pihs4> { -ship > - } -pp1. { -pp > -p } -re2> { -er > - } -rae0. { protect -ear } -ra2. { -ar > - } -ro2> { -or > - } -ru2> { -ur > - } -rr1. { -rr > -r } -rt1> { -tr > -t } -rei3y> { -ier > -y } -sei3y> { -ies > -y } -sis2. { -sis > -s } -si2> { -is > - } -ssen4> { -ness > - } -ss0. { protect -ss } -suo3> { -ous > - } -su*2. { -us > - if intact } -s*1> { -s > - if intact } -s0. { -s > -s } -tacilp4y. { -plicat > -ply } -ta2> { -at > - } -tnem4> { -ment > - } -tne3> { -ent > - } -tna3> { -ant > - } -tpir2b. { -ript > -rib } -tpro2b. { -orpt > -orb } -tcud1. { -duct > -duc } -tpmus2. { -sumpt > -sum } -tpec2iv. { -cept > -ceiv } -tulo2v. { -olut > -olv } -tsis0. { protect -sist } -tsi3> { -ist > - } -tt1. { -tt > -t } -uqi3. { -iqu > - } -ugo1. { -ogu > -og } -vis3j> { -siv > -j } -vie0. { protect -eiv } -vi2> { -iv > - } -ylb1> { -bly > -bl } -yli3y> { -ily > -y } -ylp0. { protect -ply } -yl2> { -ly > - } -ygo1. { -ogy > -og } -yhp1. { -phy > -ph } -ymo1. { -omy > -om } -ypo1. { -opy > -op } -yti3> { -ity > - } -yte3> { -ety > - } -ytl2. { -lty > -l } -yrtsi5. { -istry > - } -yra3> { -ary > - } -yro3> { -ory > - } -yfi3. { -ify > - } -ycn2t> { -ncy > -nt } -yca3> { -acy > - } -zi2> { -iz > - } -zy1s. { -yz > -ys } -""" - -# Make the standard rules available as a module-level function - -stem = PaiceHuskStemmer(defaultrules).stem - - - - - - - +"""This module contains an object that implements the Paice-Husk stemming +algorithm. + +If you just want to use the standard Paice-Husk stemming rules, use the +module's ``stem()`` function:: + + stemmed_word = stem(word) + +If you want to use a custom rule set, read the rules into a string where the +rules are separated by newlines, and instantiate the object with the string, +then use the object's stem method to stem words:: + + stemmer = PaiceHuskStemmer(my_rules_string) + stemmed_word = stemmer.stem(word) +""" + +import re +from collections import defaultdict + + +class PaiceHuskStemmer(object): + """Implements the Paice-Husk stemming algorithm. + """ + + rule_expr = re.compile(r""" + ^(?P\w+) + (?P[*]?) + (?P\d+) + (?P\w*) + (?P[.>]) + """, re.UNICODE | re.VERBOSE) + + stem_expr = re.compile("^\w+", re.UNICODE) + + def __init__(self, ruletable): + """ + :param ruletable: a string containing the rule data, separated + by newlines. + """ + self.rules = defaultdict(list) + self.read_rules(ruletable) + + def read_rules(self, ruletable): + rule_expr = self.rule_expr + rules = self.rules + + for line in ruletable.split("\n"): + line = line.strip() + if not line: + continue + + match = rule_expr.match(line) + if match: + ending = match.group("ending")[::-1] + lastchar = ending[-1] + intact = match.group("intact") == "*" + num = int(match.group("num")) + append = match.group("append") + cont = match.group("cont") == ">" + + rules[lastchar].append((ending, intact, num, append, cont)) + else: + raise Exception("Bad rule: %r" % line) + + def first_vowel(self, word): + vp = min([p for p in [word.find(v) for v in "aeiou"] + if p > -1]) + yp = word.find("y") + if yp > 0 and yp < vp: + return yp + return vp + + def strip_prefix(self, word): + for prefix in ("kilo", "micro", "milli", "intra", "ultra", "mega", + "nano", "pico", "pseudo"): + if word.startswith(prefix): + return word[len(prefix):] + return word + + def stem(self, word): + """Returns a stemmed version of the argument string. + """ + + rules = self.rules + match = self.stem_expr.match(word) + if not match: return word + stem = self.strip_prefix(match.group(0)) + + is_intact = True + continuing = True + while continuing: + pfv = self.first_vowel(stem) + rulelist = rules.get(stem[-1]) + if not rulelist: break + + continuing = False + for ending, intact, num, append, cont in rulelist: + if stem.endswith(ending): + if intact and not is_intact: continue + newlen = len(stem) - num + len(append) + + if ((pfv == 0 and newlen < 2) + or (pfv > 0 and newlen < 3)): + # If word starts with vowel, minimum stem length is 2. + # If word starts with consonant, minimum stem length is + # 3. + continue + + is_intact = False + stem = stem[:0-num] + append + + continuing = cont + break + + return stem + +# The default rules for the Paice-Husk stemming algorithm + +defaultrules = """ +ai*2. { -ia > - if intact } +a*1. { -a > - if intact } +bb1. { -bb > -b } +city3s. { -ytic > -ys } +ci2> { -ic > - } +cn1t> { -nc > -nt } +dd1. { -dd > -d } +dei3y> { -ied > -y } +deec2ss. { -ceed > -cess } +dee1. { -eed > -ee } +de2> { -ed > - } +dooh4> { -hood > - } +e1> { -e > - } +feil1v. { -lief > -liev } +fi2> { -if > - } +gni3> { -ing > - } +gai3y. { -iag > -y } +ga2> { -ag > - } +gg1. { -gg > -g } +ht*2. { -th > - if intact } +hsiug5ct. { -guish > -ct } +hsi3> { -ish > - } +i*1. { -i > - if intact } +i1y> { -i > -y } +ji1d. { -ij > -id -- see nois4j> & vis3j> } +juf1s. { -fuj > -fus } +ju1d. { -uj > -ud } +jo1d. { -oj > -od } +jeh1r. { -hej > -her } +jrev1t. { -verj > -vert } +jsim2t. { -misj > -mit } +jn1d. { -nj > -nd } +j1s. { -j > -s } +lbaifi6. { -ifiabl > - } +lbai4y. { -iabl > -y } +lba3> { -abl > - } +lbi3. { -ibl > - } +lib2l> { -bil > -bl } +lc1. { -cl > c } +lufi4y. { -iful > -y } +luf3> { -ful > - } +lu2. { -ul > - } +lai3> { -ial > - } +lau3> { -ual > - } +la2> { -al > - } +ll1. { -ll > -l } +mui3. { -ium > - } +mu*2. { -um > - if intact } +msi3> { -ism > - } +mm1. { -mm > -m } +nois4j> { -sion > -j } +noix4ct. { -xion > -ct } +noi3> { -ion > - } +nai3> { -ian > - } +na2> { -an > - } +nee0. { protect -een } +ne2> { -en > - } +nn1. { -nn > -n } +pihs4> { -ship > - } +pp1. { -pp > -p } +re2> { -er > - } +rae0. { protect -ear } +ra2. { -ar > - } +ro2> { -or > - } +ru2> { -ur > - } +rr1. { -rr > -r } +rt1> { -tr > -t } +rei3y> { -ier > -y } +sei3y> { -ies > -y } +sis2. { -sis > -s } +si2> { -is > - } +ssen4> { -ness > - } +ss0. { protect -ss } +suo3> { -ous > - } +su*2. { -us > - if intact } +s*1> { -s > - if intact } +s0. { -s > -s } +tacilp4y. { -plicat > -ply } +ta2> { -at > - } +tnem4> { -ment > - } +tne3> { -ent > - } +tna3> { -ant > - } +tpir2b. { -ript > -rib } +tpro2b. { -orpt > -orb } +tcud1. { -duct > -duc } +tpmus2. { -sumpt > -sum } +tpec2iv. { -cept > -ceiv } +tulo2v. { -olut > -olv } +tsis0. { protect -sist } +tsi3> { -ist > - } +tt1. { -tt > -t } +uqi3. { -iqu > - } +ugo1. { -ogu > -og } +vis3j> { -siv > -j } +vie0. { protect -eiv } +vi2> { -iv > - } +ylb1> { -bly > -bl } +yli3y> { -ily > -y } +ylp0. { protect -ply } +yl2> { -ly > - } +ygo1. { -ogy > -og } +yhp1. { -phy > -ph } +ymo1. { -omy > -om } +ypo1. { -opy > -op } +yti3> { -ity > - } +yte3> { -ety > - } +ytl2. { -lty > -l } +yrtsi5. { -istry > - } +yra3> { -ary > - } +yro3> { -ory > - } +yfi3. { -ify > - } +ycn2t> { -ncy > -nt } +yca3> { -acy > - } +zi2> { -iz > - } +zy1s. { -yz > -ys } +""" + +# Make the standard rules available as a module-level function + +stem = PaiceHuskStemmer(defaultrules).stem + + + + + + + diff --git a/lib/whoosh/lang/porter.py b/lib/whoosh/lang/porter.py index cd2992aa..86245370 100644 --- a/lib/whoosh/lang/porter.py +++ b/lib/whoosh/lang/porter.py @@ -1,188 +1,188 @@ -""" -Reimplementation of the -`Porter stemming algorithm `_ -in Python. - -In my quick tests, this implementation about 3.5 times faster than the -seriously weird Python linked from the official page. -""" - -import re - -# Suffix replacement lists - -_step2list = { - "ational": "ate", - "tional": "tion", - "enci": "ence", - "anci": "ance", - "izer": "ize", - "bli": "ble", - "alli": "al", - "entli": "ent", - "eli": "e", - "ousli": "ous", - "ization": "ize", - "ation": "ate", - "ator": "ate", - "alism": "al", - "iveness": "ive", - "fulness": "ful", - "ousness": "ous", - "aliti": "al", - "iviti": "ive", - "biliti": "ble", - "logi": "log", - } - -_step3list = { - "icate": "ic", - "ative": "", - "alize": "al", - "iciti": "ic", - "ical": "ic", - "ful": "", - "ness": "", - } - - -_cons = "[^aeiou]" -_vowel = "[aeiouy]" -_cons_seq = "[^aeiouy]+" -_vowel_seq = "[aeiou]+" - -# m > 0 -_mgr0 = re.compile("^(" + _cons_seq + ")?" + _vowel_seq + _cons_seq) -# m == 0 -_meq1 = re.compile("^(" + _cons_seq + ")?" + _vowel_seq + _cons_seq + "(" + _vowel_seq + ")?$") -# m > 1 -_mgr1 = re.compile("^(" + _cons_seq + ")?" + _vowel_seq + _cons_seq + _vowel_seq + _cons_seq) -# vowel in stem -_s_v = re.compile("^(" + _cons_seq + ")?" + _vowel) -# ??? -_c_v = re.compile("^" + _cons_seq + _vowel + "[^aeiouwxy]$") - -# Patterns used in the rules - -_ed_ing = re.compile("^(.*)(ed|ing)$") -_at_bl_iz = re.compile("(at|bl|iz)$") -_step1b = re.compile("([^aeiouylsz])\\1$") -_step2 = re.compile("^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$") -_step3 = re.compile("^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$") -_step4_1 = re.compile("^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$") -_step4_2 = re.compile("^(.+?)(s|t)(ion)$") -_step5 = re.compile("^(.+?)e$") - -# Stemming function - -def stem(w): - """Uses the Porter stemming algorithm to remove suffixes from English - words. - - >>> stem("fundamentally") - "fundament" - """ - - if len(w) < 3: return w - - first_is_y = w[0] == "y" - if first_is_y: - w = "Y" + w[1:] - - # Step 1a - if w.endswith("s"): - if w.endswith("sses"): - w = w[:-2] - elif w.endswith("ies"): - w = w[:-2] - elif w[-2] != "s": - w = w[:-1] - - # Step 1b - - if w.endswith("eed"): - s = w[:-3] - if _mgr0.match(s): - w = w[:-1] - else: - m = _ed_ing.match(w) - if m: - stem = m.group(1) - if _s_v.match(stem): - w = stem - if _at_bl_iz.match(w): - w += "e" - elif _step1b.match(w): - w = w[:-1] - elif _c_v.match(w): - w += "e" - - # Step 1c - - if w.endswith("y"): - stem = w[:-1] - if _s_v.match(stem): - w = stem + "i" - - # Step 2 - - m = _step2.match(w) - if m: - stem = m.group(1) - suffix = m.group(2) - if _mgr0.match(stem): - w = stem + _step2list[suffix] - - # Step 3 - - m = _step3.match(w) - if m: - stem = m.group(1) - suffix = m.group(2) - if _mgr0.match(stem): - w = stem + _step3list[suffix] - - # Step 4 - - m = _step4_1.match(w) - if m: - stem = m.group(1) - if _mgr1.match(stem): - w = stem - else: - m = _step4_2.match(w) - if m: - stem = m.group(1) + m.group(2) - if _mgr1.match(stem): - w = stem - - # Step 5 - - m = _step5.match(w) - if m: - stem = m.group(1) - if _mgr1.match(stem) or (_meq1.match(stem) and not _c_v.match(stem)): - w = stem - - if w.endswith("ll") and _mgr1.match(w): - w = w[:-1] - - if first_is_y: - w = "y" + w[1:] - - return w - -if __name__ == '__main__': - print stem("fundamentally") - - - - - - - - - - - - +""" +Reimplementation of the +`Porter stemming algorithm `_ +in Python. + +In my quick tests, this implementation about 3.5 times faster than the +seriously weird Python linked from the official page. +""" + +import re + +# Suffix replacement lists + +_step2list = { + "ational": "ate", + "tional": "tion", + "enci": "ence", + "anci": "ance", + "izer": "ize", + "bli": "ble", + "alli": "al", + "entli": "ent", + "eli": "e", + "ousli": "ous", + "ization": "ize", + "ation": "ate", + "ator": "ate", + "alism": "al", + "iveness": "ive", + "fulness": "ful", + "ousness": "ous", + "aliti": "al", + "iviti": "ive", + "biliti": "ble", + "logi": "log", + } + +_step3list = { + "icate": "ic", + "ative": "", + "alize": "al", + "iciti": "ic", + "ical": "ic", + "ful": "", + "ness": "", + } + + +_cons = "[^aeiou]" +_vowel = "[aeiouy]" +_cons_seq = "[^aeiouy]+" +_vowel_seq = "[aeiou]+" + +# m > 0 +_mgr0 = re.compile("^(" + _cons_seq + ")?" + _vowel_seq + _cons_seq) +# m == 0 +_meq1 = re.compile("^(" + _cons_seq + ")?" + _vowel_seq + _cons_seq + "(" + _vowel_seq + ")?$") +# m > 1 +_mgr1 = re.compile("^(" + _cons_seq + ")?" + _vowel_seq + _cons_seq + _vowel_seq + _cons_seq) +# vowel in stem +_s_v = re.compile("^(" + _cons_seq + ")?" + _vowel) +# ??? +_c_v = re.compile("^" + _cons_seq + _vowel + "[^aeiouwxy]$") + +# Patterns used in the rules + +_ed_ing = re.compile("^(.*)(ed|ing)$") +_at_bl_iz = re.compile("(at|bl|iz)$") +_step1b = re.compile("([^aeiouylsz])\\1$") +_step2 = re.compile("^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$") +_step3 = re.compile("^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$") +_step4_1 = re.compile("^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$") +_step4_2 = re.compile("^(.+?)(s|t)(ion)$") +_step5 = re.compile("^(.+?)e$") + +# Stemming function + +def stem(w): + """Uses the Porter stemming algorithm to remove suffixes from English + words. + + >>> stem("fundamentally") + "fundament" + """ + + if len(w) < 3: return w + + first_is_y = w[0] == "y" + if first_is_y: + w = "Y" + w[1:] + + # Step 1a + if w.endswith("s"): + if w.endswith("sses"): + w = w[:-2] + elif w.endswith("ies"): + w = w[:-2] + elif w[-2] != "s": + w = w[:-1] + + # Step 1b + + if w.endswith("eed"): + s = w[:-3] + if _mgr0.match(s): + w = w[:-1] + else: + m = _ed_ing.match(w) + if m: + stem = m.group(1) + if _s_v.match(stem): + w = stem + if _at_bl_iz.match(w): + w += "e" + elif _step1b.match(w): + w = w[:-1] + elif _c_v.match(w): + w += "e" + + # Step 1c + + if w.endswith("y"): + stem = w[:-1] + if _s_v.match(stem): + w = stem + "i" + + # Step 2 + + m = _step2.match(w) + if m: + stem = m.group(1) + suffix = m.group(2) + if _mgr0.match(stem): + w = stem + _step2list[suffix] + + # Step 3 + + m = _step3.match(w) + if m: + stem = m.group(1) + suffix = m.group(2) + if _mgr0.match(stem): + w = stem + _step3list[suffix] + + # Step 4 + + m = _step4_1.match(w) + if m: + stem = m.group(1) + if _mgr1.match(stem): + w = stem + else: + m = _step4_2.match(w) + if m: + stem = m.group(1) + m.group(2) + if _mgr1.match(stem): + w = stem + + # Step 5 + + m = _step5.match(w) + if m: + stem = m.group(1) + if _mgr1.match(stem) or (_meq1.match(stem) and not _c_v.match(stem)): + w = stem + + if w.endswith("ll") and _mgr1.match(w): + w = w[:-1] + + if first_is_y: + w = "y" + w[1:] + + return w + +if __name__ == '__main__': + print stem("fundamentally") + + + + + + + + + + + + diff --git a/lib/whoosh/lang/wordnet.py b/lib/whoosh/lang/wordnet.py index 0d10b953..f3e70bbd 100644 --- a/lib/whoosh/lang/wordnet.py +++ b/lib/whoosh/lang/wordnet.py @@ -1,259 +1,259 @@ -#=============================================================================== -# Copyright 2009 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -"""This module contains low-level functions and a high-level class for parsing -the prolog file "wn_s.pl" from the WordNet prolog download -into an object suitable for looking up synonyms and performing query expansion. - -http://wordnetcode.princeton.edu/3.0/WNprolog-3.0.tar.gz -""" - -from collections import defaultdict - -from whoosh.fields import Schema, ID, STORED -from whoosh.index import Index - - -def parse_file(f): - """Parses the WordNet wn_s.pl prolog file and returns two dictionaries: - word2nums and num2words. - """ - - word2nums = defaultdict(list) - num2words = defaultdict(list) - - for line in f: - if not line.startswith("s("): - continue - - line = line[2:] - num = int(line[:line.find(",")]) - qt = line.find("'") - line = line[qt+1:] - qt = line.find("'") - word = line[:qt].lower() - - if not word.isalpha(): - continue - - word2nums[word].append(num) - num2words[num].append(word) - - return word2nums, num2words - - -def make_index(storage, indexname, word2nums, num2words): - """Creates a Whoosh index in the given storage object containing - synonyms taken from word2nums and num2words. Returns the Index - object. - """ - - schema = Schema(word=ID, syns=STORED) - ix = storage.create_index(schema, indexname=indexname) - w = ix.writer() - for word in word2nums.iterkeys(): - syns = synonyms(word2nums, num2words, word) - w.add_document(word=unicode(word), syns=syns) - w.commit() - return ix - - -def synonyms(word2nums, num2words, word): - """Uses the word2nums and num2words dicts to look up synonyms - for the given word. Returns a list of synonym strings. - """ - - keys = word2nums[word] - syns = set() - for key in keys: - syns = syns.union(num2words[key]) - - if word in syns: - syns.remove(word) - return sorted(syns) - - -class Thesaurus(object): - """Represents the WordNet synonym database, either loaded into memory - from the wn_s.pl Prolog file, or stored on disk in a Whoosh index. - - This class allows you to parse the prolog file "wn_s.pl" from the WordNet prolog - download into an object suitable for looking up synonyms and performing query - expansion. - - http://wordnetcode.princeton.edu/3.0/WNprolog-3.0.tar.gz - - To load a Thesaurus object from the wn_s.pl file... - - >>> t = Thesaurus.from_filename("wn_s.pl") - - To save the in-memory Thesaurus to a Whoosh index... - - >>> from whoosh.filedb.filestore import FileStorage - >>> fs = FileStorage("index") - >>> t.to_storage(fs) - - To load a Thesaurus object from a Whoosh index... - - >>> t = Thesaurus.from_storage(fs) - - The Thesaurus object is thus usable in two ways: - - * Parse the wn_s.pl file into memory (Thesaurus.from_*) and then look up - synonyms in memory. This has a startup cost for parsing the file, and uses - quite a bit of memory to store two large dictionaries, however synonym - look-ups are very fast. - - * Parse the wn_s.pl file into memory (Thesaurus.from_filename) then save it to - an index (to_storage). From then on, open the thesaurus from the saved - index (Thesaurus.from_storage). This has a large cost for storing the index, - but after that it is faster to open the Thesaurus (than re-parsing the file) - but slightly slower to look up synonyms. - - Here are timings for various tasks on my (fast) Windows machine, which might - give an idea of relative costs for in-memory vs. on-disk. - - ================================================ ================ - Task Approx. time (s) - ================================================ ================ - Parsing the wn_s.pl file 1.045 - Saving to an on-disk index 13.084 - Loading from an on-disk index 0.082 - Look up synonyms for "light" (in memory) 0.0011 - Look up synonyms for "light" (loaded from disk) 0.0028 - ================================================ ================ - - Basically, if you can afford spending the memory necessary to parse the - Thesaurus and then cache it, it's faster. Otherwise, use an on-disk index. - """ - - def __init__(self): - self.w2n = None - self.n2w = None - self.searcher = None - - @classmethod - def from_file(cls, fileobj): - """Creates a Thesaurus object from the given file-like object, which should - contain the WordNet wn_s.pl file. - - >>> f = open("wn_s.pl") - >>> t = Thesaurus.from_file(f) - >>> t.synonyms("hail") - ['acclaim', 'come', 'herald'] - """ - - thes = cls() - thes.w2n, thes.n2w = parse_file(fileobj) - return thes - - @classmethod - def from_filename(cls, filename): - """Creates a Thesaurus object from the given filename, which should - contain the WordNet wn_s.pl file. - - >>> t = Thesaurus.from_filename("wn_s.pl") - >>> t.synonyms("hail") - ['acclaim', 'come', 'herald'] - """ - - f = open(filename, "rb") - try: - return cls.from_file(f) - finally: - f.close() - - @classmethod - def from_storage(cls, storage, indexname="THES"): - """Creates a Thesaurus object from the given storage object, - which should contain an index created by Thesaurus.to_storage(). - - >>> from whoosh.filedb.filestore import FileStorage - >>> fs = FileStorage("index") - >>> t = Thesaurus.from_storage(fs) - >>> t.synonyms("hail") - ['acclaim', 'come', 'herald'] - - :param storage: A :class:`whoosh.store.Storage` object from - which to load the index. - :param indexname: A name for the index. This allows you to - store multiple indexes in the same storage object. - """ - - thes = cls() - index = storage.open_index(indexname=indexname) - thes.searcher = index.searcher() - return thes - - def to_storage(self, storage, indexname="THES"): - """Creates am index in the given storage object from the - synonyms loaded from a WordNet file. - - >>> from whoosh.filedb.filestore import FileStorage - >>> fs = FileStorage("index") - >>> t = Thesaurus.from_filename("wn_s.pl") - >>> t.to_storage(fs) - - :param storage: A :class:`whoosh.store.Storage` object in - which to save the index. - :param indexname: A name for the index. This allows you to - store multiple indexes in the same storage object. - """ - - if not self.w2n or not self.n2w: - raise Exception("No synonyms loaded") - make_index(storage, indexname, self.w2n, self.n2w) - - def synonyms(self, word): - """Returns a list of synonyms for the given word. - - >>> thesaurus.synonyms("hail") - ['acclaim', 'come', 'herald'] - """ - - word = word.lower() - if self.searcher: - return self.searcher.document(word=word)["syns"] - else: - return synonyms(self.w2n, self.n2w, word) - - -if __name__ == "__main__": - from time import clock - from whoosh.filedb.filestore import FileStorage - st = FileStorage("c:/testindex") - -# t = clock() -# th = Thesaurus.from_filename("c:/wordnet/wn_s.pl") -# print clock() - t -# -# t = clock() -# th.to_storage(st) -# print clock() - t -# -# t = clock() -# print th.synonyms("light") -# print clock() - t - - t = clock() - th = Thesaurus.from_storage(st) - print clock() - t - - t = clock() - print th.synonyms("hail") - print clock() - t - +#=============================================================================== +# Copyright 2009 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +"""This module contains low-level functions and a high-level class for parsing +the prolog file "wn_s.pl" from the WordNet prolog download +into an object suitable for looking up synonyms and performing query expansion. + +http://wordnetcode.princeton.edu/3.0/WNprolog-3.0.tar.gz +""" + +from collections import defaultdict + +from whoosh.fields import Schema, ID, STORED +from whoosh.index import Index + + +def parse_file(f): + """Parses the WordNet wn_s.pl prolog file and returns two dictionaries: + word2nums and num2words. + """ + + word2nums = defaultdict(list) + num2words = defaultdict(list) + + for line in f: + if not line.startswith("s("): + continue + + line = line[2:] + num = int(line[:line.find(",")]) + qt = line.find("'") + line = line[qt+1:] + qt = line.find("'") + word = line[:qt].lower() + + if not word.isalpha(): + continue + + word2nums[word].append(num) + num2words[num].append(word) + + return word2nums, num2words + + +def make_index(storage, indexname, word2nums, num2words): + """Creates a Whoosh index in the given storage object containing + synonyms taken from word2nums and num2words. Returns the Index + object. + """ + + schema = Schema(word=ID, syns=STORED) + ix = storage.create_index(schema, indexname=indexname) + w = ix.writer() + for word in word2nums.iterkeys(): + syns = synonyms(word2nums, num2words, word) + w.add_document(word=unicode(word), syns=syns) + w.commit() + return ix + + +def synonyms(word2nums, num2words, word): + """Uses the word2nums and num2words dicts to look up synonyms + for the given word. Returns a list of synonym strings. + """ + + keys = word2nums[word] + syns = set() + for key in keys: + syns = syns.union(num2words[key]) + + if word in syns: + syns.remove(word) + return sorted(syns) + + +class Thesaurus(object): + """Represents the WordNet synonym database, either loaded into memory + from the wn_s.pl Prolog file, or stored on disk in a Whoosh index. + + This class allows you to parse the prolog file "wn_s.pl" from the WordNet prolog + download into an object suitable for looking up synonyms and performing query + expansion. + + http://wordnetcode.princeton.edu/3.0/WNprolog-3.0.tar.gz + + To load a Thesaurus object from the wn_s.pl file... + + >>> t = Thesaurus.from_filename("wn_s.pl") + + To save the in-memory Thesaurus to a Whoosh index... + + >>> from whoosh.filedb.filestore import FileStorage + >>> fs = FileStorage("index") + >>> t.to_storage(fs) + + To load a Thesaurus object from a Whoosh index... + + >>> t = Thesaurus.from_storage(fs) + + The Thesaurus object is thus usable in two ways: + + * Parse the wn_s.pl file into memory (Thesaurus.from_*) and then look up + synonyms in memory. This has a startup cost for parsing the file, and uses + quite a bit of memory to store two large dictionaries, however synonym + look-ups are very fast. + + * Parse the wn_s.pl file into memory (Thesaurus.from_filename) then save it to + an index (to_storage). From then on, open the thesaurus from the saved + index (Thesaurus.from_storage). This has a large cost for storing the index, + but after that it is faster to open the Thesaurus (than re-parsing the file) + but slightly slower to look up synonyms. + + Here are timings for various tasks on my (fast) Windows machine, which might + give an idea of relative costs for in-memory vs. on-disk. + + ================================================ ================ + Task Approx. time (s) + ================================================ ================ + Parsing the wn_s.pl file 1.045 + Saving to an on-disk index 13.084 + Loading from an on-disk index 0.082 + Look up synonyms for "light" (in memory) 0.0011 + Look up synonyms for "light" (loaded from disk) 0.0028 + ================================================ ================ + + Basically, if you can afford spending the memory necessary to parse the + Thesaurus and then cache it, it's faster. Otherwise, use an on-disk index. + """ + + def __init__(self): + self.w2n = None + self.n2w = None + self.searcher = None + + @classmethod + def from_file(cls, fileobj): + """Creates a Thesaurus object from the given file-like object, which should + contain the WordNet wn_s.pl file. + + >>> f = open("wn_s.pl") + >>> t = Thesaurus.from_file(f) + >>> t.synonyms("hail") + ['acclaim', 'come', 'herald'] + """ + + thes = cls() + thes.w2n, thes.n2w = parse_file(fileobj) + return thes + + @classmethod + def from_filename(cls, filename): + """Creates a Thesaurus object from the given filename, which should + contain the WordNet wn_s.pl file. + + >>> t = Thesaurus.from_filename("wn_s.pl") + >>> t.synonyms("hail") + ['acclaim', 'come', 'herald'] + """ + + f = open(filename, "rb") + try: + return cls.from_file(f) + finally: + f.close() + + @classmethod + def from_storage(cls, storage, indexname="THES"): + """Creates a Thesaurus object from the given storage object, + which should contain an index created by Thesaurus.to_storage(). + + >>> from whoosh.filedb.filestore import FileStorage + >>> fs = FileStorage("index") + >>> t = Thesaurus.from_storage(fs) + >>> t.synonyms("hail") + ['acclaim', 'come', 'herald'] + + :param storage: A :class:`whoosh.store.Storage` object from + which to load the index. + :param indexname: A name for the index. This allows you to + store multiple indexes in the same storage object. + """ + + thes = cls() + index = storage.open_index(indexname=indexname) + thes.searcher = index.searcher() + return thes + + def to_storage(self, storage, indexname="THES"): + """Creates am index in the given storage object from the + synonyms loaded from a WordNet file. + + >>> from whoosh.filedb.filestore import FileStorage + >>> fs = FileStorage("index") + >>> t = Thesaurus.from_filename("wn_s.pl") + >>> t.to_storage(fs) + + :param storage: A :class:`whoosh.store.Storage` object in + which to save the index. + :param indexname: A name for the index. This allows you to + store multiple indexes in the same storage object. + """ + + if not self.w2n or not self.n2w: + raise Exception("No synonyms loaded") + make_index(storage, indexname, self.w2n, self.n2w) + + def synonyms(self, word): + """Returns a list of synonyms for the given word. + + >>> thesaurus.synonyms("hail") + ['acclaim', 'come', 'herald'] + """ + + word = word.lower() + if self.searcher: + return self.searcher.document(word=word)["syns"] + else: + return synonyms(self.w2n, self.n2w, word) + + +if __name__ == "__main__": + from time import clock + from whoosh.filedb.filestore import FileStorage + st = FileStorage("c:/testindex") + +# t = clock() +# th = Thesaurus.from_filename("c:/wordnet/wn_s.pl") +# print clock() - t +# +# t = clock() +# th.to_storage(st) +# print clock() - t +# +# t = clock() +# print th.synonyms("light") +# print clock() - t + + t = clock() + th = Thesaurus.from_storage(st) + print clock() - t + + t = clock() + print th.synonyms("hail") + print clock() - t + \ No newline at end of file diff --git a/lib/whoosh/matching.py b/lib/whoosh/matching.py index cada6764..1d2fc906 100644 --- a/lib/whoosh/matching.py +++ b/lib/whoosh/matching.py @@ -1,1369 +1,1369 @@ -#=============================================================================== -# Copyright 2010 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -from bisect import bisect_left, bisect_right, insort -from itertools import izip, repeat - - -""" -This module contains "matcher" classes. Matchers deal with posting lists. The -most basic matcher, which reads the list of postings for a term, will be -provided by the backend implementation (for example, -``whoosh.filedb.filepostings.FilePostingReader``). The classes in this module -provide additional functionality, such as combining the results of two -matchers, or modifying the results of a matcher. - -You do not need to deal with the classes in this module unless you need to -write your own Matcher implementation to provide some new functionality. These -classes are not instantiated by the user. They are usually created by a -:class:`~whoosh.query.Query` object's ``matcher()`` method, which returns the -appropriate matcher to implement the query (for example, the ``Or`` query's -``matcher()`` method returns a ``UnionMatcher`` object). - -Certain backends support "quality" optimizations. These backends have the -ability to skip ahead if it knows the current block of postings can't -contribute to the top N documents. If the matcher tree and backend support -these optimizations, the matcher's ``supports_quality()`` method will return -``True``. -""" - - -class ReadTooFar(Exception): - """Raised when next() or skip_to() is called on an inactive matchers. - """ - - -class NoQualityAvailable(Exception): - """Raised when quality methods are called on a matcher that does not - support quality-based optimizations. - """ - - -# Matchers - -class Matcher(object): - """Base class for all matchers. - """ - - def is_active(self): - """Returns True if this matcher is still "active", that is, it has not - yet reached the end of the posting list. - """ - - raise NotImplementedError - - def replace(self): - """Returns a possibly-simplified version of this matcher. For example, - if one of the children of a UnionMatcher is no longer active, calling - this method on the UnionMatcher will return the other child. - """ - - return self - - def copy(self): - """Returns a copy of this matcher. - """ - - raise NotImplementedError - - def depth(self): - """Returns the depth of the tree under this matcher, or 0 if this - matcher does not have any children. - """ - - return 0 - - def supports_quality(self): - """Returns True if this matcher supports the use of ``quality`` and - ``block_quality``. - """ - - return False - - def quality(self): - """Returns a quality measurement of the current posting, according to - the current weighting algorithm. Raises ``NoQualityAvailable`` if the - matcher or weighting do not support quality measurements. - """ - - raise NoQualityAvailable - - def block_quality(self): - """Returns a quality measurement of the current block of postings, - according to the current weighting algorithm. Raises - ``NoQualityAvailable`` if the matcher or weighting do not support - quality measurements. - """ - - raise NoQualityAvailable - - def id(self): - """Returns the ID of the current posting. - """ - - raise NotImplementedError - - def all_ids(self): - """Returns a generator of all IDs in the matcher. - - What this method returns for a matcher that has already read some - postings (whether it only yields the remaining postings or all postings - from the beginning) is undefined, so it's best to only use this method - on fresh matchers. - """ - - i = 0 - while self.is_active(): - yield self.id() - self.next() - i += 1 - if i == 10: - self = self.replace() - i = 0 - - def all_items(self): - """Returns a generator of all (ID, encoded value) pairs in the matcher. - - What this method returns for a matcher that has already read some - postings (whether it only yields the remaining postings or all postings - from the beginning) is undefined, so it's best to only use this method - on fresh matchers. - """ - - i = 0 - while self.is_active(): - yield (self.id(), self.value()) - self.next() - i += 1 - if i == 10: - self = self.replace() - i = 0 - - def items_as(self, astype): - """Returns a generator of all (ID, decoded value) pairs in the matcher. - - What this method returns for a matcher that has already read some - postings (whether it only yields the remaining postings or all postings - from the beginning) is undefined, so it's best to only use this method - on fresh matchers. - """ - - while self.is_active(): - yield (self.id(), self.value_as(astype)) - - def value(self): - """Returns the encoded value of the current posting. - """ - - raise NotImplementedError - - def supports(self, astype): - """Returns True if the field's format supports the named data type, - for example 'frequency' or 'characters'. - """ - - raise NotImplementedError("supports not implemented in %s" % self.__class__) - - def value_as(self, astype): - """Returns the value(s) of the current posting as the given type. - """ - - raise NotImplementedError("value_as not implemented in %s" % self.__class__) - - def spans(self): - """Returns a list of :class:`whoosh.spans.Span` objects for the matches - in this document. Raises an exception if the field being searched does - not store positions. - """ - - from whoosh.spans import Span - if self.supports("characters"): - return [Span(pos, startchar=startchar, endchar=endchar) - for pos, startchar, endchar in self.value_as("characters")] - elif self.supports("positions"): - return [Span(pos) for pos in self.value_as("positions")] - else: - raise Exception("Field does not support spans") - - def skip_to(self, id): - """Moves this matcher to the first posting with an ID equal to or - greater than the given ID. - """ - - while self.is_active() and self.id() < id: - self.next() - - def skip_to_quality(self, minquality): - """Moves this matcher to the next block with greater than the given - minimum quality value. - """ - - raise NotImplementedError - - def next(self): - """Moves this matcher to the next posting. - """ - - raise NotImplementedError(self.__class__.__name__) - - def weight(self): - """Returns the weight of the current posting. - """ - - return self.value_as("weight") - - def score(self): - """Returns the score of the current posting. - """ - - raise NotImplementedError - - -class NullMatcher(Matcher): - """Matcher with no postings which is never active. - """ - - def is_active(self): - return False - - def all_ids(self): - return [] - - def copy(self): - return self - - -class ListMatcher(Matcher): - """Synthetic matcher backed by a list of IDs. - """ - - def __init__(self, ids, weights=None, values=None, format=None, - scorer=None, position=0, all_weights=None): - """ - :param ids: a list of doc IDs. - :param weights: a list of weights corresponding to the list of IDs. - If this argument is not supplied, a list of 1.0 values is used. - :param values: a list of encoded values corresponding to the list of - IDs. - :param format: a :class:`whoosh.formats.Format` object representing the - format of the field. - :param scorer: a :class:`whoosh.scoring.BaseScorer` object for scoring - the postings. - """ - - self._ids = ids - self._weights = weights - self._all_weights = all_weights - self._values = values - self._i = position - self._format = format - self._scorer = scorer - - def __repr__(self): - return "%s(%r, %r, %r, %d)" % (self.__class__.__name__, self._ids, - self._weights, self._values, self._i) - - def is_active(self): - return self._i < len(self._ids) - - def copy(self): - return self.__class__(self._ids, self._weights, self._values, - self._format, self._scorer, self._i) - - def supports_quality(self): - return self._scorer is not None - - def quality(self): - return self._scorer.quality(self) - - def id(self): - return self._ids[self._i] - - def all_ids(self): - return iter(self._ids) - - def all_items(self): - values = self._values - if values is None: - values = repeat('') - - return izip(self._ids, values) - - def value(self): - if self._values: - return self._values[self._i] - else: - return '' - - def value_as(self, astype): - decoder = self._format.decoder(astype) - return decoder(self.value()) - - def supports(self, astype): - return self._format.supports(astype) - - def next(self): - self._i += 1 - - def weight(self): - if self._all_weights: - return self._all_weights - elif self._weights: - return self._weights[self._i] - else: - return 1.0 - - def score(self): - if self._scorer: - return self._scorer.score(self) - else: - return self.weight() - - -class WrappingMatcher(Matcher): - """Base class for matchers that wrap sub-matchers. - """ - - def __init__(self, child, boost=1.0): - self.child = child - self.boost = boost - - def __repr__(self): - return "%s(%r, boost=%s)" % (self.__class__.__name__, self.child, self.boost) - - def copy(self): - kwargs = {} - if hasattr(self, "boost"): - kwargs["boost"] = self.boost - return self.__class__(self.child.copy(), **kwargs) - - def depth(self): - return 1 + self.child.depth() - - def _replacement(self, newchild): - return self.__class__(newchild, boost=self.boost) - - def replace(self): - r = self.child.replace() - if not r.is_active(): - return NullMatcher() - if r is not self.child: - try: - return self._replacement(r) - except TypeError, e: - raise TypeError("Class %s got exception %s trying " - "to replace itself" % (self.__class__, e)) - else: - return self - - def id(self): - return self.child.id() - - def all_ids(self): - return self.child.all_ids() - - def is_active(self): - return self.child.is_active() - - def supports(self, astype): - return self.child.supports(astype) - - def value(self): - return self.child.value() - - def value_as(self, astype): - return self.child.value_as(astype) - - def spans(self): - return self.child.spans() - - def skip_to(self, id): - return self.child.skip_to(id) - - def next(self): - self.child.next() - - def supports_quality(self): - return self.child.supports_quality() - - def skip_to_quality(self, minquality): - return self.child.skip_to_quality(minquality/self.boost) - - def quality(self): - return self.child.quality() * self.boost - - def block_quality(self): - return self.child.block_quality() * self.boost - - def weight(self): - return self.child.weight() * self.boost - - def score(self): - return self.child.score() * self.boost - - -class MultiMatcher(Matcher): - """Serializes the results of a list of sub-matchers. - """ - - def __init__(self, matchers, idoffsets, current=0): - """ - :param matchers: a list of Matcher objects. - :param idoffsets: a list of offsets corresponding to items in the - ``matchers`` list. - """ - - self.matchers = matchers - self.offsets = idoffsets - self.current = current - self._next_matcher() - - def __repr__(self): - return "%s(%r, %r, current=%s)" % (self.__class__.__name__, - self.matchers, self.offsets, - self.current) - - def is_active(self): - return self.current < len(self.matchers) - - def _next_matcher(self): - matchers = self.matchers - while self.current < len(matchers) and not matchers[self.current].is_active(): - self.current += 1 - - def copy(self): - return self.__class__([mr.copy() for mr in self.matchers[self.current:]], - self.offsets[self.current:], current=self.current) - - def depth(self): - if self.is_active(): - return 1 + max(mr.depth() for mr in self.matchers[self.current:]) - else: - return 0 - - def replace(self): - if not self.is_active(): - return NullMatcher() - # TODO: Possible optimization: if the last matcher is current, replace - # this with the last matcher, but wrap it with a matcher that adds the - # offset. Have to check whether that's actually faster, though. - return self - - def id(self): - current = self.current - return self.matchers[current].id() + self.offsets[current] - - def all_ids(self): - offsets = self.offsets - for i, mr in enumerate(self.matchers): - for id in mr.all_ids(): - yield id + offsets[i] - - def spans(self): - return self.matchers[self.current].spans() - - def supports(self, astype): - return self.matchers[self.current].supports(astype) - - def value(self): - return self.matchers[self.current].value() - - def value_as(self, astype): - return self.matchers[self.current].value_as(astype) - - def next(self): - if not self.is_active(): raise ReadTooFar - - self.matchers[self.current].next() - if not self.matchers[self.current].is_active(): - self._next_matcher() - - def skip_to(self, id): - if not self.is_active(): raise ReadTooFar - if id <= self.id(): return - - matchers = self.matchers - offsets = self.offsets - r = False - - while self.current < len(matchers) and id > self.id(): - mr = matchers[self.current] - sr = mr.skip_to(id - offsets[self.current]) - r = sr or r - if mr.is_active(): - break - - self._next_matcher() - - return r - - def supports_quality(self): - return all(mr.supports_quality() for mr in self.matchers[self.current:]) - - def quality(self): - return self.matchers[self.current].quality() - - def block_quality(self): - return self.matchers[self.current].block_quality() - - def weight(self): - return self.matchers[self.current].weight() - - def score(self): - return self.matchers[self.current].score() - - -class ExcludeMatcher(WrappingMatcher): - """Excludes a list of IDs from the postings returned by the wrapped - matcher. - """ - - def __init__(self, child, excluded, boost=1.0): - super(ExcludeMatcher, self).__init__(child) - self.excluded = excluded - self.boost = boost - self._find_next() - - def __repr__(self): - return "%s(%r, %r, boost=%s)" % (self.__class__.__name__, self.child, - self.excluded, self.boost) - - def copy(self): - return self.__class__(self.child.copy(), self.excluded, boost=self.boost) - - def _replacement(self, newchild): - return self.__class__(newchild, self.excluded, boost=self.boost) - - def _find_next(self): - child = self.child - excluded = self.excluded - r = False - while child.is_active() and child.id() in excluded: - r = child.next() or r - return r - - def next(self): - self.child.next() - self._find_next() - - def skip_to(self, id): - self.child.skip_to(id) - self._find_next() - - def all_ids(self): - excluded = self.excluded - return (id for id in self.child.all_ids() if id not in excluded) - - def all_items(self): - excluded = self.excluded - return (item for item in self.child.all_items() - if item[0] not in excluded) - - -class BiMatcher(Matcher): - """Base class for matchers that combine the results of two sub-matchers in - some way. - """ - - def __init__(self, a, b): - super(BiMatcher, self).__init__() - self.a = a - self.b = b - - def __repr__(self): - return "%s(%r, %r)" % (self.__class__.__name__, self.a, self.b) - - def copy(self): - return self.__class__(self.a.copy(), self.b.copy()) - - def depth(self): - return 1 + max(self.a.depth(), self.b.depth()) - - def skip_to(self, id): - if not self.is_active(): raise ReadTooFar - ra = self.a.skip_to(id) - rb = self.b.skip_to(id) - return ra or rb - - def supports_quality(self): - return self.a.supports_quality() and self.b.supports_quality() - - def supports(self, astype): - return self.a.supports(astype) and self.b.supports(astype) - - -class AdditiveBiMatcher(BiMatcher): - """Base class for binary matchers where the scores of the sub-matchers are - added together. - """ - - def quality(self): - q = 0.0 - if self.a.is_active(): q += self.a.quality() - if self.b.is_active(): q += self.b.quality() - return q - - def block_quality(self): - bq = 0.0 - if self.a.is_active(): bq += self.a.block_quality() - if self.b.is_active(): bq += self.b.block_quality() - return bq - - def weight(self): - return (self.a.weight() + self.b.weight()) - - def score(self): - return (self.a.score() + self.b.score()) - - -class UnionMatcher(AdditiveBiMatcher): - """Matches the union (OR) of the postings in the two sub-matchers. - """ - - def replace(self): - a = self.a.replace() - b = self.b.replace() - - a_active = a.is_active() - b_active = b.is_active() - if not (a_active or b_active): return NullMatcher() - if not a_active: - return b - if not b_active: - return a - - if a is not self.a or b is not self.b: - return self.__class__(a, b) - return self - - def is_active(self): - return self.a.is_active() or self.b.is_active() - - def skip_to(self, id): - ra = rb = False - if self.a.is_active(): - ra = self.a.skip_to(id) - if self.b.is_active(): - rb = self.b.skip_to(id) - return ra or rb - - def id(self): - a = self.a - b = self.b - if not a.is_active(): return b.id() - if not b.is_active(): return a.id() - return min(a.id(), b.id()) - - # Using sets is faster in most cases, but could potentially use a lot of - # memory - def all_ids(self): - return iter(sorted(set(self.a.all_ids()) | set(self.b.all_ids()))) - - def next(self): - a = self.a - b = self.b - a_active = a.is_active() - b_active = b.is_active() - - # Shortcut when one matcher is inactive - if not (a_active or b_active): - raise ReadTooFar - elif not a_active: - return b.next() - elif not b_active: - return a.next() - - a_id = a.id() - b_id = b.id() - ar = br = None - if a_id <= b_id: ar = a.next() - if b_id <= a_id: br = b.next() - return ar or br - - def spans(self): - # Returns the branch that currently matches, or None if both branches - # currently match - if not self.a.is_active(): return self.b.spans() - if not self.b.is_active(): return self.a.spans() - id_a = self.a.id() - id_b = self.b.id() - if id_a < id_b: - return self.a.spans() - elif id_b < id_a: - return self.b.spans() - else: - return sorted(set(self.a.spans()) | set(self.b.spans())) - - def score(self): - a = self.a - b = self.b - - if not a.is_active(): return b.score() - if not b.is_active(): return a.score() - - id_a = a.id() - id_b = b.id() - if id_a < id_b: - return a.score() - elif id_b < id_a: - return b.score() - else: - return (a.score() + b.score()) - - def skip_to_quality(self, minquality): - a = self.a - b = self.b - minquality = minquality - - # Short circuit if one matcher is inactive - if not a.is_active(): - sk = b.skip_to_quality(minquality) - return sk - elif not b.is_active(): - return a.skip_to_quality(minquality) - - skipped = 0 - aq = a.block_quality() - bq = b.block_quality() - while a.is_active() and b.is_active() and aq + bq <= minquality: - if aq < bq: - skipped += a.skip_to_quality(minquality - bq) - aq = a.block_quality() - else: - skipped += b.skip_to_quality(minquality - aq) - bq = b.block_quality() - - return skipped - - -class DisjunctionMaxMatcher(UnionMatcher): - """Matches the union (OR) of two sub-matchers. Where both sub-matchers - match the same posting, returns the weight/score of the higher-scoring - posting. - """ - - # TODO: this class inherits from AdditiveBiMatcher (through UnionMatcher) - # but it does not add the scores of the sub-matchers together (it - # overrides all methods that perform addition). Need to clean up the - # inheritance. - - def __init__(self, a, b, tiebreak=0.0): - super(DisjunctionMaxMatcher, self).__init__(a, b) - self.tiebreak = tiebreak - - def copy(self): - return self.__class__(self.a.copy(), self.b.copy(), - tiebreak=self.tiebreak) - - def score(self): - return max(self.a.score(), self.b.score()) - - def quality(self): - return max(self.a.quality(), self.b.quality()) - - def block_quality(self): - return max(self.a.block_quality(), self.b.block_quality()) - - def skip_to_quality(self, minquality): - a = self.a - b = self.b - minquality = minquality - - # Short circuit if one matcher is inactive - if not a.is_active(): - sk = b.skip_to_quality(minquality) - return sk - elif not b.is_active(): - return a.skip_to_quality(minquality) - - skipped = 0 - aq = a.block_quality() - bq = b.block_quality() - while a.is_active() and b.is_active() and max(aq, bq) <= minquality: - if aq <= minquality: - skipped += a.skip_to_quality(minquality) - aq = a.block_quality() - if bq <= minquality: - skipped += b.skip_to_quality(minquality) - bq = b.block_quality() - return skipped - - -class IntersectionMatcher(AdditiveBiMatcher): - """Matches the intersection (AND) of the postings in the two sub-matchers. - """ - - def __init__(self, a, b): - super(IntersectionMatcher, self).__init__(a, b) - if (self.a.is_active() - and self.b.is_active() - and self.a.id() != self.b.id()): - self._find_next() - - def replace(self): - a = self.a.replace() - b = self.b.replace() - - a_active = a - b_active = b.is_active() - if not (a_active and b_active): return NullMatcher() - - if a is not self.a or b is not self.b: - return self.__class__(a, b) - return self - - def is_active(self): - return self.a.is_active() and self.b.is_active() - - def _find_next(self): - a = self.a - b = self.b - a_id = a.id() - b_id = b.id() - assert a_id != b_id - r = False - - while a.is_active() and b.is_active() and a_id != b_id: - if a_id < b_id: - ra = a.skip_to(b_id) - if not a.is_active(): return - r = r or ra - a_id = a.id() - else: - rb = b.skip_to(a_id) - if not b.is_active(): return - r = r or rb - b_id = b.id() - return r - - def id(self): - return self.a.id() - - # Using sets is faster in some cases, but could potentially use a lot of - # memory - def all_ids(self): - return iter(sorted(set(self.a.all_ids()) & set(self.b.all_ids()))) - - def skip_to(self, id): - if not self.is_active(): raise ReadTooFar - ra = self.a.skip_to(id) - rb = self.b.skip_to(id) - if self.is_active(): - rn = False - if self.a.id() != self.b.id(): - rn = self._find_next() - return ra or rb or rn - - def skip_to_quality(self, minquality): - a = self.a - b = self.b - minquality = minquality - - skipped = 0 - aq = a.block_quality() - bq = b.block_quality() - while a.is_active() and b.is_active() and aq + bq <= minquality: - if aq < bq: - skipped += a.skip_to_quality(minquality - bq) - else: - skipped += b.skip_to_quality(minquality - aq) - if a.id() != b.id(): - self._find_next() - aq = a.block_quality() - bq = b.block_quality() - return skipped - - def next(self): - if not self.is_active(): raise ReadTooFar - - # We must assume that the ids are equal whenever next() is called (they - # should have been made equal by _find_next), so advance them both - ar = self.a.next() - if self.is_active(): - nr = self._find_next() - return ar or nr - - def spans(self): - return sorted(set(self.a.spans()) | set(self.b.spans())) - - -class AndNotMatcher(BiMatcher): - """Matches the postings in the first sub-matcher that are NOT present in - the second sub-matcher. - """ - - def __init__(self, a, b): - super(AndNotMatcher, self).__init__(a, b) - if (self.a.is_active() - and self.b.is_active() - and self.a.id() != self.b.id()): - self._find_next() - - def is_active(self): - return self.a.is_active() - - def _find_next(self): - pos = self.a - neg = self.b - if not neg.is_active(): return - pos_id = pos.id() - r = False - - if neg.id() < pos_id: - neg.skip_to(pos_id) - - while neg.is_active() and pos_id == neg.id(): - nr = pos.next() - r = r or nr - pos_id = pos.id() - neg.skip_to(pos_id) - - return r - - def replace(self): - if not self.a.is_active(): return NullMatcher() - if not self.b.is_active(): return self.a - return self - - def quality(self): - return self.a.quality() - - def block_quality(self): - return self.a.block_quality() - - def skip_to_quality(self, minquality): - skipped = self.a.skip_to_quality(minquality) - self._find_next() - return skipped - - def id(self): - return self.a.id() - - def all_ids(self): - return iter(sorted(set(self.a.all_ids()) - set(self.b.all_ids()))) - - def next(self): - if not self.a.is_active(): raise ReadTooFar - ar = self.a.next() - nr = False - if self.b.is_active(): - nr = self._find_next() - return ar or nr - - def skip_to(self, id): - if not self.a.is_active(): raise ReadTooFar - if id < self.a.id(): return - - self.a.skip_to(id) - if self.b.is_active(): - self.b.skip_to(id) - self._find_next() - - def weight(self): - return self.a.weight() - - def score(self): - return self.a.score() - - def supports(self, astype): - return self.a.supports(astype) - - def value(self): - return self.a.value() - - def value_as(self, astype): - return self.a.value_as(astype) - - -class InverseMatcher(WrappingMatcher): - """Synthetic matcher, generates postings that are NOT present in the - wrapped matcher. - """ - - def __init__(self, child, limit, missing=None, weight=1.0): - super(InverseMatcher, self).__init__(child) - self.limit = limit - self._weight = weight - self.missing = missing or (lambda id: False) - self._id = 0 - self._find_next() - - def copy(self): - return self.__class__(self.child.copy(), self.limit, - weight=self._weight, missing=self.missing) - - def _replacement(self, newchild): - return self.__class__(newchild, self.limit, missing=self.missing, - weight=self.weight) - - def is_active(self): - return self._id < self.limit - - def supports_quality(self): - return False - - def _find_next(self): - child = self.child - missing = self.missing - - if not child.is_active() and not missing(self._id): - return - - if child.is_active() and child.id() < self._id: - child.skip_to(self._id) - - # While self._id is missing or is in the child matcher, increase it - while child.is_active() and self._id < self.limit: - if missing(self._id): - self._id += 1 - continue - - if self._id == child.id(): - self._id += 1 - child.next() - continue - - break - - def id(self): - return self._id - - def all_ids(self): - missing = self.missing - negs = set(self.child.all_ids()) - return (id for id in xrange(self.limit) - if id not in negs and not missing(id)) - - def next(self): - if self._id >= self.limit: raise ReadTooFar - self._id += 1 - self._find_next() - - def skip_to(self, id): - if self._id >= self.limit: raise ReadTooFar - if id < self._id: return - self._id = id - self._find_next() - - def weight(self): - return self._weight - - def score(self): - return self._weight - - -class RequireMatcher(WrappingMatcher): - """Matches postings that are in both sub-matchers, but only uses scores - from the first. - """ - - def __init__(self, a, b): - self.a = a - self.b = b - self.child = IntersectionMatcher(a, b) - - def copy(self): - return self.__class__(self.a.copy(), self.b.copy()) - - def replace(self): - if not self.child.is_active(): return NullMatcher() - return self - - def quality(self): - return self.a.quality() - - def block_quality(self): - return self.a.block_quality() - - def skip_to_quality(self, minquality): - skipped = self.a.skip_to_quality(minquality) - self.child._find_next() - return skipped - - def weight(self): - return self.a.weight() - - def score(self): - return self.a.score() - - def supports(self, astype): - return self.a.supports(astype) - - def value(self): - return self.a.value() - - def value_as(self, astype): - return self.a.value_as(astype) - - -class AndMaybeMatcher(AdditiveBiMatcher): - """Matches postings in the first sub-matcher, and if the same posting is - in the second sub-matcher, adds their scores. - """ - - def __init__(self, a, b): - self.a = a - self.b = b - - if a.is_active() and b.is_active() and a.id() != b.id(): - b.skip_to(a.id()) - - def is_active(self): - return self.a.is_active() - - def id(self): - return self.a.id() - - def next(self): - if not self.a.is_active(): raise ReadTooFar - - ar = self.a.next() - br = False - if self.a.is_active() and self.b.is_active(): - br = self.b.skip_to(self.a.id()) - return ar or br - - def skip_to(self, id): - if not self.a.is_active(): raise ReadTooFar - - ra = self.a.skip_to(id) - rb = False - if self.a.is_active() and self.b.is_active(): - rb = self.b.skip_to(id) - return ra or rb - - def replace(self): - ar = self.a.replace() - br = self.b.replace() - if not ar.is_active(): return NullMatcher() - if not br.is_active(): return ar - if ar is not self.a or br is not self.b: - return self.__class__(ar, br) - return self - - def skip_to_quality(self, minquality): - a = self.a - b = self.b - minquality = minquality - - if not a.is_active(): raise ReadTooFar - if not b.is_active(): - return a.skip_to_quality(minquality) - - skipped = 0 - aq = a.block_quality() - bq = b.block_quality() - while a.is_active() and b.is_active() and aq + bq <= minquality: - if aq < bq: - skipped += a.skip_to_quality(minquality - bq) - aq = a.block_quality() - else: - skipped += b.skip_to_quality(minquality - aq) - bq = b.block_quality() - - return skipped - - def weight(self): - if self.a.id() == self.b.id(): - return self.a.weight() + self.b.weight() - else: - return self.a.weight() - - def score(self): - if self.b.is_active() and self.a.id() == self.b.id(): - return self.a.score() + self.b.score() - else: - return self.a.score() - - def supports(self, astype): - return self.a.supports(astype) - - def value(self): - return self.a.value() - - def value_as(self, astype): - return self.a.value_as(astype) - - -class ConstantScoreMatcher(WrappingMatcher): - def __init__(self, child, score=1.0): - super(ConstantScoreMatcher, self).__init__(child) - self._score = score - - def copy(self): - return self.__class__(self.child.copy(), score=self._score) - - def _replacement(self, newchild): - return self.__class__(newchild, score=self._score) - - def quality(self): - return self._score - - def block_quality(self): - return self._score - - def score(self): - return self._score - - - -#class PhraseMatcher(WrappingMatcher): -# """Matches postings where a list of sub-matchers occur next to each other -# in order. -# """ -# -# def __init__(self, wordmatchers, slop=1, boost=1.0): -# self.wordmatchers = wordmatchers -# self.child = make_binary_tree(IntersectionMatcher, wordmatchers) -# self.slop = slop -# self.boost = boost -# self._spans = None -# self._find_next() -# -# def copy(self): -# return self.__class__(self.wordmatchers[:], slop=self.slop, boost=self.boost) -# -# def replace(self): -# if not self.is_active(): -# return NullMatcher() -# return self -# -# def all_ids(self): -# # Need to redefine this because the WrappingMatcher parent class -# # forwards to the submatcher, which in this case is just the -# # IntersectionMatcher. -# while self.is_active(): -# yield self.id() -# self.next() -# -# def next(self): -# ri = self.child.next() -# rn = self._find_next() -# return ri or rn -# -# def skip_to(self, id): -# rs = self.child.skip_to(id) -# rn = self._find_next() -# return rs or rn -# -# def skip_to_quality(self, minquality): -# skipped = 0 -# while self.is_active() and self.quality() <= minquality: -# # TODO: doesn't count the documents matching the phrase yet -# skipped += self.child.skip_to_quality(minquality/self.boost) -# self._find_next() -# return skipped -# -# def positions(self): -# if not self.is_active(): -# raise ReadTooFar -# if not self.wordmatchers: -# return [] -# return self.wordmatchers[0].positions() -# -# def _find_next(self): -# isect = self.child -# slop = self.slop -# -# # List of "active" positions -# current = [] -# -# while not current and isect.is_active(): -# # [[list of positions for word 1], -# # [list of positions for word 2], ...] -# poses = [m.positions() for m in self.wordmatchers] -# -# # Set the "active" position list to the list of positions of the -# # first word. We well then iteratively update this list with the -# # positions of subsequent words if they are within the "slop" -# # distance of the positions in the active list. -# current = poses[0] -# -# # For each list of positions for the subsequent words... -# for poslist in poses[1:]: -# # A list to hold the new list of active positions -# newposes = [] -# -# # For each position in the list of positions in this next word -# for newpos in poslist: -# # Use bisect to only check the part of the current list -# # that could contain positions within the "slop" distance -# # of the new position -# start = bisect_left(current, newpos - slop) -# end = bisect_right(current, newpos) -# -# # -# for curpos in current[start:end]: -# delta = newpos - curpos -# if delta > 0 and delta <= slop: -# newposes.append(newpos) -# -# current = newposes -# if not current: break -# -# if not current: -# isect.next() -# -# self._count = len(current) -# -# -#class VectorPhraseMatcher(BasePhraseMatcher): -# """Phrase matcher for fields with a vector with positions (i.e. Positions -# or CharacterPositions format). -# """ -# -# def __init__(self, searcher, fieldname, words, isect, slop=1, boost=1.0): -# """ -# :param searcher: a Searcher object. -# :param fieldname: the field in which to search. -# :param words: a sequence of token texts representing the words in the -# phrase. -# :param isect: an intersection matcher for the words in the phrase. -# :param slop: -# """ -# -# decodefn = searcher.field(fieldname).vector.decoder("positions") -# self.reader = searcher.reader() -# self.fieldname = fieldname -# self.words = words -# self.sortedwords = sorted(self.words) -# super(VectorPhraseMatcher, self).__init__(isect, decodefn, slop=slop, -# boost=boost) -# -# def _poses(self): -# vreader = self.reader.vector(self.child.id(), self.fieldname) -# poses = {} -# decode_positions = self.decode_positions -# for word in self.sortedwords: -# vreader.skip_to(word) -# if vreader.id() != word: -# raise Exception("Phrase query: %r in term index but not in" -# " vector (possible analyzer mismatch)" % word) -# poses[word] = decode_positions(vreader.value()) -# # Now put the position lists in phrase order -# return [poses[word] for word in self.words] - - - - - - - - - - - - +#=============================================================================== +# Copyright 2010 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +from bisect import bisect_left, bisect_right, insort +from itertools import izip, repeat + + +""" +This module contains "matcher" classes. Matchers deal with posting lists. The +most basic matcher, which reads the list of postings for a term, will be +provided by the backend implementation (for example, +``whoosh.filedb.filepostings.FilePostingReader``). The classes in this module +provide additional functionality, such as combining the results of two +matchers, or modifying the results of a matcher. + +You do not need to deal with the classes in this module unless you need to +write your own Matcher implementation to provide some new functionality. These +classes are not instantiated by the user. They are usually created by a +:class:`~whoosh.query.Query` object's ``matcher()`` method, which returns the +appropriate matcher to implement the query (for example, the ``Or`` query's +``matcher()`` method returns a ``UnionMatcher`` object). + +Certain backends support "quality" optimizations. These backends have the +ability to skip ahead if it knows the current block of postings can't +contribute to the top N documents. If the matcher tree and backend support +these optimizations, the matcher's ``supports_quality()`` method will return +``True``. +""" + + +class ReadTooFar(Exception): + """Raised when next() or skip_to() is called on an inactive matchers. + """ + + +class NoQualityAvailable(Exception): + """Raised when quality methods are called on a matcher that does not + support quality-based optimizations. + """ + + +# Matchers + +class Matcher(object): + """Base class for all matchers. + """ + + def is_active(self): + """Returns True if this matcher is still "active", that is, it has not + yet reached the end of the posting list. + """ + + raise NotImplementedError + + def replace(self): + """Returns a possibly-simplified version of this matcher. For example, + if one of the children of a UnionMatcher is no longer active, calling + this method on the UnionMatcher will return the other child. + """ + + return self + + def copy(self): + """Returns a copy of this matcher. + """ + + raise NotImplementedError + + def depth(self): + """Returns the depth of the tree under this matcher, or 0 if this + matcher does not have any children. + """ + + return 0 + + def supports_quality(self): + """Returns True if this matcher supports the use of ``quality`` and + ``block_quality``. + """ + + return False + + def quality(self): + """Returns a quality measurement of the current posting, according to + the current weighting algorithm. Raises ``NoQualityAvailable`` if the + matcher or weighting do not support quality measurements. + """ + + raise NoQualityAvailable + + def block_quality(self): + """Returns a quality measurement of the current block of postings, + according to the current weighting algorithm. Raises + ``NoQualityAvailable`` if the matcher or weighting do not support + quality measurements. + """ + + raise NoQualityAvailable + + def id(self): + """Returns the ID of the current posting. + """ + + raise NotImplementedError + + def all_ids(self): + """Returns a generator of all IDs in the matcher. + + What this method returns for a matcher that has already read some + postings (whether it only yields the remaining postings or all postings + from the beginning) is undefined, so it's best to only use this method + on fresh matchers. + """ + + i = 0 + while self.is_active(): + yield self.id() + self.next() + i += 1 + if i == 10: + self = self.replace() + i = 0 + + def all_items(self): + """Returns a generator of all (ID, encoded value) pairs in the matcher. + + What this method returns for a matcher that has already read some + postings (whether it only yields the remaining postings or all postings + from the beginning) is undefined, so it's best to only use this method + on fresh matchers. + """ + + i = 0 + while self.is_active(): + yield (self.id(), self.value()) + self.next() + i += 1 + if i == 10: + self = self.replace() + i = 0 + + def items_as(self, astype): + """Returns a generator of all (ID, decoded value) pairs in the matcher. + + What this method returns for a matcher that has already read some + postings (whether it only yields the remaining postings or all postings + from the beginning) is undefined, so it's best to only use this method + on fresh matchers. + """ + + while self.is_active(): + yield (self.id(), self.value_as(astype)) + + def value(self): + """Returns the encoded value of the current posting. + """ + + raise NotImplementedError + + def supports(self, astype): + """Returns True if the field's format supports the named data type, + for example 'frequency' or 'characters'. + """ + + raise NotImplementedError("supports not implemented in %s" % self.__class__) + + def value_as(self, astype): + """Returns the value(s) of the current posting as the given type. + """ + + raise NotImplementedError("value_as not implemented in %s" % self.__class__) + + def spans(self): + """Returns a list of :class:`whoosh.spans.Span` objects for the matches + in this document. Raises an exception if the field being searched does + not store positions. + """ + + from whoosh.spans import Span + if self.supports("characters"): + return [Span(pos, startchar=startchar, endchar=endchar) + for pos, startchar, endchar in self.value_as("characters")] + elif self.supports("positions"): + return [Span(pos) for pos in self.value_as("positions")] + else: + raise Exception("Field does not support spans") + + def skip_to(self, id): + """Moves this matcher to the first posting with an ID equal to or + greater than the given ID. + """ + + while self.is_active() and self.id() < id: + self.next() + + def skip_to_quality(self, minquality): + """Moves this matcher to the next block with greater than the given + minimum quality value. + """ + + raise NotImplementedError + + def next(self): + """Moves this matcher to the next posting. + """ + + raise NotImplementedError(self.__class__.__name__) + + def weight(self): + """Returns the weight of the current posting. + """ + + return self.value_as("weight") + + def score(self): + """Returns the score of the current posting. + """ + + raise NotImplementedError + + +class NullMatcher(Matcher): + """Matcher with no postings which is never active. + """ + + def is_active(self): + return False + + def all_ids(self): + return [] + + def copy(self): + return self + + +class ListMatcher(Matcher): + """Synthetic matcher backed by a list of IDs. + """ + + def __init__(self, ids, weights=None, values=None, format=None, + scorer=None, position=0, all_weights=None): + """ + :param ids: a list of doc IDs. + :param weights: a list of weights corresponding to the list of IDs. + If this argument is not supplied, a list of 1.0 values is used. + :param values: a list of encoded values corresponding to the list of + IDs. + :param format: a :class:`whoosh.formats.Format` object representing the + format of the field. + :param scorer: a :class:`whoosh.scoring.BaseScorer` object for scoring + the postings. + """ + + self._ids = ids + self._weights = weights + self._all_weights = all_weights + self._values = values + self._i = position + self._format = format + self._scorer = scorer + + def __repr__(self): + return "%s(%r, %r, %r, %d)" % (self.__class__.__name__, self._ids, + self._weights, self._values, self._i) + + def is_active(self): + return self._i < len(self._ids) + + def copy(self): + return self.__class__(self._ids, self._weights, self._values, + self._format, self._scorer, self._i) + + def supports_quality(self): + return self._scorer is not None + + def quality(self): + return self._scorer.quality(self) + + def id(self): + return self._ids[self._i] + + def all_ids(self): + return iter(self._ids) + + def all_items(self): + values = self._values + if values is None: + values = repeat('') + + return izip(self._ids, values) + + def value(self): + if self._values: + return self._values[self._i] + else: + return '' + + def value_as(self, astype): + decoder = self._format.decoder(astype) + return decoder(self.value()) + + def supports(self, astype): + return self._format.supports(astype) + + def next(self): + self._i += 1 + + def weight(self): + if self._all_weights: + return self._all_weights + elif self._weights: + return self._weights[self._i] + else: + return 1.0 + + def score(self): + if self._scorer: + return self._scorer.score(self) + else: + return self.weight() + + +class WrappingMatcher(Matcher): + """Base class for matchers that wrap sub-matchers. + """ + + def __init__(self, child, boost=1.0): + self.child = child + self.boost = boost + + def __repr__(self): + return "%s(%r, boost=%s)" % (self.__class__.__name__, self.child, self.boost) + + def copy(self): + kwargs = {} + if hasattr(self, "boost"): + kwargs["boost"] = self.boost + return self.__class__(self.child.copy(), **kwargs) + + def depth(self): + return 1 + self.child.depth() + + def _replacement(self, newchild): + return self.__class__(newchild, boost=self.boost) + + def replace(self): + r = self.child.replace() + if not r.is_active(): + return NullMatcher() + if r is not self.child: + try: + return self._replacement(r) + except TypeError, e: + raise TypeError("Class %s got exception %s trying " + "to replace itself" % (self.__class__, e)) + else: + return self + + def id(self): + return self.child.id() + + def all_ids(self): + return self.child.all_ids() + + def is_active(self): + return self.child.is_active() + + def supports(self, astype): + return self.child.supports(astype) + + def value(self): + return self.child.value() + + def value_as(self, astype): + return self.child.value_as(astype) + + def spans(self): + return self.child.spans() + + def skip_to(self, id): + return self.child.skip_to(id) + + def next(self): + self.child.next() + + def supports_quality(self): + return self.child.supports_quality() + + def skip_to_quality(self, minquality): + return self.child.skip_to_quality(minquality/self.boost) + + def quality(self): + return self.child.quality() * self.boost + + def block_quality(self): + return self.child.block_quality() * self.boost + + def weight(self): + return self.child.weight() * self.boost + + def score(self): + return self.child.score() * self.boost + + +class MultiMatcher(Matcher): + """Serializes the results of a list of sub-matchers. + """ + + def __init__(self, matchers, idoffsets, current=0): + """ + :param matchers: a list of Matcher objects. + :param idoffsets: a list of offsets corresponding to items in the + ``matchers`` list. + """ + + self.matchers = matchers + self.offsets = idoffsets + self.current = current + self._next_matcher() + + def __repr__(self): + return "%s(%r, %r, current=%s)" % (self.__class__.__name__, + self.matchers, self.offsets, + self.current) + + def is_active(self): + return self.current < len(self.matchers) + + def _next_matcher(self): + matchers = self.matchers + while self.current < len(matchers) and not matchers[self.current].is_active(): + self.current += 1 + + def copy(self): + return self.__class__([mr.copy() for mr in self.matchers[self.current:]], + self.offsets[self.current:], current=self.current) + + def depth(self): + if self.is_active(): + return 1 + max(mr.depth() for mr in self.matchers[self.current:]) + else: + return 0 + + def replace(self): + if not self.is_active(): + return NullMatcher() + # TODO: Possible optimization: if the last matcher is current, replace + # this with the last matcher, but wrap it with a matcher that adds the + # offset. Have to check whether that's actually faster, though. + return self + + def id(self): + current = self.current + return self.matchers[current].id() + self.offsets[current] + + def all_ids(self): + offsets = self.offsets + for i, mr in enumerate(self.matchers): + for id in mr.all_ids(): + yield id + offsets[i] + + def spans(self): + return self.matchers[self.current].spans() + + def supports(self, astype): + return self.matchers[self.current].supports(astype) + + def value(self): + return self.matchers[self.current].value() + + def value_as(self, astype): + return self.matchers[self.current].value_as(astype) + + def next(self): + if not self.is_active(): raise ReadTooFar + + self.matchers[self.current].next() + if not self.matchers[self.current].is_active(): + self._next_matcher() + + def skip_to(self, id): + if not self.is_active(): raise ReadTooFar + if id <= self.id(): return + + matchers = self.matchers + offsets = self.offsets + r = False + + while self.current < len(matchers) and id > self.id(): + mr = matchers[self.current] + sr = mr.skip_to(id - offsets[self.current]) + r = sr or r + if mr.is_active(): + break + + self._next_matcher() + + return r + + def supports_quality(self): + return all(mr.supports_quality() for mr in self.matchers[self.current:]) + + def quality(self): + return self.matchers[self.current].quality() + + def block_quality(self): + return self.matchers[self.current].block_quality() + + def weight(self): + return self.matchers[self.current].weight() + + def score(self): + return self.matchers[self.current].score() + + +class ExcludeMatcher(WrappingMatcher): + """Excludes a list of IDs from the postings returned by the wrapped + matcher. + """ + + def __init__(self, child, excluded, boost=1.0): + super(ExcludeMatcher, self).__init__(child) + self.excluded = excluded + self.boost = boost + self._find_next() + + def __repr__(self): + return "%s(%r, %r, boost=%s)" % (self.__class__.__name__, self.child, + self.excluded, self.boost) + + def copy(self): + return self.__class__(self.child.copy(), self.excluded, boost=self.boost) + + def _replacement(self, newchild): + return self.__class__(newchild, self.excluded, boost=self.boost) + + def _find_next(self): + child = self.child + excluded = self.excluded + r = False + while child.is_active() and child.id() in excluded: + r = child.next() or r + return r + + def next(self): + self.child.next() + self._find_next() + + def skip_to(self, id): + self.child.skip_to(id) + self._find_next() + + def all_ids(self): + excluded = self.excluded + return (id for id in self.child.all_ids() if id not in excluded) + + def all_items(self): + excluded = self.excluded + return (item for item in self.child.all_items() + if item[0] not in excluded) + + +class BiMatcher(Matcher): + """Base class for matchers that combine the results of two sub-matchers in + some way. + """ + + def __init__(self, a, b): + super(BiMatcher, self).__init__() + self.a = a + self.b = b + + def __repr__(self): + return "%s(%r, %r)" % (self.__class__.__name__, self.a, self.b) + + def copy(self): + return self.__class__(self.a.copy(), self.b.copy()) + + def depth(self): + return 1 + max(self.a.depth(), self.b.depth()) + + def skip_to(self, id): + if not self.is_active(): raise ReadTooFar + ra = self.a.skip_to(id) + rb = self.b.skip_to(id) + return ra or rb + + def supports_quality(self): + return self.a.supports_quality() and self.b.supports_quality() + + def supports(self, astype): + return self.a.supports(astype) and self.b.supports(astype) + + +class AdditiveBiMatcher(BiMatcher): + """Base class for binary matchers where the scores of the sub-matchers are + added together. + """ + + def quality(self): + q = 0.0 + if self.a.is_active(): q += self.a.quality() + if self.b.is_active(): q += self.b.quality() + return q + + def block_quality(self): + bq = 0.0 + if self.a.is_active(): bq += self.a.block_quality() + if self.b.is_active(): bq += self.b.block_quality() + return bq + + def weight(self): + return (self.a.weight() + self.b.weight()) + + def score(self): + return (self.a.score() + self.b.score()) + + +class UnionMatcher(AdditiveBiMatcher): + """Matches the union (OR) of the postings in the two sub-matchers. + """ + + def replace(self): + a = self.a.replace() + b = self.b.replace() + + a_active = a.is_active() + b_active = b.is_active() + if not (a_active or b_active): return NullMatcher() + if not a_active: + return b + if not b_active: + return a + + if a is not self.a or b is not self.b: + return self.__class__(a, b) + return self + + def is_active(self): + return self.a.is_active() or self.b.is_active() + + def skip_to(self, id): + ra = rb = False + if self.a.is_active(): + ra = self.a.skip_to(id) + if self.b.is_active(): + rb = self.b.skip_to(id) + return ra or rb + + def id(self): + a = self.a + b = self.b + if not a.is_active(): return b.id() + if not b.is_active(): return a.id() + return min(a.id(), b.id()) + + # Using sets is faster in most cases, but could potentially use a lot of + # memory + def all_ids(self): + return iter(sorted(set(self.a.all_ids()) | set(self.b.all_ids()))) + + def next(self): + a = self.a + b = self.b + a_active = a.is_active() + b_active = b.is_active() + + # Shortcut when one matcher is inactive + if not (a_active or b_active): + raise ReadTooFar + elif not a_active: + return b.next() + elif not b_active: + return a.next() + + a_id = a.id() + b_id = b.id() + ar = br = None + if a_id <= b_id: ar = a.next() + if b_id <= a_id: br = b.next() + return ar or br + + def spans(self): + # Returns the branch that currently matches, or None if both branches + # currently match + if not self.a.is_active(): return self.b.spans() + if not self.b.is_active(): return self.a.spans() + id_a = self.a.id() + id_b = self.b.id() + if id_a < id_b: + return self.a.spans() + elif id_b < id_a: + return self.b.spans() + else: + return sorted(set(self.a.spans()) | set(self.b.spans())) + + def score(self): + a = self.a + b = self.b + + if not a.is_active(): return b.score() + if not b.is_active(): return a.score() + + id_a = a.id() + id_b = b.id() + if id_a < id_b: + return a.score() + elif id_b < id_a: + return b.score() + else: + return (a.score() + b.score()) + + def skip_to_quality(self, minquality): + a = self.a + b = self.b + minquality = minquality + + # Short circuit if one matcher is inactive + if not a.is_active(): + sk = b.skip_to_quality(minquality) + return sk + elif not b.is_active(): + return a.skip_to_quality(minquality) + + skipped = 0 + aq = a.block_quality() + bq = b.block_quality() + while a.is_active() and b.is_active() and aq + bq <= minquality: + if aq < bq: + skipped += a.skip_to_quality(minquality - bq) + aq = a.block_quality() + else: + skipped += b.skip_to_quality(minquality - aq) + bq = b.block_quality() + + return skipped + + +class DisjunctionMaxMatcher(UnionMatcher): + """Matches the union (OR) of two sub-matchers. Where both sub-matchers + match the same posting, returns the weight/score of the higher-scoring + posting. + """ + + # TODO: this class inherits from AdditiveBiMatcher (through UnionMatcher) + # but it does not add the scores of the sub-matchers together (it + # overrides all methods that perform addition). Need to clean up the + # inheritance. + + def __init__(self, a, b, tiebreak=0.0): + super(DisjunctionMaxMatcher, self).__init__(a, b) + self.tiebreak = tiebreak + + def copy(self): + return self.__class__(self.a.copy(), self.b.copy(), + tiebreak=self.tiebreak) + + def score(self): + return max(self.a.score(), self.b.score()) + + def quality(self): + return max(self.a.quality(), self.b.quality()) + + def block_quality(self): + return max(self.a.block_quality(), self.b.block_quality()) + + def skip_to_quality(self, minquality): + a = self.a + b = self.b + minquality = minquality + + # Short circuit if one matcher is inactive + if not a.is_active(): + sk = b.skip_to_quality(minquality) + return sk + elif not b.is_active(): + return a.skip_to_quality(minquality) + + skipped = 0 + aq = a.block_quality() + bq = b.block_quality() + while a.is_active() and b.is_active() and max(aq, bq) <= minquality: + if aq <= minquality: + skipped += a.skip_to_quality(minquality) + aq = a.block_quality() + if bq <= minquality: + skipped += b.skip_to_quality(minquality) + bq = b.block_quality() + return skipped + + +class IntersectionMatcher(AdditiveBiMatcher): + """Matches the intersection (AND) of the postings in the two sub-matchers. + """ + + def __init__(self, a, b): + super(IntersectionMatcher, self).__init__(a, b) + if (self.a.is_active() + and self.b.is_active() + and self.a.id() != self.b.id()): + self._find_next() + + def replace(self): + a = self.a.replace() + b = self.b.replace() + + a_active = a + b_active = b.is_active() + if not (a_active and b_active): return NullMatcher() + + if a is not self.a or b is not self.b: + return self.__class__(a, b) + return self + + def is_active(self): + return self.a.is_active() and self.b.is_active() + + def _find_next(self): + a = self.a + b = self.b + a_id = a.id() + b_id = b.id() + assert a_id != b_id + r = False + + while a.is_active() and b.is_active() and a_id != b_id: + if a_id < b_id: + ra = a.skip_to(b_id) + if not a.is_active(): return + r = r or ra + a_id = a.id() + else: + rb = b.skip_to(a_id) + if not b.is_active(): return + r = r or rb + b_id = b.id() + return r + + def id(self): + return self.a.id() + + # Using sets is faster in some cases, but could potentially use a lot of + # memory + def all_ids(self): + return iter(sorted(set(self.a.all_ids()) & set(self.b.all_ids()))) + + def skip_to(self, id): + if not self.is_active(): raise ReadTooFar + ra = self.a.skip_to(id) + rb = self.b.skip_to(id) + if self.is_active(): + rn = False + if self.a.id() != self.b.id(): + rn = self._find_next() + return ra or rb or rn + + def skip_to_quality(self, minquality): + a = self.a + b = self.b + minquality = minquality + + skipped = 0 + aq = a.block_quality() + bq = b.block_quality() + while a.is_active() and b.is_active() and aq + bq <= minquality: + if aq < bq: + skipped += a.skip_to_quality(minquality - bq) + else: + skipped += b.skip_to_quality(minquality - aq) + if a.id() != b.id(): + self._find_next() + aq = a.block_quality() + bq = b.block_quality() + return skipped + + def next(self): + if not self.is_active(): raise ReadTooFar + + # We must assume that the ids are equal whenever next() is called (they + # should have been made equal by _find_next), so advance them both + ar = self.a.next() + if self.is_active(): + nr = self._find_next() + return ar or nr + + def spans(self): + return sorted(set(self.a.spans()) | set(self.b.spans())) + + +class AndNotMatcher(BiMatcher): + """Matches the postings in the first sub-matcher that are NOT present in + the second sub-matcher. + """ + + def __init__(self, a, b): + super(AndNotMatcher, self).__init__(a, b) + if (self.a.is_active() + and self.b.is_active() + and self.a.id() != self.b.id()): + self._find_next() + + def is_active(self): + return self.a.is_active() + + def _find_next(self): + pos = self.a + neg = self.b + if not neg.is_active(): return + pos_id = pos.id() + r = False + + if neg.id() < pos_id: + neg.skip_to(pos_id) + + while neg.is_active() and pos_id == neg.id(): + nr = pos.next() + r = r or nr + pos_id = pos.id() + neg.skip_to(pos_id) + + return r + + def replace(self): + if not self.a.is_active(): return NullMatcher() + if not self.b.is_active(): return self.a + return self + + def quality(self): + return self.a.quality() + + def block_quality(self): + return self.a.block_quality() + + def skip_to_quality(self, minquality): + skipped = self.a.skip_to_quality(minquality) + self._find_next() + return skipped + + def id(self): + return self.a.id() + + def all_ids(self): + return iter(sorted(set(self.a.all_ids()) - set(self.b.all_ids()))) + + def next(self): + if not self.a.is_active(): raise ReadTooFar + ar = self.a.next() + nr = False + if self.b.is_active(): + nr = self._find_next() + return ar or nr + + def skip_to(self, id): + if not self.a.is_active(): raise ReadTooFar + if id < self.a.id(): return + + self.a.skip_to(id) + if self.b.is_active(): + self.b.skip_to(id) + self._find_next() + + def weight(self): + return self.a.weight() + + def score(self): + return self.a.score() + + def supports(self, astype): + return self.a.supports(astype) + + def value(self): + return self.a.value() + + def value_as(self, astype): + return self.a.value_as(astype) + + +class InverseMatcher(WrappingMatcher): + """Synthetic matcher, generates postings that are NOT present in the + wrapped matcher. + """ + + def __init__(self, child, limit, missing=None, weight=1.0): + super(InverseMatcher, self).__init__(child) + self.limit = limit + self._weight = weight + self.missing = missing or (lambda id: False) + self._id = 0 + self._find_next() + + def copy(self): + return self.__class__(self.child.copy(), self.limit, + weight=self._weight, missing=self.missing) + + def _replacement(self, newchild): + return self.__class__(newchild, self.limit, missing=self.missing, + weight=self.weight) + + def is_active(self): + return self._id < self.limit + + def supports_quality(self): + return False + + def _find_next(self): + child = self.child + missing = self.missing + + if not child.is_active() and not missing(self._id): + return + + if child.is_active() and child.id() < self._id: + child.skip_to(self._id) + + # While self._id is missing or is in the child matcher, increase it + while child.is_active() and self._id < self.limit: + if missing(self._id): + self._id += 1 + continue + + if self._id == child.id(): + self._id += 1 + child.next() + continue + + break + + def id(self): + return self._id + + def all_ids(self): + missing = self.missing + negs = set(self.child.all_ids()) + return (id for id in xrange(self.limit) + if id not in negs and not missing(id)) + + def next(self): + if self._id >= self.limit: raise ReadTooFar + self._id += 1 + self._find_next() + + def skip_to(self, id): + if self._id >= self.limit: raise ReadTooFar + if id < self._id: return + self._id = id + self._find_next() + + def weight(self): + return self._weight + + def score(self): + return self._weight + + +class RequireMatcher(WrappingMatcher): + """Matches postings that are in both sub-matchers, but only uses scores + from the first. + """ + + def __init__(self, a, b): + self.a = a + self.b = b + self.child = IntersectionMatcher(a, b) + + def copy(self): + return self.__class__(self.a.copy(), self.b.copy()) + + def replace(self): + if not self.child.is_active(): return NullMatcher() + return self + + def quality(self): + return self.a.quality() + + def block_quality(self): + return self.a.block_quality() + + def skip_to_quality(self, minquality): + skipped = self.a.skip_to_quality(minquality) + self.child._find_next() + return skipped + + def weight(self): + return self.a.weight() + + def score(self): + return self.a.score() + + def supports(self, astype): + return self.a.supports(astype) + + def value(self): + return self.a.value() + + def value_as(self, astype): + return self.a.value_as(astype) + + +class AndMaybeMatcher(AdditiveBiMatcher): + """Matches postings in the first sub-matcher, and if the same posting is + in the second sub-matcher, adds their scores. + """ + + def __init__(self, a, b): + self.a = a + self.b = b + + if a.is_active() and b.is_active() and a.id() != b.id(): + b.skip_to(a.id()) + + def is_active(self): + return self.a.is_active() + + def id(self): + return self.a.id() + + def next(self): + if not self.a.is_active(): raise ReadTooFar + + ar = self.a.next() + br = False + if self.a.is_active() and self.b.is_active(): + br = self.b.skip_to(self.a.id()) + return ar or br + + def skip_to(self, id): + if not self.a.is_active(): raise ReadTooFar + + ra = self.a.skip_to(id) + rb = False + if self.a.is_active() and self.b.is_active(): + rb = self.b.skip_to(id) + return ra or rb + + def replace(self): + ar = self.a.replace() + br = self.b.replace() + if not ar.is_active(): return NullMatcher() + if not br.is_active(): return ar + if ar is not self.a or br is not self.b: + return self.__class__(ar, br) + return self + + def skip_to_quality(self, minquality): + a = self.a + b = self.b + minquality = minquality + + if not a.is_active(): raise ReadTooFar + if not b.is_active(): + return a.skip_to_quality(minquality) + + skipped = 0 + aq = a.block_quality() + bq = b.block_quality() + while a.is_active() and b.is_active() and aq + bq <= minquality: + if aq < bq: + skipped += a.skip_to_quality(minquality - bq) + aq = a.block_quality() + else: + skipped += b.skip_to_quality(minquality - aq) + bq = b.block_quality() + + return skipped + + def weight(self): + if self.a.id() == self.b.id(): + return self.a.weight() + self.b.weight() + else: + return self.a.weight() + + def score(self): + if self.b.is_active() and self.a.id() == self.b.id(): + return self.a.score() + self.b.score() + else: + return self.a.score() + + def supports(self, astype): + return self.a.supports(astype) + + def value(self): + return self.a.value() + + def value_as(self, astype): + return self.a.value_as(astype) + + +class ConstantScoreMatcher(WrappingMatcher): + def __init__(self, child, score=1.0): + super(ConstantScoreMatcher, self).__init__(child) + self._score = score + + def copy(self): + return self.__class__(self.child.copy(), score=self._score) + + def _replacement(self, newchild): + return self.__class__(newchild, score=self._score) + + def quality(self): + return self._score + + def block_quality(self): + return self._score + + def score(self): + return self._score + + + +#class PhraseMatcher(WrappingMatcher): +# """Matches postings where a list of sub-matchers occur next to each other +# in order. +# """ +# +# def __init__(self, wordmatchers, slop=1, boost=1.0): +# self.wordmatchers = wordmatchers +# self.child = make_binary_tree(IntersectionMatcher, wordmatchers) +# self.slop = slop +# self.boost = boost +# self._spans = None +# self._find_next() +# +# def copy(self): +# return self.__class__(self.wordmatchers[:], slop=self.slop, boost=self.boost) +# +# def replace(self): +# if not self.is_active(): +# return NullMatcher() +# return self +# +# def all_ids(self): +# # Need to redefine this because the WrappingMatcher parent class +# # forwards to the submatcher, which in this case is just the +# # IntersectionMatcher. +# while self.is_active(): +# yield self.id() +# self.next() +# +# def next(self): +# ri = self.child.next() +# rn = self._find_next() +# return ri or rn +# +# def skip_to(self, id): +# rs = self.child.skip_to(id) +# rn = self._find_next() +# return rs or rn +# +# def skip_to_quality(self, minquality): +# skipped = 0 +# while self.is_active() and self.quality() <= minquality: +# # TODO: doesn't count the documents matching the phrase yet +# skipped += self.child.skip_to_quality(minquality/self.boost) +# self._find_next() +# return skipped +# +# def positions(self): +# if not self.is_active(): +# raise ReadTooFar +# if not self.wordmatchers: +# return [] +# return self.wordmatchers[0].positions() +# +# def _find_next(self): +# isect = self.child +# slop = self.slop +# +# # List of "active" positions +# current = [] +# +# while not current and isect.is_active(): +# # [[list of positions for word 1], +# # [list of positions for word 2], ...] +# poses = [m.positions() for m in self.wordmatchers] +# +# # Set the "active" position list to the list of positions of the +# # first word. We well then iteratively update this list with the +# # positions of subsequent words if they are within the "slop" +# # distance of the positions in the active list. +# current = poses[0] +# +# # For each list of positions for the subsequent words... +# for poslist in poses[1:]: +# # A list to hold the new list of active positions +# newposes = [] +# +# # For each position in the list of positions in this next word +# for newpos in poslist: +# # Use bisect to only check the part of the current list +# # that could contain positions within the "slop" distance +# # of the new position +# start = bisect_left(current, newpos - slop) +# end = bisect_right(current, newpos) +# +# # +# for curpos in current[start:end]: +# delta = newpos - curpos +# if delta > 0 and delta <= slop: +# newposes.append(newpos) +# +# current = newposes +# if not current: break +# +# if not current: +# isect.next() +# +# self._count = len(current) +# +# +#class VectorPhraseMatcher(BasePhraseMatcher): +# """Phrase matcher for fields with a vector with positions (i.e. Positions +# or CharacterPositions format). +# """ +# +# def __init__(self, searcher, fieldname, words, isect, slop=1, boost=1.0): +# """ +# :param searcher: a Searcher object. +# :param fieldname: the field in which to search. +# :param words: a sequence of token texts representing the words in the +# phrase. +# :param isect: an intersection matcher for the words in the phrase. +# :param slop: +# """ +# +# decodefn = searcher.field(fieldname).vector.decoder("positions") +# self.reader = searcher.reader() +# self.fieldname = fieldname +# self.words = words +# self.sortedwords = sorted(self.words) +# super(VectorPhraseMatcher, self).__init__(isect, decodefn, slop=slop, +# boost=boost) +# +# def _poses(self): +# vreader = self.reader.vector(self.child.id(), self.fieldname) +# poses = {} +# decode_positions = self.decode_positions +# for word in self.sortedwords: +# vreader.skip_to(word) +# if vreader.id() != word: +# raise Exception("Phrase query: %r in term index but not in" +# " vector (possible analyzer mismatch)" % word) +# poses[word] = decode_positions(vreader.value()) +# # Now put the position lists in phrase order +# return [poses[word] for word in self.words] + + + + + + + + + + + + diff --git a/lib/whoosh/qparser/__init__.py b/lib/whoosh/qparser/__init__.py index 10b66abd..243c9db5 100644 --- a/lib/whoosh/qparser/__init__.py +++ b/lib/whoosh/qparser/__init__.py @@ -1,20 +1,20 @@ -#=============================================================================== -# Copyright 2010 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -from whoosh.qparser.default import * - - - +#=============================================================================== +# Copyright 2010 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +from whoosh.qparser.default import * + + + diff --git a/lib/whoosh/qparser/dateparse.py b/lib/whoosh/qparser/dateparse.py index d69b0f5e..189bfe35 100644 --- a/lib/whoosh/qparser/dateparse.py +++ b/lib/whoosh/qparser/dateparse.py @@ -1,857 +1,857 @@ -#=============================================================================== -# Copyright 2010 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -import calendar, re -from datetime import date, time, datetime, timedelta - -from whoosh.qparser import BasicSyntax, ErrorToken, Plugin, RangePlugin, Group, Word -from whoosh.support.relativedelta import relativedelta -from whoosh.support.times import (adatetime, timespan, fill_in, is_void, - TimeError, relative_days) - - -class DateParseError(Exception): - "Represents an error in parsing date text." - - -# Utility functions - -def rcompile(pattern): - """Just a shortcut to call re.compile with a standard set of flags. - """ - - return re.compile(pattern, re.IGNORECASE | re.UNICODE) - - -def print_debug(level, msg, *args): - if level > 0: print (" " * (level-1)) + (msg % args) - - -# Parser element objects - -class Props(object): - """A dumb little object that just puts copies a dictionary into attibutes - so I can use dot syntax instead of square bracket string item lookup and - save a little bit of typing. Used by :class:`Regex`. - """ - - def __init__(self, **args): - self.__dict__ = args - - def __repr__(self): - return repr(self.__dict__) - - def get(self, key, default=None): - return self.__dict__.get(key, default) - - -class ParserBase(object): - """Base class for date parser elements. - """ - - def to_parser(self, e): - if isinstance(e, basestring): - return Regex(e) - else: - return e - - def parse(self, text, dt, pos=0, debug=-9999): - raise NotImplementedError - - def date_from(self, text, dt=None, pos=0, debug=-9999): - if dt is None: - dt = datetime.now() - - d, pos = self.parse(text, dt, pos, debug + 1) - return d - - -class MultiBase(ParserBase): - """Base class for date parser elements such as Sequence and Bag that - have sub-elements. - """ - - def __init__(self, elements, name=None): - """ - :param elements: the sub-elements to match. - :param name: a name for this element (for debugging purposes only). - """ - - self.elements = [self.to_parser(e) for e in elements] - self.name = name - - def __repr__(self): - return "%s<%s>%r" % (self.__class__.__name__, self.name or '', self.elements) - - -class Sequence(MultiBase): - """Merges the dates parsed by a sequence of sub-elements. - """ - - def __init__(self, elements, sep="(\\s+|\\s*,\\s*)", name=None, - progressive=False): - """ - :param elements: the sequence of sub-elements to parse. - :param sep: a separator regular expression to match between elements, - or None to not have separators. - :param name: a name for this element (for debugging purposes only). - :param progressive: if True, elements after the first do not need to - match. That is, for elements (a, b, c) and progressive=True, the - sequence matches like ``a[b[c]]``. - """ - - super(Sequence, self).__init__(elements, name) - self.sep_pattern = sep - if sep: - self.sep_expr = rcompile(sep) - else: - self.sep_expr = None - self.progressive = progressive - - def parse(self, text, dt, pos=0, debug=-9999): - d = adatetime() - first = True - foundall = False - failed = False - - print_debug(debug, "Seq %s sep=%r text=%r", self.name, self.sep_pattern, text[pos:]) - for e in self.elements: - print_debug(debug, "Seq %s text=%r", self.name, text[pos:]) - if self.sep_expr and not first: - print_debug(debug, "Seq %s looking for sep", self.name) - m = self.sep_expr.match(text, pos) - if m: - pos = m.end() - else: - print_debug(debug, "Seq %s didn't find sep", self.name) - break - - print_debug(debug, "Seq %s trying=%r at=%s", self.name, e, pos) - - try: - at, newpos = e.parse(text, dt, pos=pos, debug=debug + 1) - except TimeError: - failed = True - break - - print_debug(debug, "Seq %s result=%r", self.name, at) - if not at: - break - pos = newpos - - print_debug(debug, "Seq %s adding=%r to=%r", self.name, at, d) - try: - d = fill_in(d, at) - except TimeError: - print_debug(debug, "Seq %s Error in fill_in", self.name) - failed = True - break - print_debug(debug, "Seq %s filled date=%r", self.name, d) - - first = False - else: - foundall = True - - if not failed and (foundall or (not first and self.progressive)): - print_debug(debug, "Seq %s final=%r", self.name, d) - return (d, pos) - else: - print_debug(debug, "Seq %s failed", self.name) - return (None, None) - - -class Combo(Sequence): - """Parses a sequence of elements in order and combines the dates parsed - by the sub-elements somehow. The default behavior is to accept two dates - from the sub-elements and turn them into a range. - """ - - def __init__(self, elements, fn=None, sep="(\\s+|\\s*,\\s*)", min=2, max=2, - name=None): - """ - :param elements: the sequence of sub-elements to parse. - :param fn: a function to run on all dates found. It should return a - datetime, adatetime, or timespan object. If this argument is None, - the default behavior accepts two dates and returns a timespan. - :param sep: a separator regular expression to match between elements, - or None to not have separators. - :param min: the minimum number of dates required from the sub-elements. - :param max: the maximum number of dates allowed from the sub-elements. - :param name: a name for this element (for debugging purposes only). - """ - - super(Combo, self).__init__(elements, sep=sep, name=name) - self.fn = fn - self.min = min - self.max = max - - def parse(self, text, dt, pos=0, debug=-9999): - dates = [] - first = True - - print_debug(debug, "Combo %s sep=%r text=%r", self.name, self.sep_pattern, text[pos:]) - for e in self.elements: - if self.sep_expr and not first: - print_debug(debug, "Combo %s looking for sep at %r", self.name, text[pos:]) - m = self.sep_expr.match(text, pos) - if m: - pos = m.end() - else: - print_debug(debug, "Combo %s didn't find sep", self.name) - return (None, None) - - print_debug(debug, "Combo %s trying=%r", self.name, e) - try: - at, pos = e.parse(text, dt, pos, debug + 1) - except TimeError: - at, pos = None, None - - print_debug(debug, "Combo %s result=%r", self.name, at) - if at is None: - return (None, None) - - first = False - if is_void(at): - continue - if len(dates) == self.max: - print_debug(debug, "Combo %s length > %s", self.name, self.max) - return (None, None) - dates.append(at) - - print_debug(debug, "Combo %s dates=%r", self.name, dates) - if len(dates) < self.min: - print_debug(debug, "Combo %s length < %s", self.name, self.min) - return (None, None) - - return (self.dates_to_timespan(dates), pos) - - def dates_to_timespan(self, dates): - if self.fn: - return self.fn(dates) - elif len(dates) == 2: - return timespan(dates[0], dates[1]) - else: - raise DateParseError("Don't know what to do with %r" % (dates, )) - - -class Choice(MultiBase): - """Returns the date from the first of its sub-elements that matches. - """ - - def parse(self, text, dt, pos=0, debug=-9999): - print_debug(debug, "Choice %s text=%r", self.name, text[pos:]) - for e in self.elements: - print_debug(debug, "Choice %s trying=%r", self.name, e) - - try: - d, newpos = e.parse(text, dt, pos, debug + 1) - except TimeError: - d, newpos = None, None - if d: - print_debug(debug, "Choice %s matched", self.name) - return (d, newpos) - print_debug(debug, "Choice %s no match", self.name) - return (None, None) - - -class Bag(MultiBase): - """Parses its sub-elements in any order and merges the dates. - """ - - def __init__(self, elements, sep="(\\s+|\\s*,\\s*)", onceper=True, - requireall=False, allof=None, anyof=None, name=None): - """ - :param elements: the sub-elements to parse. - :param sep: a separator regular expression to match between elements, - or None to not have separators. - :param onceper: only allow each element to match once. - :param requireall: if True, the sub-elements can match in any order, - but they must all match. - :param allof: a list of indexes into the list of elements. When this - argument is not None, this element matches only if all the - indicated sub-elements match. - :param allof: a list of indexes into the list of elements. When this - argument is not None, this element matches only if any of the - indicated sub-elements match. - :param name: a name for this element (for debugging purposes only). - """ - - super(Bag, self).__init__(elements, name) - self.sep_expr = rcompile(sep) - self.onceper = onceper - self.requireall = requireall - self.allof = allof - self.anyof = anyof - - def parse(self, text, dt, pos=0, debug=-9999): - first = True - d = adatetime() - seen = [False] * len(self.elements) - - while True: - newpos = pos - print_debug(debug, "Bag %s text=%r", self.name, text[pos:]) - if not first: - print_debug(debug, "Bag %s looking for sep", self.name) - m = self.sep_expr.match(text, pos) - if m: - newpos = m.end() - else: - print_debug(debug, "Bag %s didn't find sep", self.name) - break - - for i, e in enumerate(self.elements): - print_debug(debug, "Bag %s trying=%r", self.name, e) - - try: - at, xpos = e.parse(text, dt, newpos, debug + 1) - except TimeError: - at, xpos = None, None - - print_debug(debug, "Bag %s result=%r", self.name, at) - if at: - if self.onceper and seen[i]: - return (None, None) - - d = fill_in(d, at) - newpos = xpos - seen[i] = True - break - else: - break - - pos = newpos - if self.onceper and all(seen): - break - - first = False - - if (not any(seen) - or (self.allof and not all(seen[pos] for pos in self.allof)) - or (self.anyof and not any(seen[pos] for pos in self.anyof)) - or (self.requireall and not all(seen))): - return (None, None) - - print_debug(debug, "Bag %s final=%r", self.name, d) - return (d, pos) - - -class Optional(ParserBase): - """Wraps a sub-element to indicate that the sub-element is optional. - """ - - def __init__(self, element): - self.element = self.to_parser(element) - - def __repr__(self): - return "%s(%r)" % (self.__class__.__name__, self.element) - - def parse(self, text, dt, pos=0, debug=-9999): - try: - d, pos = self.element.parse(text, dt, pos, debug + 1) - except TimeError: - d, pos = None, None - - if d: - return (d, pos) - else: - return (adatetime(), pos) - - -class ToEnd(ParserBase): - """Wraps a sub-element and requires that the end of the sub-element's match - be the end of the text. - """ - - def __init__(self, element): - self.element = element - - def __repr__(self): - return "%s(%r)" % (self.__class__.__name__, self.element) - - def parse(self, text, dt, pos=0, debug=-9999): - try: - d, pos = self.element.parse(text, dt, pos, debug + 1) - except TimeError: - d, pos = None, None - - if d and pos == len(text): - return (d, pos) - else: - return (None, None) - - -class Regex(ParserBase): - """Matches a regular expression and maps named groups in the pattern to - datetime attributes using a function or overridden method. - - There are two points at which you can customize the behavior of this class, - either by supplying functions to the initializer or overriding methods. - - * The ``modify`` function or ``modify_props`` method takes a ``Props`` - object containing the named groups and modifies its values (in place). - * The ``fn`` function or ``props_to_date`` method takes a ``Props`` object - and the base datetime and returns an adatetime/datetime. - """ - - fn = None - modify = None - - def __init__(self, pattern, fn=None, modify=None): - self.pattern = pattern - self.expr = rcompile(pattern) - self.fn = fn - self.modify = modify - - def __repr__(self): - return "<%r>" % (self.pattern, ) - - def parse(self, text, dt, pos=0, debug=-9999): - m = self.expr.match(text, pos) - if not m: - return (None, None) - - props = self.extract(m) - self.modify_props(props) - - try: - d = self.props_to_date(props, dt) - except TimeError: - d = None - - if d: - return (d, m.end()) - else: - return (None, None) - - def extract(self, match): - d = match.groupdict() - for key, value in d.iteritems(): - try: - value = int(value) - d[key] = value - except (ValueError, TypeError): - pass - return Props(**d) - - def modify_props(self, props): - if self.modify: - self.modify(props) - - def props_to_date(self, props, dt): - if self.fn: - return self.fn(props, dt) - else: - args = {} - for key in adatetime.units: - args[key] = props.get(key) - return adatetime(**args) - - -class Month(Regex): - def __init__(self, *patterns): - self.patterns = patterns - self.exprs = [rcompile(pat) for pat in self.patterns] - - self.pattern = ("(?P" - + "|".join("(%s)" % pat for pat in self.patterns) - + ")") - self.expr = rcompile(self.pattern) - - def modify_props(self, p): - text = p.month - for i, expr in enumerate(self.exprs): - m = expr.match(text) - if m: - p.month = i + 1 - break - - -class PlusMinus(Regex): - def __init__(self, years, months, weeks, days, hours, minutes, seconds): - rel_years = "((?P[0-9]+) *(%s))?" % years - rel_months = "((?P[0-9]+) *(%s))?" % months - rel_weeks = "((?P[0-9]+) *(%s))?" % weeks - rel_days = "((?P[0-9]+) *(%s))?" % days - rel_hours = "((?P[0-9]+) *(%s))?" % hours - rel_mins = "((?P[0-9]+) *(%s))?" % minutes - rel_secs = "((?P[0-9]+) *(%s))?" % seconds - - self.pattern = ("(?P[+-]) *%s *%s *%s *%s *%s *%s *%s(?=(\\W|$))" - % (rel_years, rel_months, rel_weeks, rel_days, - rel_hours, rel_mins, rel_secs)) - self.expr = rcompile(self.pattern) - - def props_to_date(self, p, dt): - if p.dir == "-": - dir = -1 - else: - dir = 1 - - delta = relativedelta(years=(p.get("years") or 0) * dir, - months=(p.get("months") or 0) * dir, - weeks=(p.get("weeks") or 0) * dir, - days=(p.get("days") or 0) * dir, - hours=(p.get("hours") or 0) * dir, - minutes=(p.get("mins") or 0) * dir, - seconds=(p.get("secs") or 0) * dir) - return dt + delta - - -class Daynames(Regex): - def __init__(self, next, last, daynames): - self.next_pattern = next - self.last_pattern = last - self._dayname_exprs = tuple(rcompile(pat) for pat in daynames) - dn_pattern = "|".join(daynames) - self.pattern = "(?P%s|%s) +(?P%s)(?=(\\W|$))" % (next, last, dn_pattern) - self.expr = rcompile(self.pattern) - - def props_to_date(self, p, dt): - if re.match(p.dir, self.last_pattern): - dir = -1 - else: - dir = 1 - - for daynum, expr in enumerate(self._dayname_exprs): - m = expr.match(p.day) - if m: - break - current_daynum = dt.weekday() - days_delta = relative_days(current_daynum, daynum, dir) - - d = dt.date() + timedelta(days=days_delta) - return adatetime(year=d.year, month=d.month, day=d.day) - - -class Time12(Regex): - def __init__(self): - self.pattern = "(?P[1-9]|10|11|12)(:(?P[0-5][0-9])(:(?P[0-5][0-9])(\\.(?P[0-9]{1,5}))?)?)?\\s*(?Pam|pm)(?=(\\W|$))" - self.expr = rcompile(self.pattern) - - def props_to_date(self, p, dt): - isam = p.ampm.lower().startswith("a") - - if p.hour == 12: - if isam: - hr = 0 - else: - hr = 12 - else: - hr = p.hour - if not isam: - hr += 12 - - return adatetime(hour=hr, minute=p.mins, second=p.secs, microsecond=p.usecs) - - -# Top-level parser classes - -class DateParser(object): - """Base class for locale-specific parser classes. - """ - - day = Regex("(?P([123][0-9])|[1-9])(?=(\\W|$))(?!=:)", - lambda p, dt: adatetime(day=p.day)) - year = Regex("(?P[0-9]{4})(?=(\\W|$))", - lambda p, dt: adatetime(year=p.year)) - time24 = Regex("(?P([0-1][0-9])|(2[0-3])):(?P[0-5][0-9])(:(?P[0-5][0-9])(\\.(?P[0-9]{1,5}))?)?(?=(\\W|$))", - lambda p, dt: adatetime(hour=p.hour, minute=p.mins, second=p.secs, microsecond=p.usecs)) - time12 = Time12() - - def __init__(self): - simple_year = "(?P[0-9]{4})" - simple_month = "(?P[0-1][0-9])" - simple_day = "(?P[0-3][0-9])" - simple_hour = "(?P([0-1][0-9])|(2[0-3]))" - simple_minute = "(?P[0-5][0-9])" - simple_second = "(?P[0-5][0-9])" - simple_usec = "(?P[0-9]{6})" - - simple_seq = Sequence((simple_year, simple_month, simple_day, simple_hour, - simple_minute, simple_second, simple_usec), - sep="[- .:/]*", name="simple", progressive=True) - self.simple = Sequence((simple_seq, "(?=(\\s|$))"), sep='') - - self.setup() - - def setup(self): - raise NotImplementedError - - # - - def get_parser(self): - return self.all - - def parse(self, text, dt, pos=0, debug=-9999): - parser = self.get_parser() - - d, newpos = parser.parse(text, dt, pos=pos, debug=debug) - if isinstance(d, (adatetime, timespan)): - d = d.disambiguated(dt) - - return (d, newpos) - - def date_from(self, text, basedate=None, pos=0, debug=-9999, toend=True): - if basedate is None: - basedate = datetime.utcnow() - - parser = self.get_parser() - if toend: - parser = ToEnd(parser) - - d = parser.date_from(text, basedate, pos=pos, debug=debug) - if isinstance(d, (adatetime, timespan)): - d = d.disambiguated(basedate) - return d - - - -class English(DateParser): - day = Regex("(?P([123][0-9])|[1-9])(st|nd|rd|th)?(?=(\\W|$))", - lambda p, dt: adatetime(day=p.day)) - - def setup(self): - self.plusdate = PlusMinus("years|year|yrs|yr|ys|y", - "months|month|mons|mon|mos|mo", - "weeks|week|wks|wk|ws|w", - "days|day|dys|dy|ds|d", - "hours|hour|hrs|hr|hs|h", - "minutes|minute|mins|min|ms|m", - "seconds|second|secs|sec|s") - - self.dayname = Daynames("next", "last", - ("monday|mon|mo", "tuesday|tues|tue|tu", - "wednesday|wed|we", "thursday|thur|thu|th", - "friday|fri|fr", "saturday|sat|sa", - "sunday|sun|su")) - - midnight = Regex("midnight", lambda p, dt: adatetime(hour=0, minute=0, second=0, microsecond=0)) - noon = Regex("noon", lambda p, dt: adatetime(hour=12, minute=0, second=0, microsecond=0)) - now = Regex("now", lambda p, dt: dt) - self.time = Choice((self.time12, self.time24, midnight, noon, now), name="time") - - def tomorrow_to_date(p, dt): - d = dt.date() + timedelta(days=+1) - return adatetime(year=d.year, month=d.month, day=d.day) - tomorrow = Regex("tomorrow", tomorrow_to_date) - - def yesterday_to_date(p, dt): - d = dt.date() + timedelta(days=-1) - return adatetime(year=d.year, month=d.month, day=d.day) - yesterday = Regex("yesterday", yesterday_to_date) - - thisyear = Regex("this year", lambda p, dt: adatetime(year=dt.year)) - thismonth = Regex("this month", lambda p, dt: adatetime(year=dt.year, month=dt.month)) - today = Regex("today", lambda p, dt: adatetime(year=dt.year, month=dt.month, day=dt.day)) - - self.month = Month("january|jan", "february|febuary|feb", "march|mar", - "april|apr", "may", "june|jun", "july|jul", "august|aug", - "september|sept|sep", "october|oct", "november|nov", - "december|dec") - - # If you specify a day number you must also specify a month... this - # Choice captures that constraint - - self.dmy = Choice((Sequence((self.day, self.month, self.year), name="dmy"), - Sequence((self.month, self.day, self.year), name="mdy"), - Sequence((self.year, self.month, self.day), name="ymd"), - Sequence((self.year, self.day, self.month), name="ydm"), - Sequence((self.day, self.month), name="dm"), - Sequence((self.month, self.day), name="md"), - Sequence((self.month, self.year), name="my"), - self.month, self.year, self.dayname, tomorrow, - yesterday, thisyear, thismonth, today, now, - ), name="date") - - self.datetime = Bag((self.time, self.dmy), name="datetime") - self.bundle = Choice((self.plusdate, self.datetime, self.simple), name="bundle") - self.torange = Combo((self.bundle, "to", self.bundle), name="torange") - - self.all = Choice((self.torange, self.bundle), name="all") - - -# QueryParser plugin - -class DateParserPlugin(Plugin): - """Adds more powerful parsing of DATETIME fields. - - >>> parser.add_plugin(DateParserPlugin()) - >>> parser.parse(u"date:'last tuesday'") - """ - - def __init__(self, basedate=None, dateparser=None, callback=None, - free=False): - """ - :param basedate: a datetime object representing the current time - against which to measure relative dates. If you do not supply this - argument, the plugin uses ``datetime.utcnow()``. - :param dateparser: an instance of - :class:`whoosh.qparser.dateparse.DateParser`. If you do not supply - this argument, the plugin automatically uses - :class:`whoosh.qparser.dateparse.English`. - :param callback: a callback function for parsing errors. This allows - you to provide feedback to the user about problems parsing dates. - :param remove: if True, unparseable dates are removed from the token - stream instead of being replaced with ErrorToken. - :param loose: if True, this plugin will install a filter early in the - parsing process and try to find undelimited dates such as - ``date:last tuesday``. Note that allowing this could result in - normal query words accidentally being parsed as dates sometimes. - """ - - self.basedate = basedate - if dateparser is None: - dateparser = English() - self.dateparser = dateparser - self.callback = callback - self.free = free - - def tokens(self, parser): - if self.free: - # If we're tokenizing, we have to go before the FieldsPlugin - return ((DateToken, -1), ) - else: - return () - - def filters(self, parser): - # Run the filter after the FieldsPlugin assigns field names - return ((self.do_dates, 110), ) - - def do_dates(self, parser, stream): - schema = parser.schema - if not schema: - return stream - - from whoosh.fields import DATETIME - datefields = frozenset(fieldname for fieldname, field - in parser.schema.items() - if isinstance(field, DATETIME)) - - newstream = stream.empty() - for t in stream: - if isinstance(t, Group): - t = self.do_dates(parser, t) - elif t.fieldname in datefields: - if isinstance(t, Word): - text = t.text - try: - dt = self.dateparser.date_from(text, self.basedate) - if dt is None: - if self.callback: - self.callback(text) - t = ErrorToken(t) - else: - t = DateToken(t.fieldname, dt, t.boost) - except DateParseError, e: - if self.callback: - self.callback("%s (%r)" % (str(e), text)) - t = ErrorToken(t) - - elif isinstance(t, RangePlugin.Range): - start = end = None - error = None - - dp = self.dateparser.get_parser() - - if t.start: - start = dp.date_from(t.start, self.basedate) - if start is None: - error = t.start - if t.end: - end = dp.date_from(t.end, self.basedate) - if end is None and error is None: - error = t.end - - if error is not None: - if self.callback: - self.callback(error) - t = ErrorToken(t) - else: - ts = timespan(start, end).disambiguated(self.basedate) - t = DateToken(t.fieldname, ts, boost=t.boost) - - newstream.append(t) - return newstream - - -class DateToken(BasicSyntax): - expr = re.compile("([A-Za-z][A-Za-z_0-9]*):([^^]+)") - - def __init__(self, fieldname, timeobj, boost=1.0, endpos=None): - self.fieldname = fieldname - self.timeobj = timeobj - self.boost = boost - self.endpos = endpos - - def __repr__(self): - r = "%s:(%r)" % (self.fieldname, self.timeobj) - if self.boost != 1.0: - r + "^%s" % self.boost - return r - - def set_boost(self, b): - return DateParserPlugin.Date(self.fieldname, self.timeobj, boost=b, - endpos=self.endpos) - - def set_fieldname(self, name): - if name is None: raise Exception - return self.__class__(name, self.timeobj, boost=self.boost, - endpos=self.endpos) - - def query(self, parser): - from whoosh import query - - field = parser.schema[self.fieldname] - dt = self.timeobj - if isinstance(self.timeobj, datetime): - return query.Term(self.fieldname, field.to_text(dt), - boost=self.boost) - elif isinstance(self.timeobj, timespan): - return query.DateRange(self.fieldname, dt.start, dt.end, - boost=self.boost) - else: - raise Exception("Unknown time object: %r" % dt) - - @classmethod - def create(cls, parser, match): - fieldname = match.group(1) - if parser.schema and fieldname in parser.schema: - field = parser.schema[fieldname] - - from whoosh.fields import DATETIME - if isinstance(field, DATETIME): - text = match.group(2) - textstart = match.start(2) - - plugin = parser.get_plugin(DateParserPlugin) - dateparser = plugin.dateparser - basedate = plugin.basedate - - d, newpos = dateparser.parse(text, basedate) - if d: - return cls(fieldname, d, endpos=newpos + textstart) - - - - - - - - - - - +#=============================================================================== +# Copyright 2010 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +import calendar, re +from datetime import date, time, datetime, timedelta + +from whoosh.qparser import BasicSyntax, ErrorToken, Plugin, RangePlugin, Group, Word +from whoosh.support.relativedelta import relativedelta +from whoosh.support.times import (adatetime, timespan, fill_in, is_void, + TimeError, relative_days) + + +class DateParseError(Exception): + "Represents an error in parsing date text." + + +# Utility functions + +def rcompile(pattern): + """Just a shortcut to call re.compile with a standard set of flags. + """ + + return re.compile(pattern, re.IGNORECASE | re.UNICODE) + + +def print_debug(level, msg, *args): + if level > 0: print (" " * (level-1)) + (msg % args) + + +# Parser element objects + +class Props(object): + """A dumb little object that just puts copies a dictionary into attibutes + so I can use dot syntax instead of square bracket string item lookup and + save a little bit of typing. Used by :class:`Regex`. + """ + + def __init__(self, **args): + self.__dict__ = args + + def __repr__(self): + return repr(self.__dict__) + + def get(self, key, default=None): + return self.__dict__.get(key, default) + + +class ParserBase(object): + """Base class for date parser elements. + """ + + def to_parser(self, e): + if isinstance(e, basestring): + return Regex(e) + else: + return e + + def parse(self, text, dt, pos=0, debug=-9999): + raise NotImplementedError + + def date_from(self, text, dt=None, pos=0, debug=-9999): + if dt is None: + dt = datetime.now() + + d, pos = self.parse(text, dt, pos, debug + 1) + return d + + +class MultiBase(ParserBase): + """Base class for date parser elements such as Sequence and Bag that + have sub-elements. + """ + + def __init__(self, elements, name=None): + """ + :param elements: the sub-elements to match. + :param name: a name for this element (for debugging purposes only). + """ + + self.elements = [self.to_parser(e) for e in elements] + self.name = name + + def __repr__(self): + return "%s<%s>%r" % (self.__class__.__name__, self.name or '', self.elements) + + +class Sequence(MultiBase): + """Merges the dates parsed by a sequence of sub-elements. + """ + + def __init__(self, elements, sep="(\\s+|\\s*,\\s*)", name=None, + progressive=False): + """ + :param elements: the sequence of sub-elements to parse. + :param sep: a separator regular expression to match between elements, + or None to not have separators. + :param name: a name for this element (for debugging purposes only). + :param progressive: if True, elements after the first do not need to + match. That is, for elements (a, b, c) and progressive=True, the + sequence matches like ``a[b[c]]``. + """ + + super(Sequence, self).__init__(elements, name) + self.sep_pattern = sep + if sep: + self.sep_expr = rcompile(sep) + else: + self.sep_expr = None + self.progressive = progressive + + def parse(self, text, dt, pos=0, debug=-9999): + d = adatetime() + first = True + foundall = False + failed = False + + print_debug(debug, "Seq %s sep=%r text=%r", self.name, self.sep_pattern, text[pos:]) + for e in self.elements: + print_debug(debug, "Seq %s text=%r", self.name, text[pos:]) + if self.sep_expr and not first: + print_debug(debug, "Seq %s looking for sep", self.name) + m = self.sep_expr.match(text, pos) + if m: + pos = m.end() + else: + print_debug(debug, "Seq %s didn't find sep", self.name) + break + + print_debug(debug, "Seq %s trying=%r at=%s", self.name, e, pos) + + try: + at, newpos = e.parse(text, dt, pos=pos, debug=debug + 1) + except TimeError: + failed = True + break + + print_debug(debug, "Seq %s result=%r", self.name, at) + if not at: + break + pos = newpos + + print_debug(debug, "Seq %s adding=%r to=%r", self.name, at, d) + try: + d = fill_in(d, at) + except TimeError: + print_debug(debug, "Seq %s Error in fill_in", self.name) + failed = True + break + print_debug(debug, "Seq %s filled date=%r", self.name, d) + + first = False + else: + foundall = True + + if not failed and (foundall or (not first and self.progressive)): + print_debug(debug, "Seq %s final=%r", self.name, d) + return (d, pos) + else: + print_debug(debug, "Seq %s failed", self.name) + return (None, None) + + +class Combo(Sequence): + """Parses a sequence of elements in order and combines the dates parsed + by the sub-elements somehow. The default behavior is to accept two dates + from the sub-elements and turn them into a range. + """ + + def __init__(self, elements, fn=None, sep="(\\s+|\\s*,\\s*)", min=2, max=2, + name=None): + """ + :param elements: the sequence of sub-elements to parse. + :param fn: a function to run on all dates found. It should return a + datetime, adatetime, or timespan object. If this argument is None, + the default behavior accepts two dates and returns a timespan. + :param sep: a separator regular expression to match between elements, + or None to not have separators. + :param min: the minimum number of dates required from the sub-elements. + :param max: the maximum number of dates allowed from the sub-elements. + :param name: a name for this element (for debugging purposes only). + """ + + super(Combo, self).__init__(elements, sep=sep, name=name) + self.fn = fn + self.min = min + self.max = max + + def parse(self, text, dt, pos=0, debug=-9999): + dates = [] + first = True + + print_debug(debug, "Combo %s sep=%r text=%r", self.name, self.sep_pattern, text[pos:]) + for e in self.elements: + if self.sep_expr and not first: + print_debug(debug, "Combo %s looking for sep at %r", self.name, text[pos:]) + m = self.sep_expr.match(text, pos) + if m: + pos = m.end() + else: + print_debug(debug, "Combo %s didn't find sep", self.name) + return (None, None) + + print_debug(debug, "Combo %s trying=%r", self.name, e) + try: + at, pos = e.parse(text, dt, pos, debug + 1) + except TimeError: + at, pos = None, None + + print_debug(debug, "Combo %s result=%r", self.name, at) + if at is None: + return (None, None) + + first = False + if is_void(at): + continue + if len(dates) == self.max: + print_debug(debug, "Combo %s length > %s", self.name, self.max) + return (None, None) + dates.append(at) + + print_debug(debug, "Combo %s dates=%r", self.name, dates) + if len(dates) < self.min: + print_debug(debug, "Combo %s length < %s", self.name, self.min) + return (None, None) + + return (self.dates_to_timespan(dates), pos) + + def dates_to_timespan(self, dates): + if self.fn: + return self.fn(dates) + elif len(dates) == 2: + return timespan(dates[0], dates[1]) + else: + raise DateParseError("Don't know what to do with %r" % (dates, )) + + +class Choice(MultiBase): + """Returns the date from the first of its sub-elements that matches. + """ + + def parse(self, text, dt, pos=0, debug=-9999): + print_debug(debug, "Choice %s text=%r", self.name, text[pos:]) + for e in self.elements: + print_debug(debug, "Choice %s trying=%r", self.name, e) + + try: + d, newpos = e.parse(text, dt, pos, debug + 1) + except TimeError: + d, newpos = None, None + if d: + print_debug(debug, "Choice %s matched", self.name) + return (d, newpos) + print_debug(debug, "Choice %s no match", self.name) + return (None, None) + + +class Bag(MultiBase): + """Parses its sub-elements in any order and merges the dates. + """ + + def __init__(self, elements, sep="(\\s+|\\s*,\\s*)", onceper=True, + requireall=False, allof=None, anyof=None, name=None): + """ + :param elements: the sub-elements to parse. + :param sep: a separator regular expression to match between elements, + or None to not have separators. + :param onceper: only allow each element to match once. + :param requireall: if True, the sub-elements can match in any order, + but they must all match. + :param allof: a list of indexes into the list of elements. When this + argument is not None, this element matches only if all the + indicated sub-elements match. + :param allof: a list of indexes into the list of elements. When this + argument is not None, this element matches only if any of the + indicated sub-elements match. + :param name: a name for this element (for debugging purposes only). + """ + + super(Bag, self).__init__(elements, name) + self.sep_expr = rcompile(sep) + self.onceper = onceper + self.requireall = requireall + self.allof = allof + self.anyof = anyof + + def parse(self, text, dt, pos=0, debug=-9999): + first = True + d = adatetime() + seen = [False] * len(self.elements) + + while True: + newpos = pos + print_debug(debug, "Bag %s text=%r", self.name, text[pos:]) + if not first: + print_debug(debug, "Bag %s looking for sep", self.name) + m = self.sep_expr.match(text, pos) + if m: + newpos = m.end() + else: + print_debug(debug, "Bag %s didn't find sep", self.name) + break + + for i, e in enumerate(self.elements): + print_debug(debug, "Bag %s trying=%r", self.name, e) + + try: + at, xpos = e.parse(text, dt, newpos, debug + 1) + except TimeError: + at, xpos = None, None + + print_debug(debug, "Bag %s result=%r", self.name, at) + if at: + if self.onceper and seen[i]: + return (None, None) + + d = fill_in(d, at) + newpos = xpos + seen[i] = True + break + else: + break + + pos = newpos + if self.onceper and all(seen): + break + + first = False + + if (not any(seen) + or (self.allof and not all(seen[pos] for pos in self.allof)) + or (self.anyof and not any(seen[pos] for pos in self.anyof)) + or (self.requireall and not all(seen))): + return (None, None) + + print_debug(debug, "Bag %s final=%r", self.name, d) + return (d, pos) + + +class Optional(ParserBase): + """Wraps a sub-element to indicate that the sub-element is optional. + """ + + def __init__(self, element): + self.element = self.to_parser(element) + + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self.element) + + def parse(self, text, dt, pos=0, debug=-9999): + try: + d, pos = self.element.parse(text, dt, pos, debug + 1) + except TimeError: + d, pos = None, None + + if d: + return (d, pos) + else: + return (adatetime(), pos) + + +class ToEnd(ParserBase): + """Wraps a sub-element and requires that the end of the sub-element's match + be the end of the text. + """ + + def __init__(self, element): + self.element = element + + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self.element) + + def parse(self, text, dt, pos=0, debug=-9999): + try: + d, pos = self.element.parse(text, dt, pos, debug + 1) + except TimeError: + d, pos = None, None + + if d and pos == len(text): + return (d, pos) + else: + return (None, None) + + +class Regex(ParserBase): + """Matches a regular expression and maps named groups in the pattern to + datetime attributes using a function or overridden method. + + There are two points at which you can customize the behavior of this class, + either by supplying functions to the initializer or overriding methods. + + * The ``modify`` function or ``modify_props`` method takes a ``Props`` + object containing the named groups and modifies its values (in place). + * The ``fn`` function or ``props_to_date`` method takes a ``Props`` object + and the base datetime and returns an adatetime/datetime. + """ + + fn = None + modify = None + + def __init__(self, pattern, fn=None, modify=None): + self.pattern = pattern + self.expr = rcompile(pattern) + self.fn = fn + self.modify = modify + + def __repr__(self): + return "<%r>" % (self.pattern, ) + + def parse(self, text, dt, pos=0, debug=-9999): + m = self.expr.match(text, pos) + if not m: + return (None, None) + + props = self.extract(m) + self.modify_props(props) + + try: + d = self.props_to_date(props, dt) + except TimeError: + d = None + + if d: + return (d, m.end()) + else: + return (None, None) + + def extract(self, match): + d = match.groupdict() + for key, value in d.iteritems(): + try: + value = int(value) + d[key] = value + except (ValueError, TypeError): + pass + return Props(**d) + + def modify_props(self, props): + if self.modify: + self.modify(props) + + def props_to_date(self, props, dt): + if self.fn: + return self.fn(props, dt) + else: + args = {} + for key in adatetime.units: + args[key] = props.get(key) + return adatetime(**args) + + +class Month(Regex): + def __init__(self, *patterns): + self.patterns = patterns + self.exprs = [rcompile(pat) for pat in self.patterns] + + self.pattern = ("(?P" + + "|".join("(%s)" % pat for pat in self.patterns) + + ")") + self.expr = rcompile(self.pattern) + + def modify_props(self, p): + text = p.month + for i, expr in enumerate(self.exprs): + m = expr.match(text) + if m: + p.month = i + 1 + break + + +class PlusMinus(Regex): + def __init__(self, years, months, weeks, days, hours, minutes, seconds): + rel_years = "((?P[0-9]+) *(%s))?" % years + rel_months = "((?P[0-9]+) *(%s))?" % months + rel_weeks = "((?P[0-9]+) *(%s))?" % weeks + rel_days = "((?P[0-9]+) *(%s))?" % days + rel_hours = "((?P[0-9]+) *(%s))?" % hours + rel_mins = "((?P[0-9]+) *(%s))?" % minutes + rel_secs = "((?P[0-9]+) *(%s))?" % seconds + + self.pattern = ("(?P[+-]) *%s *%s *%s *%s *%s *%s *%s(?=(\\W|$))" + % (rel_years, rel_months, rel_weeks, rel_days, + rel_hours, rel_mins, rel_secs)) + self.expr = rcompile(self.pattern) + + def props_to_date(self, p, dt): + if p.dir == "-": + dir = -1 + else: + dir = 1 + + delta = relativedelta(years=(p.get("years") or 0) * dir, + months=(p.get("months") or 0) * dir, + weeks=(p.get("weeks") or 0) * dir, + days=(p.get("days") or 0) * dir, + hours=(p.get("hours") or 0) * dir, + minutes=(p.get("mins") or 0) * dir, + seconds=(p.get("secs") or 0) * dir) + return dt + delta + + +class Daynames(Regex): + def __init__(self, next, last, daynames): + self.next_pattern = next + self.last_pattern = last + self._dayname_exprs = tuple(rcompile(pat) for pat in daynames) + dn_pattern = "|".join(daynames) + self.pattern = "(?P%s|%s) +(?P%s)(?=(\\W|$))" % (next, last, dn_pattern) + self.expr = rcompile(self.pattern) + + def props_to_date(self, p, dt): + if re.match(p.dir, self.last_pattern): + dir = -1 + else: + dir = 1 + + for daynum, expr in enumerate(self._dayname_exprs): + m = expr.match(p.day) + if m: + break + current_daynum = dt.weekday() + days_delta = relative_days(current_daynum, daynum, dir) + + d = dt.date() + timedelta(days=days_delta) + return adatetime(year=d.year, month=d.month, day=d.day) + + +class Time12(Regex): + def __init__(self): + self.pattern = "(?P[1-9]|10|11|12)(:(?P[0-5][0-9])(:(?P[0-5][0-9])(\\.(?P[0-9]{1,5}))?)?)?\\s*(?Pam|pm)(?=(\\W|$))" + self.expr = rcompile(self.pattern) + + def props_to_date(self, p, dt): + isam = p.ampm.lower().startswith("a") + + if p.hour == 12: + if isam: + hr = 0 + else: + hr = 12 + else: + hr = p.hour + if not isam: + hr += 12 + + return adatetime(hour=hr, minute=p.mins, second=p.secs, microsecond=p.usecs) + + +# Top-level parser classes + +class DateParser(object): + """Base class for locale-specific parser classes. + """ + + day = Regex("(?P([123][0-9])|[1-9])(?=(\\W|$))(?!=:)", + lambda p, dt: adatetime(day=p.day)) + year = Regex("(?P[0-9]{4})(?=(\\W|$))", + lambda p, dt: adatetime(year=p.year)) + time24 = Regex("(?P([0-1][0-9])|(2[0-3])):(?P[0-5][0-9])(:(?P[0-5][0-9])(\\.(?P[0-9]{1,5}))?)?(?=(\\W|$))", + lambda p, dt: adatetime(hour=p.hour, minute=p.mins, second=p.secs, microsecond=p.usecs)) + time12 = Time12() + + def __init__(self): + simple_year = "(?P[0-9]{4})" + simple_month = "(?P[0-1][0-9])" + simple_day = "(?P[0-3][0-9])" + simple_hour = "(?P([0-1][0-9])|(2[0-3]))" + simple_minute = "(?P[0-5][0-9])" + simple_second = "(?P[0-5][0-9])" + simple_usec = "(?P[0-9]{6})" + + simple_seq = Sequence((simple_year, simple_month, simple_day, simple_hour, + simple_minute, simple_second, simple_usec), + sep="[- .:/]*", name="simple", progressive=True) + self.simple = Sequence((simple_seq, "(?=(\\s|$))"), sep='') + + self.setup() + + def setup(self): + raise NotImplementedError + + # + + def get_parser(self): + return self.all + + def parse(self, text, dt, pos=0, debug=-9999): + parser = self.get_parser() + + d, newpos = parser.parse(text, dt, pos=pos, debug=debug) + if isinstance(d, (adatetime, timespan)): + d = d.disambiguated(dt) + + return (d, newpos) + + def date_from(self, text, basedate=None, pos=0, debug=-9999, toend=True): + if basedate is None: + basedate = datetime.utcnow() + + parser = self.get_parser() + if toend: + parser = ToEnd(parser) + + d = parser.date_from(text, basedate, pos=pos, debug=debug) + if isinstance(d, (adatetime, timespan)): + d = d.disambiguated(basedate) + return d + + + +class English(DateParser): + day = Regex("(?P([123][0-9])|[1-9])(st|nd|rd|th)?(?=(\\W|$))", + lambda p, dt: adatetime(day=p.day)) + + def setup(self): + self.plusdate = PlusMinus("years|year|yrs|yr|ys|y", + "months|month|mons|mon|mos|mo", + "weeks|week|wks|wk|ws|w", + "days|day|dys|dy|ds|d", + "hours|hour|hrs|hr|hs|h", + "minutes|minute|mins|min|ms|m", + "seconds|second|secs|sec|s") + + self.dayname = Daynames("next", "last", + ("monday|mon|mo", "tuesday|tues|tue|tu", + "wednesday|wed|we", "thursday|thur|thu|th", + "friday|fri|fr", "saturday|sat|sa", + "sunday|sun|su")) + + midnight = Regex("midnight", lambda p, dt: adatetime(hour=0, minute=0, second=0, microsecond=0)) + noon = Regex("noon", lambda p, dt: adatetime(hour=12, minute=0, second=0, microsecond=0)) + now = Regex("now", lambda p, dt: dt) + self.time = Choice((self.time12, self.time24, midnight, noon, now), name="time") + + def tomorrow_to_date(p, dt): + d = dt.date() + timedelta(days=+1) + return adatetime(year=d.year, month=d.month, day=d.day) + tomorrow = Regex("tomorrow", tomorrow_to_date) + + def yesterday_to_date(p, dt): + d = dt.date() + timedelta(days=-1) + return adatetime(year=d.year, month=d.month, day=d.day) + yesterday = Regex("yesterday", yesterday_to_date) + + thisyear = Regex("this year", lambda p, dt: adatetime(year=dt.year)) + thismonth = Regex("this month", lambda p, dt: adatetime(year=dt.year, month=dt.month)) + today = Regex("today", lambda p, dt: adatetime(year=dt.year, month=dt.month, day=dt.day)) + + self.month = Month("january|jan", "february|febuary|feb", "march|mar", + "april|apr", "may", "june|jun", "july|jul", "august|aug", + "september|sept|sep", "october|oct", "november|nov", + "december|dec") + + # If you specify a day number you must also specify a month... this + # Choice captures that constraint + + self.dmy = Choice((Sequence((self.day, self.month, self.year), name="dmy"), + Sequence((self.month, self.day, self.year), name="mdy"), + Sequence((self.year, self.month, self.day), name="ymd"), + Sequence((self.year, self.day, self.month), name="ydm"), + Sequence((self.day, self.month), name="dm"), + Sequence((self.month, self.day), name="md"), + Sequence((self.month, self.year), name="my"), + self.month, self.year, self.dayname, tomorrow, + yesterday, thisyear, thismonth, today, now, + ), name="date") + + self.datetime = Bag((self.time, self.dmy), name="datetime") + self.bundle = Choice((self.plusdate, self.datetime, self.simple), name="bundle") + self.torange = Combo((self.bundle, "to", self.bundle), name="torange") + + self.all = Choice((self.torange, self.bundle), name="all") + + +# QueryParser plugin + +class DateParserPlugin(Plugin): + """Adds more powerful parsing of DATETIME fields. + + >>> parser.add_plugin(DateParserPlugin()) + >>> parser.parse(u"date:'last tuesday'") + """ + + def __init__(self, basedate=None, dateparser=None, callback=None, + free=False): + """ + :param basedate: a datetime object representing the current time + against which to measure relative dates. If you do not supply this + argument, the plugin uses ``datetime.utcnow()``. + :param dateparser: an instance of + :class:`whoosh.qparser.dateparse.DateParser`. If you do not supply + this argument, the plugin automatically uses + :class:`whoosh.qparser.dateparse.English`. + :param callback: a callback function for parsing errors. This allows + you to provide feedback to the user about problems parsing dates. + :param remove: if True, unparseable dates are removed from the token + stream instead of being replaced with ErrorToken. + :param loose: if True, this plugin will install a filter early in the + parsing process and try to find undelimited dates such as + ``date:last tuesday``. Note that allowing this could result in + normal query words accidentally being parsed as dates sometimes. + """ + + self.basedate = basedate + if dateparser is None: + dateparser = English() + self.dateparser = dateparser + self.callback = callback + self.free = free + + def tokens(self, parser): + if self.free: + # If we're tokenizing, we have to go before the FieldsPlugin + return ((DateToken, -1), ) + else: + return () + + def filters(self, parser): + # Run the filter after the FieldsPlugin assigns field names + return ((self.do_dates, 110), ) + + def do_dates(self, parser, stream): + schema = parser.schema + if not schema: + return stream + + from whoosh.fields import DATETIME + datefields = frozenset(fieldname for fieldname, field + in parser.schema.items() + if isinstance(field, DATETIME)) + + newstream = stream.empty() + for t in stream: + if isinstance(t, Group): + t = self.do_dates(parser, t) + elif t.fieldname in datefields: + if isinstance(t, Word): + text = t.text + try: + dt = self.dateparser.date_from(text, self.basedate) + if dt is None: + if self.callback: + self.callback(text) + t = ErrorToken(t) + else: + t = DateToken(t.fieldname, dt, t.boost) + except DateParseError, e: + if self.callback: + self.callback("%s (%r)" % (str(e), text)) + t = ErrorToken(t) + + elif isinstance(t, RangePlugin.Range): + start = end = None + error = None + + dp = self.dateparser.get_parser() + + if t.start: + start = dp.date_from(t.start, self.basedate) + if start is None: + error = t.start + if t.end: + end = dp.date_from(t.end, self.basedate) + if end is None and error is None: + error = t.end + + if error is not None: + if self.callback: + self.callback(error) + t = ErrorToken(t) + else: + ts = timespan(start, end).disambiguated(self.basedate) + t = DateToken(t.fieldname, ts, boost=t.boost) + + newstream.append(t) + return newstream + + +class DateToken(BasicSyntax): + expr = re.compile("([A-Za-z][A-Za-z_0-9]*):([^^]+)") + + def __init__(self, fieldname, timeobj, boost=1.0, endpos=None): + self.fieldname = fieldname + self.timeobj = timeobj + self.boost = boost + self.endpos = endpos + + def __repr__(self): + r = "%s:(%r)" % (self.fieldname, self.timeobj) + if self.boost != 1.0: + r + "^%s" % self.boost + return r + + def set_boost(self, b): + return DateParserPlugin.Date(self.fieldname, self.timeobj, boost=b, + endpos=self.endpos) + + def set_fieldname(self, name): + if name is None: raise Exception + return self.__class__(name, self.timeobj, boost=self.boost, + endpos=self.endpos) + + def query(self, parser): + from whoosh import query + + field = parser.schema[self.fieldname] + dt = self.timeobj + if isinstance(self.timeobj, datetime): + return query.Term(self.fieldname, field.to_text(dt), + boost=self.boost) + elif isinstance(self.timeobj, timespan): + return query.DateRange(self.fieldname, dt.start, dt.end, + boost=self.boost) + else: + raise Exception("Unknown time object: %r" % dt) + + @classmethod + def create(cls, parser, match): + fieldname = match.group(1) + if parser.schema and fieldname in parser.schema: + field = parser.schema[fieldname] + + from whoosh.fields import DATETIME + if isinstance(field, DATETIME): + text = match.group(2) + textstart = match.start(2) + + plugin = parser.get_plugin(DateParserPlugin) + dateparser = plugin.dateparser + basedate = plugin.basedate + + d, newpos = dateparser.parse(text, basedate) + if d: + return cls(fieldname, d, endpos=newpos + textstart) + + + + + + + + + + + diff --git a/lib/whoosh/ramdb/ramindex.py b/lib/whoosh/ramdb/ramindex.py index 443c23cf..1a44c40f 100644 --- a/lib/whoosh/ramdb/ramindex.py +++ b/lib/whoosh/ramdb/ramindex.py @@ -1,305 +1,305 @@ -#=============================================================================== -# Copyright 2009 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -from collections import defaultdict -from threading import Lock - -from whoosh.fields import UnknownFieldError -from whoosh.index import Index -from whoosh.ramdb.ramreading import RamIndexReader -from whoosh.util import protected - - -class RamIndex(Index): - def __init__(self, schema): - self.schema = schema - self.docnum = 0 - self._sync_lock = Lock() - self.is_closed = False - - self.clear() - - def clear(self): - # Maps fieldname -> a sorted list of term texts in that field - self.termlists = defaultdict(list) - - # Maps fieldnames to dictionaries of term -> posting list - self.invertedindex = {} - for fieldname in self.schema.names(): - self.invertedindex[fieldname] = defaultdict(list) - - # Maps terms -> index frequencies - self.indexfreqs = defaultdict(int) - - # Maps docnum -> stored field dicts - self.storedfields = {} - - # Maps (docnum, fieldname) -> field length - self.fieldlengths = defaultdict(int) - - # Maps (docnum, fieldname) -> posting list - self.vectors = {} - - # Contains docnums of deleted documents - self.deleted = set() - - def close(self): - del self.termlists - del self.invertedindex - del self.indexfreqs - del self.storedfields - del self.fieldlengths - del self.vectors - del self.deleted - self.is_closed = True - - def doc_count_all(self): - return len(self.storedfields) - - def doc_count(self): - return len(self.storedfields) - len(self.deleted) - - def field_length(self, fieldname): - return sum(l for docnum_fieldname, l in self.fieldlengths.iteritems() - if docnum_fieldname[1] == fieldname) - - def max_field_length(self, fieldname): - return max(l for docnum_fieldname, l in self.fieldlengths.iteritems() - if docnum_fieldname[1] == fieldname) - - def reader(self): - return RamIndexReader(self) - - def writer(self): - return self - - @protected - def add_field(self, *args, **kwargs): - self.schema.add_field(*args, **kwargs) - - @protected - def remove_field(self, fieldname): - self.schema.remove_field(fieldname) - if fieldname in self.termlists: - del self.termlists[fieldname] - for fn, text in self.indexfreqs.iterkeys(): - if fn == fieldname: - del self.indexfreqs[(fn, text)] - for sfields in self.storedfields.itervalues(): - if fieldname in sfields: - del sfields[fieldname] - for docnum, fn in self.fieldlengths.iterkeys(): - if fn == fieldname: - del self.fieldlengths[(docnum, fn)] - if fieldname in self.fieldlength_maxes: - del self.fieldlength_maxes[fieldname] - for docnum, fn in self.vectors.iterkeys(): - if fn == fieldname: - del self.vectors[(docnum, fn)] - - @protected - def delete_document(self, docnum, delete=True): - if delete: - self.deleted.add(docnum) - else: - self.deleted.remove(docnum) - - @protected - def delete_by_term(self, fieldname, text): - inv = self.invertedindex - if fieldname in inv: - terms = inv[fieldname] - if text in terms: - postings = terms[text] - for p in postings: - self.deleted.add(p[0]) - - @protected - def delete_by_query(self, q, searcher=None): - s = self.searcher() - for docnum in q.docs(s): - self.deleted.add(docnum) - - def has_deletions(self): - return bool(self.deleted) - - @protected - def optimize(self): - deleted = self.deleted - - # Remove deleted documents from stored fields - storedfields = self.storedfields - for docnum in deleted: - del storedfields[docnum] - - # Remove deleted documents from inverted index - removedterms = defaultdict(set) - for fieldname in self.schema.names(): - inv = self.invertedindex[fieldname] - for term, postlist in inv.iteritems(): - inv[term] = [x for x in postlist if x[0] not in deleted] - - # Remove terms that no longer have any postings after the - # documents are deleted - for term in inv.keys(): - if not inv[term]: - removedterms[fieldname].add(term) - del inv[term] - - # If terms were removed as a result of document deletion, - # update termlists and indexfreqs - termlists = self.termlists - for fieldname, removed in removedterms.iteritems(): - termlists[fieldname] = [t for t in termlists[fieldname] - if t not in removed] - for text in removed: - del self.indexfreqs[(fieldname, text)] - - # Remove documents from field lengths - fieldlengths = self.fieldlengths - for docnum, fieldname in fieldlengths.keys(): - if docnum in deleted: - del fieldlengths[(docnum, fieldname)] - - # Remove documents from vectors - vectors = self.vectors - for docnum, fieldname in vectors.keys(): - if docnum in deleted: - del vectors[(docnum, fieldname)] - - # Reset deleted list - self.deleted = set() - - @protected - def add_document(self, **fields): - schema = self.schema - invertedindex = self.invertedindex - indexfreqs = self.indexfreqs - fieldlengths = self.fieldlengths - - fieldnames = [name for name in sorted(fields.keys()) - if not name.startswith("_")] - - storedvalues = {} - - for name in fieldnames: - if name not in schema: - raise UnknownFieldError("There is no field named %r" % name) - - for name in fieldnames: - value = fields.get(name) - if value: - field = schema[name] - - newwords = set() - fielddict = invertedindex[name] - - # If the field is indexed, add the words in the value to the - # index - if field.indexed: - # Count of all terms in the value - count = 0 - # Count of UNIQUE terms in the value - unique = 0 - - for w, freq, weight, valuestring in field.index(value): - if w not in fielddict: - newwords.add(w) - fielddict[w].append((self.docnum, weight, valuestring)) - indexfreqs[(name, w)] += freq - count += freq - unique += 1 - - self.termlists[name] = sorted(set(self.termlists[name]) | newwords) - - if field.scorable: - fieldlengths[(self.docnum, name)] = count - - vector = field.vector - if vector: - vlist = sorted((w, weight, valuestring) for w, freq, weight, valuestring - in vector.word_values(value)) - self.vectors[(self.docnum, name)] = vlist - - if field.stored: - storedname = "_stored_" + name - if storedname in fields: - stored_value = fields[storedname] - else : - stored_value = value - - storedvalues[name] = stored_value - - self.storedfields[self.docnum] = storedvalues - self.docnum += 1 - - @protected - def add_reader(self, reader): - startdoc = self.docnum - - has_deletions = reader.has_deletions() - if has_deletions: - docmap = {} - - fieldnames = set(self.schema.names()) - - for docnum in xrange(reader.doc_count_all()): - if (not has_deletions) or (not reader.is_deleted(docnum)): - d = dict(item for item - in reader.stored_fields(docnum).iteritems() - if item[0] in fieldnames) - self.storedfields[self.docnum] = d - - if has_deletions: - docmap[docnum] = self.docnum - - for fieldname, length in reader.doc_field_lengths(docnum): - if fieldname in fieldnames: - self.fieldlengths[(self.docnum, fieldname)] = length - - for fieldname in reader.vector_names(): - if (fieldname in fieldnames - and reader.has_vector(docnum, fieldname)): - vpostreader = reader.vector(docnum, fieldname) - self.vectors[(self.docnum, fieldname)] = list(vpostreader.all_items()) - vpostreader.close() - - self.docnum += 1 - - for fieldname, text, _, _ in reader: - if fieldname in fieldnames: - postreader = reader.postings(fieldname, text) - while postreader.is_active(): - docnum = postreader.id() - valuestring = postreader.value() - weight = postreader.weight() - if has_deletions: - newdoc = docmap[docnum] - else: - newdoc = startdoc + docnum - self.invertedindex[fieldname][text].append((newdoc, - weight, - valuestring)) - postreader.next() - - - - - - - - +#=============================================================================== +# Copyright 2009 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +from collections import defaultdict +from threading import Lock + +from whoosh.fields import UnknownFieldError +from whoosh.index import Index +from whoosh.ramdb.ramreading import RamIndexReader +from whoosh.util import protected + + +class RamIndex(Index): + def __init__(self, schema): + self.schema = schema + self.docnum = 0 + self._sync_lock = Lock() + self.is_closed = False + + self.clear() + + def clear(self): + # Maps fieldname -> a sorted list of term texts in that field + self.termlists = defaultdict(list) + + # Maps fieldnames to dictionaries of term -> posting list + self.invertedindex = {} + for fieldname in self.schema.names(): + self.invertedindex[fieldname] = defaultdict(list) + + # Maps terms -> index frequencies + self.indexfreqs = defaultdict(int) + + # Maps docnum -> stored field dicts + self.storedfields = {} + + # Maps (docnum, fieldname) -> field length + self.fieldlengths = defaultdict(int) + + # Maps (docnum, fieldname) -> posting list + self.vectors = {} + + # Contains docnums of deleted documents + self.deleted = set() + + def close(self): + del self.termlists + del self.invertedindex + del self.indexfreqs + del self.storedfields + del self.fieldlengths + del self.vectors + del self.deleted + self.is_closed = True + + def doc_count_all(self): + return len(self.storedfields) + + def doc_count(self): + return len(self.storedfields) - len(self.deleted) + + def field_length(self, fieldname): + return sum(l for docnum_fieldname, l in self.fieldlengths.iteritems() + if docnum_fieldname[1] == fieldname) + + def max_field_length(self, fieldname): + return max(l for docnum_fieldname, l in self.fieldlengths.iteritems() + if docnum_fieldname[1] == fieldname) + + def reader(self): + return RamIndexReader(self) + + def writer(self): + return self + + @protected + def add_field(self, *args, **kwargs): + self.schema.add_field(*args, **kwargs) + + @protected + def remove_field(self, fieldname): + self.schema.remove_field(fieldname) + if fieldname in self.termlists: + del self.termlists[fieldname] + for fn, text in self.indexfreqs.iterkeys(): + if fn == fieldname: + del self.indexfreqs[(fn, text)] + for sfields in self.storedfields.itervalues(): + if fieldname in sfields: + del sfields[fieldname] + for docnum, fn in self.fieldlengths.iterkeys(): + if fn == fieldname: + del self.fieldlengths[(docnum, fn)] + if fieldname in self.fieldlength_maxes: + del self.fieldlength_maxes[fieldname] + for docnum, fn in self.vectors.iterkeys(): + if fn == fieldname: + del self.vectors[(docnum, fn)] + + @protected + def delete_document(self, docnum, delete=True): + if delete: + self.deleted.add(docnum) + else: + self.deleted.remove(docnum) + + @protected + def delete_by_term(self, fieldname, text): + inv = self.invertedindex + if fieldname in inv: + terms = inv[fieldname] + if text in terms: + postings = terms[text] + for p in postings: + self.deleted.add(p[0]) + + @protected + def delete_by_query(self, q, searcher=None): + s = self.searcher() + for docnum in q.docs(s): + self.deleted.add(docnum) + + def has_deletions(self): + return bool(self.deleted) + + @protected + def optimize(self): + deleted = self.deleted + + # Remove deleted documents from stored fields + storedfields = self.storedfields + for docnum in deleted: + del storedfields[docnum] + + # Remove deleted documents from inverted index + removedterms = defaultdict(set) + for fieldname in self.schema.names(): + inv = self.invertedindex[fieldname] + for term, postlist in inv.iteritems(): + inv[term] = [x for x in postlist if x[0] not in deleted] + + # Remove terms that no longer have any postings after the + # documents are deleted + for term in inv.keys(): + if not inv[term]: + removedterms[fieldname].add(term) + del inv[term] + + # If terms were removed as a result of document deletion, + # update termlists and indexfreqs + termlists = self.termlists + for fieldname, removed in removedterms.iteritems(): + termlists[fieldname] = [t for t in termlists[fieldname] + if t not in removed] + for text in removed: + del self.indexfreqs[(fieldname, text)] + + # Remove documents from field lengths + fieldlengths = self.fieldlengths + for docnum, fieldname in fieldlengths.keys(): + if docnum in deleted: + del fieldlengths[(docnum, fieldname)] + + # Remove documents from vectors + vectors = self.vectors + for docnum, fieldname in vectors.keys(): + if docnum in deleted: + del vectors[(docnum, fieldname)] + + # Reset deleted list + self.deleted = set() + + @protected + def add_document(self, **fields): + schema = self.schema + invertedindex = self.invertedindex + indexfreqs = self.indexfreqs + fieldlengths = self.fieldlengths + + fieldnames = [name for name in sorted(fields.keys()) + if not name.startswith("_")] + + storedvalues = {} + + for name in fieldnames: + if name not in schema: + raise UnknownFieldError("There is no field named %r" % name) + + for name in fieldnames: + value = fields.get(name) + if value: + field = schema[name] + + newwords = set() + fielddict = invertedindex[name] + + # If the field is indexed, add the words in the value to the + # index + if field.indexed: + # Count of all terms in the value + count = 0 + # Count of UNIQUE terms in the value + unique = 0 + + for w, freq, weight, valuestring in field.index(value): + if w not in fielddict: + newwords.add(w) + fielddict[w].append((self.docnum, weight, valuestring)) + indexfreqs[(name, w)] += freq + count += freq + unique += 1 + + self.termlists[name] = sorted(set(self.termlists[name]) | newwords) + + if field.scorable: + fieldlengths[(self.docnum, name)] = count + + vector = field.vector + if vector: + vlist = sorted((w, weight, valuestring) for w, freq, weight, valuestring + in vector.word_values(value)) + self.vectors[(self.docnum, name)] = vlist + + if field.stored: + storedname = "_stored_" + name + if storedname in fields: + stored_value = fields[storedname] + else : + stored_value = value + + storedvalues[name] = stored_value + + self.storedfields[self.docnum] = storedvalues + self.docnum += 1 + + @protected + def add_reader(self, reader): + startdoc = self.docnum + + has_deletions = reader.has_deletions() + if has_deletions: + docmap = {} + + fieldnames = set(self.schema.names()) + + for docnum in xrange(reader.doc_count_all()): + if (not has_deletions) or (not reader.is_deleted(docnum)): + d = dict(item for item + in reader.stored_fields(docnum).iteritems() + if item[0] in fieldnames) + self.storedfields[self.docnum] = d + + if has_deletions: + docmap[docnum] = self.docnum + + for fieldname, length in reader.doc_field_lengths(docnum): + if fieldname in fieldnames: + self.fieldlengths[(self.docnum, fieldname)] = length + + for fieldname in reader.vector_names(): + if (fieldname in fieldnames + and reader.has_vector(docnum, fieldname)): + vpostreader = reader.vector(docnum, fieldname) + self.vectors[(self.docnum, fieldname)] = list(vpostreader.all_items()) + vpostreader.close() + + self.docnum += 1 + + for fieldname, text, _, _ in reader: + if fieldname in fieldnames: + postreader = reader.postings(fieldname, text) + while postreader.is_active(): + docnum = postreader.id() + valuestring = postreader.value() + weight = postreader.weight() + if has_deletions: + newdoc = docmap[docnum] + else: + newdoc = startdoc + docnum + self.invertedindex[fieldname][text].append((newdoc, + weight, + valuestring)) + postreader.next() + + + + + + + + \ No newline at end of file diff --git a/lib/whoosh/ramdb/ramreading.py b/lib/whoosh/ramdb/ramreading.py index 8cb79f4b..2398a48c 100644 --- a/lib/whoosh/ramdb/ramreading.py +++ b/lib/whoosh/ramdb/ramreading.py @@ -1,229 +1,229 @@ -#=============================================================================== -# Copyright 2009 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -from bisect import bisect_left -from time import clock as now - -from whoosh.matching import Matcher, ReadTooFar -from whoosh.reading import IndexReader - - -class RamIndexReader(IndexReader): - def __init__(self, ix): - self.ix = ix - self.is_closed = False - self.schema = ix.schema - - def __contains__(self, term): - fieldname, text = term - inv = self.ix.invertedindex - return fieldname in inv and text in inv[fieldname] - - def close(self): - del self.ix - self._is_closed = True - - def has_deletions(self): - return len(self.ix.deleted) > 0 - - def is_deleted(self, docnum): - return docnum in self.ix.deleted - - def stored_fields(self, docnum): - return self.ix.storedfields[docnum] - - def all_stored_fields(self): - sfs = self.ix.storedfields - for docnum in xrange(self.ix.doc_count_all()): - if docnum in sfs: - yield sfs[docnum] - - def doc_count_all(self): - return self.ix.doc_count_all() - - def doc_count(self): - return self.ix.doc_count() - - def field_length(self, fieldname): - return self.ix.field_length(fieldname) - - def max_field_length(self, fieldname): - return self.ix.max_field_length(fieldname) - - def doc_field_length(self, docnum, fieldname, default=0): - return self.ix.fieldlengths.get((docnum, fieldname), default) - - def has_vector(self, docnum, fieldname): - return (docnum, fieldname) in self.ix.vectors - - def vector(self, docnum, fieldname): - vformat = self.schema[fieldname].vector - return RamPostingReader(vformat, self.ix.vectors[(docnum, fieldname)]) - - def __iter__(self): - tls = self.ix.termlists - inv = self.ix.invertedindex - ixf = self.ix.indexfreqs - for fieldname in sorted(tls.iterkeys()): - for text in tls[fieldname]: - docfreq = len(inv[fieldname][text]) - indexfreq = ixf[(fieldname, text)] - yield (fieldname, text, docfreq, indexfreq) - - def doc_frequency(self, fieldname, text): - return len(self.ix.invertedindex[fieldname][text]) - - def frequency(self, fieldname, text): - return self.ix.indexfreqs[(fieldname, text)] - - def iter_from(self, fieldname, text): - tls = self.ix.termlists - inv = self.ix.invertedindex - ixf = self.ix.indexfreqs - - fnms = sorted(fn for fn in tls.iterkeys() if fn >= fieldname) - for fn in fnms: - texts = tls[fn] - start = 0 - if fn == fieldname: - start = bisect_left(texts, text) - - for text in texts[start:]: - docfreq = len(inv[fieldname][text]) - indexfreq = ixf[(fieldname, text)] - yield (fieldname, text, docfreq, indexfreq) - - def lexicon(self, fieldname): - return self.ix.termlists[fieldname] - - def iter_field(self, fieldname, prefix=''): - inv = self.ix.invertedindex - ixf = self.ix.indexfreqs - - fieldtexts = self.ix.termlists[fieldname] - start = bisect_left(fieldtexts, prefix) - for text in fieldtexts[start:]: - docfreq = len(inv[fieldname][text]) - indexfreq = ixf[(fieldname, text)] - yield (text, docfreq, indexfreq) - - def expand_prefix(self, fieldname, prefix): - fieldtexts = self.ix.termlists[fieldname] - start = bisect_left(fieldtexts, prefix) - for text in fieldtexts[start:]: - if text.startswith(prefix): - yield text - else: - break - - def postings(self, fieldname, text, exclude_docs=None, scorer=None): - if not exclude_docs: - exclude_docs = frozenset() - excludeset = self.ix.deleted | exclude_docs - inv = self.ix.invertedindex - format = self.schema[fieldname].format - postings = inv[fieldname][text] - if excludeset: - postings = [(docnum, weight, stringvalue) for docnum, weight, stringvalue - in postings if docnum not in excludeset] - return RamPostingReader(format, postings, scorer=scorer) - - -class RamPostingReader(Matcher): - def __init__(self, format, postings, scorer=None, stringids=False): - self.format = format - self.postings = postings - self.i = 0 - - self.scorer = scorer - - def is_active(self): - return self.i < len(self.postings) - - def id(self): - return self.postings[self.i][0] - - def all_items(self): - return self.postings - - def all_ids(self): - return (x[0] for x in self.postings) - - def next(self): - if not self.is_active(): - raise ReadTooFar - self.i += 1 - - def skip_to(self, target): - if not self.is_active(): - raise ReadTooFar - - if target <= self.id: - return - - postings = self.postings - i = self.i - - while i < len(postings): - i += 1 - if i == len(postings): - break - elif postings[i][0] >= target: - break - - self.i = i - - def weight(self): - if not self.is_active(): - raise ReadTooFar - - return self.postings[self.i][1] - - def value(self): - if not self.is_active(): - raise ReadTooFar - - return self.postings[self.i][2] - - def score(self): - return self.scorer.score(self) - - def quality(self): - return self.scorer.quality(self) - - def block_quality(self): - return self.scorer.block_quality(self) - - - - - - - - - - - - - - - - - - - - - +#=============================================================================== +# Copyright 2009 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +from bisect import bisect_left +from time import clock as now + +from whoosh.matching import Matcher, ReadTooFar +from whoosh.reading import IndexReader + + +class RamIndexReader(IndexReader): + def __init__(self, ix): + self.ix = ix + self.is_closed = False + self.schema = ix.schema + + def __contains__(self, term): + fieldname, text = term + inv = self.ix.invertedindex + return fieldname in inv and text in inv[fieldname] + + def close(self): + del self.ix + self._is_closed = True + + def has_deletions(self): + return len(self.ix.deleted) > 0 + + def is_deleted(self, docnum): + return docnum in self.ix.deleted + + def stored_fields(self, docnum): + return self.ix.storedfields[docnum] + + def all_stored_fields(self): + sfs = self.ix.storedfields + for docnum in xrange(self.ix.doc_count_all()): + if docnum in sfs: + yield sfs[docnum] + + def doc_count_all(self): + return self.ix.doc_count_all() + + def doc_count(self): + return self.ix.doc_count() + + def field_length(self, fieldname): + return self.ix.field_length(fieldname) + + def max_field_length(self, fieldname): + return self.ix.max_field_length(fieldname) + + def doc_field_length(self, docnum, fieldname, default=0): + return self.ix.fieldlengths.get((docnum, fieldname), default) + + def has_vector(self, docnum, fieldname): + return (docnum, fieldname) in self.ix.vectors + + def vector(self, docnum, fieldname): + vformat = self.schema[fieldname].vector + return RamPostingReader(vformat, self.ix.vectors[(docnum, fieldname)]) + + def __iter__(self): + tls = self.ix.termlists + inv = self.ix.invertedindex + ixf = self.ix.indexfreqs + for fieldname in sorted(tls.iterkeys()): + for text in tls[fieldname]: + docfreq = len(inv[fieldname][text]) + indexfreq = ixf[(fieldname, text)] + yield (fieldname, text, docfreq, indexfreq) + + def doc_frequency(self, fieldname, text): + return len(self.ix.invertedindex[fieldname][text]) + + def frequency(self, fieldname, text): + return self.ix.indexfreqs[(fieldname, text)] + + def iter_from(self, fieldname, text): + tls = self.ix.termlists + inv = self.ix.invertedindex + ixf = self.ix.indexfreqs + + fnms = sorted(fn for fn in tls.iterkeys() if fn >= fieldname) + for fn in fnms: + texts = tls[fn] + start = 0 + if fn == fieldname: + start = bisect_left(texts, text) + + for text in texts[start:]: + docfreq = len(inv[fieldname][text]) + indexfreq = ixf[(fieldname, text)] + yield (fieldname, text, docfreq, indexfreq) + + def lexicon(self, fieldname): + return self.ix.termlists[fieldname] + + def iter_field(self, fieldname, prefix=''): + inv = self.ix.invertedindex + ixf = self.ix.indexfreqs + + fieldtexts = self.ix.termlists[fieldname] + start = bisect_left(fieldtexts, prefix) + for text in fieldtexts[start:]: + docfreq = len(inv[fieldname][text]) + indexfreq = ixf[(fieldname, text)] + yield (text, docfreq, indexfreq) + + def expand_prefix(self, fieldname, prefix): + fieldtexts = self.ix.termlists[fieldname] + start = bisect_left(fieldtexts, prefix) + for text in fieldtexts[start:]: + if text.startswith(prefix): + yield text + else: + break + + def postings(self, fieldname, text, exclude_docs=None, scorer=None): + if not exclude_docs: + exclude_docs = frozenset() + excludeset = self.ix.deleted | exclude_docs + inv = self.ix.invertedindex + format = self.schema[fieldname].format + postings = inv[fieldname][text] + if excludeset: + postings = [(docnum, weight, stringvalue) for docnum, weight, stringvalue + in postings if docnum not in excludeset] + return RamPostingReader(format, postings, scorer=scorer) + + +class RamPostingReader(Matcher): + def __init__(self, format, postings, scorer=None, stringids=False): + self.format = format + self.postings = postings + self.i = 0 + + self.scorer = scorer + + def is_active(self): + return self.i < len(self.postings) + + def id(self): + return self.postings[self.i][0] + + def all_items(self): + return self.postings + + def all_ids(self): + return (x[0] for x in self.postings) + + def next(self): + if not self.is_active(): + raise ReadTooFar + self.i += 1 + + def skip_to(self, target): + if not self.is_active(): + raise ReadTooFar + + if target <= self.id: + return + + postings = self.postings + i = self.i + + while i < len(postings): + i += 1 + if i == len(postings): + break + elif postings[i][0] >= target: + break + + self.i = i + + def weight(self): + if not self.is_active(): + raise ReadTooFar + + return self.postings[self.i][1] + + def value(self): + if not self.is_active(): + raise ReadTooFar + + return self.postings[self.i][2] + + def score(self): + return self.scorer.score(self) + + def quality(self): + return self.scorer.quality(self) + + def block_quality(self): + return self.scorer.block_quality(self) + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/whoosh/spans.py b/lib/whoosh/spans.py index dffffce0..54119762 100644 --- a/lib/whoosh/spans.py +++ b/lib/whoosh/spans.py @@ -1,621 +1,621 @@ -#=============================================================================== -# Copyright 2010 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -""" -This module contains Query objects that deal with "spans". - -Span queries allow for positional constraints on matching documents. For -example, the :class:`whoosh.spans.SpanNear` query matches documents where one -term occurs near another. Because you can nest span queries, and wrap them -around almost any non-span query, you can create very complex constraints. - -For example, to find documents containing "whoosh" at most 5 positions before -"library" in the "text" field:: - - from whoosh import query, spans - t1 = query.Term("text", "whoosh") - t2 = query.Term("text", "library") - q = spans.SpanNear(t1, t2, slop=5) - -""" - - -from whoosh.matching import (WrappingMatcher, AndMaybeMatcher, UnionMatcher, - IntersectionMatcher, NullMatcher) -from whoosh.query import And, AndMaybe, Or, Query, Term -from whoosh.util import make_binary_tree - - -# Span class - -class Span(object): - __slots__ = ("start", "end", "startchar", "endchar") - - def __init__(self, start, end=None, startchar=None, endchar=None): - if end is None: - end = start - assert start <= end - self.start = start - self.end = end - self.startchar = startchar - self.endchar = endchar - - def __repr__(self): - if self.startchar or self.endchar: - return "<%d-%d %d:%d>" % (self.start, self.end, self.startchar, self.endchar) - else: - return "<%d-%d>" % (self.start, self.end) - - def __eq__(self, span): - return (self.start == span.start - and self.end == span.end - and self.startchar == span.startchar - and self.endchar == span.endchar) - - def __ne__(self, span): - return self.start != span.start or self.end != span.end - - def __lt__(self, span): - return self.start < span.start - - def __gt__(self, span): - return self.start > span.start - - def __hash__(self): - return hash((self.start, self.end)) - - @classmethod - def merge(cls, spans): - """Merges overlapping and touches spans in the given list of spans. - - Note that this modifies the original list. - - >>> spans = [Span(1,2), Span(3)] - >>> Span.merge(spans) - >>> spans - [<1-3>] - """ - - i = 0 - while i < len(spans)-1: - here = spans[i] - j = i + 1 - while j < len(spans): - there = spans[j] - if there.start > here.end + 1: - break - if here.touches(there) or here.overlaps(there): - here = here.to(there) - spans[i] = here - del spans[j] - else: - j += 1 - i += 1 - return spans - - def to(self, span): - return self.__class__(min(self.start, span.start), max(self.end, span.end), - min(self.startchar, span.startchar), max(self.endchar, span.endchar)) - - def overlaps(self, span): - return ((self.start >= span.start and self.start <= span.end) - or (self.end >= span.start and self.end <= span.end) - or (span.start >= self.start and span.start <= self.end) - or (span.end >= self.start and span.end <= self.end)) - - def surrounds(self, span): - return self.start < span.start and self.end > span.end - - def is_within(self, span): - return self.start >= span.start and self.end <= span.end - - def is_before(self, span): - return self.end < span.start - - def is_after(self, span): - return self.start > span.end - - def touches(self, span): - return self.start == span.end + 1 or self.end == span.start - 1 - - def distance_to(self, span): - if self.overlaps(span): - return 0 - elif self.is_before(span): - return span.start - self.end - elif self.is_after(span): - return self.start - span.end - - -# - -class SpanWrappingMatcher(WrappingMatcher): - """An abstract matcher class that wraps a "regular" matcher. This matcher - uses the sub-matcher's matching logic, but only matches documents that have - matching spans, i.e. where ``_get_spans()`` returns a non-empty list. - - Subclasses must implement the ``_get_spans()`` method, which returns a list - of valid spans for the current document. - """ - - def __init__(self, child): - super(SpanWrappingMatcher, self).__init__(child) - self._spans = None - if self.is_active(): - self._find_next() - - def copy(self): - m = self.__class__(self.child.copy()) - m._spans = self._spans - return m - - def _replacement(self, newchild): - return self.__class__(newchild) - - def _find_next(self): - if not self.is_active(): - return - - child = self.child - r = False - - spans = self._get_spans() - while child.is_active() and not spans: - r = child.next() or r - if not child.is_active(): - return True - spans = self._get_spans() - self._spans = spans - - return r - - def spans(self): - return self._spans - - def next(self): - self.child.next() - self._find_next() - - def skip_to(self, id): - self.child.skip_to(id) - self._find_next() - - def all_ids(self): - while self.is_active(): - if self.spans(): - yield self.id() - self.next() - - -# Queries - -class SpanQuery(Query): - """Abstract base class for span-based queries. Each span query type wraps - a "regular" query that implements the basic document-matching functionality - (for example, SpanNear wraps an And query, because SpanNear requires that - the two sub-queries occur in the same documents. The wrapped query is - stored in the ``q`` attribute. - - Subclasses usually only need to implement the initializer to set the - wrapped query, and ``matcher()`` to return a span-aware matcher object. - """ - - def _subm(self, s, excl): - return self.q.matcher(s, exclude_docs=excl) - - def __getattr__(self, name): - return getattr(self.q, name) - - def __repr__(self): - return "%s(%r)" % (self.__class__.__name__, self.q) - - -class SpanFirst(SpanQuery): - """Matches spans that end within the first N positions. This lets you - for example only match terms near the beginning of the document. - """ - - def __init__(self, q, limit=0): - """ - :param q: the query to match. - :param limit: the query must match within this position at the start - of a document. The default is ``0``, which means the query must - match at the first position. - """ - - self.q = q - self.limit = limit - - def matcher(self, searcher, exclude_docs=None): - return SpanFirst.SpanFirstMatcher(self._subm(searcher, exclude_docs), - limit=self.limit) - - class SpanFirstMatcher(SpanWrappingMatcher): - def __init__(self, child, limit=0): - self.limit = limit - super(SpanFirst.SpanFirstMatcher, self).__init__(child) - - def copy(self): - return self.__class__(self.child.copy(), limit=self.limit) - - def _replacement(self, newchild): - return self.__class__(newchild, limit=self.limit) - - def _get_spans(self): - return [span for span in self.child.spans() - if span.end <= self.limit] - - -class SpanNear(SpanQuery): - """Matches queries that occur near each other. By default, only matches - queries that occur right next to each other (slop=1) and in order - (ordered=True). - - For example, to find documents where "whoosh" occurs next to "library" - in the "text" field:: - - from whoosh import query, spans - t1 = query.Term("text", "whoosh") - t2 = query.Term("text", "library") - q = spans.SpanNear(t1, t2) - - To find documents where "whoosh" occurs at most 5 positions before - "library":: - - q = spans.SpanNear(t1, t2, slop=5) - - To find documents where "whoosh" occurs at most 5 positions before or after - "library":: - - q = spans.SpanNear(t1, t2, slop=5, ordered=False) - - You can use the ``phrase()`` class method to create a tree of SpanNear - queries to match a list of terms:: - - q = spans.SpanNear.phrase("text", [u"whoosh", u"search", u"library"], slop=2) - """ - - def __init__(self, a, b, slop=1, ordered=True, mindist=1): - """ - :param a: the first query to match. - :param b: the second query that must occur within "slop" positions of - the first query. - :param slop: the number of positions within which the queries must - occur. Default is 1, meaning the queries must occur right next - to each other. - :param ordered: whether a must occur before b. Default is True. - :pram mindist: the minimum distance allowed between the queries. - """ - - self.q = And([a, b]) - self.a = a - self.b = b - self.slop = slop - self.ordered = ordered - self.mindist = mindist - - def __repr__(self): - return "%s(%r, slop=%d, ordered=%s, mindist=%d)" % (self.__class__.__name__, - self.q, self.slop, - self.ordered, - self.mindist) - - def matcher(self, searcher, exclude_docs=None): - ma = self.a.matcher(searcher, exclude_docs=exclude_docs) - mb = self.b.matcher(searcher, exclude_docs=exclude_docs) - return SpanNear.SpanNearMatcher(ma, mb, slop=self.slop, - ordered=self.ordered, - mindist=self.mindist) - - @classmethod - def phrase(cls, fieldname, words, slop=1, ordered=True): - """Returns a tree of SpanNear queries to match a list of terms. - - This class method is a convenience for constructing a phrase query - using a binary tree of SpanNear queries. - - >>> SpanNear.phrase("f", [u"a", u"b", u"c", u"d"]) - SpanNear(SpanNear(Term("f", u"a"), Term("f", u"b")), SpanNear(Term("f", u"c"), Term("f", u"d"))) - - :param fieldname: the name of the field to search in. - :param words: a sequence of token texts to search for. - :param slop: the number of positions within which the terms must - occur. Default is 1, meaning the terms must occur right next - to each other. - :param ordered: whether the terms must occur in order. Default is True. - """ - - terms = [Term(fieldname, word) for word in words] - return make_binary_tree(cls, terms, slop=slop, ordered=ordered) - - class SpanNearMatcher(SpanWrappingMatcher): - def __init__(self, a, b, slop=1, ordered=True, mindist=1): - self.a = a - self.b = b - self.slop = slop - self.ordered = ordered - self.mindist = mindist - isect = IntersectionMatcher(a, b) - super(SpanNear.SpanNearMatcher, self).__init__(isect) - - def copy(self): - return self.__class__(self.a.copy(), self.b.copy(), slop=self.slop, - ordered=self.ordered, mindist=self.mindist) - - def replace(self): - if not self.is_active(): - return NullMatcher() - return self - - def _get_spans(self): - slop = self.slop - mindist = self.mindist - ordered = self.ordered - spans = set() - - bspans = self.b.spans() - for aspan in self.a.spans(): - for bspan in bspans: - if (bspan.end < aspan.start - slop - or (ordered and aspan.start > bspan.start)): - # B is too far in front of A, or B is in front of A - # *at all* when ordered is True - continue - if bspan.start > aspan.end + slop: - # B is too far from A. Since spans are listed in - # start position order, we know that all spans after - # this one will also be too far. - break - - # Check the distance between the spans - dist = aspan.distance_to(bspan) - if dist >= mindist and dist <= slop: - spans.add(aspan.to(bspan)) - - return sorted(spans) - - -class SpanBiMatcher(SpanWrappingMatcher): - def copy(self): - return self.__class__(self.a.copy(), self.b.copy()) - - def replace(self): - if not self.is_active(): - return NullMatcher() - return self - - -class SpanNot(SpanQuery): - """Matches spans from the first query only if they don't overlap with - spans from the second query. If there are no non-overlapping spans, the - document does not match. - - For example, to match documents that contain "bear" at most 2 places after - "apple" in the "text" field but don't have "cute" between them:: - - from whoosh import query, spans - t1 = query.Term("text", "apple") - t2 = query.Term("text", "bear") - near = spans.SpanNear(t1, t2, slop=2) - q = spans.SpanNot(near, query.Term("text", "cute")) - """ - - def __init__(self, a, b): - """ - :param a: the query to match. - :param b: do not match any spans that overlap with spans from this - query. - """ - - self.q = AndMaybe(a, b) - self.a = a - self.b = b - - def matcher(self, searcher, exclude_docs=None): - ma = self.a.matcher(searcher, exclude_docs=exclude_docs) - mb = self.b.matcher(searcher, exclude_docs=exclude_docs) - return SpanNot.SpanNotMatcher(ma, mb) - - class SpanNotMatcher(SpanBiMatcher): - def __init__(self, a, b): - self.a = a - self.b = b - super(SpanNot.SpanNotMatcher, self).__init__(AndMaybeMatcher(a, b)) - - def _get_spans(self): - if self.a.id() == self.b.id(): - spans = [] - bspans = self.b.spans() - for aspan in self.a.spans(): - overlapped = False - for bspan in bspans: - if aspan.overlaps(bspan): - overlapped = True - break - if not overlapped: - spans.append(aspan) - return spans - else: - return self.a.spans() - - -class SpanOr(SpanQuery): - """Matches documents that match any of a list of sub-queries. Unlike - query.Or, this class merges together matching spans from the different - sub-queries when they overlap. - """ - - def __init__(self, subqs): - """ - :param subqs: a list of queries to match. - """ - - self.q = Or(subqs) - self.subqs = subqs - - def matcher(self, searcher, exclude_docs=None): - matchers = [q.matcher(searcher, exclude_docs=exclude_docs) - for q in self.subqs] - return make_binary_tree(SpanOr.SpanOrMatcher, matchers) - - class SpanOrMatcher(SpanBiMatcher): - def __init__(self, a, b): - self.a = a - self.b = b - super(SpanOr.SpanOrMatcher, self).__init__(UnionMatcher(a, b)) - - def _get_spans(self): - if self.a.is_active() and self.b.is_active() and self.a.id() == self.b.id(): - spans = sorted(set(self.a.spans()) | set(self.b.spans())) - elif not self.b.is_active() or self.a.id() < self.b.id(): - spans = self.a.spans() - else: - spans = self.b.spans() - - Span.merge(spans) - return spans - - -class SpanContains(SpanQuery): - """Matches documents where the spans of the first query contain any spans - of the second query. - - For example, to match documents where "apple" occurs at most 10 places - before "bear" in the "text" field and "cute" is between them:: - - from whoosh import query, spans - t1 = query.Term("text", "apple") - t2 = query.Term("text", "bear") - near = spans.SpanNear(t1, t2, slop=10) - q = spans.SpanContains(near, query.Term("text", "cute")) - """ - - def __init__(self, a, b): - """ - :param a: the query to match. - :param b: the query whose spans must occur within the matching spans - of the first query. - """ - - self.q = And([a, b]) - self.a = a - self.b = b - - def matcher(self, searcher, exclude_docs=None): - ma = self.a.matcher(searcher, exclude_docs=exclude_docs) - mb = self.b.matcher(searcher, exclude_docs=exclude_docs) - return SpanContains.SpanContainsMatcher(ma, mb) - - class SpanContainsMatcher(SpanBiMatcher): - def __init__(self, a, b): - self.a = a - self.b = b - isect = IntersectionMatcher(a, b) - super(SpanContains.SpanContainsMatcher, self).__init__(isect) - - def _get_spans(self): - spans = [] - bspans = self.b.spans() - for aspan in self.a.spans(): - for bspan in bspans: - if aspan.start > bspan.end: - continue - if aspan.end < bspan.start: - break - - if bspan.is_within(aspan): - spans.append(aspan) - break - return spans - - -class SpanBefore(SpanQuery): - """Matches documents where the spans of the first query occur before any - spans of the second query. - - For example, to match documents where "apple" occurs anywhere before - "bear":: - - from whoosh import query, spans - t1 = query.Term("text", "apple") - t2 = query.Term("text", "bear") - q = spans.SpanBefore(t1, t2) - """ - - def __init__(self, a, b): - """ - :param a: the query that must occur before the second. - :param b: the query that must occur after the first. - """ - - self.a = a - self.b = b - self.q = And([a, b]) - - def matcher(self, searcher, exclude_docs=None): - ma = self.a.matcher(searcher, exclude_docs=exclude_docs) - mb = self.b.matcher(searcher, exclude_docs=exclude_docs) - return SpanBefore.SpanBeforeMatcher(ma, mb) - - class SpanBeforeMatcher(SpanBiMatcher): - def __init__(self, a, b): - self.a = a - self.b = b - isect = IntersectionMatcher(a, b) - super(SpanBefore.SpanBeforeMatcher, self).__init__(isect) - - def _get_spans(self): - bminstart = min(bspan.start for bspan in self.b.spans()) - return [aspan for aspan in self.a.spans() if aspan.end < bminstart] - - -class SpanCondition(SpanQuery): - """Matches documents that satisfy both subqueries, but only uses the spans - from the first subquery. - - This is useful when you want to place conditions on matches but not have - those conditions affect the spans returned. - - For example, to get spans for the term ``alfa`` in documents that also - must contain the term ``bravo``:: - - SpanCondition(Term("text", u"alfa"), Term("text", u"bravo")) - - """ - - def __init__(self, a, b): - self.a = a - self.b = b - self.q = And([a, b]) - - def matcher(self, searcher, exclude_docs=None): - ma = self.a.matcher(searcher, exclude_docs=exclude_docs) - mb = self.b.matcher(searcher, exclude_docs=exclude_docs) - return SpanCondition.SpanConditionMatcher(ma, mb) - - class SpanConditionMatcher(SpanBiMatcher): - def __init__(self, a, b): - self.a = a - isect = IntersectionMatcher(a, b) - super(SpanCondition.SpanConditionMatcher, self).__init__(isect) - - def _get_spans(self): - return self.a.spans() - - - - - +#=============================================================================== +# Copyright 2010 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +""" +This module contains Query objects that deal with "spans". + +Span queries allow for positional constraints on matching documents. For +example, the :class:`whoosh.spans.SpanNear` query matches documents where one +term occurs near another. Because you can nest span queries, and wrap them +around almost any non-span query, you can create very complex constraints. + +For example, to find documents containing "whoosh" at most 5 positions before +"library" in the "text" field:: + + from whoosh import query, spans + t1 = query.Term("text", "whoosh") + t2 = query.Term("text", "library") + q = spans.SpanNear(t1, t2, slop=5) + +""" + + +from whoosh.matching import (WrappingMatcher, AndMaybeMatcher, UnionMatcher, + IntersectionMatcher, NullMatcher) +from whoosh.query import And, AndMaybe, Or, Query, Term +from whoosh.util import make_binary_tree + + +# Span class + +class Span(object): + __slots__ = ("start", "end", "startchar", "endchar") + + def __init__(self, start, end=None, startchar=None, endchar=None): + if end is None: + end = start + assert start <= end + self.start = start + self.end = end + self.startchar = startchar + self.endchar = endchar + + def __repr__(self): + if self.startchar or self.endchar: + return "<%d-%d %d:%d>" % (self.start, self.end, self.startchar, self.endchar) + else: + return "<%d-%d>" % (self.start, self.end) + + def __eq__(self, span): + return (self.start == span.start + and self.end == span.end + and self.startchar == span.startchar + and self.endchar == span.endchar) + + def __ne__(self, span): + return self.start != span.start or self.end != span.end + + def __lt__(self, span): + return self.start < span.start + + def __gt__(self, span): + return self.start > span.start + + def __hash__(self): + return hash((self.start, self.end)) + + @classmethod + def merge(cls, spans): + """Merges overlapping and touches spans in the given list of spans. + + Note that this modifies the original list. + + >>> spans = [Span(1,2), Span(3)] + >>> Span.merge(spans) + >>> spans + [<1-3>] + """ + + i = 0 + while i < len(spans)-1: + here = spans[i] + j = i + 1 + while j < len(spans): + there = spans[j] + if there.start > here.end + 1: + break + if here.touches(there) or here.overlaps(there): + here = here.to(there) + spans[i] = here + del spans[j] + else: + j += 1 + i += 1 + return spans + + def to(self, span): + return self.__class__(min(self.start, span.start), max(self.end, span.end), + min(self.startchar, span.startchar), max(self.endchar, span.endchar)) + + def overlaps(self, span): + return ((self.start >= span.start and self.start <= span.end) + or (self.end >= span.start and self.end <= span.end) + or (span.start >= self.start and span.start <= self.end) + or (span.end >= self.start and span.end <= self.end)) + + def surrounds(self, span): + return self.start < span.start and self.end > span.end + + def is_within(self, span): + return self.start >= span.start and self.end <= span.end + + def is_before(self, span): + return self.end < span.start + + def is_after(self, span): + return self.start > span.end + + def touches(self, span): + return self.start == span.end + 1 or self.end == span.start - 1 + + def distance_to(self, span): + if self.overlaps(span): + return 0 + elif self.is_before(span): + return span.start - self.end + elif self.is_after(span): + return self.start - span.end + + +# + +class SpanWrappingMatcher(WrappingMatcher): + """An abstract matcher class that wraps a "regular" matcher. This matcher + uses the sub-matcher's matching logic, but only matches documents that have + matching spans, i.e. where ``_get_spans()`` returns a non-empty list. + + Subclasses must implement the ``_get_spans()`` method, which returns a list + of valid spans for the current document. + """ + + def __init__(self, child): + super(SpanWrappingMatcher, self).__init__(child) + self._spans = None + if self.is_active(): + self._find_next() + + def copy(self): + m = self.__class__(self.child.copy()) + m._spans = self._spans + return m + + def _replacement(self, newchild): + return self.__class__(newchild) + + def _find_next(self): + if not self.is_active(): + return + + child = self.child + r = False + + spans = self._get_spans() + while child.is_active() and not spans: + r = child.next() or r + if not child.is_active(): + return True + spans = self._get_spans() + self._spans = spans + + return r + + def spans(self): + return self._spans + + def next(self): + self.child.next() + self._find_next() + + def skip_to(self, id): + self.child.skip_to(id) + self._find_next() + + def all_ids(self): + while self.is_active(): + if self.spans(): + yield self.id() + self.next() + + +# Queries + +class SpanQuery(Query): + """Abstract base class for span-based queries. Each span query type wraps + a "regular" query that implements the basic document-matching functionality + (for example, SpanNear wraps an And query, because SpanNear requires that + the two sub-queries occur in the same documents. The wrapped query is + stored in the ``q`` attribute. + + Subclasses usually only need to implement the initializer to set the + wrapped query, and ``matcher()`` to return a span-aware matcher object. + """ + + def _subm(self, s, excl): + return self.q.matcher(s, exclude_docs=excl) + + def __getattr__(self, name): + return getattr(self.q, name) + + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self.q) + + +class SpanFirst(SpanQuery): + """Matches spans that end within the first N positions. This lets you + for example only match terms near the beginning of the document. + """ + + def __init__(self, q, limit=0): + """ + :param q: the query to match. + :param limit: the query must match within this position at the start + of a document. The default is ``0``, which means the query must + match at the first position. + """ + + self.q = q + self.limit = limit + + def matcher(self, searcher, exclude_docs=None): + return SpanFirst.SpanFirstMatcher(self._subm(searcher, exclude_docs), + limit=self.limit) + + class SpanFirstMatcher(SpanWrappingMatcher): + def __init__(self, child, limit=0): + self.limit = limit + super(SpanFirst.SpanFirstMatcher, self).__init__(child) + + def copy(self): + return self.__class__(self.child.copy(), limit=self.limit) + + def _replacement(self, newchild): + return self.__class__(newchild, limit=self.limit) + + def _get_spans(self): + return [span for span in self.child.spans() + if span.end <= self.limit] + + +class SpanNear(SpanQuery): + """Matches queries that occur near each other. By default, only matches + queries that occur right next to each other (slop=1) and in order + (ordered=True). + + For example, to find documents where "whoosh" occurs next to "library" + in the "text" field:: + + from whoosh import query, spans + t1 = query.Term("text", "whoosh") + t2 = query.Term("text", "library") + q = spans.SpanNear(t1, t2) + + To find documents where "whoosh" occurs at most 5 positions before + "library":: + + q = spans.SpanNear(t1, t2, slop=5) + + To find documents where "whoosh" occurs at most 5 positions before or after + "library":: + + q = spans.SpanNear(t1, t2, slop=5, ordered=False) + + You can use the ``phrase()`` class method to create a tree of SpanNear + queries to match a list of terms:: + + q = spans.SpanNear.phrase("text", [u"whoosh", u"search", u"library"], slop=2) + """ + + def __init__(self, a, b, slop=1, ordered=True, mindist=1): + """ + :param a: the first query to match. + :param b: the second query that must occur within "slop" positions of + the first query. + :param slop: the number of positions within which the queries must + occur. Default is 1, meaning the queries must occur right next + to each other. + :param ordered: whether a must occur before b. Default is True. + :pram mindist: the minimum distance allowed between the queries. + """ + + self.q = And([a, b]) + self.a = a + self.b = b + self.slop = slop + self.ordered = ordered + self.mindist = mindist + + def __repr__(self): + return "%s(%r, slop=%d, ordered=%s, mindist=%d)" % (self.__class__.__name__, + self.q, self.slop, + self.ordered, + self.mindist) + + def matcher(self, searcher, exclude_docs=None): + ma = self.a.matcher(searcher, exclude_docs=exclude_docs) + mb = self.b.matcher(searcher, exclude_docs=exclude_docs) + return SpanNear.SpanNearMatcher(ma, mb, slop=self.slop, + ordered=self.ordered, + mindist=self.mindist) + + @classmethod + def phrase(cls, fieldname, words, slop=1, ordered=True): + """Returns a tree of SpanNear queries to match a list of terms. + + This class method is a convenience for constructing a phrase query + using a binary tree of SpanNear queries. + + >>> SpanNear.phrase("f", [u"a", u"b", u"c", u"d"]) + SpanNear(SpanNear(Term("f", u"a"), Term("f", u"b")), SpanNear(Term("f", u"c"), Term("f", u"d"))) + + :param fieldname: the name of the field to search in. + :param words: a sequence of token texts to search for. + :param slop: the number of positions within which the terms must + occur. Default is 1, meaning the terms must occur right next + to each other. + :param ordered: whether the terms must occur in order. Default is True. + """ + + terms = [Term(fieldname, word) for word in words] + return make_binary_tree(cls, terms, slop=slop, ordered=ordered) + + class SpanNearMatcher(SpanWrappingMatcher): + def __init__(self, a, b, slop=1, ordered=True, mindist=1): + self.a = a + self.b = b + self.slop = slop + self.ordered = ordered + self.mindist = mindist + isect = IntersectionMatcher(a, b) + super(SpanNear.SpanNearMatcher, self).__init__(isect) + + def copy(self): + return self.__class__(self.a.copy(), self.b.copy(), slop=self.slop, + ordered=self.ordered, mindist=self.mindist) + + def replace(self): + if not self.is_active(): + return NullMatcher() + return self + + def _get_spans(self): + slop = self.slop + mindist = self.mindist + ordered = self.ordered + spans = set() + + bspans = self.b.spans() + for aspan in self.a.spans(): + for bspan in bspans: + if (bspan.end < aspan.start - slop + or (ordered and aspan.start > bspan.start)): + # B is too far in front of A, or B is in front of A + # *at all* when ordered is True + continue + if bspan.start > aspan.end + slop: + # B is too far from A. Since spans are listed in + # start position order, we know that all spans after + # this one will also be too far. + break + + # Check the distance between the spans + dist = aspan.distance_to(bspan) + if dist >= mindist and dist <= slop: + spans.add(aspan.to(bspan)) + + return sorted(spans) + + +class SpanBiMatcher(SpanWrappingMatcher): + def copy(self): + return self.__class__(self.a.copy(), self.b.copy()) + + def replace(self): + if not self.is_active(): + return NullMatcher() + return self + + +class SpanNot(SpanQuery): + """Matches spans from the first query only if they don't overlap with + spans from the second query. If there are no non-overlapping spans, the + document does not match. + + For example, to match documents that contain "bear" at most 2 places after + "apple" in the "text" field but don't have "cute" between them:: + + from whoosh import query, spans + t1 = query.Term("text", "apple") + t2 = query.Term("text", "bear") + near = spans.SpanNear(t1, t2, slop=2) + q = spans.SpanNot(near, query.Term("text", "cute")) + """ + + def __init__(self, a, b): + """ + :param a: the query to match. + :param b: do not match any spans that overlap with spans from this + query. + """ + + self.q = AndMaybe(a, b) + self.a = a + self.b = b + + def matcher(self, searcher, exclude_docs=None): + ma = self.a.matcher(searcher, exclude_docs=exclude_docs) + mb = self.b.matcher(searcher, exclude_docs=exclude_docs) + return SpanNot.SpanNotMatcher(ma, mb) + + class SpanNotMatcher(SpanBiMatcher): + def __init__(self, a, b): + self.a = a + self.b = b + super(SpanNot.SpanNotMatcher, self).__init__(AndMaybeMatcher(a, b)) + + def _get_spans(self): + if self.a.id() == self.b.id(): + spans = [] + bspans = self.b.spans() + for aspan in self.a.spans(): + overlapped = False + for bspan in bspans: + if aspan.overlaps(bspan): + overlapped = True + break + if not overlapped: + spans.append(aspan) + return spans + else: + return self.a.spans() + + +class SpanOr(SpanQuery): + """Matches documents that match any of a list of sub-queries. Unlike + query.Or, this class merges together matching spans from the different + sub-queries when they overlap. + """ + + def __init__(self, subqs): + """ + :param subqs: a list of queries to match. + """ + + self.q = Or(subqs) + self.subqs = subqs + + def matcher(self, searcher, exclude_docs=None): + matchers = [q.matcher(searcher, exclude_docs=exclude_docs) + for q in self.subqs] + return make_binary_tree(SpanOr.SpanOrMatcher, matchers) + + class SpanOrMatcher(SpanBiMatcher): + def __init__(self, a, b): + self.a = a + self.b = b + super(SpanOr.SpanOrMatcher, self).__init__(UnionMatcher(a, b)) + + def _get_spans(self): + if self.a.is_active() and self.b.is_active() and self.a.id() == self.b.id(): + spans = sorted(set(self.a.spans()) | set(self.b.spans())) + elif not self.b.is_active() or self.a.id() < self.b.id(): + spans = self.a.spans() + else: + spans = self.b.spans() + + Span.merge(spans) + return spans + + +class SpanContains(SpanQuery): + """Matches documents where the spans of the first query contain any spans + of the second query. + + For example, to match documents where "apple" occurs at most 10 places + before "bear" in the "text" field and "cute" is between them:: + + from whoosh import query, spans + t1 = query.Term("text", "apple") + t2 = query.Term("text", "bear") + near = spans.SpanNear(t1, t2, slop=10) + q = spans.SpanContains(near, query.Term("text", "cute")) + """ + + def __init__(self, a, b): + """ + :param a: the query to match. + :param b: the query whose spans must occur within the matching spans + of the first query. + """ + + self.q = And([a, b]) + self.a = a + self.b = b + + def matcher(self, searcher, exclude_docs=None): + ma = self.a.matcher(searcher, exclude_docs=exclude_docs) + mb = self.b.matcher(searcher, exclude_docs=exclude_docs) + return SpanContains.SpanContainsMatcher(ma, mb) + + class SpanContainsMatcher(SpanBiMatcher): + def __init__(self, a, b): + self.a = a + self.b = b + isect = IntersectionMatcher(a, b) + super(SpanContains.SpanContainsMatcher, self).__init__(isect) + + def _get_spans(self): + spans = [] + bspans = self.b.spans() + for aspan in self.a.spans(): + for bspan in bspans: + if aspan.start > bspan.end: + continue + if aspan.end < bspan.start: + break + + if bspan.is_within(aspan): + spans.append(aspan) + break + return spans + + +class SpanBefore(SpanQuery): + """Matches documents where the spans of the first query occur before any + spans of the second query. + + For example, to match documents where "apple" occurs anywhere before + "bear":: + + from whoosh import query, spans + t1 = query.Term("text", "apple") + t2 = query.Term("text", "bear") + q = spans.SpanBefore(t1, t2) + """ + + def __init__(self, a, b): + """ + :param a: the query that must occur before the second. + :param b: the query that must occur after the first. + """ + + self.a = a + self.b = b + self.q = And([a, b]) + + def matcher(self, searcher, exclude_docs=None): + ma = self.a.matcher(searcher, exclude_docs=exclude_docs) + mb = self.b.matcher(searcher, exclude_docs=exclude_docs) + return SpanBefore.SpanBeforeMatcher(ma, mb) + + class SpanBeforeMatcher(SpanBiMatcher): + def __init__(self, a, b): + self.a = a + self.b = b + isect = IntersectionMatcher(a, b) + super(SpanBefore.SpanBeforeMatcher, self).__init__(isect) + + def _get_spans(self): + bminstart = min(bspan.start for bspan in self.b.spans()) + return [aspan for aspan in self.a.spans() if aspan.end < bminstart] + + +class SpanCondition(SpanQuery): + """Matches documents that satisfy both subqueries, but only uses the spans + from the first subquery. + + This is useful when you want to place conditions on matches but not have + those conditions affect the spans returned. + + For example, to get spans for the term ``alfa`` in documents that also + must contain the term ``bravo``:: + + SpanCondition(Term("text", u"alfa"), Term("text", u"bravo")) + + """ + + def __init__(self, a, b): + self.a = a + self.b = b + self.q = And([a, b]) + + def matcher(self, searcher, exclude_docs=None): + ma = self.a.matcher(searcher, exclude_docs=exclude_docs) + mb = self.b.matcher(searcher, exclude_docs=exclude_docs) + return SpanCondition.SpanConditionMatcher(ma, mb) + + class SpanConditionMatcher(SpanBiMatcher): + def __init__(self, a, b): + self.a = a + isect = IntersectionMatcher(a, b) + super(SpanCondition.SpanConditionMatcher, self).__init__(isect) + + def _get_spans(self): + return self.a.spans() + + + + + diff --git a/lib/whoosh/spelling.py b/lib/whoosh/spelling.py index 302dcd84..fbf195f3 100644 --- a/lib/whoosh/spelling.py +++ b/lib/whoosh/spelling.py @@ -1,239 +1,239 @@ -#=============================================================================== -# Copyright 2007 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -"""This module contains functions/classes using a Whoosh index as a backend for -a spell-checking engine. -""" - -from collections import defaultdict - -from whoosh import analysis, fields, query, scoring -from whoosh.support.levenshtein import relative, distance - - -nullsorter = scoring.NullSorter() - -class SpellChecker(object): - """Implements a spell-checking engine using a search index for the backend - storage and lookup. This class is based on the Lucene contributed spell- - checker code. - - To use this object:: - - st = store.FileStorage("spelldict") - sp = SpellChecker(st) - - sp.add_words([u"aardvark", u"manticore", u"zebra", ...]) - # or - ix = index.open_dir("index") - sp.add_field(ix, "content") - - suggestions = sp.suggest(u"ardvark", number = 2) - """ - - def __init__(self, storage, indexname="SPELL", - booststart=2.0, boostend=1.0, - mingram=3, maxgram=4, - minscore=0.5): - """ - :param storage: The storage object in which to create the - spell-checker's dictionary index. - :param indexname: The name to use for the spell-checker's dictionary - index. You only need to change this if you have multiple spelling - indexes in the same storage. - :param booststart: How much to boost matches of the first N-gram (the - beginning of the word). - :param boostend: How much to boost matches of the last N-gram (the end - of the word). - :param mingram: The minimum gram length to store. - :param maxgram: The maximum gram length to store. - :param minscore: The minimum score matches much achieve to be returned. - """ - - self.storage = storage - self.indexname = indexname - - self._index = None - - self.booststart = booststart - self.boostend = boostend - self.mingram = mingram - self.maxgram = maxgram - self.minscore = minscore - - def index(self, create=False): - """Returns the backend index of this object (instantiating it if it - didn't already exist). - """ - - import index - if create or not self._index: - create = create or not index.exists(self.storage, indexname=self.indexname) - if create: - self._index = self.storage.create_index(self._schema(), self.indexname) - else: - self._index = self.storage.open_index(self.indexname) - return self._index - - def _schema(self): - # Creates a schema given this object's mingram and maxgram attributes. - - from fields import Schema, FieldType, Frequency, ID, STORED - from analysis import SimpleAnalyzer - - idtype = ID() - freqtype = FieldType(format=Frequency(SimpleAnalyzer())) - - fls = [("word", STORED), ("score", STORED)] - for size in xrange(self.mingram, self.maxgram + 1): - fls.extend([("start%s" % size, idtype), - ("end%s" % size, idtype), - ("gram%s" % size, freqtype)]) - - return Schema(**dict(fls)) - - def suggestions_and_scores(self, text, weighting=None): - """Returns a list of possible alternative spellings of 'text', as - ('word', score, weight) triples, where 'word' is the suggested - word, 'score' is the score that was assigned to the word using - :meth:`SpellChecker.add_field` or :meth:`SpellChecker.add_scored_words`, - and 'weight' is the score the word received in the search for the - original word's ngrams. - - You must add words to the dictionary (using add_field, add_words, - and/or add_scored_words) before you can use this. - - This is a lower-level method, in case an expert user needs access to - the raw scores, for example to implement a custom suggestion ranking - algorithm. Most people will want to call :meth:`~SpellChecker.suggest` - instead, which simply returns the top N valued words. - - :param text: The word to check. - :rtype: list - """ - - if weighting is None: - weighting = scoring.TF_IDF() - - grams = defaultdict(list) - for size in xrange(self.mingram, self.maxgram + 1): - key = "gram%s" % size - nga = analysis.NgramAnalyzer(size) - for t in nga(text): - grams[key].append(t.text) - - queries = [] - for size in xrange(self.mingram, min(self.maxgram + 1, len(text))): - key = "gram%s" % size - gramlist = grams[key] - queries.append(query.Term("start%s" % size, gramlist[0], - boost=self.booststart)) - queries.append(query.Term("end%s" % size, gramlist[-1], - boost=self.boostend)) - for gram in gramlist: - queries.append(query.Term(key, gram)) - - q = query.Or(queries) - ix = self.index() - s = ix.searcher(weighting=weighting) - try: - result = s.search(q, limit=None) - return [(fs["word"], fs["score"], result.score(i)) - for i, fs in enumerate(result) - if fs["word"] != text] - finally: - s.close() - - def suggest(self, text, number=3, usescores=False): - """Returns a list of suggested alternative spellings of 'text'. You - must add words to the dictionary (using add_field, add_words, and/or - add_scored_words) before you can use this. - - :param text: The word to check. - :param number: The maximum number of suggestions to return. - :param usescores: Use the per-word score to influence the suggestions. - :rtype: list - """ - - if usescores: - def keyfn(a): - return 0 - (1 / distance(text, a[0])) * a[1] - else: - def keyfn(a): - return distance(text, a[0]) - - suggestions = self.suggestions_and_scores(text) - suggestions.sort(key=keyfn) - return [word for word, _, weight in suggestions[:number] - if weight >= self.minscore] - - def add_field(self, ix, fieldname): - """Adds the terms in a field from another index to the backend - dictionary. This method calls add_scored_words() and uses each term's - frequency as the score. As a result, more common words will be - suggested before rare words. If you want to calculate the scores - differently, use add_scored_words() directly. - - :param ix: The index.Index object from which to add terms. - :param fieldname: The field name (or number) of a field in the source - index. All the indexed terms from this field will be added to the - dictionary. - """ - - r = ix.reader() - try: - self.add_scored_words((w, freq) - for w, _, freq in r.iter_field(fieldname)) - finally: - r.close() - - def add_words(self, ws, score=1): - """Adds a list of words to the backend dictionary. - - :param ws: A sequence of words (strings) to add to the dictionary. - :param score: An optional score to use for ALL the words in 'ws'. - """ - self.add_scored_words((w, score) for w in ws) - - def add_scored_words(self, ws): - """Adds a list of ("word", score) tuples to the backend dictionary. - Associating words with a score lets you use the 'usescores' keyword - argument of the suggest() method to order the suggestions using the - scores. - - :param ws: A sequence of ("word", score) tuples. - """ - - writer = self.index().writer() - for text, score in ws: - fields = {"word": text, "score": score} - for size in xrange(self.mingram, self.maxgram + 1): - nga = analysis.NgramAnalyzer(size) - gramlist = [t.text for t in nga(text)] - if len(gramlist) > 0: - fields["start%s" % size] = gramlist[0] - fields["end%s" % size] = gramlist[-1] - fields["gram%s" % size] = " ".join(gramlist) - writer.add_document(**fields) - writer.commit() - - - -if __name__ == '__main__': - pass - - - +#=============================================================================== +# Copyright 2007 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +"""This module contains functions/classes using a Whoosh index as a backend for +a spell-checking engine. +""" + +from collections import defaultdict + +from whoosh import analysis, fields, query, scoring +from whoosh.support.levenshtein import relative, distance + + +nullsorter = scoring.NullSorter() + +class SpellChecker(object): + """Implements a spell-checking engine using a search index for the backend + storage and lookup. This class is based on the Lucene contributed spell- + checker code. + + To use this object:: + + st = store.FileStorage("spelldict") + sp = SpellChecker(st) + + sp.add_words([u"aardvark", u"manticore", u"zebra", ...]) + # or + ix = index.open_dir("index") + sp.add_field(ix, "content") + + suggestions = sp.suggest(u"ardvark", number = 2) + """ + + def __init__(self, storage, indexname="SPELL", + booststart=2.0, boostend=1.0, + mingram=3, maxgram=4, + minscore=0.5): + """ + :param storage: The storage object in which to create the + spell-checker's dictionary index. + :param indexname: The name to use for the spell-checker's dictionary + index. You only need to change this if you have multiple spelling + indexes in the same storage. + :param booststart: How much to boost matches of the first N-gram (the + beginning of the word). + :param boostend: How much to boost matches of the last N-gram (the end + of the word). + :param mingram: The minimum gram length to store. + :param maxgram: The maximum gram length to store. + :param minscore: The minimum score matches much achieve to be returned. + """ + + self.storage = storage + self.indexname = indexname + + self._index = None + + self.booststart = booststart + self.boostend = boostend + self.mingram = mingram + self.maxgram = maxgram + self.minscore = minscore + + def index(self, create=False): + """Returns the backend index of this object (instantiating it if it + didn't already exist). + """ + + import index + if create or not self._index: + create = create or not index.exists(self.storage, indexname=self.indexname) + if create: + self._index = self.storage.create_index(self._schema(), self.indexname) + else: + self._index = self.storage.open_index(self.indexname) + return self._index + + def _schema(self): + # Creates a schema given this object's mingram and maxgram attributes. + + from fields import Schema, FieldType, Frequency, ID, STORED + from analysis import SimpleAnalyzer + + idtype = ID() + freqtype = FieldType(format=Frequency(SimpleAnalyzer())) + + fls = [("word", STORED), ("score", STORED)] + for size in xrange(self.mingram, self.maxgram + 1): + fls.extend([("start%s" % size, idtype), + ("end%s" % size, idtype), + ("gram%s" % size, freqtype)]) + + return Schema(**dict(fls)) + + def suggestions_and_scores(self, text, weighting=None): + """Returns a list of possible alternative spellings of 'text', as + ('word', score, weight) triples, where 'word' is the suggested + word, 'score' is the score that was assigned to the word using + :meth:`SpellChecker.add_field` or :meth:`SpellChecker.add_scored_words`, + and 'weight' is the score the word received in the search for the + original word's ngrams. + + You must add words to the dictionary (using add_field, add_words, + and/or add_scored_words) before you can use this. + + This is a lower-level method, in case an expert user needs access to + the raw scores, for example to implement a custom suggestion ranking + algorithm. Most people will want to call :meth:`~SpellChecker.suggest` + instead, which simply returns the top N valued words. + + :param text: The word to check. + :rtype: list + """ + + if weighting is None: + weighting = scoring.TF_IDF() + + grams = defaultdict(list) + for size in xrange(self.mingram, self.maxgram + 1): + key = "gram%s" % size + nga = analysis.NgramAnalyzer(size) + for t in nga(text): + grams[key].append(t.text) + + queries = [] + for size in xrange(self.mingram, min(self.maxgram + 1, len(text))): + key = "gram%s" % size + gramlist = grams[key] + queries.append(query.Term("start%s" % size, gramlist[0], + boost=self.booststart)) + queries.append(query.Term("end%s" % size, gramlist[-1], + boost=self.boostend)) + for gram in gramlist: + queries.append(query.Term(key, gram)) + + q = query.Or(queries) + ix = self.index() + s = ix.searcher(weighting=weighting) + try: + result = s.search(q, limit=None) + return [(fs["word"], fs["score"], result.score(i)) + for i, fs in enumerate(result) + if fs["word"] != text] + finally: + s.close() + + def suggest(self, text, number=3, usescores=False): + """Returns a list of suggested alternative spellings of 'text'. You + must add words to the dictionary (using add_field, add_words, and/or + add_scored_words) before you can use this. + + :param text: The word to check. + :param number: The maximum number of suggestions to return. + :param usescores: Use the per-word score to influence the suggestions. + :rtype: list + """ + + if usescores: + def keyfn(a): + return 0 - (1 / distance(text, a[0])) * a[1] + else: + def keyfn(a): + return distance(text, a[0]) + + suggestions = self.suggestions_and_scores(text) + suggestions.sort(key=keyfn) + return [word for word, _, weight in suggestions[:number] + if weight >= self.minscore] + + def add_field(self, ix, fieldname): + """Adds the terms in a field from another index to the backend + dictionary. This method calls add_scored_words() and uses each term's + frequency as the score. As a result, more common words will be + suggested before rare words. If you want to calculate the scores + differently, use add_scored_words() directly. + + :param ix: The index.Index object from which to add terms. + :param fieldname: The field name (or number) of a field in the source + index. All the indexed terms from this field will be added to the + dictionary. + """ + + r = ix.reader() + try: + self.add_scored_words((w, freq) + for w, _, freq in r.iter_field(fieldname)) + finally: + r.close() + + def add_words(self, ws, score=1): + """Adds a list of words to the backend dictionary. + + :param ws: A sequence of words (strings) to add to the dictionary. + :param score: An optional score to use for ALL the words in 'ws'. + """ + self.add_scored_words((w, score) for w in ws) + + def add_scored_words(self, ws): + """Adds a list of ("word", score) tuples to the backend dictionary. + Associating words with a score lets you use the 'usescores' keyword + argument of the suggest() method to order the suggestions using the + scores. + + :param ws: A sequence of ("word", score) tuples. + """ + + writer = self.index().writer() + for text, score in ws: + fields = {"word": text, "score": score} + for size in xrange(self.mingram, self.maxgram + 1): + nga = analysis.NgramAnalyzer(size) + gramlist = [t.text for t in nga(text)] + if len(gramlist) > 0: + fields["start%s" % size] = gramlist[0] + fields["end%s" % size] = gramlist[-1] + fields["gram%s" % size] = " ".join(gramlist) + writer.add_document(**fields) + writer.commit() + + + +if __name__ == '__main__': + pass + + + diff --git a/lib/whoosh/store.py b/lib/whoosh/store.py index 62bf56ed..eab5e7ad 100644 --- a/lib/whoosh/store.py +++ b/lib/whoosh/store.py @@ -1,67 +1,67 @@ -#=============================================================================== -# Copyright 2007 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - - -class LockError(Exception): - pass - - -class Storage(object): - """Abstract base class for storage objects. - """ - - def create_index(self, schema, indexname=None): - raise NotImplementedError - - def open_index(self, indexname=None, schema=None): - raise NotImplementedError - - def create_file(self, name): - raise NotImplementedError - - def open_file(self, name, *args, **kwargs): - raise NotImplementedError - - def list(self): - raise NotImplementedError - - def file_exists(self, name): - raise NotImplementedError - - def file_modified(self, name): - raise NotImplementedError - - def delete_file(self, name): - raise NotImplementedError - - def rename_file(self, frm, to, safe=False): - raise NotImplementedError - - def lock(self, name): - raise NotImplementedError - - def close(self): - pass - - def optimize(self): - pass - - - - - - - +#=============================================================================== +# Copyright 2007 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + + +class LockError(Exception): + pass + + +class Storage(object): + """Abstract base class for storage objects. + """ + + def create_index(self, schema, indexname=None): + raise NotImplementedError + + def open_index(self, indexname=None, schema=None): + raise NotImplementedError + + def create_file(self, name): + raise NotImplementedError + + def open_file(self, name, *args, **kwargs): + raise NotImplementedError + + def list(self): + raise NotImplementedError + + def file_exists(self, name): + raise NotImplementedError + + def file_modified(self, name): + raise NotImplementedError + + def delete_file(self, name): + raise NotImplementedError + + def rename_file(self, frm, to, safe=False): + raise NotImplementedError + + def lock(self, name): + raise NotImplementedError + + def close(self): + pass + + def optimize(self): + pass + + + + + + + diff --git a/lib/whoosh/support/charset.py b/lib/whoosh/support/charset.py index 63162d8d..79f0959a 100644 --- a/lib/whoosh/support/charset.py +++ b/lib/whoosh/support/charset.py @@ -1,798 +1,798 @@ -# coding=utf-8 - -"""This module contains tools for working with Sphinx charset table files. These files -are useful for doing case and accent folding. -See :class:`whoosh.analysis.CharsetTokenizer` and :class:`whoosh.analysis.CharsetFilter`. -""" - -from collections import defaultdict -from itertools import izip -import re - -# This is a straightforward accent-folding charset taken from Carlos Bueno's -# article "Accent Folding for Auto-Complete", for use with CharsetFilter. -# -# http://www.alistapart.com/articles/accent-folding-for-auto-complete/ -# -# See the article for information and caveats. The code is lifted directly -# from here: -# -# http://github.com/aristus/accent-folding/blob/master/accent_fold.py - -accent_map = {u'ẚ':u'a',u'Á':u'a',u'á':u'a',u'À':u'a',u'à':u'a',u'Ă':u'a', - u'ă':u'a',u'Ắ':u'a',u'ắ':u'a',u'Ằ':u'a',u'ằ':u'a',u'Ẵ':u'a', - u'ẵ':u'a',u'Ẳ':u'a',u'ẳ':u'a',u'Â':u'a',u'â':u'a',u'Ấ':u'a', - u'ấ':u'a',u'Ầ':u'a',u'ầ':u'a',u'Ẫ':u'a',u'ẫ':u'a',u'Ẩ':u'a', - u'ẩ':u'a',u'Ǎ':u'a',u'ǎ':u'a',u'Å':u'a',u'å':u'a',u'Ǻ':u'a', - u'ǻ':u'a',u'Ä':u'a',u'ä':u'a',u'Ǟ':u'a',u'ǟ':u'a',u'Ã':u'a', - u'ã':u'a',u'Ȧ':u'a',u'ȧ':u'a',u'Ǡ':u'a',u'ǡ':u'a',u'Ą':u'a', - u'ą':u'a',u'Ā':u'a',u'ā':u'a',u'Ả':u'a',u'ả':u'a',u'Ȁ':u'a', - u'ȁ':u'a',u'Ȃ':u'a',u'ȃ':u'a',u'Ạ':u'a',u'ạ':u'a',u'Ặ':u'a', - u'ặ':u'a',u'Ậ':u'a',u'ậ':u'a',u'Ḁ':u'a',u'ḁ':u'a',u'Ⱥ':u'a', - u'ⱥ':u'a',u'Ǽ':u'a',u'ǽ':u'a',u'Ǣ':u'a',u'ǣ':u'a',u'Ḃ':u'b', - u'ḃ':u'b',u'Ḅ':u'b',u'ḅ':u'b',u'Ḇ':u'b',u'ḇ':u'b',u'Ƀ':u'b', - u'ƀ':u'b',u'ᵬ':u'b',u'Ɓ':u'b',u'ɓ':u'b',u'Ƃ':u'b',u'ƃ':u'b', - u'Ć':u'c',u'ć':u'c',u'Ĉ':u'c',u'ĉ':u'c',u'Č':u'c',u'č':u'c', - u'Ċ':u'c',u'ċ':u'c',u'Ç':u'c',u'ç':u'c',u'Ḉ':u'c',u'ḉ':u'c', - u'Ȼ':u'c',u'ȼ':u'c',u'Ƈ':u'c',u'ƈ':u'c',u'ɕ':u'c',u'Ď':u'd', - u'ď':u'd',u'Ḋ':u'd',u'ḋ':u'd',u'Ḑ':u'd',u'ḑ':u'd',u'Ḍ':u'd', - u'ḍ':u'd',u'Ḓ':u'd',u'ḓ':u'd',u'Ḏ':u'd',u'ḏ':u'd',u'Đ':u'd', - u'đ':u'd',u'ᵭ':u'd',u'Ɖ':u'd',u'ɖ':u'd',u'Ɗ':u'd',u'ɗ':u'd', - u'Ƌ':u'd',u'ƌ':u'd',u'ȡ':u'd',u'ð':u'd',u'É':u'e',u'Ə':u'e', - u'Ǝ':u'e',u'ǝ':u'e',u'é':u'e',u'È':u'e',u'è':u'e',u'Ĕ':u'e', - u'ĕ':u'e',u'Ê':u'e',u'ê':u'e',u'Ế':u'e',u'ế':u'e',u'Ề':u'e', - u'ề':u'e',u'Ễ':u'e',u'ễ':u'e',u'Ể':u'e',u'ể':u'e',u'Ě':u'e', - u'ě':u'e',u'Ë':u'e',u'ë':u'e',u'Ẽ':u'e',u'ẽ':u'e',u'Ė':u'e', - u'ė':u'e',u'Ȩ':u'e',u'ȩ':u'e',u'Ḝ':u'e',u'ḝ':u'e',u'Ę':u'e', - u'ę':u'e',u'Ē':u'e',u'ē':u'e',u'Ḗ':u'e',u'ḗ':u'e',u'Ḕ':u'e', - u'ḕ':u'e',u'Ẻ':u'e',u'ẻ':u'e',u'Ȅ':u'e',u'ȅ':u'e',u'Ȇ':u'e', - u'ȇ':u'e',u'Ẹ':u'e',u'ẹ':u'e',u'Ệ':u'e',u'ệ':u'e',u'Ḙ':u'e', - u'ḙ':u'e',u'Ḛ':u'e',u'ḛ':u'e',u'Ɇ':u'e',u'ɇ':u'e',u'ɚ':u'e', - u'ɝ':u'e',u'Ḟ':u'f',u'ḟ':u'f',u'ᵮ':u'f',u'Ƒ':u'f',u'ƒ':u'f', - u'Ǵ':u'g',u'ǵ':u'g',u'Ğ':u'g',u'ğ':u'g',u'Ĝ':u'g',u'ĝ':u'g', - u'Ǧ':u'g',u'ǧ':u'g',u'Ġ':u'g',u'ġ':u'g',u'Ģ':u'g',u'ģ':u'g', - u'Ḡ':u'g',u'ḡ':u'g',u'Ǥ':u'g',u'ǥ':u'g',u'Ɠ':u'g',u'ɠ':u'g', - u'Ĥ':u'h',u'ĥ':u'h',u'Ȟ':u'h',u'ȟ':u'h',u'Ḧ':u'h',u'ḧ':u'h', - u'Ḣ':u'h',u'ḣ':u'h',u'Ḩ':u'h',u'ḩ':u'h',u'Ḥ':u'h',u'ḥ':u'h', - u'Ḫ':u'h',u'ḫ':u'h',u'H':u'h',u'̱':u'h',u'ẖ':u'h',u'Ħ':u'h', - u'ħ':u'h',u'Ⱨ':u'h',u'ⱨ':u'h',u'Í':u'i',u'í':u'i',u'Ì':u'i', - u'ì':u'i',u'Ĭ':u'i',u'ĭ':u'i',u'Î':u'i',u'î':u'i',u'Ǐ':u'i', - u'ǐ':u'i',u'Ï':u'i',u'ï':u'i',u'Ḯ':u'i',u'ḯ':u'i',u'Ĩ':u'i', - u'ĩ':u'i',u'İ':u'i',u'i':u'i',u'Į':u'i',u'į':u'i',u'Ī':u'i', - u'ī':u'i',u'Ỉ':u'i',u'ỉ':u'i',u'Ȉ':u'i',u'ȉ':u'i',u'Ȋ':u'i', - u'ȋ':u'i',u'Ị':u'i',u'ị':u'i',u'Ḭ':u'i',u'ḭ':u'i',u'I':u'i', - u'ı':u'i',u'Ɨ':u'i',u'ɨ':u'i',u'Ĵ':u'j',u'ĵ':u'j',u'J':u'j', - u'̌':u'j',u'ǰ':u'j',u'ȷ':u'j',u'Ɉ':u'j',u'ɉ':u'j',u'ʝ':u'j', - u'ɟ':u'j',u'ʄ':u'j',u'Ḱ':u'k',u'ḱ':u'k',u'Ǩ':u'k',u'ǩ':u'k', - u'Ķ':u'k',u'ķ':u'k',u'Ḳ':u'k',u'ḳ':u'k',u'Ḵ':u'k',u'ḵ':u'k', - u'Ƙ':u'k',u'ƙ':u'k',u'Ⱪ':u'k',u'ⱪ':u'k',u'Ĺ':u'a',u'ĺ':u'l', - u'Ľ':u'l',u'ľ':u'l',u'Ļ':u'l',u'ļ':u'l',u'Ḷ':u'l',u'ḷ':u'l', - u'Ḹ':u'l',u'ḹ':u'l',u'Ḽ':u'l',u'ḽ':u'l',u'Ḻ':u'l',u'ḻ':u'l', - u'Ł':u'l',u'ł':u'l',u'Ł':u'l',u'̣':u'l',u'ł':u'l',u'̣':u'l', - u'Ŀ':u'l',u'ŀ':u'l',u'Ƚ':u'l',u'ƚ':u'l',u'Ⱡ':u'l',u'ⱡ':u'l', - u'Ɫ':u'l',u'ɫ':u'l',u'ɬ':u'l',u'ɭ':u'l',u'ȴ':u'l',u'Ḿ':u'm', - u'ḿ':u'm',u'Ṁ':u'm',u'ṁ':u'm',u'Ṃ':u'm',u'ṃ':u'm',u'ɱ':u'm', - u'Ń':u'n',u'ń':u'n',u'Ǹ':u'n',u'ǹ':u'n',u'Ň':u'n',u'ň':u'n', - u'Ñ':u'n',u'ñ':u'n',u'Ṅ':u'n',u'ṅ':u'n',u'Ņ':u'n',u'ņ':u'n', - u'Ṇ':u'n',u'ṇ':u'n',u'Ṋ':u'n',u'ṋ':u'n',u'Ṉ':u'n',u'ṉ':u'n', - u'Ɲ':u'n',u'ɲ':u'n',u'Ƞ':u'n',u'ƞ':u'n',u'ɳ':u'n',u'ȵ':u'n', - u'N':u'n',u'̈':u'n',u'n':u'n',u'̈':u'n',u'Ó':u'o',u'ó':u'o', - u'Ò':u'o',u'ò':u'o',u'Ŏ':u'o',u'ŏ':u'o',u'Ô':u'o',u'ô':u'o', - u'Ố':u'o',u'ố':u'o',u'Ồ':u'o',u'ồ':u'o',u'Ỗ':u'o',u'ỗ':u'o', - u'Ổ':u'o',u'ổ':u'o',u'Ǒ':u'o',u'ǒ':u'o',u'Ö':u'o',u'ö':u'o', - u'Ȫ':u'o',u'ȫ':u'o',u'Ő':u'o',u'ő':u'o',u'Õ':u'o',u'õ':u'o', - u'Ṍ':u'o',u'ṍ':u'o',u'Ṏ':u'o',u'ṏ':u'o',u'Ȭ':u'o',u'ȭ':u'o', - u'Ȯ':u'o',u'ȯ':u'o',u'Ȱ':u'o',u'ȱ':u'o',u'Ø':u'o',u'ø':u'o', - u'Ǿ':u'o',u'ǿ':u'o',u'Ǫ':u'o',u'ǫ':u'o',u'Ǭ':u'o',u'ǭ':u'o', - u'Ō':u'o',u'ō':u'o',u'Ṓ':u'o',u'ṓ':u'o',u'Ṑ':u'o',u'ṑ':u'o', - u'Ỏ':u'o',u'ỏ':u'o',u'Ȍ':u'o',u'ȍ':u'o',u'Ȏ':u'o',u'ȏ':u'o', - u'Ơ':u'o',u'ơ':u'o',u'Ớ':u'o',u'ớ':u'o',u'Ờ':u'o',u'ờ':u'o', - u'Ỡ':u'o',u'ỡ':u'o',u'Ở':u'o',u'ở':u'o',u'Ợ':u'o',u'ợ':u'o', - u'Ọ':u'o',u'ọ':u'o',u'Ộ':u'o',u'ộ':u'o',u'Ɵ':u'o',u'ɵ':u'o', - u'Ṕ':u'p',u'ṕ':u'p',u'Ṗ':u'p',u'ṗ':u'p',u'Ᵽ':u'p',u'Ƥ':u'p', - u'ƥ':u'p',u'P':u'p',u'̃':u'p',u'p':u'p',u'̃':u'p',u'ʠ':u'q', - u'Ɋ':u'q',u'ɋ':u'q',u'Ŕ':u'r',u'ŕ':u'r',u'Ř':u'r',u'ř':u'r', - u'Ṙ':u'r',u'ṙ':u'r',u'Ŗ':u'r',u'ŗ':u'r',u'Ȑ':u'r',u'ȑ':u'r', - u'Ȓ':u'r',u'ȓ':u'r',u'Ṛ':u'r',u'ṛ':u'r',u'Ṝ':u'r',u'ṝ':u'r', - u'Ṟ':u'r',u'ṟ':u'r',u'Ɍ':u'r',u'ɍ':u'r',u'ᵲ':u'r',u'ɼ':u'r', - u'Ɽ':u'r',u'ɽ':u'r',u'ɾ':u'r',u'ᵳ':u'r',u'ß':u's',u'Ś':u's', - u'ś':u's',u'Ṥ':u's',u'ṥ':u's',u'Ŝ':u's',u'ŝ':u's',u'Š':u's', - u'š':u's',u'Ṧ':u's',u'ṧ':u's',u'Ṡ':u's',u'ṡ':u's',u'ẛ':u's', - u'Ş':u's',u'ş':u's',u'Ṣ':u's',u'ṣ':u's',u'Ṩ':u's',u'ṩ':u's', - u'Ș':u's',u'ș':u's',u'ʂ':u's',u'S':u's',u'̩':u's',u's':u's', - u'̩':u's',u'Þ':u't',u'þ':u't',u'Ť':u't',u'ť':u't',u'T':u't', - u'̈':u't',u'ẗ':u't',u'Ṫ':u't',u'ṫ':u't',u'Ţ':u't',u'ţ':u't', - u'Ṭ':u't',u'ṭ':u't',u'Ț':u't',u'ț':u't',u'Ṱ':u't',u'ṱ':u't', - u'Ṯ':u't',u'ṯ':u't',u'Ŧ':u't',u'ŧ':u't',u'Ⱦ':u't',u'ⱦ':u't', - u'ᵵ':u't',u'ƫ':u't',u'Ƭ':u't',u'ƭ':u't',u'Ʈ':u't',u'ʈ':u't', - u'ȶ':u't',u'Ú':u'u',u'ú':u'u',u'Ù':u'u',u'ù':u'u',u'Ŭ':u'u', - u'ŭ':u'u',u'Û':u'u',u'û':u'u',u'Ǔ':u'u',u'ǔ':u'u',u'Ů':u'u', - u'ů':u'u',u'Ü':u'u',u'ü':u'u',u'Ǘ':u'u',u'ǘ':u'u',u'Ǜ':u'u', - u'ǜ':u'u',u'Ǚ':u'u',u'ǚ':u'u',u'Ǖ':u'u',u'ǖ':u'u',u'Ű':u'u', - u'ű':u'u',u'Ũ':u'u',u'ũ':u'u',u'Ṹ':u'u',u'ṹ':u'u',u'Ų':u'u', - u'ų':u'u',u'Ū':u'u',u'ū':u'u',u'Ṻ':u'u',u'ṻ':u'u',u'Ủ':u'u', - u'ủ':u'u',u'Ȕ':u'u',u'ȕ':u'u',u'Ȗ':u'u',u'ȗ':u'u',u'Ư':u'u', - u'ư':u'u',u'Ứ':u'u',u'ứ':u'u',u'Ừ':u'u',u'ừ':u'u',u'Ữ':u'u', - u'ữ':u'u',u'Ử':u'u',u'ử':u'u',u'Ự':u'u',u'ự':u'u',u'Ụ':u'u', - u'ụ':u'u',u'Ṳ':u'u',u'ṳ':u'u',u'Ṷ':u'u',u'ṷ':u'u',u'Ṵ':u'u', - u'ṵ':u'u',u'Ʉ':u'u',u'ʉ':u'u',u'Ṽ':u'v',u'ṽ':u'v',u'Ṿ':u'v', - u'ṿ':u'v',u'Ʋ':u'v',u'ʋ':u'v',u'Ẃ':u'w',u'ẃ':u'w',u'Ẁ':u'w', - u'ẁ':u'w',u'Ŵ':u'w',u'ŵ':u'w',u'W':u'w',u'̊':u'w',u'ẘ':u'w', - u'Ẅ':u'w',u'ẅ':u'w',u'Ẇ':u'w',u'ẇ':u'w',u'Ẉ':u'w',u'ẉ':u'w', - u'Ẍ':u'x',u'ẍ':u'x',u'Ẋ':u'x',u'ẋ':u'x',u'Ý':u'y',u'ý':u'y', - u'Ỳ':u'y',u'ỳ':u'y',u'Ŷ':u'y',u'ŷ':u'y',u'Y':u'y',u'̊':u'y', - u'ẙ':u'y',u'Ÿ':u'y',u'ÿ':u'y',u'Ỹ':u'y',u'ỹ':u'y',u'Ẏ':u'y', - u'ẏ':u'y',u'Ȳ':u'y',u'ȳ':u'y',u'Ỷ':u'y',u'ỷ':u'y',u'Ỵ':u'y', - u'ỵ':u'y',u'ʏ':u'y',u'Ɏ':u'y',u'ɏ':u'y',u'Ƴ':u'y',u'ƴ':u'y', - u'Ź':u'z',u'ź':u'z',u'Ẑ':u'z',u'ẑ':u'z',u'Ž':u'z',u'ž':u'z', - u'Ż':u'z',u'ż':u'z',u'Ẓ':u'z',u'ẓ':u'z',u'Ẕ':u'z',u'ẕ':u'z', - u'Ƶ':u'z',u'ƶ':u'z',u'Ȥ':u'z',u'ȥ':u'z',u'ʐ':u'z',u'ʑ':u'z', - u'Ⱬ':u'z',u'ⱬ':u'z',u'Ǯ':u'z',u'ǯ':u'z',u'ƺ':u'z',u'2':u'2', - u'6':u'6',u'B':u'B',u'F':u'F',u'J':u'J',u'N':u'N',u'R':u'R', - u'V':u'V',u'Z':u'Z',u'b':u'b',u'f':u'f',u'j':u'j',u'n':u'n', - u'r':u'r',u'v':u'v',u'z':u'z',u'1':u'1',u'5':u'5',u'9':u'9', - u'A':u'A',u'E':u'E',u'I':u'I',u'M':u'M',u'Q':u'Q',u'U':u'U', - u'Y':u'Y',u'a':u'a',u'e':u'e',u'i':u'i',u'm':u'm',u'q':u'q', - u'u':u'u',u'y':u'y',u'0':u'0',u'4':u'4',u'8':u'8',u'D':u'D', - u'H':u'H',u'L':u'L',u'P':u'P',u'T':u'T',u'X':u'X',u'd':u'd', - u'h':u'h',u'l':u'l',u'p':u'p',u't':u't',u'x':u'x',u'3':u'3', - u'7':u'7',u'C':u'C',u'G':u'G',u'K':u'K',u'O':u'O',u'S':u'S', - u'W':u'W',u'c':u'c',u'g':u'g',u'k':u'k',u'o':u'o',u's':u's', - u'w':u'w'}; -# The unicode.translate() method actually requires a dictionary mapping -# character *numbers* to characters, for some reason. -accent_map = dict((ord(k), v) for k, v in accent_map.iteritems()) - - -# This Sphinx charset table taken from http://speeple.com/unicode-maps.txt - -default_charset = """ -################################################## -# Latin -# A -U+00C0->a, U+00C1->a, U+00C2->a, U+00C3->a, U+00C4->a, U+00C5->a, U+00E0->a, U+00E1->a, U+00E2->a, U+00E3->a, U+00E4->a, U+00E5->a, -U+0100->a, U+0101->a, U+0102->a, U+0103->a, U+010300->a, U+0104->a, U+0105->a, U+01CD->a, U+01CE->a, U+01DE->a, U+01DF->a, U+01E0->a, -U+01E1->a, U+01FA->a, U+01FB->a, U+0200->a, U+0201->a, U+0202->a, U+0203->a, U+0226->a, U+0227->a, U+023A->a, U+0250->a, U+04D0->a, -U+04D1->a, U+1D2C->a, U+1D43->a, U+1D44->a, U+1D8F->a, U+1E00->a, U+1E01->a, U+1E9A->a, U+1EA0->a, U+1EA1->a, U+1EA2->a, U+1EA3->a, -U+1EA4->a, U+1EA5->a, U+1EA6->a, U+1EA7->a, U+1EA8->a, U+1EA9->a, U+1EAA->a, U+1EAB->a, U+1EAC->a, U+1EAD->a, U+1EAE->a, U+1EAF->a, -U+1EB0->a, U+1EB1->a, U+1EB2->a, U+1EB3->a, U+1EB4->a, U+1EB5->a, U+1EB6->a, U+1EB7->a, U+2090->a, U+2C65->a - -# B -U+0180->b, U+0181->b, U+0182->b, U+0183->b, U+0243->b, U+0253->b, U+0299->b, U+16D2->b, U+1D03->b, U+1D2E->b, U+1D2F->b, U+1D47->b, -U+1D6C->b, U+1D80->b, U+1E02->b, U+1E03->b, U+1E04->b, U+1E05->b, U+1E06->b, U+1E07->b - -# C -U+00C7->c, U+00E7->c, U+0106->c, U+0107->c, U+0108->c, U+0109->c, U+010A->c, U+010B->c, U+010C->c, U+010D->c, U+0187->c, U+0188->c, -U+023B->c, U+023C->c, U+0255->c, U+0297->c, U+1D9C->c, U+1D9D->c, U+1E08->c, U+1E09->c, U+212D->c, U+2184->c - -# D -U+010E->d, U+010F->d, U+0110->d, U+0111->d, U+0189->d, U+018A->d, U+018B->d, U+018C->d, U+01C5->d, U+01F2->d, U+0221->d, U+0256->d, -U+0257->d, U+1D05->d, U+1D30->d, U+1D48->d, U+1D6D->d, U+1D81->d, U+1D91->d, U+1E0A->d, U+1E0B->d, U+1E0C->d, U+1E0D->d, U+1E0E->d, -U+1E0F->d, U+1E10->d, U+1E11->d, U+1E12->d, U+1E13->d - -# E -U+00C8->e, U+00C9->e, U+00CA->e, U+00CB->e, U+00E8->e, U+00E9->e, U+00EA->e, U+00EB->e, U+0112->e, U+0113->e, U+0114->e, U+0115->e, -U+0116->e, U+0117->e, U+0118->e, U+0119->e, U+011A->e, U+011B->e, U+018E->e, U+0190->e, U+01DD->e, U+0204->e, U+0205->e, U+0206->e, -U+0207->e, U+0228->e, U+0229->e, U+0246->e, U+0247->e, U+0258->e, U+025B->e, U+025C->e, U+025D->e, U+025E->e, U+029A->e, U+1D07->e, -U+1D08->e, U+1D31->e, U+1D32->e, U+1D49->e, U+1D4B->e, U+1D4C->e, U+1D92->e, U+1D93->e, U+1D94->e, U+1D9F->e, U+1E14->e, U+1E15->e, -U+1E16->e, U+1E17->e, U+1E18->e, U+1E19->e, U+1E1A->e, U+1E1B->e, U+1E1C->e, U+1E1D->e, U+1EB8->e, U+1EB9->e, U+1EBA->e, U+1EBB->e, -U+1EBC->e, U+1EBD->e, U+1EBE->e, U+1EBF->e, U+1EC0->e, U+1EC1->e, U+1EC2->e, U+1EC3->e, U+1EC4->e, U+1EC5->e, U+1EC6->e, U+1EC7->e, -U+2091->e - -# F -U+0191->f, U+0192->f, U+1D6E->f, U+1D82->f, U+1DA0->f, U+1E1E->f, U+1E1F->f - -# G -U+011C->g, U+011D->g, U+011E->g, U+011F->g, U+0120->g, U+0121->g, U+0122->g, U+0123->g, U+0193->g, U+01E4->g, U+01E5->g, U+01E6->g, -U+01E7->g, U+01F4->g, U+01F5->g, U+0260->g, U+0261->g, U+0262->g, U+029B->g, U+1D33->g, U+1D4D->g, U+1D77->g, U+1D79->g, U+1D83->g, -U+1DA2->g, U+1E20->g, U+1E21->g - -# H -U+0124->h, U+0125->h, U+0126->h, U+0127->h, U+021E->h, U+021F->h, U+0265->h, U+0266->h, U+029C->h, U+02AE->h, U+02AF->h, U+02B0->h, -U+02B1->h, U+1D34->h, U+1DA3->h, U+1E22->h, U+1E23->h, U+1E24->h, U+1E25->h, U+1E26->h, U+1E27->h, U+1E28->h, U+1E29->h, U+1E2A->h, -U+1E2B->h, U+1E96->h, U+210C->h, U+2C67->h, U+2C68->h, U+2C75->h, U+2C76->h - -# I -U+00CC->i, U+00CD->i, U+00CE->i, U+00CF->i, U+00EC->i, U+00ED->i, U+00EE->i, U+00EF->i, U+010309->i, U+0128->i, U+0129->i, U+012A->i, -U+012B->i, U+012C->i, U+012D->i, U+012E->i, U+012F->i, U+0130->i, U+0131->i, U+0197->i, U+01CF->i, U+01D0->i, U+0208->i, U+0209->i, -U+020A->i, U+020B->i, U+0268->i, U+026A->i, U+040D->i, U+0418->i, U+0419->i, U+0438->i, U+0439->i, U+0456->i, U+1D09->i, U+1D35->i, -U+1D4E->i, U+1D62->i, U+1D7B->i, U+1D96->i, U+1DA4->i, U+1DA6->i, U+1DA7->i, U+1E2C->i, U+1E2D->i, U+1E2E->i, U+1E2F->i, U+1EC8->i, -U+1EC9->i, U+1ECA->i, U+1ECB->i, U+2071->i, U+2111->i - -# J -U+0134->j, U+0135->j, U+01C8->j, U+01CB->j, U+01F0->j, U+0237->j, U+0248->j, U+0249->j, U+025F->j, U+0284->j, U+029D->j, U+02B2->j, -U+1D0A->j, U+1D36->j, U+1DA1->j, U+1DA8->j - -# K -U+0136->k, U+0137->k, U+0198->k, U+0199->k, U+01E8->k, U+01E9->k, U+029E->k, U+1D0B->k, U+1D37->k, U+1D4F->k, U+1D84->k, U+1E30->k, -U+1E31->k, U+1E32->k, U+1E33->k, U+1E34->k, U+1E35->k, U+2C69->k, U+2C6A->k - -# L -U+0139->l, U+013A->l, U+013B->l, U+013C->l, U+013D->l, U+013E->l, U+013F->l, U+0140->l, U+0141->l, U+0142->l, U+019A->l, U+01C8->l, -U+0234->l, U+023D->l, U+026B->l, U+026C->l, U+026D->l, U+029F->l, U+02E1->l, U+1D0C->l, U+1D38->l, U+1D85->l, U+1DA9->l, U+1DAA->l, -U+1DAB->l, U+1E36->l, U+1E37->l, U+1E38->l, U+1E39->l, U+1E3A->l, U+1E3B->l, U+1E3C->l, U+1E3D->l, U+2C60->l, U+2C61->l, U+2C62->l - -# M -U+019C->m, U+026F->m, U+0270->m, U+0271->m, U+1D0D->m, U+1D1F->m, U+1D39->m, U+1D50->m, U+1D5A->m, U+1D6F->m, U+1D86->m, U+1DAC->m, -U+1DAD->m, U+1E3E->m, U+1E3F->m, U+1E40->m, U+1E41->m, U+1E42->m, U+1E43->m - -# N -U+00D1->n, U+00F1->n, U+0143->n, U+0144->n, U+0145->n, U+0146->n, U+0147->n, U+0148->n, U+0149->n, U+019D->n, U+019E->n, U+01CB->n, -U+01F8->n, U+01F9->n, U+0220->n, U+0235->n, U+0272->n, U+0273->n, U+0274->n, U+1D0E->n, U+1D3A->n, U+1D3B->n, U+1D70->n, U+1D87->n, -U+1DAE->n, U+1DAF->n, U+1DB0->n, U+1E44->n, U+1E45->n, U+1E46->n, U+1E47->n, U+1E48->n, U+1E49->n, U+1E4A->n, U+1E4B->n, U+207F->n - -# O -U+00D2->o, U+00D3->o, U+00D4->o, U+00D5->o, U+00D6->o, U+00D8->o, U+00F2->o, U+00F3->o, U+00F4->o, U+00F5->o, U+00F6->o, U+00F8->o, -U+01030F->o, U+014C->o, U+014D->o, U+014E->o, U+014F->o, U+0150->o, U+0151->o, U+0186->o, U+019F->o, U+01A0->o, U+01A1->o, U+01D1->o, -U+01D2->o, U+01EA->o, U+01EB->o, U+01EC->o, U+01ED->o, U+01FE->o, U+01FF->o, U+020C->o, U+020D->o, U+020E->o, U+020F->o, U+022A->o, -U+022B->o, U+022C->o, U+022D->o, U+022E->o, U+022F->o, U+0230->o, U+0231->o, U+0254->o, U+0275->o, U+043E->o, U+04E6->o, U+04E7->o, -U+04E8->o, U+04E9->o, U+04EA->o, U+04EB->o, U+1D0F->o, U+1D10->o, U+1D11->o, U+1D12->o, U+1D13->o, U+1D16->o, U+1D17->o, U+1D3C->o, -U+1D52->o, U+1D53->o, U+1D54->o, U+1D55->o, U+1D97->o, U+1DB1->o, U+1E4C->o, U+1E4D->o, U+1E4E->o, U+1E4F->o, U+1E50->o, U+1E51->o, -U+1E52->o, U+1E53->o, U+1ECC->o, U+1ECD->o, U+1ECE->o, U+1ECF->o, U+1ED0->o, U+1ED1->o, U+1ED2->o, U+1ED3->o, U+1ED4->o, U+1ED5->o, -U+1ED6->o, U+1ED7->o, U+1ED8->o, U+1ED9->o, U+1EDA->o, U+1EDB->o, U+1EDC->o, U+1EDD->o, U+1EDE->o, U+1EDF->o, U+1EE0->o, U+1EE1->o, -U+1EE2->o, U+1EE3->o, U+2092->o, U+2C9E->o, U+2C9F->o - -# P -U+01A4->p, U+01A5->p, U+1D18->p, U+1D3E->p, U+1D56->p, U+1D71->p, U+1D7D->p, U+1D88->p, U+1E54->p, U+1E55->p, U+1E56->p, U+1E57->p, -U+2C63->p - -# Q -U+024A->q, U+024B->q, U+02A0->q - -# R -U+0154->r, U+0155->r, U+0156->r, U+0157->r, U+0158->r, U+0159->r, U+0210->r, U+0211->r, U+0212->r, U+0213->r, U+024C->r, U+024D->r, -U+0279->r, U+027A->r, U+027B->r, U+027C->r, U+027D->r, U+027E->r, U+027F->r, U+0280->r, U+0281->r, U+02B3->r, U+02B4->r, U+02B5->r, -U+02B6->r, U+1D19->r, U+1D1A->r, U+1D3F->r, U+1D63->r, U+1D72->r, U+1D73->r, U+1D89->r, U+1DCA->r, U+1E58->r, U+1E59->r, U+1E5A->r, -U+1E5B->r, U+1E5C->r, U+1E5D->r, U+1E5E->r, U+1E5F->r, U+211C->r, U+2C64->r - -# S -U+00DF->s, U+015A->s, U+015B->s, U+015C->s, U+015D->s, U+015E->s, U+015F->s, U+0160->s, U+0161->s, U+017F->s, U+0218->s, U+0219->s, -U+023F->s, U+0282->s, U+02E2->s, U+1D74->s, U+1D8A->s, U+1DB3->s, U+1E60->s, U+1E61->s, U+1E62->s, U+1E63->s, U+1E64->s, U+1E65->s, -U+1E66->s, U+1E67->s, U+1E68->s, U+1E69->s, U+1E9B->s - -# T -U+0162->t, U+0163->t, U+0164->t, U+0165->t, U+0166->t, U+0167->t, U+01AB->t, U+01AC->t, U+01AD->t, U+01AE->t, U+021A->t, U+021B->t, -U+0236->t, U+023E->t, U+0287->t, U+0288->t, U+1D1B->t, U+1D40->t, U+1D57->t, U+1D75->t, U+1DB5->t, U+1E6A->t, U+1E6B->t, U+1E6C->t, -U+1E6D->t, U+1E6E->t, U+1E6F->t, U+1E70->t, U+1E71->t, U+1E97->t, U+2C66->t - -# U -U+00D9->u, U+00DA->u, U+00DB->u, U+00DC->u, U+00F9->u, U+00FA->u, U+00FB->u, U+00FC->u, U+010316->u, U+0168->u, U+0169->u, U+016A->u, -U+016B->u, U+016C->u, U+016D->u, U+016E->u, U+016F->u, U+0170->u, U+0171->u, U+0172->u, U+0173->u, U+01AF->u, U+01B0->u, U+01D3->u, -U+01D4->u, U+01D5->u, U+01D6->u, U+01D7->u, U+01D8->u, U+01D9->u, U+01DA->u, U+01DB->u, U+01DC->u, U+0214->u, U+0215->u, U+0216->u, -U+0217->u, U+0244->u, U+0289->u, U+1D1C->u, U+1D1D->u, U+1D1E->u, U+1D41->u, U+1D58->u, U+1D59->u, U+1D64->u, U+1D7E->u, U+1D99->u, -U+1DB6->u, U+1DB8->u, U+1E72->u, U+1E73->u, U+1E74->u, U+1E75->u, U+1E76->u, U+1E77->u, U+1E78->u, U+1E79->u, U+1E7A->u, U+1E7B->u, -U+1EE4->u, U+1EE5->u, U+1EE6->u, U+1EE7->u, U+1EE8->u, U+1EE9->u, U+1EEA->u, U+1EEB->u, U+1EEC->u, U+1EED->u, U+1EEE->u, U+1EEF->u, -U+1EF0->u, U+1EF1->u - -# V -U+01B2->v, U+0245->v, U+028B->v, U+028C->v, U+1D20->v, U+1D5B->v, U+1D65->v, U+1D8C->v, U+1DB9->v, U+1DBA->v, U+1E7C->v, U+1E7D->v, -U+1E7E->v, U+1E7F->v, U+2C74->v - -# W -U+0174->w, U+0175->w, U+028D->w, U+02B7->w, U+1D21->w, U+1D42->w, U+1E80->w, U+1E81->w, U+1E82->w, U+1E83->w, U+1E84->w, U+1E85->w, -U+1E86->w, U+1E87->w, U+1E88->w, U+1E89->w, U+1E98->w - -# X -U+02E3->x, U+1D8D->x, U+1E8A->x, U+1E8B->x, U+1E8C->x, U+1E8D->x, U+2093->x - -# Y -U+00DD->y, U+00FD->y, U+00FF->y, U+0176->y, U+0177->y, U+0178->y, U+01B3->y, U+01B4->y, U+0232->y, U+0233->y, U+024E->y, U+024F->y, -U+028E->y, U+028F->y, U+02B8->y, U+1E8E->y, U+1E8F->y, U+1E99->y, U+1EF2->y, U+1EF3->y, U+1EF4->y, U+1EF5->y, U+1EF6->y, U+1EF7->y, -U+1EF8->y, U+1EF9->y - -# Z -U+0179->z, U+017A->z, U+017B->z, U+017C->z, U+017D->z, U+017E->z, U+01B5->z, U+01B6->z, U+0224->z, U+0225->z, U+0240->z, U+0290->z, -U+0291->z, U+1D22->z, U+1D76->z, U+1D8E->z, U+1DBB->z, U+1DBC->z, U+1DBD->z, U+1E90->z, U+1E91->z, U+1E92->z, U+1E93->z, U+1E94->z, -U+1E95->z, U+2128->z, U+2C6B->z, U+2C6C->z - -# Latin Extras: -U+00C6->U+00E6, U+01E2->U+00E6, U+01E3->U+00E6, U+01FC->U+00E6, U+01FD->U+00E6, U+1D01->U+00E6, U+1D02->U+00E6, U+1D2D->U+00E6, -U+1D46->U+00E6, U+00E6 - -################################################## -# Arabic -U+0622->U+0627, U+0623->U+0627, U+0624->U+0648, U+0625->U+0627, U+0626->U+064A, U+06C0->U+06D5, U+06C2->U+06C1, U+06D3->U+06D2, -U+FB50->U+0671, U+FB51->U+0671, U+FB52->U+067B, U+FB53->U+067B, U+FB54->U+067B, U+FB56->U+067E, U+FB57->U+067E, U+FB58->U+067E, -U+FB5A->U+0680, U+FB5B->U+0680, U+FB5C->U+0680, U+FB5E->U+067A, U+FB5F->U+067A, U+FB60->U+067A, U+FB62->U+067F, U+FB63->U+067F, -U+FB64->U+067F, U+FB66->U+0679, U+FB67->U+0679, U+FB68->U+0679, U+FB6A->U+06A4, U+FB6B->U+06A4, U+FB6C->U+06A4, U+FB6E->U+06A6, -U+FB6F->U+06A6, U+FB70->U+06A6, U+FB72->U+0684, U+FB73->U+0684, U+FB74->U+0684, U+FB76->U+0683, U+FB77->U+0683, U+FB78->U+0683, -U+FB7A->U+0686, U+FB7B->U+0686, U+FB7C->U+0686, U+FB7E->U+0687, U+FB7F->U+0687, U+FB80->U+0687, U+FB82->U+068D, U+FB83->U+068D, -U+FB84->U+068C, U+FB85->U+068C, U+FB86->U+068E, U+FB87->U+068E, U+FB88->U+0688, U+FB89->U+0688, U+FB8A->U+0698, U+FB8B->U+0698, -U+FB8C->U+0691, U+FB8D->U+0691, U+FB8E->U+06A9, U+FB8F->U+06A9, U+FB90->U+06A9, U+FB92->U+06AF, U+FB93->U+06AF, U+FB94->U+06AF, -U+FB96->U+06B3, U+FB97->U+06B3, U+FB98->U+06B3, U+FB9A->U+06B1, U+FB9B->U+06B1, U+FB9C->U+06B1, U+FB9E->U+06BA, U+FB9F->U+06BA, -U+FBA0->U+06BB, U+FBA1->U+06BB, U+FBA2->U+06BB, U+FBA4->U+06C0, U+FBA5->U+06C0, U+FBA6->U+06C1, U+FBA7->U+06C1, U+FBA8->U+06C1, -U+FBAA->U+06BE, U+FBAB->U+06BE, U+FBAC->U+06BE, U+FBAE->U+06D2, U+FBAF->U+06D2, U+FBB0->U+06D3, U+FBB1->U+06D3, U+FBD3->U+06AD, -U+FBD4->U+06AD, U+FBD5->U+06AD, U+FBD7->U+06C7, U+FBD8->U+06C7, U+FBD9->U+06C6, U+FBDA->U+06C6, U+FBDB->U+06C8, U+FBDC->U+06C8, -U+FBDD->U+0677, U+FBDE->U+06CB, U+FBDF->U+06CB, U+FBE0->U+06C5, U+FBE1->U+06C5, U+FBE2->U+06C9, U+FBE3->U+06C9, U+FBE4->U+06D0, -U+FBE5->U+06D0, U+FBE6->U+06D0, U+FBE8->U+0649, U+FBFC->U+06CC, U+FBFD->U+06CC, U+FBFE->U+06CC, U+0621, U+0627..U+063A, U+0641..U+064A, -U+0660..U+0669, U+066E, U+066F, U+0671..U+06BF, U+06C1, U+06C3..U+06D2, U+06D5, U+06EE..U+06FC, U+06FF, U+0750..U+076D, U+FB55, U+FB59, -U+FB5D, U+FB61, U+FB65, U+FB69, U+FB6D, U+FB71, U+FB75, U+FB79, U+FB7D, U+FB81, U+FB91, U+FB95, U+FB99, U+FB9D, U+FBA3, U+FBA9, U+FBAD, -U+FBD6, U+FBE7, U+FBE9, U+FBFF - -################################################## -# Armenian -U+0531..U+0556->U+0561..U+0586, U+0561..U+0586, U+0587 - -################################################# -# Bengali -U+09DC->U+09A1, U+09DD->U+09A2, U+09DF->U+09AF, U+09F0->U+09AC, U+09F1->U+09AC, U+0985..U+0990, U+0993..U+09B0, U+09B2, U+09B6..U+09B9, -U+09CE, U+09E0, U+09E1, U+09E6..U+09EF - -################################################# -# CJK* -U+F900->U+8C48, U+F901->U+66F4, U+F902->U+8ECA, U+F903->U+8CC8, U+F904->U+6ED1, U+F905->U+4E32, U+F906->U+53E5, U+F907->U+9F9C, -U+F908->U+9F9C, U+F909->U+5951, U+F90A->U+91D1, U+F90B->U+5587, U+F90C->U+5948, U+F90D->U+61F6, U+F90E->U+7669, U+F90F->U+7F85, -U+F910->U+863F, U+F911->U+87BA, U+F912->U+88F8, U+F913->U+908F, U+F914->U+6A02, U+F915->U+6D1B, U+F916->U+70D9, U+F917->U+73DE, -U+F918->U+843D, U+F919->U+916A, U+F91A->U+99F1, U+F91B->U+4E82, U+F91C->U+5375, U+F91D->U+6B04, U+F91E->U+721B, U+F91F->U+862D, -U+F920->U+9E1E, U+F921->U+5D50, U+F922->U+6FEB, U+F923->U+85CD, U+F924->U+8964, U+F925->U+62C9, U+F926->U+81D8, U+F927->U+881F, -U+F928->U+5ECA, U+F929->U+6717, U+F92A->U+6D6A, U+F92B->U+72FC, U+F92C->U+90CE, U+F92D->U+4F86, U+F92E->U+51B7, U+F92F->U+52DE, -U+F930->U+64C4, U+F931->U+6AD3, U+F932->U+7210, U+F933->U+76E7, U+F934->U+8001, U+F935->U+8606, U+F936->U+865C, U+F937->U+8DEF, -U+F938->U+9732, U+F939->U+9B6F, U+F93A->U+9DFA, U+F93B->U+788C, U+F93C->U+797F, U+F93D->U+7DA0, U+F93E->U+83C9, U+F93F->U+9304, -U+F940->U+9E7F, U+F941->U+8AD6, U+F942->U+58DF, U+F943->U+5F04, U+F944->U+7C60, U+F945->U+807E, U+F946->U+7262, U+F947->U+78CA, -U+F948->U+8CC2, U+F949->U+96F7, U+F94A->U+58D8, U+F94B->U+5C62, U+F94C->U+6A13, U+F94D->U+6DDA, U+F94E->U+6F0F, U+F94F->U+7D2F, -U+F950->U+7E37, U+F951->U+964B, U+F952->U+52D2, U+F953->U+808B, U+F954->U+51DC, U+F955->U+51CC, U+F956->U+7A1C, U+F957->U+7DBE, -U+F958->U+83F1, U+F959->U+9675, U+F95A->U+8B80, U+F95B->U+62CF, U+F95C->U+6A02, U+F95D->U+8AFE, U+F95E->U+4E39, U+F95F->U+5BE7, -U+F960->U+6012, U+F961->U+7387, U+F962->U+7570, U+F963->U+5317, U+F964->U+78FB, U+F965->U+4FBF, U+F966->U+5FA9, U+F967->U+4E0D, -U+F968->U+6CCC, U+F969->U+6578, U+F96A->U+7D22, U+F96B->U+53C3, U+F96C->U+585E, U+F96D->U+7701, U+F96E->U+8449, U+F96F->U+8AAA, -U+F970->U+6BBA, U+F971->U+8FB0, U+F972->U+6C88, U+F973->U+62FE, U+F974->U+82E5, U+F975->U+63A0, U+F976->U+7565, U+F977->U+4EAE, -U+F978->U+5169, U+F979->U+51C9, U+F97A->U+6881, U+F97B->U+7CE7, U+F97C->U+826F, U+F97D->U+8AD2, U+F97E->U+91CF, U+F97F->U+52F5, -U+F980->U+5442, U+F981->U+5973, U+F982->U+5EEC, U+F983->U+65C5, U+F984->U+6FFE, U+F985->U+792A, U+F986->U+95AD, U+F987->U+9A6A, -U+F988->U+9E97, U+F989->U+9ECE, U+F98A->U+529B, U+F98B->U+66C6, U+F98C->U+6B77, U+F98D->U+8F62, U+F98E->U+5E74, U+F98F->U+6190, -U+F990->U+6200, U+F991->U+649A, U+F992->U+6F23, U+F993->U+7149, U+F994->U+7489, U+F995->U+79CA, U+F996->U+7DF4, U+F997->U+806F, -U+F998->U+8F26, U+F999->U+84EE, U+F99A->U+9023, U+F99B->U+934A, U+F99C->U+5217, U+F99D->U+52A3, U+F99E->U+54BD, U+F99F->U+70C8, -U+F9A0->U+88C2, U+F9A1->U+8AAA, U+F9A2->U+5EC9, U+F9A3->U+5FF5, U+F9A4->U+637B, U+F9A5->U+6BAE, U+F9A6->U+7C3E, U+F9A7->U+7375, -U+F9A8->U+4EE4, U+F9A9->U+56F9, U+F9AA->U+5BE7, U+F9AB->U+5DBA, U+F9AC->U+601C, U+F9AD->U+73B2, U+F9AE->U+7469, U+F9AF->U+7F9A, -U+F9B0->U+8046, U+F9B1->U+9234, U+F9B2->U+96F6, U+F9B3->U+9748, U+F9B4->U+9818, U+F9B5->U+4F8B, U+F9B6->U+79AE, U+F9B7->U+91B4, -U+F9B8->U+96B8, U+F9B9->U+60E1, U+F9BA->U+4E86, U+F9BB->U+50DA, U+F9BC->U+5BEE, U+F9BD->U+5C3F, U+F9BE->U+6599, U+F9BF->U+6A02, -U+F9C0->U+71CE, U+F9C1->U+7642, U+F9C2->U+84FC, U+F9C3->U+907C, U+F9C4->U+9F8D, U+F9C5->U+6688, U+F9C6->U+962E, U+F9C7->U+5289, -U+F9C8->U+677B, U+F9C9->U+67F3, U+F9CA->U+6D41, U+F9CB->U+6E9C, U+F9CC->U+7409, U+F9CD->U+7559, U+F9CE->U+786B, U+F9CF->U+7D10, -U+F9D0->U+985E, U+F9D1->U+516D, U+F9D2->U+622E, U+F9D3->U+9678, U+F9D4->U+502B, U+F9D5->U+5D19, U+F9D6->U+6DEA, U+F9D7->U+8F2A, -U+F9D8->U+5F8B, U+F9D9->U+6144, U+F9DA->U+6817, U+F9DB->U+7387, U+F9DC->U+9686, U+F9DD->U+5229, U+F9DE->U+540F, U+F9DF->U+5C65, -U+F9E0->U+6613, U+F9E1->U+674E, U+F9E2->U+68A8, U+F9E3->U+6CE5, U+F9E4->U+7406, U+F9E5->U+75E2, U+F9E6->U+7F79, U+F9E7->U+88CF, -U+F9E8->U+88E1, U+F9E9->U+91CC, U+F9EA->U+96E2, U+F9EB->U+533F, U+F9EC->U+6EBA, U+F9ED->U+541D, U+F9EE->U+71D0, U+F9EF->U+7498, -U+F9F0->U+85FA, U+F9F1->U+96A3, U+F9F2->U+9C57, U+F9F3->U+9E9F, U+F9F4->U+6797, U+F9F5->U+6DCB, U+F9F6->U+81E8, U+F9F7->U+7ACB, -U+F9F8->U+7B20, U+F9F9->U+7C92, U+F9FA->U+72C0, U+F9FB->U+7099, U+F9FC->U+8B58, U+F9FD->U+4EC0, U+F9FE->U+8336, U+F9FF->U+523A, -U+FA00->U+5207, U+FA01->U+5EA6, U+FA02->U+62D3, U+FA03->U+7CD6, U+FA04->U+5B85, U+FA05->U+6D1E, U+FA06->U+66B4, U+FA07->U+8F3B, -U+FA08->U+884C, U+FA09->U+964D, U+FA0A->U+898B, U+FA0B->U+5ED3, U+FA0C->U+5140, U+FA0D->U+55C0, U+FA10->U+585A, U+FA12->U+6674, -U+FA15->U+51DE, U+FA16->U+732A, U+FA17->U+76CA, U+FA18->U+793C, U+FA19->U+795E, U+FA1A->U+7965, U+FA1B->U+798F, U+FA1C->U+9756, -U+FA1D->U+7CBE, U+FA1E->U+7FBD, U+FA20->U+8612, U+FA22->U+8AF8, U+FA25->U+9038, U+FA26->U+90FD, U+FA2A->U+98EF, U+FA2B->U+98FC, -U+FA2C->U+9928, U+FA2D->U+9DB4, U+FA30->U+4FAE, U+FA31->U+50E7, U+FA32->U+514D, U+FA33->U+52C9, U+FA34->U+52E4, U+FA35->U+5351, -U+FA36->U+559D, U+FA37->U+5606, U+FA38->U+5668, U+FA39->U+5840, U+FA3A->U+58A8, U+FA3B->U+5C64, U+FA3C->U+5C6E, U+FA3D->U+6094, -U+FA3E->U+6168, U+FA3F->U+618E, U+FA40->U+61F2, U+FA41->U+654F, U+FA42->U+65E2, U+FA43->U+6691, U+FA44->U+6885, U+FA45->U+6D77, -U+FA46->U+6E1A, U+FA47->U+6F22, U+FA48->U+716E, U+FA49->U+722B, U+FA4A->U+7422, U+FA4B->U+7891, U+FA4C->U+793E, U+FA4D->U+7949, -U+FA4E->U+7948, U+FA4F->U+7950, U+FA50->U+7956, U+FA51->U+795D, U+FA52->U+798D, U+FA53->U+798E, U+FA54->U+7A40, U+FA55->U+7A81, -U+FA56->U+7BC0, U+FA57->U+7DF4, U+FA58->U+7E09, U+FA59->U+7E41, U+FA5A->U+7F72, U+FA5B->U+8005, U+FA5C->U+81ED, U+FA5D->U+8279, -U+FA5E->U+8279, U+FA5F->U+8457, U+FA60->U+8910, U+FA61->U+8996, U+FA62->U+8B01, U+FA63->U+8B39, U+FA64->U+8CD3, U+FA65->U+8D08, -U+FA66->U+8FB6, U+FA67->U+9038, U+FA68->U+96E3, U+FA69->U+97FF, U+FA6A->U+983B, U+FA70->U+4E26, U+FA71->U+51B5, U+FA72->U+5168, -U+FA73->U+4F80, U+FA74->U+5145, U+FA75->U+5180, U+FA76->U+52C7, U+FA77->U+52FA, U+FA78->U+559D, U+FA79->U+5555, U+FA7A->U+5599, -U+FA7B->U+55E2, U+FA7C->U+585A, U+FA7D->U+58B3, U+FA7E->U+5944, U+FA7F->U+5954, U+FA80->U+5A62, U+FA81->U+5B28, U+FA82->U+5ED2, -U+FA83->U+5ED9, U+FA84->U+5F69, U+FA85->U+5FAD, U+FA86->U+60D8, U+FA87->U+614E, U+FA88->U+6108, U+FA89->U+618E, U+FA8A->U+6160, -U+FA8B->U+61F2, U+FA8C->U+6234, U+FA8D->U+63C4, U+FA8E->U+641C, U+FA8F->U+6452, U+FA90->U+6556, U+FA91->U+6674, U+FA92->U+6717, -U+FA93->U+671B, U+FA94->U+6756, U+FA95->U+6B79, U+FA96->U+6BBA, U+FA97->U+6D41, U+FA98->U+6EDB, U+FA99->U+6ECB, U+FA9A->U+6F22, -U+FA9B->U+701E, U+FA9C->U+716E, U+FA9D->U+77A7, U+FA9E->U+7235, U+FA9F->U+72AF, U+FAA0->U+732A, U+FAA1->U+7471, U+FAA2->U+7506, -U+FAA3->U+753B, U+FAA4->U+761D, U+FAA5->U+761F, U+FAA6->U+76CA, U+FAA7->U+76DB, U+FAA8->U+76F4, U+FAA9->U+774A, U+FAAA->U+7740, -U+FAAB->U+78CC, U+FAAC->U+7AB1, U+FAAD->U+7BC0, U+FAAE->U+7C7B, U+FAAF->U+7D5B, U+FAB0->U+7DF4, U+FAB1->U+7F3E, U+FAB2->U+8005, -U+FAB3->U+8352, U+FAB4->U+83EF, U+FAB5->U+8779, U+FAB6->U+8941, U+FAB7->U+8986, U+FAB8->U+8996, U+FAB9->U+8ABF, U+FABA->U+8AF8, -U+FABB->U+8ACB, U+FABC->U+8B01, U+FABD->U+8AFE, U+FABE->U+8AED, U+FABF->U+8B39, U+FAC0->U+8B8A, U+FAC1->U+8D08, U+FAC2->U+8F38, -U+FAC3->U+9072, U+FAC4->U+9199, U+FAC5->U+9276, U+FAC6->U+967C, U+FAC7->U+96E3, U+FAC8->U+9756, U+FAC9->U+97DB, U+FACA->U+97FF, -U+FACB->U+980B, U+FACC->U+983B, U+FACD->U+9B12, U+FACE->U+9F9C, U+FACF->U+2284A, U+FAD0->U+22844, U+FAD1->U+233D5, U+FAD2->U+3B9D, -U+FAD3->U+4018, U+FAD4->U+4039, U+FAD5->U+25249, U+FAD6->U+25CD0, U+FAD7->U+27ED3, U+FAD8->U+9F43, U+FAD9->U+9F8E, U+2F800->U+4E3D, -U+2F801->U+4E38, U+2F802->U+4E41, U+2F803->U+20122, U+2F804->U+4F60, U+2F805->U+4FAE, U+2F806->U+4FBB, U+2F807->U+5002, U+2F808->U+507A, -U+2F809->U+5099, U+2F80A->U+50E7, U+2F80B->U+50CF, U+2F80C->U+349E, U+2F80D->U+2063A, U+2F80E->U+514D, U+2F80F->U+5154, U+2F810->U+5164, -U+2F811->U+5177, U+2F812->U+2051C, U+2F813->U+34B9, U+2F814->U+5167, U+2F815->U+518D, U+2F816->U+2054B, U+2F817->U+5197, -U+2F818->U+51A4, U+2F819->U+4ECC, U+2F81A->U+51AC, U+2F81B->U+51B5, U+2F81C->U+291DF, U+2F81D->U+51F5, U+2F81E->U+5203, -U+2F81F->U+34DF, U+2F820->U+523B, U+2F821->U+5246, U+2F822->U+5272, U+2F823->U+5277, U+2F824->U+3515, U+2F825->U+52C7, -U+2F826->U+52C9, U+2F827->U+52E4, U+2F828->U+52FA, U+2F829->U+5305, U+2F82A->U+5306, U+2F82B->U+5317, U+2F82C->U+5349, -U+2F82D->U+5351, U+2F82E->U+535A, U+2F82F->U+5373, U+2F830->U+537D, U+2F831->U+537F, U+2F832->U+537F, U+2F833->U+537F, -U+2F834->U+20A2C, U+2F835->U+7070, U+2F836->U+53CA, U+2F837->U+53DF, U+2F838->U+20B63, U+2F839->U+53EB, U+2F83A->U+53F1, -U+2F83B->U+5406, U+2F83C->U+549E, U+2F83D->U+5438, U+2F83E->U+5448, U+2F83F->U+5468, U+2F840->U+54A2, U+2F841->U+54F6, -U+2F842->U+5510, U+2F843->U+5553, U+2F844->U+5563, U+2F845->U+5584, U+2F846->U+5584, U+2F847->U+5599, U+2F848->U+55AB, -U+2F849->U+55B3, U+2F84A->U+55C2, U+2F84B->U+5716, U+2F84C->U+5606, U+2F84D->U+5717, U+2F84E->U+5651, U+2F84F->U+5674, -U+2F850->U+5207, U+2F851->U+58EE, U+2F852->U+57CE, U+2F853->U+57F4, U+2F854->U+580D, U+2F855->U+578B, U+2F856->U+5832, -U+2F857->U+5831, U+2F858->U+58AC, U+2F859->U+214E4, U+2F85A->U+58F2, U+2F85B->U+58F7, U+2F85C->U+5906, U+2F85D->U+591A, -U+2F85E->U+5922, U+2F85F->U+5962, U+2F860->U+216A8, U+2F861->U+216EA, U+2F862->U+59EC, U+2F863->U+5A1B, U+2F864->U+5A27, -U+2F865->U+59D8, U+2F866->U+5A66, U+2F867->U+36EE, U+2F868->U+36FC, U+2F869->U+5B08, U+2F86A->U+5B3E, U+2F86B->U+5B3E, -U+2F86C->U+219C8, U+2F86D->U+5BC3, U+2F86E->U+5BD8, U+2F86F->U+5BE7, U+2F870->U+5BF3, U+2F871->U+21B18, U+2F872->U+5BFF, -U+2F873->U+5C06, U+2F874->U+5F53, U+2F875->U+5C22, U+2F876->U+3781, U+2F877->U+5C60, U+2F878->U+5C6E, U+2F879->U+5CC0, -U+2F87A->U+5C8D, U+2F87B->U+21DE4, U+2F87C->U+5D43, U+2F87D->U+21DE6, U+2F87E->U+5D6E, U+2F87F->U+5D6B, U+2F880->U+5D7C, -U+2F881->U+5DE1, U+2F882->U+5DE2, U+2F883->U+382F, U+2F884->U+5DFD, U+2F885->U+5E28, U+2F886->U+5E3D, U+2F887->U+5E69, -U+2F888->U+3862, U+2F889->U+22183, U+2F88A->U+387C, U+2F88B->U+5EB0, U+2F88C->U+5EB3, U+2F88D->U+5EB6, U+2F88E->U+5ECA, -U+2F88F->U+2A392, U+2F890->U+5EFE, U+2F891->U+22331, U+2F892->U+22331, U+2F893->U+8201, U+2F894->U+5F22, U+2F895->U+5F22, -U+2F896->U+38C7, U+2F897->U+232B8, U+2F898->U+261DA, U+2F899->U+5F62, U+2F89A->U+5F6B, U+2F89B->U+38E3, U+2F89C->U+5F9A, -U+2F89D->U+5FCD, U+2F89E->U+5FD7, U+2F89F->U+5FF9, U+2F8A0->U+6081, U+2F8A1->U+393A, U+2F8A2->U+391C, U+2F8A3->U+6094, -U+2F8A4->U+226D4, U+2F8A5->U+60C7, U+2F8A6->U+6148, U+2F8A7->U+614C, U+2F8A8->U+614E, U+2F8A9->U+614C, U+2F8AA->U+617A, -U+2F8AB->U+618E, U+2F8AC->U+61B2, U+2F8AD->U+61A4, U+2F8AE->U+61AF, U+2F8AF->U+61DE, U+2F8B0->U+61F2, U+2F8B1->U+61F6, -U+2F8B2->U+6210, U+2F8B3->U+621B, U+2F8B4->U+625D, U+2F8B5->U+62B1, U+2F8B6->U+62D4, U+2F8B7->U+6350, U+2F8B8->U+22B0C, -U+2F8B9->U+633D, U+2F8BA->U+62FC, U+2F8BB->U+6368, U+2F8BC->U+6383, U+2F8BD->U+63E4, U+2F8BE->U+22BF1, U+2F8BF->U+6422, -U+2F8C0->U+63C5, U+2F8C1->U+63A9, U+2F8C2->U+3A2E, U+2F8C3->U+6469, U+2F8C4->U+647E, U+2F8C5->U+649D, U+2F8C6->U+6477, -U+2F8C7->U+3A6C, U+2F8C8->U+654F, U+2F8C9->U+656C, U+2F8CA->U+2300A, U+2F8CB->U+65E3, U+2F8CC->U+66F8, U+2F8CD->U+6649, -U+2F8CE->U+3B19, U+2F8CF->U+6691, U+2F8D0->U+3B08, U+2F8D1->U+3AE4, U+2F8D2->U+5192, U+2F8D3->U+5195, U+2F8D4->U+6700, -U+2F8D5->U+669C, U+2F8D6->U+80AD, U+2F8D7->U+43D9, U+2F8D8->U+6717, U+2F8D9->U+671B, U+2F8DA->U+6721, U+2F8DB->U+675E, -U+2F8DC->U+6753, U+2F8DD->U+233C3, U+2F8DE->U+3B49, U+2F8DF->U+67FA, U+2F8E0->U+6785, U+2F8E1->U+6852, U+2F8E2->U+6885, -U+2F8E3->U+2346D, U+2F8E4->U+688E, U+2F8E5->U+681F, U+2F8E6->U+6914, U+2F8E7->U+3B9D, U+2F8E8->U+6942, U+2F8E9->U+69A3, -U+2F8EA->U+69EA, U+2F8EB->U+6AA8, U+2F8EC->U+236A3, U+2F8ED->U+6ADB, U+2F8EE->U+3C18, U+2F8EF->U+6B21, U+2F8F0->U+238A7, -U+2F8F1->U+6B54, U+2F8F2->U+3C4E, U+2F8F3->U+6B72, U+2F8F4->U+6B9F, U+2F8F5->U+6BBA, U+2F8F6->U+6BBB, U+2F8F7->U+23A8D, -U+2F8F8->U+21D0B, U+2F8F9->U+23AFA, U+2F8FA->U+6C4E, U+2F8FB->U+23CBC, U+2F8FC->U+6CBF, U+2F8FD->U+6CCD, U+2F8FE->U+6C67, -U+2F8FF->U+6D16, U+2F900->U+6D3E, U+2F901->U+6D77, U+2F902->U+6D41, U+2F903->U+6D69, U+2F904->U+6D78, U+2F905->U+6D85, -U+2F906->U+23D1E, U+2F907->U+6D34, U+2F908->U+6E2F, U+2F909->U+6E6E, U+2F90A->U+3D33, U+2F90B->U+6ECB, U+2F90C->U+6EC7, -U+2F90D->U+23ED1, U+2F90E->U+6DF9, U+2F90F->U+6F6E, U+2F910->U+23F5E, U+2F911->U+23F8E, U+2F912->U+6FC6, U+2F913->U+7039, -U+2F914->U+701E, U+2F915->U+701B, U+2F916->U+3D96, U+2F917->U+704A, U+2F918->U+707D, U+2F919->U+7077, U+2F91A->U+70AD, -U+2F91B->U+20525, U+2F91C->U+7145, U+2F91D->U+24263, U+2F91E->U+719C, U+2F91F->U+243AB, U+2F920->U+7228, U+2F921->U+7235, -U+2F922->U+7250, U+2F923->U+24608, U+2F924->U+7280, U+2F925->U+7295, U+2F926->U+24735, U+2F927->U+24814, U+2F928->U+737A, -U+2F929->U+738B, U+2F92A->U+3EAC, U+2F92B->U+73A5, U+2F92C->U+3EB8, U+2F92D->U+3EB8, U+2F92E->U+7447, U+2F92F->U+745C, -U+2F930->U+7471, U+2F931->U+7485, U+2F932->U+74CA, U+2F933->U+3F1B, U+2F934->U+7524, U+2F935->U+24C36, U+2F936->U+753E, -U+2F937->U+24C92, U+2F938->U+7570, U+2F939->U+2219F, U+2F93A->U+7610, U+2F93B->U+24FA1, U+2F93C->U+24FB8, U+2F93D->U+25044, -U+2F93E->U+3FFC, U+2F93F->U+4008, U+2F940->U+76F4, U+2F941->U+250F3, U+2F942->U+250F2, U+2F943->U+25119, U+2F944->U+25133, -U+2F945->U+771E, U+2F946->U+771F, U+2F947->U+771F, U+2F948->U+774A, U+2F949->U+4039, U+2F94A->U+778B, U+2F94B->U+4046, -U+2F94C->U+4096, U+2F94D->U+2541D, U+2F94E->U+784E, U+2F94F->U+788C, U+2F950->U+78CC, U+2F951->U+40E3, U+2F952->U+25626, -U+2F953->U+7956, U+2F954->U+2569A, U+2F955->U+256C5, U+2F956->U+798F, U+2F957->U+79EB, U+2F958->U+412F, U+2F959->U+7A40, -U+2F95A->U+7A4A, U+2F95B->U+7A4F, U+2F95C->U+2597C, U+2F95D->U+25AA7, U+2F95E->U+25AA7, U+2F95F->U+7AEE, U+2F960->U+4202, -U+2F961->U+25BAB, U+2F962->U+7BC6, U+2F963->U+7BC9, U+2F964->U+4227, U+2F965->U+25C80, U+2F966->U+7CD2, U+2F967->U+42A0, -U+2F968->U+7CE8, U+2F969->U+7CE3, U+2F96A->U+7D00, U+2F96B->U+25F86, U+2F96C->U+7D63, U+2F96D->U+4301, U+2F96E->U+7DC7, -U+2F96F->U+7E02, U+2F970->U+7E45, U+2F971->U+4334, U+2F972->U+26228, U+2F973->U+26247, U+2F974->U+4359, U+2F975->U+262D9, -U+2F976->U+7F7A, U+2F977->U+2633E, U+2F978->U+7F95, U+2F979->U+7FFA, U+2F97A->U+8005, U+2F97B->U+264DA, U+2F97C->U+26523, -U+2F97D->U+8060, U+2F97E->U+265A8, U+2F97F->U+8070, U+2F980->U+2335F, U+2F981->U+43D5, U+2F982->U+80B2, U+2F983->U+8103, -U+2F984->U+440B, U+2F985->U+813E, U+2F986->U+5AB5, U+2F987->U+267A7, U+2F988->U+267B5, U+2F989->U+23393, U+2F98A->U+2339C, -U+2F98B->U+8201, U+2F98C->U+8204, U+2F98D->U+8F9E, U+2F98E->U+446B, U+2F98F->U+8291, U+2F990->U+828B, U+2F991->U+829D, -U+2F992->U+52B3, U+2F993->U+82B1, U+2F994->U+82B3, U+2F995->U+82BD, U+2F996->U+82E6, U+2F997->U+26B3C, U+2F998->U+82E5, -U+2F999->U+831D, U+2F99A->U+8363, U+2F99B->U+83AD, U+2F99C->U+8323, U+2F99D->U+83BD, U+2F99E->U+83E7, U+2F99F->U+8457, -U+2F9A0->U+8353, U+2F9A1->U+83CA, U+2F9A2->U+83CC, U+2F9A3->U+83DC, U+2F9A4->U+26C36, U+2F9A5->U+26D6B, U+2F9A6->U+26CD5, -U+2F9A7->U+452B, U+2F9A8->U+84F1, U+2F9A9->U+84F3, U+2F9AA->U+8516, U+2F9AB->U+273CA, U+2F9AC->U+8564, U+2F9AD->U+26F2C, -U+2F9AE->U+455D, U+2F9AF->U+4561, U+2F9B0->U+26FB1, U+2F9B1->U+270D2, U+2F9B2->U+456B, U+2F9B3->U+8650, U+2F9B4->U+865C, -U+2F9B5->U+8667, U+2F9B6->U+8669, U+2F9B7->U+86A9, U+2F9B8->U+8688, U+2F9B9->U+870E, U+2F9BA->U+86E2, U+2F9BB->U+8779, -U+2F9BC->U+8728, U+2F9BD->U+876B, U+2F9BE->U+8786, U+2F9BF->U+45D7, U+2F9C0->U+87E1, U+2F9C1->U+8801, U+2F9C2->U+45F9, -U+2F9C3->U+8860, U+2F9C4->U+8863, U+2F9C5->U+27667, U+2F9C6->U+88D7, U+2F9C7->U+88DE, U+2F9C8->U+4635, U+2F9C9->U+88FA, -U+2F9CA->U+34BB, U+2F9CB->U+278AE, U+2F9CC->U+27966, U+2F9CD->U+46BE, U+2F9CE->U+46C7, U+2F9CF->U+8AA0, U+2F9D0->U+8AED, -U+2F9D1->U+8B8A, U+2F9D2->U+8C55, U+2F9D3->U+27CA8, U+2F9D4->U+8CAB, U+2F9D5->U+8CC1, U+2F9D6->U+8D1B, U+2F9D7->U+8D77, -U+2F9D8->U+27F2F, U+2F9D9->U+20804, U+2F9DA->U+8DCB, U+2F9DB->U+8DBC, U+2F9DC->U+8DF0, U+2F9DD->U+208DE, U+2F9DE->U+8ED4, -U+2F9DF->U+8F38, U+2F9E0->U+285D2, U+2F9E1->U+285ED, U+2F9E2->U+9094, U+2F9E3->U+90F1, U+2F9E4->U+9111, U+2F9E5->U+2872E, -U+2F9E6->U+911B, U+2F9E7->U+9238, U+2F9E8->U+92D7, U+2F9E9->U+92D8, U+2F9EA->U+927C, U+2F9EB->U+93F9, U+2F9EC->U+9415, -U+2F9ED->U+28BFA, U+2F9EE->U+958B, U+2F9EF->U+4995, U+2F9F0->U+95B7, U+2F9F1->U+28D77, U+2F9F2->U+49E6, U+2F9F3->U+96C3, -U+2F9F4->U+5DB2, U+2F9F5->U+9723, U+2F9F6->U+29145, U+2F9F7->U+2921A, U+2F9F8->U+4A6E, U+2F9F9->U+4A76, U+2F9FA->U+97E0, -U+2F9FB->U+2940A, U+2F9FC->U+4AB2, U+2F9FD->U+29496, U+2F9FE->U+980B, U+2F9FF->U+980B, U+2FA00->U+9829, U+2FA01->U+295B6, -U+2FA02->U+98E2, U+2FA03->U+4B33, U+2FA04->U+9929, U+2FA05->U+99A7, U+2FA06->U+99C2, U+2FA07->U+99FE, U+2FA08->U+4BCE, -U+2FA09->U+29B30, U+2FA0A->U+9B12, U+2FA0B->U+9C40, U+2FA0C->U+9CFD, U+2FA0D->U+4CCE, U+2FA0E->U+4CED, U+2FA0F->U+9D67, -U+2FA10->U+2A0CE, U+2FA11->U+4CF8, U+2FA12->U+2A105, U+2FA13->U+2A20E, U+2FA14->U+2A291, U+2FA15->U+9EBB, U+2FA16->U+4D56, -U+2FA17->U+9EF9, U+2FA18->U+9EFE, U+2FA19->U+9F05, U+2FA1A->U+9F0F, U+2FA1B->U+9F16, U+2FA1C->U+9F3B, U+2FA1D->U+2A600, -U+2F00->U+4E00, U+2F01->U+4E28, U+2F02->U+4E36, U+2F03->U+4E3F, U+2F04->U+4E59, U+2F05->U+4E85, U+2F06->U+4E8C, U+2F07->U+4EA0, -U+2F08->U+4EBA, U+2F09->U+513F, U+2F0A->U+5165, U+2F0B->U+516B, U+2F0C->U+5182, U+2F0D->U+5196, U+2F0E->U+51AB, U+2F0F->U+51E0, -U+2F10->U+51F5, U+2F11->U+5200, U+2F12->U+529B, U+2F13->U+52F9, U+2F14->U+5315, U+2F15->U+531A, U+2F16->U+5338, U+2F17->U+5341, -U+2F18->U+535C, U+2F19->U+5369, U+2F1A->U+5382, U+2F1B->U+53B6, U+2F1C->U+53C8, U+2F1D->U+53E3, U+2F1E->U+56D7, U+2F1F->U+571F, -U+2F20->U+58EB, U+2F21->U+5902, U+2F22->U+590A, U+2F23->U+5915, U+2F24->U+5927, U+2F25->U+5973, U+2F26->U+5B50, U+2F27->U+5B80, -U+2F28->U+5BF8, U+2F29->U+5C0F, U+2F2A->U+5C22, U+2F2B->U+5C38, U+2F2C->U+5C6E, U+2F2D->U+5C71, U+2F2E->U+5DDB, U+2F2F->U+5DE5, -U+2F30->U+5DF1, U+2F31->U+5DFE, U+2F32->U+5E72, U+2F33->U+5E7A, U+2F34->U+5E7F, U+2F35->U+5EF4, U+2F36->U+5EFE, U+2F37->U+5F0B, -U+2F38->U+5F13, U+2F39->U+5F50, U+2F3A->U+5F61, U+2F3B->U+5F73, U+2F3C->U+5FC3, U+2F3D->U+6208, U+2F3E->U+6236, U+2F3F->U+624B, -U+2F40->U+652F, U+2F41->U+6534, U+2F42->U+6587, U+2F43->U+6597, U+2F44->U+65A4, U+2F45->U+65B9, U+2F46->U+65E0, U+2F47->U+65E5, -U+2F48->U+66F0, U+2F49->U+6708, U+2F4A->U+6728, U+2F4B->U+6B20, U+2F4C->U+6B62, U+2F4D->U+6B79, U+2F4E->U+6BB3, U+2F4F->U+6BCB, -U+2F50->U+6BD4, U+2F51->U+6BDB, U+2F52->U+6C0F, U+2F53->U+6C14, U+2F54->U+6C34, U+2F55->U+706B, U+2F56->U+722A, U+2F57->U+7236, -U+2F58->U+723B, U+2F59->U+723F, U+2F5A->U+7247, U+2F5B->U+7259, U+2F5C->U+725B, U+2F5D->U+72AC, U+2F5E->U+7384, U+2F5F->U+7389, -U+2F60->U+74DC, U+2F61->U+74E6, U+2F62->U+7518, U+2F63->U+751F, U+2F64->U+7528, U+2F65->U+7530, U+2F66->U+758B, U+2F67->U+7592, -U+2F68->U+7676, U+2F69->U+767D, U+2F6A->U+76AE, U+2F6B->U+76BF, U+2F6C->U+76EE, U+2F6D->U+77DB, U+2F6E->U+77E2, U+2F6F->U+77F3, -U+2F70->U+793A, U+2F71->U+79B8, U+2F72->U+79BE, U+2F73->U+7A74, U+2F74->U+7ACB, U+2F75->U+7AF9, U+2F76->U+7C73, U+2F77->U+7CF8, -U+2F78->U+7F36, U+2F79->U+7F51, U+2F7A->U+7F8A, U+2F7B->U+7FBD, U+2F7C->U+8001, U+2F7D->U+800C, U+2F7E->U+8012, U+2F7F->U+8033, -U+2F80->U+807F, U+2F81->U+8089, U+2F82->U+81E3, U+2F83->U+81EA, U+2F84->U+81F3, U+2F85->U+81FC, U+2F86->U+820C, U+2F87->U+821B, -U+2F88->U+821F, U+2F89->U+826E, U+2F8A->U+8272, U+2F8B->U+8278, U+2F8C->U+864D, U+2F8D->U+866B, U+2F8E->U+8840, U+2F8F->U+884C, -U+2F90->U+8863, U+2F91->U+897E, U+2F92->U+898B, U+2F93->U+89D2, U+2F94->U+8A00, U+2F95->U+8C37, U+2F96->U+8C46, U+2F97->U+8C55, -U+2F98->U+8C78, U+2F99->U+8C9D, U+2F9A->U+8D64, U+2F9B->U+8D70, U+2F9C->U+8DB3, U+2F9D->U+8EAB, U+2F9E->U+8ECA, U+2F9F->U+8F9B, -U+2FA0->U+8FB0, U+2FA1->U+8FB5, U+2FA2->U+9091, U+2FA3->U+9149, U+2FA4->U+91C6, U+2FA5->U+91CC, U+2FA6->U+91D1, U+2FA7->U+9577, -U+2FA8->U+9580, U+2FA9->U+961C, U+2FAA->U+96B6, U+2FAB->U+96B9, U+2FAC->U+96E8, U+2FAD->U+9751, U+2FAE->U+975E, U+2FAF->U+9762, -U+2FB0->U+9769, U+2FB1->U+97CB, U+2FB2->U+97ED, U+2FB3->U+97F3, U+2FB4->U+9801, U+2FB5->U+98A8, U+2FB6->U+98DB, U+2FB7->U+98DF, -U+2FB8->U+9996, U+2FB9->U+9999, U+2FBA->U+99AC, U+2FBB->U+9AA8, U+2FBC->U+9AD8, U+2FBD->U+9ADF, U+2FBE->U+9B25, U+2FBF->U+9B2F, -U+2FC0->U+9B32, U+2FC1->U+9B3C, U+2FC2->U+9B5A, U+2FC3->U+9CE5, U+2FC4->U+9E75, U+2FC5->U+9E7F, U+2FC6->U+9EA5, U+2FC7->U+9EBB, -U+2FC8->U+9EC3, U+2FC9->U+9ECD, U+2FCA->U+9ED1, U+2FCB->U+9EF9, U+2FCC->U+9EFD, U+2FCD->U+9F0E, U+2FCE->U+9F13, U+2FCF->U+9F20, -U+2FD0->U+9F3B, U+2FD1->U+9F4A, U+2FD2->U+9F52, U+2FD3->U+9F8D, U+2FD4->U+9F9C, U+2FD5->U+9FA0, U+3042->U+3041, U+3044->U+3043, -U+3046->U+3045, U+3048->U+3047, U+304A->U+3049, U+304C->U+304B, U+304E->U+304D, U+3050->U+304F, U+3052->U+3051, U+3054->U+3053, -U+3056->U+3055, U+3058->U+3057, U+305A->U+3059, U+305C->U+305B, U+305E->U+305D, U+3060->U+305F, U+3062->U+3061, U+3064->U+3063, -U+3065->U+3063, U+3067->U+3066, U+3069->U+3068, U+3070->U+306F, U+3071->U+306F, U+3073->U+3072, U+3074->U+3072, U+3076->U+3075, -U+3077->U+3075, U+3079->U+3078, U+307A->U+3078, U+307C->U+307B, U+307D->U+307B, U+3084->U+3083, U+3086->U+3085, U+3088->U+3087, -U+308F->U+308E, U+3094->U+3046, U+3095->U+304B, U+3096->U+3051, U+30A2->U+30A1, U+30A4->U+30A3, U+30A6->U+30A5, U+30A8->U+30A7, -U+30AA->U+30A9, U+30AC->U+30AB, U+30AE->U+30AD, U+30B0->U+30AF, U+30B2->U+30B1, U+30B4->U+30B3, U+30B6->U+30B5, U+30B8->U+30B7, -U+30BA->U+30B9, U+30BC->U+30BB, U+30BE->U+30BD, U+30C0->U+30BF, U+30C2->U+30C1, U+30C5->U+30C4, U+30C7->U+30C6, U+30C9->U+30C8, -U+30D0->U+30CF, U+30D1->U+30CF, U+30D3->U+30D2, U+30D4->U+30D2, U+30D6->U+30D5, U+30D7->U+30D5, U+30D9->U+30D8, U+30DA->U+30D8, -U+30DC->U+30DB, U+30DD->U+30DB, U+30E4->U+30E3, U+30E6->U+30E5, U+30E8->U+30E7, U+30EF->U+30EE, U+30F4->U+30A6, U+30AB->U+30F5, -U+30B1->U+30F6, U+30F7->U+30EF, U+30F8->U+30F0, U+30F9->U+30F1, U+30FA->U+30F2, U+30AF->U+31F0, U+30B7->U+31F1, U+30B9->U+31F2, -U+30C8->U+31F3, U+30CC->U+31F4, U+30CF->U+31F5, U+30D2->U+31F6, U+30D5->U+31F7, U+30D8->U+31F8, U+30DB->U+31F9, U+30E0->U+31FA, -U+30E9->U+31FB, U+30EA->U+31FC, U+30EB->U+31FD, U+30EC->U+31FE, U+30ED->U+31FF, U+FF66->U+30F2, U+FF67->U+30A1, U+FF68->U+30A3, -U+FF69->U+30A5, U+FF6A->U+30A7, U+FF6B->U+30A9, U+FF6C->U+30E3, U+FF6D->U+30E5, U+FF6E->U+30E7, U+FF6F->U+30C3, U+FF71->U+30A1, -U+FF72->U+30A3, U+FF73->U+30A5, U+FF74->U+30A7, U+FF75->U+30A9, U+FF76->U+30AB, U+FF77->U+30AD, U+FF78->U+30AF, U+FF79->U+30B1, -U+FF7A->U+30B3, U+FF7B->U+30B5, U+FF7C->U+30B7, U+FF7D->U+30B9, U+FF7E->U+30BB, U+FF7F->U+30BD, U+FF80->U+30BF, U+FF81->U+30C1, -U+FF82->U+30C3, U+FF83->U+30C6, U+FF84->U+30C8, U+FF85->U+30CA, U+FF86->U+30CB, U+FF87->U+30CC, U+FF88->U+30CD, U+FF89->U+30CE, -U+FF8A->U+30CF, U+FF8B->U+30D2, U+FF8C->U+30D5, U+FF8D->U+30D8, U+FF8E->U+30DB, U+FF8F->U+30DE, U+FF90->U+30DF, U+FF91->U+30E0, -U+FF92->U+30E1, U+FF93->U+30E2, U+FF94->U+30E3, U+FF95->U+30E5, U+FF96->U+30E7, U+FF97->U+30E9, U+FF98->U+30EA, U+FF99->U+30EB, -U+FF9A->U+30EC, U+FF9B->U+30ED, U+FF9C->U+30EF, U+FF9D->U+30F3, U+FFA0->U+3164, U+FFA1->U+3131, U+FFA2->U+3132, U+FFA3->U+3133, -U+FFA4->U+3134, U+FFA5->U+3135, U+FFA6->U+3136, U+FFA7->U+3137, U+FFA8->U+3138, U+FFA9->U+3139, U+FFAA->U+313A, U+FFAB->U+313B, -U+FFAC->U+313C, U+FFAD->U+313D, U+FFAE->U+313E, U+FFAF->U+313F, U+FFB0->U+3140, U+FFB1->U+3141, U+FFB2->U+3142, U+FFB3->U+3143, -U+FFB4->U+3144, U+FFB5->U+3145, U+FFB6->U+3146, U+FFB7->U+3147, U+FFB8->U+3148, U+FFB9->U+3149, U+FFBA->U+314A, U+FFBB->U+314B, -U+FFBC->U+314C, U+FFBD->U+314D, U+FFBE->U+314E, U+FFC2->U+314F, U+FFC3->U+3150, U+FFC4->U+3151, U+FFC5->U+3152, U+FFC6->U+3153, -U+FFC7->U+3154, U+FFCA->U+3155, U+FFCB->U+3156, U+FFCC->U+3157, U+FFCD->U+3158, U+FFCE->U+3159, U+FFCF->U+315A, U+FFD2->U+315B, -U+FFD3->U+315C, U+FFD4->U+315D, U+FFD5->U+315E, U+FFD6->U+315F, U+FFD7->U+3160, U+FFDA->U+3161, U+FFDB->U+3162, U+FFDC->U+3163, -U+3131->U+1100, U+3132->U+1101, U+3133->U+11AA, U+3134->U+1102, U+3135->U+11AC, U+3136->U+11AD, U+3137->U+1103, U+3138->U+1104, -U+3139->U+1105, U+313A->U+11B0, U+313B->U+11B1, U+313C->U+11B2, U+313D->U+11B3, U+313E->U+11B4, U+313F->U+11B5, U+3140->U+111A, -U+3141->U+1106, U+3142->U+1107, U+3143->U+1108, U+3144->U+1121, U+3145->U+1109, U+3146->U+110A, U+3147->U+110B, U+3148->U+110C, -U+3149->U+110D, U+314A->U+110E, U+314B->U+110F, U+314C->U+1110, U+314D->U+1111, U+314E->U+1112, U+314F->U+1161, U+3150->U+1162, -U+3151->U+1163, U+3152->U+1164, U+3153->U+1165, U+3154->U+1166, U+3155->U+1167, U+3156->U+1168, U+3157->U+1169, U+3158->U+116A, -U+3159->U+116B, U+315A->U+116C, U+315B->U+116D, U+315C->U+116E, U+315D->U+116F, U+315E->U+1170, U+315F->U+1171, U+3160->U+1172, -U+3161->U+1173, U+3162->U+1174, U+3163->U+1175, U+3165->U+1114, U+3166->U+1115, U+3167->U+11C7, U+3168->U+11C8, U+3169->U+11CC, -U+316A->U+11CE, U+316B->U+11D3, U+316C->U+11D7, U+316D->U+11D9, U+316E->U+111C, U+316F->U+11DD, U+3170->U+11DF, U+3171->U+111D, -U+3172->U+111E, U+3173->U+1120, U+3174->U+1122, U+3175->U+1123, U+3176->U+1127, U+3177->U+1129, U+3178->U+112B, U+3179->U+112C, -U+317A->U+112D, U+317B->U+112E, U+317C->U+112F, U+317D->U+1132, U+317E->U+1136, U+317F->U+1140, U+3180->U+1147, U+3181->U+114C, -U+3182->U+11F1, U+3183->U+11F2, U+3184->U+1157, U+3185->U+1158, U+3186->U+1159, U+3187->U+1184, U+3188->U+1185, U+3189->U+1188, -U+318A->U+1191, U+318B->U+1192, U+318C->U+1194, U+318D->U+119E, U+318E->U+11A1, U+A490->U+A408, U+A491->U+A1B9, U+4E00..U+9FBB, -U+3400..U+4DB5, U+20000..U+2A6D6, U+FA0E, U+FA0F, U+FA11, U+FA13, U+FA14, U+FA1F, U+FA21, U+FA23, U+FA24, U+FA27, U+FA28, U+FA29, -U+3105..U+312C, U+31A0..U+31B7, U+3041, U+3043, U+3045, U+3047, U+3049, U+304B, U+304D, U+304F, U+3051, U+3053, U+3055, U+3057, -U+3059, U+305B, U+305D, U+305F, U+3061, U+3063, U+3066, U+3068, U+306A..U+306F, U+3072, U+3075, U+3078, U+307B, U+307E..U+3083, -U+3085, U+3087, U+3089..U+308E, U+3090..U+3093, U+30A1, U+30A3, U+30A5, U+30A7, U+30A9, U+30AD, U+30AF, U+30B3, U+30B5, U+30BB, -U+30BD, U+30BF, U+30C1, U+30C3, U+30C4, U+30C6, U+30CA, U+30CB, U+30CD, U+30CE, U+30DE, U+30DF, U+30E1, U+30E2, U+30E3, U+30E5, -U+30E7, U+30EE, U+30F0..U+30F3, U+30F5, U+30F6, U+31F0, U+31F1, U+31F2, U+31F3, U+31F4, U+31F5, U+31F6, U+31F7, U+31F8, U+31F9, -U+31FA, U+31FB, U+31FC, U+31FD, U+31FE, U+31FF, U+AC00..U+D7A3, U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9, U+A000..U+A48C, -U+A492..U+A4C6 - -################################################## -# Coptic -# Notes: Some shared Greek characters, may require amendments. -U+2C80->U+2C81, U+2C81, U+2C82->U+2C83, U+2C83, U+2C84->U+2C85, U+2C85, U+2C86->U+2C87, U+2C87, U+2C88->U+2C89, U+2C89, U+2C8A->U+2C8B, -U+2C8B, U+2C8C->U+2C8D, U+2C8D, U+2C8E->U+2C8F, U+2C8F, U+2C90->U+2C91, U+2C91, U+2C92->U+2C93, U+2C93, U+2C94->U+2C95, U+2C95, -U+2C96->U+2C97, U+2C97, U+2C98->U+2C99, U+2C99, U+2C9A->U+2C9B, U+2C9B, U+2C9C->U+2C9D, U+2C9D, U+2C9E->U+2C9F, U+2C9F, U+2CA0->U+2CA1, -U+2CA1, U+2CA2->U+2CA3, U+2CA3, U+2CA4->U+2CA5, U+2CA5, U+2CA6->U+2CA7, U+2CA7, U+2CA8->U+2CA9, U+2CA9, U+2CAA->U+2CAB, U+2CAB, -U+2CAC->U+2CAD, U+2CAD, U+2CAE->U+2CAF, U+2CAF, U+2CB0->U+2CB1, U+2CB1, U+2CB2->U+2CB3, U+2CB3, U+2CB4->U+2CB5, U+2CB5, -U+2CB6->U+2CB7, U+2CB7, U+2CB8->U+2CB9, U+2CB9, U+2CBA->U+2CBB, U+2CBB, U+2CBC->U+2CBD, U+2CBD, U+2CBE->U+2CBF, U+2CBF, -U+2CC0->U+2CC1, U+2CC1, U+2CC2->U+2CC3, U+2CC3, U+2CC4->U+2CC5, U+2CC5, U+2CC6->U+2CC7, U+2CC7, U+2CC8->U+2CC9, U+2CC9, -U+2CCA->U+2CCB, U+2CCB, U+2CCC->U+2CCD, U+2CCD, U+2CCE->U+2CCF, U+2CCF, U+2CD0->U+2CD1, U+2CD1, U+2CD2->U+2CD3, U+2CD3, -U+2CD4->U+2CD5, U+2CD5, U+2CD6->U+2CD7, U+2CD7, U+2CD8->U+2CD9, U+2CD9, U+2CDA->U+2CDB, U+2CDB, U+2CDC->U+2CDD, U+2CDD, -U+2CDE->U+2CDF, U+2CDF, U+2CE0->U+2CE1, U+2CE1, U+2CE2->U+2CE3, U+2CE3 - -################################################## -# Cryllic* -U+0400->U+0435, U+0401->U+0435, U+0402->U+0452, U+0452, U+0403->U+0433, U+0404->U+0454, U+0454, U+0405->U+0455, U+0455, -U+0406->U+0456, U+0407->U+0456, U+0457->U+0456, U+0456, U+0408..U+040B->U+0458..U+045B, U+0458..U+045B, U+040C->U+043A, -U+040D->U+0438, U+040E->U+0443, U+040F->U+045F, U+045F, U+0450->U+0435, U+0451->U+0435, U+0453->U+0433, U+045C->U+043A, -U+045D->U+0438, U+045E->U+0443, U+0460->U+0461, U+0461, U+0462->U+0463, U+0463, U+0464->U+0465, U+0465, U+0466->U+0467, -U+0467, U+0468->U+0469, U+0469, U+046A->U+046B, U+046B, U+046C->U+046D, U+046D, U+046E->U+046F, U+046F, U+0470->U+0471, -U+0471, U+0472->U+0473, U+0473, U+0474->U+0475, U+0476->U+0475, U+0477->U+0475, U+0475, U+0478->U+0479, U+0479, U+047A->U+047B, -U+047B, U+047C->U+047D, U+047D, U+047E->U+047F, U+047F, U+0480->U+0481, U+0481, U+048A->U+0438, U+048B->U+0438, U+048C->U+044C, -U+048D->U+044C, U+048E->U+0440, U+048F->U+0440, U+0490->U+0433, U+0491->U+0433, U+0490->U+0433, U+0491->U+0433, U+0492->U+0433, -U+0493->U+0433, U+0494->U+0433, U+0495->U+0433, U+0496->U+0436, U+0497->U+0436, U+0498->U+0437, U+0499->U+0437, U+049A->U+043A, -U+049B->U+043A, U+049C->U+043A, U+049D->U+043A, U+049E->U+043A, U+049F->U+043A, U+04A0->U+043A, U+04A1->U+043A, U+04A2->U+043D, -U+04A3->U+043D, U+04A4->U+043D, U+04A5->U+043D, U+04A6->U+043F, U+04A7->U+043F, U+04A8->U+04A9, U+04A9, U+04AA->U+0441, -U+04AB->U+0441, U+04AC->U+0442, U+04AD->U+0442, U+04AE->U+0443, U+04AF->U+0443, U+04B0->U+0443, U+04B1->U+0443, U+04B2->U+0445, -U+04B3->U+0445, U+04B4->U+04B5, U+04B5, U+04B6->U+0447, U+04B7->U+0447, U+04B8->U+0447, U+04B9->U+0447, U+04BA->U+04BB, U+04BB, -U+04BC->U+04BD, U+04BE->U+04BD, U+04BF->U+04BD, U+04BD, U+04C0->U+04CF, U+04CF, U+04C1->U+0436, U+04C2->U+0436, U+04C3->U+043A, -U+04C4->U+043A, U+04C5->U+043B, U+04C6->U+043B, U+04C7->U+043D, U+04C8->U+043D, U+04C9->U+043D, U+04CA->U+043D, U+04CB->U+0447, -U+04CC->U+0447, U+04CD->U+043C, U+04CE->U+043C, U+04D0->U+0430, U+04D1->U+0430, U+04D2->U+0430, U+04D3->U+0430, U+04D4->U+00E6, -U+04D5->U+00E6, U+04D6->U+0435, U+04D7->U+0435, U+04D8->U+04D9, U+04DA->U+04D9, U+04DB->U+04D9, U+04D9, U+04DC->U+0436, -U+04DD->U+0436, U+04DE->U+0437, U+04DF->U+0437, U+04E0->U+04E1, U+04E1, U+04E2->U+0438, U+04E3->U+0438, U+04E4->U+0438, -U+04E5->U+0438, U+04E6->U+043E, U+04E7->U+043E, U+04E8->U+043E, U+04E9->U+043E, U+04EA->U+043E, U+04EB->U+043E, U+04EC->U+044D, -U+04ED->U+044D, U+04EE->U+0443, U+04EF->U+0443, U+04F0->U+0443, U+04F1->U+0443, U+04F2->U+0443, U+04F3->U+0443, U+04F4->U+0447, -U+04F5->U+0447, U+04F6->U+0433, U+04F7->U+0433, U+04F8->U+044B, U+04F9->U+044B, U+04FA->U+0433, U+04FB->U+0433, U+04FC->U+0445, -U+04FD->U+0445, U+04FE->U+0445, U+04FF->U+0445, U+0410..U+0418->U+0430..U+0438, U+0419->U+0438, U+0430..U+0438, -U+041A..U+042F->U+043A..U+044F, U+043A..U+044F - -################################################## -# Devanagari -U+0929->U+0928, U+0931->U+0930, U+0934->U+0933, U+0958->U+0915, U+0959->U+0916, U+095A->U+0917, U+095B->U+091C, U+095C->U+0921, -U+095D->U+0922, U+095E->U+092B, U+095F->U+092F, U+0904..U+0928, U+092A..U+0930, U+0932, U+0933, U+0935..U+0939, U+0960, U+0961, -U+0966..U+096F, U+097B..U+097F - -################################################## -# Georgian -U+10FC->U+10DC, U+10D0..U+10FA, U+10A0..U+10C5->U+2D00..U+2D25, U+2D00..U+2D25 - -################################################## -# Greek -U+0386->U+03B1, U+0388->U+03B5, U+0389->U+03B7, U+038A->U+03B9, U+038C->U+03BF, U+038E->U+03C5, U+038F->U+03C9, U+0390->U+03B9, -U+03AA->U+03B9, U+03AB->U+03C5, U+03AC->U+03B1, U+03AD->U+03B5, U+03AE->U+03B7, U+03AF->U+03B9, U+03B0->U+03C5, U+03CA->U+03B9, -U+03CB->U+03C5, U+03CC->U+03BF, U+03CD->U+03C5, U+03CE->U+03C9, U+03D0->U+03B2, U+03D1->U+03B8, U+03D2->U+03C5, U+03D3->U+03C5, -U+03D4->U+03C5, U+03D5->U+03C6, U+03D6->U+03C0, U+03D8->U+03D9, U+03DA->U+03DB, U+03DC->U+03DD, U+03DE->U+03DF, U+03E0->U+03E1, -U+03E2->U+03E3, U+03E4->U+03E5, U+03E6->U+03E7, U+03E8->U+03E9, U+03EA->U+03EB, U+03EC->U+03ED, U+03EE->U+03EF, U+03F0->U+03BA, -U+03F1->U+03C1, U+03F2->U+03C3, U+03F4->U+03B8, U+03F5->U+03B5, U+03F6->U+03B5, U+03F7->U+03F8, U+03F9->U+03C3, U+03FA->U+03FB, -U+1F00->U+03B1, U+1F01->U+03B1, U+1F02->U+03B1, U+1F03->U+03B1, U+1F04->U+03B1, U+1F05->U+03B1, U+1F06->U+03B1, U+1F07->U+03B1, -U+1F08->U+03B1, U+1F09->U+03B1, U+1F0A->U+03B1, U+1F0B->U+03B1, U+1F0C->U+03B1, U+1F0D->U+03B1, U+1F0E->U+03B1, U+1F0F->U+03B1, -U+1F10->U+03B5, U+1F11->U+03B5, U+1F12->U+03B5, U+1F13->U+03B5, U+1F14->U+03B5, U+1F15->U+03B5, U+1F18->U+03B5, U+1F19->U+03B5, -U+1F1A->U+03B5, U+1F1B->U+03B5, U+1F1C->U+03B5, U+1F1D->U+03B5, U+1F20->U+03B7, U+1F21->U+03B7, U+1F22->U+03B7, U+1F23->U+03B7, -U+1F24->U+03B7, U+1F25->U+03B7, U+1F26->U+03B7, U+1F27->U+03B7, U+1F28->U+03B7, U+1F29->U+03B7, U+1F2A->U+03B7, U+1F2B->U+03B7, -U+1F2C->U+03B7, U+1F2D->U+03B7, U+1F2E->U+03B7, U+1F2F->U+03B7, U+1F30->U+03B9, U+1F31->U+03B9, U+1F32->U+03B9, U+1F33->U+03B9, -U+1F34->U+03B9, U+1F35->U+03B9, U+1F36->U+03B9, U+1F37->U+03B9, U+1F38->U+03B9, U+1F39->U+03B9, U+1F3A->U+03B9, U+1F3B->U+03B9, -U+1F3C->U+03B9, U+1F3D->U+03B9, U+1F3E->U+03B9, U+1F3F->U+03B9, U+1F40->U+03BF, U+1F41->U+03BF, U+1F42->U+03BF, U+1F43->U+03BF, -U+1F44->U+03BF, U+1F45->U+03BF, U+1F48->U+03BF, U+1F49->U+03BF, U+1F4A->U+03BF, U+1F4B->U+03BF, U+1F4C->U+03BF, U+1F4D->U+03BF, -U+1F50->U+03C5, U+1F51->U+03C5, U+1F52->U+03C5, U+1F53->U+03C5, U+1F54->U+03C5, U+1F55->U+03C5, U+1F56->U+03C5, U+1F57->U+03C5, -U+1F59->U+03C5, U+1F5B->U+03C5, U+1F5D->U+03C5, U+1F5F->U+03C5, U+1F60->U+03C9, U+1F61->U+03C9, U+1F62->U+03C9, U+1F63->U+03C9, -U+1F64->U+03C9, U+1F65->U+03C9, U+1F66->U+03C9, U+1F67->U+03C9, U+1F68->U+03C9, U+1F69->U+03C9, U+1F6A->U+03C9, U+1F6B->U+03C9, -U+1F6C->U+03C9, U+1F6D->U+03C9, U+1F6E->U+03C9, U+1F6F->U+03C9, U+1F70->U+03B1, U+1F71->U+03B1, U+1F72->U+03B5, U+1F73->U+03B5, -U+1F74->U+03B7, U+1F75->U+03B7, U+1F76->U+03B9, U+1F77->U+03B9, U+1F78->U+03BF, U+1F79->U+03BF, U+1F7A->U+03C5, U+1F7B->U+03C5, -U+1F7C->U+03C9, U+1F7D->U+03C9, U+1F80->U+03B1, U+1F81->U+03B1, U+1F82->U+03B1, U+1F83->U+03B1, U+1F84->U+03B1, U+1F85->U+03B1, -U+1F86->U+03B1, U+1F87->U+03B1, U+1F88->U+03B1, U+1F89->U+03B1, U+1F8A->U+03B1, U+1F8B->U+03B1, U+1F8C->U+03B1, U+1F8D->U+03B1, -U+1F8E->U+03B1, U+1F8F->U+03B1, U+1F90->U+03B7, U+1F91->U+03B7, U+1F92->U+03B7, U+1F93->U+03B7, U+1F94->U+03B7, U+1F95->U+03B7, -U+1F96->U+03B7, U+1F97->U+03B7, U+1F98->U+03B7, U+1F99->U+03B7, U+1F9A->U+03B7, U+1F9B->U+03B7, U+1F9C->U+03B7, U+1F9D->U+03B7, -U+1F9E->U+03B7, U+1F9F->U+03B7, U+1FA0->U+03C9, U+1FA1->U+03C9, U+1FA2->U+03C9, U+1FA3->U+03C9, U+1FA4->U+03C9, U+1FA5->U+03C9, -U+1FA6->U+03C9, U+1FA7->U+03C9, U+1FA8->U+03C9, U+1FA9->U+03C9, U+1FAA->U+03C9, U+1FAB->U+03C9, U+1FAC->U+03C9, U+1FAD->U+03C9, -U+1FAE->U+03C9, U+1FAF->U+03C9, U+1FB0->U+03B1, U+1FB1->U+03B1, U+1FB2->U+03B1, U+1FB3->U+03B1, U+1FB4->U+03B1, U+1FB6->U+03B1, -U+1FB7->U+03B1, U+1FB8->U+03B1, U+1FB9->U+03B1, U+1FBA->U+03B1, U+1FBB->U+03B1, U+1FBC->U+03B1, U+1FC2->U+03B7, U+1FC3->U+03B7, -U+1FC4->U+03B7, U+1FC6->U+03B7, U+1FC7->U+03B7, U+1FC8->U+03B5, U+1FC9->U+03B5, U+1FCA->U+03B7, U+1FCB->U+03B7, U+1FCC->U+03B7, -U+1FD0->U+03B9, U+1FD1->U+03B9, U+1FD2->U+03B9, U+1FD3->U+03B9, U+1FD6->U+03B9, U+1FD7->U+03B9, U+1FD8->U+03B9, U+1FD9->U+03B9, -U+1FDA->U+03B9, U+1FDB->U+03B9, U+1FE0->U+03C5, U+1FE1->U+03C5, U+1FE2->U+03C5, U+1FE3->U+03C5, U+1FE4->U+03C1, U+1FE5->U+03C1, -U+1FE6->U+03C5, U+1FE7->U+03C5, U+1FE8->U+03C5, U+1FE9->U+03C5, U+1FEA->U+03C5, U+1FEB->U+03C5, U+1FEC->U+03C1, U+1FF2->U+03C9, -U+1FF3->U+03C9, U+1FF4->U+03C9, U+1FF6->U+03C9, U+1FF7->U+03C9, U+1FF8->U+03BF, U+1FF9->U+03BF, U+1FFA->U+03C9, U+1FFB->U+03C9, -U+1FFC->U+03C9, U+0391..U+03A1->U+03B1..U+03C1, U+03B1..U+03C1, U+03A3..U+03A9->U+03C3..U+03C9, U+03C3..U+03C9, U+03C2, U+03D9, -U+03DB, U+03DD, U+03DF, U+03E1, U+03E3, U+03E5, U+03E7, U+03E9, U+03EB, U+03ED, U+03EF, U+03F3, U+03F8, U+03FB - -################################################## -# Gujarati -U+0A85..U+0A8C, U+0A8F, U+0A90, U+0A93..U+0AB0, U+0AB2, U+0AB3, U+0AB5..U+0AB9, U+0AE0, U+0AE1, U+0AE6..U+0AEF - -################################################## -# Gurmukhi -U+0A33->U+0A32, U+0A36->U+0A38, U+0A59->U+0A16, U+0A5A->U+0A17, U+0A5B->U+0A1C, U+0A5E->U+0A2B, U+0A05..U+0A0A, U+0A0F, U+0A10, -U+0A13..U+0A28, U+0A2A..U+0A30, U+0A32, U+0A35, U+0A38, U+0A39, U+0A5C, U+0A66..U+0A6F - -################################################# -# Hebrew* -U+FB1D->U+05D9, U+FB1F->U+05F2, U+FB20->U+05E2, U+FB21->U+05D0, U+FB22->U+05D3, U+FB23->U+05D4, U+FB24->U+05DB, U+FB25->U+05DC, -U+FB26->U+05DD, U+FB27->U+05E8, U+FB28->U+05EA, U+FB2A->U+05E9, U+FB2B->U+05E9, U+FB2C->U+05E9, U+FB2D->U+05E9, U+FB2E->U+05D0, -U+FB2F->U+05D0, U+FB30->U+05D0, U+FB31->U+05D1, U+FB32->U+05D2, U+FB33->U+05D3, U+FB34->U+05D4, U+FB35->U+05D5, U+FB36->U+05D6, -U+FB38->U+05D8, U+FB39->U+05D9, U+FB3A->U+05DA, U+FB3B->U+05DB, U+FB3C->U+05DC, U+FB3E->U+05DE, U+FB40->U+05E0, U+FB41->U+05E1, -U+FB43->U+05E3, U+FB44->U+05E4, U+FB46->U+05E6, U+FB47->U+05E7, U+FB48->U+05E8, U+FB49->U+05E9, U+FB4A->U+05EA, U+FB4B->U+05D5, -U+FB4C->U+05D1, U+FB4D->U+05DB, U+FB4E->U+05E4, U+FB4F->U+05D0, U+05D0..U+05F2 - -################################################# -# Kannada -U+0C85..U+0C8C, U+0C8E..U+0C90, U+0C92..U+0CA8, U+0CAA..U+0CB3, U+0CB5..U+0CB9, U+0CE0, U+0CE1, U+0CE6..U+0CEF - -################################################# -# Limbu -U+1900..U+191C, U+1930..U+1938, U+1946..U+194F - -################################################# -# Malayalam -U+0D05..U+0D0C, U+0D0E..U+0D10, U+0D12..U+0D28, U+0D2A..U+0D39, U+0D60, U+0D61, U+0D66..U+0D6F - -################################################# -# Tamil -U+0B94->U+0B92, U+0B85..U+0B8A, U+0B8E..U+0B90, U+0B92, U+0B93, U+0B95, U+0B99, U+0B9A, U+0B9C, U+0B9E, U+0B9F, U+0BA3, U+0BA4, -U+0BA8..U+0BAA, U+0BAE..U+0BB9, U+0BE6..U+0BEF - -################################################# -# Thai -U+0E01..U+0E30, U+0E32, U+0E33, U+0E40..U+0E46, U+0E50..U+0E5B - -################################################## -# Common -U+FF10..U+FF19->0..9, U+FF21..U+FF3A->a..z, U+FF41..U+FF5A->a..z, 0..9, A..Z->a..z, a..z -""" - -# The expected value format is a commas-separated list of mappings. -# Two simplest mappings simply declare a character as valid, and map a single character -# to another single character, respectively. But specifying the whole table in such -# form would result in bloated and barely manageable specifications. So there are -# several syntax shortcuts that let you map ranges of characters at once. The complete -# list is as follows: -# -# A->a -# Single char mapping, declares source char 'A' as allowed to occur within keywords -# and maps it to destination char 'a' (but does not declare 'a' as allowed). -# A..Z->a..z -# Range mapping, declares all chars in source range as allowed and maps them to -# the destination range. Does not declare destination range as allowed. Also checks -# ranges' lengths (the lengths must be equal). -# a -# Stray char mapping, declares a character as allowed and maps it to itself. -# Equivalent to a->a single char mapping. -# a..z -# Stray range mapping, declares all characters in range as allowed and maps them to -# themselves. Equivalent to a..z->a..z range mapping. -# A..Z/2 -# Checkerboard range map. Maps every pair of chars to the second char. -# More formally, declares odd characters in range as allowed and maps them to the -# even ones; also declares even characters as allowed and maps them to themselves. -# For instance, A..Z/2 is equivalent to A->B, B->B, C->D, D->D, ..., Y->Z, Z->Z. -# This mapping shortcut is helpful for a number of Unicode blocks where uppercase -# and lowercase letters go in such interleaved order instead of contiguous chunks. - -_dewhite = re.compile(r"\s") -_char = r"((?:U\+[0-9A-Fa-f]{4,6})|.)" -_char_map = re.compile("^" + _char + "->" + _char + "$") -_range_map = re.compile("^" + _char + r"\.\." + _char + "->" + _char + ".." + _char + "$") -_stray_char = re.compile("^" + _char + "$") -_stray_range = re.compile("^" + _char + r"\.\." + _char + "$") -_checker_range = re.compile("^" + _char + r"\.\." + _char + "/2$") - -def charspec_to_int(string): - # Converts a character specification of the form 'A' or 'U+23BC' - # to an integer - if string.startswith("U+"): - return int(string[2:], 16) - elif len(string) == 1: - return ord(string) - else: - raise Exception("Can't convert charspec: %r" % string) - -def charset_table_to_dict(tablestring): - """Takes a string with the contents of a Sphinx charset table file and - returns a mapping object (a defaultdict, actually) of the kind expected by - the unicode.translate() method: that is, it maps a character number to a unicode - character or None if the character is not a valid word character. - - The Sphinx charset table format is described at - http://www.sphinxsearch.com/docs/current.html#conf-charset-table. - """ - - #map = {} - map = defaultdict(lambda: None) - for line in tablestring.split("\n"): - if not line or line.startswith("#"): continue - line = _dewhite.sub("", line) - for item in line.split(","): - if not item: continue - match = _range_map.match(item) - if match: - start1 = charspec_to_int(match.group(1)) - end1 = charspec_to_int(match.group(2)) - start2 = charspec_to_int(match.group(3)) - end2 = charspec_to_int(match.group(4)) - assert (end1 - start1) == (end2 - start2) - try: - for fromord, tooord in izip(xrange(start1, end1+1), xrange(start2, end2+1)): - map[fromord] = unichr(tooord) - except ValueError: - pass - continue - - match = _char_map.match(item) - if match: - fromord = charspec_to_int(match.group(1)) - toord = charspec_to_int(match.group(2)) - try: - map[fromord] = unichr(toord) - except ValueError: - pass - continue - - match = _stray_char.match(item) - if match: - ord = charspec_to_int(match.group(0)) - try: - map[ord] = unichr(ord) - except ValueError: - pass - continue - - match = _stray_range.match(item) - if match: - start = charspec_to_int(match.group(1)) - end = charspec_to_int(match.group(2)) - try: - for ord in xrange(start, end+1): - map[ord] = unichr(ord) - except ValueError: - pass - continue - - match = _checker_range.match(item) - if match: - fromord = charspec_to_int(match.group(1)) - toord = charspec_to_int(match.group(2)) - assert toord-fromord % 2 == 0 - for ord in xrange(fromord, toord + 1, 2): - try: - map[ord] = unichr(ord+1) - map[ord+1] = unichr(ord+1) - except ValueError: - pass - continue - - raise Exception("Don't know what to do with %r" % item) - return map - - - - - - - - - - - - - - - +# coding=utf-8 + +"""This module contains tools for working with Sphinx charset table files. These files +are useful for doing case and accent folding. +See :class:`whoosh.analysis.CharsetTokenizer` and :class:`whoosh.analysis.CharsetFilter`. +""" + +from collections import defaultdict +from itertools import izip +import re + +# This is a straightforward accent-folding charset taken from Carlos Bueno's +# article "Accent Folding for Auto-Complete", for use with CharsetFilter. +# +# http://www.alistapart.com/articles/accent-folding-for-auto-complete/ +# +# See the article for information and caveats. The code is lifted directly +# from here: +# +# http://github.com/aristus/accent-folding/blob/master/accent_fold.py + +accent_map = {u'ẚ':u'a',u'Á':u'a',u'á':u'a',u'À':u'a',u'à':u'a',u'Ă':u'a', + u'ă':u'a',u'Ắ':u'a',u'ắ':u'a',u'Ằ':u'a',u'ằ':u'a',u'Ẵ':u'a', + u'ẵ':u'a',u'Ẳ':u'a',u'ẳ':u'a',u'Â':u'a',u'â':u'a',u'Ấ':u'a', + u'ấ':u'a',u'Ầ':u'a',u'ầ':u'a',u'Ẫ':u'a',u'ẫ':u'a',u'Ẩ':u'a', + u'ẩ':u'a',u'Ǎ':u'a',u'ǎ':u'a',u'Å':u'a',u'å':u'a',u'Ǻ':u'a', + u'ǻ':u'a',u'Ä':u'a',u'ä':u'a',u'Ǟ':u'a',u'ǟ':u'a',u'Ã':u'a', + u'ã':u'a',u'Ȧ':u'a',u'ȧ':u'a',u'Ǡ':u'a',u'ǡ':u'a',u'Ą':u'a', + u'ą':u'a',u'Ā':u'a',u'ā':u'a',u'Ả':u'a',u'ả':u'a',u'Ȁ':u'a', + u'ȁ':u'a',u'Ȃ':u'a',u'ȃ':u'a',u'Ạ':u'a',u'ạ':u'a',u'Ặ':u'a', + u'ặ':u'a',u'Ậ':u'a',u'ậ':u'a',u'Ḁ':u'a',u'ḁ':u'a',u'Ⱥ':u'a', + u'ⱥ':u'a',u'Ǽ':u'a',u'ǽ':u'a',u'Ǣ':u'a',u'ǣ':u'a',u'Ḃ':u'b', + u'ḃ':u'b',u'Ḅ':u'b',u'ḅ':u'b',u'Ḇ':u'b',u'ḇ':u'b',u'Ƀ':u'b', + u'ƀ':u'b',u'ᵬ':u'b',u'Ɓ':u'b',u'ɓ':u'b',u'Ƃ':u'b',u'ƃ':u'b', + u'Ć':u'c',u'ć':u'c',u'Ĉ':u'c',u'ĉ':u'c',u'Č':u'c',u'č':u'c', + u'Ċ':u'c',u'ċ':u'c',u'Ç':u'c',u'ç':u'c',u'Ḉ':u'c',u'ḉ':u'c', + u'Ȼ':u'c',u'ȼ':u'c',u'Ƈ':u'c',u'ƈ':u'c',u'ɕ':u'c',u'Ď':u'd', + u'ď':u'd',u'Ḋ':u'd',u'ḋ':u'd',u'Ḑ':u'd',u'ḑ':u'd',u'Ḍ':u'd', + u'ḍ':u'd',u'Ḓ':u'd',u'ḓ':u'd',u'Ḏ':u'd',u'ḏ':u'd',u'Đ':u'd', + u'đ':u'd',u'ᵭ':u'd',u'Ɖ':u'd',u'ɖ':u'd',u'Ɗ':u'd',u'ɗ':u'd', + u'Ƌ':u'd',u'ƌ':u'd',u'ȡ':u'd',u'ð':u'd',u'É':u'e',u'Ə':u'e', + u'Ǝ':u'e',u'ǝ':u'e',u'é':u'e',u'È':u'e',u'è':u'e',u'Ĕ':u'e', + u'ĕ':u'e',u'Ê':u'e',u'ê':u'e',u'Ế':u'e',u'ế':u'e',u'Ề':u'e', + u'ề':u'e',u'Ễ':u'e',u'ễ':u'e',u'Ể':u'e',u'ể':u'e',u'Ě':u'e', + u'ě':u'e',u'Ë':u'e',u'ë':u'e',u'Ẽ':u'e',u'ẽ':u'e',u'Ė':u'e', + u'ė':u'e',u'Ȩ':u'e',u'ȩ':u'e',u'Ḝ':u'e',u'ḝ':u'e',u'Ę':u'e', + u'ę':u'e',u'Ē':u'e',u'ē':u'e',u'Ḗ':u'e',u'ḗ':u'e',u'Ḕ':u'e', + u'ḕ':u'e',u'Ẻ':u'e',u'ẻ':u'e',u'Ȅ':u'e',u'ȅ':u'e',u'Ȇ':u'e', + u'ȇ':u'e',u'Ẹ':u'e',u'ẹ':u'e',u'Ệ':u'e',u'ệ':u'e',u'Ḙ':u'e', + u'ḙ':u'e',u'Ḛ':u'e',u'ḛ':u'e',u'Ɇ':u'e',u'ɇ':u'e',u'ɚ':u'e', + u'ɝ':u'e',u'Ḟ':u'f',u'ḟ':u'f',u'ᵮ':u'f',u'Ƒ':u'f',u'ƒ':u'f', + u'Ǵ':u'g',u'ǵ':u'g',u'Ğ':u'g',u'ğ':u'g',u'Ĝ':u'g',u'ĝ':u'g', + u'Ǧ':u'g',u'ǧ':u'g',u'Ġ':u'g',u'ġ':u'g',u'Ģ':u'g',u'ģ':u'g', + u'Ḡ':u'g',u'ḡ':u'g',u'Ǥ':u'g',u'ǥ':u'g',u'Ɠ':u'g',u'ɠ':u'g', + u'Ĥ':u'h',u'ĥ':u'h',u'Ȟ':u'h',u'ȟ':u'h',u'Ḧ':u'h',u'ḧ':u'h', + u'Ḣ':u'h',u'ḣ':u'h',u'Ḩ':u'h',u'ḩ':u'h',u'Ḥ':u'h',u'ḥ':u'h', + u'Ḫ':u'h',u'ḫ':u'h',u'H':u'h',u'̱':u'h',u'ẖ':u'h',u'Ħ':u'h', + u'ħ':u'h',u'Ⱨ':u'h',u'ⱨ':u'h',u'Í':u'i',u'í':u'i',u'Ì':u'i', + u'ì':u'i',u'Ĭ':u'i',u'ĭ':u'i',u'Î':u'i',u'î':u'i',u'Ǐ':u'i', + u'ǐ':u'i',u'Ï':u'i',u'ï':u'i',u'Ḯ':u'i',u'ḯ':u'i',u'Ĩ':u'i', + u'ĩ':u'i',u'İ':u'i',u'i':u'i',u'Į':u'i',u'į':u'i',u'Ī':u'i', + u'ī':u'i',u'Ỉ':u'i',u'ỉ':u'i',u'Ȉ':u'i',u'ȉ':u'i',u'Ȋ':u'i', + u'ȋ':u'i',u'Ị':u'i',u'ị':u'i',u'Ḭ':u'i',u'ḭ':u'i',u'I':u'i', + u'ı':u'i',u'Ɨ':u'i',u'ɨ':u'i',u'Ĵ':u'j',u'ĵ':u'j',u'J':u'j', + u'̌':u'j',u'ǰ':u'j',u'ȷ':u'j',u'Ɉ':u'j',u'ɉ':u'j',u'ʝ':u'j', + u'ɟ':u'j',u'ʄ':u'j',u'Ḱ':u'k',u'ḱ':u'k',u'Ǩ':u'k',u'ǩ':u'k', + u'Ķ':u'k',u'ķ':u'k',u'Ḳ':u'k',u'ḳ':u'k',u'Ḵ':u'k',u'ḵ':u'k', + u'Ƙ':u'k',u'ƙ':u'k',u'Ⱪ':u'k',u'ⱪ':u'k',u'Ĺ':u'a',u'ĺ':u'l', + u'Ľ':u'l',u'ľ':u'l',u'Ļ':u'l',u'ļ':u'l',u'Ḷ':u'l',u'ḷ':u'l', + u'Ḹ':u'l',u'ḹ':u'l',u'Ḽ':u'l',u'ḽ':u'l',u'Ḻ':u'l',u'ḻ':u'l', + u'Ł':u'l',u'ł':u'l',u'Ł':u'l',u'̣':u'l',u'ł':u'l',u'̣':u'l', + u'Ŀ':u'l',u'ŀ':u'l',u'Ƚ':u'l',u'ƚ':u'l',u'Ⱡ':u'l',u'ⱡ':u'l', + u'Ɫ':u'l',u'ɫ':u'l',u'ɬ':u'l',u'ɭ':u'l',u'ȴ':u'l',u'Ḿ':u'm', + u'ḿ':u'm',u'Ṁ':u'm',u'ṁ':u'm',u'Ṃ':u'm',u'ṃ':u'm',u'ɱ':u'm', + u'Ń':u'n',u'ń':u'n',u'Ǹ':u'n',u'ǹ':u'n',u'Ň':u'n',u'ň':u'n', + u'Ñ':u'n',u'ñ':u'n',u'Ṅ':u'n',u'ṅ':u'n',u'Ņ':u'n',u'ņ':u'n', + u'Ṇ':u'n',u'ṇ':u'n',u'Ṋ':u'n',u'ṋ':u'n',u'Ṉ':u'n',u'ṉ':u'n', + u'Ɲ':u'n',u'ɲ':u'n',u'Ƞ':u'n',u'ƞ':u'n',u'ɳ':u'n',u'ȵ':u'n', + u'N':u'n',u'̈':u'n',u'n':u'n',u'̈':u'n',u'Ó':u'o',u'ó':u'o', + u'Ò':u'o',u'ò':u'o',u'Ŏ':u'o',u'ŏ':u'o',u'Ô':u'o',u'ô':u'o', + u'Ố':u'o',u'ố':u'o',u'Ồ':u'o',u'ồ':u'o',u'Ỗ':u'o',u'ỗ':u'o', + u'Ổ':u'o',u'ổ':u'o',u'Ǒ':u'o',u'ǒ':u'o',u'Ö':u'o',u'ö':u'o', + u'Ȫ':u'o',u'ȫ':u'o',u'Ő':u'o',u'ő':u'o',u'Õ':u'o',u'õ':u'o', + u'Ṍ':u'o',u'ṍ':u'o',u'Ṏ':u'o',u'ṏ':u'o',u'Ȭ':u'o',u'ȭ':u'o', + u'Ȯ':u'o',u'ȯ':u'o',u'Ȱ':u'o',u'ȱ':u'o',u'Ø':u'o',u'ø':u'o', + u'Ǿ':u'o',u'ǿ':u'o',u'Ǫ':u'o',u'ǫ':u'o',u'Ǭ':u'o',u'ǭ':u'o', + u'Ō':u'o',u'ō':u'o',u'Ṓ':u'o',u'ṓ':u'o',u'Ṑ':u'o',u'ṑ':u'o', + u'Ỏ':u'o',u'ỏ':u'o',u'Ȍ':u'o',u'ȍ':u'o',u'Ȏ':u'o',u'ȏ':u'o', + u'Ơ':u'o',u'ơ':u'o',u'Ớ':u'o',u'ớ':u'o',u'Ờ':u'o',u'ờ':u'o', + u'Ỡ':u'o',u'ỡ':u'o',u'Ở':u'o',u'ở':u'o',u'Ợ':u'o',u'ợ':u'o', + u'Ọ':u'o',u'ọ':u'o',u'Ộ':u'o',u'ộ':u'o',u'Ɵ':u'o',u'ɵ':u'o', + u'Ṕ':u'p',u'ṕ':u'p',u'Ṗ':u'p',u'ṗ':u'p',u'Ᵽ':u'p',u'Ƥ':u'p', + u'ƥ':u'p',u'P':u'p',u'̃':u'p',u'p':u'p',u'̃':u'p',u'ʠ':u'q', + u'Ɋ':u'q',u'ɋ':u'q',u'Ŕ':u'r',u'ŕ':u'r',u'Ř':u'r',u'ř':u'r', + u'Ṙ':u'r',u'ṙ':u'r',u'Ŗ':u'r',u'ŗ':u'r',u'Ȑ':u'r',u'ȑ':u'r', + u'Ȓ':u'r',u'ȓ':u'r',u'Ṛ':u'r',u'ṛ':u'r',u'Ṝ':u'r',u'ṝ':u'r', + u'Ṟ':u'r',u'ṟ':u'r',u'Ɍ':u'r',u'ɍ':u'r',u'ᵲ':u'r',u'ɼ':u'r', + u'Ɽ':u'r',u'ɽ':u'r',u'ɾ':u'r',u'ᵳ':u'r',u'ß':u's',u'Ś':u's', + u'ś':u's',u'Ṥ':u's',u'ṥ':u's',u'Ŝ':u's',u'ŝ':u's',u'Š':u's', + u'š':u's',u'Ṧ':u's',u'ṧ':u's',u'Ṡ':u's',u'ṡ':u's',u'ẛ':u's', + u'Ş':u's',u'ş':u's',u'Ṣ':u's',u'ṣ':u's',u'Ṩ':u's',u'ṩ':u's', + u'Ș':u's',u'ș':u's',u'ʂ':u's',u'S':u's',u'̩':u's',u's':u's', + u'̩':u's',u'Þ':u't',u'þ':u't',u'Ť':u't',u'ť':u't',u'T':u't', + u'̈':u't',u'ẗ':u't',u'Ṫ':u't',u'ṫ':u't',u'Ţ':u't',u'ţ':u't', + u'Ṭ':u't',u'ṭ':u't',u'Ț':u't',u'ț':u't',u'Ṱ':u't',u'ṱ':u't', + u'Ṯ':u't',u'ṯ':u't',u'Ŧ':u't',u'ŧ':u't',u'Ⱦ':u't',u'ⱦ':u't', + u'ᵵ':u't',u'ƫ':u't',u'Ƭ':u't',u'ƭ':u't',u'Ʈ':u't',u'ʈ':u't', + u'ȶ':u't',u'Ú':u'u',u'ú':u'u',u'Ù':u'u',u'ù':u'u',u'Ŭ':u'u', + u'ŭ':u'u',u'Û':u'u',u'û':u'u',u'Ǔ':u'u',u'ǔ':u'u',u'Ů':u'u', + u'ů':u'u',u'Ü':u'u',u'ü':u'u',u'Ǘ':u'u',u'ǘ':u'u',u'Ǜ':u'u', + u'ǜ':u'u',u'Ǚ':u'u',u'ǚ':u'u',u'Ǖ':u'u',u'ǖ':u'u',u'Ű':u'u', + u'ű':u'u',u'Ũ':u'u',u'ũ':u'u',u'Ṹ':u'u',u'ṹ':u'u',u'Ų':u'u', + u'ų':u'u',u'Ū':u'u',u'ū':u'u',u'Ṻ':u'u',u'ṻ':u'u',u'Ủ':u'u', + u'ủ':u'u',u'Ȕ':u'u',u'ȕ':u'u',u'Ȗ':u'u',u'ȗ':u'u',u'Ư':u'u', + u'ư':u'u',u'Ứ':u'u',u'ứ':u'u',u'Ừ':u'u',u'ừ':u'u',u'Ữ':u'u', + u'ữ':u'u',u'Ử':u'u',u'ử':u'u',u'Ự':u'u',u'ự':u'u',u'Ụ':u'u', + u'ụ':u'u',u'Ṳ':u'u',u'ṳ':u'u',u'Ṷ':u'u',u'ṷ':u'u',u'Ṵ':u'u', + u'ṵ':u'u',u'Ʉ':u'u',u'ʉ':u'u',u'Ṽ':u'v',u'ṽ':u'v',u'Ṿ':u'v', + u'ṿ':u'v',u'Ʋ':u'v',u'ʋ':u'v',u'Ẃ':u'w',u'ẃ':u'w',u'Ẁ':u'w', + u'ẁ':u'w',u'Ŵ':u'w',u'ŵ':u'w',u'W':u'w',u'̊':u'w',u'ẘ':u'w', + u'Ẅ':u'w',u'ẅ':u'w',u'Ẇ':u'w',u'ẇ':u'w',u'Ẉ':u'w',u'ẉ':u'w', + u'Ẍ':u'x',u'ẍ':u'x',u'Ẋ':u'x',u'ẋ':u'x',u'Ý':u'y',u'ý':u'y', + u'Ỳ':u'y',u'ỳ':u'y',u'Ŷ':u'y',u'ŷ':u'y',u'Y':u'y',u'̊':u'y', + u'ẙ':u'y',u'Ÿ':u'y',u'ÿ':u'y',u'Ỹ':u'y',u'ỹ':u'y',u'Ẏ':u'y', + u'ẏ':u'y',u'Ȳ':u'y',u'ȳ':u'y',u'Ỷ':u'y',u'ỷ':u'y',u'Ỵ':u'y', + u'ỵ':u'y',u'ʏ':u'y',u'Ɏ':u'y',u'ɏ':u'y',u'Ƴ':u'y',u'ƴ':u'y', + u'Ź':u'z',u'ź':u'z',u'Ẑ':u'z',u'ẑ':u'z',u'Ž':u'z',u'ž':u'z', + u'Ż':u'z',u'ż':u'z',u'Ẓ':u'z',u'ẓ':u'z',u'Ẕ':u'z',u'ẕ':u'z', + u'Ƶ':u'z',u'ƶ':u'z',u'Ȥ':u'z',u'ȥ':u'z',u'ʐ':u'z',u'ʑ':u'z', + u'Ⱬ':u'z',u'ⱬ':u'z',u'Ǯ':u'z',u'ǯ':u'z',u'ƺ':u'z',u'2':u'2', + u'6':u'6',u'B':u'B',u'F':u'F',u'J':u'J',u'N':u'N',u'R':u'R', + u'V':u'V',u'Z':u'Z',u'b':u'b',u'f':u'f',u'j':u'j',u'n':u'n', + u'r':u'r',u'v':u'v',u'z':u'z',u'1':u'1',u'5':u'5',u'9':u'9', + u'A':u'A',u'E':u'E',u'I':u'I',u'M':u'M',u'Q':u'Q',u'U':u'U', + u'Y':u'Y',u'a':u'a',u'e':u'e',u'i':u'i',u'm':u'm',u'q':u'q', + u'u':u'u',u'y':u'y',u'0':u'0',u'4':u'4',u'8':u'8',u'D':u'D', + u'H':u'H',u'L':u'L',u'P':u'P',u'T':u'T',u'X':u'X',u'd':u'd', + u'h':u'h',u'l':u'l',u'p':u'p',u't':u't',u'x':u'x',u'3':u'3', + u'7':u'7',u'C':u'C',u'G':u'G',u'K':u'K',u'O':u'O',u'S':u'S', + u'W':u'W',u'c':u'c',u'g':u'g',u'k':u'k',u'o':u'o',u's':u's', + u'w':u'w'}; +# The unicode.translate() method actually requires a dictionary mapping +# character *numbers* to characters, for some reason. +accent_map = dict((ord(k), v) for k, v in accent_map.iteritems()) + + +# This Sphinx charset table taken from http://speeple.com/unicode-maps.txt + +default_charset = """ +################################################## +# Latin +# A +U+00C0->a, U+00C1->a, U+00C2->a, U+00C3->a, U+00C4->a, U+00C5->a, U+00E0->a, U+00E1->a, U+00E2->a, U+00E3->a, U+00E4->a, U+00E5->a, +U+0100->a, U+0101->a, U+0102->a, U+0103->a, U+010300->a, U+0104->a, U+0105->a, U+01CD->a, U+01CE->a, U+01DE->a, U+01DF->a, U+01E0->a, +U+01E1->a, U+01FA->a, U+01FB->a, U+0200->a, U+0201->a, U+0202->a, U+0203->a, U+0226->a, U+0227->a, U+023A->a, U+0250->a, U+04D0->a, +U+04D1->a, U+1D2C->a, U+1D43->a, U+1D44->a, U+1D8F->a, U+1E00->a, U+1E01->a, U+1E9A->a, U+1EA0->a, U+1EA1->a, U+1EA2->a, U+1EA3->a, +U+1EA4->a, U+1EA5->a, U+1EA6->a, U+1EA7->a, U+1EA8->a, U+1EA9->a, U+1EAA->a, U+1EAB->a, U+1EAC->a, U+1EAD->a, U+1EAE->a, U+1EAF->a, +U+1EB0->a, U+1EB1->a, U+1EB2->a, U+1EB3->a, U+1EB4->a, U+1EB5->a, U+1EB6->a, U+1EB7->a, U+2090->a, U+2C65->a + +# B +U+0180->b, U+0181->b, U+0182->b, U+0183->b, U+0243->b, U+0253->b, U+0299->b, U+16D2->b, U+1D03->b, U+1D2E->b, U+1D2F->b, U+1D47->b, +U+1D6C->b, U+1D80->b, U+1E02->b, U+1E03->b, U+1E04->b, U+1E05->b, U+1E06->b, U+1E07->b + +# C +U+00C7->c, U+00E7->c, U+0106->c, U+0107->c, U+0108->c, U+0109->c, U+010A->c, U+010B->c, U+010C->c, U+010D->c, U+0187->c, U+0188->c, +U+023B->c, U+023C->c, U+0255->c, U+0297->c, U+1D9C->c, U+1D9D->c, U+1E08->c, U+1E09->c, U+212D->c, U+2184->c + +# D +U+010E->d, U+010F->d, U+0110->d, U+0111->d, U+0189->d, U+018A->d, U+018B->d, U+018C->d, U+01C5->d, U+01F2->d, U+0221->d, U+0256->d, +U+0257->d, U+1D05->d, U+1D30->d, U+1D48->d, U+1D6D->d, U+1D81->d, U+1D91->d, U+1E0A->d, U+1E0B->d, U+1E0C->d, U+1E0D->d, U+1E0E->d, +U+1E0F->d, U+1E10->d, U+1E11->d, U+1E12->d, U+1E13->d + +# E +U+00C8->e, U+00C9->e, U+00CA->e, U+00CB->e, U+00E8->e, U+00E9->e, U+00EA->e, U+00EB->e, U+0112->e, U+0113->e, U+0114->e, U+0115->e, +U+0116->e, U+0117->e, U+0118->e, U+0119->e, U+011A->e, U+011B->e, U+018E->e, U+0190->e, U+01DD->e, U+0204->e, U+0205->e, U+0206->e, +U+0207->e, U+0228->e, U+0229->e, U+0246->e, U+0247->e, U+0258->e, U+025B->e, U+025C->e, U+025D->e, U+025E->e, U+029A->e, U+1D07->e, +U+1D08->e, U+1D31->e, U+1D32->e, U+1D49->e, U+1D4B->e, U+1D4C->e, U+1D92->e, U+1D93->e, U+1D94->e, U+1D9F->e, U+1E14->e, U+1E15->e, +U+1E16->e, U+1E17->e, U+1E18->e, U+1E19->e, U+1E1A->e, U+1E1B->e, U+1E1C->e, U+1E1D->e, U+1EB8->e, U+1EB9->e, U+1EBA->e, U+1EBB->e, +U+1EBC->e, U+1EBD->e, U+1EBE->e, U+1EBF->e, U+1EC0->e, U+1EC1->e, U+1EC2->e, U+1EC3->e, U+1EC4->e, U+1EC5->e, U+1EC6->e, U+1EC7->e, +U+2091->e + +# F +U+0191->f, U+0192->f, U+1D6E->f, U+1D82->f, U+1DA0->f, U+1E1E->f, U+1E1F->f + +# G +U+011C->g, U+011D->g, U+011E->g, U+011F->g, U+0120->g, U+0121->g, U+0122->g, U+0123->g, U+0193->g, U+01E4->g, U+01E5->g, U+01E6->g, +U+01E7->g, U+01F4->g, U+01F5->g, U+0260->g, U+0261->g, U+0262->g, U+029B->g, U+1D33->g, U+1D4D->g, U+1D77->g, U+1D79->g, U+1D83->g, +U+1DA2->g, U+1E20->g, U+1E21->g + +# H +U+0124->h, U+0125->h, U+0126->h, U+0127->h, U+021E->h, U+021F->h, U+0265->h, U+0266->h, U+029C->h, U+02AE->h, U+02AF->h, U+02B0->h, +U+02B1->h, U+1D34->h, U+1DA3->h, U+1E22->h, U+1E23->h, U+1E24->h, U+1E25->h, U+1E26->h, U+1E27->h, U+1E28->h, U+1E29->h, U+1E2A->h, +U+1E2B->h, U+1E96->h, U+210C->h, U+2C67->h, U+2C68->h, U+2C75->h, U+2C76->h + +# I +U+00CC->i, U+00CD->i, U+00CE->i, U+00CF->i, U+00EC->i, U+00ED->i, U+00EE->i, U+00EF->i, U+010309->i, U+0128->i, U+0129->i, U+012A->i, +U+012B->i, U+012C->i, U+012D->i, U+012E->i, U+012F->i, U+0130->i, U+0131->i, U+0197->i, U+01CF->i, U+01D0->i, U+0208->i, U+0209->i, +U+020A->i, U+020B->i, U+0268->i, U+026A->i, U+040D->i, U+0418->i, U+0419->i, U+0438->i, U+0439->i, U+0456->i, U+1D09->i, U+1D35->i, +U+1D4E->i, U+1D62->i, U+1D7B->i, U+1D96->i, U+1DA4->i, U+1DA6->i, U+1DA7->i, U+1E2C->i, U+1E2D->i, U+1E2E->i, U+1E2F->i, U+1EC8->i, +U+1EC9->i, U+1ECA->i, U+1ECB->i, U+2071->i, U+2111->i + +# J +U+0134->j, U+0135->j, U+01C8->j, U+01CB->j, U+01F0->j, U+0237->j, U+0248->j, U+0249->j, U+025F->j, U+0284->j, U+029D->j, U+02B2->j, +U+1D0A->j, U+1D36->j, U+1DA1->j, U+1DA8->j + +# K +U+0136->k, U+0137->k, U+0198->k, U+0199->k, U+01E8->k, U+01E9->k, U+029E->k, U+1D0B->k, U+1D37->k, U+1D4F->k, U+1D84->k, U+1E30->k, +U+1E31->k, U+1E32->k, U+1E33->k, U+1E34->k, U+1E35->k, U+2C69->k, U+2C6A->k + +# L +U+0139->l, U+013A->l, U+013B->l, U+013C->l, U+013D->l, U+013E->l, U+013F->l, U+0140->l, U+0141->l, U+0142->l, U+019A->l, U+01C8->l, +U+0234->l, U+023D->l, U+026B->l, U+026C->l, U+026D->l, U+029F->l, U+02E1->l, U+1D0C->l, U+1D38->l, U+1D85->l, U+1DA9->l, U+1DAA->l, +U+1DAB->l, U+1E36->l, U+1E37->l, U+1E38->l, U+1E39->l, U+1E3A->l, U+1E3B->l, U+1E3C->l, U+1E3D->l, U+2C60->l, U+2C61->l, U+2C62->l + +# M +U+019C->m, U+026F->m, U+0270->m, U+0271->m, U+1D0D->m, U+1D1F->m, U+1D39->m, U+1D50->m, U+1D5A->m, U+1D6F->m, U+1D86->m, U+1DAC->m, +U+1DAD->m, U+1E3E->m, U+1E3F->m, U+1E40->m, U+1E41->m, U+1E42->m, U+1E43->m + +# N +U+00D1->n, U+00F1->n, U+0143->n, U+0144->n, U+0145->n, U+0146->n, U+0147->n, U+0148->n, U+0149->n, U+019D->n, U+019E->n, U+01CB->n, +U+01F8->n, U+01F9->n, U+0220->n, U+0235->n, U+0272->n, U+0273->n, U+0274->n, U+1D0E->n, U+1D3A->n, U+1D3B->n, U+1D70->n, U+1D87->n, +U+1DAE->n, U+1DAF->n, U+1DB0->n, U+1E44->n, U+1E45->n, U+1E46->n, U+1E47->n, U+1E48->n, U+1E49->n, U+1E4A->n, U+1E4B->n, U+207F->n + +# O +U+00D2->o, U+00D3->o, U+00D4->o, U+00D5->o, U+00D6->o, U+00D8->o, U+00F2->o, U+00F3->o, U+00F4->o, U+00F5->o, U+00F6->o, U+00F8->o, +U+01030F->o, U+014C->o, U+014D->o, U+014E->o, U+014F->o, U+0150->o, U+0151->o, U+0186->o, U+019F->o, U+01A0->o, U+01A1->o, U+01D1->o, +U+01D2->o, U+01EA->o, U+01EB->o, U+01EC->o, U+01ED->o, U+01FE->o, U+01FF->o, U+020C->o, U+020D->o, U+020E->o, U+020F->o, U+022A->o, +U+022B->o, U+022C->o, U+022D->o, U+022E->o, U+022F->o, U+0230->o, U+0231->o, U+0254->o, U+0275->o, U+043E->o, U+04E6->o, U+04E7->o, +U+04E8->o, U+04E9->o, U+04EA->o, U+04EB->o, U+1D0F->o, U+1D10->o, U+1D11->o, U+1D12->o, U+1D13->o, U+1D16->o, U+1D17->o, U+1D3C->o, +U+1D52->o, U+1D53->o, U+1D54->o, U+1D55->o, U+1D97->o, U+1DB1->o, U+1E4C->o, U+1E4D->o, U+1E4E->o, U+1E4F->o, U+1E50->o, U+1E51->o, +U+1E52->o, U+1E53->o, U+1ECC->o, U+1ECD->o, U+1ECE->o, U+1ECF->o, U+1ED0->o, U+1ED1->o, U+1ED2->o, U+1ED3->o, U+1ED4->o, U+1ED5->o, +U+1ED6->o, U+1ED7->o, U+1ED8->o, U+1ED9->o, U+1EDA->o, U+1EDB->o, U+1EDC->o, U+1EDD->o, U+1EDE->o, U+1EDF->o, U+1EE0->o, U+1EE1->o, +U+1EE2->o, U+1EE3->o, U+2092->o, U+2C9E->o, U+2C9F->o + +# P +U+01A4->p, U+01A5->p, U+1D18->p, U+1D3E->p, U+1D56->p, U+1D71->p, U+1D7D->p, U+1D88->p, U+1E54->p, U+1E55->p, U+1E56->p, U+1E57->p, +U+2C63->p + +# Q +U+024A->q, U+024B->q, U+02A0->q + +# R +U+0154->r, U+0155->r, U+0156->r, U+0157->r, U+0158->r, U+0159->r, U+0210->r, U+0211->r, U+0212->r, U+0213->r, U+024C->r, U+024D->r, +U+0279->r, U+027A->r, U+027B->r, U+027C->r, U+027D->r, U+027E->r, U+027F->r, U+0280->r, U+0281->r, U+02B3->r, U+02B4->r, U+02B5->r, +U+02B6->r, U+1D19->r, U+1D1A->r, U+1D3F->r, U+1D63->r, U+1D72->r, U+1D73->r, U+1D89->r, U+1DCA->r, U+1E58->r, U+1E59->r, U+1E5A->r, +U+1E5B->r, U+1E5C->r, U+1E5D->r, U+1E5E->r, U+1E5F->r, U+211C->r, U+2C64->r + +# S +U+00DF->s, U+015A->s, U+015B->s, U+015C->s, U+015D->s, U+015E->s, U+015F->s, U+0160->s, U+0161->s, U+017F->s, U+0218->s, U+0219->s, +U+023F->s, U+0282->s, U+02E2->s, U+1D74->s, U+1D8A->s, U+1DB3->s, U+1E60->s, U+1E61->s, U+1E62->s, U+1E63->s, U+1E64->s, U+1E65->s, +U+1E66->s, U+1E67->s, U+1E68->s, U+1E69->s, U+1E9B->s + +# T +U+0162->t, U+0163->t, U+0164->t, U+0165->t, U+0166->t, U+0167->t, U+01AB->t, U+01AC->t, U+01AD->t, U+01AE->t, U+021A->t, U+021B->t, +U+0236->t, U+023E->t, U+0287->t, U+0288->t, U+1D1B->t, U+1D40->t, U+1D57->t, U+1D75->t, U+1DB5->t, U+1E6A->t, U+1E6B->t, U+1E6C->t, +U+1E6D->t, U+1E6E->t, U+1E6F->t, U+1E70->t, U+1E71->t, U+1E97->t, U+2C66->t + +# U +U+00D9->u, U+00DA->u, U+00DB->u, U+00DC->u, U+00F9->u, U+00FA->u, U+00FB->u, U+00FC->u, U+010316->u, U+0168->u, U+0169->u, U+016A->u, +U+016B->u, U+016C->u, U+016D->u, U+016E->u, U+016F->u, U+0170->u, U+0171->u, U+0172->u, U+0173->u, U+01AF->u, U+01B0->u, U+01D3->u, +U+01D4->u, U+01D5->u, U+01D6->u, U+01D7->u, U+01D8->u, U+01D9->u, U+01DA->u, U+01DB->u, U+01DC->u, U+0214->u, U+0215->u, U+0216->u, +U+0217->u, U+0244->u, U+0289->u, U+1D1C->u, U+1D1D->u, U+1D1E->u, U+1D41->u, U+1D58->u, U+1D59->u, U+1D64->u, U+1D7E->u, U+1D99->u, +U+1DB6->u, U+1DB8->u, U+1E72->u, U+1E73->u, U+1E74->u, U+1E75->u, U+1E76->u, U+1E77->u, U+1E78->u, U+1E79->u, U+1E7A->u, U+1E7B->u, +U+1EE4->u, U+1EE5->u, U+1EE6->u, U+1EE7->u, U+1EE8->u, U+1EE9->u, U+1EEA->u, U+1EEB->u, U+1EEC->u, U+1EED->u, U+1EEE->u, U+1EEF->u, +U+1EF0->u, U+1EF1->u + +# V +U+01B2->v, U+0245->v, U+028B->v, U+028C->v, U+1D20->v, U+1D5B->v, U+1D65->v, U+1D8C->v, U+1DB9->v, U+1DBA->v, U+1E7C->v, U+1E7D->v, +U+1E7E->v, U+1E7F->v, U+2C74->v + +# W +U+0174->w, U+0175->w, U+028D->w, U+02B7->w, U+1D21->w, U+1D42->w, U+1E80->w, U+1E81->w, U+1E82->w, U+1E83->w, U+1E84->w, U+1E85->w, +U+1E86->w, U+1E87->w, U+1E88->w, U+1E89->w, U+1E98->w + +# X +U+02E3->x, U+1D8D->x, U+1E8A->x, U+1E8B->x, U+1E8C->x, U+1E8D->x, U+2093->x + +# Y +U+00DD->y, U+00FD->y, U+00FF->y, U+0176->y, U+0177->y, U+0178->y, U+01B3->y, U+01B4->y, U+0232->y, U+0233->y, U+024E->y, U+024F->y, +U+028E->y, U+028F->y, U+02B8->y, U+1E8E->y, U+1E8F->y, U+1E99->y, U+1EF2->y, U+1EF3->y, U+1EF4->y, U+1EF5->y, U+1EF6->y, U+1EF7->y, +U+1EF8->y, U+1EF9->y + +# Z +U+0179->z, U+017A->z, U+017B->z, U+017C->z, U+017D->z, U+017E->z, U+01B5->z, U+01B6->z, U+0224->z, U+0225->z, U+0240->z, U+0290->z, +U+0291->z, U+1D22->z, U+1D76->z, U+1D8E->z, U+1DBB->z, U+1DBC->z, U+1DBD->z, U+1E90->z, U+1E91->z, U+1E92->z, U+1E93->z, U+1E94->z, +U+1E95->z, U+2128->z, U+2C6B->z, U+2C6C->z + +# Latin Extras: +U+00C6->U+00E6, U+01E2->U+00E6, U+01E3->U+00E6, U+01FC->U+00E6, U+01FD->U+00E6, U+1D01->U+00E6, U+1D02->U+00E6, U+1D2D->U+00E6, +U+1D46->U+00E6, U+00E6 + +################################################## +# Arabic +U+0622->U+0627, U+0623->U+0627, U+0624->U+0648, U+0625->U+0627, U+0626->U+064A, U+06C0->U+06D5, U+06C2->U+06C1, U+06D3->U+06D2, +U+FB50->U+0671, U+FB51->U+0671, U+FB52->U+067B, U+FB53->U+067B, U+FB54->U+067B, U+FB56->U+067E, U+FB57->U+067E, U+FB58->U+067E, +U+FB5A->U+0680, U+FB5B->U+0680, U+FB5C->U+0680, U+FB5E->U+067A, U+FB5F->U+067A, U+FB60->U+067A, U+FB62->U+067F, U+FB63->U+067F, +U+FB64->U+067F, U+FB66->U+0679, U+FB67->U+0679, U+FB68->U+0679, U+FB6A->U+06A4, U+FB6B->U+06A4, U+FB6C->U+06A4, U+FB6E->U+06A6, +U+FB6F->U+06A6, U+FB70->U+06A6, U+FB72->U+0684, U+FB73->U+0684, U+FB74->U+0684, U+FB76->U+0683, U+FB77->U+0683, U+FB78->U+0683, +U+FB7A->U+0686, U+FB7B->U+0686, U+FB7C->U+0686, U+FB7E->U+0687, U+FB7F->U+0687, U+FB80->U+0687, U+FB82->U+068D, U+FB83->U+068D, +U+FB84->U+068C, U+FB85->U+068C, U+FB86->U+068E, U+FB87->U+068E, U+FB88->U+0688, U+FB89->U+0688, U+FB8A->U+0698, U+FB8B->U+0698, +U+FB8C->U+0691, U+FB8D->U+0691, U+FB8E->U+06A9, U+FB8F->U+06A9, U+FB90->U+06A9, U+FB92->U+06AF, U+FB93->U+06AF, U+FB94->U+06AF, +U+FB96->U+06B3, U+FB97->U+06B3, U+FB98->U+06B3, U+FB9A->U+06B1, U+FB9B->U+06B1, U+FB9C->U+06B1, U+FB9E->U+06BA, U+FB9F->U+06BA, +U+FBA0->U+06BB, U+FBA1->U+06BB, U+FBA2->U+06BB, U+FBA4->U+06C0, U+FBA5->U+06C0, U+FBA6->U+06C1, U+FBA7->U+06C1, U+FBA8->U+06C1, +U+FBAA->U+06BE, U+FBAB->U+06BE, U+FBAC->U+06BE, U+FBAE->U+06D2, U+FBAF->U+06D2, U+FBB0->U+06D3, U+FBB1->U+06D3, U+FBD3->U+06AD, +U+FBD4->U+06AD, U+FBD5->U+06AD, U+FBD7->U+06C7, U+FBD8->U+06C7, U+FBD9->U+06C6, U+FBDA->U+06C6, U+FBDB->U+06C8, U+FBDC->U+06C8, +U+FBDD->U+0677, U+FBDE->U+06CB, U+FBDF->U+06CB, U+FBE0->U+06C5, U+FBE1->U+06C5, U+FBE2->U+06C9, U+FBE3->U+06C9, U+FBE4->U+06D0, +U+FBE5->U+06D0, U+FBE6->U+06D0, U+FBE8->U+0649, U+FBFC->U+06CC, U+FBFD->U+06CC, U+FBFE->U+06CC, U+0621, U+0627..U+063A, U+0641..U+064A, +U+0660..U+0669, U+066E, U+066F, U+0671..U+06BF, U+06C1, U+06C3..U+06D2, U+06D5, U+06EE..U+06FC, U+06FF, U+0750..U+076D, U+FB55, U+FB59, +U+FB5D, U+FB61, U+FB65, U+FB69, U+FB6D, U+FB71, U+FB75, U+FB79, U+FB7D, U+FB81, U+FB91, U+FB95, U+FB99, U+FB9D, U+FBA3, U+FBA9, U+FBAD, +U+FBD6, U+FBE7, U+FBE9, U+FBFF + +################################################## +# Armenian +U+0531..U+0556->U+0561..U+0586, U+0561..U+0586, U+0587 + +################################################# +# Bengali +U+09DC->U+09A1, U+09DD->U+09A2, U+09DF->U+09AF, U+09F0->U+09AC, U+09F1->U+09AC, U+0985..U+0990, U+0993..U+09B0, U+09B2, U+09B6..U+09B9, +U+09CE, U+09E0, U+09E1, U+09E6..U+09EF + +################################################# +# CJK* +U+F900->U+8C48, U+F901->U+66F4, U+F902->U+8ECA, U+F903->U+8CC8, U+F904->U+6ED1, U+F905->U+4E32, U+F906->U+53E5, U+F907->U+9F9C, +U+F908->U+9F9C, U+F909->U+5951, U+F90A->U+91D1, U+F90B->U+5587, U+F90C->U+5948, U+F90D->U+61F6, U+F90E->U+7669, U+F90F->U+7F85, +U+F910->U+863F, U+F911->U+87BA, U+F912->U+88F8, U+F913->U+908F, U+F914->U+6A02, U+F915->U+6D1B, U+F916->U+70D9, U+F917->U+73DE, +U+F918->U+843D, U+F919->U+916A, U+F91A->U+99F1, U+F91B->U+4E82, U+F91C->U+5375, U+F91D->U+6B04, U+F91E->U+721B, U+F91F->U+862D, +U+F920->U+9E1E, U+F921->U+5D50, U+F922->U+6FEB, U+F923->U+85CD, U+F924->U+8964, U+F925->U+62C9, U+F926->U+81D8, U+F927->U+881F, +U+F928->U+5ECA, U+F929->U+6717, U+F92A->U+6D6A, U+F92B->U+72FC, U+F92C->U+90CE, U+F92D->U+4F86, U+F92E->U+51B7, U+F92F->U+52DE, +U+F930->U+64C4, U+F931->U+6AD3, U+F932->U+7210, U+F933->U+76E7, U+F934->U+8001, U+F935->U+8606, U+F936->U+865C, U+F937->U+8DEF, +U+F938->U+9732, U+F939->U+9B6F, U+F93A->U+9DFA, U+F93B->U+788C, U+F93C->U+797F, U+F93D->U+7DA0, U+F93E->U+83C9, U+F93F->U+9304, +U+F940->U+9E7F, U+F941->U+8AD6, U+F942->U+58DF, U+F943->U+5F04, U+F944->U+7C60, U+F945->U+807E, U+F946->U+7262, U+F947->U+78CA, +U+F948->U+8CC2, U+F949->U+96F7, U+F94A->U+58D8, U+F94B->U+5C62, U+F94C->U+6A13, U+F94D->U+6DDA, U+F94E->U+6F0F, U+F94F->U+7D2F, +U+F950->U+7E37, U+F951->U+964B, U+F952->U+52D2, U+F953->U+808B, U+F954->U+51DC, U+F955->U+51CC, U+F956->U+7A1C, U+F957->U+7DBE, +U+F958->U+83F1, U+F959->U+9675, U+F95A->U+8B80, U+F95B->U+62CF, U+F95C->U+6A02, U+F95D->U+8AFE, U+F95E->U+4E39, U+F95F->U+5BE7, +U+F960->U+6012, U+F961->U+7387, U+F962->U+7570, U+F963->U+5317, U+F964->U+78FB, U+F965->U+4FBF, U+F966->U+5FA9, U+F967->U+4E0D, +U+F968->U+6CCC, U+F969->U+6578, U+F96A->U+7D22, U+F96B->U+53C3, U+F96C->U+585E, U+F96D->U+7701, U+F96E->U+8449, U+F96F->U+8AAA, +U+F970->U+6BBA, U+F971->U+8FB0, U+F972->U+6C88, U+F973->U+62FE, U+F974->U+82E5, U+F975->U+63A0, U+F976->U+7565, U+F977->U+4EAE, +U+F978->U+5169, U+F979->U+51C9, U+F97A->U+6881, U+F97B->U+7CE7, U+F97C->U+826F, U+F97D->U+8AD2, U+F97E->U+91CF, U+F97F->U+52F5, +U+F980->U+5442, U+F981->U+5973, U+F982->U+5EEC, U+F983->U+65C5, U+F984->U+6FFE, U+F985->U+792A, U+F986->U+95AD, U+F987->U+9A6A, +U+F988->U+9E97, U+F989->U+9ECE, U+F98A->U+529B, U+F98B->U+66C6, U+F98C->U+6B77, U+F98D->U+8F62, U+F98E->U+5E74, U+F98F->U+6190, +U+F990->U+6200, U+F991->U+649A, U+F992->U+6F23, U+F993->U+7149, U+F994->U+7489, U+F995->U+79CA, U+F996->U+7DF4, U+F997->U+806F, +U+F998->U+8F26, U+F999->U+84EE, U+F99A->U+9023, U+F99B->U+934A, U+F99C->U+5217, U+F99D->U+52A3, U+F99E->U+54BD, U+F99F->U+70C8, +U+F9A0->U+88C2, U+F9A1->U+8AAA, U+F9A2->U+5EC9, U+F9A3->U+5FF5, U+F9A4->U+637B, U+F9A5->U+6BAE, U+F9A6->U+7C3E, U+F9A7->U+7375, +U+F9A8->U+4EE4, U+F9A9->U+56F9, U+F9AA->U+5BE7, U+F9AB->U+5DBA, U+F9AC->U+601C, U+F9AD->U+73B2, U+F9AE->U+7469, U+F9AF->U+7F9A, +U+F9B0->U+8046, U+F9B1->U+9234, U+F9B2->U+96F6, U+F9B3->U+9748, U+F9B4->U+9818, U+F9B5->U+4F8B, U+F9B6->U+79AE, U+F9B7->U+91B4, +U+F9B8->U+96B8, U+F9B9->U+60E1, U+F9BA->U+4E86, U+F9BB->U+50DA, U+F9BC->U+5BEE, U+F9BD->U+5C3F, U+F9BE->U+6599, U+F9BF->U+6A02, +U+F9C0->U+71CE, U+F9C1->U+7642, U+F9C2->U+84FC, U+F9C3->U+907C, U+F9C4->U+9F8D, U+F9C5->U+6688, U+F9C6->U+962E, U+F9C7->U+5289, +U+F9C8->U+677B, U+F9C9->U+67F3, U+F9CA->U+6D41, U+F9CB->U+6E9C, U+F9CC->U+7409, U+F9CD->U+7559, U+F9CE->U+786B, U+F9CF->U+7D10, +U+F9D0->U+985E, U+F9D1->U+516D, U+F9D2->U+622E, U+F9D3->U+9678, U+F9D4->U+502B, U+F9D5->U+5D19, U+F9D6->U+6DEA, U+F9D7->U+8F2A, +U+F9D8->U+5F8B, U+F9D9->U+6144, U+F9DA->U+6817, U+F9DB->U+7387, U+F9DC->U+9686, U+F9DD->U+5229, U+F9DE->U+540F, U+F9DF->U+5C65, +U+F9E0->U+6613, U+F9E1->U+674E, U+F9E2->U+68A8, U+F9E3->U+6CE5, U+F9E4->U+7406, U+F9E5->U+75E2, U+F9E6->U+7F79, U+F9E7->U+88CF, +U+F9E8->U+88E1, U+F9E9->U+91CC, U+F9EA->U+96E2, U+F9EB->U+533F, U+F9EC->U+6EBA, U+F9ED->U+541D, U+F9EE->U+71D0, U+F9EF->U+7498, +U+F9F0->U+85FA, U+F9F1->U+96A3, U+F9F2->U+9C57, U+F9F3->U+9E9F, U+F9F4->U+6797, U+F9F5->U+6DCB, U+F9F6->U+81E8, U+F9F7->U+7ACB, +U+F9F8->U+7B20, U+F9F9->U+7C92, U+F9FA->U+72C0, U+F9FB->U+7099, U+F9FC->U+8B58, U+F9FD->U+4EC0, U+F9FE->U+8336, U+F9FF->U+523A, +U+FA00->U+5207, U+FA01->U+5EA6, U+FA02->U+62D3, U+FA03->U+7CD6, U+FA04->U+5B85, U+FA05->U+6D1E, U+FA06->U+66B4, U+FA07->U+8F3B, +U+FA08->U+884C, U+FA09->U+964D, U+FA0A->U+898B, U+FA0B->U+5ED3, U+FA0C->U+5140, U+FA0D->U+55C0, U+FA10->U+585A, U+FA12->U+6674, +U+FA15->U+51DE, U+FA16->U+732A, U+FA17->U+76CA, U+FA18->U+793C, U+FA19->U+795E, U+FA1A->U+7965, U+FA1B->U+798F, U+FA1C->U+9756, +U+FA1D->U+7CBE, U+FA1E->U+7FBD, U+FA20->U+8612, U+FA22->U+8AF8, U+FA25->U+9038, U+FA26->U+90FD, U+FA2A->U+98EF, U+FA2B->U+98FC, +U+FA2C->U+9928, U+FA2D->U+9DB4, U+FA30->U+4FAE, U+FA31->U+50E7, U+FA32->U+514D, U+FA33->U+52C9, U+FA34->U+52E4, U+FA35->U+5351, +U+FA36->U+559D, U+FA37->U+5606, U+FA38->U+5668, U+FA39->U+5840, U+FA3A->U+58A8, U+FA3B->U+5C64, U+FA3C->U+5C6E, U+FA3D->U+6094, +U+FA3E->U+6168, U+FA3F->U+618E, U+FA40->U+61F2, U+FA41->U+654F, U+FA42->U+65E2, U+FA43->U+6691, U+FA44->U+6885, U+FA45->U+6D77, +U+FA46->U+6E1A, U+FA47->U+6F22, U+FA48->U+716E, U+FA49->U+722B, U+FA4A->U+7422, U+FA4B->U+7891, U+FA4C->U+793E, U+FA4D->U+7949, +U+FA4E->U+7948, U+FA4F->U+7950, U+FA50->U+7956, U+FA51->U+795D, U+FA52->U+798D, U+FA53->U+798E, U+FA54->U+7A40, U+FA55->U+7A81, +U+FA56->U+7BC0, U+FA57->U+7DF4, U+FA58->U+7E09, U+FA59->U+7E41, U+FA5A->U+7F72, U+FA5B->U+8005, U+FA5C->U+81ED, U+FA5D->U+8279, +U+FA5E->U+8279, U+FA5F->U+8457, U+FA60->U+8910, U+FA61->U+8996, U+FA62->U+8B01, U+FA63->U+8B39, U+FA64->U+8CD3, U+FA65->U+8D08, +U+FA66->U+8FB6, U+FA67->U+9038, U+FA68->U+96E3, U+FA69->U+97FF, U+FA6A->U+983B, U+FA70->U+4E26, U+FA71->U+51B5, U+FA72->U+5168, +U+FA73->U+4F80, U+FA74->U+5145, U+FA75->U+5180, U+FA76->U+52C7, U+FA77->U+52FA, U+FA78->U+559D, U+FA79->U+5555, U+FA7A->U+5599, +U+FA7B->U+55E2, U+FA7C->U+585A, U+FA7D->U+58B3, U+FA7E->U+5944, U+FA7F->U+5954, U+FA80->U+5A62, U+FA81->U+5B28, U+FA82->U+5ED2, +U+FA83->U+5ED9, U+FA84->U+5F69, U+FA85->U+5FAD, U+FA86->U+60D8, U+FA87->U+614E, U+FA88->U+6108, U+FA89->U+618E, U+FA8A->U+6160, +U+FA8B->U+61F2, U+FA8C->U+6234, U+FA8D->U+63C4, U+FA8E->U+641C, U+FA8F->U+6452, U+FA90->U+6556, U+FA91->U+6674, U+FA92->U+6717, +U+FA93->U+671B, U+FA94->U+6756, U+FA95->U+6B79, U+FA96->U+6BBA, U+FA97->U+6D41, U+FA98->U+6EDB, U+FA99->U+6ECB, U+FA9A->U+6F22, +U+FA9B->U+701E, U+FA9C->U+716E, U+FA9D->U+77A7, U+FA9E->U+7235, U+FA9F->U+72AF, U+FAA0->U+732A, U+FAA1->U+7471, U+FAA2->U+7506, +U+FAA3->U+753B, U+FAA4->U+761D, U+FAA5->U+761F, U+FAA6->U+76CA, U+FAA7->U+76DB, U+FAA8->U+76F4, U+FAA9->U+774A, U+FAAA->U+7740, +U+FAAB->U+78CC, U+FAAC->U+7AB1, U+FAAD->U+7BC0, U+FAAE->U+7C7B, U+FAAF->U+7D5B, U+FAB0->U+7DF4, U+FAB1->U+7F3E, U+FAB2->U+8005, +U+FAB3->U+8352, U+FAB4->U+83EF, U+FAB5->U+8779, U+FAB6->U+8941, U+FAB7->U+8986, U+FAB8->U+8996, U+FAB9->U+8ABF, U+FABA->U+8AF8, +U+FABB->U+8ACB, U+FABC->U+8B01, U+FABD->U+8AFE, U+FABE->U+8AED, U+FABF->U+8B39, U+FAC0->U+8B8A, U+FAC1->U+8D08, U+FAC2->U+8F38, +U+FAC3->U+9072, U+FAC4->U+9199, U+FAC5->U+9276, U+FAC6->U+967C, U+FAC7->U+96E3, U+FAC8->U+9756, U+FAC9->U+97DB, U+FACA->U+97FF, +U+FACB->U+980B, U+FACC->U+983B, U+FACD->U+9B12, U+FACE->U+9F9C, U+FACF->U+2284A, U+FAD0->U+22844, U+FAD1->U+233D5, U+FAD2->U+3B9D, +U+FAD3->U+4018, U+FAD4->U+4039, U+FAD5->U+25249, U+FAD6->U+25CD0, U+FAD7->U+27ED3, U+FAD8->U+9F43, U+FAD9->U+9F8E, U+2F800->U+4E3D, +U+2F801->U+4E38, U+2F802->U+4E41, U+2F803->U+20122, U+2F804->U+4F60, U+2F805->U+4FAE, U+2F806->U+4FBB, U+2F807->U+5002, U+2F808->U+507A, +U+2F809->U+5099, U+2F80A->U+50E7, U+2F80B->U+50CF, U+2F80C->U+349E, U+2F80D->U+2063A, U+2F80E->U+514D, U+2F80F->U+5154, U+2F810->U+5164, +U+2F811->U+5177, U+2F812->U+2051C, U+2F813->U+34B9, U+2F814->U+5167, U+2F815->U+518D, U+2F816->U+2054B, U+2F817->U+5197, +U+2F818->U+51A4, U+2F819->U+4ECC, U+2F81A->U+51AC, U+2F81B->U+51B5, U+2F81C->U+291DF, U+2F81D->U+51F5, U+2F81E->U+5203, +U+2F81F->U+34DF, U+2F820->U+523B, U+2F821->U+5246, U+2F822->U+5272, U+2F823->U+5277, U+2F824->U+3515, U+2F825->U+52C7, +U+2F826->U+52C9, U+2F827->U+52E4, U+2F828->U+52FA, U+2F829->U+5305, U+2F82A->U+5306, U+2F82B->U+5317, U+2F82C->U+5349, +U+2F82D->U+5351, U+2F82E->U+535A, U+2F82F->U+5373, U+2F830->U+537D, U+2F831->U+537F, U+2F832->U+537F, U+2F833->U+537F, +U+2F834->U+20A2C, U+2F835->U+7070, U+2F836->U+53CA, U+2F837->U+53DF, U+2F838->U+20B63, U+2F839->U+53EB, U+2F83A->U+53F1, +U+2F83B->U+5406, U+2F83C->U+549E, U+2F83D->U+5438, U+2F83E->U+5448, U+2F83F->U+5468, U+2F840->U+54A2, U+2F841->U+54F6, +U+2F842->U+5510, U+2F843->U+5553, U+2F844->U+5563, U+2F845->U+5584, U+2F846->U+5584, U+2F847->U+5599, U+2F848->U+55AB, +U+2F849->U+55B3, U+2F84A->U+55C2, U+2F84B->U+5716, U+2F84C->U+5606, U+2F84D->U+5717, U+2F84E->U+5651, U+2F84F->U+5674, +U+2F850->U+5207, U+2F851->U+58EE, U+2F852->U+57CE, U+2F853->U+57F4, U+2F854->U+580D, U+2F855->U+578B, U+2F856->U+5832, +U+2F857->U+5831, U+2F858->U+58AC, U+2F859->U+214E4, U+2F85A->U+58F2, U+2F85B->U+58F7, U+2F85C->U+5906, U+2F85D->U+591A, +U+2F85E->U+5922, U+2F85F->U+5962, U+2F860->U+216A8, U+2F861->U+216EA, U+2F862->U+59EC, U+2F863->U+5A1B, U+2F864->U+5A27, +U+2F865->U+59D8, U+2F866->U+5A66, U+2F867->U+36EE, U+2F868->U+36FC, U+2F869->U+5B08, U+2F86A->U+5B3E, U+2F86B->U+5B3E, +U+2F86C->U+219C8, U+2F86D->U+5BC3, U+2F86E->U+5BD8, U+2F86F->U+5BE7, U+2F870->U+5BF3, U+2F871->U+21B18, U+2F872->U+5BFF, +U+2F873->U+5C06, U+2F874->U+5F53, U+2F875->U+5C22, U+2F876->U+3781, U+2F877->U+5C60, U+2F878->U+5C6E, U+2F879->U+5CC0, +U+2F87A->U+5C8D, U+2F87B->U+21DE4, U+2F87C->U+5D43, U+2F87D->U+21DE6, U+2F87E->U+5D6E, U+2F87F->U+5D6B, U+2F880->U+5D7C, +U+2F881->U+5DE1, U+2F882->U+5DE2, U+2F883->U+382F, U+2F884->U+5DFD, U+2F885->U+5E28, U+2F886->U+5E3D, U+2F887->U+5E69, +U+2F888->U+3862, U+2F889->U+22183, U+2F88A->U+387C, U+2F88B->U+5EB0, U+2F88C->U+5EB3, U+2F88D->U+5EB6, U+2F88E->U+5ECA, +U+2F88F->U+2A392, U+2F890->U+5EFE, U+2F891->U+22331, U+2F892->U+22331, U+2F893->U+8201, U+2F894->U+5F22, U+2F895->U+5F22, +U+2F896->U+38C7, U+2F897->U+232B8, U+2F898->U+261DA, U+2F899->U+5F62, U+2F89A->U+5F6B, U+2F89B->U+38E3, U+2F89C->U+5F9A, +U+2F89D->U+5FCD, U+2F89E->U+5FD7, U+2F89F->U+5FF9, U+2F8A0->U+6081, U+2F8A1->U+393A, U+2F8A2->U+391C, U+2F8A3->U+6094, +U+2F8A4->U+226D4, U+2F8A5->U+60C7, U+2F8A6->U+6148, U+2F8A7->U+614C, U+2F8A8->U+614E, U+2F8A9->U+614C, U+2F8AA->U+617A, +U+2F8AB->U+618E, U+2F8AC->U+61B2, U+2F8AD->U+61A4, U+2F8AE->U+61AF, U+2F8AF->U+61DE, U+2F8B0->U+61F2, U+2F8B1->U+61F6, +U+2F8B2->U+6210, U+2F8B3->U+621B, U+2F8B4->U+625D, U+2F8B5->U+62B1, U+2F8B6->U+62D4, U+2F8B7->U+6350, U+2F8B8->U+22B0C, +U+2F8B9->U+633D, U+2F8BA->U+62FC, U+2F8BB->U+6368, U+2F8BC->U+6383, U+2F8BD->U+63E4, U+2F8BE->U+22BF1, U+2F8BF->U+6422, +U+2F8C0->U+63C5, U+2F8C1->U+63A9, U+2F8C2->U+3A2E, U+2F8C3->U+6469, U+2F8C4->U+647E, U+2F8C5->U+649D, U+2F8C6->U+6477, +U+2F8C7->U+3A6C, U+2F8C8->U+654F, U+2F8C9->U+656C, U+2F8CA->U+2300A, U+2F8CB->U+65E3, U+2F8CC->U+66F8, U+2F8CD->U+6649, +U+2F8CE->U+3B19, U+2F8CF->U+6691, U+2F8D0->U+3B08, U+2F8D1->U+3AE4, U+2F8D2->U+5192, U+2F8D3->U+5195, U+2F8D4->U+6700, +U+2F8D5->U+669C, U+2F8D6->U+80AD, U+2F8D7->U+43D9, U+2F8D8->U+6717, U+2F8D9->U+671B, U+2F8DA->U+6721, U+2F8DB->U+675E, +U+2F8DC->U+6753, U+2F8DD->U+233C3, U+2F8DE->U+3B49, U+2F8DF->U+67FA, U+2F8E0->U+6785, U+2F8E1->U+6852, U+2F8E2->U+6885, +U+2F8E3->U+2346D, U+2F8E4->U+688E, U+2F8E5->U+681F, U+2F8E6->U+6914, U+2F8E7->U+3B9D, U+2F8E8->U+6942, U+2F8E9->U+69A3, +U+2F8EA->U+69EA, U+2F8EB->U+6AA8, U+2F8EC->U+236A3, U+2F8ED->U+6ADB, U+2F8EE->U+3C18, U+2F8EF->U+6B21, U+2F8F0->U+238A7, +U+2F8F1->U+6B54, U+2F8F2->U+3C4E, U+2F8F3->U+6B72, U+2F8F4->U+6B9F, U+2F8F5->U+6BBA, U+2F8F6->U+6BBB, U+2F8F7->U+23A8D, +U+2F8F8->U+21D0B, U+2F8F9->U+23AFA, U+2F8FA->U+6C4E, U+2F8FB->U+23CBC, U+2F8FC->U+6CBF, U+2F8FD->U+6CCD, U+2F8FE->U+6C67, +U+2F8FF->U+6D16, U+2F900->U+6D3E, U+2F901->U+6D77, U+2F902->U+6D41, U+2F903->U+6D69, U+2F904->U+6D78, U+2F905->U+6D85, +U+2F906->U+23D1E, U+2F907->U+6D34, U+2F908->U+6E2F, U+2F909->U+6E6E, U+2F90A->U+3D33, U+2F90B->U+6ECB, U+2F90C->U+6EC7, +U+2F90D->U+23ED1, U+2F90E->U+6DF9, U+2F90F->U+6F6E, U+2F910->U+23F5E, U+2F911->U+23F8E, U+2F912->U+6FC6, U+2F913->U+7039, +U+2F914->U+701E, U+2F915->U+701B, U+2F916->U+3D96, U+2F917->U+704A, U+2F918->U+707D, U+2F919->U+7077, U+2F91A->U+70AD, +U+2F91B->U+20525, U+2F91C->U+7145, U+2F91D->U+24263, U+2F91E->U+719C, U+2F91F->U+243AB, U+2F920->U+7228, U+2F921->U+7235, +U+2F922->U+7250, U+2F923->U+24608, U+2F924->U+7280, U+2F925->U+7295, U+2F926->U+24735, U+2F927->U+24814, U+2F928->U+737A, +U+2F929->U+738B, U+2F92A->U+3EAC, U+2F92B->U+73A5, U+2F92C->U+3EB8, U+2F92D->U+3EB8, U+2F92E->U+7447, U+2F92F->U+745C, +U+2F930->U+7471, U+2F931->U+7485, U+2F932->U+74CA, U+2F933->U+3F1B, U+2F934->U+7524, U+2F935->U+24C36, U+2F936->U+753E, +U+2F937->U+24C92, U+2F938->U+7570, U+2F939->U+2219F, U+2F93A->U+7610, U+2F93B->U+24FA1, U+2F93C->U+24FB8, U+2F93D->U+25044, +U+2F93E->U+3FFC, U+2F93F->U+4008, U+2F940->U+76F4, U+2F941->U+250F3, U+2F942->U+250F2, U+2F943->U+25119, U+2F944->U+25133, +U+2F945->U+771E, U+2F946->U+771F, U+2F947->U+771F, U+2F948->U+774A, U+2F949->U+4039, U+2F94A->U+778B, U+2F94B->U+4046, +U+2F94C->U+4096, U+2F94D->U+2541D, U+2F94E->U+784E, U+2F94F->U+788C, U+2F950->U+78CC, U+2F951->U+40E3, U+2F952->U+25626, +U+2F953->U+7956, U+2F954->U+2569A, U+2F955->U+256C5, U+2F956->U+798F, U+2F957->U+79EB, U+2F958->U+412F, U+2F959->U+7A40, +U+2F95A->U+7A4A, U+2F95B->U+7A4F, U+2F95C->U+2597C, U+2F95D->U+25AA7, U+2F95E->U+25AA7, U+2F95F->U+7AEE, U+2F960->U+4202, +U+2F961->U+25BAB, U+2F962->U+7BC6, U+2F963->U+7BC9, U+2F964->U+4227, U+2F965->U+25C80, U+2F966->U+7CD2, U+2F967->U+42A0, +U+2F968->U+7CE8, U+2F969->U+7CE3, U+2F96A->U+7D00, U+2F96B->U+25F86, U+2F96C->U+7D63, U+2F96D->U+4301, U+2F96E->U+7DC7, +U+2F96F->U+7E02, U+2F970->U+7E45, U+2F971->U+4334, U+2F972->U+26228, U+2F973->U+26247, U+2F974->U+4359, U+2F975->U+262D9, +U+2F976->U+7F7A, U+2F977->U+2633E, U+2F978->U+7F95, U+2F979->U+7FFA, U+2F97A->U+8005, U+2F97B->U+264DA, U+2F97C->U+26523, +U+2F97D->U+8060, U+2F97E->U+265A8, U+2F97F->U+8070, U+2F980->U+2335F, U+2F981->U+43D5, U+2F982->U+80B2, U+2F983->U+8103, +U+2F984->U+440B, U+2F985->U+813E, U+2F986->U+5AB5, U+2F987->U+267A7, U+2F988->U+267B5, U+2F989->U+23393, U+2F98A->U+2339C, +U+2F98B->U+8201, U+2F98C->U+8204, U+2F98D->U+8F9E, U+2F98E->U+446B, U+2F98F->U+8291, U+2F990->U+828B, U+2F991->U+829D, +U+2F992->U+52B3, U+2F993->U+82B1, U+2F994->U+82B3, U+2F995->U+82BD, U+2F996->U+82E6, U+2F997->U+26B3C, U+2F998->U+82E5, +U+2F999->U+831D, U+2F99A->U+8363, U+2F99B->U+83AD, U+2F99C->U+8323, U+2F99D->U+83BD, U+2F99E->U+83E7, U+2F99F->U+8457, +U+2F9A0->U+8353, U+2F9A1->U+83CA, U+2F9A2->U+83CC, U+2F9A3->U+83DC, U+2F9A4->U+26C36, U+2F9A5->U+26D6B, U+2F9A6->U+26CD5, +U+2F9A7->U+452B, U+2F9A8->U+84F1, U+2F9A9->U+84F3, U+2F9AA->U+8516, U+2F9AB->U+273CA, U+2F9AC->U+8564, U+2F9AD->U+26F2C, +U+2F9AE->U+455D, U+2F9AF->U+4561, U+2F9B0->U+26FB1, U+2F9B1->U+270D2, U+2F9B2->U+456B, U+2F9B3->U+8650, U+2F9B4->U+865C, +U+2F9B5->U+8667, U+2F9B6->U+8669, U+2F9B7->U+86A9, U+2F9B8->U+8688, U+2F9B9->U+870E, U+2F9BA->U+86E2, U+2F9BB->U+8779, +U+2F9BC->U+8728, U+2F9BD->U+876B, U+2F9BE->U+8786, U+2F9BF->U+45D7, U+2F9C0->U+87E1, U+2F9C1->U+8801, U+2F9C2->U+45F9, +U+2F9C3->U+8860, U+2F9C4->U+8863, U+2F9C5->U+27667, U+2F9C6->U+88D7, U+2F9C7->U+88DE, U+2F9C8->U+4635, U+2F9C9->U+88FA, +U+2F9CA->U+34BB, U+2F9CB->U+278AE, U+2F9CC->U+27966, U+2F9CD->U+46BE, U+2F9CE->U+46C7, U+2F9CF->U+8AA0, U+2F9D0->U+8AED, +U+2F9D1->U+8B8A, U+2F9D2->U+8C55, U+2F9D3->U+27CA8, U+2F9D4->U+8CAB, U+2F9D5->U+8CC1, U+2F9D6->U+8D1B, U+2F9D7->U+8D77, +U+2F9D8->U+27F2F, U+2F9D9->U+20804, U+2F9DA->U+8DCB, U+2F9DB->U+8DBC, U+2F9DC->U+8DF0, U+2F9DD->U+208DE, U+2F9DE->U+8ED4, +U+2F9DF->U+8F38, U+2F9E0->U+285D2, U+2F9E1->U+285ED, U+2F9E2->U+9094, U+2F9E3->U+90F1, U+2F9E4->U+9111, U+2F9E5->U+2872E, +U+2F9E6->U+911B, U+2F9E7->U+9238, U+2F9E8->U+92D7, U+2F9E9->U+92D8, U+2F9EA->U+927C, U+2F9EB->U+93F9, U+2F9EC->U+9415, +U+2F9ED->U+28BFA, U+2F9EE->U+958B, U+2F9EF->U+4995, U+2F9F0->U+95B7, U+2F9F1->U+28D77, U+2F9F2->U+49E6, U+2F9F3->U+96C3, +U+2F9F4->U+5DB2, U+2F9F5->U+9723, U+2F9F6->U+29145, U+2F9F7->U+2921A, U+2F9F8->U+4A6E, U+2F9F9->U+4A76, U+2F9FA->U+97E0, +U+2F9FB->U+2940A, U+2F9FC->U+4AB2, U+2F9FD->U+29496, U+2F9FE->U+980B, U+2F9FF->U+980B, U+2FA00->U+9829, U+2FA01->U+295B6, +U+2FA02->U+98E2, U+2FA03->U+4B33, U+2FA04->U+9929, U+2FA05->U+99A7, U+2FA06->U+99C2, U+2FA07->U+99FE, U+2FA08->U+4BCE, +U+2FA09->U+29B30, U+2FA0A->U+9B12, U+2FA0B->U+9C40, U+2FA0C->U+9CFD, U+2FA0D->U+4CCE, U+2FA0E->U+4CED, U+2FA0F->U+9D67, +U+2FA10->U+2A0CE, U+2FA11->U+4CF8, U+2FA12->U+2A105, U+2FA13->U+2A20E, U+2FA14->U+2A291, U+2FA15->U+9EBB, U+2FA16->U+4D56, +U+2FA17->U+9EF9, U+2FA18->U+9EFE, U+2FA19->U+9F05, U+2FA1A->U+9F0F, U+2FA1B->U+9F16, U+2FA1C->U+9F3B, U+2FA1D->U+2A600, +U+2F00->U+4E00, U+2F01->U+4E28, U+2F02->U+4E36, U+2F03->U+4E3F, U+2F04->U+4E59, U+2F05->U+4E85, U+2F06->U+4E8C, U+2F07->U+4EA0, +U+2F08->U+4EBA, U+2F09->U+513F, U+2F0A->U+5165, U+2F0B->U+516B, U+2F0C->U+5182, U+2F0D->U+5196, U+2F0E->U+51AB, U+2F0F->U+51E0, +U+2F10->U+51F5, U+2F11->U+5200, U+2F12->U+529B, U+2F13->U+52F9, U+2F14->U+5315, U+2F15->U+531A, U+2F16->U+5338, U+2F17->U+5341, +U+2F18->U+535C, U+2F19->U+5369, U+2F1A->U+5382, U+2F1B->U+53B6, U+2F1C->U+53C8, U+2F1D->U+53E3, U+2F1E->U+56D7, U+2F1F->U+571F, +U+2F20->U+58EB, U+2F21->U+5902, U+2F22->U+590A, U+2F23->U+5915, U+2F24->U+5927, U+2F25->U+5973, U+2F26->U+5B50, U+2F27->U+5B80, +U+2F28->U+5BF8, U+2F29->U+5C0F, U+2F2A->U+5C22, U+2F2B->U+5C38, U+2F2C->U+5C6E, U+2F2D->U+5C71, U+2F2E->U+5DDB, U+2F2F->U+5DE5, +U+2F30->U+5DF1, U+2F31->U+5DFE, U+2F32->U+5E72, U+2F33->U+5E7A, U+2F34->U+5E7F, U+2F35->U+5EF4, U+2F36->U+5EFE, U+2F37->U+5F0B, +U+2F38->U+5F13, U+2F39->U+5F50, U+2F3A->U+5F61, U+2F3B->U+5F73, U+2F3C->U+5FC3, U+2F3D->U+6208, U+2F3E->U+6236, U+2F3F->U+624B, +U+2F40->U+652F, U+2F41->U+6534, U+2F42->U+6587, U+2F43->U+6597, U+2F44->U+65A4, U+2F45->U+65B9, U+2F46->U+65E0, U+2F47->U+65E5, +U+2F48->U+66F0, U+2F49->U+6708, U+2F4A->U+6728, U+2F4B->U+6B20, U+2F4C->U+6B62, U+2F4D->U+6B79, U+2F4E->U+6BB3, U+2F4F->U+6BCB, +U+2F50->U+6BD4, U+2F51->U+6BDB, U+2F52->U+6C0F, U+2F53->U+6C14, U+2F54->U+6C34, U+2F55->U+706B, U+2F56->U+722A, U+2F57->U+7236, +U+2F58->U+723B, U+2F59->U+723F, U+2F5A->U+7247, U+2F5B->U+7259, U+2F5C->U+725B, U+2F5D->U+72AC, U+2F5E->U+7384, U+2F5F->U+7389, +U+2F60->U+74DC, U+2F61->U+74E6, U+2F62->U+7518, U+2F63->U+751F, U+2F64->U+7528, U+2F65->U+7530, U+2F66->U+758B, U+2F67->U+7592, +U+2F68->U+7676, U+2F69->U+767D, U+2F6A->U+76AE, U+2F6B->U+76BF, U+2F6C->U+76EE, U+2F6D->U+77DB, U+2F6E->U+77E2, U+2F6F->U+77F3, +U+2F70->U+793A, U+2F71->U+79B8, U+2F72->U+79BE, U+2F73->U+7A74, U+2F74->U+7ACB, U+2F75->U+7AF9, U+2F76->U+7C73, U+2F77->U+7CF8, +U+2F78->U+7F36, U+2F79->U+7F51, U+2F7A->U+7F8A, U+2F7B->U+7FBD, U+2F7C->U+8001, U+2F7D->U+800C, U+2F7E->U+8012, U+2F7F->U+8033, +U+2F80->U+807F, U+2F81->U+8089, U+2F82->U+81E3, U+2F83->U+81EA, U+2F84->U+81F3, U+2F85->U+81FC, U+2F86->U+820C, U+2F87->U+821B, +U+2F88->U+821F, U+2F89->U+826E, U+2F8A->U+8272, U+2F8B->U+8278, U+2F8C->U+864D, U+2F8D->U+866B, U+2F8E->U+8840, U+2F8F->U+884C, +U+2F90->U+8863, U+2F91->U+897E, U+2F92->U+898B, U+2F93->U+89D2, U+2F94->U+8A00, U+2F95->U+8C37, U+2F96->U+8C46, U+2F97->U+8C55, +U+2F98->U+8C78, U+2F99->U+8C9D, U+2F9A->U+8D64, U+2F9B->U+8D70, U+2F9C->U+8DB3, U+2F9D->U+8EAB, U+2F9E->U+8ECA, U+2F9F->U+8F9B, +U+2FA0->U+8FB0, U+2FA1->U+8FB5, U+2FA2->U+9091, U+2FA3->U+9149, U+2FA4->U+91C6, U+2FA5->U+91CC, U+2FA6->U+91D1, U+2FA7->U+9577, +U+2FA8->U+9580, U+2FA9->U+961C, U+2FAA->U+96B6, U+2FAB->U+96B9, U+2FAC->U+96E8, U+2FAD->U+9751, U+2FAE->U+975E, U+2FAF->U+9762, +U+2FB0->U+9769, U+2FB1->U+97CB, U+2FB2->U+97ED, U+2FB3->U+97F3, U+2FB4->U+9801, U+2FB5->U+98A8, U+2FB6->U+98DB, U+2FB7->U+98DF, +U+2FB8->U+9996, U+2FB9->U+9999, U+2FBA->U+99AC, U+2FBB->U+9AA8, U+2FBC->U+9AD8, U+2FBD->U+9ADF, U+2FBE->U+9B25, U+2FBF->U+9B2F, +U+2FC0->U+9B32, U+2FC1->U+9B3C, U+2FC2->U+9B5A, U+2FC3->U+9CE5, U+2FC4->U+9E75, U+2FC5->U+9E7F, U+2FC6->U+9EA5, U+2FC7->U+9EBB, +U+2FC8->U+9EC3, U+2FC9->U+9ECD, U+2FCA->U+9ED1, U+2FCB->U+9EF9, U+2FCC->U+9EFD, U+2FCD->U+9F0E, U+2FCE->U+9F13, U+2FCF->U+9F20, +U+2FD0->U+9F3B, U+2FD1->U+9F4A, U+2FD2->U+9F52, U+2FD3->U+9F8D, U+2FD4->U+9F9C, U+2FD5->U+9FA0, U+3042->U+3041, U+3044->U+3043, +U+3046->U+3045, U+3048->U+3047, U+304A->U+3049, U+304C->U+304B, U+304E->U+304D, U+3050->U+304F, U+3052->U+3051, U+3054->U+3053, +U+3056->U+3055, U+3058->U+3057, U+305A->U+3059, U+305C->U+305B, U+305E->U+305D, U+3060->U+305F, U+3062->U+3061, U+3064->U+3063, +U+3065->U+3063, U+3067->U+3066, U+3069->U+3068, U+3070->U+306F, U+3071->U+306F, U+3073->U+3072, U+3074->U+3072, U+3076->U+3075, +U+3077->U+3075, U+3079->U+3078, U+307A->U+3078, U+307C->U+307B, U+307D->U+307B, U+3084->U+3083, U+3086->U+3085, U+3088->U+3087, +U+308F->U+308E, U+3094->U+3046, U+3095->U+304B, U+3096->U+3051, U+30A2->U+30A1, U+30A4->U+30A3, U+30A6->U+30A5, U+30A8->U+30A7, +U+30AA->U+30A9, U+30AC->U+30AB, U+30AE->U+30AD, U+30B0->U+30AF, U+30B2->U+30B1, U+30B4->U+30B3, U+30B6->U+30B5, U+30B8->U+30B7, +U+30BA->U+30B9, U+30BC->U+30BB, U+30BE->U+30BD, U+30C0->U+30BF, U+30C2->U+30C1, U+30C5->U+30C4, U+30C7->U+30C6, U+30C9->U+30C8, +U+30D0->U+30CF, U+30D1->U+30CF, U+30D3->U+30D2, U+30D4->U+30D2, U+30D6->U+30D5, U+30D7->U+30D5, U+30D9->U+30D8, U+30DA->U+30D8, +U+30DC->U+30DB, U+30DD->U+30DB, U+30E4->U+30E3, U+30E6->U+30E5, U+30E8->U+30E7, U+30EF->U+30EE, U+30F4->U+30A6, U+30AB->U+30F5, +U+30B1->U+30F6, U+30F7->U+30EF, U+30F8->U+30F0, U+30F9->U+30F1, U+30FA->U+30F2, U+30AF->U+31F0, U+30B7->U+31F1, U+30B9->U+31F2, +U+30C8->U+31F3, U+30CC->U+31F4, U+30CF->U+31F5, U+30D2->U+31F6, U+30D5->U+31F7, U+30D8->U+31F8, U+30DB->U+31F9, U+30E0->U+31FA, +U+30E9->U+31FB, U+30EA->U+31FC, U+30EB->U+31FD, U+30EC->U+31FE, U+30ED->U+31FF, U+FF66->U+30F2, U+FF67->U+30A1, U+FF68->U+30A3, +U+FF69->U+30A5, U+FF6A->U+30A7, U+FF6B->U+30A9, U+FF6C->U+30E3, U+FF6D->U+30E5, U+FF6E->U+30E7, U+FF6F->U+30C3, U+FF71->U+30A1, +U+FF72->U+30A3, U+FF73->U+30A5, U+FF74->U+30A7, U+FF75->U+30A9, U+FF76->U+30AB, U+FF77->U+30AD, U+FF78->U+30AF, U+FF79->U+30B1, +U+FF7A->U+30B3, U+FF7B->U+30B5, U+FF7C->U+30B7, U+FF7D->U+30B9, U+FF7E->U+30BB, U+FF7F->U+30BD, U+FF80->U+30BF, U+FF81->U+30C1, +U+FF82->U+30C3, U+FF83->U+30C6, U+FF84->U+30C8, U+FF85->U+30CA, U+FF86->U+30CB, U+FF87->U+30CC, U+FF88->U+30CD, U+FF89->U+30CE, +U+FF8A->U+30CF, U+FF8B->U+30D2, U+FF8C->U+30D5, U+FF8D->U+30D8, U+FF8E->U+30DB, U+FF8F->U+30DE, U+FF90->U+30DF, U+FF91->U+30E0, +U+FF92->U+30E1, U+FF93->U+30E2, U+FF94->U+30E3, U+FF95->U+30E5, U+FF96->U+30E7, U+FF97->U+30E9, U+FF98->U+30EA, U+FF99->U+30EB, +U+FF9A->U+30EC, U+FF9B->U+30ED, U+FF9C->U+30EF, U+FF9D->U+30F3, U+FFA0->U+3164, U+FFA1->U+3131, U+FFA2->U+3132, U+FFA3->U+3133, +U+FFA4->U+3134, U+FFA5->U+3135, U+FFA6->U+3136, U+FFA7->U+3137, U+FFA8->U+3138, U+FFA9->U+3139, U+FFAA->U+313A, U+FFAB->U+313B, +U+FFAC->U+313C, U+FFAD->U+313D, U+FFAE->U+313E, U+FFAF->U+313F, U+FFB0->U+3140, U+FFB1->U+3141, U+FFB2->U+3142, U+FFB3->U+3143, +U+FFB4->U+3144, U+FFB5->U+3145, U+FFB6->U+3146, U+FFB7->U+3147, U+FFB8->U+3148, U+FFB9->U+3149, U+FFBA->U+314A, U+FFBB->U+314B, +U+FFBC->U+314C, U+FFBD->U+314D, U+FFBE->U+314E, U+FFC2->U+314F, U+FFC3->U+3150, U+FFC4->U+3151, U+FFC5->U+3152, U+FFC6->U+3153, +U+FFC7->U+3154, U+FFCA->U+3155, U+FFCB->U+3156, U+FFCC->U+3157, U+FFCD->U+3158, U+FFCE->U+3159, U+FFCF->U+315A, U+FFD2->U+315B, +U+FFD3->U+315C, U+FFD4->U+315D, U+FFD5->U+315E, U+FFD6->U+315F, U+FFD7->U+3160, U+FFDA->U+3161, U+FFDB->U+3162, U+FFDC->U+3163, +U+3131->U+1100, U+3132->U+1101, U+3133->U+11AA, U+3134->U+1102, U+3135->U+11AC, U+3136->U+11AD, U+3137->U+1103, U+3138->U+1104, +U+3139->U+1105, U+313A->U+11B0, U+313B->U+11B1, U+313C->U+11B2, U+313D->U+11B3, U+313E->U+11B4, U+313F->U+11B5, U+3140->U+111A, +U+3141->U+1106, U+3142->U+1107, U+3143->U+1108, U+3144->U+1121, U+3145->U+1109, U+3146->U+110A, U+3147->U+110B, U+3148->U+110C, +U+3149->U+110D, U+314A->U+110E, U+314B->U+110F, U+314C->U+1110, U+314D->U+1111, U+314E->U+1112, U+314F->U+1161, U+3150->U+1162, +U+3151->U+1163, U+3152->U+1164, U+3153->U+1165, U+3154->U+1166, U+3155->U+1167, U+3156->U+1168, U+3157->U+1169, U+3158->U+116A, +U+3159->U+116B, U+315A->U+116C, U+315B->U+116D, U+315C->U+116E, U+315D->U+116F, U+315E->U+1170, U+315F->U+1171, U+3160->U+1172, +U+3161->U+1173, U+3162->U+1174, U+3163->U+1175, U+3165->U+1114, U+3166->U+1115, U+3167->U+11C7, U+3168->U+11C8, U+3169->U+11CC, +U+316A->U+11CE, U+316B->U+11D3, U+316C->U+11D7, U+316D->U+11D9, U+316E->U+111C, U+316F->U+11DD, U+3170->U+11DF, U+3171->U+111D, +U+3172->U+111E, U+3173->U+1120, U+3174->U+1122, U+3175->U+1123, U+3176->U+1127, U+3177->U+1129, U+3178->U+112B, U+3179->U+112C, +U+317A->U+112D, U+317B->U+112E, U+317C->U+112F, U+317D->U+1132, U+317E->U+1136, U+317F->U+1140, U+3180->U+1147, U+3181->U+114C, +U+3182->U+11F1, U+3183->U+11F2, U+3184->U+1157, U+3185->U+1158, U+3186->U+1159, U+3187->U+1184, U+3188->U+1185, U+3189->U+1188, +U+318A->U+1191, U+318B->U+1192, U+318C->U+1194, U+318D->U+119E, U+318E->U+11A1, U+A490->U+A408, U+A491->U+A1B9, U+4E00..U+9FBB, +U+3400..U+4DB5, U+20000..U+2A6D6, U+FA0E, U+FA0F, U+FA11, U+FA13, U+FA14, U+FA1F, U+FA21, U+FA23, U+FA24, U+FA27, U+FA28, U+FA29, +U+3105..U+312C, U+31A0..U+31B7, U+3041, U+3043, U+3045, U+3047, U+3049, U+304B, U+304D, U+304F, U+3051, U+3053, U+3055, U+3057, +U+3059, U+305B, U+305D, U+305F, U+3061, U+3063, U+3066, U+3068, U+306A..U+306F, U+3072, U+3075, U+3078, U+307B, U+307E..U+3083, +U+3085, U+3087, U+3089..U+308E, U+3090..U+3093, U+30A1, U+30A3, U+30A5, U+30A7, U+30A9, U+30AD, U+30AF, U+30B3, U+30B5, U+30BB, +U+30BD, U+30BF, U+30C1, U+30C3, U+30C4, U+30C6, U+30CA, U+30CB, U+30CD, U+30CE, U+30DE, U+30DF, U+30E1, U+30E2, U+30E3, U+30E5, +U+30E7, U+30EE, U+30F0..U+30F3, U+30F5, U+30F6, U+31F0, U+31F1, U+31F2, U+31F3, U+31F4, U+31F5, U+31F6, U+31F7, U+31F8, U+31F9, +U+31FA, U+31FB, U+31FC, U+31FD, U+31FE, U+31FF, U+AC00..U+D7A3, U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9, U+A000..U+A48C, +U+A492..U+A4C6 + +################################################## +# Coptic +# Notes: Some shared Greek characters, may require amendments. +U+2C80->U+2C81, U+2C81, U+2C82->U+2C83, U+2C83, U+2C84->U+2C85, U+2C85, U+2C86->U+2C87, U+2C87, U+2C88->U+2C89, U+2C89, U+2C8A->U+2C8B, +U+2C8B, U+2C8C->U+2C8D, U+2C8D, U+2C8E->U+2C8F, U+2C8F, U+2C90->U+2C91, U+2C91, U+2C92->U+2C93, U+2C93, U+2C94->U+2C95, U+2C95, +U+2C96->U+2C97, U+2C97, U+2C98->U+2C99, U+2C99, U+2C9A->U+2C9B, U+2C9B, U+2C9C->U+2C9D, U+2C9D, U+2C9E->U+2C9F, U+2C9F, U+2CA0->U+2CA1, +U+2CA1, U+2CA2->U+2CA3, U+2CA3, U+2CA4->U+2CA5, U+2CA5, U+2CA6->U+2CA7, U+2CA7, U+2CA8->U+2CA9, U+2CA9, U+2CAA->U+2CAB, U+2CAB, +U+2CAC->U+2CAD, U+2CAD, U+2CAE->U+2CAF, U+2CAF, U+2CB0->U+2CB1, U+2CB1, U+2CB2->U+2CB3, U+2CB3, U+2CB4->U+2CB5, U+2CB5, +U+2CB6->U+2CB7, U+2CB7, U+2CB8->U+2CB9, U+2CB9, U+2CBA->U+2CBB, U+2CBB, U+2CBC->U+2CBD, U+2CBD, U+2CBE->U+2CBF, U+2CBF, +U+2CC0->U+2CC1, U+2CC1, U+2CC2->U+2CC3, U+2CC3, U+2CC4->U+2CC5, U+2CC5, U+2CC6->U+2CC7, U+2CC7, U+2CC8->U+2CC9, U+2CC9, +U+2CCA->U+2CCB, U+2CCB, U+2CCC->U+2CCD, U+2CCD, U+2CCE->U+2CCF, U+2CCF, U+2CD0->U+2CD1, U+2CD1, U+2CD2->U+2CD3, U+2CD3, +U+2CD4->U+2CD5, U+2CD5, U+2CD6->U+2CD7, U+2CD7, U+2CD8->U+2CD9, U+2CD9, U+2CDA->U+2CDB, U+2CDB, U+2CDC->U+2CDD, U+2CDD, +U+2CDE->U+2CDF, U+2CDF, U+2CE0->U+2CE1, U+2CE1, U+2CE2->U+2CE3, U+2CE3 + +################################################## +# Cryllic* +U+0400->U+0435, U+0401->U+0435, U+0402->U+0452, U+0452, U+0403->U+0433, U+0404->U+0454, U+0454, U+0405->U+0455, U+0455, +U+0406->U+0456, U+0407->U+0456, U+0457->U+0456, U+0456, U+0408..U+040B->U+0458..U+045B, U+0458..U+045B, U+040C->U+043A, +U+040D->U+0438, U+040E->U+0443, U+040F->U+045F, U+045F, U+0450->U+0435, U+0451->U+0435, U+0453->U+0433, U+045C->U+043A, +U+045D->U+0438, U+045E->U+0443, U+0460->U+0461, U+0461, U+0462->U+0463, U+0463, U+0464->U+0465, U+0465, U+0466->U+0467, +U+0467, U+0468->U+0469, U+0469, U+046A->U+046B, U+046B, U+046C->U+046D, U+046D, U+046E->U+046F, U+046F, U+0470->U+0471, +U+0471, U+0472->U+0473, U+0473, U+0474->U+0475, U+0476->U+0475, U+0477->U+0475, U+0475, U+0478->U+0479, U+0479, U+047A->U+047B, +U+047B, U+047C->U+047D, U+047D, U+047E->U+047F, U+047F, U+0480->U+0481, U+0481, U+048A->U+0438, U+048B->U+0438, U+048C->U+044C, +U+048D->U+044C, U+048E->U+0440, U+048F->U+0440, U+0490->U+0433, U+0491->U+0433, U+0490->U+0433, U+0491->U+0433, U+0492->U+0433, +U+0493->U+0433, U+0494->U+0433, U+0495->U+0433, U+0496->U+0436, U+0497->U+0436, U+0498->U+0437, U+0499->U+0437, U+049A->U+043A, +U+049B->U+043A, U+049C->U+043A, U+049D->U+043A, U+049E->U+043A, U+049F->U+043A, U+04A0->U+043A, U+04A1->U+043A, U+04A2->U+043D, +U+04A3->U+043D, U+04A4->U+043D, U+04A5->U+043D, U+04A6->U+043F, U+04A7->U+043F, U+04A8->U+04A9, U+04A9, U+04AA->U+0441, +U+04AB->U+0441, U+04AC->U+0442, U+04AD->U+0442, U+04AE->U+0443, U+04AF->U+0443, U+04B0->U+0443, U+04B1->U+0443, U+04B2->U+0445, +U+04B3->U+0445, U+04B4->U+04B5, U+04B5, U+04B6->U+0447, U+04B7->U+0447, U+04B8->U+0447, U+04B9->U+0447, U+04BA->U+04BB, U+04BB, +U+04BC->U+04BD, U+04BE->U+04BD, U+04BF->U+04BD, U+04BD, U+04C0->U+04CF, U+04CF, U+04C1->U+0436, U+04C2->U+0436, U+04C3->U+043A, +U+04C4->U+043A, U+04C5->U+043B, U+04C6->U+043B, U+04C7->U+043D, U+04C8->U+043D, U+04C9->U+043D, U+04CA->U+043D, U+04CB->U+0447, +U+04CC->U+0447, U+04CD->U+043C, U+04CE->U+043C, U+04D0->U+0430, U+04D1->U+0430, U+04D2->U+0430, U+04D3->U+0430, U+04D4->U+00E6, +U+04D5->U+00E6, U+04D6->U+0435, U+04D7->U+0435, U+04D8->U+04D9, U+04DA->U+04D9, U+04DB->U+04D9, U+04D9, U+04DC->U+0436, +U+04DD->U+0436, U+04DE->U+0437, U+04DF->U+0437, U+04E0->U+04E1, U+04E1, U+04E2->U+0438, U+04E3->U+0438, U+04E4->U+0438, +U+04E5->U+0438, U+04E6->U+043E, U+04E7->U+043E, U+04E8->U+043E, U+04E9->U+043E, U+04EA->U+043E, U+04EB->U+043E, U+04EC->U+044D, +U+04ED->U+044D, U+04EE->U+0443, U+04EF->U+0443, U+04F0->U+0443, U+04F1->U+0443, U+04F2->U+0443, U+04F3->U+0443, U+04F4->U+0447, +U+04F5->U+0447, U+04F6->U+0433, U+04F7->U+0433, U+04F8->U+044B, U+04F9->U+044B, U+04FA->U+0433, U+04FB->U+0433, U+04FC->U+0445, +U+04FD->U+0445, U+04FE->U+0445, U+04FF->U+0445, U+0410..U+0418->U+0430..U+0438, U+0419->U+0438, U+0430..U+0438, +U+041A..U+042F->U+043A..U+044F, U+043A..U+044F + +################################################## +# Devanagari +U+0929->U+0928, U+0931->U+0930, U+0934->U+0933, U+0958->U+0915, U+0959->U+0916, U+095A->U+0917, U+095B->U+091C, U+095C->U+0921, +U+095D->U+0922, U+095E->U+092B, U+095F->U+092F, U+0904..U+0928, U+092A..U+0930, U+0932, U+0933, U+0935..U+0939, U+0960, U+0961, +U+0966..U+096F, U+097B..U+097F + +################################################## +# Georgian +U+10FC->U+10DC, U+10D0..U+10FA, U+10A0..U+10C5->U+2D00..U+2D25, U+2D00..U+2D25 + +################################################## +# Greek +U+0386->U+03B1, U+0388->U+03B5, U+0389->U+03B7, U+038A->U+03B9, U+038C->U+03BF, U+038E->U+03C5, U+038F->U+03C9, U+0390->U+03B9, +U+03AA->U+03B9, U+03AB->U+03C5, U+03AC->U+03B1, U+03AD->U+03B5, U+03AE->U+03B7, U+03AF->U+03B9, U+03B0->U+03C5, U+03CA->U+03B9, +U+03CB->U+03C5, U+03CC->U+03BF, U+03CD->U+03C5, U+03CE->U+03C9, U+03D0->U+03B2, U+03D1->U+03B8, U+03D2->U+03C5, U+03D3->U+03C5, +U+03D4->U+03C5, U+03D5->U+03C6, U+03D6->U+03C0, U+03D8->U+03D9, U+03DA->U+03DB, U+03DC->U+03DD, U+03DE->U+03DF, U+03E0->U+03E1, +U+03E2->U+03E3, U+03E4->U+03E5, U+03E6->U+03E7, U+03E8->U+03E9, U+03EA->U+03EB, U+03EC->U+03ED, U+03EE->U+03EF, U+03F0->U+03BA, +U+03F1->U+03C1, U+03F2->U+03C3, U+03F4->U+03B8, U+03F5->U+03B5, U+03F6->U+03B5, U+03F7->U+03F8, U+03F9->U+03C3, U+03FA->U+03FB, +U+1F00->U+03B1, U+1F01->U+03B1, U+1F02->U+03B1, U+1F03->U+03B1, U+1F04->U+03B1, U+1F05->U+03B1, U+1F06->U+03B1, U+1F07->U+03B1, +U+1F08->U+03B1, U+1F09->U+03B1, U+1F0A->U+03B1, U+1F0B->U+03B1, U+1F0C->U+03B1, U+1F0D->U+03B1, U+1F0E->U+03B1, U+1F0F->U+03B1, +U+1F10->U+03B5, U+1F11->U+03B5, U+1F12->U+03B5, U+1F13->U+03B5, U+1F14->U+03B5, U+1F15->U+03B5, U+1F18->U+03B5, U+1F19->U+03B5, +U+1F1A->U+03B5, U+1F1B->U+03B5, U+1F1C->U+03B5, U+1F1D->U+03B5, U+1F20->U+03B7, U+1F21->U+03B7, U+1F22->U+03B7, U+1F23->U+03B7, +U+1F24->U+03B7, U+1F25->U+03B7, U+1F26->U+03B7, U+1F27->U+03B7, U+1F28->U+03B7, U+1F29->U+03B7, U+1F2A->U+03B7, U+1F2B->U+03B7, +U+1F2C->U+03B7, U+1F2D->U+03B7, U+1F2E->U+03B7, U+1F2F->U+03B7, U+1F30->U+03B9, U+1F31->U+03B9, U+1F32->U+03B9, U+1F33->U+03B9, +U+1F34->U+03B9, U+1F35->U+03B9, U+1F36->U+03B9, U+1F37->U+03B9, U+1F38->U+03B9, U+1F39->U+03B9, U+1F3A->U+03B9, U+1F3B->U+03B9, +U+1F3C->U+03B9, U+1F3D->U+03B9, U+1F3E->U+03B9, U+1F3F->U+03B9, U+1F40->U+03BF, U+1F41->U+03BF, U+1F42->U+03BF, U+1F43->U+03BF, +U+1F44->U+03BF, U+1F45->U+03BF, U+1F48->U+03BF, U+1F49->U+03BF, U+1F4A->U+03BF, U+1F4B->U+03BF, U+1F4C->U+03BF, U+1F4D->U+03BF, +U+1F50->U+03C5, U+1F51->U+03C5, U+1F52->U+03C5, U+1F53->U+03C5, U+1F54->U+03C5, U+1F55->U+03C5, U+1F56->U+03C5, U+1F57->U+03C5, +U+1F59->U+03C5, U+1F5B->U+03C5, U+1F5D->U+03C5, U+1F5F->U+03C5, U+1F60->U+03C9, U+1F61->U+03C9, U+1F62->U+03C9, U+1F63->U+03C9, +U+1F64->U+03C9, U+1F65->U+03C9, U+1F66->U+03C9, U+1F67->U+03C9, U+1F68->U+03C9, U+1F69->U+03C9, U+1F6A->U+03C9, U+1F6B->U+03C9, +U+1F6C->U+03C9, U+1F6D->U+03C9, U+1F6E->U+03C9, U+1F6F->U+03C9, U+1F70->U+03B1, U+1F71->U+03B1, U+1F72->U+03B5, U+1F73->U+03B5, +U+1F74->U+03B7, U+1F75->U+03B7, U+1F76->U+03B9, U+1F77->U+03B9, U+1F78->U+03BF, U+1F79->U+03BF, U+1F7A->U+03C5, U+1F7B->U+03C5, +U+1F7C->U+03C9, U+1F7D->U+03C9, U+1F80->U+03B1, U+1F81->U+03B1, U+1F82->U+03B1, U+1F83->U+03B1, U+1F84->U+03B1, U+1F85->U+03B1, +U+1F86->U+03B1, U+1F87->U+03B1, U+1F88->U+03B1, U+1F89->U+03B1, U+1F8A->U+03B1, U+1F8B->U+03B1, U+1F8C->U+03B1, U+1F8D->U+03B1, +U+1F8E->U+03B1, U+1F8F->U+03B1, U+1F90->U+03B7, U+1F91->U+03B7, U+1F92->U+03B7, U+1F93->U+03B7, U+1F94->U+03B7, U+1F95->U+03B7, +U+1F96->U+03B7, U+1F97->U+03B7, U+1F98->U+03B7, U+1F99->U+03B7, U+1F9A->U+03B7, U+1F9B->U+03B7, U+1F9C->U+03B7, U+1F9D->U+03B7, +U+1F9E->U+03B7, U+1F9F->U+03B7, U+1FA0->U+03C9, U+1FA1->U+03C9, U+1FA2->U+03C9, U+1FA3->U+03C9, U+1FA4->U+03C9, U+1FA5->U+03C9, +U+1FA6->U+03C9, U+1FA7->U+03C9, U+1FA8->U+03C9, U+1FA9->U+03C9, U+1FAA->U+03C9, U+1FAB->U+03C9, U+1FAC->U+03C9, U+1FAD->U+03C9, +U+1FAE->U+03C9, U+1FAF->U+03C9, U+1FB0->U+03B1, U+1FB1->U+03B1, U+1FB2->U+03B1, U+1FB3->U+03B1, U+1FB4->U+03B1, U+1FB6->U+03B1, +U+1FB7->U+03B1, U+1FB8->U+03B1, U+1FB9->U+03B1, U+1FBA->U+03B1, U+1FBB->U+03B1, U+1FBC->U+03B1, U+1FC2->U+03B7, U+1FC3->U+03B7, +U+1FC4->U+03B7, U+1FC6->U+03B7, U+1FC7->U+03B7, U+1FC8->U+03B5, U+1FC9->U+03B5, U+1FCA->U+03B7, U+1FCB->U+03B7, U+1FCC->U+03B7, +U+1FD0->U+03B9, U+1FD1->U+03B9, U+1FD2->U+03B9, U+1FD3->U+03B9, U+1FD6->U+03B9, U+1FD7->U+03B9, U+1FD8->U+03B9, U+1FD9->U+03B9, +U+1FDA->U+03B9, U+1FDB->U+03B9, U+1FE0->U+03C5, U+1FE1->U+03C5, U+1FE2->U+03C5, U+1FE3->U+03C5, U+1FE4->U+03C1, U+1FE5->U+03C1, +U+1FE6->U+03C5, U+1FE7->U+03C5, U+1FE8->U+03C5, U+1FE9->U+03C5, U+1FEA->U+03C5, U+1FEB->U+03C5, U+1FEC->U+03C1, U+1FF2->U+03C9, +U+1FF3->U+03C9, U+1FF4->U+03C9, U+1FF6->U+03C9, U+1FF7->U+03C9, U+1FF8->U+03BF, U+1FF9->U+03BF, U+1FFA->U+03C9, U+1FFB->U+03C9, +U+1FFC->U+03C9, U+0391..U+03A1->U+03B1..U+03C1, U+03B1..U+03C1, U+03A3..U+03A9->U+03C3..U+03C9, U+03C3..U+03C9, U+03C2, U+03D9, +U+03DB, U+03DD, U+03DF, U+03E1, U+03E3, U+03E5, U+03E7, U+03E9, U+03EB, U+03ED, U+03EF, U+03F3, U+03F8, U+03FB + +################################################## +# Gujarati +U+0A85..U+0A8C, U+0A8F, U+0A90, U+0A93..U+0AB0, U+0AB2, U+0AB3, U+0AB5..U+0AB9, U+0AE0, U+0AE1, U+0AE6..U+0AEF + +################################################## +# Gurmukhi +U+0A33->U+0A32, U+0A36->U+0A38, U+0A59->U+0A16, U+0A5A->U+0A17, U+0A5B->U+0A1C, U+0A5E->U+0A2B, U+0A05..U+0A0A, U+0A0F, U+0A10, +U+0A13..U+0A28, U+0A2A..U+0A30, U+0A32, U+0A35, U+0A38, U+0A39, U+0A5C, U+0A66..U+0A6F + +################################################# +# Hebrew* +U+FB1D->U+05D9, U+FB1F->U+05F2, U+FB20->U+05E2, U+FB21->U+05D0, U+FB22->U+05D3, U+FB23->U+05D4, U+FB24->U+05DB, U+FB25->U+05DC, +U+FB26->U+05DD, U+FB27->U+05E8, U+FB28->U+05EA, U+FB2A->U+05E9, U+FB2B->U+05E9, U+FB2C->U+05E9, U+FB2D->U+05E9, U+FB2E->U+05D0, +U+FB2F->U+05D0, U+FB30->U+05D0, U+FB31->U+05D1, U+FB32->U+05D2, U+FB33->U+05D3, U+FB34->U+05D4, U+FB35->U+05D5, U+FB36->U+05D6, +U+FB38->U+05D8, U+FB39->U+05D9, U+FB3A->U+05DA, U+FB3B->U+05DB, U+FB3C->U+05DC, U+FB3E->U+05DE, U+FB40->U+05E0, U+FB41->U+05E1, +U+FB43->U+05E3, U+FB44->U+05E4, U+FB46->U+05E6, U+FB47->U+05E7, U+FB48->U+05E8, U+FB49->U+05E9, U+FB4A->U+05EA, U+FB4B->U+05D5, +U+FB4C->U+05D1, U+FB4D->U+05DB, U+FB4E->U+05E4, U+FB4F->U+05D0, U+05D0..U+05F2 + +################################################# +# Kannada +U+0C85..U+0C8C, U+0C8E..U+0C90, U+0C92..U+0CA8, U+0CAA..U+0CB3, U+0CB5..U+0CB9, U+0CE0, U+0CE1, U+0CE6..U+0CEF + +################################################# +# Limbu +U+1900..U+191C, U+1930..U+1938, U+1946..U+194F + +################################################# +# Malayalam +U+0D05..U+0D0C, U+0D0E..U+0D10, U+0D12..U+0D28, U+0D2A..U+0D39, U+0D60, U+0D61, U+0D66..U+0D6F + +################################################# +# Tamil +U+0B94->U+0B92, U+0B85..U+0B8A, U+0B8E..U+0B90, U+0B92, U+0B93, U+0B95, U+0B99, U+0B9A, U+0B9C, U+0B9E, U+0B9F, U+0BA3, U+0BA4, +U+0BA8..U+0BAA, U+0BAE..U+0BB9, U+0BE6..U+0BEF + +################################################# +# Thai +U+0E01..U+0E30, U+0E32, U+0E33, U+0E40..U+0E46, U+0E50..U+0E5B + +################################################## +# Common +U+FF10..U+FF19->0..9, U+FF21..U+FF3A->a..z, U+FF41..U+FF5A->a..z, 0..9, A..Z->a..z, a..z +""" + +# The expected value format is a commas-separated list of mappings. +# Two simplest mappings simply declare a character as valid, and map a single character +# to another single character, respectively. But specifying the whole table in such +# form would result in bloated and barely manageable specifications. So there are +# several syntax shortcuts that let you map ranges of characters at once. The complete +# list is as follows: +# +# A->a +# Single char mapping, declares source char 'A' as allowed to occur within keywords +# and maps it to destination char 'a' (but does not declare 'a' as allowed). +# A..Z->a..z +# Range mapping, declares all chars in source range as allowed and maps them to +# the destination range. Does not declare destination range as allowed. Also checks +# ranges' lengths (the lengths must be equal). +# a +# Stray char mapping, declares a character as allowed and maps it to itself. +# Equivalent to a->a single char mapping. +# a..z +# Stray range mapping, declares all characters in range as allowed and maps them to +# themselves. Equivalent to a..z->a..z range mapping. +# A..Z/2 +# Checkerboard range map. Maps every pair of chars to the second char. +# More formally, declares odd characters in range as allowed and maps them to the +# even ones; also declares even characters as allowed and maps them to themselves. +# For instance, A..Z/2 is equivalent to A->B, B->B, C->D, D->D, ..., Y->Z, Z->Z. +# This mapping shortcut is helpful for a number of Unicode blocks where uppercase +# and lowercase letters go in such interleaved order instead of contiguous chunks. + +_dewhite = re.compile(r"\s") +_char = r"((?:U\+[0-9A-Fa-f]{4,6})|.)" +_char_map = re.compile("^" + _char + "->" + _char + "$") +_range_map = re.compile("^" + _char + r"\.\." + _char + "->" + _char + ".." + _char + "$") +_stray_char = re.compile("^" + _char + "$") +_stray_range = re.compile("^" + _char + r"\.\." + _char + "$") +_checker_range = re.compile("^" + _char + r"\.\." + _char + "/2$") + +def charspec_to_int(string): + # Converts a character specification of the form 'A' or 'U+23BC' + # to an integer + if string.startswith("U+"): + return int(string[2:], 16) + elif len(string) == 1: + return ord(string) + else: + raise Exception("Can't convert charspec: %r" % string) + +def charset_table_to_dict(tablestring): + """Takes a string with the contents of a Sphinx charset table file and + returns a mapping object (a defaultdict, actually) of the kind expected by + the unicode.translate() method: that is, it maps a character number to a unicode + character or None if the character is not a valid word character. + + The Sphinx charset table format is described at + http://www.sphinxsearch.com/docs/current.html#conf-charset-table. + """ + + #map = {} + map = defaultdict(lambda: None) + for line in tablestring.split("\n"): + if not line or line.startswith("#"): continue + line = _dewhite.sub("", line) + for item in line.split(","): + if not item: continue + match = _range_map.match(item) + if match: + start1 = charspec_to_int(match.group(1)) + end1 = charspec_to_int(match.group(2)) + start2 = charspec_to_int(match.group(3)) + end2 = charspec_to_int(match.group(4)) + assert (end1 - start1) == (end2 - start2) + try: + for fromord, tooord in izip(xrange(start1, end1+1), xrange(start2, end2+1)): + map[fromord] = unichr(tooord) + except ValueError: + pass + continue + + match = _char_map.match(item) + if match: + fromord = charspec_to_int(match.group(1)) + toord = charspec_to_int(match.group(2)) + try: + map[fromord] = unichr(toord) + except ValueError: + pass + continue + + match = _stray_char.match(item) + if match: + ord = charspec_to_int(match.group(0)) + try: + map[ord] = unichr(ord) + except ValueError: + pass + continue + + match = _stray_range.match(item) + if match: + start = charspec_to_int(match.group(1)) + end = charspec_to_int(match.group(2)) + try: + for ord in xrange(start, end+1): + map[ord] = unichr(ord) + except ValueError: + pass + continue + + match = _checker_range.match(item) + if match: + fromord = charspec_to_int(match.group(1)) + toord = charspec_to_int(match.group(2)) + assert toord-fromord % 2 == 0 + for ord in xrange(fromord, toord + 1, 2): + try: + map[ord] = unichr(ord+1) + map[ord+1] = unichr(ord+1) + except ValueError: + pass + continue + + raise Exception("Don't know what to do with %r" % item) + return map + + + + + + + + + + + + + + + diff --git a/lib/whoosh/support/filelock.py b/lib/whoosh/support/filelock.py index 65eeed9a..9b183fa4 100644 --- a/lib/whoosh/support/filelock.py +++ b/lib/whoosh/support/filelock.py @@ -1,137 +1,137 @@ -#=============================================================================== -# Copyright 2010 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -import errno, os, time - - -def try_for(fn, timeout=5.0, delay=0.1): - """Calls ``fn`` every ``delay`` seconds until it returns True or ``timeout`` - seconds elapse. Returns True if the lock was acquired, or False if the timeout - was reached. - - :param timeout: Length of time (in seconds) to keep retrying to acquire the - lock. 0 means return immediately. Only used when blocking is False. - :param delay: How often (in seconds) to retry acquiring the lock during - the timeout period. Only used when blocking is False and timeout > 0. - """ - - until = time.time() + timeout - v = fn() - while not v and time.time() < until: - time.sleep(delay) - v = fn() - return v - - -class LockBase(object): - """Base class for file locks. - """ - - def __init__(self, filename): - self.fd = None - self.filename = filename - self.locked = False - - def __del__(self): - if hasattr(self, "fd") and self.fd: - try: - self.release() - except: - pass - - def acquire(self, blocking=False): - """Acquire the lock. Returns True if the lock was acquired. - - :param blocking: if True, call blocks until the lock is acquired. - This may not be available on all platforms. On Windows, this is - actually just a delay of 10 seconds, rechecking every second. - """ - pass - - def release(self): - pass - - -class FcntlLock(LockBase): - """File lock based on UNIX-only fcntl module. - """ - - def acquire(self, blocking=False): - import fcntl - - flags = os.O_CREAT | os.O_WRONLY - self.fd = os.open(self.filename, flags) - - mode = fcntl.LOCK_EX - if not blocking: mode |= fcntl.LOCK_NB - - try: - fcntl.flock(self.fd, mode) - self.locked = True - return True - except IOError, e: - if e.errno not in (errno.EAGAIN, errno.EACCES): - raise - os.close(self.fd) - self.fd = None - return False - - def release(self): - import fcntl - fcntl.flock(self.fd, fcntl.LOCK_UN) - os.close(self.fd) - self.fd = None - - -class MsvcrtLock(LockBase): - """File lock based on Windows-only msvcrt module. - """ - - def acquire(self, blocking=False): - import msvcrt - - flags = os.O_CREAT | os.O_WRONLY - mode = msvcrt.LK_NBLCK - if blocking: mode = msvcrt.LK_LOCK - - self.fd = os.open(self.filename, flags) - try: - msvcrt.locking(self.fd, mode, 1) - return True - except IOError, e: - if e.errno not in (errno.EAGAIN, errno.EACCES, errno.EDEADLK): - raise - os.close(self.fd) - self.fd = None - return False - - def release(self): - if self.fd is None: - raise Exception("Lock was not acquired") - - import msvcrt - msvcrt.locking(self.fd, msvcrt.LK_UNLCK, 1) - os.close(self.fd) - self.fd = None - - -if os.name == "nt": - FileLock = MsvcrtLock -else: - FileLock = FcntlLock - - - +#=============================================================================== +# Copyright 2010 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +import errno, os, time + + +def try_for(fn, timeout=5.0, delay=0.1): + """Calls ``fn`` every ``delay`` seconds until it returns True or ``timeout`` + seconds elapse. Returns True if the lock was acquired, or False if the timeout + was reached. + + :param timeout: Length of time (in seconds) to keep retrying to acquire the + lock. 0 means return immediately. Only used when blocking is False. + :param delay: How often (in seconds) to retry acquiring the lock during + the timeout period. Only used when blocking is False and timeout > 0. + """ + + until = time.time() + timeout + v = fn() + while not v and time.time() < until: + time.sleep(delay) + v = fn() + return v + + +class LockBase(object): + """Base class for file locks. + """ + + def __init__(self, filename): + self.fd = None + self.filename = filename + self.locked = False + + def __del__(self): + if hasattr(self, "fd") and self.fd: + try: + self.release() + except: + pass + + def acquire(self, blocking=False): + """Acquire the lock. Returns True if the lock was acquired. + + :param blocking: if True, call blocks until the lock is acquired. + This may not be available on all platforms. On Windows, this is + actually just a delay of 10 seconds, rechecking every second. + """ + pass + + def release(self): + pass + + +class FcntlLock(LockBase): + """File lock based on UNIX-only fcntl module. + """ + + def acquire(self, blocking=False): + import fcntl + + flags = os.O_CREAT | os.O_WRONLY + self.fd = os.open(self.filename, flags) + + mode = fcntl.LOCK_EX + if not blocking: mode |= fcntl.LOCK_NB + + try: + fcntl.flock(self.fd, mode) + self.locked = True + return True + except IOError, e: + if e.errno not in (errno.EAGAIN, errno.EACCES): + raise + os.close(self.fd) + self.fd = None + return False + + def release(self): + import fcntl + fcntl.flock(self.fd, fcntl.LOCK_UN) + os.close(self.fd) + self.fd = None + + +class MsvcrtLock(LockBase): + """File lock based on Windows-only msvcrt module. + """ + + def acquire(self, blocking=False): + import msvcrt + + flags = os.O_CREAT | os.O_WRONLY + mode = msvcrt.LK_NBLCK + if blocking: mode = msvcrt.LK_LOCK + + self.fd = os.open(self.filename, flags) + try: + msvcrt.locking(self.fd, mode, 1) + return True + except IOError, e: + if e.errno not in (errno.EAGAIN, errno.EACCES, errno.EDEADLK): + raise + os.close(self.fd) + self.fd = None + return False + + def release(self): + if self.fd is None: + raise Exception("Lock was not acquired") + + import msvcrt + msvcrt.locking(self.fd, msvcrt.LK_UNLCK, 1) + os.close(self.fd) + self.fd = None + + +if os.name == "nt": + FileLock = MsvcrtLock +else: + FileLock = FcntlLock + + + diff --git a/lib/whoosh/support/numeric.py b/lib/whoosh/support/numeric.py index b3f2f12e..ffc355ab 100644 --- a/lib/whoosh/support/numeric.py +++ b/lib/whoosh/support/numeric.py @@ -1,246 +1,246 @@ -#=============================================================================== -# Copyright 2010 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -import struct -from array import array - - -_dstruct = struct.Struct("= 0 - return x - -def sortable_int_to_int(x, signed=True): - if signed: x -= 1 << 31 - return x - -def long_to_sortable_long(x, signed=True): - if signed: x += 1 << 63 - assert x >= 0 - return x - -def sortable_long_to_long(x, signed=True): - if signed: x -= 1 << 63 - return x - -def float_to_sortable_long(x, signed=True): - x = _qunpack(_dpack(x))[0] - if x < 0: - x ^= 0x7fffffffffffffff - if signed: x += 1 << 63 - assert x >= 0 - return x - -def sortable_long_to_float(x, signed=True): - if signed: x -= 1 << 63 - if x < 0: - x ^= 0x7fffffffffffffff - x = _dunpack(_qpack(x))[0] - return x - -# Functions for converting numbers to and from text - -def int_to_text(x, shift=0, signed=True): - x = int_to_sortable_int(x, signed) - return sortable_int_to_text(x, shift) - -def text_to_int(text, signed=True): - x = text_to_sortable_int(text) - x = sortable_int_to_int(x, signed) - return x - -def long_to_text(x, shift=0, signed=True): - x = long_to_sortable_long(x, signed) - return sortable_long_to_text(x, shift) - -def text_to_long(text, signed=True): - x = text_to_sortable_long(text) - x = sortable_long_to_long(x, signed) - return x - -def float_to_text(x, shift=0, signed=True): - x = float_to_sortable_long(x, signed) - return sortable_long_to_text(x, shift) - -def text_to_float(text, signed=True): - x = text_to_sortable_long(text) - x = sortable_long_to_float(x, signed) - return x - -# Functions for converting sortable representations to and from text. -# -# These functions use hexadecimal strings to encode the numbers, rather than -# converting them to text using a 7-bit encoding, because while the hex -# representation uses more space (8 bytes as opposed to 5 bytes for a 32 bit -# number), it's 5-10 times faster to encode/decode in Python. -# -# The functions for 7 bit encoding are still available (to_7bit and from_7bit) -# if needed. - - -def sortable_int_to_text(x, shift=0): - if shift: - x >>= shift - text = chr(shift) + u"%08x" % x - assert len(text) == 9 - return text - -def sortable_long_to_text(x, shift=0): - if shift: - x >>= shift - text = chr(shift) + u"%016x" % x - assert len(text) == 17 - return text - -def text_to_sortable_int(text): - #assert len(text) == 9 - return int(text[1:], 16) - -def text_to_sortable_long(text): - #assert len(text) == 17 - return long(text[1:], 16) - - -# Functions for generating tiered ranges - -def split_range(valsize, step, start, end): - """Splits a range of numbers (from ``start`` to ``end``, inclusive) - into a sequence of trie ranges of the form ``(start, end, shift)``. The - consumer of these tuples is expected to shift the ``start`` and ``end`` - right by ``shift``. - - This is used for generating term ranges for a numeric field. The queries - for the edges of the range are generated at high precision and large blocks - in the middle are generated at low precision. - """ - - shift = 0 - while True: - diff = 1 << (shift + step) - mask = ((1 << step) - 1) << shift - setbits = lambda x: x | ((1 << shift) - 1) - - haslower = (start & mask) != 0 - hasupper = (end & mask) != mask - - not_mask = ~mask & ((1 << valsize + 1) - 1) - nextstart = (start + diff if haslower else start) & not_mask - nextend = (end - diff if hasupper else end) & not_mask - - if shift + step >= valsize or nextstart > nextend: - yield (start, setbits(end), shift) - break - - if haslower: - yield (start, setbits(start | mask), shift) - if hasupper: - yield (end & not_mask, setbits(end), shift) - - start = nextstart - end = nextend - shift += step - - -def tiered_ranges(numtype, signed, start, end, shift_step, startexcl, endexcl): - # First, convert the start and end of the range to sortable representations - - valsize = 32 if numtype is int else 64 - - # Convert start and end values to sortable ints - if start is None: - start = 0 - else: - if numtype is int: - start = int_to_sortable_int(start, signed) - elif numtype is long: - start = long_to_sortable_long(start, signed) - elif numtype is float: - start = float_to_sortable_long(start, signed) - if startexcl: start += 1 - - if end is None: - end = _max_sortable_int if valsize == 32 else _max_sortable_long - else: - if numtype is int: - end = int_to_sortable_int(end, signed) - elif numtype is long: - end = long_to_sortable_long(end, signed) - elif numtype is float: - end = float_to_sortable_long(end, signed) - if endexcl: end -= 1 - - if numtype is int: - to_text = sortable_int_to_text - else: - to_text = sortable_long_to_text - - if not shift_step: - yield (to_text(start), to_text(end)) - return - - # Yield the term ranges for the different resolutions - for rstart, rend, shift in split_range(valsize, shift_step, start, end): - starttext = to_text(rstart, shift=shift) - endtext = to_text(rend, shift=shift) - yield (starttext, endtext) - - -# Functions for encoding numeric values as sequences of 7-bit ascii characters - -def to_7bit(x, islong): - if not islong: - shift = 31 - nchars = 5 - else: - shift = 63 - nchars = 10 - - buffer = array("c", "\x00" * nchars) - x += (1 << shift) - 1 - while x: - buffer[nchars - 1] = chr(x & 0x7f) - x >>= 7 - nchars -= 1 - return buffer.tostring() - -def from_7bit(text): - if len(text) == 5: - shift = 31 - elif len(text) == 10: - shift = 63 - else: - raise ValueError("text is not 5 or 10 bytes") - - x = 0 - for char in text: - x <<= 7 - char = ord(char) - if char > 0x7f: - raise Exception - x |= char - x -= (1 << shift) - 1 - return int(x) +#=============================================================================== +# Copyright 2010 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +import struct +from array import array + + +_dstruct = struct.Struct("= 0 + return x + +def sortable_int_to_int(x, signed=True): + if signed: x -= 1 << 31 + return x + +def long_to_sortable_long(x, signed=True): + if signed: x += 1 << 63 + assert x >= 0 + return x + +def sortable_long_to_long(x, signed=True): + if signed: x -= 1 << 63 + return x + +def float_to_sortable_long(x, signed=True): + x = _qunpack(_dpack(x))[0] + if x < 0: + x ^= 0x7fffffffffffffff + if signed: x += 1 << 63 + assert x >= 0 + return x + +def sortable_long_to_float(x, signed=True): + if signed: x -= 1 << 63 + if x < 0: + x ^= 0x7fffffffffffffff + x = _dunpack(_qpack(x))[0] + return x + +# Functions for converting numbers to and from text + +def int_to_text(x, shift=0, signed=True): + x = int_to_sortable_int(x, signed) + return sortable_int_to_text(x, shift) + +def text_to_int(text, signed=True): + x = text_to_sortable_int(text) + x = sortable_int_to_int(x, signed) + return x + +def long_to_text(x, shift=0, signed=True): + x = long_to_sortable_long(x, signed) + return sortable_long_to_text(x, shift) + +def text_to_long(text, signed=True): + x = text_to_sortable_long(text) + x = sortable_long_to_long(x, signed) + return x + +def float_to_text(x, shift=0, signed=True): + x = float_to_sortable_long(x, signed) + return sortable_long_to_text(x, shift) + +def text_to_float(text, signed=True): + x = text_to_sortable_long(text) + x = sortable_long_to_float(x, signed) + return x + +# Functions for converting sortable representations to and from text. +# +# These functions use hexadecimal strings to encode the numbers, rather than +# converting them to text using a 7-bit encoding, because while the hex +# representation uses more space (8 bytes as opposed to 5 bytes for a 32 bit +# number), it's 5-10 times faster to encode/decode in Python. +# +# The functions for 7 bit encoding are still available (to_7bit and from_7bit) +# if needed. + + +def sortable_int_to_text(x, shift=0): + if shift: + x >>= shift + text = chr(shift) + u"%08x" % x + assert len(text) == 9 + return text + +def sortable_long_to_text(x, shift=0): + if shift: + x >>= shift + text = chr(shift) + u"%016x" % x + assert len(text) == 17 + return text + +def text_to_sortable_int(text): + #assert len(text) == 9 + return int(text[1:], 16) + +def text_to_sortable_long(text): + #assert len(text) == 17 + return long(text[1:], 16) + + +# Functions for generating tiered ranges + +def split_range(valsize, step, start, end): + """Splits a range of numbers (from ``start`` to ``end``, inclusive) + into a sequence of trie ranges of the form ``(start, end, shift)``. The + consumer of these tuples is expected to shift the ``start`` and ``end`` + right by ``shift``. + + This is used for generating term ranges for a numeric field. The queries + for the edges of the range are generated at high precision and large blocks + in the middle are generated at low precision. + """ + + shift = 0 + while True: + diff = 1 << (shift + step) + mask = ((1 << step) - 1) << shift + setbits = lambda x: x | ((1 << shift) - 1) + + haslower = (start & mask) != 0 + hasupper = (end & mask) != mask + + not_mask = ~mask & ((1 << valsize + 1) - 1) + nextstart = (start + diff if haslower else start) & not_mask + nextend = (end - diff if hasupper else end) & not_mask + + if shift + step >= valsize or nextstart > nextend: + yield (start, setbits(end), shift) + break + + if haslower: + yield (start, setbits(start | mask), shift) + if hasupper: + yield (end & not_mask, setbits(end), shift) + + start = nextstart + end = nextend + shift += step + + +def tiered_ranges(numtype, signed, start, end, shift_step, startexcl, endexcl): + # First, convert the start and end of the range to sortable representations + + valsize = 32 if numtype is int else 64 + + # Convert start and end values to sortable ints + if start is None: + start = 0 + else: + if numtype is int: + start = int_to_sortable_int(start, signed) + elif numtype is long: + start = long_to_sortable_long(start, signed) + elif numtype is float: + start = float_to_sortable_long(start, signed) + if startexcl: start += 1 + + if end is None: + end = _max_sortable_int if valsize == 32 else _max_sortable_long + else: + if numtype is int: + end = int_to_sortable_int(end, signed) + elif numtype is long: + end = long_to_sortable_long(end, signed) + elif numtype is float: + end = float_to_sortable_long(end, signed) + if endexcl: end -= 1 + + if numtype is int: + to_text = sortable_int_to_text + else: + to_text = sortable_long_to_text + + if not shift_step: + yield (to_text(start), to_text(end)) + return + + # Yield the term ranges for the different resolutions + for rstart, rend, shift in split_range(valsize, shift_step, start, end): + starttext = to_text(rstart, shift=shift) + endtext = to_text(rend, shift=shift) + yield (starttext, endtext) + + +# Functions for encoding numeric values as sequences of 7-bit ascii characters + +def to_7bit(x, islong): + if not islong: + shift = 31 + nchars = 5 + else: + shift = 63 + nchars = 10 + + buffer = array("c", "\x00" * nchars) + x += (1 << shift) - 1 + while x: + buffer[nchars - 1] = chr(x & 0x7f) + x >>= 7 + nchars -= 1 + return buffer.tostring() + +def from_7bit(text): + if len(text) == 5: + shift = 31 + elif len(text) == 10: + shift = 63 + else: + raise ValueError("text is not 5 or 10 bytes") + + x = 0 + for char in text: + x <<= 7 + char = ord(char) + if char > 0x7f: + raise Exception + x |= char + x -= (1 << shift) - 1 + return int(x) diff --git a/lib/whoosh/support/relativedelta.py b/lib/whoosh/support/relativedelta.py index 96e96306..3804cda2 100644 --- a/lib/whoosh/support/relativedelta.py +++ b/lib/whoosh/support/relativedelta.py @@ -1,432 +1,432 @@ -""" -Copyright (c) 2003-2010 Gustavo Niemeyer - -This module offers extensions to the standard python 2.3+ -datetime module. -""" -__author__ = "Gustavo Niemeyer " -__license__ = "PSF License" - -import datetime -import calendar - -__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] - -class weekday(object): - __slots__ = ["weekday", "n"] - - def __init__(self, weekday, n=None): - self.weekday = weekday - self.n = n - - def __call__(self, n): - if n == self.n: - return self - else: - return self.__class__(self.weekday, n) - - def __eq__(self, other): - try: - if self.weekday != other.weekday or self.n != other.n: - return False - except AttributeError: - return False - return True - - def __repr__(self): - s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] - if not self.n: - return s - else: - return "%s(%+d)" % (s, self.n) - -MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)]) - -class relativedelta: - """ -The relativedelta type is based on the specification of the excellent -work done by M.-A. Lemburg in his mx.DateTime extension. However, -notice that this type does *NOT* implement the same algorithm as -his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. - -There's two different ways to build a relativedelta instance. The -first one is passing it two date/datetime classes: - - relativedelta(datetime1, datetime2) - -And the other way is to use the following keyword arguments: - - year, month, day, hour, minute, second, microsecond: - Absolute information. - - years, months, weeks, days, hours, minutes, seconds, microseconds: - Relative information, may be negative. - - weekday: - One of the weekday instances (MO, TU, etc). These instances may - receive a parameter N, specifying the Nth weekday, which could - be positive or negative (like MO(+1) or MO(-2). Not specifying - it is the same as specifying +1. You can also use an integer, - where 0=MO. - - leapdays: - Will add given days to the date found, if year is a leap - year, and the date found is post 28 of february. - - yearday, nlyearday: - Set the yearday or the non-leap year day (jump leap days). - These are converted to day/month/leapdays information. - -Here is the behavior of operations with relativedelta: - -1) Calculate the absolute year, using the 'year' argument, or the - original datetime year, if the argument is not present. - -2) Add the relative 'years' argument to the absolute year. - -3) Do steps 1 and 2 for month/months. - -4) Calculate the absolute day, using the 'day' argument, or the - original datetime day, if the argument is not present. Then, - subtract from the day until it fits in the year and month - found after their operations. - -5) Add the relative 'days' argument to the absolute day. Notice - that the 'weeks' argument is multiplied by 7 and added to - 'days'. - -6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds, - microsecond/microseconds. - -7) If the 'weekday' argument is present, calculate the weekday, - with the given (wday, nth) tuple. wday is the index of the - weekday (0-6, 0=Mon), and nth is the number of weeks to add - forward or backward, depending on its signal. Notice that if - the calculated date is already Monday, for example, using - (0, 1) or (0, -1) won't change the day. - """ - - def __init__(self, dt1=None, dt2=None, - years=0, months=0, days=0, leapdays=0, weeks=0, - hours=0, minutes=0, seconds=0, microseconds=0, - year=None, month=None, day=None, weekday=None, - yearday=None, nlyearday=None, - hour=None, minute=None, second=None, microsecond=None): - if dt1 and dt2: - if not isinstance(dt1, datetime.date) or \ - not isinstance(dt2, datetime.date): - raise TypeError, "relativedelta only diffs datetime/date" - if type(dt1) is not type(dt2): - if not isinstance(dt1, datetime.datetime): - dt1 = datetime.datetime.fromordinal(dt1.toordinal()) - elif not isinstance(dt2, datetime.datetime): - dt2 = datetime.datetime.fromordinal(dt2.toordinal()) - self.years = 0 - self.months = 0 - self.days = 0 - self.leapdays = 0 - self.hours = 0 - self.minutes = 0 - self.seconds = 0 - self.microseconds = 0 - self.year = None - self.month = None - self.day = None - self.weekday = None - self.hour = None - self.minute = None - self.second = None - self.microsecond = None - self._has_time = 0 - - months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month) - self._set_months(months) - dtm = self.__radd__(dt2) - if dt1 < dt2: - while dt1 > dtm: - months += 1 - self._set_months(months) - dtm = self.__radd__(dt2) - else: - while dt1 < dtm: - months -= 1 - self._set_months(months) - dtm = self.__radd__(dt2) - delta = dt1 - dtm - self.seconds = delta.seconds+delta.days*86400 - self.microseconds = delta.microseconds - else: - self.years = years - self.months = months - self.days = days+weeks*7 - self.leapdays = leapdays - self.hours = hours - self.minutes = minutes - self.seconds = seconds - self.microseconds = microseconds - self.year = year - self.month = month - self.day = day - self.hour = hour - self.minute = minute - self.second = second - self.microsecond = microsecond - - if type(weekday) is int: - self.weekday = weekdays[weekday] - else: - self.weekday = weekday - - yday = 0 - if nlyearday: - yday = nlyearday - elif yearday: - yday = yearday - if yearday > 59: - self.leapdays = -1 - if yday: - ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366] - for idx, ydays in enumerate(ydayidx): - if yday <= ydays: - self.month = idx+1 - if idx == 0: - self.day = yday - else: - self.day = yday-ydayidx[idx-1] - break - else: - raise ValueError, "invalid year day (%d)" % yday - - self._fix() - - def _fix(self): - if abs(self.microseconds) > 999999: - s = self.microseconds//abs(self.microseconds) - div, mod = divmod(self.microseconds*s, 1000000) - self.microseconds = mod*s - self.seconds += div*s - if abs(self.seconds) > 59: - s = self.seconds//abs(self.seconds) - div, mod = divmod(self.seconds*s, 60) - self.seconds = mod*s - self.minutes += div*s - if abs(self.minutes) > 59: - s = self.minutes//abs(self.minutes) - div, mod = divmod(self.minutes*s, 60) - self.minutes = mod*s - self.hours += div*s - if abs(self.hours) > 23: - s = self.hours//abs(self.hours) - div, mod = divmod(self.hours*s, 24) - self.hours = mod*s - self.days += div*s - if abs(self.months) > 11: - s = self.months//abs(self.months) - div, mod = divmod(self.months*s, 12) - self.months = mod*s - self.years += div*s - if (self.hours or self.minutes or self.seconds or self.microseconds or - self.hour is not None or self.minute is not None or - self.second is not None or self.microsecond is not None): - self._has_time = 1 - else: - self._has_time = 0 - - def _set_months(self, months): - self.months = months - if abs(self.months) > 11: - s = self.months//abs(self.months) - div, mod = divmod(self.months*s, 12) - self.months = mod*s - self.years = div*s - else: - self.years = 0 - - def __radd__(self, other): - if not isinstance(other, datetime.date): - raise TypeError, "unsupported type for add operation" - elif self._has_time and not isinstance(other, datetime.datetime): - other = datetime.datetime.fromordinal(other.toordinal()) - year = (self.year or other.year)+self.years - month = self.month or other.month - if self.months: - assert 1 <= abs(self.months) <= 12 - month += self.months - if month > 12: - year += 1 - month -= 12 - elif month < 1: - year -= 1 - month += 12 - day = min(calendar.monthrange(year, month)[1], - self.day or other.day) - repl = {"year": year, "month": month, "day": day} - for attr in ["hour", "minute", "second", "microsecond"]: - value = getattr(self, attr) - if value is not None: - repl[attr] = value - days = self.days - if self.leapdays and month > 2 and calendar.isleap(year): - days += self.leapdays - ret = (other.replace(**repl) - + datetime.timedelta(days=days, - hours=self.hours, - minutes=self.minutes, - seconds=self.seconds, - microseconds=self.microseconds)) - if self.weekday: - weekday, nth = self.weekday.weekday, self.weekday.n or 1 - jumpdays = (abs(nth)-1)*7 - if nth > 0: - jumpdays += (7-ret.weekday()+weekday)%7 - else: - jumpdays += (ret.weekday()-weekday)%7 - jumpdays *= -1 - ret += datetime.timedelta(days=jumpdays) - return ret - - def __rsub__(self, other): - return self.__neg__().__radd__(other) - - def __add__(self, other): - if not isinstance(other, relativedelta): - raise TypeError, "unsupported type for add operation" - return relativedelta(years=other.years+self.years, - months=other.months+self.months, - days=other.days+self.days, - hours=other.hours+self.hours, - minutes=other.minutes+self.minutes, - seconds=other.seconds+self.seconds, - microseconds=other.microseconds+self.microseconds, - leapdays=other.leapdays or self.leapdays, - year=other.year or self.year, - month=other.month or self.month, - day=other.day or self.day, - weekday=other.weekday or self.weekday, - hour=other.hour or self.hour, - minute=other.minute or self.minute, - second=other.second or self.second, - microsecond=other.second or self.microsecond) - - def __sub__(self, other): - if not isinstance(other, relativedelta): - raise TypeError, "unsupported type for sub operation" - return relativedelta(years=other.years-self.years, - months=other.months-self.months, - days=other.days-self.days, - hours=other.hours-self.hours, - minutes=other.minutes-self.minutes, - seconds=other.seconds-self.seconds, - microseconds=other.microseconds-self.microseconds, - leapdays=other.leapdays or self.leapdays, - year=other.year or self.year, - month=other.month or self.month, - day=other.day or self.day, - weekday=other.weekday or self.weekday, - hour=other.hour or self.hour, - minute=other.minute or self.minute, - second=other.second or self.second, - microsecond=other.second or self.microsecond) - - def __neg__(self): - return relativedelta(years=-self.years, - months=-self.months, - days=-self.days, - hours=-self.hours, - minutes=-self.minutes, - seconds=-self.seconds, - microseconds=-self.microseconds, - leapdays=self.leapdays, - year=self.year, - month=self.month, - day=self.day, - weekday=self.weekday, - hour=self.hour, - minute=self.minute, - second=self.second, - microsecond=self.microsecond) - - def __nonzero__(self): - return not (not self.years and - not self.months and - not self.days and - not self.hours and - not self.minutes and - not self.seconds and - not self.microseconds and - not self.leapdays and - self.year is None and - self.month is None and - self.day is None and - self.weekday is None and - self.hour is None and - self.minute is None and - self.second is None and - self.microsecond is None) - - def __mul__(self, other): - f = float(other) - return relativedelta(years=self.years*f, - months=self.months*f, - days=self.days*f, - hours=self.hours*f, - minutes=self.minutes*f, - seconds=self.seconds*f, - microseconds=self.microseconds*f, - leapdays=self.leapdays, - year=self.year, - month=self.month, - day=self.day, - weekday=self.weekday, - hour=self.hour, - minute=self.minute, - second=self.second, - microsecond=self.microsecond) - - def __eq__(self, other): - if not isinstance(other, relativedelta): - return False - if self.weekday or other.weekday: - if not self.weekday or not other.weekday: - return False - if self.weekday.weekday != other.weekday.weekday: - return False - n1, n2 = self.weekday.n, other.weekday.n - if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): - return False - return (self.years == other.years and - self.months == other.months and - self.days == other.days and - self.hours == other.hours and - self.minutes == other.minutes and - self.seconds == other.seconds and - self.leapdays == other.leapdays and - self.year == other.year and - self.month == other.month and - self.day == other.day and - self.hour == other.hour and - self.minute == other.minute and - self.second == other.second and - self.microsecond == other.microsecond) - - def __ne__(self, other): - return not self.__eq__(other) - - def __div__(self, other): - return self.__mul__(1/float(other)) - - def __repr__(self): - l = [] - for attr in ["years", "months", "days", "leapdays", - "hours", "minutes", "seconds", "microseconds"]: - value = getattr(self, attr) - if value: - l.append("%s=%+d" % (attr, value)) - for attr in ["year", "month", "day", "weekday", - "hour", "minute", "second", "microsecond"]: - value = getattr(self, attr) - if value is not None: - l.append("%s=%s" % (attr, `value`)) - return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) - -# vim:ts=4:sw=4:et +""" +Copyright (c) 2003-2010 Gustavo Niemeyer + +This module offers extensions to the standard python 2.3+ +datetime module. +""" +__author__ = "Gustavo Niemeyer " +__license__ = "PSF License" + +import datetime +import calendar + +__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] + +class weekday(object): + __slots__ = ["weekday", "n"] + + def __init__(self, weekday, n=None): + self.weekday = weekday + self.n = n + + def __call__(self, n): + if n == self.n: + return self + else: + return self.__class__(self.weekday, n) + + def __eq__(self, other): + try: + if self.weekday != other.weekday or self.n != other.n: + return False + except AttributeError: + return False + return True + + def __repr__(self): + s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] + if not self.n: + return s + else: + return "%s(%+d)" % (s, self.n) + +MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)]) + +class relativedelta: + """ +The relativedelta type is based on the specification of the excellent +work done by M.-A. Lemburg in his mx.DateTime extension. However, +notice that this type does *NOT* implement the same algorithm as +his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. + +There's two different ways to build a relativedelta instance. The +first one is passing it two date/datetime classes: + + relativedelta(datetime1, datetime2) + +And the other way is to use the following keyword arguments: + + year, month, day, hour, minute, second, microsecond: + Absolute information. + + years, months, weeks, days, hours, minutes, seconds, microseconds: + Relative information, may be negative. + + weekday: + One of the weekday instances (MO, TU, etc). These instances may + receive a parameter N, specifying the Nth weekday, which could + be positive or negative (like MO(+1) or MO(-2). Not specifying + it is the same as specifying +1. You can also use an integer, + where 0=MO. + + leapdays: + Will add given days to the date found, if year is a leap + year, and the date found is post 28 of february. + + yearday, nlyearday: + Set the yearday or the non-leap year day (jump leap days). + These are converted to day/month/leapdays information. + +Here is the behavior of operations with relativedelta: + +1) Calculate the absolute year, using the 'year' argument, or the + original datetime year, if the argument is not present. + +2) Add the relative 'years' argument to the absolute year. + +3) Do steps 1 and 2 for month/months. + +4) Calculate the absolute day, using the 'day' argument, or the + original datetime day, if the argument is not present. Then, + subtract from the day until it fits in the year and month + found after their operations. + +5) Add the relative 'days' argument to the absolute day. Notice + that the 'weeks' argument is multiplied by 7 and added to + 'days'. + +6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds, + microsecond/microseconds. + +7) If the 'weekday' argument is present, calculate the weekday, + with the given (wday, nth) tuple. wday is the index of the + weekday (0-6, 0=Mon), and nth is the number of weeks to add + forward or backward, depending on its signal. Notice that if + the calculated date is already Monday, for example, using + (0, 1) or (0, -1) won't change the day. + """ + + def __init__(self, dt1=None, dt2=None, + years=0, months=0, days=0, leapdays=0, weeks=0, + hours=0, minutes=0, seconds=0, microseconds=0, + year=None, month=None, day=None, weekday=None, + yearday=None, nlyearday=None, + hour=None, minute=None, second=None, microsecond=None): + if dt1 and dt2: + if not isinstance(dt1, datetime.date) or \ + not isinstance(dt2, datetime.date): + raise TypeError, "relativedelta only diffs datetime/date" + if type(dt1) is not type(dt2): + if not isinstance(dt1, datetime.datetime): + dt1 = datetime.datetime.fromordinal(dt1.toordinal()) + elif not isinstance(dt2, datetime.datetime): + dt2 = datetime.datetime.fromordinal(dt2.toordinal()) + self.years = 0 + self.months = 0 + self.days = 0 + self.leapdays = 0 + self.hours = 0 + self.minutes = 0 + self.seconds = 0 + self.microseconds = 0 + self.year = None + self.month = None + self.day = None + self.weekday = None + self.hour = None + self.minute = None + self.second = None + self.microsecond = None + self._has_time = 0 + + months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month) + self._set_months(months) + dtm = self.__radd__(dt2) + if dt1 < dt2: + while dt1 > dtm: + months += 1 + self._set_months(months) + dtm = self.__radd__(dt2) + else: + while dt1 < dtm: + months -= 1 + self._set_months(months) + dtm = self.__radd__(dt2) + delta = dt1 - dtm + self.seconds = delta.seconds+delta.days*86400 + self.microseconds = delta.microseconds + else: + self.years = years + self.months = months + self.days = days+weeks*7 + self.leapdays = leapdays + self.hours = hours + self.minutes = minutes + self.seconds = seconds + self.microseconds = microseconds + self.year = year + self.month = month + self.day = day + self.hour = hour + self.minute = minute + self.second = second + self.microsecond = microsecond + + if type(weekday) is int: + self.weekday = weekdays[weekday] + else: + self.weekday = weekday + + yday = 0 + if nlyearday: + yday = nlyearday + elif yearday: + yday = yearday + if yearday > 59: + self.leapdays = -1 + if yday: + ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366] + for idx, ydays in enumerate(ydayidx): + if yday <= ydays: + self.month = idx+1 + if idx == 0: + self.day = yday + else: + self.day = yday-ydayidx[idx-1] + break + else: + raise ValueError, "invalid year day (%d)" % yday + + self._fix() + + def _fix(self): + if abs(self.microseconds) > 999999: + s = self.microseconds//abs(self.microseconds) + div, mod = divmod(self.microseconds*s, 1000000) + self.microseconds = mod*s + self.seconds += div*s + if abs(self.seconds) > 59: + s = self.seconds//abs(self.seconds) + div, mod = divmod(self.seconds*s, 60) + self.seconds = mod*s + self.minutes += div*s + if abs(self.minutes) > 59: + s = self.minutes//abs(self.minutes) + div, mod = divmod(self.minutes*s, 60) + self.minutes = mod*s + self.hours += div*s + if abs(self.hours) > 23: + s = self.hours//abs(self.hours) + div, mod = divmod(self.hours*s, 24) + self.hours = mod*s + self.days += div*s + if abs(self.months) > 11: + s = self.months//abs(self.months) + div, mod = divmod(self.months*s, 12) + self.months = mod*s + self.years += div*s + if (self.hours or self.minutes or self.seconds or self.microseconds or + self.hour is not None or self.minute is not None or + self.second is not None or self.microsecond is not None): + self._has_time = 1 + else: + self._has_time = 0 + + def _set_months(self, months): + self.months = months + if abs(self.months) > 11: + s = self.months//abs(self.months) + div, mod = divmod(self.months*s, 12) + self.months = mod*s + self.years = div*s + else: + self.years = 0 + + def __radd__(self, other): + if not isinstance(other, datetime.date): + raise TypeError, "unsupported type for add operation" + elif self._has_time and not isinstance(other, datetime.datetime): + other = datetime.datetime.fromordinal(other.toordinal()) + year = (self.year or other.year)+self.years + month = self.month or other.month + if self.months: + assert 1 <= abs(self.months) <= 12 + month += self.months + if month > 12: + year += 1 + month -= 12 + elif month < 1: + year -= 1 + month += 12 + day = min(calendar.monthrange(year, month)[1], + self.day or other.day) + repl = {"year": year, "month": month, "day": day} + for attr in ["hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + repl[attr] = value + days = self.days + if self.leapdays and month > 2 and calendar.isleap(year): + days += self.leapdays + ret = (other.replace(**repl) + + datetime.timedelta(days=days, + hours=self.hours, + minutes=self.minutes, + seconds=self.seconds, + microseconds=self.microseconds)) + if self.weekday: + weekday, nth = self.weekday.weekday, self.weekday.n or 1 + jumpdays = (abs(nth)-1)*7 + if nth > 0: + jumpdays += (7-ret.weekday()+weekday)%7 + else: + jumpdays += (ret.weekday()-weekday)%7 + jumpdays *= -1 + ret += datetime.timedelta(days=jumpdays) + return ret + + def __rsub__(self, other): + return self.__neg__().__radd__(other) + + def __add__(self, other): + if not isinstance(other, relativedelta): + raise TypeError, "unsupported type for add operation" + return relativedelta(years=other.years+self.years, + months=other.months+self.months, + days=other.days+self.days, + hours=other.hours+self.hours, + minutes=other.minutes+self.minutes, + seconds=other.seconds+self.seconds, + microseconds=other.microseconds+self.microseconds, + leapdays=other.leapdays or self.leapdays, + year=other.year or self.year, + month=other.month or self.month, + day=other.day or self.day, + weekday=other.weekday or self.weekday, + hour=other.hour or self.hour, + minute=other.minute or self.minute, + second=other.second or self.second, + microsecond=other.second or self.microsecond) + + def __sub__(self, other): + if not isinstance(other, relativedelta): + raise TypeError, "unsupported type for sub operation" + return relativedelta(years=other.years-self.years, + months=other.months-self.months, + days=other.days-self.days, + hours=other.hours-self.hours, + minutes=other.minutes-self.minutes, + seconds=other.seconds-self.seconds, + microseconds=other.microseconds-self.microseconds, + leapdays=other.leapdays or self.leapdays, + year=other.year or self.year, + month=other.month or self.month, + day=other.day or self.day, + weekday=other.weekday or self.weekday, + hour=other.hour or self.hour, + minute=other.minute or self.minute, + second=other.second or self.second, + microsecond=other.second or self.microsecond) + + def __neg__(self): + return relativedelta(years=-self.years, + months=-self.months, + days=-self.days, + hours=-self.hours, + minutes=-self.minutes, + seconds=-self.seconds, + microseconds=-self.microseconds, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __nonzero__(self): + return not (not self.years and + not self.months and + not self.days and + not self.hours and + not self.minutes and + not self.seconds and + not self.microseconds and + not self.leapdays and + self.year is None and + self.month is None and + self.day is None and + self.weekday is None and + self.hour is None and + self.minute is None and + self.second is None and + self.microsecond is None) + + def __mul__(self, other): + f = float(other) + return relativedelta(years=self.years*f, + months=self.months*f, + days=self.days*f, + hours=self.hours*f, + minutes=self.minutes*f, + seconds=self.seconds*f, + microseconds=self.microseconds*f, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __eq__(self, other): + if not isinstance(other, relativedelta): + return False + if self.weekday or other.weekday: + if not self.weekday or not other.weekday: + return False + if self.weekday.weekday != other.weekday.weekday: + return False + n1, n2 = self.weekday.n, other.weekday.n + if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): + return False + return (self.years == other.years and + self.months == other.months and + self.days == other.days and + self.hours == other.hours and + self.minutes == other.minutes and + self.seconds == other.seconds and + self.leapdays == other.leapdays and + self.year == other.year and + self.month == other.month and + self.day == other.day and + self.hour == other.hour and + self.minute == other.minute and + self.second == other.second and + self.microsecond == other.microsecond) + + def __ne__(self, other): + return not self.__eq__(other) + + def __div__(self, other): + return self.__mul__(1/float(other)) + + def __repr__(self): + l = [] + for attr in ["years", "months", "days", "leapdays", + "hours", "minutes", "seconds", "microseconds"]: + value = getattr(self, attr) + if value: + l.append("%s=%+d" % (attr, value)) + for attr in ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, `value`)) + return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) + +# vim:ts=4:sw=4:et diff --git a/lib/whoosh/support/times.py b/lib/whoosh/support/times.py index 7108cfcb..af92ff0b 100644 --- a/lib/whoosh/support/times.py +++ b/lib/whoosh/support/times.py @@ -1,420 +1,420 @@ -#=============================================================================== -# Copyright 2010 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -import calendar, copy -from datetime import date, time, datetime, timedelta - -from whoosh.support.relativedelta import relativedelta - - -class TimeError(Exception): pass - - - -def relative_days(current_wday, wday, dir): - """Returns the number of days (positive or negative) to the "next" or - "last" of a certain weekday. ``current_wday`` and ``wday`` are numbers, - i.e. 0 = monday, 1 = tuesday, 2 = wednesday, etc. - - >>> # Get the number of days to the next tuesday, if today is Sunday - >>> relative_days(6, 1, 1) - 2 - - :param current_wday: the number of the current weekday. - :param wday: the target weekday. - :param dir: -1 for the "last" (past) weekday, 1 for the "next" (future) - weekday. - """ - - if current_wday == wday: - return 7 * dir - - if dir == 1: - return (wday + 7 - current_wday) % 7 - else: - return (current_wday + 7 - wday) % 7 * -1 - - -def datetime_to_long(dt): - """Converts a datetime object to a long integer representing the number - of microseconds since ``datetime.min``. - """ - - td = dt - dt.min - total = td.days * 86400000000 # Microseconds in a day - total += td.seconds * 1000000 # Microseconds in a second - total += td.microseconds - return total - - -# Ambiguous datetime object - -class adatetime(object): - """An "ambiguous" datetime object. This object acts like a - ``datetime.datetime`` object but can have any of its attributes set to - None, meaning unspecified. - """ - - units = frozenset(("year", "month", "day", "hour", "minute", "second", "microsecond")) - - def __init__(self, year=None, month=None, day=None, hour=None, minute=None, - second=None, microsecond=None): - if isinstance(year, datetime): - self.year, self.month, self.day = year.year, year.month, year.day - self.hour, self.minute, self.second = year.hour, year.minute, year.second - self.microsecond = year.microsecond - else: - if month is not None and month < 1 or month > 12: - raise TimeError("month must be in 1..12") - - if day is not None and day < 1: - raise TimeError("day must be greater than 1") - if (year is not None and month is not None and day is not None - and day > calendar.monthrange(year, month)[1]): - raise TimeError("day is out of range for month") - - if hour is not None and hour < 0 or hour > 23: - raise TimeError("hour must be in 0..23") - if minute is not None and minute < 0 or minute > 59: - raise TimeError("minute must be in 0..59") - if second is not None and second < 0 or second > 59: - raise TimeError("second must be in 0..59") - if microsecond is not None and microsecond < 0 or microsecond > 999999: - raise TimeError("microsecond must be in 0..999999") - - self.year, self.month, self.day = year, month, day - self.hour, self.minute, self.second = hour, minute, second - self.microsecond = microsecond - - def __eq__(self, other): - if not other.__class__ is self.__class__: - if not is_ambiguous(self) and isinstance(other, datetime): - return fix(self) == other - else: - return False - return all(getattr(self, unit) == getattr(other, unit) - for unit in self.units) - - def __repr__(self): - return "%s%r" % (self.__class__.__name__, self.tuple()) - - def tuple(self): - """Returns the attributes of the ``adatetime`` object as a tuple of - ``(year, month, day, hour, minute, second, microsecond)``. - """ - - return (self.year, self.month, self.day, self.hour, self.minute, - self.second, self.microsecond) - - def date(self): - return date(self.year, self.month, self.day) - - def copy(self): - return adatetime(year=self.year, month=self.month, day=self.day, - hour=self.hour, minute=self.minute, second=self.second, - microsecond=self.microsecond) - - def replace(self, **kwargs): - """Returns a copy of this object with the attributes given as keyword - arguments replaced. - - >>> adt = adatetime(year=2009, month=10, day=31) - >>> adt.replace(year=2010) - (2010, 10, 31, None, None, None, None) - """ - - newadatetime = self.copy() - for key, value in kwargs.iteritems(): - if key in self.units: - setattr(newadatetime, key, value) - else: - raise KeyError("Unknown argument %r" % key) - return newadatetime - - def floor(self): - """Returns a ``datetime`` version of this object with all unspecified - (None) attributes replaced by their lowest values. - - This method raises an error if the ``adatetime`` object has no year. - - >>> adt = adatetime(year=2009, month=5) - >>> adt.floor() - datetime.datetime(2009, 5, 1, 0, 0, 0, 0) - """ - - year, month, day, hour, minute, second, microsecond =\ - self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond - - if year is None: - raise ValueError("Date has no year") - - if month is None: month = 1 - if day is None: day = 1 - if hour is None: hour = 0 - if minute is None: minute = 0 - if second is None: second = 0 - if microsecond is None: microsecond = 0 - return datetime(year, month, day, hour, minute, second, microsecond) - - def ceil(self): - """Returns a ``datetime`` version of this object with all unspecified - (None) attributes replaced by their highest values. - - This method raises an error if the ``adatetime`` object has no year. - - >>> adt = adatetime(year=2009, month=5) - >>> adt.floor() - datetime.datetime(2009, 5, 30, 23, 59, 59, 999999) - """ - - year, month, day, hour, minute, second, microsecond =\ - self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond - - if year is None: - raise ValueError("Date has no year") - - if month is None: month = 12 - if day is None: day = calendar.monthrange(year, month)[1] - if hour is None: hour = 23 - if minute is None: minute = 59 - if second is None: second = 59 - if microsecond is None: microsecond = 999999 - return datetime(year, month, day, hour, minute, second, microsecond) - - def disambiguated(self, basedate): - """Returns either a ``datetime`` or unambiguous ``timespan`` version - of this object. - - Unless this ``adatetime`` object is full specified down to the - microsecond, this method will return a timespan built from the "floor" - and "ceil" of this object. - - This method raises an error if the ``adatetime`` object has no year. - - >>> adt = adatetime(year=2009, month=10, day=31) - >>> adt.disambiguated() - timespan(datetime.datetime(2009, 10, 31, 0, 0, 0, 0), datetime.datetime(2009, 10, 31, 23, 59 ,59, 999999) - """ - - dt = self - if not is_ambiguous(dt): - return fix(dt) - return timespan(dt, dt).disambiguated(basedate) - - -# Time span class - -class timespan(object): - """A span of time between two ``datetime`` or ``adatetime`` objects. - """ - - def __init__(self, start, end): - """ - :param start: a ``datetime`` or ``adatetime`` object representing the - start of the time span. - :param end: a ``datetime`` or ``adatetime`` object representing the - end of the time span. - """ - - if not isinstance(start, (datetime, adatetime)): - raise TimeError("%r is not a datetime object" % start) - if not isinstance(end, (datetime, adatetime)): - raise TimeError("%r is not a datetime object" % end) - - self.start = copy.copy(start) - self.end = copy.copy(end) - - def __eq__(self, other): - if not other.__class__ is self.__class__: return False - return self.start == other.start and self.end == other.end - - def __repr__(self): - return "%s(%r, %r)" % (self.__class__.__name__, self.start, self.end) - - def disambiguated(self, basedate, debug=0): - """Returns an unambiguous version of this object. - - >>> start = adatetime(year=2009, month=2) - >>> end = adatetime(year=2009, month=10) - >>> ts = timespan(start, end) - >>> ts - timespan(adatetime(2009, 2, None, None, None, None, None), adatetime(2009, 10, None, None, None, None, None)) - >>> td.disambiguated(datetime.now()) - timespan(datetime.datetime(2009, 2, 28, 0, 0, 0, 0), datetime.datetime(2009, 10, 31, 23, 59 ,59, 999999) - """ - - #- If year is in start but not end, use basedate.year for end - #-- If year is in start but not end, but startdate is > basedate, - # use "next " to get end month/year - #- If year is in end but not start, copy year from end to start - #- Support "next february", "last april", etc. - - start, end = copy.copy(self.start), copy.copy(self.end) - start_year_was_amb = start.year is None - end_year_was_amb = end.year is None - - if has_no_date(start) and has_no_date(end): - # The start and end points are just times, so use the basedate - # for the date information. - by, bm, bd = basedate.year, basedate.month, basedate.day - start = start.replace(year=by, month=bm, day=bd) - end = end.replace(year=by, month=bm, day=bd) - else: - # If one side has a year and the other doesn't, the decision - # of what year to assign to the ambiguous side is kind of - # arbitrary. I've used a heuristic here based on how the range - # "reads", but it may only be reasonable in English. And maybe - # even just to me. - - if start.year is None and end.year is None: - # No year on either side, use the basedate - start.year = end.year = basedate.year - elif start.year is None: - # No year in the start, use the year from the end - start.year = end.year - elif end.year is None: - end.year = max(start.year, basedate.year) - - if start.year == end.year: - # Once again, if one side has a month and day but the other side - # doesn't, the disambiguation is arbitrary. Does "3 am to 5 am - # tomorrow" mean 3 AM today to 5 AM tomorrow, or 3am tomorrow to - # 5 am tomorrow? What I picked is similar to the year: if the - # end has a month+day and the start doesn't, copy the month+day - # from the end to the start UNLESS that would make the end come - # before the start on that day, in which case use the basedate - # instead. If the start has a month+day and the end doesn't, use - # the basedate. - start_dm = not (start.month is None and start.day is None) - end_dm = not (end.month is None and end.day is None) - if end_dm and not start_dm: - if start.floor().time() > end.ceil().time(): - start.month = basedate.month - start.day = basedate.day - else: - start.month = end.month - start.day = end.day - elif start_dm and not end_dm: - end.month = basedate.month - end.day = basedate.day - - if floor(start).date() > ceil(end).date(): - # If the disambiguated dates are out of order: - # - If no start year was given, reduce the start year to put the - # start before the end - # - If no end year was given, increase the end year to put the end - # after the start - # - If a year was specified for both, just swap the start and end - if start_year_was_amb: - start.year = end.year - 1 - elif end_year_was_amb: - end.year = start.year + 1 - else: - start, end = end, start - - start = floor(start) - end = ceil(end) - - if start.date() == end.date() and start.time() > end.time(): - # If the start and end are on the same day, but the start time - # is after the end time, move the end time to the next day - end += timedelta(days=1) - - return timespan(start, end) - - -# Functions for working with datetime/adatetime objects - -def floor(at): - if isinstance(at, datetime): - return at - return at.floor() - -def ceil(at): - if isinstance(at, datetime): - return at - return at.ceil() - -def fill_in(at, basedate, units=adatetime.units): - """Returns a copy of ``at`` with any unspecified (None) units filled in - with values from ``basedate``. - """ - - if isinstance(at, datetime): - return at - - args = {} - for unit in units: - v = getattr(at, unit) - if v is None: - v = getattr(basedate, unit) - args[unit] = v - return fix(adatetime(**args)) - - -def has_no_date(at): - """Returns True if the given object is an ``adatetime`` where ``year``, - ``month``, and ``day`` are all None. - """ - - if isinstance(at, datetime): - return False - return at.year is None and at.month is None and at.day is None - - -def has_no_time(at): - """Returns True if the given object is an ``adatetime`` where ``hour``, - ``minute``, ``second`` and ``microsecond`` are all None. - """ - - if isinstance(at, datetime): - return False - return at.hour is None and at.minute is None and at.second is None and at.microsecond is None - - -def is_ambiguous(at): - """Returns True if the given object is an ``adatetime`` with any of its - attributes equal to None. - """ - - if isinstance(at, datetime): - return False - return any((getattr(at, attr) is None) for attr in adatetime.units) - - -def is_void(at): - """Returns True if the given object is an ``adatetime`` with all of its - attributes equal to None. - """ - - if isinstance(at, datetime): - return False - return all((getattr(at, attr) is None) for attr in adatetime.units) - - -def fix(at): - """If the given object is an ``adatetime`` that is unambiguous (because - all its attributes are specified, that is, not equal to None), returns a - ``datetime`` version of it. Otherwise returns the ``adatetime`` object - unchanged. - """ - - if is_ambiguous(at) or isinstance(at, datetime): - return at - return datetime(year=at.year, month=at.month, day=at.day, hour=at.hour, - minute=at.minute, second=at.second, microsecond=at.microsecond) - - +#=============================================================================== +# Copyright 2010 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +import calendar, copy +from datetime import date, time, datetime, timedelta + +from whoosh.support.relativedelta import relativedelta + + +class TimeError(Exception): pass + + + +def relative_days(current_wday, wday, dir): + """Returns the number of days (positive or negative) to the "next" or + "last" of a certain weekday. ``current_wday`` and ``wday`` are numbers, + i.e. 0 = monday, 1 = tuesday, 2 = wednesday, etc. + + >>> # Get the number of days to the next tuesday, if today is Sunday + >>> relative_days(6, 1, 1) + 2 + + :param current_wday: the number of the current weekday. + :param wday: the target weekday. + :param dir: -1 for the "last" (past) weekday, 1 for the "next" (future) + weekday. + """ + + if current_wday == wday: + return 7 * dir + + if dir == 1: + return (wday + 7 - current_wday) % 7 + else: + return (current_wday + 7 - wday) % 7 * -1 + + +def datetime_to_long(dt): + """Converts a datetime object to a long integer representing the number + of microseconds since ``datetime.min``. + """ + + td = dt - dt.min + total = td.days * 86400000000 # Microseconds in a day + total += td.seconds * 1000000 # Microseconds in a second + total += td.microseconds + return total + + +# Ambiguous datetime object + +class adatetime(object): + """An "ambiguous" datetime object. This object acts like a + ``datetime.datetime`` object but can have any of its attributes set to + None, meaning unspecified. + """ + + units = frozenset(("year", "month", "day", "hour", "minute", "second", "microsecond")) + + def __init__(self, year=None, month=None, day=None, hour=None, minute=None, + second=None, microsecond=None): + if isinstance(year, datetime): + self.year, self.month, self.day = year.year, year.month, year.day + self.hour, self.minute, self.second = year.hour, year.minute, year.second + self.microsecond = year.microsecond + else: + if month is not None and month < 1 or month > 12: + raise TimeError("month must be in 1..12") + + if day is not None and day < 1: + raise TimeError("day must be greater than 1") + if (year is not None and month is not None and day is not None + and day > calendar.monthrange(year, month)[1]): + raise TimeError("day is out of range for month") + + if hour is not None and hour < 0 or hour > 23: + raise TimeError("hour must be in 0..23") + if minute is not None and minute < 0 or minute > 59: + raise TimeError("minute must be in 0..59") + if second is not None and second < 0 or second > 59: + raise TimeError("second must be in 0..59") + if microsecond is not None and microsecond < 0 or microsecond > 999999: + raise TimeError("microsecond must be in 0..999999") + + self.year, self.month, self.day = year, month, day + self.hour, self.minute, self.second = hour, minute, second + self.microsecond = microsecond + + def __eq__(self, other): + if not other.__class__ is self.__class__: + if not is_ambiguous(self) and isinstance(other, datetime): + return fix(self) == other + else: + return False + return all(getattr(self, unit) == getattr(other, unit) + for unit in self.units) + + def __repr__(self): + return "%s%r" % (self.__class__.__name__, self.tuple()) + + def tuple(self): + """Returns the attributes of the ``adatetime`` object as a tuple of + ``(year, month, day, hour, minute, second, microsecond)``. + """ + + return (self.year, self.month, self.day, self.hour, self.minute, + self.second, self.microsecond) + + def date(self): + return date(self.year, self.month, self.day) + + def copy(self): + return adatetime(year=self.year, month=self.month, day=self.day, + hour=self.hour, minute=self.minute, second=self.second, + microsecond=self.microsecond) + + def replace(self, **kwargs): + """Returns a copy of this object with the attributes given as keyword + arguments replaced. + + >>> adt = adatetime(year=2009, month=10, day=31) + >>> adt.replace(year=2010) + (2010, 10, 31, None, None, None, None) + """ + + newadatetime = self.copy() + for key, value in kwargs.iteritems(): + if key in self.units: + setattr(newadatetime, key, value) + else: + raise KeyError("Unknown argument %r" % key) + return newadatetime + + def floor(self): + """Returns a ``datetime`` version of this object with all unspecified + (None) attributes replaced by their lowest values. + + This method raises an error if the ``adatetime`` object has no year. + + >>> adt = adatetime(year=2009, month=5) + >>> adt.floor() + datetime.datetime(2009, 5, 1, 0, 0, 0, 0) + """ + + year, month, day, hour, minute, second, microsecond =\ + self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond + + if year is None: + raise ValueError("Date has no year") + + if month is None: month = 1 + if day is None: day = 1 + if hour is None: hour = 0 + if minute is None: minute = 0 + if second is None: second = 0 + if microsecond is None: microsecond = 0 + return datetime(year, month, day, hour, minute, second, microsecond) + + def ceil(self): + """Returns a ``datetime`` version of this object with all unspecified + (None) attributes replaced by their highest values. + + This method raises an error if the ``adatetime`` object has no year. + + >>> adt = adatetime(year=2009, month=5) + >>> adt.floor() + datetime.datetime(2009, 5, 30, 23, 59, 59, 999999) + """ + + year, month, day, hour, minute, second, microsecond =\ + self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond + + if year is None: + raise ValueError("Date has no year") + + if month is None: month = 12 + if day is None: day = calendar.monthrange(year, month)[1] + if hour is None: hour = 23 + if minute is None: minute = 59 + if second is None: second = 59 + if microsecond is None: microsecond = 999999 + return datetime(year, month, day, hour, minute, second, microsecond) + + def disambiguated(self, basedate): + """Returns either a ``datetime`` or unambiguous ``timespan`` version + of this object. + + Unless this ``adatetime`` object is full specified down to the + microsecond, this method will return a timespan built from the "floor" + and "ceil" of this object. + + This method raises an error if the ``adatetime`` object has no year. + + >>> adt = adatetime(year=2009, month=10, day=31) + >>> adt.disambiguated() + timespan(datetime.datetime(2009, 10, 31, 0, 0, 0, 0), datetime.datetime(2009, 10, 31, 23, 59 ,59, 999999) + """ + + dt = self + if not is_ambiguous(dt): + return fix(dt) + return timespan(dt, dt).disambiguated(basedate) + + +# Time span class + +class timespan(object): + """A span of time between two ``datetime`` or ``adatetime`` objects. + """ + + def __init__(self, start, end): + """ + :param start: a ``datetime`` or ``adatetime`` object representing the + start of the time span. + :param end: a ``datetime`` or ``adatetime`` object representing the + end of the time span. + """ + + if not isinstance(start, (datetime, adatetime)): + raise TimeError("%r is not a datetime object" % start) + if not isinstance(end, (datetime, adatetime)): + raise TimeError("%r is not a datetime object" % end) + + self.start = copy.copy(start) + self.end = copy.copy(end) + + def __eq__(self, other): + if not other.__class__ is self.__class__: return False + return self.start == other.start and self.end == other.end + + def __repr__(self): + return "%s(%r, %r)" % (self.__class__.__name__, self.start, self.end) + + def disambiguated(self, basedate, debug=0): + """Returns an unambiguous version of this object. + + >>> start = adatetime(year=2009, month=2) + >>> end = adatetime(year=2009, month=10) + >>> ts = timespan(start, end) + >>> ts + timespan(adatetime(2009, 2, None, None, None, None, None), adatetime(2009, 10, None, None, None, None, None)) + >>> td.disambiguated(datetime.now()) + timespan(datetime.datetime(2009, 2, 28, 0, 0, 0, 0), datetime.datetime(2009, 10, 31, 23, 59 ,59, 999999) + """ + + #- If year is in start but not end, use basedate.year for end + #-- If year is in start but not end, but startdate is > basedate, + # use "next " to get end month/year + #- If year is in end but not start, copy year from end to start + #- Support "next february", "last april", etc. + + start, end = copy.copy(self.start), copy.copy(self.end) + start_year_was_amb = start.year is None + end_year_was_amb = end.year is None + + if has_no_date(start) and has_no_date(end): + # The start and end points are just times, so use the basedate + # for the date information. + by, bm, bd = basedate.year, basedate.month, basedate.day + start = start.replace(year=by, month=bm, day=bd) + end = end.replace(year=by, month=bm, day=bd) + else: + # If one side has a year and the other doesn't, the decision + # of what year to assign to the ambiguous side is kind of + # arbitrary. I've used a heuristic here based on how the range + # "reads", but it may only be reasonable in English. And maybe + # even just to me. + + if start.year is None and end.year is None: + # No year on either side, use the basedate + start.year = end.year = basedate.year + elif start.year is None: + # No year in the start, use the year from the end + start.year = end.year + elif end.year is None: + end.year = max(start.year, basedate.year) + + if start.year == end.year: + # Once again, if one side has a month and day but the other side + # doesn't, the disambiguation is arbitrary. Does "3 am to 5 am + # tomorrow" mean 3 AM today to 5 AM tomorrow, or 3am tomorrow to + # 5 am tomorrow? What I picked is similar to the year: if the + # end has a month+day and the start doesn't, copy the month+day + # from the end to the start UNLESS that would make the end come + # before the start on that day, in which case use the basedate + # instead. If the start has a month+day and the end doesn't, use + # the basedate. + start_dm = not (start.month is None and start.day is None) + end_dm = not (end.month is None and end.day is None) + if end_dm and not start_dm: + if start.floor().time() > end.ceil().time(): + start.month = basedate.month + start.day = basedate.day + else: + start.month = end.month + start.day = end.day + elif start_dm and not end_dm: + end.month = basedate.month + end.day = basedate.day + + if floor(start).date() > ceil(end).date(): + # If the disambiguated dates are out of order: + # - If no start year was given, reduce the start year to put the + # start before the end + # - If no end year was given, increase the end year to put the end + # after the start + # - If a year was specified for both, just swap the start and end + if start_year_was_amb: + start.year = end.year - 1 + elif end_year_was_amb: + end.year = start.year + 1 + else: + start, end = end, start + + start = floor(start) + end = ceil(end) + + if start.date() == end.date() and start.time() > end.time(): + # If the start and end are on the same day, but the start time + # is after the end time, move the end time to the next day + end += timedelta(days=1) + + return timespan(start, end) + + +# Functions for working with datetime/adatetime objects + +def floor(at): + if isinstance(at, datetime): + return at + return at.floor() + +def ceil(at): + if isinstance(at, datetime): + return at + return at.ceil() + +def fill_in(at, basedate, units=adatetime.units): + """Returns a copy of ``at`` with any unspecified (None) units filled in + with values from ``basedate``. + """ + + if isinstance(at, datetime): + return at + + args = {} + for unit in units: + v = getattr(at, unit) + if v is None: + v = getattr(basedate, unit) + args[unit] = v + return fix(adatetime(**args)) + + +def has_no_date(at): + """Returns True if the given object is an ``adatetime`` where ``year``, + ``month``, and ``day`` are all None. + """ + + if isinstance(at, datetime): + return False + return at.year is None and at.month is None and at.day is None + + +def has_no_time(at): + """Returns True if the given object is an ``adatetime`` where ``hour``, + ``minute``, ``second`` and ``microsecond`` are all None. + """ + + if isinstance(at, datetime): + return False + return at.hour is None and at.minute is None and at.second is None and at.microsecond is None + + +def is_ambiguous(at): + """Returns True if the given object is an ``adatetime`` with any of its + attributes equal to None. + """ + + if isinstance(at, datetime): + return False + return any((getattr(at, attr) is None) for attr in adatetime.units) + + +def is_void(at): + """Returns True if the given object is an ``adatetime`` with all of its + attributes equal to None. + """ + + if isinstance(at, datetime): + return False + return all((getattr(at, attr) is None) for attr in adatetime.units) + + +def fix(at): + """If the given object is an ``adatetime`` that is unambiguous (because + all its attributes are specified, that is, not equal to None), returns a + ``datetime`` version of it. Otherwise returns the ``adatetime`` object + unchanged. + """ + + if is_ambiguous(at) or isinstance(at, datetime): + return at + return datetime(year=at.year, month=at.month, day=at.day, hour=at.hour, + minute=at.minute, second=at.second, microsecond=at.microsecond) + + diff --git a/lib/whoosh/system.py b/lib/whoosh/system.py index 9d726c5e..27244e91 100644 --- a/lib/whoosh/system.py +++ b/lib/whoosh/system.py @@ -1,47 +1,47 @@ -#=============================================================================== -# Copyright 2007 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -import sys -from struct import Struct, calcsize - -IS_LITTLE = sys.byteorder == "little" - -_INT_SIZE = calcsize("!i") -_SHORT_SIZE = calcsize("!H") -_LONG_SIZE = calcsize("!Q") -_FLOAT_SIZE = calcsize("!f") - -_sbyte_struct = Struct("!b") -_ushort_struct = Struct("!H") -_int_struct = Struct("!i") -_uint_struct = Struct("!I") -_long_struct = Struct("!q") -_float_struct = Struct("!f") - -pack_sbyte = _sbyte_struct.pack -pack_ushort = _ushort_struct.pack -pack_int = _int_struct.pack -pack_uint = _uint_struct.pack -pack_long = _long_struct.pack -pack_float = _float_struct.pack - -unpack_sbyte = _sbyte_struct.unpack -unpack_ushort = _ushort_struct.unpack -unpack_int = _int_struct.unpack -unpack_uint = _uint_struct.unpack -unpack_long = _long_struct.unpack -unpack_float = _float_struct.unpack - +#=============================================================================== +# Copyright 2007 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +import sys +from struct import Struct, calcsize + +IS_LITTLE = sys.byteorder == "little" + +_INT_SIZE = calcsize("!i") +_SHORT_SIZE = calcsize("!H") +_LONG_SIZE = calcsize("!Q") +_FLOAT_SIZE = calcsize("!f") + +_sbyte_struct = Struct("!b") +_ushort_struct = Struct("!H") +_int_struct = Struct("!i") +_uint_struct = Struct("!I") +_long_struct = Struct("!q") +_float_struct = Struct("!f") + +pack_sbyte = _sbyte_struct.pack +pack_ushort = _ushort_struct.pack +pack_int = _int_struct.pack +pack_uint = _uint_struct.pack +pack_long = _long_struct.pack +pack_float = _float_struct.pack + +unpack_sbyte = _sbyte_struct.unpack +unpack_ushort = _ushort_struct.unpack +unpack_int = _int_struct.unpack +unpack_uint = _uint_struct.unpack +unpack_long = _long_struct.unpack +unpack_float = _float_struct.unpack + diff --git a/lib/whoosh/util.py b/lib/whoosh/util.py index aacd65df..90888d87 100644 --- a/lib/whoosh/util.py +++ b/lib/whoosh/util.py @@ -1,423 +1,423 @@ -#=============================================================================== -# Copyright 2007 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -"""Miscellaneous utility functions and classes. -""" - -from array import array -from math import log -import codecs, re, sys, time - -from collections import deque, defaultdict -from copy import copy -from functools import wraps -from struct import pack, unpack - -from whoosh.system import IS_LITTLE - - -try: - from itertools import permutations -except ImportError: - # This function was only added to itertools in 2.6... - def permutations(iterable, r=None): - pool = tuple(iterable) - n = len(pool) - r = n if r is None else r - if r > n: - return - indices = range(n) - cycles = range(n, n-r, -1) - yield tuple(pool[i] for i in indices[:r]) - while n: - for i in reversed(range(r)): - cycles[i] -= 1 - if cycles[i] == 0: - indices[i:] = indices[i+1:] + indices[i:i+1] - cycles[i] = n - i - else: - j = cycles[i] - indices[i], indices[-j] = indices[-j], indices[i] - yield tuple(pool[i] for i in indices[:r]) - break - else: - return - - -if sys.platform == 'win32': - now = time.clock -else: - now = time.time - - -# Note: these functions return a tuple of (text, length), so when you call -# them, you have to add [0] on the end, e.g. str = utf8encode(unicode)[0] - -utf8encode = codecs.getencoder("utf_8") -utf8decode = codecs.getdecoder("utf_8") - - -# Functions - -def array_to_string(a): - if IS_LITTLE: - a = copy(a) - a.byteswap() - return a.tostring() - -def string_to_array(typecode, s): - a = array(typecode) - a.fromstring(s) - if IS_LITTLE: - a.byteswap() - return a - - -def make_binary_tree(fn, args, **kwargs): - """Takes a function/class that takes two positional arguments and a list of - arguments and returns a binary tree of instances. - - >>> make_binary_tree(UnionMatcher, [matcher1, matcher2, matcher3]) - UnionMatcher(matcher1, UnionMatcher(matcher2, matcher3)) - - Any keyword arguments given to this function are passed to the class - initializer. - """ - - count = len(args) - if not count: - raise ValueError("Called make_binary_tree with empty list") - elif count == 1: - return args[0] - - half = count // 2 - return fn(make_binary_tree(fn, args[:half], **kwargs), - make_binary_tree(fn, args[half:], **kwargs), **kwargs) - - -# Varint cache - -# Build a cache of the varint byte sequences for the first N integers, so we -# don't have to constantly recalculate them on the fly. This makes a small but -# noticeable difference. - -def _varint(i): - s = "" - while (i & ~0x7F) != 0: - s += chr((i & 0x7F) | 0x80) - i = i >> 7 - s += chr(i) - return s - -_varint_cache_size = 512 -_varint_cache = [] -for i in xrange(0, _varint_cache_size): - _varint_cache.append(_varint(i)) -_varint_cache = tuple(_varint_cache) - -def varint(i): - """Encodes the given integer into a string of the minimum number of bytes. - """ - if i < len(_varint_cache): - return _varint_cache[i] - return _varint(i) - -def varint_to_int(vi): - b = ord(vi[0]) - p = 1 - i = b & 0x7f - shift = 7 - while b & 0x80 != 0: - b = ord(vi[p]) - p += 1 - i |= (b & 0x7F) << shift - shift += 7 - return i - - -def signed_varint(i): - """Zig-zag encodes a signed integer into a varint. - """ - - if i >= 0: - return varint(i << 1) - return varint((i << 1) ^ (~0)) - -def decode_signed_varint(i): - """Zig-zag decodes an integer value. - """ - - if not i & 1: - return i >> 1 - return (i >> 1) ^ (~0) - - -def read_varint(readfn): - """ - Reads a variable-length encoded integer. - - :param readfn: a callable that reads a given number of bytes, - like file.read(). - """ - - b = ord(readfn(1)) - i = b & 0x7F - - shift = 7 - while b & 0x80 != 0: - b = ord(readfn(1)) - i |= (b & 0x7F) << shift - shift += 7 - return i - - -_fib_cache = {} -def fib(n): - """Returns the nth value in the Fibonacci sequence. - """ - - if n <= 2: return n - if n in _fib_cache: return _fib_cache[n] - result = fib(n - 1) + fib(n - 2) - _fib_cache[n] = result - return result - - -def float_to_byte(value, mantissabits=5, zeroexp=2): - """Encodes a floating point number in a single byte. - """ - - # Assume int size == float size - - fzero = (63 - zeroexp) << mantissabits - bits = unpack("i", pack("f", value))[0] - smallfloat = bits >> (24 - mantissabits) - if smallfloat < fzero: - # Map negative numbers and 0 to 0 - # Map underflow to next smallest non-zero number - if bits <= 0: - return chr(0) - else: - return chr(1) - elif smallfloat >= fzero + 0x100: - # Map overflow to largest number - return chr(255) - else: - return chr(smallfloat - fzero) - -def byte_to_float(b, mantissabits=5, zeroexp=2): - """Decodes a floating point number stored in a single byte. - """ - b = ord(b) - if b == 0: - return 0.0 - - bits = (b & 0xff) << (24 - mantissabits) - bits += (63 - zeroexp) << 24 - return unpack("f", pack("i", bits))[0] - - -# Length-to-byte approximation functions - -def length_to_byte(length): - """Returns a logarithmic approximation of the given number, in the range - 0-255. The approximation has high precision at the low end (e.g. - 1 -> 0, 2 -> 1, 3 -> 2 ...) and low precision at the high end. Numbers - equal to or greater than 108116 all approximate to 255. - - This is useful for storing field lengths, where the general case is small - documents and very large documents are more rare. - """ - - # This encoding formula works up to 108116 -> 255, so if the length is - # equal to or greater than that limit, just return 255. - if length >= 108116: return 255 - - # The parameters of this formula where chosen heuristically so that low - # numbers would approximate closely, and the byte range 0-255 would cover - # a decent range of document lengths (i.e. 1 to ~100000). - return int(round(log((length/27.0)+1, 1.033))) - -def _byte_to_length(n): - return int(round((pow(1.033, n)-1)*27)) - -_length_byte_cache = array("i", (_byte_to_length(i) for i in xrange(256))) -byte_to_length = _length_byte_cache.__getitem__ - -# Prefix encoding functions - -def first_diff(a, b): - """Returns the position of the first differing character in the strings - a and b. For example, first_diff('render', 'rending') == 4. This function - limits the return value to 255 so the difference can be encoded in a single - byte. - """ - - i = -1 - for i in xrange(0, len(a)): - if a[i] != b[1]: - return i - if i == 255: return i - - -def prefix_encode(a, b): - """Compresses string b as an integer (encoded in a byte) representing - the prefix it shares with a, followed by the suffix encoded as UTF-8. - """ - i = first_diff(a, b) - return chr(i) + b[i:].encode("utf8") - - -def prefix_encode_all(ls): - """Compresses the given list of (unicode) strings by storing each string - (except the first one) as an integer (encoded in a byte) representing - the prefix it shares with its predecessor, followed by the suffix encoded - as UTF-8. - """ - - last = u'' - for w in ls: - i = first_diff(last, w) - yield chr(i) + w[i:].encode("utf8") - last = w - -def prefix_decode_all(ls): - """Decompresses a list of strings compressed by prefix_encode(). - """ - - last = u'' - for w in ls: - i = ord(w[0]) - decoded = last[:i] + w[1:].decode("utf8") - yield decoded - last = decoded - - -_nkre = re.compile(r"\D+|\d+", re.UNICODE) -def _nkconv(i): - try: - return int(i) - except ValueError: - return i.lower() -def natural_key(s): - """Converts string ``s`` into a tuple that will sort "naturally" (i.e., - ``name5`` will come before ``name10`` and ``1`` will come before ``A``). - This function is designed to be used as the ``key`` argument to sorting - functions. - - :param s: the str/unicode string to convert. - :rtype: tuple - """ - - # Use _nkre to split the input string into a sequence of - # digit runs and non-digit runs. Then use _nkconv() to convert - # the digit runs into ints and the non-digit runs to lowercase. - return tuple(_nkconv(m) for m in _nkre.findall(s)) - - -class ClosableMixin(object): - """Mix-in for classes with a close() method to allow them to be used as a - context manager. - """ - - def __enter__(self): - return self - - def __exit__(self, *exc_info): - self.close() - - -def protected(func): - """Decorator for storage-access methods. This decorator (a) checks if the - object has already been closed, and (b) synchronizes on a threading lock. - The parent object must have 'is_closed' and '_sync_lock' attributes. - """ - - @wraps(func) - def protected_wrapper(self, *args, **kwargs): - if self.is_closed: - raise Exception("%r has been closed" % self) - if self._sync_lock.acquire(False): - try: - return func(self, *args, **kwargs) - finally: - self._sync_lock.release() - else: - raise Exception("Could not acquire sync lock") - - return protected_wrapper - - -class LRUCache(object): - def __init__(self, size): - self.size = size - self.clock = [] - for i in xrange(0, size): - self.clock.append([None, False]) - self.hand = 0 - self.data = {} - - def __contains__(self, key): - return key in self.data - - def __getitem__(self, key): - pos, val = self.data[key] - self.clock[pos][1] = True - self.hand = (pos + 1) % self.size - return val - - def __setitem__(self, key, val): - size = self.size - hand = self.hand - clock = self.clock - data = self.data - - end = (hand or size) - 1 - while True: - current = clock[hand] - ref = current[1] - if ref: - current[1] = False - hand = (hand + 1) % size - elif ref is False or hand == end: - oldkey = current[0] - if oldkey in data: - del data[oldkey] - current[0] = key - current[1] = True - data[key] = (hand, val) - hand = (hand + 1) % size - self.hand = hand - return - - - - - - - - - - - - - - - - - - - +#=============================================================================== +# Copyright 2007 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +"""Miscellaneous utility functions and classes. +""" + +from array import array +from math import log +import codecs, re, sys, time + +from collections import deque, defaultdict +from copy import copy +from functools import wraps +from struct import pack, unpack + +from whoosh.system import IS_LITTLE + + +try: + from itertools import permutations +except ImportError: + # This function was only added to itertools in 2.6... + def permutations(iterable, r=None): + pool = tuple(iterable) + n = len(pool) + r = n if r is None else r + if r > n: + return + indices = range(n) + cycles = range(n, n-r, -1) + yield tuple(pool[i] for i in indices[:r]) + while n: + for i in reversed(range(r)): + cycles[i] -= 1 + if cycles[i] == 0: + indices[i:] = indices[i+1:] + indices[i:i+1] + cycles[i] = n - i + else: + j = cycles[i] + indices[i], indices[-j] = indices[-j], indices[i] + yield tuple(pool[i] for i in indices[:r]) + break + else: + return + + +if sys.platform == 'win32': + now = time.clock +else: + now = time.time + + +# Note: these functions return a tuple of (text, length), so when you call +# them, you have to add [0] on the end, e.g. str = utf8encode(unicode)[0] + +utf8encode = codecs.getencoder("utf_8") +utf8decode = codecs.getdecoder("utf_8") + + +# Functions + +def array_to_string(a): + if IS_LITTLE: + a = copy(a) + a.byteswap() + return a.tostring() + +def string_to_array(typecode, s): + a = array(typecode) + a.fromstring(s) + if IS_LITTLE: + a.byteswap() + return a + + +def make_binary_tree(fn, args, **kwargs): + """Takes a function/class that takes two positional arguments and a list of + arguments and returns a binary tree of instances. + + >>> make_binary_tree(UnionMatcher, [matcher1, matcher2, matcher3]) + UnionMatcher(matcher1, UnionMatcher(matcher2, matcher3)) + + Any keyword arguments given to this function are passed to the class + initializer. + """ + + count = len(args) + if not count: + raise ValueError("Called make_binary_tree with empty list") + elif count == 1: + return args[0] + + half = count // 2 + return fn(make_binary_tree(fn, args[:half], **kwargs), + make_binary_tree(fn, args[half:], **kwargs), **kwargs) + + +# Varint cache + +# Build a cache of the varint byte sequences for the first N integers, so we +# don't have to constantly recalculate them on the fly. This makes a small but +# noticeable difference. + +def _varint(i): + s = "" + while (i & ~0x7F) != 0: + s += chr((i & 0x7F) | 0x80) + i = i >> 7 + s += chr(i) + return s + +_varint_cache_size = 512 +_varint_cache = [] +for i in xrange(0, _varint_cache_size): + _varint_cache.append(_varint(i)) +_varint_cache = tuple(_varint_cache) + +def varint(i): + """Encodes the given integer into a string of the minimum number of bytes. + """ + if i < len(_varint_cache): + return _varint_cache[i] + return _varint(i) + +def varint_to_int(vi): + b = ord(vi[0]) + p = 1 + i = b & 0x7f + shift = 7 + while b & 0x80 != 0: + b = ord(vi[p]) + p += 1 + i |= (b & 0x7F) << shift + shift += 7 + return i + + +def signed_varint(i): + """Zig-zag encodes a signed integer into a varint. + """ + + if i >= 0: + return varint(i << 1) + return varint((i << 1) ^ (~0)) + +def decode_signed_varint(i): + """Zig-zag decodes an integer value. + """ + + if not i & 1: + return i >> 1 + return (i >> 1) ^ (~0) + + +def read_varint(readfn): + """ + Reads a variable-length encoded integer. + + :param readfn: a callable that reads a given number of bytes, + like file.read(). + """ + + b = ord(readfn(1)) + i = b & 0x7F + + shift = 7 + while b & 0x80 != 0: + b = ord(readfn(1)) + i |= (b & 0x7F) << shift + shift += 7 + return i + + +_fib_cache = {} +def fib(n): + """Returns the nth value in the Fibonacci sequence. + """ + + if n <= 2: return n + if n in _fib_cache: return _fib_cache[n] + result = fib(n - 1) + fib(n - 2) + _fib_cache[n] = result + return result + + +def float_to_byte(value, mantissabits=5, zeroexp=2): + """Encodes a floating point number in a single byte. + """ + + # Assume int size == float size + + fzero = (63 - zeroexp) << mantissabits + bits = unpack("i", pack("f", value))[0] + smallfloat = bits >> (24 - mantissabits) + if smallfloat < fzero: + # Map negative numbers and 0 to 0 + # Map underflow to next smallest non-zero number + if bits <= 0: + return chr(0) + else: + return chr(1) + elif smallfloat >= fzero + 0x100: + # Map overflow to largest number + return chr(255) + else: + return chr(smallfloat - fzero) + +def byte_to_float(b, mantissabits=5, zeroexp=2): + """Decodes a floating point number stored in a single byte. + """ + b = ord(b) + if b == 0: + return 0.0 + + bits = (b & 0xff) << (24 - mantissabits) + bits += (63 - zeroexp) << 24 + return unpack("f", pack("i", bits))[0] + + +# Length-to-byte approximation functions + +def length_to_byte(length): + """Returns a logarithmic approximation of the given number, in the range + 0-255. The approximation has high precision at the low end (e.g. + 1 -> 0, 2 -> 1, 3 -> 2 ...) and low precision at the high end. Numbers + equal to or greater than 108116 all approximate to 255. + + This is useful for storing field lengths, where the general case is small + documents and very large documents are more rare. + """ + + # This encoding formula works up to 108116 -> 255, so if the length is + # equal to or greater than that limit, just return 255. + if length >= 108116: return 255 + + # The parameters of this formula where chosen heuristically so that low + # numbers would approximate closely, and the byte range 0-255 would cover + # a decent range of document lengths (i.e. 1 to ~100000). + return int(round(log((length/27.0)+1, 1.033))) + +def _byte_to_length(n): + return int(round((pow(1.033, n)-1)*27)) + +_length_byte_cache = array("i", (_byte_to_length(i) for i in xrange(256))) +byte_to_length = _length_byte_cache.__getitem__ + +# Prefix encoding functions + +def first_diff(a, b): + """Returns the position of the first differing character in the strings + a and b. For example, first_diff('render', 'rending') == 4. This function + limits the return value to 255 so the difference can be encoded in a single + byte. + """ + + i = -1 + for i in xrange(0, len(a)): + if a[i] != b[1]: + return i + if i == 255: return i + + +def prefix_encode(a, b): + """Compresses string b as an integer (encoded in a byte) representing + the prefix it shares with a, followed by the suffix encoded as UTF-8. + """ + i = first_diff(a, b) + return chr(i) + b[i:].encode("utf8") + + +def prefix_encode_all(ls): + """Compresses the given list of (unicode) strings by storing each string + (except the first one) as an integer (encoded in a byte) representing + the prefix it shares with its predecessor, followed by the suffix encoded + as UTF-8. + """ + + last = u'' + for w in ls: + i = first_diff(last, w) + yield chr(i) + w[i:].encode("utf8") + last = w + +def prefix_decode_all(ls): + """Decompresses a list of strings compressed by prefix_encode(). + """ + + last = u'' + for w in ls: + i = ord(w[0]) + decoded = last[:i] + w[1:].decode("utf8") + yield decoded + last = decoded + + +_nkre = re.compile(r"\D+|\d+", re.UNICODE) +def _nkconv(i): + try: + return int(i) + except ValueError: + return i.lower() +def natural_key(s): + """Converts string ``s`` into a tuple that will sort "naturally" (i.e., + ``name5`` will come before ``name10`` and ``1`` will come before ``A``). + This function is designed to be used as the ``key`` argument to sorting + functions. + + :param s: the str/unicode string to convert. + :rtype: tuple + """ + + # Use _nkre to split the input string into a sequence of + # digit runs and non-digit runs. Then use _nkconv() to convert + # the digit runs into ints and the non-digit runs to lowercase. + return tuple(_nkconv(m) for m in _nkre.findall(s)) + + +class ClosableMixin(object): + """Mix-in for classes with a close() method to allow them to be used as a + context manager. + """ + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.close() + + +def protected(func): + """Decorator for storage-access methods. This decorator (a) checks if the + object has already been closed, and (b) synchronizes on a threading lock. + The parent object must have 'is_closed' and '_sync_lock' attributes. + """ + + @wraps(func) + def protected_wrapper(self, *args, **kwargs): + if self.is_closed: + raise Exception("%r has been closed" % self) + if self._sync_lock.acquire(False): + try: + return func(self, *args, **kwargs) + finally: + self._sync_lock.release() + else: + raise Exception("Could not acquire sync lock") + + return protected_wrapper + + +class LRUCache(object): + def __init__(self, size): + self.size = size + self.clock = [] + for i in xrange(0, size): + self.clock.append([None, False]) + self.hand = 0 + self.data = {} + + def __contains__(self, key): + return key in self.data + + def __getitem__(self, key): + pos, val = self.data[key] + self.clock[pos][1] = True + self.hand = (pos + 1) % self.size + return val + + def __setitem__(self, key, val): + size = self.size + hand = self.hand + clock = self.clock + data = self.data + + end = (hand or size) - 1 + while True: + current = clock[hand] + ref = current[1] + if ref: + current[1] = False + hand = (hand + 1) % size + elif ref is False or hand == end: + oldkey = current[0] + if oldkey in data: + del data[oldkey] + current[0] = key + current[1] = True + data[key] = (hand, val) + hand = (hand + 1) % size + self.hand = hand + return + + + + + + + + + + + + + + + + + + + diff --git a/lib/whoosh/writing.py b/lib/whoosh/writing.py index 14bb4079..4943f5fc 100644 --- a/lib/whoosh/writing.py +++ b/lib/whoosh/writing.py @@ -1,398 +1,398 @@ -#=============================================================================== -# Copyright 2007 Matt Chaput -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#=============================================================================== - -import threading, time - -from whoosh.store import LockError - -# Exceptions - -class IndexingError(Exception): - pass - - -# Base class - -class IndexWriter(object): - """High-level object for writing to an index. - - To get a writer for a particular index, call - :meth:`~whoosh.index.Index.writer` on the Index object. - - >>> writer = my_index.writer() - - You can use this object as a context manager. If an exception is thrown - from within the context it calls cancel(), otherwise it calls commit() when - the context exits. - """ - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - if exc_type: - self.cancel() - else: - self.commit() - - def add_field(self, fieldname, fieldtype, **kwargs): - """Adds a field to the index's schema. - - :param fieldname: the name of the field to add. - :param fieldtype: an instantiated :class:`whoosh.fields.FieldType` - object. - """ - - self.schema.add(fieldname, fieldtype, **kwargs) - - def remove_field(self, fieldname, **kwargs): - """Removes the named field from the index's schema. Depending on the - backend implementation, this may or may not actually remove existing - data for the field from the index. Optimizing the index should always - clear out existing data for a removed field. - """ - - self.schema.remove(fieldname, **kwargs) - - def searcher(self, **kwargs): - """Returns a searcher for the existing index. - """ - - raise NotImplementedError - - def delete_by_term(self, fieldname, text, searcher=None): - """Deletes any documents containing "term" in the "fieldname" field. - This is useful when you have an indexed field containing a unique ID - (such as "pathname") for each document. - - :returns: the number of documents deleted. - """ - - from whoosh.query import Term - q = Term(fieldname, text) - return self.delete_by_query(q, searcher=searcher) - - def delete_by_query(self, q, searcher=None): - """Deletes any documents matching a query object. - - :returns: the number of documents deleted. - """ - - if searcher: - s = searcher - else: - s = self.searcher() - - count = 0 - for docnum in q.docs(s): - if not self.is_deleted(docnum): - self.delete_document(docnum) - count += 1 - - if not searcher: - s.close() - - return count - - def delete_document(self, docnum, delete=True): - """Deletes a document by number. - """ - raise NotImplementedError - - def add_document(self, **fields): - """The keyword arguments map field names to the values to index/store. - - For fields that are both indexed and stored, you can specify an - alternate value to store using a keyword argument in the form - "_stored_". For example, if you have a field named "title" - and you want to index the text "a b c" but store the text "e f g", use - keyword arguments like this:: - - writer.add_document(title=u"a b c", _stored_title=u"e f g") - """ - raise NotImplementedError - - def update_document(self, **fields): - """The keyword arguments map field names to the values to index/store. - - Note that this method will only replace a *committed* document; - currently it cannot replace documents you've added to the IndexWriter - but haven't yet committed. For example, if you do this: - - >>> writer.update_document(unique_id=u"1", content=u"Replace me") - >>> writer.update_document(unique_id=u"1", content=u"Replacement") - - ...this will add two documents with the same value of ``unique_id``, - instead of the second document replacing the first. - - For fields that are both indexed and stored, you can specify an - alternate value to store using a keyword argument in the form - "_stored_". For example, if you have a field named "title" - and you want to index the text "a b c" but store the text "e f g", use - keyword arguments like this:: - - writer.update_document(title=u"a b c", _stored_title=u"e f g") - """ - - # Check which of the supplied fields are unique - unique_fields = [name for name, field in self.schema.items() - if name in fields and field.unique] - if not unique_fields: - raise IndexingError("None of the fields in %r" - " are unique" % fields.keys()) - - # Find the set of documents matching the unique terms - delset = set() - reader = self.searcher().reader() - for name in unique_fields: - field = self.schema[name] - text = field.to_text(fields[name]) - docnum = reader.postings(name, text).id() - delset.add(docnum) - reader.close() - - # Delete the old docs - for docnum in delset: - self.delete_document(docnum) - - # Add the given fields - self.add_document(**fields) - - def commit(self): - """Finishes writing and unlocks the index. - """ - pass - - def cancel(self): - """Cancels any documents/deletions added by this object - and unlocks the index. - """ - pass - - -class PostingWriter(object): - def start(self, format): - """Start a new set of postings for a new term. Implementations may - raise an exception if this is called without a corresponding call to - finish(). - """ - raise NotImplementedError - - def write(self, id, weight, valuestring): - """Add a posting with the given ID and value. - """ - raise NotImplementedError - - def finish(self): - """Finish writing the postings for the current term. Implementations - may raise an exception if this is called without a preceding call to - start(). - """ - pass - - def close(self): - """Finish writing all postings and close the underlying file. - """ - pass - - -class AsyncWriter(threading.Thread, IndexWriter): - """Convenience wrapper for a writer object that might fail due to locking - (i.e. the ``filedb`` writer). This object will attempt once to obtain the - underlying writer, and if it's successful, will simply pass method calls on - to it. - - If this object *can't* obtain a writer immediately, it will *buffer* - delete, add, and update method calls in memory until you call ``commit()``. - At that point, this object will start running in a separate thread, trying - to obtain the writer over and over, and once it obtains it, "replay" all - the buffered method calls on it. - - In a typical scenario where you're adding a single or a few documents to - the index as the result of a Web transaction, this lets you just create the - writer, add, and commit, without having to worry about index locks, - retries, etc. - - For example, to get an aynchronous writer, instead of this: - - >>> writer = myindex.writer(postlimitmb=128) - - Do this: - - >>> from whoosh.writing import AsyncWriter - >>> writer = AsyncWriter(myindex, ) - """ - - def __init__(self, index, delay=0.25, writerargs=None): - """ - :param index: the :class:`whoosh.index.Index` to write to. - :param delay: the delay (in seconds) between attempts to instantiate - the actual writer. - :param writerargs: an optional dictionary specifying keyword arguments - to to be passed to the index's ``writer()`` method. - """ - - threading.Thread.__init__(self) - self.running = False - self.index = index - self.writerargs = writerargs or {} - self.delay = delay - self.events = [] - try: - self.writer = self.index.writer(**self.writerargs) - except LockError: - self.writer = None - - def searcher(self): - return self.index.searcher() - - def _record(self, method, args, kwargs): - if self.writer: - getattr(self.writer, method)(*args, **kwargs) - else: - self.events.append((method, args, kwargs)) - - def run(self): - self.running = True - writer = self.writer - while writer is None: - try: - writer = self.writerfn(**self.writerargs) - except LockError: - time.sleep(self.delay) - for method, args, kwargs in self.events: - getattr(writer, method)(*args, **kwargs) - writer.commit(*self.commitargs, **self.commitkwargs) - - def delete_document(self, *args, **kwargs): - self._record("delete_document", args, kwargs) - - def add_document(self, *args, **kwargs): - self._record("add_document", args, kwargs) - - def update_document(self, *args, **kwargs): - self._record("update_document", args, kwargs) - - def add_field(self, *args, **kwargs): - self._record("add_field", args, kwargs) - - def remove_field(self, *args, **kwargs): - self._record("remove_field", args, kwargs) - - def delete_by_term(self, *args, **kwargs): - self._record("delete_by_term", args, kwargs) - - def commit(self, *args, **kwargs): - if self.writer: - self.writer.commit(*args, **kwargs) - else: - self.commitargs, self.commitkwargs = args, kwargs - self.start() - - def cancel(self, *args, **kwargs): - if self.writer: - self.writer.cancel(*args, **kwargs) - - -class BatchWriter(object): - """Convenience wrapper that batches up calls to ``add_document()``, - ``update_document()``, and/or ``delete_document()``, and commits them - whenever a maximum amount of time passes or a maximum number of batched - changes accumulate. - - This is useful when you're adding documents one at a time, in rapid - succession (e.g. a web app). The more documents you add per commit, the - more efficient Whoosh is. This class batches multiple documents and adds - them all at once. If you're adding a bunch of documents at a time, just use - a regular writer -- you're already committing a "batch" of documents, so - you don't need this class. - - In scenarios where you are continuously adding single documents very - rapidly (for example a web application where lots of users are adding - content simultaneously), and you don't mind a delay between documents being - added and becoming searchable, using a BatchWriter is *much* faster than - opening and committing a writer for each document you add. - - >>> from whoosh.writing import BatchWriter - >>> writer = BatchWriter(myindex) - - Calling ``commit()`` on this object opens a writer and commits any batched - up changes. You can continue to make changes after calling ``commit()``, - and you can call ``commit()`` multiple times. - - You should explicitly call ``commit()`` on this object before it goes out - of scope to make sure any uncommitted changes are saved. - """ - - def __init__(self, index, period=60, limit=10, writerargs=None, - commitargs=None): - """ - :param index: the :class:`whoosh.index.Index` to write to. - :param period: the maximum amount of time (in seconds) between commits. - :param limit: the maximum number of changes to accumulate before - committing. - :param writerargs: dictionary specifying keyword arguments to be passed - to the index's ``writer()`` method. - :param commitargs: dictionary specifying keyword arguments to be passed - to the writer's ``commit()`` method. - """ - self.index = index - self.period = period - self.limit = limit - self.writerargs = writerargs or {} - self.commitargs = commitargs or {} - - self.events = [] - self.timer = threading.Timer(self.period, self.commit) - - def __del__(self): - self.commit(restart=False) - - def commit(self, restart=True): - self.timer.cancel() - if self.events: - writer = self.index.writer(**self.writerargs) - for method, args, kwargs in self.events: - getattr(writer, method)(*args, **kwargs) - writer.commit(**self.commitargs) - self.events = [] - - if restart: - self.timer = threading.Timer(self.period, self.commit) - - def _record(self, method, args, kwargs): - self.events.append((method, args, kwargs)) - if len(self.events) >= self.limit: - self.commit() - - def delete_document(self, *args, **kwargs): - self._record("delete_document", args, kwargs) - - def add_document(self, *args, **kwargs): - self._record("add_document", args, kwargs) - - def update_document(self, *args, **kwargs): - self._record("update_document", args, kwargs) - - def add_field(self, *args, **kwargs): - self._record("add_field", args, kwargs) - - def remove_field(self, *args, **kwargs): - self._record("remove_field", args, kwargs) - - - - +#=============================================================================== +# Copyright 2007 Matt Chaput +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#=============================================================================== + +import threading, time + +from whoosh.store import LockError + +# Exceptions + +class IndexingError(Exception): + pass + + +# Base class + +class IndexWriter(object): + """High-level object for writing to an index. + + To get a writer for a particular index, call + :meth:`~whoosh.index.Index.writer` on the Index object. + + >>> writer = my_index.writer() + + You can use this object as a context manager. If an exception is thrown + from within the context it calls cancel(), otherwise it calls commit() when + the context exits. + """ + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type: + self.cancel() + else: + self.commit() + + def add_field(self, fieldname, fieldtype, **kwargs): + """Adds a field to the index's schema. + + :param fieldname: the name of the field to add. + :param fieldtype: an instantiated :class:`whoosh.fields.FieldType` + object. + """ + + self.schema.add(fieldname, fieldtype, **kwargs) + + def remove_field(self, fieldname, **kwargs): + """Removes the named field from the index's schema. Depending on the + backend implementation, this may or may not actually remove existing + data for the field from the index. Optimizing the index should always + clear out existing data for a removed field. + """ + + self.schema.remove(fieldname, **kwargs) + + def searcher(self, **kwargs): + """Returns a searcher for the existing index. + """ + + raise NotImplementedError + + def delete_by_term(self, fieldname, text, searcher=None): + """Deletes any documents containing "term" in the "fieldname" field. + This is useful when you have an indexed field containing a unique ID + (such as "pathname") for each document. + + :returns: the number of documents deleted. + """ + + from whoosh.query import Term + q = Term(fieldname, text) + return self.delete_by_query(q, searcher=searcher) + + def delete_by_query(self, q, searcher=None): + """Deletes any documents matching a query object. + + :returns: the number of documents deleted. + """ + + if searcher: + s = searcher + else: + s = self.searcher() + + count = 0 + for docnum in q.docs(s): + if not self.is_deleted(docnum): + self.delete_document(docnum) + count += 1 + + if not searcher: + s.close() + + return count + + def delete_document(self, docnum, delete=True): + """Deletes a document by number. + """ + raise NotImplementedError + + def add_document(self, **fields): + """The keyword arguments map field names to the values to index/store. + + For fields that are both indexed and stored, you can specify an + alternate value to store using a keyword argument in the form + "_stored_". For example, if you have a field named "title" + and you want to index the text "a b c" but store the text "e f g", use + keyword arguments like this:: + + writer.add_document(title=u"a b c", _stored_title=u"e f g") + """ + raise NotImplementedError + + def update_document(self, **fields): + """The keyword arguments map field names to the values to index/store. + + Note that this method will only replace a *committed* document; + currently it cannot replace documents you've added to the IndexWriter + but haven't yet committed. For example, if you do this: + + >>> writer.update_document(unique_id=u"1", content=u"Replace me") + >>> writer.update_document(unique_id=u"1", content=u"Replacement") + + ...this will add two documents with the same value of ``unique_id``, + instead of the second document replacing the first. + + For fields that are both indexed and stored, you can specify an + alternate value to store using a keyword argument in the form + "_stored_". For example, if you have a field named "title" + and you want to index the text "a b c" but store the text "e f g", use + keyword arguments like this:: + + writer.update_document(title=u"a b c", _stored_title=u"e f g") + """ + + # Check which of the supplied fields are unique + unique_fields = [name for name, field in self.schema.items() + if name in fields and field.unique] + if not unique_fields: + raise IndexingError("None of the fields in %r" + " are unique" % fields.keys()) + + # Find the set of documents matching the unique terms + delset = set() + reader = self.searcher().reader() + for name in unique_fields: + field = self.schema[name] + text = field.to_text(fields[name]) + docnum = reader.postings(name, text).id() + delset.add(docnum) + reader.close() + + # Delete the old docs + for docnum in delset: + self.delete_document(docnum) + + # Add the given fields + self.add_document(**fields) + + def commit(self): + """Finishes writing and unlocks the index. + """ + pass + + def cancel(self): + """Cancels any documents/deletions added by this object + and unlocks the index. + """ + pass + + +class PostingWriter(object): + def start(self, format): + """Start a new set of postings for a new term. Implementations may + raise an exception if this is called without a corresponding call to + finish(). + """ + raise NotImplementedError + + def write(self, id, weight, valuestring): + """Add a posting with the given ID and value. + """ + raise NotImplementedError + + def finish(self): + """Finish writing the postings for the current term. Implementations + may raise an exception if this is called without a preceding call to + start(). + """ + pass + + def close(self): + """Finish writing all postings and close the underlying file. + """ + pass + + +class AsyncWriter(threading.Thread, IndexWriter): + """Convenience wrapper for a writer object that might fail due to locking + (i.e. the ``filedb`` writer). This object will attempt once to obtain the + underlying writer, and if it's successful, will simply pass method calls on + to it. + + If this object *can't* obtain a writer immediately, it will *buffer* + delete, add, and update method calls in memory until you call ``commit()``. + At that point, this object will start running in a separate thread, trying + to obtain the writer over and over, and once it obtains it, "replay" all + the buffered method calls on it. + + In a typical scenario where you're adding a single or a few documents to + the index as the result of a Web transaction, this lets you just create the + writer, add, and commit, without having to worry about index locks, + retries, etc. + + For example, to get an aynchronous writer, instead of this: + + >>> writer = myindex.writer(postlimitmb=128) + + Do this: + + >>> from whoosh.writing import AsyncWriter + >>> writer = AsyncWriter(myindex, ) + """ + + def __init__(self, index, delay=0.25, writerargs=None): + """ + :param index: the :class:`whoosh.index.Index` to write to. + :param delay: the delay (in seconds) between attempts to instantiate + the actual writer. + :param writerargs: an optional dictionary specifying keyword arguments + to to be passed to the index's ``writer()`` method. + """ + + threading.Thread.__init__(self) + self.running = False + self.index = index + self.writerargs = writerargs or {} + self.delay = delay + self.events = [] + try: + self.writer = self.index.writer(**self.writerargs) + except LockError: + self.writer = None + + def searcher(self): + return self.index.searcher() + + def _record(self, method, args, kwargs): + if self.writer: + getattr(self.writer, method)(*args, **kwargs) + else: + self.events.append((method, args, kwargs)) + + def run(self): + self.running = True + writer = self.writer + while writer is None: + try: + writer = self.writerfn(**self.writerargs) + except LockError: + time.sleep(self.delay) + for method, args, kwargs in self.events: + getattr(writer, method)(*args, **kwargs) + writer.commit(*self.commitargs, **self.commitkwargs) + + def delete_document(self, *args, **kwargs): + self._record("delete_document", args, kwargs) + + def add_document(self, *args, **kwargs): + self._record("add_document", args, kwargs) + + def update_document(self, *args, **kwargs): + self._record("update_document", args, kwargs) + + def add_field(self, *args, **kwargs): + self._record("add_field", args, kwargs) + + def remove_field(self, *args, **kwargs): + self._record("remove_field", args, kwargs) + + def delete_by_term(self, *args, **kwargs): + self._record("delete_by_term", args, kwargs) + + def commit(self, *args, **kwargs): + if self.writer: + self.writer.commit(*args, **kwargs) + else: + self.commitargs, self.commitkwargs = args, kwargs + self.start() + + def cancel(self, *args, **kwargs): + if self.writer: + self.writer.cancel(*args, **kwargs) + + +class BatchWriter(object): + """Convenience wrapper that batches up calls to ``add_document()``, + ``update_document()``, and/or ``delete_document()``, and commits them + whenever a maximum amount of time passes or a maximum number of batched + changes accumulate. + + This is useful when you're adding documents one at a time, in rapid + succession (e.g. a web app). The more documents you add per commit, the + more efficient Whoosh is. This class batches multiple documents and adds + them all at once. If you're adding a bunch of documents at a time, just use + a regular writer -- you're already committing a "batch" of documents, so + you don't need this class. + + In scenarios where you are continuously adding single documents very + rapidly (for example a web application where lots of users are adding + content simultaneously), and you don't mind a delay between documents being + added and becoming searchable, using a BatchWriter is *much* faster than + opening and committing a writer for each document you add. + + >>> from whoosh.writing import BatchWriter + >>> writer = BatchWriter(myindex) + + Calling ``commit()`` on this object opens a writer and commits any batched + up changes. You can continue to make changes after calling ``commit()``, + and you can call ``commit()`` multiple times. + + You should explicitly call ``commit()`` on this object before it goes out + of scope to make sure any uncommitted changes are saved. + """ + + def __init__(self, index, period=60, limit=10, writerargs=None, + commitargs=None): + """ + :param index: the :class:`whoosh.index.Index` to write to. + :param period: the maximum amount of time (in seconds) between commits. + :param limit: the maximum number of changes to accumulate before + committing. + :param writerargs: dictionary specifying keyword arguments to be passed + to the index's ``writer()`` method. + :param commitargs: dictionary specifying keyword arguments to be passed + to the writer's ``commit()`` method. + """ + self.index = index + self.period = period + self.limit = limit + self.writerargs = writerargs or {} + self.commitargs = commitargs or {} + + self.events = [] + self.timer = threading.Timer(self.period, self.commit) + + def __del__(self): + self.commit(restart=False) + + def commit(self, restart=True): + self.timer.cancel() + if self.events: + writer = self.index.writer(**self.writerargs) + for method, args, kwargs in self.events: + getattr(writer, method)(*args, **kwargs) + writer.commit(**self.commitargs) + self.events = [] + + if restart: + self.timer = threading.Timer(self.period, self.commit) + + def _record(self, method, args, kwargs): + self.events.append((method, args, kwargs)) + if len(self.events) >= self.limit: + self.commit() + + def delete_document(self, *args, **kwargs): + self._record("delete_document", args, kwargs) + + def add_document(self, *args, **kwargs): + self._record("add_document", args, kwargs) + + def update_document(self, *args, **kwargs): + self._record("update_document", args, kwargs) + + def add_field(self, *args, **kwargs): + self._record("add_field", args, kwargs) + + def remove_field(self, *args, **kwargs): + self._record("remove_field", args, kwargs) + + + + diff --git a/setup_macosx.py b/setup_macosx.py index 8387b80e..712f84ec 100644 --- a/setup_macosx.py +++ b/setup_macosx.py @@ -1,41 +1,41 @@ -""" -This is a setup.py script generated by py2applet - -No guarantees for working! - -Usage: - python setup_macosx.py py2app -""" - -from setuptools import setup -import os -from glob import glob - -APP = ['WikidPad.py'] -OPTIONS = {'argv_emulation': True, 'iconfile': '/Applications/WikidPad-2/WikidPad.icns'} - -DATA_FILES = [('icons', glob(os.path.join('icons', '*.*'))), -# ('lib', glob('sql_mar.*')), - ('extensions', glob('extensions/*.*')), - ('extensions/wikidPadParser', glob('extensions/wikidPadParser/*.*')), - ('', ['WikidPad.xrc', 'readme_Wic.txt', 'gadfly.zip', - 'langlist.txt', 'appbase.css'] + glob('WikidPad_*.po')), - ('WikidPadHelp', glob(os.path.join('WikidPadHelp', '*.wiki'))), - (os.path.join('WikidPadHelp', 'data'), - glob(os.path.join('WikidPadHelp', 'data', '*.*'))), - (os.path.join('WikidPadHelp', 'files'), - glob(os.path.join('WikidPadHelp', 'files', '*.*'))), - ('export', [])] - -setup( - app=APP, - data_files=DATA_FILES, - options={'py2app': OPTIONS}, - setup_requires=['py2app'], - package_dir={'': 'lib'}, - packages= ['pwiki', 'pwiki.wikidata', 'pwiki.wikidata.compact_sqlite', - 'pwiki.wikidata.original_gadfly', - 'pwiki.wikidata.original_sqlite', 'pwiki.timeView', - 'pwiki.rtlibRepl'], -# py_modules=['encodings.utf_8', 'encodings.latin_1'], -) +""" +This is a setup.py script generated by py2applet + +No guarantees for working! + +Usage: + python setup_macosx.py py2app +""" + +from setuptools import setup +import os +from glob import glob + +APP = ['WikidPad.py'] +OPTIONS = {'argv_emulation': True, 'iconfile': '/Applications/WikidPad-2/WikidPad.icns'} + +DATA_FILES = [('icons', glob(os.path.join('icons', '*.*'))), +# ('lib', glob('sql_mar.*')), + ('extensions', glob('extensions/*.*')), + ('extensions/wikidPadParser', glob('extensions/wikidPadParser/*.*')), + ('', ['WikidPad.xrc', 'readme_Wic.txt', 'gadfly.zip', + 'langlist.txt', 'appbase.css'] + glob('WikidPad_*.po')), + ('WikidPadHelp', glob(os.path.join('WikidPadHelp', '*.wiki'))), + (os.path.join('WikidPadHelp', 'data'), + glob(os.path.join('WikidPadHelp', 'data', '*.*'))), + (os.path.join('WikidPadHelp', 'files'), + glob(os.path.join('WikidPadHelp', 'files', '*.*'))), + ('export', [])] + +setup( + app=APP, + data_files=DATA_FILES, + options={'py2app': OPTIONS}, + setup_requires=['py2app'], + package_dir={'': 'lib'}, + packages= ['pwiki', 'pwiki.wikidata', 'pwiki.wikidata.compact_sqlite', + 'pwiki.wikidata.original_gadfly', + 'pwiki.wikidata.original_sqlite', 'pwiki.timeView', + 'pwiki.rtlibRepl'], +# py_modules=['encodings.utf_8', 'encodings.latin_1'], +) diff --git a/wikidpad_unicode.iss b/wikidpad_unicode.iss index b61b0613..a0823458 100644 --- a/wikidpad_unicode.iss +++ b/wikidpad_unicode.iss @@ -8,16 +8,17 @@ Source: dist\WikidPadHelp\*; DestDir: {app}\WikidPadHelp\; Flags: recursesubdirs Source: dist\license.txt; DestDir: {app} Source: dist\python26.dll; DestDir: {app} Source: dist\sqlite3.dll; DestDir: {app} +Source: dist\wxbase28uh_net_vc.dll; DestDir: {app}; Check: ShouldInstallWxRuntime +Source: dist\wxbase28uh_vc.dll; DestDir: {app}; Check: ShouldInstallWxRuntime +Source: dist\wxbase28uh_xml_vc.dll; DestDir: {app}; Check: ShouldInstallWxRuntime Source: dist\wxmsw28uh_adv_vc.dll; DestDir: {app}; Check: ShouldInstallWxRuntime Source: dist\wxmsw28uh_core_vc.dll; DestDir: {app}; Check: ShouldInstallWxRuntime Source: dist\wxmsw28uh_html_vc.dll; DestDir: {app}; Check: ShouldInstallWxRuntime Source: dist\wxmsw28uh_stc_vc.dll; DestDir: {app}; Check: ShouldInstallWxRuntime Source: dist\wxmsw28uh_xrc_vc.dll; DestDir: {app}; Check: ShouldInstallWxRuntime -Source: dist\wxbase28uh_net_vc.dll; DestDir: {app}; Check: ShouldInstallWxRuntime -Source: dist\wxbase28uh_vc.dll; DestDir: {app}; Check: ShouldInstallWxRuntime -Source: dist\wxbase28uh_xml_vc.dll; DestDir: {app}; Check: ShouldInstallWxRuntime Source: dist\_ctypes.pyd; DestDir: {app}; Flags: replacesameversion ignoreversion Source: dist\_hashlib.pyd; DestDir: {app}; Flags: replacesameversion ignoreversion +Source: dist\_multiprocessing.pyd; DestDir: {app}; Flags: replacesameversion ignoreversion Source: dist\pyexpat.pyd; DestDir: {app}; Flags: replacesameversion ignoreversion Source: dist\_sqlite3.pyd; DestDir: {app}; Flags: replacesameversion ignoreversion Source: dist\_socket.pyd; DestDir: {app}; Flags: replacesameversion ignoreversion @@ -50,8 +51,8 @@ Name: {app}\WikidPadHelp\data Name: {app}\WikidPadHelp\files Name: {app}\export [Setup] -#define verStr "2.2beta04_2" -#define verNo "002.002.104.002" +#define verStr "2.2beta05" +#define verNo "002.002.105.000" SolidCompression=true AppName=WikidPad