├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── pyproject.toml ├── src └── tokenizer │ ├── Abbrev.conf │ ├── __init__.py │ ├── abbrev.py │ ├── definitions.py │ ├── main.py │ ├── py.typed │ └── tokenizer.py └── test ├── Overview.txt ├── example.txt ├── test_abbrev.py ├── test_cli.py ├── test_detokenize.py ├── test_helper_functions.py ├── test_index_calculation.py ├── test_tokenizer.py ├── test_tokenizer_tok.py ├── toktest_edgecases.txt ├── toktest_edgecases_diff.txt ├── toktest_edgecases_gold_expected.txt ├── toktest_large.txt ├── toktest_large_gold_acceptable.txt ├── toktest_large_gold_perfect.txt ├── toktest_normal.txt ├── toktest_normal_gold_expected.txt └── toktest_sentences.txt /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' # Run on all branches 7 | pull_request: 8 | branches: 9 | - '*' # Run on all branches 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest] 18 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.9", "pypy-3.10"] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | 28 | - name: Install Tokenizer 29 | run: | 30 | python -m pip install --upgrade uv 31 | uv pip install --system --upgrade wheel setuptools 32 | uv pip install --system ".[dev]" 33 | 34 | - name: Type check with mypy (only on oldest supported Python version) 35 | run: | 36 | if [ "${{ matrix.python-version }}" == "3.9" ]; then python -m pip install mypy; fi 37 | if [ "${{ matrix.python-version }}" == "3.9" ]; then mypy --python-version=3.9 src/tokenizer; fi 38 | 39 | - name: Test with pytest 40 | run: | 41 | python -m pytest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | .vscode/ 12 | p2/ 13 | p3/ 14 | env/ 15 | venv/ 16 | python2.7/ 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | # The doc directory is currently only used for temporary stuff 21 | doc/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Sublime and VSCode 42 | *.sublime-project 43 | *.sublime-workspace 44 | *.code-workspace 45 | 46 | # Installer logs 47 | pip-log.txt 48 | pip-delete-this-directory.txt 49 | 50 | # Unit test / coverage reports 51 | htmlcov/ 52 | .tox/ 53 | .coverage 54 | .coverage.* 55 | .cache 56 | .pytest_cache/ 57 | nosetests.xml 58 | coverage.xml 59 | *.cover 60 | .hypothesis/ 61 | tokout.txt 62 | test/toktest_edgecases_out.txt 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Django stuff: 69 | *.log 70 | local_settings.py 71 | 72 | # Flask stuff: 73 | instance/ 74 | .webassets-cache 75 | 76 | # Scrapy stuff: 77 | .scrapy 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # PyBuilder 83 | target/ 84 | 85 | # Jupyter Notebook 86 | .ipynb_checkpoints 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # celery beat schedule file 92 | celerybeat-schedule 93 | 94 | # SageMath parsed files 95 | *.sage.py 96 | 97 | # dotenv 98 | .env 99 | 100 | # virtualenv 101 | .venv 102 | venv 103 | ENV/ 104 | p39 105 | p310 106 | p311 107 | p312 108 | p313 109 | 110 | # Spyder project settings 111 | .spyderproject 112 | .spyproject 113 | 114 | # Rope project settings 115 | .ropeproject 116 | 117 | # mkdocs documentation 118 | /site 119 | 120 | # mypy 121 | .mypy_cache/ 122 | mypy.ini 123 | 124 | # Vim swap files 125 | .*.swp 126 | 127 | .DS_Store 128 | 129 | # Sveinbjörn's temp files 130 | test.py 131 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2016-2025 Miðeind ehf. 4 | Original author: Vilhjálmur Þorsteinsson 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft src 2 | prune src/tokenizer/__pycache__ 3 | prune src/tokenizer/.mypy_cache 4 | prune src/tokenizer/.DS_Store -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "tokenizer" 3 | version = "3.4.5" 4 | description = "A tokenizer for Icelandic text" 5 | authors = [{ name = "Miðeind ehf.", email = "mideind@mideind.is" }] 6 | readme = { file = "README.rst", content-type = "text/x-rst" } 7 | license = { text = "MIT" } 8 | classifiers = [ 9 | "Development Status :: 5 - Production/Stable", 10 | "Intended Audience :: Developers", 11 | "License :: OSI Approved :: MIT License", 12 | "Operating System :: Unix", 13 | "Operating System :: POSIX", 14 | "Operating System :: MacOS", 15 | "Operating System :: Microsoft :: Windows", 16 | "Natural Language :: Icelandic", 17 | "Programming Language :: Python", 18 | "Programming Language :: Python :: 3", 19 | "Programming Language :: Python :: 3.9", 20 | "Programming Language :: Python :: 3.10", 21 | "Programming Language :: Python :: 3.11", 22 | "Programming Language :: Python :: 3.12", 23 | "Programming Language :: Python :: 3.13", 24 | "Programming Language :: Python :: Implementation :: CPython", 25 | "Programming Language :: Python :: Implementation :: PyPy", 26 | "Topic :: Software Development :: Libraries :: Python Modules", 27 | "Topic :: Utilities", 28 | "Topic :: Text Processing :: Linguistic", 29 | ] 30 | requires-python = ">=3.9" 31 | 32 | [project.urls] 33 | Repository = "https://github.com/mideind/Tokenizer" 34 | 35 | [project.optional-dependencies] 36 | # dev dependencies 37 | dev = ["pytest"] 38 | 39 | [project.scripts] 40 | # 'tokenize' command line tool 41 | tokenize = "tokenizer.main:main" 42 | 43 | # *** Configuration of tools *** 44 | 45 | [tool.setuptools.packages.find] 46 | where = ["src"] 47 | 48 | [tool.setuptools.package-data] 49 | where = ["src"] 50 | 51 | [tool.pytest.ini_options] 52 | filterwarnings = [ 53 | # Ignore deprecation warnings in libraries, their problem not ours 54 | # "ignore::DeprecationWarning", 55 | ] 56 | 57 | [tool.ruff] 58 | line-length = 88 59 | #lint.select = ["ALL"] # We use default rules for now 60 | extend-select = ["E501"] # Complain about line length 61 | # Ignore specific rules 62 | # (we should aim to have these as few as possible) 63 | lint.ignore = [ 64 | "F405", # 'F405: Name may be undefined, or defined from star imports: typing' 65 | "E731", # 'E731: Do not assign a lambda expression, use a def' 66 | ] 67 | 68 | [tool.black] 69 | line-length = 88 70 | 71 | [tool.isort] 72 | # This forces these imports to placed at the top 73 | known_future_library = ["typing"] 74 | profile = "black" 75 | line_length = 88 76 | -------------------------------------------------------------------------------- /src/tokenizer/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Copyright(C) 2016-2025 Miðeind ehf. 4 | Original author: Vilhjálmur Þorsteinsson 5 | 6 | This software is licensed under the MIT License: 7 | 8 | Permission is hereby granted, free of charge, to any person 9 | obtaining a copy of this software and associated documentation 10 | files (the "Software"), to deal in the Software without restriction, 11 | including without limitation the rights to use, copy, modify, merge, 12 | publish, distribute, sublicense, and/or sell copies of the Software, 13 | and to permit persons to whom the Software is furnished to do so, 14 | subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | """ 28 | 29 | import importlib.metadata 30 | 31 | from .definitions import ( 32 | TP_LEFT, 33 | TP_CENTER, 34 | TP_RIGHT, 35 | TP_NONE, 36 | TP_WORD, 37 | EN_DASH, 38 | EM_DASH, 39 | KLUDGY_ORDINALS_PASS_THROUGH, 40 | KLUDGY_ORDINALS_MODIFY, 41 | KLUDGY_ORDINALS_TRANSLATE, 42 | BIN_Tuple, 43 | BIN_TupleList, 44 | ) 45 | from .tokenizer import ( 46 | TOK, 47 | Tok, 48 | tokenize, 49 | tokenize_without_annotation, 50 | split_into_sentences, 51 | parse_tokens, 52 | correct_spaces, 53 | detokenize, 54 | mark_paragraphs, 55 | paragraphs, 56 | normalized_text, 57 | normalized_text_from_tokens, 58 | text_from_tokens, 59 | calculate_indexes, 60 | generate_raw_tokens, 61 | TokenStream, 62 | ) 63 | from .abbrev import Abbreviations, ConfigError 64 | 65 | __author__ = "Miðeind ehf." 66 | __copyright__ = "(C) 2016-2025 Miðeind ehf." 67 | __version__ = importlib.metadata.version(__name__) 68 | 69 | __all__ = ( 70 | "__author__", 71 | "__copyright__", 72 | "__version__", 73 | "Abbreviations", 74 | "BIN_Tuple", 75 | "BIN_TupleList", 76 | "calculate_indexes", 77 | "ConfigError", 78 | "correct_spaces", 79 | "detokenize", 80 | "EM_DASH", 81 | "EN_DASH", 82 | "generate_raw_tokens", 83 | "KLUDGY_ORDINALS_MODIFY", 84 | "KLUDGY_ORDINALS_PASS_THROUGH", 85 | "KLUDGY_ORDINALS_TRANSLATE", 86 | "mark_paragraphs", 87 | "normalized_text_from_tokens", 88 | "normalized_text", 89 | "paragraphs", 90 | "parse_tokens", 91 | "split_into_sentences", 92 | "text_from_tokens", 93 | "Tok", 94 | "TOK", 95 | "tokenize_without_annotation", 96 | "tokenize", 97 | "TokenStream", 98 | "TP_CENTER", 99 | "TP_LEFT", 100 | "TP_NONE", 101 | "TP_RIGHT", 102 | "TP_WORD", 103 | ) 104 | -------------------------------------------------------------------------------- /src/tokenizer/abbrev.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Abbreviations module for tokenization of Icelandic text 4 | 5 | Copyright (C) 2016-2025 Miðeind ehf. 6 | Original author: Vilhjálmur Þorsteinsson 7 | 8 | This software is licensed under the MIT License: 9 | 10 | Permission is hereby granted, free of charge, to any person 11 | obtaining a copy of this software and associated documentation 12 | files (the "Software"), to deal in the Software without restriction, 13 | including without limitation the rights to use, copy, modify, merge, 14 | publish, distribute, sublicense, and/or sell copies of the Software, 15 | and to permit persons to whom the Software is furnished to do so, 16 | subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | 30 | This module reads the definition of abbreviations from the file 31 | Abbrev.conf, assumed to be located in the same directory (or installation 32 | resource library) as this Python source file. 33 | 34 | """ 35 | 36 | from typing import Generic, Iterator, Optional, TypeVar 37 | 38 | from threading import Lock 39 | from collections import defaultdict 40 | import importlib.resources as importlib_resources 41 | 42 | from .definitions import BIN_Tuple 43 | 44 | 45 | class ConfigError(Exception): 46 | pass 47 | 48 | 49 | _T = TypeVar("_T") 50 | 51 | 52 | class OrderedSet(Generic[_T]): 53 | """Shim class to provide an ordered set API on top 54 | of a dictionary. This is necessary to make abbreviation 55 | lookups predictable and repeatable, which they would not be 56 | if a standard Python set() was used.""" 57 | 58 | def __init__(self) -> None: 59 | # Insertions are ordered in Python 3.7+ dicts 60 | self._dict: dict[_T, None] = {} 61 | 62 | def add(self, item: _T) -> None: 63 | """Add an item at the end of the ordered set""" 64 | # For plain dicts in Python 3.7+, direct assignment works: 65 | # * If item is new, it is added at the end. 66 | # * If item already exists, its value is updated (to None again), 67 | # and the order remains unchanged. 68 | self._dict[item] = None 69 | 70 | def __contains__(self, item: _T) -> bool: 71 | return item in self._dict 72 | 73 | def __iter__(self) -> Iterator[_T]: 74 | return self._dict.__iter__() 75 | 76 | def __repr__(self) -> str: 77 | return f"{self.__class__.__name__}({list(self._dict.keys())})" 78 | 79 | 80 | class Abbreviations: 81 | """Wrapper around dictionary of abbreviations, 82 | initialized from the config file""" 83 | 84 | # Dictionary of abbreviations and their meanings 85 | DICT: dict[str, OrderedSet[BIN_Tuple]] = defaultdict(OrderedSet) 86 | # Wrong versions of abbreviations 87 | WRONGDICT: dict[str, OrderedSet[BIN_Tuple]] = defaultdict(OrderedSet) 88 | # All abbreviation meanings 89 | MEANINGS: set[str] = set() 90 | # Single-word abbreviations, i.e. those with only one dot at the end 91 | SINGLES: set[str] = set() 92 | # Set of abbreviations without periods, e.g. "td", "osfrv" 93 | WRONGSINGLES: set[str] = set() 94 | # Potential sentence finishers, i.e. those with a dot at the end, 95 | # marked with an asterisk in the config file 96 | FINISHERS: set[str] = set() 97 | # Abbreviations that should not be seen as such at the end of sentences, 98 | # marked with an exclamation mark in the config file 99 | NOT_FINISHERS: set[str] = set() 100 | # Abbreviations that should not be seen as such at the end of sentences, but 101 | # are allowed in front of person names; marked with a hat ^ in the config file 102 | NAME_FINISHERS: set[str] = set() 103 | # Wrong versions of abbreviations with possible corrections 104 | # wrong version : [correction1, correction2, ...] 105 | WRONGDOTS: dict[str, list[str]] = defaultdict(list) 106 | # Word forms that should never be interpreted as abbreviations 107 | NOT_ABBREVIATIONS: set[str] = set() 108 | 109 | # Ensure that only one thread initializes the abbreviations 110 | _lock = Lock() 111 | 112 | @staticmethod 113 | def add(abbrev: str, meaning: str, gender: str, fl: Optional[str] = None) -> None: 114 | """Add an abbreviation to the dictionary. 115 | Called from the config file handler.""" 116 | # Check for sentence finishers 117 | finisher = False 118 | not_finisher = False 119 | name_finisher = False 120 | if abbrev.endswith("*"): 121 | # This abbreviation is explicitly allowed to finish a sentence 122 | finisher = True 123 | abbrev = abbrev[0:-1] 124 | if not abbrev.endswith("."): 125 | raise ConfigError( 126 | "Only abbreviations ending with periods can be sentence finishers" 127 | ) 128 | elif abbrev.endswith("!"): 129 | # A not-finisher cannot finish a sentence, because it is also a valid word 130 | # (Example: 'dags.', 'mín.', 'sek.') 131 | not_finisher = True 132 | abbrev = abbrev[0:-1] 133 | if not abbrev.endswith("."): 134 | raise ConfigError( 135 | "Only abbreviations ending with periods " 136 | "can be marked as not-finishers" 137 | ) 138 | elif abbrev.endswith("^"): 139 | # This abbreviation is only to be interpreted as an abbreviation 140 | # if it is followed by a name (example: 'próf.' for 'prófessor'). 141 | # This logic is not fully present in Tokenizer as information 142 | # about person names is needed to make it work. The full implementation, 143 | # using the NAME_FINISHERS set, is found in bintokenizer.py in 144 | # GreynirEngine. 145 | name_finisher = True 146 | abbrev = abbrev[0:-1] 147 | if not abbrev.endswith("."): 148 | raise ConfigError( 149 | "Only abbreviations ending with periods " 150 | "can be marked as name finishers" 151 | ) 152 | if abbrev.endswith("!") or abbrev.endswith("*") or abbrev.endswith("^"): 153 | raise ConfigError( 154 | "!, * and ^ modifiers are mutually exclusive on abbreviations" 155 | ) 156 | # Append the abbreviation and its meaning in tuple form 157 | # Multiple meanings are supported for each abbreviation 158 | Abbreviations.DICT[abbrev].add( 159 | BIN_Tuple( 160 | meaning, 161 | 0, 162 | gender, 163 | "skst" if fl is None else fl, 164 | abbrev, 165 | "-", 166 | ) 167 | ) 168 | Abbreviations.MEANINGS.add(meaning) 169 | # Adding wrong versions of abbreviations 170 | if abbrev[-1] == "." and "." not in abbrev[0:-1]: 171 | # Only one dot, at the end 172 | # Lookup is without the dot 173 | wabbrev = abbrev[0:-1] 174 | Abbreviations.SINGLES.add(wabbrev) 175 | if finisher: 176 | Abbreviations.FINISHERS.add(wabbrev) 177 | Abbreviations.WRONGDOTS[wabbrev].append(abbrev) 178 | if len(wabbrev) > 1: 179 | # We don't add single letters (such as Í and Á) 180 | # as abbreviations, even though they are listed as such 181 | # in the form 'Í.' and 'Á.' for use within person names 182 | Abbreviations.WRONGDICT[wabbrev].add( 183 | BIN_Tuple( 184 | meaning, 185 | 0, 186 | gender, 187 | "skst" if fl is None else fl, 188 | wabbrev, 189 | "-", 190 | ) 191 | ) 192 | 193 | elif "." in abbrev: 194 | # Only multiple dots, checked single dots above 195 | # Want to see versions with each one deleted, 196 | # and one where all are deleted 197 | indices = [pos for pos, char in enumerate(abbrev) if char == "."] 198 | for i in indices: 199 | # Removing one dot at a time 200 | wabbrev = abbrev[:i] + abbrev[i + 1 :] 201 | Abbreviations.WRONGDOTS[wabbrev].append(abbrev) 202 | Abbreviations.WRONGDICT[wabbrev].add( 203 | BIN_Tuple( 204 | meaning, 205 | 0, 206 | gender, 207 | "skst" if fl is None else fl, 208 | wabbrev, 209 | "-", 210 | ) 211 | ) 212 | if len(indices) > 2: 213 | # 3 or 4 dots currently in vocabulary 214 | # Not all cases with 4 dots are handled. 215 | i1 = indices[0] 216 | i2 = indices[1] 217 | i3 = indices[2] 218 | wabbrevs: list[str] = [] 219 | # 1 and 2 removed 220 | wabbrevs.append(abbrev[:i1] + abbrev[i1 + 1 : i2] + abbrev[i2 + 1 :]) 221 | # 1 and 3 removed 222 | wabbrevs.append(abbrev[:i1] + abbrev[i1 + 1 : i3] + abbrev[i3 + 1 :]) 223 | # 2 and 3 removed 224 | wabbrevs.append(abbrev[:i2] + abbrev[i2 + 1 : i3] + abbrev[i3 + 1 :]) 225 | for wabbrev in wabbrevs: 226 | Abbreviations.WRONGDOTS[wabbrev].append(abbrev) 227 | Abbreviations.WRONGDICT[wabbrev].add( 228 | BIN_Tuple( 229 | meaning, 230 | 0, 231 | gender, 232 | "skst" if fl is None else fl, 233 | wabbrev, 234 | "-", 235 | ) 236 | ) 237 | # Removing all dots 238 | wabbrev = abbrev.replace(".", "") 239 | Abbreviations.WRONGSINGLES.add(wabbrev) 240 | Abbreviations.WRONGDOTS[wabbrev].append(abbrev) 241 | Abbreviations.WRONGDICT[wabbrev].add( 242 | BIN_Tuple( 243 | meaning, 244 | 0, 245 | gender, 246 | "skst" if fl is None else fl, 247 | wabbrev, 248 | "-", 249 | ) 250 | ) 251 | if finisher: 252 | Abbreviations.FINISHERS.add(abbrev) 253 | if not_finisher: 254 | Abbreviations.NOT_FINISHERS.add(abbrev) 255 | if name_finisher: 256 | Abbreviations.NAME_FINISHERS.add(abbrev) 257 | 258 | @staticmethod 259 | def has_meaning(abbrev: str) -> bool: 260 | return abbrev in Abbreviations.DICT or abbrev in Abbreviations.WRONGDICT 261 | 262 | @staticmethod 263 | def has_abbreviation(meaning: str) -> bool: 264 | return meaning in Abbreviations.MEANINGS 265 | 266 | @staticmethod 267 | def get_meaning(abbrev: str) -> Optional[list[BIN_Tuple]]: 268 | """Look up meaning(s) of abbreviation, if available.""" 269 | m = Abbreviations.DICT.get(abbrev) 270 | if not m: 271 | m = Abbreviations.WRONGDICT.get(abbrev) 272 | return list(m) if m else None 273 | 274 | @staticmethod 275 | def _handle_abbreviations(s: str) -> None: 276 | """Handle abbreviations in the settings section""" 277 | # Format: abbrev[*] = "meaning" gender (kk|kvk|hk) 278 | # An asterisk after an abbreviation ending with a period 279 | # indicates that the abbreviation may finish a sentence 280 | a = s.split("=", 1) # maxsplit=1 281 | if len(a) != 2: 282 | raise ConfigError( 283 | "Wrong format for abbreviation: should be abbreviation = meaning" 284 | ) 285 | abbrev = a[0].strip() 286 | if not abbrev: 287 | raise ConfigError( 288 | "Missing abbreviation. Format should be abbreviation = meaning." 289 | ) 290 | m = a[1].strip().split('"') 291 | par = "" 292 | if len(m) >= 3: 293 | # Something follows the last quote 294 | par = m[-1].strip() 295 | gender = "hk" # Default gender is neutral 296 | fl = None # Default word category is None 297 | if par: 298 | p = par.split() 299 | if len(p) >= 1: 300 | gender = p[0].strip() 301 | if len(p) >= 2: 302 | fl = p[1].strip() 303 | Abbreviations.add(abbrev, m[1], gender, fl) 304 | 305 | @staticmethod 306 | def _handle_not_abbreviations(s: str) -> None: 307 | """Handle not_abbreviations in the settings section""" 308 | if len(s) < 3 or s[0] != '"' or s[-1] != '"': 309 | raise ConfigError("not_abbreviations should be enclosed in double quotes") 310 | Abbreviations.NOT_ABBREVIATIONS.add(s[1:-1]) 311 | 312 | @staticmethod 313 | def initialize() -> None: 314 | """Read the abbreviations config file""" 315 | with Abbreviations._lock: 316 | if len(Abbreviations.DICT): 317 | # Already initialized 318 | return 319 | 320 | section = None 321 | 322 | p = importlib_resources.files("tokenizer").joinpath("Abbrev.conf") 323 | config = p.read_text(encoding="utf-8") 324 | 325 | for s in config.split("\n"): 326 | # Ignore comments 327 | ix = s.find("#") 328 | if ix >= 0: 329 | s = s[0:ix] 330 | s = s.strip() 331 | if not s: 332 | # Blank line: ignore 333 | continue 334 | if s[0] == "[": 335 | # Section header (we expect [abbreviations]/[not_abbreviations]) 336 | if s not in {"[abbreviations]", "[not_abbreviations]"}: 337 | raise ConfigError("Wrong section header") 338 | section = s 339 | continue 340 | if section == "[abbreviations]": 341 | Abbreviations._handle_abbreviations(s) 342 | elif section == "[not_abbreviations]": 343 | Abbreviations._handle_not_abbreviations(s) 344 | else: 345 | raise ConfigError("Content outside section") 346 | 347 | # Remove not_abbreviations from WRONGDICT 348 | for abbr in Abbreviations.NOT_ABBREVIATIONS: 349 | if abbr in Abbreviations.WRONGDICT: 350 | del Abbreviations.WRONGDICT[abbr] 351 | Abbreviations.NOT_ABBREVIATIONS = set() 352 | -------------------------------------------------------------------------------- /src/tokenizer/definitions.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Definitions used for tokenization of Icelandic text 4 | 5 | Copyright (C) 2016-2025 Miðeind ehf. 6 | Original author: Vilhjálmur Þorsteinsson 7 | 8 | This software is licensed under the MIT License: 9 | 10 | Permission is hereby granted, free of charge, to any person 11 | obtaining a copy of this software and associated documentation 12 | files (the "Software"), to deal in the Software without restriction, 13 | including without limitation the rights to use, copy, modify, merge, 14 | publish, distribute, sublicense, and/or sell copies of the Software, 15 | and to permit persons to whom the Software is furnished to do so, 16 | subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | """ 30 | 31 | from typing import ( 32 | Mapping, 33 | Union, 34 | Callable, 35 | Sequence, 36 | Optional, 37 | NamedTuple, 38 | cast, 39 | ) 40 | 41 | import re 42 | 43 | 44 | BeginTuple = tuple[int, Optional[int]] 45 | PunctuationTuple = tuple[int, str] 46 | NumberTuple = tuple[float, Optional[list[str]], Optional[list[str]]] 47 | DateTimeTuple = tuple[int, int, int] 48 | MeasurementTuple = tuple[str, float] 49 | TimeStampTuple = tuple[int, int, int, int, int, int] 50 | AmountTuple = tuple[float, str, Optional[list[str]], Optional[list[str]]] 51 | TelnoTuple = tuple[str, str] 52 | CurrencyTuple = tuple[str, Optional[list[str]], Optional[list[str]]] 53 | 54 | 55 | class BIN_Tuple(NamedTuple): 56 | stofn: str 57 | utg: int 58 | ordfl: str 59 | fl: str 60 | ordmynd: str 61 | beyging: str 62 | 63 | 64 | BIN_TupleList = Sequence[BIN_Tuple] 65 | 66 | 67 | class PersonNameTuple(NamedTuple): 68 | name: str 69 | gender: Optional[str] 70 | case: Optional[str] 71 | 72 | 73 | PersonNameList = Sequence[PersonNameTuple] 74 | 75 | # All possible contents of the Tok.val attribute 76 | ValType = Union[ 77 | None, 78 | int, # YEAR, ORDINAL 79 | str, # USERNAME 80 | BeginTuple, # S_BEGIN 81 | PunctuationTuple, # PUNCTUATION, NUMWLETTER 82 | MeasurementTuple, # MEASUREMENT 83 | TelnoTuple, # TELNO 84 | DateTimeTuple, # DATE, TIME 85 | TimeStampTuple, # TIMESTAMP 86 | NumberTuple, # PERCENT, NUMBER 87 | AmountTuple, # AMOUNT 88 | CurrencyTuple, # CURRENCY 89 | BIN_TupleList, # WORD 90 | PersonNameList, # PERSON 91 | ] 92 | 93 | # This seems to be needed as a workaround for Pylance/Pyright 94 | _escape = cast(Callable[[str], str], re.escape) 95 | 96 | ACCENT = chr(769) 97 | UMLAUT = chr(776) 98 | SOFT_HYPHEN = chr(173) 99 | ZEROWIDTH_SPACE = chr(8203) 100 | ZEROWIDTH_NBSP = chr(65279) 101 | 102 | # Preprocessing of unicode characters before tokenization 103 | UNICODE_REPLACEMENTS = { 104 | # Translations of separate umlauts and accents to single glyphs. 105 | # The strings to the left in each tuple are two Unicode code 106 | # points: vowel + COMBINING ACUTE ACCENT (chr(769)) or 107 | # vowel + COMBINING DIAERESIS (chr(776)). 108 | "a" + ACCENT: "á", 109 | "a" + UMLAUT: "ä", 110 | "e" + ACCENT: "é", 111 | "e" + UMLAUT: "ë", 112 | "i" + ACCENT: "í", 113 | "o" + ACCENT: "ó", 114 | "u" + ACCENT: "ú", 115 | "u" + UMLAUT: "ü", 116 | "y" + ACCENT: "ý", 117 | "o" + UMLAUT: "ö", 118 | "A" + UMLAUT: "Ä", 119 | "A" + ACCENT: "Á", 120 | "E" + ACCENT: "É", 121 | "E" + UMLAUT: "Ë", 122 | "I" + ACCENT: "Í", 123 | "O" + ACCENT: "Ó", 124 | "U" + ACCENT: "Ú", 125 | "U" + UMLAUT: "Ü", 126 | "Y" + ACCENT: "Ý", 127 | "O" + UMLAUT: "Ö", 128 | # Also remove these unwanted characters 129 | SOFT_HYPHEN: "", 130 | ZEROWIDTH_SPACE: "", 131 | ZEROWIDTH_NBSP: "", 132 | } 133 | UNICODE_REGEX = re.compile( 134 | r"|".join(map(_escape, UNICODE_REPLACEMENTS.keys())), re.UNICODE 135 | ) 136 | 137 | # Used for the first step of token splitting 138 | ROUGH_TOKEN_REGEX = re.compile(r"(\s*)([^\s]*)", re.UNICODE) 139 | # Constants for readability when using the ROUGH_TOKEN_REGEX 140 | ROUGH_TOKEN_REGEX_ENTIRE_MATCH = 0 141 | ROUGH_TOKEN_REGEX_WHITE_SPACE_GROUP = 1 142 | ROUGH_TOKEN_REGEX_TOKEN_GROUP = 2 143 | 144 | # Hyphens are normalized to '-' 145 | HYPHEN = "-" # Normal hyphen 146 | EN_DASH = "\u2013" # "–" 147 | EM_DASH = "\u2014" # "—" 148 | 149 | HYPHENS = HYPHEN + EN_DASH + EM_DASH 150 | 151 | # Hyphens that may indicate composite words ('fjármála- og efnahagsráðuneyti') 152 | COMPOSITE_HYPHENS = HYPHEN + EN_DASH 153 | COMPOSITE_HYPHEN = EN_DASH 154 | 155 | # Recognized punctuation 156 | LEFT_PUNCTUATION = "([„‚«#$€£¥₽<" 157 | RIGHT_PUNCTUATION = ".,:;)]!%‰?“»”’‛‘…>°" 158 | CENTER_PUNCTUATION = '"*•&+=@©|' 159 | NONE_PUNCTUATION = "^/±'´~\\" + HYPHEN + EN_DASH + EM_DASH 160 | PUNCTUATION = ( 161 | LEFT_PUNCTUATION + CENTER_PUNCTUATION + RIGHT_PUNCTUATION + NONE_PUNCTUATION 162 | ) 163 | PUNCTUATION_REGEX = "[{0}]".format("|".join(re.escape(p) for p in PUNCTUATION)) 164 | 165 | # Punctuation types: left, center or right of word 166 | 167 | TP_LEFT = 1 # Whitespace to the left 168 | TP_CENTER = 2 # Whitespace to the left and right 169 | TP_RIGHT = 3 # Whitespace to the right 170 | TP_NONE = 4 # No whitespace 171 | TP_WORD = 5 # Flexible whitespace depending on surroundings 172 | 173 | # Matrix indicating correct spacing between tokens 174 | 175 | TP_SPACE = ( 176 | # Next token is: 177 | # LEFT CENTER RIGHT NONE WORD 178 | # Last token was TP_LEFT: 179 | (False, True, False, False, False), 180 | # Last token was TP_CENTER: 181 | (True, True, True, True, True), 182 | # Last token was TP_RIGHT: 183 | (True, True, False, False, True), 184 | # Last token was TP_NONE: 185 | (False, True, False, False, False), 186 | # Last token was TP_WORD: 187 | (True, True, False, False, True), 188 | ) 189 | 190 | # Punctuation that ends a sentence 191 | END_OF_SENTENCE = frozenset([".", "?", "!", "…"]) # Removed […] 192 | # Punctuation symbols that may additionally occur at the end of a sentence 193 | SENTENCE_FINISHERS = frozenset([")", "]", "“", "»", "”", "’", '"', "[…]"]) 194 | # Punctuation symbols that may occur inside words 195 | # Note that an EM_DASH is not allowed inside a word and will split words if present 196 | PUNCT_INSIDE_WORD = frozenset([".", "'", "‘", "´", "’", HYPHEN, EN_DASH]) 197 | # Punctuation symbols that can end words 198 | PUNCT_ENDING_WORD = frozenset(["'", "²", "³"]) 199 | # Punctuation symbols that may occur together 200 | PUNCT_COMBINATIONS = frozenset(["?", "!", "…"]) 201 | # Punctuation in end of indirect speech that doesn't necessarily end sentences 202 | PUNCT_INDIRECT_SPEECH = frozenset(["?", "!"]) 203 | 204 | 205 | # Single and double quotes 206 | SQUOTES = "'‚‛‘´" 207 | DQUOTES = '"“„”«»' 208 | 209 | CLOCK_ABBREVS = frozenset(("kl", "kl.", "klukkan")) 210 | 211 | # Allowed first digits in Icelandic telephone numbers 212 | TELNO_PREFIXES = "45678" 213 | 214 | # Known telephone country codes 215 | COUNTRY_CODES = frozenset( 216 | ( 217 | "354", 218 | "+354", 219 | "00354", 220 | ) 221 | ) 222 | 223 | # Words that can precede a year number; will be assimilated into the year token 224 | YEAR_WORD = frozenset(("árið", "ársins", "árinu")) 225 | 226 | # Characters that can start a numeric token 227 | DIGITS_PREFIX = frozenset([d for d in "0123456789"]) 228 | SIGN_PREFIX = frozenset(("+", "-")) 229 | 230 | # Month names and numbers 231 | MONTHS = { 232 | "janúar": 1, 233 | "janúars": 1, 234 | "febrúar": 2, 235 | "febrúars": 2, 236 | "mars": 3, 237 | "apríl": 4, 238 | "apríls": 4, 239 | "maí": 5, 240 | "maís": 5, 241 | "júní": 6, 242 | "júnís": 6, 243 | "júlí": 7, 244 | "júlís": 7, 245 | "ágúst": 8, 246 | "ágústs": 8, 247 | "september": 9, 248 | "septembers": 9, 249 | "október": 10, 250 | "októbers": 10, 251 | "nóvember": 11, 252 | "nóvembers": 11, 253 | "desember": 12, 254 | "desembers": 12, 255 | "jan.": 1, 256 | "feb.": 2, 257 | "mar.": 3, 258 | "apr.": 4, 259 | "jún.": 6, 260 | "júl.": 7, 261 | "ág.": 8, 262 | "ágú.": 8, 263 | "sep.": 9, 264 | "sept.": 9, 265 | "okt.": 10, 266 | "nóv.": 11, 267 | "des.": 12, 268 | "jan": 1, 269 | "feb": 2, 270 | "mar": 3, 271 | "apr": 4, 272 | "jún": 6, 273 | "júl": 7, 274 | "ág": 8, 275 | "ágú": 8, 276 | "sep": 9, 277 | "sept": 9, 278 | "okt": 10, 279 | "nóv": 11, 280 | "des": 12, 281 | } 282 | 283 | # The masculine Icelandic name should not be identified as a month 284 | MONTH_BLACKLIST = frozenset(("Ágúst",)) 285 | 286 | # Word forms that are not unambiguous as month names 287 | AMBIGUOUS_MONTH_NAMES = frozenset( 288 | ("jan", "Jan", "mar", "Mar", "júl", "Júl", "des", "Des", "Ágúst") 289 | ) 290 | 291 | # Max number of days in each month, indexed so that 1=January 292 | DAYS_IN_MONTH = (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) 293 | 294 | # Days of the month spelled out 295 | # DAYS_OF_MONTH = { 296 | # "fyrsti": 1, 297 | # "fyrsta": 1, 298 | # "annar": 2, 299 | # "annan": 2, 300 | # "þriðji": 3, 301 | # "þriðja": 3, 302 | # "fjórði": 4, 303 | # "fjórða": 4, 304 | # "fimmti": 5, 305 | # "fimmta": 5, 306 | # "sjötti": 6, 307 | # "sjötta": 6, 308 | # "sjöundi": 7, 309 | # "sjöunda": 7, 310 | # "áttundi": 8, 311 | # "áttunda": 8, 312 | # "níundi": 9, 313 | # "níunda": 9, 314 | # "tíundi": 10, 315 | # "tíunda": 10, 316 | # "ellefti": 11, 317 | # "ellefta": 11, 318 | # "tólfti": 12, 319 | # "tólfta": 12, 320 | # "þrettándi": 13, 321 | # "þrettánda": 13, 322 | # "fjórtándi": 14, 323 | # "fjórtánda": 14, 324 | # "fimmtándi": 15, 325 | # "fimmtánda": 15, 326 | # "sextándi": 16, 327 | # "sextánda": 16, 328 | # "sautjándi": 17, 329 | # "sautjánda": 17, 330 | # "átjándi": 18, 331 | # "átjánda": 18, 332 | # "nítjándi": 19, 333 | # "nítjánda": 19, 334 | # "tuttugasti": 20, 335 | # "tuttugasta": 20, 336 | # "þrítugasti": 30, 337 | # "þrítugasta": 30, 338 | # } 339 | 340 | # Time of day expressions spelled out 341 | CLOCK_NUMBERS: Mapping[str, tuple[int, int, int]] = { 342 | "eitt": (1, 0, 0), 343 | "tvö": (2, 0, 0), 344 | "þrjú": (3, 0, 0), 345 | "fjögur": (4, 0, 0), 346 | "fimm": (5, 0, 0), 347 | "sex": (6, 0, 0), 348 | "sjö": (7, 0, 0), 349 | "átta": (8, 0, 0), 350 | "níu": (9, 0, 0), 351 | "tíu": (10, 0, 0), 352 | "ellefu": (11, 0, 0), 353 | "tólf": (12, 0, 0), 354 | "hálfeitt": (12, 30, 0), 355 | "hálftvö": (1, 30, 0), 356 | "hálfþrjú": (2, 30, 0), 357 | "hálffjögur": (3, 30, 0), 358 | "hálffimm": (4, 30, 0), 359 | "hálfsex": (5, 30, 0), 360 | "hálfsjö": (6, 30, 0), 361 | "hálfátta": (7, 30, 0), 362 | "hálfníu": (8, 30, 0), 363 | "hálftíu": (9, 30, 0), 364 | "hálfellefu": (10, 30, 0), 365 | "hálftólf": (11, 30, 0), 366 | } 367 | 368 | # Set of words only possible in temporal phrases 369 | CLOCK_HALF = frozenset( 370 | [ 371 | "hálfeitt", 372 | "hálftvö", 373 | "hálfþrjú", 374 | "hálffjögur", 375 | "hálffimm", 376 | "hálfsex", 377 | "hálfsjö", 378 | "hálfátta", 379 | "hálfníu", 380 | "hálftíu", 381 | "hálfellefu", 382 | "hálftólf", 383 | ] 384 | ) 385 | 386 | # 'Current Era', 'Before Current Era' 387 | CE = frozenset(("e.Kr", "e.Kr.")) # !!! Add AD and CE here? 388 | BCE = frozenset(("f.Kr", "f.Kr.")) # !!! Add BCE here? 389 | CE_BCE = CE | BCE 390 | 391 | # Supported ISO 4217 currency codes 392 | CURRENCY_ABBREV = frozenset( 393 | ( 394 | "ISK", # Icelandic króna 395 | "DKK", # Danish krone 396 | "NOK", # Norwegian krone 397 | "SEK", # Swedish krona 398 | "GBP", # British pounds sterling 399 | "USD", # US dollar 400 | "EUR", # Euro 401 | "CAD", # Canadian dollar 402 | "AUD", # Australian dollar 403 | "CHF", # Swiss franc 404 | "JPY", # Japanese yen 405 | "PLN", # Polish złoty 406 | "RUB", # Russian ruble 407 | "CZK", # Czech koruna 408 | "INR", # Indian rupee 409 | "IDR", # Indonesian rupiah 410 | "CNY", # Chinese renminbi 411 | "RMB", # Chinese renminbi (alternate) 412 | "HKD", # Hong Kong dollar 413 | "NZD", # New Zealand dollar 414 | "SGD", # Singapore dollar 415 | "MXN", # Mexican peso 416 | "ZAR", # South African rand 417 | ) 418 | ) 419 | 420 | # Map symbols to currency abbreviations 421 | CURRENCY_SYMBOLS = { 422 | "$": "USD", 423 | "€": "EUR", 424 | "£": "GBP", 425 | "¥": "JPY", # Also used for China's renminbi (yuan) 426 | "₽": "RUB", # Russian ruble 427 | } 428 | 429 | # Single-character vulgar fractions in Unicode 430 | SINGLECHAR_FRACTIONS = "↉⅒⅑⅛⅐⅙⅕¼⅓½⅖⅔⅜⅗¾⅘⅝⅚⅞" 431 | 432 | # Derived unit : (base SI unit, conversion factor/function) 433 | SI_UNITS: dict[str, tuple[str, Union[float, Callable[[float], float]]]] = { 434 | # Distance 435 | "m": ("m", 1.0), 436 | "mm": ("m", 1.0e-3), 437 | "μm": ("m", 1.0e-6), 438 | "cm": ("m", 1.0e-2), 439 | "sm": ("m", 1.0e-2), 440 | "km": ("m", 1.0e3), 441 | "ft": ("m", 0.3048), # feet 442 | "mi": ("m", 1609.34), # miles 443 | # Area 444 | "m²": ("m²", 1.0), 445 | "fm": ("m²", 1.0), 446 | "km²": ("m²", 1.0e6), 447 | "cm²": ("m²", 1.0e-2), 448 | "ha": ("m²", 1.0e4), 449 | # Volume 450 | "m³": ("m³", 1.0), 451 | "cm³": ("m³", 1.0e-6), 452 | "km³": ("m³", 1.0e9), 453 | "l": ("m³", 1.0e-3), 454 | "ltr": ("m³", 1.0e-3), 455 | "dl": ("m³", 1.0e-4), 456 | "cl": ("m³", 1.0e-5), 457 | "ml": ("m³", 1.0e-6), 458 | "gal": ("m³", 3.78541e-3), 459 | "bbl": ("m³", 158.987294928e-3), 460 | # Temperature 461 | "K": ("K", 1.0), 462 | "°K": ("K", 1.0), # Strictly speaking this should be K, not °K 463 | "°C": ("K", lambda x: x + 273.15), 464 | "°F": ("K", lambda x: (x + 459.67) * 5 / 9), 465 | # Mass 466 | "g": ("kg", 1.0e-3), 467 | "gr": ("kg", 1.0e-3), 468 | "kg": ("kg", 1.0), 469 | "t": ("kg", 1.0e3), 470 | "mg": ("kg", 1.0e-6), 471 | "μg": ("kg", 1.0e-9), 472 | "tn": ("kg", 1.0e3), 473 | "lb": ("kg", 0.453592), 474 | # Duration 475 | "s": ("s", 1.0), 476 | "ms": ("s", 1.0e-3), 477 | "μs": ("s", 1.0e-6), 478 | "klst": ("s", 3600.0), 479 | "mín": ("s", 60.0), 480 | # Force 481 | "N": ("N", 1.0), 482 | "kN": ("N", 1.0e3), 483 | # Energy 484 | "Nm": ("J", 1.0), 485 | "J": ("J", 1.0), 486 | "kJ": ("J", 1.0e3), 487 | "MJ": ("J", 1.0e6), 488 | "GJ": ("J", 1.0e9), 489 | "TJ": ("J", 1.0e12), 490 | "kWh": ("J", 3.6e6), 491 | "MWh": ("J", 3.6e9), 492 | "kWst": ("J", 3.6e6), 493 | "MWst": ("J", 3.6e9), 494 | "kcal": ("J", 4184.0), 495 | "cal": ("J", 4.184), 496 | # Power 497 | "W": ("W", 1.0), 498 | "mW": ("W", 1.0e-3), 499 | "kW": ("W", 1.0e3), 500 | "MW": ("W", 1.0e6), 501 | "GW": ("W", 1.0e9), 502 | "TW": ("W", 1.0e12), 503 | # Electric potential 504 | "V": ("V", 1.0), 505 | "mV": ("V", 1.0e-3), 506 | "kV": ("V", 1.0e3), 507 | # Electric current 508 | "A": ("A", 1.0), 509 | "mA": ("A", 1.0e-3), 510 | # Frequency 511 | "Hz": ("Hz", 1.0), 512 | "kHz": ("Hz", 1.0e3), 513 | "MHz": ("Hz", 1.0e6), 514 | "GHz": ("Hz", 1.0e9), 515 | # Pressure 516 | "Pa": ("Pa", 1.0), 517 | "hPa": ("Pa", 1.0e2), 518 | # Angle 519 | "°": ("°", 1.0), # Degree 520 | # Percentage and promille 521 | "%": ("%", 1.0), 522 | "‰": ("‰", 0.1), 523 | # Velocity 524 | "m/s": ("m/s", 1.0), 525 | "km/klst": ("m/s", 1000.0 / (60 * 60)), 526 | # "km/klst.": ("m/s", 1000.0/(60*60)), 527 | } 528 | 529 | DIRECTIONS = { 530 | "N": "Norður", 531 | } 532 | 533 | _unit_lambda: Callable[[str], str] = lambda unit: ( 534 | unit + r"(?!\w)" if unit[-1].isalpha() else unit 535 | ) 536 | 537 | SI_UNITS_SET: frozenset[str] = frozenset(SI_UNITS.keys()) 538 | SI_UNITS_REGEX_STRING = r"|".join( 539 | map( 540 | # If the unit ends with a letter, don't allow the next character 541 | # after it to be a letter (i.e. don't match '220Volts' as '220V') 542 | _unit_lambda, 543 | # Sort in descending order by length, so that longer strings 544 | # are matched before shorter ones 545 | sorted(SI_UNITS.keys(), key=len, reverse=True), 546 | ) 547 | ) 548 | SI_UNITS_REGEX = re.compile(r"({0})".format(SI_UNITS_REGEX_STRING), re.UNICODE) 549 | 550 | CURRENCY_REGEX_STRING = r"|".join( 551 | map( 552 | # Sort in descending order by length, so that longer strings 553 | # are matched before shorter ones 554 | _escape, 555 | sorted(CURRENCY_SYMBOLS.keys(), key=lambda s: len(s), reverse=True), 556 | ) 557 | ) 558 | 559 | # Combined pattern regex for SI units, percentage, promille and currency symbols 560 | UNIT_REGEX_STRING = SI_UNITS_REGEX_STRING + r"|" + CURRENCY_REGEX_STRING 561 | 562 | # Icelandic-style number, followed by a unit 563 | NUM_WITH_UNIT_REGEX1 = re.compile( 564 | r"([-+]?\d+(\.\d\d\d)*(,\d+)?)({0})".format(UNIT_REGEX_STRING), re.UNICODE 565 | ) 566 | 567 | # English-style number, followed by a unit 568 | NUM_WITH_UNIT_REGEX2 = re.compile( 569 | r"([-+]?\d+(,\d\d\d)*(\.\d+)?)({0})".format(UNIT_REGEX_STRING), re.UNICODE 570 | ) 571 | 572 | # One or more digits, followed by a unicode vulgar fraction char (e.g. '2½') 573 | # and a unit (SI, percent or currency symbol) 574 | NUM_WITH_UNIT_REGEX3 = re.compile( 575 | r"(\d+)([\u00BC-\u00BE\u2150-\u215E])({0})".format(UNIT_REGEX_STRING), re.UNICODE 576 | ) 577 | 578 | 579 | # If the handle_kludgy_ordinals option is set to 580 | # KLUDGY_ORDINALS_PASS_THROUGH, we do not convert 581 | # kludgy ordinals but pass them through as word tokens. 582 | KLUDGY_ORDINALS_PASS_THROUGH = 0 583 | # If the handle_kludgy_ordinals option is set to 584 | # KLUDGY_ORDINALS_MODIFY, we convert '1sti' to 'fyrsti', etc., 585 | # and return the modified word as a token. 586 | KLUDGY_ORDINALS_MODIFY = 1 587 | # If the handle_kludgy_ordinals option is set to 588 | # KLUDGY_ORDINALS_TRANSLATE, we convert '1sti' to TOK.Ordinal('1sti', 1), etc., 589 | # but otherwise pass the original word through as a word token ('2ja'). 590 | KLUDGY_ORDINALS_TRANSLATE = 2 591 | 592 | # Incorrectly written ('kludgy') ordinals 593 | ORDINAL_ERRORS = { 594 | "1sti": "fyrsti", 595 | "1sta": "fyrsta", 596 | "1stu": "fyrstu", 597 | "3ji": "þriðji", 598 | # "3ja": "þriðja", # þriggja 599 | "3ju": "þriðju", 600 | "4ði": "fjórði", 601 | "4ða": "fjórða", 602 | "4ðu": "fjórðu", 603 | "5ti": "fimmti", 604 | "5ta": "fimmta", 605 | "5tu": "fimmtu", 606 | "2svar": "tvisvar", 607 | "3svar": "þrisvar", 608 | "2ja": "tveggja", 609 | "3ja": "þriggja", 610 | "4ra": "fjögurra", 611 | } 612 | 613 | # Translations of kludgy ordinal words into numbers 614 | ORDINAL_NUMBERS = { 615 | "1sti": 1, 616 | "1sta": 1, 617 | "1stu": 1, 618 | "3ji": 3, 619 | "3ja": 3, 620 | "3ju": 3, 621 | "4ði": 4, 622 | "4ða": 4, 623 | "4ðu": 4, 624 | "5ti": 5, 625 | "5ta": 5, 626 | "5tu": 5, 627 | } 628 | 629 | # Handling of Roman numerals 630 | 631 | RE_ROMAN_NUMERAL = re.compile( 632 | r"^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$" 633 | ) 634 | 635 | ROMAN_NUMERAL_MAP = tuple( 636 | zip( 637 | (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1), 638 | ("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"), 639 | ) 640 | ) 641 | 642 | 643 | def roman_to_int(s: str) -> int: 644 | """Quick and dirty conversion of an already validated Roman numeral to integer""" 645 | # Adapted from http://code.activestate.com/recipes/81611-roman-numerals/ 646 | i = result = 0 647 | for integer, numeral in ROMAN_NUMERAL_MAP: 648 | while s[i : i + len(numeral)] == numeral: 649 | result += integer 650 | i += len(numeral) 651 | assert i == len(s) 652 | return result 653 | 654 | 655 | NUMBER_ABBREV = { 656 | "þús.": 1000, 657 | "millj.": 10**6, 658 | "mljó.": 10**6, 659 | "ma.": 10**9, 660 | "mrð.": 10**9, 661 | "billj.": 10**12, 662 | "bljó.": 10**12, 663 | "trillj.": 10**18, 664 | } 665 | 666 | # Recognize words for percentages 667 | PERCENTAGES = { 668 | "prósent": 1, 669 | "prósenta": 1, 670 | "prósenti": 1, 671 | "prósents": 1, 672 | "prósentur": 1, 673 | "prósentum": 1, 674 | "hundraðshluti": 1, 675 | "hundraðshluta": 1, 676 | "hundraðshlutar": 1, 677 | "hundraðshlutum": 1, 678 | "prósentustig": 1, 679 | "prósentustigi": 1, 680 | "prósentustigs": 1, 681 | "prósentustigum": 1, 682 | "prósentustiga": 1, 683 | } 684 | 685 | # Amount abbreviations including 'kr' for the ISK 686 | # Corresponding abbreviations are found in Abbrev.conf 687 | AMOUNT_ABBREV = { 688 | "kr.": 1, 689 | "kr": 1, 690 | "krónur": 1, 691 | "þ.kr.": 1e3, 692 | "þ.kr": 1e3, 693 | "þús.kr.": 1e3, 694 | "þús.kr": 1e3, 695 | "m.kr.": 1e6, 696 | "m.kr": 1e6, 697 | "mkr.": 1e6, 698 | "mkr": 1e6, 699 | "millj.kr.": 1e6, 700 | "millj.kr": 1e6, 701 | "mljó.kr.": 1e6, 702 | "mljó.kr": 1e6, 703 | "ma.kr.": 1e9, 704 | "ma.kr": 1e9, 705 | "mö.kr.": 1e9, 706 | "mö.kr": 1e9, 707 | "mlja.kr.": 1e9, 708 | "mlja.kr": 1e9, 709 | # "mrð.kr.": 1e9, 710 | # "mrð.kr": 1e9, 711 | # "billj.kr.": 1e12, 712 | # "billj.kr": 1e12, 713 | # "trillj.kr.": 1e18, 714 | # "trillj.kr": 1e18, 715 | } 716 | 717 | # Króna amount strings allowed before a number, e.g. "kr. 9.900" 718 | ISK_AMOUNT_PRECEDING = frozenset(("kr.", "kr", "krónur")) 719 | 720 | # URI scheme prefixes 721 | URI_PREFIXES = ( 722 | "http://", 723 | "https://", 724 | "file://", 725 | "ftp://", 726 | "ssh://", 727 | "sftp://", 728 | "smb://", 729 | "git://", 730 | "git+ssh://", 731 | "svn://", 732 | "svn+ssh://", 733 | "imap://", 734 | "rtmp://", 735 | "rtsp://", 736 | "telnet://", 737 | "udp://", 738 | "vnc://", 739 | "irc://", 740 | "nntp://", 741 | "wss://", 742 | "ws://", 743 | "xmpp://", 744 | "mtqp://", 745 | "afp://", 746 | "nfs://", 747 | "mms://", 748 | "tftp://", 749 | "ldap://", 750 | ) 751 | 752 | TOP_LEVEL_DOMAINS = frozenset( 753 | ( 754 | "com", 755 | "org", 756 | "net", 757 | "edu", 758 | "gov", 759 | "mil", 760 | "int", 761 | "arpa", 762 | "eu", 763 | "biz", 764 | "info", 765 | "xyz", 766 | "online", 767 | "site", 768 | "tech", 769 | "top", 770 | "space", 771 | "news", 772 | "pro", 773 | "club", 774 | "loan", 775 | "win", 776 | "vip", 777 | "icu", 778 | "app", 779 | "blog", 780 | "shop", 781 | "work", 782 | "ltd", 783 | "mobi", 784 | "live", 785 | "store", 786 | "gdn", 787 | "art", 788 | "events", 789 | # ccTLDs 790 | "ac", 791 | "ad", 792 | "ae", 793 | "af", 794 | "ag", 795 | "ai", 796 | "al", 797 | "am", 798 | "ao", 799 | "aq", 800 | "ar", 801 | "as", 802 | "at", 803 | "au", 804 | "aw", 805 | "ax", 806 | "az", 807 | "ba", 808 | "bb", 809 | "bd", 810 | "be", 811 | "bf", 812 | "bg", 813 | "bh", 814 | "bi", 815 | "bj", 816 | "bm", 817 | "bn", 818 | "bo", 819 | "br", 820 | "bs", 821 | "bt", 822 | "bw", 823 | "by", 824 | "bz", 825 | "ca", 826 | "cc", 827 | "cd", 828 | "cf", 829 | "cg", 830 | "ch", 831 | "ci", 832 | "ck", 833 | "cl", 834 | "cm", 835 | "cn", 836 | "co", 837 | "cr", 838 | "cu", 839 | "cv", 840 | "cw", 841 | "cx", 842 | "cy", 843 | "cz", 844 | "de", 845 | "dj", 846 | "dk", 847 | "dm", 848 | "do", 849 | "dz", 850 | "ec", 851 | "ee", 852 | "eg", 853 | "er", 854 | "es", 855 | "et", 856 | "eu", 857 | "fi", 858 | "fj", 859 | "fk", 860 | "fm", 861 | "fo", 862 | "fr", 863 | "ga", 864 | "gd", 865 | "ge", 866 | "gf", 867 | "gg", 868 | "gh", 869 | "gi", 870 | "gl", 871 | "gm", 872 | "gn", 873 | "gp", 874 | "gq", 875 | "gr", 876 | "gs", 877 | "gt", 878 | "gu", 879 | "gw", 880 | "gy", 881 | "hk", 882 | "hm", 883 | "hn", 884 | "hr", 885 | "ht", 886 | "hu", 887 | "id", 888 | "ie", 889 | "il", 890 | "im", 891 | "in", 892 | "io", 893 | "iq", 894 | "ir", 895 | "is", 896 | "it", 897 | "je", 898 | "jm", 899 | "jo", 900 | "jp", 901 | "ke", 902 | "kg", 903 | "kh", 904 | "ki", 905 | "km", 906 | "kn", 907 | "kp", 908 | # "kr", # Clashes with "kr" abbreviation (e.g. "þús.kr" is a legitimate domain name) 909 | "kw", 910 | "ky", 911 | "kz", 912 | "la", 913 | "lb", 914 | "lc", 915 | "li", 916 | "lk", 917 | "lr", 918 | "ls", 919 | "lt", 920 | "lu", 921 | "lv", 922 | "ly", 923 | "ma", 924 | "mc", 925 | "md", 926 | "me", 927 | "mg", 928 | "mh", 929 | "mk", 930 | "ml", 931 | "mm", 932 | "mn", 933 | "mo", 934 | "mp", 935 | "mq", 936 | "mr", 937 | "ms", 938 | "mt", 939 | "mu", 940 | "mv", 941 | "mw", 942 | "mx", 943 | "my", 944 | "mz", 945 | "na", 946 | "nc", 947 | "ne", 948 | "nf", 949 | "ng", 950 | "ni", 951 | "nl", 952 | "no", 953 | "np", 954 | "nr", 955 | "nu", 956 | "nz", 957 | "om", 958 | "pa", 959 | "pe", 960 | "pf", 961 | "pg", 962 | "ph", 963 | "pk", 964 | "pl", 965 | "pm", 966 | "pn", 967 | "pr", 968 | "ps", 969 | "pt", 970 | "pw", 971 | "py", 972 | "qa", 973 | "re", 974 | "ro", 975 | "rs", 976 | "ru", 977 | "rw", 978 | "sa", 979 | "sb", 980 | "sc", 981 | "sd", 982 | "se", 983 | "sg", 984 | "sh", 985 | "si", 986 | "sk", 987 | "sl", 988 | "sm", 989 | "sn", 990 | "so", 991 | "sr", 992 | "ss", 993 | "st", 994 | "sv", 995 | "sx", 996 | "sy", 997 | "sz", 998 | "tc", 999 | "td", 1000 | "tf", 1001 | "tg", 1002 | "th", 1003 | "tj", 1004 | "tk", 1005 | "tl", 1006 | "tm", 1007 | "tn", 1008 | "to", 1009 | "tr", 1010 | "tt", 1011 | "tv", 1012 | "tw", 1013 | "tz", 1014 | "ua", 1015 | "ug", 1016 | "uk", 1017 | "us", 1018 | "uy", 1019 | "uz", 1020 | "va", 1021 | "vc", 1022 | "ve", 1023 | "vg", 1024 | "vi", 1025 | "vn", 1026 | "vu", 1027 | "wf", 1028 | "ws", 1029 | "ye", 1030 | "yt", 1031 | "za", 1032 | "zm", 1033 | "zw", 1034 | ) 1035 | ) 1036 | 1037 | # Regex to recognise domain names 1038 | MIN_DOMAIN_LENGTH = 4 # E.g. "t.co" 1039 | DOMAIN_REGEX = re.compile( 1040 | r"({0})({1}*)$".format( 1041 | r"|".join(r"\w\." + d for d in map(_escape, TOP_LEVEL_DOMAINS)), 1042 | PUNCTUATION_REGEX, 1043 | ), 1044 | re.UNICODE, 1045 | ) 1046 | 1047 | # A list of the symbols of the natural elements. 1048 | # Note that single-letter symbols should follow two-letter symbols, 1049 | # so that regexes do not match the single-letter ones greedily before 1050 | # the two-letter ones. 1051 | ELEMENTS = ( 1052 | "Ac", 1053 | "Ag", 1054 | "Al", 1055 | "Am", 1056 | "Ar", 1057 | "As", 1058 | "At", 1059 | "Au", 1060 | "Ba", 1061 | "Be", 1062 | "Bh", 1063 | "Bi", 1064 | "Bk", 1065 | "Br", 1066 | "B", 1067 | "Ca", 1068 | "Cd", 1069 | "Ce", 1070 | "Cf", 1071 | "Cl", 1072 | "Cm", 1073 | "Cn", 1074 | "Co", 1075 | "Cr", 1076 | "Cs", 1077 | "Cu", 1078 | "C", 1079 | "Db", 1080 | "Ds", 1081 | "Dy", 1082 | "Er", 1083 | "Es", 1084 | "Eu", 1085 | "Fe", 1086 | "Fl", 1087 | "Fm", 1088 | "Fr", 1089 | "F", 1090 | "Ga", 1091 | "Gd", 1092 | "Ge", 1093 | "He", 1094 | "Hf", 1095 | "Hg", 1096 | "Ho", 1097 | "Hs", 1098 | "H", 1099 | "In", 1100 | "Ir", 1101 | "I", 1102 | "Kr", 1103 | "K", 1104 | "La", 1105 | "Li", 1106 | "Lr", 1107 | "Lu", 1108 | "Lv", 1109 | "Mc", 1110 | "Md", 1111 | "Mg", 1112 | "Mn", 1113 | "Mo", 1114 | "Mt", 1115 | "Na", 1116 | "Nb", 1117 | "Nd", 1118 | "Ne", 1119 | "Nh", 1120 | "Ni", 1121 | "No", 1122 | "Np", 1123 | "N", 1124 | "Og", 1125 | "Os", 1126 | "O", 1127 | "Pa", 1128 | "Pb", 1129 | "Pd", 1130 | "Pm", 1131 | "Po", 1132 | "Pr", 1133 | "Pt", 1134 | "Pu", 1135 | "P", 1136 | "Ra", 1137 | "Rb", 1138 | "Re", 1139 | "Rf", 1140 | "Rg", 1141 | "Rh", 1142 | "Rn", 1143 | "Ru", 1144 | "Sb", 1145 | "Sc", 1146 | "Se", 1147 | "Sg", 1148 | "Si", 1149 | "Sm", 1150 | "Sn", 1151 | "Sr", 1152 | "S", 1153 | "Ta", 1154 | "Tb", 1155 | "Tc", 1156 | "Te", 1157 | "Th", 1158 | "Ti", 1159 | "Tl", 1160 | "Tm", 1161 | "Ts", 1162 | "U", 1163 | "V", 1164 | "W", 1165 | "Xe", 1166 | "Yb", 1167 | "Y", 1168 | "Zn", 1169 | "Zr", 1170 | ) 1171 | 1172 | # Regex to recognize molecules ('H2SO4') 1173 | # Note that we place a further constraint on the token so that 1174 | # it must contain at least one digit to qualify as a molecular formula 1175 | ELEMENTS_REGEX = r"|".join(ELEMENTS) 1176 | MOLECULE_REGEX = re.compile(r"^(({0})+\d*)+".format(ELEMENTS_REGEX)) 1177 | MOLECULE_FILTER = re.compile(r"\d") 1178 | 1179 | 1180 | # Validation of Icelandic social security numbers 1181 | KT_MAGIC = [3, 2, 7, 6, 5, 4, 0, 3, 2] 1182 | 1183 | 1184 | def valid_ssn(kt: str) -> bool: 1185 | """Validate Icelandic social security number ("kennitala")""" 1186 | if not kt or len(kt) != 11 or kt[6] != "-": 1187 | return False 1188 | m = 11 - sum((ord(kt[i]) - 48) * KT_MAGIC[i] for i in range(9)) % 11 1189 | c = ord(kt[9]) - 48 1190 | return m == 11 if c == 0 else m == c 1191 | 1192 | 1193 | # HTML escaped characters/ligatures, e.g. 'á' meaning 'á'. 1194 | # The following is a subset of HTML escape codes, roughly selected 1195 | # by analyzing the content of the Icelandic Gigaword Corpus (Risamálheild). 1196 | HTML_ESCAPES = { 1197 | # Icelandic letters 1198 | "aacute": "á", 1199 | "eth": "ð", 1200 | "eacute": "é", 1201 | "iacute": "í", 1202 | "oacute": "ó", 1203 | "uacute": "ú", 1204 | "yacute": "ý", 1205 | "thorn": "þ", 1206 | "aelig": "æ", 1207 | "ouml": "ö", 1208 | "Aacute": "Á", 1209 | "ETH": "Ð", 1210 | "Eacute": "É", 1211 | "Iacute": "Í", 1212 | "Oacute": "Ó", 1213 | "Uacute": "Ú", 1214 | "Yacute": "Ý", 1215 | "THORN": "Þ", 1216 | "AElig": "Æ", 1217 | "Ouml": "Ö", 1218 | # Punctuation 1219 | "amp": "&", 1220 | "lt": "<", 1221 | "gt": ">", 1222 | "quot": '"', 1223 | "apos": "'", 1224 | "bdquo": "„", 1225 | "ldquo": "“", 1226 | "rdquo": "”", 1227 | "lsquo": "‘", 1228 | "acute": "´", 1229 | "lcub": "{", 1230 | "rcub": "}", 1231 | "darr": "↓", 1232 | "uarr": "↑", 1233 | "ring": "˚", 1234 | "deg": "°", 1235 | "diam": "⋄", 1236 | "ordm": "º", 1237 | "ogon": "˛", 1238 | "hellip": "…", 1239 | "copy": "©", 1240 | "reg": "®", 1241 | "trade": "™", 1242 | # Spaces - all spaces are mapped to \x20 1243 | "nbsp": " ", 1244 | "ensp": " ", 1245 | "emsp": " ", 1246 | "thinsp": " ", 1247 | # Dashes and hyphens 1248 | "ndash": "–", 1249 | "mdash": "—", 1250 | # The soft hyphen ­ is mapped to an empty string 1251 | "shy": "", 1252 | # Other non-ASCII letters 1253 | "uuml": "ü", 1254 | "Uuml": "Ü", 1255 | "zcaron": "ž", 1256 | "Zcaron": "Ž", 1257 | "lstrok": "ł", 1258 | "Lstrok": "Ł", 1259 | "ntilde": "ñ", 1260 | "inodot": "ı", 1261 | # Ligatures 1262 | "filig": "fi", 1263 | "fllig": "fl", 1264 | } 1265 | 1266 | ESCAPES_REGEX = r"|".join(HTML_ESCAPES.keys()) 1267 | HTML_ESCAPE_REGEX = re.compile( 1268 | r"&((#x[0-9a-fA-F]{r1})|(#\d{r2})|({ex}))\;".format( 1269 | r1="{1,8}", r2="{1,10}", ex=ESCAPES_REGEX 1270 | ) 1271 | ) 1272 | -------------------------------------------------------------------------------- /src/tokenizer/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | 4 | Tokenizer for Icelandic text 5 | 6 | Copyright (C) 2016-2025 Miðeind ehf. 7 | Original author: Vilhjálmur Þorsteinsson 8 | 9 | This software is licensed under the MIT License: 10 | 11 | Permission is hereby granted, free of charge, to any person 12 | obtaining a copy of this software and associated documentation 13 | files (the "Software"), to deal in the Software without restriction, 14 | including without limitation the rights to use, copy, modify, merge, 15 | publish, distribute, sublicense, and/or sell copies of the Software, 16 | and to permit persons to whom the Software is furnished to do so, 17 | subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be 20 | included in all copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 25 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 26 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 27 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 28 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | 31 | This is an executable program wrapper (main module) for the Tokenizer 32 | package. It can be used to invoke the Tokenizer from the command line, 33 | or via fork() or exec(), with the command 'tokenize'. The main() function 34 | of this module is registered as a console_script entry point in setup.py. 35 | 36 | """ 37 | 38 | from typing import TextIO, Iterator, Callable, Any, Union, cast 39 | 40 | import sys 41 | import argparse 42 | import json 43 | from functools import partial 44 | 45 | from .definitions import AmountTuple, BIN_Tuple, NumberTuple, PunctuationTuple 46 | from .tokenizer import TOK, Tok, tokenize 47 | from . import __version__ as tokenizer_version 48 | 49 | 50 | ReadFile = argparse.FileType("r", encoding="utf-8") 51 | WriteFile = argparse.FileType("w", encoding="utf-8") 52 | 53 | # Define the command line arguments 54 | 55 | parser = argparse.ArgumentParser(description="Tokenizes Icelandic text") 56 | 57 | parser.add_argument( 58 | "infile", 59 | nargs="?", 60 | type=ReadFile, 61 | default=sys.stdin, 62 | help="UTF-8 text file to tokenize", 63 | ) 64 | 65 | parser.add_argument( 66 | "outfile", 67 | nargs="?", 68 | type=WriteFile, 69 | default=sys.stdout, 70 | help="UTF-8 output text file", 71 | ) 72 | 73 | group = parser.add_mutually_exclusive_group() 74 | 75 | group.add_argument( 76 | "--csv", help="Output one token per line in CSV format", action="store_true" 77 | ) 78 | group.add_argument( 79 | "--json", help="Output one token per line in JSON format", action="store_true" 80 | ) 81 | 82 | parser.add_argument( 83 | "-s", 84 | "--one_sent_per_line", 85 | action="store_true", 86 | help="Input contains one sentence per line", 87 | ) 88 | 89 | parser.add_argument( 90 | "-m", 91 | "--convert_measurements", 92 | action="store_true", 93 | help="Degree signal in temperature tokens normalized (200° C -> 200 °C)", 94 | ) 95 | 96 | parser.add_argument( 97 | "-p", 98 | "--coalesce_percent", 99 | action="store_true", 100 | help=( 101 | "Numbers combined into one token with percentage word forms " 102 | "(prósent/prósentustig/hundraðshlutar)" 103 | ), 104 | ) 105 | 106 | parser.add_argument( 107 | "-n", 108 | "--normalize", 109 | action="store_true", 110 | help="Outputs normalized value of punctuation tokens instead of original text", 111 | ) 112 | 113 | parser.add_argument( 114 | "-o", 115 | "--original", 116 | action="store_true", 117 | help="Outputs original text of tokens", 118 | ) 119 | 120 | parser.add_argument( 121 | "-g", 122 | "--keep_composite_glyphs", 123 | action="store_true", 124 | help="Composite glyphs not replaced with a single code point", 125 | ) 126 | 127 | parser.add_argument( 128 | "-e", 129 | "--replace_html_escapes", 130 | action="store_true", 131 | help="Escape codes from HTML replaced", 132 | ) 133 | 134 | parser.add_argument( 135 | "-c", 136 | "--convert_numbers", 137 | action="store_true", 138 | help=( 139 | "English-style decimal points and thousands separators " 140 | "in numbers changed to Icelandic style" 141 | ), 142 | ) 143 | 144 | parser.add_argument( 145 | "-k", 146 | "--handle_kludgy_ordinals", 147 | type=int, 148 | default=0, 149 | help=( 150 | "Kludgy ordinal handling defined.\n" 151 | "\t0: Returns the original word form.\n" 152 | "\t1: Ordinals returned as pure words.\n" 153 | "\t2: Ordinals returned as numbers." 154 | ), 155 | ) 156 | 157 | parser.add_argument( 158 | "-v", 159 | "--version", 160 | action="version", 161 | version=f"%(prog)s {tokenizer_version}", 162 | help="Show the version number and exit", 163 | ) 164 | 165 | 166 | def main() -> None: 167 | """Main function, called when the tokenize command is invoked""" 168 | 169 | args = parser.parse_args() 170 | options: dict[str, bool] = {} 171 | 172 | def quote(s: str) -> str: 173 | """Return the string s within double quotes, and with any contained 174 | backslashes and double quotes escaped with a backslash""" 175 | return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"' 176 | 177 | def spanquote(lst: list[int]) -> str: 178 | """Return the list lst as a string within double quotes""" 179 | return '"' + "-".join(str(x) for x in lst) + '"' 180 | 181 | def gen(f: TextIO) -> Iterator[str]: 182 | """Generate the lines of text in the input file""" 183 | for line in f: 184 | yield line 185 | 186 | def val(t: Tok, quote_word: bool = False) -> Any: 187 | """Return the value part of the token t""" 188 | if t.val is None: 189 | return None 190 | if t.kind == TOK.WORD: 191 | # Get the full expansion of an abbreviation 192 | mm = cast(list[BIN_Tuple], t.val) 193 | if quote_word: 194 | # Return a |-delimited list of possible meanings, 195 | # joined into a single string 196 | return quote("|".join(m[0] for m in mm)) 197 | # Return a list of all possible meanings 198 | return [m[0] for m in mm] 199 | if t.kind in {TOK.PERCENT, TOK.NUMBER, TOK.CURRENCY}: 200 | return cast(NumberTuple, t.val)[0] 201 | if t.kind == TOK.AMOUNT: 202 | am = cast(AmountTuple, t.val) 203 | if quote_word: 204 | # Format as "1234.56|USD" 205 | return '"{0}|{1}"'.format(am[0], am[1]) 206 | return am[0], am[1] 207 | if t.kind == TOK.S_BEGIN: 208 | return None 209 | if t.kind == TOK.PUNCTUATION: 210 | pt = cast(PunctuationTuple, t.val) 211 | return quote(pt[1]) if quote_word else pt[1] 212 | if quote_word and t.kind in { 213 | TOK.DATE, 214 | TOK.TIME, 215 | TOK.DATEABS, 216 | TOK.DATEREL, 217 | TOK.TIMESTAMP, 218 | TOK.TIMESTAMPABS, 219 | TOK.TIMESTAMPREL, 220 | TOK.TELNO, 221 | TOK.NUMWLETTER, 222 | TOK.MEASUREMENT, 223 | }: 224 | # Return a |-delimited list of numbers 225 | vv = cast(tuple[Any, ...], t.val) 226 | return quote("|".join(str(v) for v in vv)) 227 | if quote_word and isinstance(t.val, str): 228 | return quote(t.val) 229 | return t.val 230 | 231 | to_text: Callable[[Tok], str] 232 | if args.normalize: 233 | to_text = lambda t: t.punctuation if t.kind == TOK.PUNCTUATION else t.txt 234 | elif args.original: 235 | to_text = lambda t: t.original or "" 236 | else: 237 | to_text = lambda t: t.txt 238 | 239 | if args.convert_measurements: 240 | options["convert_measurements"] = True 241 | 242 | if args.coalesce_percent: 243 | options["coalesce_percent"] = True 244 | 245 | if args.keep_composite_glyphs: 246 | # True is the default in tokenizer.py 247 | options["replace_composite_glyphs"] = False 248 | 249 | if args.replace_html_escapes: 250 | options["replace_html_escapes"] = True 251 | 252 | if args.convert_numbers: 253 | options["convert_numbers"] = True 254 | 255 | if args.one_sent_per_line: 256 | options["one_sent_per_line"] = True 257 | 258 | if args.handle_kludgy_ordinals: 259 | options["handle_kludgy_ordinals"] = args.handle_kludgy_ordinals 260 | 261 | if args.original: 262 | options["original"] = args.original 263 | 264 | # Configure our JSON dump function 265 | json_dumps = partial(json.dumps, ensure_ascii=False, separators=(",", ":")) 266 | curr_sent: list[str] = [] 267 | tsep = "" if args.original else " " # token separator 268 | for t in tokenize(gen(args.infile), **options): 269 | if args.csv: 270 | # Output the tokens in CSV format, one line per token 271 | if t.txt: 272 | print( 273 | "{0},{1},{2},{3},{4}".format( 274 | t.kind, 275 | quote(t.txt), 276 | val(t, quote_word=True) or '""', 277 | '""' if t.original is None else quote(t.original), 278 | "[]" if t.origin_spans is None else spanquote(t.origin_spans), 279 | ), 280 | file=args.outfile, 281 | ) 282 | elif t.kind == TOK.S_END: 283 | # Indicate end of sentence 284 | print('0,"","","",""', file=args.outfile) 285 | elif args.json: 286 | # Output the tokens in JSON format, one line per token 287 | d: dict[str, Union[str, list[int]]] = { "k": TOK.descr[t.kind] } 288 | if t.txt is not None: 289 | d["t"] = t.txt 290 | v = val(t) 291 | if v is not None: 292 | d["v"] = v 293 | if t.original is not None: 294 | d["o"] = t.original 295 | if t.origin_spans is not None: 296 | d["s"] = t.origin_spans 297 | print(json_dumps(d), file=args.outfile) 298 | else: 299 | # Normal shallow parse, sentences separated by newline by default, 300 | # tokens separated by spaces 301 | if t.kind in TOK.END: 302 | # End of sentence/paragraph 303 | if curr_sent: 304 | print(tsep.join(curr_sent), file=args.outfile) 305 | curr_sent = [] 306 | txt = to_text(t) 307 | if txt: 308 | curr_sent.append(txt) 309 | if curr_sent: 310 | print(tsep.join(curr_sent), file=args.outfile) 311 | 312 | 313 | if __name__ == "__main__": 314 | main() 315 | -------------------------------------------------------------------------------- /src/tokenizer/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mideind/Tokenizer/23c8f3d2b714b78fb16e10dd5b287dccd3b72893/src/tokenizer/py.typed -------------------------------------------------------------------------------- /test/Overview.txt: -------------------------------------------------------------------------------- 1 | Bil eða hástaf vantar [SS,TS] [1-3|1-5] 2 | #A: Finnur nýja setningu þó að bil vanti eða hástaf. 3 | #B: Finnur nýja setningu nema þegar hástaf vantar á eftir skammstöfun í lok setningar. 4 | #C: Eins og #B. 5 | Skammstafanir\setningastaða [SS,TS,TF] [4-89] 6 | Án punkts; acronyms [SS,TS] [4-8|6-12] 7 | #A: Heldur hástafa orðum saman, líka með bandstriki. Leyfir lágstafa stafi á eftir, heldur saman. 8 | #B: Sama 9 | #C: Sama 10 | Einn punktur; styttingar fyrir beygjanleg orð [SS, TS, TF] [9-20|13-27] 11 | #A: Heldur skammstöfunum saman með punktinum. Skiptir setningum rétt ef aftast í setningu. Ef í lok setningar er punkturinn ekki hluti af skammstöfuninni. 12 | #B: Sama 13 | #C: Sama 14 | Tveir eða fleiri punktar; ao frasar, st frasar [SS, TS, TF] [21-30|28-41] 15 | #A: Heldur skammstöfunum saman og lokapunkti. Ef í lok setningar er lokapunkturinn ekki hluti af skammstöfun. Skiptir setningum rétt ef aftast í setningu. 16 | #B: Sama 17 | #C: Sama 18 | Margorða skammstafanir [TS, TF] [31-33|42-45] 19 | #A: Hefur ekki áhrif á grunntókun, þekkir "þ." og "m." svo nær grunntókun rétt. Lokapunktur er ekki hluti af skammstöfun í lok setningar. 20 | #B: Sama 21 | #C: Sama 22 | Hástafa skammstafanir [SS, TS, TF] [34-42|46-57] 23 | #A: Skiptir rétt í setningar ef aftast í setningu. Lokapunktur ekki hluti af skammstöfun í lok setningar. 24 | #B: Sama 25 | #C: Sama 26 | Skammstafanir með margar merkingar [TF] [43-46|58-61] 27 | #A: Hefur ekki áhrif á grunntókun. 28 | #B: Sama 29 | #C: Sama 30 | Óþekktar/erlendar skammstafanir [SS, TS, TF] [47-55|62-77] 31 | #A: Nær að skipta rétt í setningar þó aftast í setningu. 32 | #B: Þekkir algengar erlendar skammstafanir, annað er ekki hægt að tækla. Skiptir rétt í setningar að mestu, ef lágstafur á eftir. Getur ekki ef t.d. á eftir kemur sérnafn. Ef næsta setning byrjar á tölu skiptir hún því ekki upp. 33 | #C: Sama 34 | Orð sem lítur út eins og skammstöfun aftast í setningu [SS, TS, TF] [56-57|78-82] 35 | #A: Skiptir setningum rétt, annar munur sést ekki í grunntókun. 36 | #B: Skiptir að mestu rétt út frá há-/lágstaf. Ef sérnafn kemur á eftir, skiptir setningu. Ef næsta setning hefst á tölu, skiptir ekki upp. 37 | #C: Sama 38 | Rangar skammstafanir án punkta [SS, TS, TF] [58-84|83-120] 39 | Var með einum punkti [SS, TS, TF] [58-64|83-91] 40 | #A: Skiptir setningum rétt þegar aftast í setningu (enda sést munurinn ekki), hefur annars lítil áhrif á grunntókun. 41 | #B: Sama. Greinist sem óþekkt nafnorð. 42 | #C: Sama og #B 43 | Var með mörgum punktum [SS, TS, TF] [65-84|92-120] 44 | #A: Skiptir rétt í setningar þó að sé aftast í setningu. Skiptir ekki upp þegar í miðri setningu. Heldur tókum saman, fyrir utan lokapunkt þegar aftast í setningu. 45 | #B: Skiptir að mestu rétt út frá há-/lágstaf. Ef sérnafn kemur á eftir, skiptir setningu. Ef næsta setning hefst á tölu, skiptir ekki upp. Ef öllum punktum er sleppt er þetta sagt óþekkt sérnafn og setningu er alltaf skipt þegar það finnst aftast í setningu. Annars er gert ráð fyrir að þetta sé óþekkt skammstöfun. Jafnvel er hætta á að þetta greinist sem lén (t.om)! TODO skoða hvað hægt að gera hér til að hjálpa málrýni. 46 | #C: Sama og #B 47 | Skammstafanir ranglega með punktum; acronyms [SS, TS, TF] [84-88|121-125] 48 | #A: Heldur tókum saman. Skiptir rétt í setningar þó að sé aftast í setningu. Skiptir ekki setningu þegar tóki birtist í henni miðri. 49 | #B: Sama, byrjar nýja setningu á næsta orði ef það hefst á hástaf, skiptir ekki ef hefst á lágstaf eða tölu eða öðru. 50 | #C: Sama og #B 51 | Skammstafanir með bandstriki (e-a) [TS, TF] [89|126] 52 | #A: Heldur tóka saman. Hefur ekki áhrif á setningaskiptingu. 53 | #B: Sama 54 | #C: Sama 55 | Mælieiningar\setn.staða\tölur+útskr.tölur [SS, TS, TF] [90-12004|127-15968] 56 | Fyrir framan með bili [SS, TS, TF] [90-1769|127-2366] 57 | Skammstafaðar mælieiningar án punkts [TS, TF] [90-329|127-446] 58 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 59 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Sameinar gjaldmiðlatákn tölu á eftir, s.s. tekur bilið. 60 | #C: Sama og #B. 61 | Skammstafaðar mælieiningar með punkti [SS, TS, TF] [330-570|447-766] 62 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 63 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. 64 | #C: Sama og #B. 65 | Skammstafaðar mælieiningar ranglega án punkts [TS, TF] [571-809|767-1086] 66 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 67 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. 68 | #C: Sama og #B. 69 | Gjaldmiðlaskammstafanir [SS, TS, TF] [810-1049|1087-1406] 70 | #A: Heldur tölu og gjaldmiðli eins og er í grunntókun en sameinar í einn tóka í djúptókun. 71 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. 72 | #C: Sama og #B. 73 | Gjaldmiðlaskammstafanir ranglega án punkts [TS, TF] [1050-1289|1407-1726] 74 | #A: Heldur tölu og gjaldmiðli eins og er í grunntókun en sameinar í einn tóka í djúptókun. 75 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. 76 | #C: Sama og #B. 77 | Gjaldmiðlaskammstafanir ranglega með punkti [SS, TS, TF] [1290-1529|1727-2046] 78 | #A: Heldur tölu og gjaldmiðli eins og er í grunntókun en sameinar í einn tóka í djúptókun. 79 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. 80 | #C: Sama og #B. 81 | Gjaldmiðlatákn [TS, TF] [1530-1769|2047-2366] 82 | #A: Heldur tölu og gjaldmiðli eins og er í grunntókun en sameinar í einn tóka í djúptókun. 83 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. 84 | #C: Sama og #B. 85 | Fyrir framan án bils [SS, TS, TF] [1770-3289|2367-4446] 86 | Skammstafaðar mælieiningar án punkts [TS, TF] [1770-2009|2367-2686] 87 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 88 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Rífur mælieiningu framan af, ef það er formerki á tölunni er það haft sér (kg + 54). 89 | #C: Sama og #B. 90 | Skammstafaðar mælieiningar með punkti [SS, TS, TF] [2010-2249|2687-3006] 91 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 92 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Rífur mælieiningu framan af, ef það er formerki á tölunni er það haft sér (klst. + 54). 93 | #C: Sama og #B. 94 | Skammstafaðar mælieiningar ranglega án punkts [TS, TF] [2250-2489|3007-3326] 95 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 96 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. 97 | #C: Sama og #B. 98 | Gjaldmiðlaskammstafanir [SS, TS, TF] [2490-2729|3327-3646] 99 | #A: Heldur tölu og gjaldmiðli eins og er í grunntókun en sameinar í einn tóka í djúptókun. 100 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Heldur mælieiningunni með tölunni, komið sértilvik fyrir það, nema þegar það er formerki framan á tölu. Þá er allt rifið í sundur (USD + 54) 101 | #C: Sama og #B. 102 | Gjaldmiðlaskammstafanir ranglega án punkts [TS, TF] [2730-2809|3647-3806] 103 | #A: Heldur tölu og gjaldmiðli eins og er í grunntókun en sameinar í einn tóka í djúptókun. 104 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Rífur gjaldmiðil framan af, ef það er formerki á tölunni er það haft sér (dkr + 54) 105 | #C: Sama og #B. 106 | Gjaldmiðlaskammstafanir ranglega með punkti [SS, TS, TF] [2810-3049|3807-4126] 107 | #A: Heldur tölu og gjaldmiðli eins og er í grunntókun en sameinar í einn tóka í djúptókun. 108 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Rífur gjaldmiðil framan af, ef það er formerki á tölunni er það haft sér (USD. + 54) 109 | #C: Sama og #B. 110 | Gjaldmiðlatákn [TS, TF] [3050-3289|4127-4446] 111 | #A: Heldur tölu og gjaldmiðli eins og er í grunntókun en sameinar í einn tóka í djúptókun. 112 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Heldur gjaldmiðlinum með tölunni, komið sértilvik fyrir það, nema þegar það er formerki framan á tölu. Þá er allt rifið í sundur ($ + 54) 113 | #C: Sama og #B. 114 | Fyrir aftan með bili [SS, TS, TF] [3290-8644|4447-11489] 115 | Skammstafaðar mælieiningar án punkts [TS, TF] [3290-3568|4447-4818] 116 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 117 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars sama og #A. 118 | #C: Sama og #B. 119 | Skammstafaðar mælieiningar með punkti [SS, TS, TF] [3569-3940|4819-5190] 120 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 121 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars sama og #A. 122 | #C: Sama og #B. 123 | Skammstafaðar mælieiningar ranglega án punkts [TS, TF] [3941-4219|5191-5562] 124 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 125 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars eins og #A. 126 | #C: Sama og #B. 127 | Skammstafaðar mælieiningar ranglega með punkti [SS, TS, TF] [4220-4498|5563-5960] 128 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 129 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Nær ekki að þekkja ranga mælieiningu þegar útskrifuð tala er fyrir framan (tveir dl.), skiptir setningu þar. Annars eins og #A. 130 | #C: Sama og #B. 131 | Útskrifaðar mælieiningar [TS, TF] [4499-4777|5961-6332] 132 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 133 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars eins og #A. 134 | #C: Sama og #B. 135 | Mælieiningar með / [ TS, TF] [4778-5056|6333-6704] 136 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 137 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Þekkir ekki sameinaða mælieiningu þegar útskrifuð tala kemur á undan, hefur ekki áhrif á setningaskiptingu en skiptir tóka upp (km / klst.). Annars eins og #A. 138 | #C: Sama og #B. 139 | Gjaldmiðlaskammstafanir [SS, TS, TF] [5057-5335|6705-7076] 140 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 141 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars eins og #A. 142 | #C: Sama og #B. 143 | Gjaldmiðlaskammstafanir ranglega án punkts [TS, TF] [5336-5614|7077-7448] 144 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 145 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars eins og #A. 146 | #C: Sama og #B. 147 | Gjaldmiðlaskammstafanir ranglega með punkti [SS, TS, TF] [5615-5893|7449-7820] 148 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 149 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars eins og #A. Nota bene, höndlar útskrifaðar tölur. 150 | #C: Sama og #B. 151 | Útskrifaðir gjaldmiðlar [TS, TF] [5894-6172|7821-8192] 152 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 153 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars eins og #A. 154 | #C: Sama og #B. 155 | Gjaldmiðlatákn [TS, TF] [6173-6451|8193-8564] 156 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 157 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars eins og #A. 158 | #C: Sama og #B. 159 | Prósentumerki [TS, TF] [6452-6730|8565-8936] 160 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 161 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars eins og #A. 162 | #C: Sama og #B. 163 | Útskrifaðar prósentur [TS, TF] [6731-7009|8937-9308] 164 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 165 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars eins og #A. 166 | #C: Sama og #B. 167 | Prómill [TS, TF] [7010-7288|9309-9680] 168 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 169 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars eins og #A. 170 | #C: Sama og #B. 171 | Útskrifuð prómill [TS, TF] [7289-7567|9681-10052] 172 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 173 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars eins og #A. 174 | #C: Sama og #B. 175 | Tommumerki [TS, TF] [7568-7807|10053-10372] 176 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 177 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Heldur í sundur í tókaranum, nota bene breytir í lokandi gæsalappir og dregur að orði á seinni stigum, sem er líklega það skásta í stöðunni. Annars eins og #A. 178 | #C: Sama og #B. 179 | ² og ³ [TS, TF] [7808-8086|10373-10744] 180 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 181 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Annars eins og #A. 182 | #C: Sama og #B. 183 | Gráðumerki [TS, TF] [8087-8644|10745-11488] 184 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 185 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Í lok setningar rífur gráðumerkið af C (nær samt að sameina tölu og ° C í einn tóka á seinni stigum). Ef bil er á eftir gráðumerki helst það í sundur, setningaskil eru sett inn á undan hástaf en ekki á undan lágstaf eða tölu. Nota bene þetta er allt sameinað rétt á seinni stigum. 186 | #C: Sama og #B. 187 | Fyrir aftan án bils [SS, TS, TF] [8645-12004|11489-15968] 188 | Skammstafaðar mælieiningar án punkts [TS, TF] [8645-8884|11489-11808] 189 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 190 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Ekki heldur þegar "1/3" fyrir aftan. Skilur þá í sundur og bil er sett á undan mælieiningu. Þegar bil er á undan broti þekkist talan ekki sem heild og bil er sett á undan mælieiningu. 191 | #C: Sama og #B. 192 | Skammstafaðar mælieiningar með punkti [SS, TS, TF] [8885-9124|11809-12128] 193 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 194 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Ekki heldur þegar "1/3" fyrir aftan. Skilur þá í sundur og bil er sett á undan mælieiningu. Þegar bil er á undan broti þekkist talan ekki sem heild og bil er sett á undan mælieiningu. 195 | #C: Sama og #B. 196 | Skammstafaðar mælieiningar ranglega án punkts [TS, TF] [9125-9364|12129-12448] 197 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 198 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Ekki heldur þegar "1/3" fyrir aftan. Skilur þá í sundur og bil er sett á undan mælieiningu. Þegar bil er á undan broti þekkist talan ekki sem heild og bil er sett á undan mælieiningu. 199 | #C: Sama og #B. 200 | Skammstafaðar mælieiningar ranglega með punkti [SS, TS, TF] [9365-9604|12449-12768] 201 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 202 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Ekki heldur þegar "1/3" fyrir aftan. Skilur þá í sundur og bil er sett á undan mælieiningu. Þegar bil er á undan broti þekkist talan ekki sem heild og bil er sett á undan mælieiningu. 203 | #C: Sama og #B. 204 | Mælieiningar með / [TS, TF] [9605-9844|12769-13088] 205 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 206 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Ekki heldur þegar "1/3" fyrir aftan. Þegar bil er á undan broti þekkist talan ekki sem heild. Þegar talan þekkist, næst ekki að greina sameinuðu mælieininguna rétt (af því að klesst upp við tölu) og bara "km" er greint með tölu. Þegar talan þekkist ekki er tala og rest rifið í sundur og mælieiningu er haldið saman en frá tölu. 207 | #C: Sama og #B. 208 | Gjaldmiðlaskammstafanir [SS, TS, TF] [9845-10084|13089-13408] 209 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 210 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Ekki heldur þegar "1/3" fyrir aftan. Skilur þá í sundur. Þegar bil er á undan broti þekkist talan ekki sem heild. Setur alltaf bil á undan gjaldmiðli. 211 | #C: Sama og #B. 212 | Gjaldmiðlaskammstafanir ranglega með punkti [SS, TS, TF] [10085-10324|13409-13728] 213 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 214 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Setur alltaf bil á undan gjaldmiðli. 215 | #C: Sama og #B. 216 | Gjaldmiðlatákn [TS, TF] [10325-10564|13729-14048] 217 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 218 | #B: Þekkir ekki háar tölur með ¼,½ eða 1/3 fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Ekki heldur þegar "1/3" fyrir aftan. Skilur þá í sundur og bil er sett á undan mælieiningu. Þegar bil er á undan broti þekkist talan ekki sem heild og bil er sett á undan mælieiningu. 219 | #C: Sama og #B. 220 | Prósentumerki [TS, TF] [10565-10804|14049-14368] 221 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 222 | #B: Þekkir ekki háar tölur með ¼,½ eða 1/3 fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Ekki heldur þegar "1/3" fyrir aftan. Skilur þá í sundur og bil er sett á undan mælieiningu. Þegar bil er á undan broti þekkist talan ekki sem heild og bil er sett á undan mælieiningu. 223 | #C: Sama og #B. 224 | Prómill [TS, TF] [10805-11044|14369-14688] 225 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 226 | #B: Þekkir ekki háar tölur með ¼,½ eða 1/3 fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Ekki heldur þegar "1/3" fyrir aftan. Skilur þá í sundur og bil er sett á undan mælieiningu. Þegar bil er á undan broti þekkist talan ekki sem heild og bil er sett á undan mælieiningu. 227 | #C: Sama og #B. 228 | Tommumerki [TS, TF] [11045-11284|14689-15008] 229 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 230 | #B: Setur alltaf bil á undan tommumerki. 231 | #C: Sama og #B. 232 | ² og ³ [TS, TF] [11285-11524|15009-15328] 233 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 234 | #B: Þekkir ekki háar tölur með ¼,½ eða 1/3 fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Ekki heldur þegar "1/3" fyrir aftan. Skilur þá í sundur og bil er sett á undan mælieiningu. Þegar bil er á undan broti þekkist talan ekki sem heild og bil er sett á undan mælieiningu. 235 | #C: Sama og #B. 236 | Gráðumerki [TS, TF] [11525-12004|15329-15968] 237 | #A: Heldur tölu og mælieiningu eins og er í grunntókun en sameinar í einn tóka í djúptókun. 238 | #B: Þekkir ekki háar tölur með ¼,½ eða 1/3 fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Ekki heldur þegar "1/3" fyrir aftan. Skilur þá í sundur. Þegar bil er á undan broti þekkist talan ekki sem heild. Þegar tala þekkist ekki er sett bil á undan og eftir gráðumerki. Ef þetta gerist í lok setningar, s.s. að mælieiningin þekkist ekki, fer skiptingin eftir því hvort hástafur, lágstafur, tala eða annað kemur á eftir. Ath. á eftir "1/3" er "°C" haldið saman en rifið af tölunni. 239 | #C: Sama og #B. 240 | Raðtölur [SS, TS, TF] [12005-12018|15969-15986] 241 | #A: Heldur rómverskum tölum saman. Veit hvenær á að skipta setningu fyrir aftan og hvenær ekki. Þekkir blandaðar tölur (4ra, 2svar). 242 | #B: Heldur rómverskum tölum saman. Byrjar nýja setningu ef það er hástafur fyrir aftan, ef lágstafur eða tala heldur setningin áfram. Greinir raðtölu aftast í setningu sem tölu. 243 | #C:Sama og #B. 244 | Tölur\setn.staða\formerki\þúsundamerki\brot [SS, TS, TF] [12019-12378|15987-16466] 245 | #A: Þekkir tölur á ólíku formi, sameinar í einn tóka í djúptókun en heldur textanum upprunalegum í grunntókun. 246 | #B: Þekkir ekki háar tölur með ¼½ fyrir aftan. Ekki heldur lágar þegar formerki (+-) fyrir framan. Skilur þá í sundur. Þekkir annars tölur með formerkjum, erlend þúsunda- og brotamerki o.fl. 247 | #C: Sama og #B. 248 | Útskrifaðar tölur\setn.staða [TS, TF] [12379-12420|16467-16508] 249 | #A: Hefur ekki áhrif á setningaskiptingu. Heldur textanum réttum en sameinar í djúptókun. 250 | #B: Margorða útskrifaðar tölur eru réttar í grunntókun en ekki næst að sameina allt í djúptókun. 251 | #C: Sama og #B. 252 | Aðrar tölur [SS, TS, TF] [12421-12429|16509-16516] 253 | #A: Öllum tölum haldið rétt saman. Hefur áhrif á tókaskiptingu, því almenn brot t.d. líta oft út eins og dagsetningar. 254 | #B: Þekkir ekki 9,- svo rífur greinarmerkin af og í sundur. Tekur almenn brot í sundur nema þegar eru eins og dagsetningar. 255 | #C: Sama og #B. 256 | Greinarmerki [SS, TS, TF] [12430-12529|16517-16636] 257 | Ólíkar samsetningar greinarmerkja [] [12430-12439|16517-16533] 258 | #A: Greinarmerkjum er haldið saman. 259 | #B: Mörg greinarmerki í röð eru sameinuð, staðlaða gildið er gildi fyrsta. Bara gert þegar þau ljúka setningu saman; t.d. ?!? og margir punktar í röð. Skiptir setningu ranglega þegar !", eða ?", eða bara !" og ?" í lok beinnar ræðu. Nær því réttu ef bara ," í lok beinnar ræðu. 260 | #C: Sama og #B. 261 | Þrípunktur og margir punktar [SS, TS, TF] [12440-12464|16534-16565] 262 | #A: Mörgum punktum er haldið saman. Skipt er rétt í setningar í kringum þá. 263 | #B: Þekkir úrfellingarmerki og heldur setningu áfram ef við á. Ath. hefur úrfellingarmerkið í lok fyrri setningar ef á eftir kemur ný setning. Sameinar marga punkta, staðlaða gildið í djúptókuninni er þrípunktur. Byrjar nýja setningu á eftir ef á eftir kemur hástafur, heldur áfram í sömu setningu ef á eftir kemur lágstafur eða tala. Sameinar ekki marga punkta eða þrípunkt og svo spurningarmerki í einn tóka. Þekkir ekki úrfellingarmerki í lok orðs til að tákna styttingu. 264 | #C: Sama og #B. 265 | Gæsalappir [TS, TF] [12465-12529|16566-16636] 266 | #A: Gæsalappir þekkjast rétt en þeim er ekki breytt sjálfkrafa. 267 | #B: Tekur gæsalappir frá orðum. Íslenskar, einfaldar gæsalappir leyfast ekki sem úrfellingarmerki á erlendum orðum en erlent úrfellingarmerki leyfist. Þekkir erlendar gæsalappir, heldur þeim eins og eru en í djúptókun fá þær staðlað gildi sem er samsvarandi íslenskar gæsalappir. Ef setning endar á beinni ræðu innan einfaldra gæsalappa og á eftir kemur ný setning er aftari gæsalöppunum haldið með síðustu setningunni. 268 | #C: Sama og #B. 269 | Erlendar 11279-11283 270 | Úrfellingarkomma 11284-11284 271 | Ólíkar samsetningar kringum orð 11285-11342 272 | Lagagreinar [TS, TF] [12530-12532|16637-16640] 273 | #A: Lagagreinar þekkjast, jafnvel er hægt að tengja þær við réttan lagatexta á vefnum. 274 | #B: Lagagreinar þekkjast ekki, þeim er skipt upp á skástriki (50 / 2000). 275 | #C: Sama og #B. 276 | Símanúmer\setn.staða\skiptimerki [SS, TS, TF] [12533-12592|16641-16698] 277 | #A: Leyfileg símanúmer þekkjast, þeim er ekki skipt upp. 278 | #B: Símanúmer sem hefjast á leyfilegum strengjum þekkjast, annað er kallað vörunúmer (SerialNumber). Skiptimerki er ekki staðlað í grunntókun. Íslenskur landskóði þekkist með 00 eða plúsmerki fyrir framan, það þarf að bæta við það safn. Skiptimerki þar getur verið bil eða bandstrik. Ef það er ekkert þekkist símanúmerið ekki, það telst tala og ef næsta setning hefst á tölu skiptist setning ekki þar. Það mætti bæta við stuðningi við 112, en líklega er það of margrætt. 279 | #C: Sama og #B. 280 | Bandstrik á milli [] [12533-12550|16641-16658] 281 | Enginn landskóði [12533-12538|16641-16646] 282 | 00landskóði [12539-12544|16647-16652] 283 | +landskóði [12544-12550|16653-16658] 284 | Bil [] [12551-12568|16659-16676] 285 | Enginn landskóði [12551-12556|16659-16664] 286 | 00landskóði [12557-12562|16665-16670] 287 | +landskóði [12563-12568|16671-16676] 288 | Ekkert bil [] [12569-12586|16677-16698] 289 | Enginn landskóði [12569-12574|16677-16682] 290 | 00landskóði [12575-12580|16683-16687] 291 | +landskóði [12581-12586|166|166867-16692] 292 | Ógilt [] [12586-12592|16693-16698] 293 | Kennitala [TS, TF] [12593-12604|16699-16714] 294 | #A: Leyfilegar kennitölur þekkjast og þeim er haldið saman. 295 | #B: Kennitölur sem uppfylla skilyrði þekkjast, bæði manneskju og fyrirtækis. Aðrar eru teknar í sundur á bandstriki. 296 | #C: Sama og #B. 297 | Manneskju 298 | Fyrirtækis 299 | Ógild 300 | Bandstrik [SS, TF] [12605-12617|16715-16727] 301 | #A: Rétt er skipt í setningar í kringum ólík bandstrik. 302 | #B: Þeim er haldið í grunntókun eins og eru en eru stöðluð í djúptókun. Ekki næst að greina nýja setningu á eftir þeim þegar við á. 303 | #C: Sama og #B. 304 | Samsett orð [TS, TF] [12618-12657|16728-16767] 305 | #A: Samsetningum er haldið saman. 306 | #B: Samsetningum með bandstriki er haldið saman, nema þegar fyrri hlutinn er skammstöfun (Ph.D.-gráða verður Ph.D. - gráða). Bandstriki er haldið aftan á styttum fyrri hlutum. Þegar margir fyrri hlutar finnast og komma kemur á milli þeirra er henni haldið með fyrri hlutanum. Munurinn sést ekki í grunntókun en ekki tekst að sameina "þingkonur og -menn". Bandstrikinu er haldið aftan á styttum seinni hlutum. Skammstafanir eða samsetningar með skástriki þekkjast ekki og eru slitnar í sundur á skástriki. Samsetningar með "-/" þekkjast ekki, orðið er slitið í sundur á báðum greinarmerkjum. Munurinn sést ekki í grunntókun en ekki tekst að sameina samsetningar með margorða fyrri hluta í einn tóka. 307 | #C: Sama og #B. 308 | Bandstrik eða skástrik [12618-12647|16728-16757] 309 | Erlent sérnafn fremst [12618-12620|16728-16730] 310 | Margorða (erlent) sérnafn fremst [12621|16731] 311 | Sérnafn fremst [12622|16732] 312 | Viðurnefni eða átt framan á sérnafni [12623-12630|16733-16740] 313 | Skammstöfun fremst [12631-12633|16741-16743] 314 | Einn fyrri hluti [12644-12635|16744-16745] 315 | Tveir fyrri hlutar [12636-12637|16746-16747] 316 | Þrír fyrri hlutar [12638-12639|16748-16749] 317 | Fjórir fyrri hlutar [12640-12641|16750-16751] 318 | Einn seinni hluti [12642-12643|16752-16753] 319 | -/ samsetning [12644|16754] 320 | / samsetning [12645-12647|16755-16757] 321 | Ekkert bandstrik [12648-12657|16758-16767] 322 | Erlent sérnafn fremst [12648-12650|16758-16760] 323 | Margorða sérnafn fremst [12651-12652|16761-16762] 324 | Sérnafn fremst [12653-12654|16763-16764] 325 | Einn fyrri hluti [12655|16765] 326 | Margir fyrri hlutar [12656-12657|16766-16767] 327 | Nöfn [TS, TF] [12658-12664|16768-16774] 328 | #A: Hefur ekki áhrif á setningaskiptingu eða tókaskiptingu í grunntókun. Nöfn eru sameinuð rétt í tóka í djúptókun. 329 | #B: Sama í grunntókun. Mætti þekkja fleiri mynstur í djúptókun, og koma í veg fyrir of mikla græðgi í sameiningu. 330 | #C: Sama og #B. 331 | Tími [SS, TS, TF] [12665-12991|16775-17109] 332 | #A: Allar tegundir tímaliða þekkjast og eru sameinaðar í einn tóka. 333 | #B: Margt mætti sameina betur í djúptókun. Það sem hefur áhrif á grunntókun er hér talið upp. MM:SS þekkist ekki og er slitið í sundur á tvípunkti. GMT+4 þekkist ekki og er slitið í sundur á plúsmerki. Sekúndubrot þekkjast en upplýsingar um þau eru ekki komin inn í tókagildið. D.MM, D.MM., D.M og D.M. þekkist ekki þar sem það er svo margrætt, seinna dæmið hefur áhrif á setningaskiptingu. D-MM, DD-MM, D-M og DD-M þekkist ekki þar sem það er of margrætt, er slitið í sundur á bandstriki. 'YY þekkist ekki, er slitið í sundur. MM-YY þekkist ekki því of margrætt, slitið í sundur á bandstriki. YYYY.M.DD þekkist ekki, setningu skipt á undan M.DD. YYYY/M/DD og YYYY-M-DD þekkist ekki, slitið í sundur á fyrra skástriki og báðum bandstrikum. YYYY-YYYY þekkist ekki, slitið í sundur á bandstriki. 'mars-apríl' þekkist sem samsetning, þarf sértilfelli til að greina rétt sem tímabil. 'nóv.-des.' er skipt upp á bandstriki. Erlendu skammstafanirnar BC, AD, AM, PM, am og pm eru slitnar aftan af. 334 | #C: Sama og #B. 335 | Tímabil [12665-12677|16775-16787] 336 | Tímasetning [12678-12802|16788-16912] 337 | Dagsetning [12803-12964|167913-17082] 338 | Annað [12965-12991|17083-17109] 339 | Netföng [SS, TS, TF] [12992-12996|17110-17119] 340 | #A: Gild netföng greinast rétt með öllum leyfilegum táknum. 341 | #B: Greinast rétt, jafnvel í lok setningar. 342 | #C: Sama og #B. 343 | Vefsíður [SS, TS, TF] [12997-13009|17120-17133] 344 | #A: Gildar vefsíður greinast rétt með öllum leyfilegum táknum. 345 | #B: Greinast rétt, eina sem veldur vandræðum er þegar spurningarmerki og samasemmerki koma fyrir. Þyrfti að bera kennsl á "www" og leyfa flest þar á eftir. 346 | #C: Sama og #B. 347 | Efnasambönd [TS, TF] [13010-13019|17134-17143] 348 | #A: Ólíkar gerðir efnasambanda þekkjast. 349 | #B: Þekkjast. 350 | #C: Sama og #B. 351 | Twitter-notendanöfn [TS, TF] [13020-13022|17144-17147] 352 | #A: Gild notendanöfn með öllum leyfilegum táknum greinast rétt. 353 | #B: Þekkjast. Það mætti prófa betur öll tákn sem mega koma fyrir. 354 | #C: Sama og #B. 355 | Myllumerki [TS, TF] [13023-13024|17148-17151] 356 | #A: Leyfileg myllumerki með öllum leyfilegum táknum þekkjast. Samsetningar með kassmerki fremst leyfast og er haldið saman. 357 | #B: Greinast. Ath. samsetningar með kassmerki fremst þekkjast ekki, eru slitnar í sundur á bandstriki; hægt að bera kennsl á síðar í greiningu. 358 | #C: Sama og #B. 359 | Aðrir flóknir tókar [SS, TS, TF] [13025-13075|17152-17198] 360 | #A: Ólíkir tókar þekkjast. 361 | #B: Skák þekkist ekki, tölur eru slitnar aftan af. Stærðfræðidæmi eru slitin í sundur á flestum táknum nema skástrikum þegar þetta gæti verið dagsetning. Setningaskiptingin er líka röng á eftir tölu því næsta setning hefst á tölu eða öðru en hástaf, talan og punkturinn greinast því sem raðtala. Hnit eru slitin í sundur á kló og gráðumerki. Nótur með myllumerki t.d. þekkjast ekki. Möppuslóðir þekkjast ekki, er skipt upp á skástriki. Ýmis vörunúmer þekkjast ekki, almennt skipt upp á tölum og greinarmerkjum, hefur áhrif á setningaskiptingu. Pin-númer þekkjast ekki en hefur ekki áhrif á grunntókun. Sama gildir um CVC-númer. Kreditkortanúmer þekkjast ekki en hafa ekki áhrif á grunntókun. Flugnúmer þekkjast ekki. Flýtiskipanir þekkjast ekki, er skipt upp á plúsmerki. Þáttaraðastyttingar þekkjast ekki. Tákn eins og & eða * innan orða þekkjast ekki og skipta orðum. Tilvísanir innan sviga þekkjast ekki en hafa ekki áhrif á grunntókun. Tjákn þekkjast ekki og hafa áhrif á setningaskiptingu ef þau koma aftast í línu. HTML þekkist ekki. Önnur stafróf þekkjast ekki, stöfum er jafnvel skipt upp. Lausnarrunur þekkjast ekki, skipt er á skástriki og tölum. Hlutföll með tvípunkti þekkjast ekki, stjörnugjöf með skástriki eða stjörnum þekkjast ekki, skipt er á greinarmerkjum. 362 | #C: Sama og #B. 363 | Skák [13025|17152-17153] 364 | Stærðfræði [13026-13033|17154-17157] 365 | Staðsetning [13034-13045|17158-17169] 366 | Nótur [13046|17170] 367 | Möppuslóð [13047|17171] 368 | Vörunúmer [13048-13052|17172-17177] 369 | Fjármálanúmer [13053-13056|17178-17181] 370 | Flugnúmer [13057-13058|17182-17183] 371 | Flýtiskipanir [13059|17184] 372 | Þáttaraðir [13060|17185] 373 | Önnur tákn innan orða [13061-13062|17186-18187] 374 | Tilvísanir [13063-13064|17188-17190] 375 | Tjákn [13065-13066|17191] 376 | HTML [13067-13068|17191-17192] 377 | Önnur stafróf [13069|17192] 378 | Lausnarrunur [13070|17193] 379 | Annað [13071-13075|17194-17198] 380 | 381 | =========================================== 382 | Setningastaða = framstaða|innstaða|bakstaða 383 | [SS, TS, TF] 384 | SS = Setningaskipting 385 | TS = Tókaskipting 386 | TF = Tókaflokkun 387 | [X-X|Y-Y] 388 | X = Línutal í toktest_large.txt 389 | Y = Línutal í toktest_large_gold_acceptable.txt 390 | Línutal í toktest_large_gold_perfect.txt er nánast það sama og í toktest_large_gold_acceptable.txt. 391 | #A: Hegðun tókara í fullkomnum heimi, skilgreind í toktest_large_gold_perfect.txt 392 | #B: Ásættanleg hegðun tilreiðara, skilgreind í toktest_large_gold_acceptable.txt 393 | #C: Núverandi hegðun tilreiðarans skv. úttaki tokenize-skipunarinnar 394 | -------------------------------------------------------------------------------- /test/example.txt: -------------------------------------------------------------------------------- 1 | "Sæll!", sagði hún. 2 | 3 | Evrópski tölfræðidagurinn er haldinn á sunnudaginn undir kjörorðinu: „Horfðu á staðreyndirnar“. Á þessum degi vekja "hagstofur" í Evrópu athygli á þýðingu og mikilvægi evrópskra hagtalna fyrir samfélög í Evrópu... Kjörorðið minnir á að lýðræðisríki þarf að standa á traustum grunni áreiðanlegra og hlutlausra tölfræðiupplýsinga. 4 | 5 | Í tilefni 29. október 2019 opnar Hagstofa Íslands kl. 11:00 nýja vísasíðu á www.hagstofan.is, sem mun m.a. hýsa félagsvísa og Heimsmarkmið Sameinuðu þjóðanna! Á vef heimsmarkmiðanna er að finna mælikvarða fyrir þau markmið um sjálfbæra þróun sem heimsbyggðin öll hefur 6 | skuldbundið sig til að uppfylla? Á nýjum vef félagsvísa er að finna fjölbreytta mælikvarða félagslegrar velferðar sem skiptast í 11 víddir. (Senda má póst á mail@hagstofan.is.) Félagsvísar gefa heildarmynd af stöðu félagslegrar velferðar innan hverrar víddar. 7 | 8 | Víddir félagslegrar velferðar 9 | 10 | Sé heilsuvíddin skoðuð klukkan þrettán sést á vef félagsvísa að 77% íbúa á Íslandi voru við góða heilsu árið 2018 og hefur það hlutfall haldist nokkuð stöðugt síðan mælingar hófust. Marktækur munur er á milli kynja en 73% kvenna er við góða heilsu á móti 81% karla. 11 | 12 | Tæp 8% hafa neitað sér um tannlæknaþjónustu klukkan 9 vegna kostnaðar og tæp 3% neitað sér um heilbrigðisþjónustu vegna kostnaðar árið 2018. Eins og fyrri ár hefur hærra hlutfall kvenna en karla neitað sér um heilbrigðisþjónustu af fjárhagsástæðum, t.d. 13 | var hlutfallið tæp 9% hjá konum þegar kemur að tannlæknaþjónustu á móti 7% meðal karla árið 2018, en sá munur er ekki tölfræðilega marktækur. Niðurstöðurnar byggja á 11. lífskjararannsókn Hagstofu Íslands sem er samevrópsk rannsókn og er fjármögnuð með 55 milljónum evra eða 22.000.000 krónum. 14 | 15 | Hlutfall fólks sem neitar sér um tannlæknaþjónustu vegna kostnaðar 16 | 17 | Nýr vefur félagsvísa miðar fyrst og fremst að því að miðla tölulegum upplýsingum á aðgengilegri hátt en áður. Félagsmálaráðuneytið og Hagstofa Íslands hafa frá árinu 2012 að frumkvæði Velferðarvaktarinnar, 18 | safnað og birt árlega samfélagslegar mælingar undir yfirskriftinni félagsvísar. Markmið vísanna nú sem áður hefur verið að auðvelda almenningi og stjórnvöldum að fylgjast með samfélagslegri þróun. Hringja má í 800 19 | 7000 eða 8007001eða 800 1992 til að fá frekari upplýsingar. 20 | 21 | Einnig mega blaðamenn DV hringja í 545-9039. Fréttin birtist þá kannski í DV. 22 | 23 | Hér er um að ræða dagsetninguna 2019-10-24 en þá, nánar til tekið 2019-10-24 15:00, 24 | kom maður í heimsókn. Það var 3. ágúst 2013, en áður kom hann 17/10/2018 og þar áður 25 | 3/15/1992. Jón kom í heiminn 1965. Hann átti 300,10 krónur en Páll 1,200.83 dollara. 26 | Hver átti 17000.83 evrur? Eða voru það 220 V? Eða kannski 1023 hPa lægð? Eða 800MW 27 | virkjun? Gengið lækkaði um 3% en hækkaði svo um 4 % og svo upp um tvö prósent. En 28 | síðan var það 17 prósent lækkun og svo loks 18 prósenta hækkun. 29 | 30 | Vefslóðin var https://það.held.ég.nú, en áður http://þetta.er.prófun. Fréttin 31 | birtist fyrst á kjarninn.is en síðan á vísi.is og news.reuters.com. Hún var hluti 32 | af #metoo herferðinni. Þetta var í 17. skipti sem hún fór fram. XVII. kafli var ekki 33 | samþykktur fyrr en árið MCMLXXVII. 34 | -------------------------------------------------------------------------------- /test/test_abbrev.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | 3 | from tokenizer.abbrev import OrderedSet 4 | 5 | def test_ordered_set(): 6 | # Test empty set 7 | s_empty = OrderedSet() 8 | assert list(s_empty) == [] 9 | assert 1 not in s_empty 10 | assert repr(s_empty) == "OrderedSet([])" 11 | 12 | # Test add and iteration order 13 | s_order = OrderedSet() 14 | s_order.add(1) 15 | s_order.add('a') 16 | s_order.add(3.0) 17 | assert list(s_order) == [1, 'a', 3.0] 18 | 19 | # Test add existing item preserves order and uniqueness 20 | s_existing = OrderedSet() 21 | s_existing.add(10) 22 | s_existing.add(20) 23 | s_existing.add(10) # Add existing 24 | assert list(s_existing) == [10, 20] 25 | 26 | # Test contains 27 | s_contains = OrderedSet() 28 | s_contains.add('x') 29 | s_contains.add('y') 30 | assert 'x' in s_contains 31 | assert 'y' in s_contains 32 | assert 'z' not in s_contains 33 | assert 'a' not in s_contains 34 | 35 | # Test repr non-empty 36 | s_repr = OrderedSet() 37 | s_repr.add(1) 38 | s_repr.add('foo') 39 | assert repr(s_repr) == "OrderedSet([1, 'foo'])" 40 | -------------------------------------------------------------------------------- /test/test_cli.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | 3 | """ 4 | Test the command line interface (CLI) of the tokenizer. 5 | Copyright (C) 2025 Miðeind ehf. 6 | """ 7 | 8 | 9 | from io import StringIO 10 | import sys 11 | from unittest.mock import patch 12 | 13 | from tokenizer.main import main 14 | from tokenizer import __version__ as tokenizer_version 15 | 16 | 17 | CLT_NAME = "tokenize" 18 | 19 | 20 | def run_cli(c, m, args: list[str], standard_input: str = "") -> str: 21 | """Run the command line interface (CLI) main function with 22 | the given arguments and standard input.""" 23 | 24 | # Feed the provided string into standard input 25 | old_stdin = sys.stdin 26 | input = StringIO(standard_input) 27 | m.setattr(sys, "stdin", input) 28 | 29 | # Run the main function with the provided arguments 30 | try: 31 | with patch.object(sys, "argv", [CLT_NAME] + args): 32 | main() 33 | except SystemExit as e: 34 | assert e.code == 0, f"Expected exit code 0 from CLT, got {e.code}" 35 | 36 | # Capture the output 37 | output = c.readouterr() 38 | 39 | # Restore the original standard input 40 | m.setattr(sys, "stdin", old_stdin) 41 | 42 | return output.out.strip() 43 | 44 | 45 | def test_cli(capsys, monkeypatch): 46 | """Test the command line interface (CLI) of the tokenizer.""" 47 | c = capsys 48 | m = monkeypatch 49 | 50 | assert run_cli(c, m, ["--version"]).endswith(tokenizer_version) 51 | assert "usage:" in run_cli(c, m, ["--help"]) 52 | 53 | assert ( 54 | run_cli(c, m, ["-", "-"], "Hann Jón,sem kom kl.14:00 í dag, fór seinna.") 55 | == "Hann Jón , sem kom kl. 14:00 í dag , fór seinna ." 56 | ) 57 | 58 | def clean_json(s: str) -> str: 59 | """Clean the JSON output by removing unnecessary whitespace.""" 60 | return s.strip() 61 | 62 | expected_json_out = """ 63 | {"k":"BEGIN SENT","t":""} 64 | {"k":"WORD","t":"Þetta","o":"Þetta","s":[0,1,2,3,4]} 65 | {"k":"WORD","t":"var","o":" var","s":[1,2,3]} 66 | {"k":"DATEREL","t":"14. mars","v":[0,3,14],"o":" 14. mars","s":[1,2,3,4,5,6,7,8]} 67 | {"k":"WORD","t":"og","o":" og","s":[1,2]} 68 | {"k":"NUMBER","t":"11","v":11,"o":" 11","s":[1,2]} 69 | {"k":"WORD","t":"manns","o":" manns","s":[1,2,3,4,5]} 70 | {"k":"WORD","t":"viðstaddir","o":" viðstaddir","s":[1,2,3,4,5,6,7,8,9,10]} 71 | {"k":"PUNCTUATION","t":".","v":".","o":".","s":[0]} 72 | {"k":"END SENT","t":""} 73 | """ 74 | r = run_cli( 75 | c, m, ["-", "-", "--json"], "Þetta var 14. mars og 11 manns viðstaddir." 76 | ) 77 | assert clean_json(r) == clean_json(expected_json_out) 78 | 79 | # TODO: Add more tests for the CLI to achieve 100% coverage 80 | -------------------------------------------------------------------------------- /test/test_detokenize.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | 3 | """ 4 | 5 | test_detokenize.py 6 | 7 | Tests for Tokenizer module 8 | 9 | Copyright (C) 2016-2025 by Miðeind ehf. 10 | Original author: Vilhjálmur Þorsteinsson 11 | 12 | This software is licensed under the MIT License: 13 | 14 | Permission is hereby granted, free of charge, to any person 15 | obtaining a copy of this software and associated documentation 16 | files (the "Software"), to deal in the Software without restriction, 17 | including without limitation the rights to use, copy, modify, merge, 18 | publish, distribute, sublicense, and/or sell copies of the Software, 19 | and to permit persons to whom the Software is furnished to do so, 20 | subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be 23 | included in all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 28 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 29 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 30 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 31 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | 33 | """ 34 | 35 | import tokenizer as t 36 | 37 | 38 | def test_detokenize() -> None: 39 | 40 | options = {"normalize": True} 41 | 42 | def should_be_equal(s: str) -> None: 43 | toklist = t.tokenize(s, **options) 44 | assert s == t.detokenize(toklist, **options) 45 | 46 | def should_be(s1: str, s2: str) -> None: 47 | toklist = t.tokenize(s1, **options) 48 | assert s2 == t.detokenize(toklist, **options) 49 | 50 | should_be_equal("Jón átti 1.234,56 kr. í vasanum t.a.m. og 12. gr. átti ekki við.") 51 | should_be_equal("o.s.frv.") 52 | should_be_equal("http://www.malfong.is") 53 | should_be_equal("Páll skoðaði t.d. http://www.malfong.is.") 54 | should_be_equal("Páll var með netfangið palli@einn.i.heiminum.is.") 55 | should_be_equal("Páll var með „netfangið“ palli@einn.i.heiminum.is.") 56 | should_be_equal("Páll var m.a. [palli@einn.i.heiminum.is] þann 10. 12. 1998.") 57 | should_be_equal("Páll var m.a. [palli@einn.i.heiminum.is] þann 10.12.1998.") 58 | should_be_equal("Páll veiddi 74 cm. lax í Norðurá þann 1.3.") 59 | 60 | should_be( 61 | 'Páll var með "netfangið" palli@einn.i.heiminum.is.', 62 | "Páll var með „netfangið“ palli@einn.i.heiminum.is.", 63 | ) 64 | 65 | # !!! BUG 66 | # should_be( 67 | # "Páll var með \"netfangið\", þ.e.a.s. (\"þetta\").", 68 | # "Páll var með „netfangið“, þ.e.a.s. („þetta“).", 69 | # ) 70 | 71 | options = {"normalize": False} 72 | 73 | should_be_equal("Páll var með „netfangið“, þ.e.a.s. („þetta“).") 74 | should_be_equal('Páll var með "netfangið" palli@einn.i.heiminum.is.') 75 | should_be_equal('Páll var með "netfangið", þ.e.a.s. ("þetta").') 76 | -------------------------------------------------------------------------------- /test/test_helper_functions.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | 3 | """ 4 | Test helper functions from definitions.py 5 | Copyright (C) 2025 Miðeind ehf. 6 | """ 7 | 8 | from tokenizer.definitions import valid_ssn, roman_to_int 9 | 10 | 11 | def test_valid_ssn(): 12 | # Test valid Icelandic SSNs (kennitölur) 13 | assert valid_ssn("311281-5189") 14 | assert valid_ssn("101275-5239") 15 | assert valid_ssn("500101-2880") 16 | assert valid_ssn("700269-1169") 17 | assert valid_ssn("010130-7789") 18 | assert valid_ssn("140543-3229") 19 | assert valid_ssn("120375-3509") 20 | assert valid_ssn("650376-0649") 21 | 22 | # Test invalid formats - wrong length 23 | assert not valid_ssn("01010-1019") 24 | assert not valid_ssn("0101011019") 25 | assert not valid_ssn("010101-10199") 26 | assert not valid_ssn("") 27 | assert not valid_ssn(None) 28 | 29 | # Test invalid formats - no hyphen 30 | assert not valid_ssn("0101011019") 31 | assert not valid_ssn("010101+1019") 32 | assert not valid_ssn("010101 1019") 33 | 34 | # Test invalid formats - hyphen in wrong position 35 | assert not valid_ssn("01010-11019") 36 | assert not valid_ssn("0101011-019") 37 | 38 | # Test checksum failures 39 | assert not valid_ssn("010101-0000") 40 | assert not valid_ssn("010101-1018") 41 | assert not valid_ssn("310354-2268") 42 | 43 | # Test invalid format - non-digit characters 44 | assert not valid_ssn("01010A-1019") 45 | assert not valid_ssn("010101-10B9") 46 | 47 | # Test completely malformed inputs 48 | assert not valid_ssn("abcde-fghi") 49 | assert not valid_ssn("12345") 50 | assert not valid_ssn("not-a-ssn") 51 | 52 | 53 | def test_roman_to_int(): 54 | # Test basic single numerals 55 | assert roman_to_int("I") == 1 56 | assert roman_to_int("V") == 5 57 | assert roman_to_int("X") == 10 58 | assert roman_to_int("L") == 50 59 | assert roman_to_int("C") == 100 60 | assert roman_to_int("D") == 500 61 | assert roman_to_int("M") == 1000 62 | 63 | # Test subtractive notation 64 | assert roman_to_int("IV") == 4 65 | assert roman_to_int("IX") == 9 66 | assert roman_to_int("XL") == 40 67 | assert roman_to_int("XC") == 90 68 | assert roman_to_int("CD") == 400 69 | assert roman_to_int("CM") == 900 70 | 71 | # Test additive notation and combinations 72 | assert roman_to_int("II") == 2 73 | assert roman_to_int("III") == 3 74 | assert roman_to_int("VI") == 6 75 | assert roman_to_int("VII") == 7 76 | assert roman_to_int("VIII") == 8 77 | assert roman_to_int("XI") == 11 78 | assert roman_to_int("XV") == 15 79 | assert roman_to_int("XVI") == 16 80 | 81 | # Test various complex numbers 82 | assert roman_to_int("XXIV") == 24 83 | assert roman_to_int("XLII") == 42 84 | assert roman_to_int("XCIX") == 99 85 | assert roman_to_int("CDXLIV") == 444 86 | assert roman_to_int("MCMXCIX") == 1999 87 | assert roman_to_int("MMXXIII") == 2023 88 | assert roman_to_int("MMMCMXCIX") == 3999 89 | -------------------------------------------------------------------------------- /test/test_index_calculation.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | 3 | """ 4 | 5 | test_index_calculation.py 6 | 7 | Tests for Tokenizer module 8 | 9 | Copyright (C) 2016-2025 by Miðeind ehf. 10 | 11 | This software is licensed under the MIT License: 12 | 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated documentation 15 | files (the "Software"), to deal in the Software without restriction, 16 | including without limitation the rights to use, copy, modify, merge, 17 | publish, distribute, sublicense, and/or sell copies of the Software, 18 | and to permit persons to whom the Software is furnished to do so, 19 | subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | 32 | This module tests the token index generation of the tokenizer. 33 | 34 | """ 35 | 36 | import tokenizer 37 | 38 | Tok = tokenizer.Tok 39 | TOK = tokenizer.TOK 40 | 41 | ACCENT = chr(769) 42 | UMLAUT = chr(776) 43 | EM_DASH = "\u2014" 44 | 45 | 46 | def test_small_easy_cases() -> None: 47 | s = "Bara ASCII." 48 | # 01234567890 49 | # ^ ^ ^ 50 | toks = tokenizer.parse_tokens([s]) 51 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 52 | assert char_indexes == [0, 4, 10] 53 | assert byte_indexes == [0, 4, 10] 54 | toks = tokenizer.parse_tokens([s]) 55 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 56 | assert char_indexes == [0, 4, 10, 11] 57 | assert byte_indexes == [0, 4, 10, 11] 58 | 59 | s = "Á bát." 60 | # char: 61 | # 012345 62 | # ^^ ^ 63 | # byte: 64 | # two-byte letters: 65 | # x x 66 | # indexes: 67 | # 023467 68 | # ^^ ^ 69 | toks = tokenizer.parse_tokens([s]) 70 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 71 | assert char_indexes == [0, 1, 5] 72 | assert byte_indexes == [0, 2, 7] 73 | toks = tokenizer.parse_tokens([s]) 74 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 75 | assert char_indexes == [0, 1, 5, 6] 76 | assert byte_indexes == [0, 2, 7, 8] 77 | 78 | s = "endar á ö" 79 | # 012345678 80 | # ^ ^ ^ 81 | # x x 82 | toks = tokenizer.parse_tokens([s]) 83 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 84 | assert char_indexes == [0, 5, 7] 85 | assert byte_indexes == [0, 5, 8] 86 | toks = tokenizer.parse_tokens([s]) 87 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 88 | assert char_indexes == [0, 5, 7, 9] 89 | assert byte_indexes == [0, 5, 8, 11] 90 | 91 | 92 | def test_small_difficult_cases() -> None: 93 | s = "" 94 | toks = tokenizer.parse_tokens([s]) 95 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 96 | assert char_indexes == [] 97 | assert byte_indexes == [] 98 | toks = tokenizer.parse_tokens([s]) 99 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 100 | assert char_indexes == [0] 101 | assert byte_indexes == [0] 102 | 103 | s = " " 104 | toks = tokenizer.parse_tokens([s]) 105 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 106 | assert char_indexes == [0] 107 | assert byte_indexes == [0] 108 | toks = tokenizer.parse_tokens([s]) 109 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 110 | assert char_indexes == [0, 1] 111 | assert byte_indexes == [0, 1] 112 | 113 | # Single byte characters 114 | for x in ["a", "A", ".", "?", "!"]: 115 | s = x 116 | toks = tokenizer.parse_tokens([s]) 117 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 118 | assert char_indexes == [0] 119 | assert byte_indexes == [0] 120 | toks = tokenizer.parse_tokens([s]) 121 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 122 | assert char_indexes == [0, 1] 123 | assert byte_indexes == [0, 1] 124 | 125 | s = " " + x 126 | toks = tokenizer.parse_tokens([s]) 127 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 128 | assert char_indexes == [0] 129 | assert byte_indexes == [0] 130 | toks = tokenizer.parse_tokens([s]) 131 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 132 | assert char_indexes == [0, 2] 133 | assert byte_indexes == [0, 2] 134 | 135 | s = " " + x 136 | toks = tokenizer.parse_tokens([s]) 137 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 138 | assert char_indexes == [0] 139 | assert byte_indexes == [0] 140 | toks = tokenizer.parse_tokens([s]) 141 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 142 | assert char_indexes == [0, 3] 143 | assert byte_indexes == [0, 3] 144 | 145 | s = " " + x + " " 146 | # example: 147 | # " a " 148 | # 0123 149 | # ^ ^ 150 | toks = tokenizer.parse_tokens([s]) 151 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 152 | assert char_indexes == [0, 3] 153 | assert byte_indexes == [0, 3] 154 | toks = tokenizer.parse_tokens([s]) 155 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 156 | assert char_indexes == [0, 3, 4] 157 | assert byte_indexes == [0, 3, 4] 158 | 159 | s = " " + x + " " + x 160 | # example: 161 | # " a a" 162 | # ^ ^ 163 | toks = tokenizer.parse_tokens([s]) 164 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 165 | assert char_indexes == [0, 2] 166 | assert byte_indexes == [0, 2] 167 | toks = tokenizer.parse_tokens([s]) 168 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 169 | assert char_indexes == [0, 2, 4] 170 | assert byte_indexes == [0, 2, 4] 171 | 172 | # Two byte characters 173 | for x in ["þ", "æ", "á"]: 174 | s = x 175 | toks = tokenizer.parse_tokens([s]) 176 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 177 | assert char_indexes == [0], s 178 | assert byte_indexes == [0], s 179 | toks = tokenizer.parse_tokens([s]) 180 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 181 | assert char_indexes == [0, 1], s 182 | assert byte_indexes == [0, 2], s 183 | 184 | s = " " + x 185 | toks = tokenizer.parse_tokens([s]) 186 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 187 | assert char_indexes == [0] 188 | assert byte_indexes == [0] 189 | toks = tokenizer.parse_tokens([s]) 190 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 191 | assert char_indexes == [0, 2] 192 | assert byte_indexes == [0, 3] 193 | 194 | s = " " + x 195 | toks = tokenizer.parse_tokens([s]) 196 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 197 | assert char_indexes == [0] 198 | assert byte_indexes == [0] 199 | toks = tokenizer.parse_tokens([s]) 200 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 201 | assert char_indexes == [0, 3] 202 | assert byte_indexes == [0, 4] 203 | 204 | s = " " + x + " " 205 | # example bytes: 206 | # " þ_ " 207 | # 01234 208 | # ^ ^ 209 | toks = tokenizer.parse_tokens([s]) 210 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 211 | assert char_indexes == [0, 3] 212 | assert byte_indexes == [0, 4] 213 | toks = tokenizer.parse_tokens([s]) 214 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 215 | assert char_indexes == [0, 3, 4] 216 | assert byte_indexes == [0, 4, 5] 217 | 218 | s = " " + x + " " + x 219 | # example bytes: 220 | # " þ_ þ_" 221 | # 012345 222 | # ^ ^ 223 | toks = tokenizer.parse_tokens([s]) 224 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 225 | assert char_indexes == [0, 2] 226 | assert byte_indexes == [0, 3] 227 | toks = tokenizer.parse_tokens([s]) 228 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 229 | assert char_indexes == [0, 2, 4] 230 | assert byte_indexes == [0, 3, 6] 231 | 232 | # Two character characters 233 | # These strings contain two unicode code points that are rendered as one letter. 234 | # They are counted as two characters in python. 235 | # In addition the accent and umlaut characters are two bytes. 236 | for x in ["a" + ACCENT, "o" + UMLAUT]: 237 | s = x 238 | toks = tokenizer.parse_tokens([s]) 239 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 240 | assert char_indexes == [0], s 241 | assert byte_indexes == [0], s 242 | toks = tokenizer.parse_tokens([s]) 243 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 244 | assert char_indexes == [0, 2], s 245 | assert byte_indexes == [0, 3], s 246 | 247 | s = " " + x 248 | toks = tokenizer.parse_tokens([s]) 249 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 250 | assert char_indexes == [0] 251 | assert byte_indexes == [0] 252 | toks = tokenizer.parse_tokens([s]) 253 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 254 | assert char_indexes == [0, 3] 255 | assert byte_indexes == [0, 4] 256 | 257 | s = " " + x 258 | toks = tokenizer.parse_tokens([s]) 259 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 260 | assert char_indexes == [0] 261 | assert byte_indexes == [0] 262 | toks = tokenizer.parse_tokens([s]) 263 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 264 | assert char_indexes == [0, 4] 265 | assert byte_indexes == [0, 5] 266 | 267 | s = " " + x + " " 268 | # example chars: 269 | # " a´ " 270 | # 01234 271 | # ^ ^^ 272 | # example bytes: 273 | # " a´_ " 274 | # 012345 275 | # ^ ^ ^ 276 | toks = tokenizer.parse_tokens([s]) 277 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 278 | assert char_indexes == [0, 4] 279 | assert byte_indexes == [0, 5] 280 | toks = tokenizer.parse_tokens([s]) 281 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 282 | assert char_indexes == [0, 4, 5] 283 | assert byte_indexes == [0, 5, 6] 284 | 285 | s = " " + x + " " + x 286 | # example chars: 287 | # " a´ a´" 288 | # 012345 289 | # ^ ^ 290 | # example bytes: 291 | # " a´_ a´_" 292 | # 01234567 293 | # ^ ^ 294 | toks = tokenizer.parse_tokens([s]) 295 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 296 | assert char_indexes == [0, 3] 297 | assert byte_indexes == [0, 4] 298 | toks = tokenizer.parse_tokens([s]) 299 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 300 | assert char_indexes == [0, 3, 6] 301 | assert byte_indexes == [0, 4, 8] 302 | 303 | # The em-dash is 3 bytes 304 | for x in [EM_DASH]: 305 | s = x 306 | toks = tokenizer.parse_tokens([s]) 307 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 308 | assert char_indexes == [0], s 309 | assert byte_indexes == [0], s 310 | toks = tokenizer.parse_tokens([s]) 311 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 312 | assert char_indexes == [0, 1], s 313 | assert byte_indexes == [0, 3], s 314 | 315 | s = " " + x 316 | toks = tokenizer.parse_tokens([s]) 317 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 318 | assert char_indexes == [0] 319 | assert byte_indexes == [0] 320 | toks = tokenizer.parse_tokens([s]) 321 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 322 | assert char_indexes == [0, 2] 323 | assert byte_indexes == [0, 4] 324 | 325 | s = " " + x 326 | toks = tokenizer.parse_tokens([s]) 327 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 328 | assert char_indexes == [0] 329 | assert byte_indexes == [0] 330 | toks = tokenizer.parse_tokens([s]) 331 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 332 | assert char_indexes == [0, 3] 333 | assert byte_indexes == [0, 5] 334 | 335 | s = " " + x + " " 336 | # example chars: 337 | # " a " 338 | # 0123 339 | # ^ ^ 340 | # example bytes: 341 | # " a__ " 342 | # 012345 343 | # ^ ^ 344 | toks = tokenizer.parse_tokens([s]) 345 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 346 | assert char_indexes == [0, 3] 347 | assert byte_indexes == [0, 5] 348 | toks = tokenizer.parse_tokens([s]) 349 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 350 | assert char_indexes == [0, 3, 4] 351 | assert byte_indexes == [0, 5, 6] 352 | 353 | s = " " + x + " " + x 354 | # example chars: 355 | # " a a" 356 | # 0123 357 | # ^ ^ 358 | # example bytes: 359 | # " a__ a__" 360 | # 01234567 361 | # ^ ^ 362 | toks = tokenizer.parse_tokens([s]) 363 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 364 | assert char_indexes == [0, 2] 365 | assert byte_indexes == [0, 4] 366 | toks = tokenizer.parse_tokens([s]) 367 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 368 | assert char_indexes == [0, 2, 4] 369 | assert byte_indexes == [0, 4, 8] 370 | 371 | 372 | def test_larger_case() -> None: 373 | s = "Þessi setning er í lengra lagi og er með bæði eins og tveggja bæta stafi." 374 | # 0123456789012345678901234567890123456789012345678901234567890123456789012 375 | # ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ 376 | # x x x xx x 377 | toks = tokenizer.parse_tokens([s]) 378 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 379 | assert char_indexes == [ 380 | 0, 381 | 5, 382 | 13, 383 | 16, 384 | 18, 385 | 25, 386 | 30, 387 | 33, 388 | 36, 389 | 40, 390 | 45, 391 | 50, 392 | 53, 393 | 61, 394 | 66, 395 | 72, 396 | ] 397 | assert byte_indexes == [ 398 | 0, 399 | 6, 400 | 14, 401 | 17, 402 | 20, 403 | 27, 404 | 32, 405 | 35, 406 | 38, 407 | 43, 408 | 50, 409 | 55, 410 | 58, 411 | 66, 412 | 72, 413 | 78, 414 | ] 415 | toks = tokenizer.parse_tokens([s]) 416 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 417 | assert char_indexes == [ 418 | 0, 419 | 5, 420 | 13, 421 | 16, 422 | 18, 423 | 25, 424 | 30, 425 | 33, 426 | 36, 427 | 40, 428 | 45, 429 | 50, 430 | 53, 431 | 61, 432 | 66, 433 | 72, 434 | 73, 435 | ] 436 | assert byte_indexes == [ 437 | 0, 438 | 6, 439 | 14, 440 | 17, 441 | 20, 442 | 27, 443 | 32, 444 | 35, 445 | 38, 446 | 43, 447 | 50, 448 | 55, 449 | 58, 450 | 66, 451 | 72, 452 | 78, 453 | 79, 454 | ] 455 | 456 | 457 | def test_iterator_cases() -> None: 458 | s = [ 459 | "Þessi ", 460 | "setning ", 461 | "er ", 462 | "í ", 463 | "lengra ", 464 | "lagi ", 465 | "og ", 466 | "er ", 467 | "með ", 468 | "bæði ", 469 | "eins ", 470 | "og ", 471 | "tveggja ", 472 | "bæta ", 473 | "stafi.", 474 | ] 475 | # (char and byte indexes in a similar test above) 476 | toks = tokenizer.parse_tokens(s) 477 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 478 | assert char_indexes == [ 479 | 0, 480 | 5, 481 | 13, 482 | 16, 483 | 18, 484 | 25, 485 | 30, 486 | 33, 487 | 36, 488 | 40, 489 | 45, 490 | 50, 491 | 53, 492 | 61, 493 | 66, 494 | 72, 495 | ] 496 | assert byte_indexes == [ 497 | 0, 498 | 6, 499 | 14, 500 | 17, 501 | 20, 502 | 27, 503 | 32, 504 | 35, 505 | 38, 506 | 43, 507 | 50, 508 | 55, 509 | 58, 510 | 66, 511 | 72, 512 | 78, 513 | ] 514 | toks = tokenizer.parse_tokens(s) 515 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 516 | assert char_indexes == [ 517 | 0, 518 | 5, 519 | 13, 520 | 16, 521 | 18, 522 | 25, 523 | 30, 524 | 33, 525 | 36, 526 | 40, 527 | 45, 528 | 50, 529 | 53, 530 | 61, 531 | 66, 532 | 72, 533 | 73, 534 | ] 535 | assert byte_indexes == [ 536 | 0, 537 | 6, 538 | 14, 539 | 17, 540 | 20, 541 | 27, 542 | 32, 543 | 35, 544 | 38, 545 | 43, 546 | 50, 547 | 55, 548 | 58, 549 | 66, 550 | 72, 551 | 78, 552 | 79, 553 | ] 554 | 555 | s = ["Stutt setning.", "", "Önnur setning."] 556 | # 01234567890123 45678901234567 557 | # ^ ^ ^ ^ ^ ^ 558 | # x 559 | toks = tokenizer.parse_tokens(s) 560 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 561 | assert char_indexes == [0, 5, 13, 14, 19, 27] 562 | assert byte_indexes == [0, 5, 13, 14, 20, 28] 563 | toks = tokenizer.parse_tokens(s) 564 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 565 | assert char_indexes == [0, 5, 13, 14, 19, 27, 28] 566 | assert byte_indexes == [0, 5, 13, 14, 20, 28, 29] 567 | 568 | # parse_tokens does some implentation-detail-stuff here. Use tokenize instead. 569 | s = [" Stutt setning. ", "\n \n", "Önnur setning."] 570 | # 0123456789012345 6 78 90123456789012 571 | # ^ ^ ^^ ^ ^ 572 | # x 573 | toks = tokenizer.tokenize(s) 574 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 575 | assert char_indexes == [0, 6, 14, 15, 24, 32] 576 | assert byte_indexes == [0, 6, 14, 15, 25, 33] 577 | toks = tokenizer.tokenize(s) 578 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 579 | assert char_indexes == [0, 6, 14, 15, 24, 32, 33] 580 | assert byte_indexes == [0, 6, 14, 15, 25, 33, 34] 581 | 582 | 583 | def test_paragraph_markers() -> None: 584 | s = "[[Stutt setning.]][[]][[Önnur setning.]]" 585 | # 012345678901234567890123456789012345678901234567 586 | # ^^^ ^ ^^ ^ ^ ^ ^ ^ ^^ 587 | # x 588 | toks = tokenizer.parse_tokens(s) 589 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 590 | assert char_indexes == [0, 2, 7, 15, 16, 18, 20, 22, 24, 29, 37, 38] 591 | assert byte_indexes == [0, 2, 7, 15, 16, 18, 20, 22, 24, 30, 38, 39] 592 | toks = tokenizer.parse_tokens(s) 593 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 594 | assert char_indexes == [0, 2, 7, 15, 16, 18, 20, 22, 24, 29, 37, 38, 40] 595 | assert byte_indexes == [0, 2, 7, 15, 16, 18, 20, 22, 24, 30, 38, 39, 41] 596 | 597 | # The tokenize functions does stuff to paragraph markers. Test that the 598 | # indexes are properly calculated after that. 599 | # Note that the text of the dropped empty paragraph markers disappears. 600 | s = "[[Stutt setning.]][[]][[Önnur setning.]]" 601 | # 012345678901234567890123456789012345678901234567 602 | # ^ ^ ^ ^^ ^ ^ ^ ^^ 603 | # x 604 | toks = tokenizer.tokenize(s) 605 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 606 | assert char_indexes == [0, 2, 7, 15, 16, 18, 24, 29, 37, 38] 607 | assert byte_indexes == [0, 2, 7, 15, 16, 18, 24, 30, 38, 39] 608 | toks = tokenizer.tokenize(s) 609 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 610 | assert char_indexes == [0, 2, 7, 15, 16, 18, 24, 29, 37, 38, 40] 611 | assert byte_indexes == [0, 2, 7, 15, 16, 18, 24, 30, 38, 39, 41] 612 | 613 | 614 | def test_composite_phrases() -> None: 615 | s = "Orða- og tengingasetning." 616 | # 0123456789012345678901234 617 | # ^ ^^ ^ ^ 618 | # x 619 | toks = tokenizer.parse_tokens(s) 620 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 621 | assert char_indexes == [0, 4, 5, 8, 24] 622 | assert byte_indexes == [0, 5, 6, 9, 25] 623 | toks = tokenizer.parse_tokens(s) 624 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 625 | assert char_indexes == [0, 4, 5, 8, 24, 25] 626 | assert byte_indexes == [0, 5, 6, 9, 25, 26] 627 | 628 | # The whole thing gets squished together into a single token. 629 | s = "Orða- og tengingasetning." 630 | # 0123456789012345678901234 631 | # ^ ^ 632 | # x 633 | toks = tokenizer.tokenize(s) 634 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 635 | assert char_indexes == [0, 24] 636 | assert byte_indexes == [0, 25] 637 | toks = tokenizer.tokenize(s) 638 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 639 | assert char_indexes == [0, 24, 25] 640 | assert byte_indexes == [0, 25, 26] 641 | 642 | 643 | def test_lengthening_substitutions() -> None: 644 | s = "Þetta er 3ji báturinn!" 645 | # 0123456789012345678901 646 | # ^ ^ ^ ^ ^ 647 | # x x 648 | # ! lengthening happens here (3ji->þriðji) 649 | toks = tokenizer.parse_tokens( 650 | s, handle_kludgy_ordinals=tokenizer.KLUDGY_ORDINALS_MODIFY 651 | ) 652 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 653 | assert char_indexes == [0, 5, 8, 12, 21] 654 | assert byte_indexes == [0, 6, 9, 13, 23] 655 | toks = tokenizer.parse_tokens( 656 | s, handle_kludgy_ordinals=tokenizer.KLUDGY_ORDINALS_MODIFY 657 | ) 658 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 659 | assert char_indexes == [0, 5, 8, 12, 21, 22] 660 | assert byte_indexes == [0, 6, 9, 13, 23, 24] 661 | 662 | 663 | def test_converted_measurements() -> None: 664 | s = "Stillið ofninn á 12° C til að baka kökuna." 665 | # 012345678901234567890123456789012345678901 666 | # ^ ^ ^ ^ ^ ^ ^ ^ ^ 667 | # x x x x x 668 | toks = tokenizer.tokenize(s, convert_measurements=True) 669 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks) 670 | assert char_indexes == [0, 7, 14, 16, 22, 26, 29, 34, 41] 671 | assert byte_indexes == [0, 8, 15, 18, 25, 29, 33, 38, 46] 672 | toks = tokenizer.tokenize(s, convert_measurements=True) 673 | char_indexes, byte_indexes = tokenizer.calculate_indexes(toks, last_is_end=True) 674 | assert char_indexes == [0, 7, 14, 16, 22, 26, 29, 34, 41, 42] 675 | assert byte_indexes == [0, 8, 15, 18, 25, 29, 33, 38, 46, 47] 676 | 677 | 678 | def test_compound() -> None: 679 | s = " Katrín Júlíusdóttir var iðnaðar- \n\t og \t\t viðskiptaráðherra" 680 | tlist = list(tokenizer.tokenize(s)) 681 | assert sum(len(t.original or "") for t in tlist) == len(s) 682 | -------------------------------------------------------------------------------- /test/test_tokenizer_tok.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | """ 3 | 4 | Tests for Tokenizer module 5 | 6 | Copyright (C) 2016-2025 by Miðeind ehf. 7 | 8 | This software is licensed under the MIT License: 9 | 10 | Permission is hereby granted, free of charge, to any person 11 | obtaining a copy of this software and associated documentation 12 | files (the "Software"), to deal in the Software without restriction, 13 | including without limitation the rights to use, copy, modify, merge, 14 | publish, distribute, sublicense, and/or sell copies of the Software, 15 | and to permit persons to whom the Software is furnished to do so, 16 | subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | """ 30 | 31 | import tokenizer 32 | 33 | Tok = tokenizer.Tok 34 | TOK = tokenizer.TOK 35 | 36 | ACCENT = chr(769) 37 | UMLAUT = chr(776) 38 | SOFT_HYPHEN = chr(173) 39 | ZEROWIDTH_SPACE = chr(8203) 40 | ZEROWIDTH_NBSP = chr(65279) 41 | 42 | 43 | def test_split_simple() -> None: 44 | t = Tok(TOK.RAW, "boat", None) 45 | l, r = t.split(2) 46 | 47 | assert l == Tok(TOK.RAW, "bo", None) 48 | assert r == Tok(TOK.RAW, "at", None) 49 | 50 | 51 | def test_split_simple_original() -> None: 52 | t = Tok(TOK.RAW, "boat", None, "boat", [0, 1, 2, 3]) 53 | l, r = t.split(2) 54 | 55 | assert l == Tok(TOK.RAW, "bo", None, "bo", [0, 1]) 56 | assert r == Tok(TOK.RAW, "at", None, "at", [0, 1]) 57 | 58 | 59 | def test_split_with_substitutions() -> None: 60 | # original: "a&123b". replace "&123" with "x" and end up with "axb" 61 | t = Tok(TOK.RAW, "axb", None, "a&123b", [0, 1, 5]) 62 | 63 | l1, r1 = t.split(1) 64 | assert l1 == Tok(TOK.RAW, "a", None, "a", [0]) 65 | assert r1 == Tok(TOK.RAW, "xb", None, "&123b", [0, 4]) 66 | 67 | l2, r2 = t.split(2) 68 | assert l2 == Tok(TOK.RAW, "ax", None, "a&123", [0, 1]) 69 | assert r2 == Tok(TOK.RAW, "b", None, "b", [0]) 70 | 71 | 72 | def test_split_with_substitutions_with_whitespace_prefix() -> None: 73 | # original: " a&123b". strip whitespace and replace "&123" with "x" and end up with "axb" 74 | t = Tok(TOK.RAW, "axb", None, " a&123b", [2, 3, 7]) 75 | 76 | l1, r1 = t.split(1) 77 | assert l1 == Tok(TOK.RAW, "a", None, " a", [2]) 78 | assert r1 == Tok(TOK.RAW, "xb", None, "&123b", [0, 4]) 79 | 80 | l2, r2 = t.split(2) 81 | assert l2 == Tok(TOK.RAW, "ax", None, " a&123", [2, 3]) 82 | assert r2 == Tok(TOK.RAW, "b", None, "b", [0]) 83 | 84 | 85 | def test_split_with_whitespace_prefix() -> None: 86 | t = Tok(TOK.RAW, "boat", None, " boat", [3, 4, 5, 6]) 87 | l, r = t.split(2) 88 | 89 | assert l == Tok(TOK.RAW, "bo", None, " bo", [3, 4]) 90 | assert r == Tok(TOK.RAW, "at", None, "at", [0, 1]) 91 | 92 | 93 | def test_split_at_ends() -> None: 94 | t = Tok(TOK.RAW, "ab", None, "ab", [0, 1]) 95 | l, r = t.split(0) 96 | assert l == Tok(TOK.RAW, "", None, "", []) 97 | assert r == Tok(TOK.RAW, "ab", None, "ab", [0, 1]) 98 | 99 | t = Tok(TOK.RAW, "ab", None, "ab", [0, 1]) 100 | l, r = t.split(2) 101 | assert l == Tok(TOK.RAW, "ab", None, "ab", [0, 1]) 102 | assert r == Tok(TOK.RAW, "", None, "", []) 103 | 104 | t = Tok(TOK.RAW, "ab", None) 105 | l, r = t.split(0) 106 | assert l == Tok(TOK.RAW, "", None) 107 | assert r == Tok(TOK.RAW, "ab", None) 108 | 109 | t = Tok(TOK.RAW, "ab", None) 110 | l, r = t.split(2) 111 | assert l == Tok(TOK.RAW, "ab", None) 112 | assert r == Tok(TOK.RAW, "", None) 113 | 114 | 115 | def test_split_with_negative_index() -> None: 116 | test_string = "abcde" 117 | t = Tok(TOK.RAW, test_string, None, test_string, list(range(len(test_string)))) 118 | l, r = t.split(-2) 119 | assert l == Tok(TOK.RAW, "abc", None, "abc", [0, 1, 2]) 120 | assert r == Tok(TOK.RAW, "de", None, "de", [0, 1]) 121 | 122 | 123 | """ 124 | TODO: Haven't decided what's the correct behavior. 125 | def test_split_on_empty_txt(): 126 | t = Tok(TOK.RAW, "", None, "this got removed", []) 127 | 128 | l, r = t.split(0) 129 | assert l == Tok(TOK.RAW, "", None, "", []) 130 | assert r == Tok(TOK.RAW, "", None, "this got removed", []) 131 | 132 | l, r = t.split(1) 133 | assert l == Tok(TOK.RAW, "", None, "this got removed", []) 134 | assert r == Tok(TOK.RAW, "", None, "", []) 135 | """ 136 | 137 | 138 | def test_substitute() -> None: 139 | t = Tok(TOK.RAW, "a&123b", None, "a&123b", [0, 1, 2, 3, 4, 5]) 140 | t.substitute((1, 5), "x") 141 | assert t == Tok(TOK.RAW, "axb", None, "a&123b", [0, 1, 5]) 142 | 143 | t = Tok(TOK.RAW, "ab&123", None, "ab&123", [0, 1, 2, 3, 4, 5]) 144 | t.substitute((2, 6), "x") 145 | assert t == Tok(TOK.RAW, "abx", None, "ab&123", [0, 1, 2]) 146 | 147 | t = Tok(TOK.RAW, "&123ab", None, "&123ab", [0, 1, 2, 3, 4, 5]) 148 | t.substitute((0, 4), "x") 149 | assert t == Tok(TOK.RAW, "xab", None, "&123ab", [0, 4, 5]) 150 | 151 | 152 | def test_substitute_bugfix_1() -> None: 153 | test_string = "xya" + ACCENT + "zu" + ACCENT + "wáo" + UMLAUT + "b" 154 | # 012 3 45 6 7890123456 7 8 155 | # 0123456789012345 156 | t = Tok( 157 | kind=-1, 158 | txt=test_string, 159 | val=None, 160 | original=test_string, 161 | origin_spans=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], 162 | ) 163 | t.substitute((2, 4), "á") 164 | assert t == Tok( 165 | kind=-1, 166 | txt="xyázu" + ACCENT + "wáo" + UMLAUT + "b", 167 | val=None, 168 | original=test_string, 169 | origin_spans=[0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], 170 | ) 171 | 172 | t.substitute((4, 6), "ú") 173 | assert t == Tok( 174 | kind=-1, 175 | txt="xyázúwáo" + UMLAUT + "b", 176 | val=None, 177 | original=test_string, 178 | origin_spans=[0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], 179 | ) 180 | 181 | t.substitute((14, 16), "ö") 182 | assert t == Tok( 183 | kind=-1, 184 | txt="xyázúwáöb", 185 | val=None, 186 | original=test_string, 187 | origin_spans=[0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18], 188 | ) 189 | 190 | # bug was here 191 | t.substitute((6, 14), "á") 192 | assert t == Tok( 193 | kind=-1, 194 | txt="xyázúwáöb", 195 | val=None, 196 | original=test_string, 197 | origin_spans=[0, 1, 2, 4, 5, 7, 8, 16, 18], 198 | ) 199 | 200 | 201 | def test_multiple_substitutions() -> None: 202 | t = Tok( 203 | TOK.RAW, 204 | "a&123b&456&789c", 205 | None, 206 | "a&123b&456&789c", 207 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 208 | ) 209 | t.substitute((1, 5), "x") 210 | assert t == Tok( 211 | TOK.RAW, 212 | "axb&456&789c", 213 | None, 214 | "a&123b&456&789c", 215 | [0, 1, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 216 | ) 217 | t.substitute((3, 7), "y") 218 | assert t == Tok( 219 | TOK.RAW, "axby&789c", None, "a&123b&456&789c", [0, 1, 5, 6, 10, 11, 12, 13, 14] 220 | ) 221 | t.substitute((4, 8), "z") 222 | assert t == Tok(TOK.RAW, "axbyzc", None, "a&123b&456&789c", [0, 1, 5, 6, 10, 14]) 223 | 224 | 225 | def test_substitute_without_origin_tracking() -> None: 226 | t = Tok(TOK.RAW, "a&123b", None) 227 | t.substitute((1, 5), "x") 228 | assert t == Tok(TOK.RAW, "axb", None) 229 | 230 | t = Tok(TOK.RAW, "ab&123", None) 231 | t.substitute((2, 6), "x") 232 | assert t == Tok(TOK.RAW, "abx", None) 233 | 234 | t = Tok(TOK.RAW, "&123ab", None) 235 | t.substitute((0, 4), "x") 236 | assert t == Tok(TOK.RAW, "xab", None) 237 | 238 | t = Tok(TOK.RAW, "a&123b&456c", None) 239 | t.substitute((1, 5), "x") 240 | assert t == Tok(TOK.RAW, "axb&456c", None) 241 | t.substitute((3, 7), "y") 242 | assert t == Tok(TOK.RAW, "axbyc", None) 243 | 244 | 245 | def test_substitute_that_removes() -> None: 246 | t = Tok(TOK.RAW, "a&123b", None, "a&123b", [0, 1, 2, 3, 4, 5]) 247 | t.substitute((1, 5), "") 248 | assert t == Tok(TOK.RAW, "ab", None, "a&123b", [0, 5]) 249 | 250 | t = Tok(TOK.RAW, "&123ab", None, "&123ab", [0, 1, 2, 3, 4, 5]) 251 | t.substitute((0, 4), "") 252 | assert t == Tok(TOK.RAW, "ab", None, "&123ab", [4, 5]) 253 | 254 | t = Tok(TOK.RAW, "ab&123", None, "ab&123", [0, 1, 2, 3, 4, 5]) 255 | t.substitute((2, 6), "") 256 | assert t == Tok(TOK.RAW, "ab", None, "ab&123", [0, 1]) 257 | 258 | 259 | def test_split_without_origin_tracking() -> None: 260 | t = Tok(TOK.RAW, "boat", None) 261 | l, r = t.split(2) 262 | 263 | assert l == Tok(TOK.RAW, "bo", None) 264 | assert r == Tok(TOK.RAW, "at", None) 265 | 266 | ### 267 | 268 | # original: "a&123b". replace "&123" with "x" and end up with "axb" 269 | t = Tok(TOK.RAW, "axb", None) 270 | 271 | l1, r1 = t.split(1) 272 | assert l1 == Tok(TOK.RAW, "a", None) 273 | assert r1 == Tok(TOK.RAW, "xb", None) 274 | 275 | l2, r2 = t.split(2) 276 | assert l2 == Tok(TOK.RAW, "ax", None) 277 | assert r2 == Tok(TOK.RAW, "b", None) 278 | 279 | ### 280 | 281 | # original: " a&123b". strip whitespace and replace "&123" with "x" and end up with "axb" 282 | t = Tok(TOK.RAW, "axb", None) 283 | 284 | l1, r1 = t.split(1) 285 | assert l1 == Tok(TOK.RAW, "a", None) 286 | assert r1 == Tok(TOK.RAW, "xb", None) 287 | 288 | l2, r2 = t.split(2) 289 | assert l2 == Tok(TOK.RAW, "ax", None) 290 | assert r2 == Tok(TOK.RAW, "b", None) 291 | 292 | ### 293 | 294 | t = Tok(TOK.RAW, "boat", None) 295 | l, r = t.split(2) 296 | 297 | assert l == Tok(TOK.RAW, "bo", None) 298 | assert r == Tok(TOK.RAW, "at", None) 299 | 300 | 301 | def test_html_escapes_with_origin_tracking() -> None: 302 | test_string = "xyazáwab" 303 | tokens = list(tokenizer.generate_raw_tokens(test_string, replace_html_escapes=True)) 304 | assert len(tokens) == 1 305 | assert tokens[0] == Tok( 306 | kind=TOK.RAW, 307 | txt="xyazáwab", 308 | val=None, 309 | original=test_string, 310 | origin_spans=[0, 1, 2, 8, 9, 17, 18, 23], 311 | ) 312 | # Note the space after   313 | test_string = "Ég  fór út." 314 | # Here we show in comments when a new token starts in the string with "|" (inclusive). 315 | # | | | 316 | # We also show the character indices for each token. 317 | # 0101234567890123 318 | # And where the 'txt' is. 319 | # ^^ ^^^ ^^^ 320 | tokens = list(tokenizer.generate_raw_tokens(test_string, replace_html_escapes=True)) 321 | assert len(tokens) == 3 322 | assert tokens == [ 323 | Tok(kind=TOK.RAW, txt="Ég", val=None, original="Ég", origin_spans=[0, 1]), 324 | Tok( 325 | kind=TOK.RAW, 326 | txt="fór", 327 | val=None, 328 | original="  fór", 329 | origin_spans=[7, 8, 9], 330 | ), 331 | Tok(kind=TOK.RAW, txt="út.", val=None, original=" út.", origin_spans=[1, 2, 3]), 332 | ] 333 | test_string = "Ég  fór út." 334 | # | | | 335 | # 010123456789012340123 336 | # ^^ ^^^ ^^^ 337 | tokens = list(tokenizer.generate_raw_tokens(test_string, replace_html_escapes=True)) 338 | assert len(tokens) == 3 339 | assert tokens == [ 340 | Tok(kind=TOK.RAW, txt="Ég", val=None, original="Ég", origin_spans=[0, 1]), 341 | Tok( 342 | kind=TOK.RAW, 343 | txt="fór", 344 | val=None, 345 | original="  fór", 346 | origin_spans=[12, 13, 14], 347 | ), 348 | Tok(kind=TOK.RAW, txt="út.", val=None, original=" út.", origin_spans=[1, 2, 3]), 349 | ] 350 | test_string = "Ég fór út." 351 | # | | | 352 | # 01012345678012345678 353 | # ^^ ^^^ ^^^ 354 | tokens = list(tokenizer.generate_raw_tokens(test_string, replace_html_escapes=True)) 355 | assert len(tokens) == 3 356 | assert tokens == [ 357 | Tok(kind=TOK.RAW, txt="Ég", val=None, original="Ég", origin_spans=[0, 1]), 358 | Tok( 359 | kind=TOK.RAW, 360 | txt="fór", 361 | val=None, 362 | original=" fór", 363 | origin_spans=[6, 7, 8], 364 | ), 365 | Tok( 366 | kind=TOK.RAW, 367 | txt="út.", 368 | val=None, 369 | original=" út.", 370 | origin_spans=[6, 7, 8], 371 | ), 372 | ] 373 | test_string = "Ég fór út. " 374 | # | | | 375 | # 0101230123012345 376 | # ^^ ^^^ ^^^ 377 | tokens = list(tokenizer.generate_raw_tokens(test_string, replace_html_escapes=True)) 378 | assert len(tokens) == 4 379 | assert tokens == [ 380 | Tok(kind=TOK.RAW, txt="Ég", val=None, original="Ég", origin_spans=[0, 1]), 381 | Tok(kind=TOK.RAW, txt="fór", val=None, original=" fór", origin_spans=[1, 2, 3]), 382 | Tok(kind=TOK.RAW, txt="út.", val=None, original=" út.", origin_spans=[1, 2, 3]), 383 | Tok(kind=TOK.S_SPLIT, txt="", val=None, original=" ", origin_spans=[]), 384 | ] 385 | test_string = " Ég fór út." 386 | # | | | 387 | # 0123456701230123 388 | # ^^ ^^^ ^^^ 389 | tokens = list(tokenizer.generate_raw_tokens(test_string, replace_html_escapes=True)) 390 | assert len(tokens) == 3 391 | assert tokens == [ 392 | Tok(kind=TOK.RAW, txt="Ég", val=None, original=" Ég", origin_spans=[6, 7]), 393 | Tok(kind=TOK.RAW, txt="fór", val=None, original=" fór", origin_spans=[1, 2, 3]), 394 | Tok(kind=TOK.RAW, txt="út.", val=None, original=" út.", origin_spans=[1, 2, 3]), 395 | ] 396 | 397 | 398 | def test_unicode_escapes_with_origin_tracking() -> None: 399 | test_string = "xya" + ACCENT + "zu" + ACCENT + "wo" + UMLAUT + "b" 400 | tokens = list( 401 | tokenizer.generate_raw_tokens(test_string, replace_composite_glyphs=True) 402 | ) 403 | assert len(tokens) == 1 404 | assert tokens[0] == Tok( 405 | kind=TOK.RAW, 406 | txt="xyázúwöb", 407 | val=None, 408 | original=test_string, 409 | origin_spans=[0, 1, 2, 4, 5, 7, 8, 10], 410 | ) 411 | 412 | test_string = ( 413 | "þetta" + ZEROWIDTH_SPACE + "er" + ZEROWIDTH_NBSP + "eitt" + SOFT_HYPHEN + "orð" 414 | ) 415 | tokens = list( 416 | tokenizer.generate_raw_tokens(test_string, replace_composite_glyphs=True) 417 | ) 418 | assert len(tokens) == 1 419 | assert tokens[0] == Tok( 420 | kind=TOK.RAW, 421 | txt="þettaereittorð", 422 | val=None, 423 | original=test_string, 424 | origin_spans=[0, 1, 2, 3, 4, 6, 7, 9, 10, 11, 12, 14, 15, 16], 425 | ) 426 | 427 | 428 | def test_unicode_escapes_that_are_removed() -> None: 429 | test_string = "a\xadb\xadc" 430 | tokens = list( 431 | tokenizer.generate_raw_tokens(test_string, replace_composite_glyphs=True) 432 | ) 433 | assert len(tokens) == 1 434 | assert tokens[0] == Tok( 435 | kind=TOK.RAW, txt="abc", val=None, original=test_string, origin_spans=[0, 2, 4] 436 | ) 437 | 438 | 439 | def test_html_unicode_mix() -> None: 440 | test_string = "xya" + ACCENT + "zu" + ACCENT + "wáo" + UMLAUT + "b" 441 | # 012 3 45 6 7890123456 7 8 442 | tokens = list( 443 | tokenizer.generate_raw_tokens( 444 | test_string, replace_composite_glyphs=True, replace_html_escapes=True 445 | ) 446 | ) 447 | assert len(tokens) == 1 448 | assert tokens[0] == Tok( 449 | kind=TOK.RAW, 450 | txt="xyázúwáöb", 451 | val=None, 452 | original=test_string, 453 | origin_spans=[0, 1, 2, 4, 5, 7, 8, 16, 18], 454 | ) 455 | 456 | 457 | def test_tok_concatenation() -> None: 458 | str1 = "asdf" 459 | tok1 = Tok(TOK.RAW, str1, None, str1, list(range(len(str1)))) 460 | str2 = "jklæ" 461 | tok2 = Tok(TOK.RAW, str2, None, str2, list(range(len(str1)))) 462 | assert tok1.concatenate(tok2) == Tok( 463 | TOK.RAW, str1 + str2, None, str1 + str2, list(range(len(str1 + str2))) 464 | ) 465 | 466 | str1 = "abc" 467 | or1 = "&123&456&789" 468 | str2 = "xyz" 469 | or2 = "&xx&yy&zz" 470 | tok1 = Tok(TOK.RAW, str1, None, or1, [0, 4, 8]) 471 | tok2 = Tok(TOK.RAW, str2, None, or2, [0, 2, 4]) 472 | assert tok1.concatenate(tok2) == Tok( 473 | TOK.RAW, str1 + str2, None, or1 + or2, [0, 4, 8, 12, 14, 16] 474 | ) 475 | 476 | 477 | def test_tok_concatenation_with_separator() -> None: 478 | str1 = "asdf" 479 | tok1 = Tok(TOK.RAW, str1, None, str1, list(range(len(str1)))) 480 | str2 = "jklæ" 481 | tok2 = Tok(TOK.RAW, str2, None, str2, list(range(len(str1)))) 482 | sep = "WOLOLO" 483 | assert tok1.concatenate(tok2, separator=sep) == Tok( 484 | TOK.RAW, 485 | str1 + sep + str2, 486 | None, 487 | str1 + str2, 488 | [0, 1, 2, 3, 4, 4, 4, 4, 4, 4, 4, 5, 6, 7], 489 | ) 490 | 491 | str1 = "abc" 492 | or1 = "&123&456&789" 493 | str2 = "xyz" 494 | or2 = "&xx&yy&zz" 495 | tok1 = Tok(TOK.RAW, str1, None, or1, [0, 4, 8]) 496 | tok2 = Tok(TOK.RAW, str2, None, or2, [0, 2, 4]) 497 | sep = "WOLOLO" 498 | assert tok1.concatenate(tok2, separator=sep) == Tok( 499 | TOK.RAW, 500 | str1 + sep + str2, 501 | None, 502 | or1 + or2, 503 | [0, 4, 8, 12, 12, 12, 12, 12, 12, 12, 14, 16], 504 | ) 505 | 506 | 507 | def test_tok_substitute_all() -> None: 508 | s = "asdf" 509 | t = Tok(TOK.RAW, s, None, s, list(range(len(s)))) 510 | t.substitute_all("d", "x") 511 | assert t == Tok(TOK.RAW, "asxf", None, s, [0, 1, 2, 3]) 512 | 513 | s = "Þetta er lengri strengur." 514 | t = Tok(TOK.RAW, s, None, s, list(range(len(s)))) 515 | t.substitute_all("e", "x") 516 | assert t == Tok(TOK.RAW, "Þxtta xr lxngri strxngur.", None, s, list(range(len(s)))) 517 | 518 | s = "asdf" 519 | t = Tok(TOK.RAW, s, None, s, list(range(len(s)))) 520 | t.substitute_all("d", "") 521 | assert t == Tok(TOK.RAW, "asf", None, s, [0, 1, 3]) 522 | 523 | s = "Þessi verður lengri." 524 | # 01234567890123456789 525 | t = Tok(TOK.RAW, s, None, s, list(range(len(s)))) 526 | t.substitute_all("r", "") 527 | assert t == Tok( 528 | TOK.RAW, 529 | "Þessi veðu lengi.", 530 | None, 531 | s, 532 | [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 15, 16, 18, 19], 533 | ) 534 | 535 | 536 | def test_tok_substitute_longer() -> None: 537 | s = "asdf" 538 | t = Tok(TOK.RAW, s, None, s, list(range(len(s)))) 539 | t.substitute_longer((1, 2), "xyz") 540 | assert t == Tok(TOK.RAW, "axyzdf", None, s, [0, 2, 2, 2, 2, 3]) 541 | 542 | s = "asdf" 543 | t = Tok(TOK.RAW, s, None, s, list(range(len(s)))) 544 | t.substitute_longer((3, 4), "xyz") 545 | assert t == Tok(TOK.RAW, "asdxyz", None, s, [0, 1, 2, 4, 4, 4]) 546 | 547 | s = "asdf" 548 | t = Tok(TOK.RAW, s, None, s, list(range(len(s)))) 549 | t.substitute_longer((0, 1), "xyz") 550 | assert t == Tok(TOK.RAW, "xyzsdf", None, s, [1, 1, 1, 1, 2, 3]) 551 | 552 | 553 | def test_tok_from_txt() -> None: 554 | s = "asdf" 555 | t = Tok.from_txt(s) 556 | assert t == Tok(TOK.RAW, s, None, s, list(range(len(s)))) 557 | 558 | s = " asdf" 559 | t = Tok.from_txt(s) 560 | assert t == Tok(TOK.RAW, s, None, s, list(range(len(s)))) 561 | 562 | s = "asdf " 563 | t = Tok.from_txt(s) 564 | assert t == Tok(TOK.RAW, s, None, s, list(range(len(s)))) 565 | 566 | s = " asdf " 567 | t = Tok.from_txt(s) 568 | assert t == Tok(TOK.RAW, s, None, s, list(range(len(s)))) 569 | 570 | s = "Tok getur alveg verið heil setning." 571 | t = Tok.from_txt(s) 572 | assert t == Tok(TOK.RAW, s, None, s, list(range(len(s)))) 573 | 574 | s = "HTML & er líka óbreytt" 575 | t = Tok.from_txt(s) 576 | assert t == Tok(TOK.RAW, s, None, s, list(range(len(s)))) 577 | 578 | s = ( 579 | "unicode" 580 | + ZEROWIDTH_SPACE 581 | + "er" 582 | + ZEROWIDTH_NBSP 583 | + "líka" 584 | + SOFT_HYPHEN 585 | + "óbreytt" 586 | ) 587 | t = Tok.from_txt(s) 588 | assert t == Tok(TOK.RAW, s, None, s, list(range(len(s)))) 589 | -------------------------------------------------------------------------------- /test/toktest_edgecases.txt: -------------------------------------------------------------------------------- 1 | Þrjátíu starfsmenn Landspítala eru í einangrun með kórónuveiruna og 176 eru í sóttkví. Fresta hefur þurft aðgerðum vegna þeirrar stöðu sem uppi er komin. Þetta kemur fram í tilkynningu frá viðbragðsstjórn og farsóttanefnd Landspítala en spítalinn er nú á hættustigi vegna kórónuveirufaraldursins. Einangrun og sóttkví starfsmanna hefur haft áhrif á starfsemi í skurðlækningaþjónustu og hefur nú um 60 aðgerðum verið frestað, að því er fram kemur í frétt Mbl. Aðgerðum er þó forgangsraðað þannig að allar brýnar aðgerðir á borð við krabbameinsaðgerðir eru framkvæmdar. Þá hafa 40 starfsmenn á Hringbraut verið boðaðir í skimun fyrir veirunni í dag. Dregið verður úr starfsemi göngudeilda A3 og B3 til að styrkja Covid-göngudeild spítalans. Gert er ráð fyrir að sú ráðstöfun gildi fram yfir helgi. Einn liggur inni á spítalanum með veiruna og 357 sjúklingar eru í eftirliti Covid-göngudeildar. Líkt og áður segir eru 176 starfsmenn í sóttkví, þar á meðal forstjórinn Páll Matthíasson og Anna Sigrún Baldursdóttir aðstoðarmaður forstjóra. Þrjátíu starfsmenn eru smitaðir af veirunni og eru í einangrun. Ísland er komið á rauða lista breskra stjórn­valda sem þýðir að all­ir þeir sem ferðast frá Íslandi til Bret­lands frá og með klukk­an 4 að morgni laug­ar­dags, þurfa að fara í 14 daga sótt­kví. Ásamt Íslandi fara Dan­mörk, Slóvakía og Karíbahafs-eyj­an Curacao á rauða lista breskra stjórn­valda. Að því er kem­ur fram í frétt The Even­ing Stand­ard, þá telja bresk stjórn­völd ekki ör­uggt að ferðalang­ar frá Íslandi fari ekki í sótt­kví við kom­una til Bret­lands. „Gögn­in sýna að við verðum að fjar­lægja Ísland, Dan­mörk, Slóvakíu og Curacao, af lista stjórn­valda yfir ör­ugg lönd,“ sagði Grant Shapps, sam­göngu­málaráðherra Bret­lands á Twitter nú síðdeg­is. Fari fólk ekki eft­ir þess­um regl­um má það bú­ast við að lág­marki 1.000 punda sekt, sem jafn­gild­ir um 178 þúsund krón­um. Utanríkisráðuneytið í Kaupmannahöfn ræður Dönum frá því að ferðast til Íslands eins og sakir standa. Þetta kemur fram í nýjum lista sem ráðuneytið birti í dag. Auk Íslands hefur þremur ríkjum til viðbótar verið bætt á listann; Bretlandi, Írlandi og Slóveníu. Í öllum tilfellum eru smit í þessum löndum orðin fleiri á viku en þrjátíu á hverja hundrað þúsund íbúa. Þegar tíðnin er orðin svo mikil ráðleggja stjórnvöld í Danmörku landsmönnum að ferðast ekki til landanna að nauðsynjalausu. Nítján Evrópuríki til viðbótar eru á lista danska utanríkisráðuneytisins. Bresk stjórnvöld fjarlægðu Ísland í dag af lista yfir þau ríki sem teljast örugg vegna kórónuveirufaraldursins. Bretar tilkynntu um metfjöldi nýrra smita í dag. Grant Shapps, samgönguráðherra, tilkynnti síðdegis að Ísland, Danmörk, Slóvakía og karabíska eyjan Curaco væru nú ekki talin örugg. Það þýðir að allir sem koma frá þeim löndum þurfa að fara í tveggja vikna sóttkví við komuna til Englands. Ef reglur um sóttkví eru brotnar varðar það sektum frá þúsund pundum að minnsta kosti. Alls greindust 6.634 ný smit í Bretlandi síðasta sólarhringinn og hafa þau aldrei verið fleiri frá upphafi faraldursins. Tilkynnt var um 40 ný andlát vegna COVID-19, en þungamiðja faraldursins hjá Bretum er í Englandi. Stjórn Rauða Krossins hefur síðustu mánuði tekið upp á sameiningu deilda til að draga úr kostnaði vegna fjárhagsvanda í kjölfar kórónaveirufaraldursins. Fjárhagsstaða Rauða Krossins hefur verið erfið vegna COVID-19 og er gríðarlegt álag á starfsmönnum og sjálfboðaliðum Rauða krossins. Í maí var orðið ljóst að faraldurinn myndi hafa mikil áhrif á tekjustofna félagsins. Var þá rætt um möguleika til hagræðingar bæði hjá landsskrifstofu og félaginu öllu. Fjölmargar breytingar hafa í kjölfarið verið gerðar á skipulagi Rauða krossins. Svæðisfulltrúum Rauða krossins, þ.e. launuðum fulltrúm deilda víða um landið, hefur verið sagt upp. Sérstakur neyðarvarnarfulltrúi sem sér um samhæfingu neyðarvarna alls staðar á landinu hefur verið ráðin inn. UEFA hefur tilkynnt þá ákvörðun að fimm skiptingar verða leyfðar í mótum á vegum UEFA tímabilið 2020-2021. Breytingin tekur til móta líkt og Þjóðadeildar UEFA og umspils fyrir EM 2020 hjá A-landsliði karla og undankeppni EM 2022 hjá A-landsliði kvenna. Hinar nýju reglur munu einnig gilda í Meistaradeild Evrópu, karla og kvenna, og Evrópudeildinni. A-landslið karla leikur þrjá leiki í október. Fyrst mætir liðið Rúmeníu í undanúrslitum umspilsins fyrir EM 2020, en síðan mæta strákarnir Dönum og Belgum í Þjóðadeild UEFA. Allir leikirnir fara fram á Laugardalsvelli. A-landslið kvenna mætir Svíþjóð ytra í október í undankeppni EM 2022. Tíu prósent Íslendinga hafa nýtt sér Erasmus-áætlunina frá aðild Íslands að EES og jafn hátt hlutfall ríkisborgara ESB-ríkjanna býr nú á Íslandi, segir Lucie Samcová – Hall Allen, nýr sendiherra Evrópusambandsins á Íslandi. Hún afhenti Guðna Th. Jóhannessyni, forseta Íslands, trúnaðarbréf sitt á Bessastöðum nýverið. Lucie Samcová er frá Prag í Tékklandi og hefur starfað fyrir Evrópusambandið síðan 2005, meðal annars fyrir ráðherraráðið og utanríkisþjónustuna. Þá hefur hún einnig starfað fyrir sendinefnd ESB gagnvart Sameinuðu þjóðunum og öðrum alþjóðastofnunum í Genf í Sviss. Hún hefur lokið háskólanámi í blaðamennsku, hagfræði og stjórnun. Knattspyrnustjóri Arsenal segir það verða mjög erfitt fyrir Mesut Özil að vinna sér sæti í leikmannahópi liðsins, ekki liðinu heldur hópnum. Mikel Arteta, knattspyrnustjóri Arsenal, var spurður út í enn eina fjarveruna hjá Mesut Özil þegar liðið mætti Leicester City í enska deildabikarnum í gær. Þetta var fjórði leikur Arsenal á tímabilinu og Mesut Özil hefur aldrei verið í hóp. Arteta svaraði að það yrði mjög erfitt fyrir Þjóðverjann að vinna sér sæti í leikmannahópnum. 2 | -------------------------------------------------------------------------------- /test/toktest_edgecases_diff.txt: -------------------------------------------------------------------------------- 1 | 7c7 2 | < Dregið verður úr starfsemi göngudeilda A 3 og B3 til að styrkja Covid-göngudeild spítalans . 3 | --- 4 | > Dregið verður úr starfsemi göngudeilda A3 og B3 til að styrkja Covid-göngudeild spítalans . 5 | 29c29 6 | < Tilkynnt var um 40 ný andlát vegna COVID - 19 , en þungamiðja faraldursins hjá Bretum er í Englandi . 7 | --- 8 | > Tilkynnt var um 40 ný andlát vegna COVID-19 , en þungamiðja faraldursins hjá Bretum er í Englandi . 9 | 31c31 10 | < Fjárhagsstaða Rauða Krossins hefur verið erfið vegna COVID - 19 og er gríðarlegt álag á starfsmönnum og sjálfboðaliðum Rauða krossins . 11 | --- 12 | > Fjárhagsstaða Rauða Krossins hefur verið erfið vegna COVID-19 og er gríðarlegt álag á starfsmönnum og sjálfboðaliðum Rauða krossins . 13 | -------------------------------------------------------------------------------- /test/toktest_edgecases_gold_expected.txt: -------------------------------------------------------------------------------- 1 | Þrjátíu starfsmenn Landspítala eru í einangrun með kórónuveiruna og 176 eru í sóttkví . 2 | Fresta hefur þurft aðgerðum vegna þeirrar stöðu sem uppi er komin . 3 | Þetta kemur fram í tilkynningu frá viðbragðsstjórn og farsóttanefnd Landspítala en spítalinn er nú á hættustigi vegna kórónuveirufaraldursins . 4 | Einangrun og sóttkví starfsmanna hefur haft áhrif á starfsemi í skurðlækningaþjónustu og hefur nú um 60 aðgerðum verið frestað , að því er fram kemur í frétt Mbl . 5 | Aðgerðum er þó forgangsraðað þannig að allar brýnar aðgerðir á borð við krabbameinsaðgerðir eru framkvæmdar . 6 | Þá hafa 40 starfsmenn á Hringbraut verið boðaðir í skimun fyrir veirunni í dag . 7 | Dregið verður úr starfsemi göngudeilda A3 og B3 til að styrkja Covid-göngudeild spítalans . 8 | Gert er ráð fyrir að sú ráðstöfun gildi fram yfir helgi . 9 | Einn liggur inni á spítalanum með veiruna og 357 sjúklingar eru í eftirliti Covid-göngudeildar . 10 | Líkt og áður segir eru 176 starfsmenn í sóttkví , þar á meðal forstjórinn Páll Matthíasson og Anna Sigrún Baldursdóttir aðstoðarmaður forstjóra . 11 | Þrjátíu starfsmenn eru smitaðir af veirunni og eru í einangrun . 12 | Ísland er komið á rauða lista breskra stjórnvalda sem þýðir að allir þeir sem ferðast frá Íslandi til Bretlands frá og með klukkan 4 að morgni laugardags , þurfa að fara í 14 daga sóttkví . 13 | Ásamt Íslandi fara Danmörk , Slóvakía og Karíbahafs-eyjan Curacao á rauða lista breskra stjórnvalda . 14 | Að því er kemur fram í frétt The Evening Standard , þá telja bresk stjórnvöld ekki öruggt að ferðalangar frá Íslandi fari ekki í sóttkví við komuna til Bretlands . 15 | „ Gögnin sýna að við verðum að fjarlægja Ísland , Danmörk , Slóvakíu og Curacao , af lista stjórnvalda yfir örugg lönd , “ sagði Grant Shapps , samgöngumálaráðherra Bretlands á Twitter nú síðdegis . 16 | Fari fólk ekki eftir þessum reglum má það búast við að lágmarki 1.000 punda sekt , sem jafngildir um 178 þúsund krónum . 17 | Utanríkisráðuneytið í Kaupmannahöfn ræður Dönum frá því að ferðast til Íslands eins og sakir standa . 18 | Þetta kemur fram í nýjum lista sem ráðuneytið birti í dag . 19 | Auk Íslands hefur þremur ríkjum til viðbótar verið bætt á listann ; Bretlandi , Írlandi og Slóveníu . 20 | Í öllum tilfellum eru smit í þessum löndum orðin fleiri á viku en þrjátíu á hverja hundrað þúsund íbúa . 21 | Þegar tíðnin er orðin svo mikil ráðleggja stjórnvöld í Danmörku landsmönnum að ferðast ekki til landanna að nauðsynjalausu . 22 | Nítján Evrópuríki til viðbótar eru á lista danska utanríkisráðuneytisins . 23 | Bresk stjórnvöld fjarlægðu Ísland í dag af lista yfir þau ríki sem teljast örugg vegna kórónuveirufaraldursins . 24 | Bretar tilkynntu um metfjöldi nýrra smita í dag . 25 | Grant Shapps , samgönguráðherra , tilkynnti síðdegis að Ísland , Danmörk , Slóvakía og karabíska eyjan Curaco væru nú ekki talin örugg . 26 | Það þýðir að allir sem koma frá þeim löndum þurfa að fara í tveggja vikna sóttkví við komuna til Englands . 27 | Ef reglur um sóttkví eru brotnar varðar það sektum frá þúsund pundum að minnsta kosti . 28 | Alls greindust 6.634 ný smit í Bretlandi síðasta sólarhringinn og hafa þau aldrei verið fleiri frá upphafi faraldursins . 29 | Tilkynnt var um 40 ný andlát vegna COVID-19 , en þungamiðja faraldursins hjá Bretum er í Englandi . 30 | Stjórn Rauða Krossins hefur síðustu mánuði tekið upp á sameiningu deilda til að draga úr kostnaði vegna fjárhagsvanda í kjölfar kórónaveirufaraldursins . 31 | Fjárhagsstaða Rauða Krossins hefur verið erfið vegna COVID-19 og er gríðarlegt álag á starfsmönnum og sjálfboðaliðum Rauða krossins . 32 | Í maí var orðið ljóst að faraldurinn myndi hafa mikil áhrif á tekjustofna félagsins . 33 | Var þá rætt um möguleika til hagræðingar bæði hjá landsskrifstofu og félaginu öllu . 34 | Fjölmargar breytingar hafa í kjölfarið verið gerðar á skipulagi Rauða krossins . 35 | Svæðisfulltrúum Rauða krossins , þ.e. launuðum fulltrúm deilda víða um landið , hefur verið sagt upp . 36 | Sérstakur neyðarvarnarfulltrúi sem sér um samhæfingu neyðarvarna alls staðar á landinu hefur verið ráðin inn . 37 | UEFA hefur tilkynnt þá ákvörðun að fimm skiptingar verða leyfðar í mótum á vegum UEFA tímabilið 2020 - 2021 . 38 | Breytingin tekur til móta líkt og Þjóðadeildar UEFA og umspils fyrir EM 2020 hjá A-landsliði karla og undankeppni EM 2022 hjá A-landsliði kvenna . 39 | Hinar nýju reglur munu einnig gilda í Meistaradeild Evrópu , karla og kvenna , og Evrópudeildinni . 40 | A-landslið karla leikur þrjá leiki í október . 41 | Fyrst mætir liðið Rúmeníu í undanúrslitum umspilsins fyrir EM 2020 , en síðan mæta strákarnir Dönum og Belgum í Þjóðadeild UEFA . 42 | Allir leikirnir fara fram á Laugardalsvelli . 43 | A-landslið kvenna mætir Svíþjóð ytra í október í undankeppni EM 2022 . 44 | Tíu prósent Íslendinga hafa nýtt sér Erasmus-áætlunina frá aðild Íslands að EES og jafn hátt hlutfall ríkisborgara ESB-ríkjanna býr nú á Íslandi , segir Lucie Samcová – Hall Allen , nýr sendiherra Evrópusambandsins á Íslandi . 45 | Hún afhenti Guðna Th. Jóhannessyni , forseta Íslands , trúnaðarbréf sitt á Bessastöðum nýverið . 46 | Lucie Samcová er frá Prag í Tékklandi og hefur starfað fyrir Evrópusambandið síðan 2005 , meðal annars fyrir ráðherraráðið og utanríkisþjónustuna . 47 | Þá hefur hún einnig starfað fyrir sendinefnd ESB gagnvart Sameinuðu þjóðunum og öðrum alþjóðastofnunum í Genf í Sviss . 48 | Hún hefur lokið háskólanámi í blaðamennsku , hagfræði og stjórnun . 49 | Knattspyrnustjóri Arsenal segir það verða mjög erfitt fyrir Mesut Özil að vinna sér sæti í leikmannahópi liðsins , ekki liðinu heldur hópnum . 50 | Mikel Arteta , knattspyrnustjóri Arsenal , var spurður út í enn eina fjarveruna hjá Mesut Özil þegar liðið mætti Leicester City í enska deildabikarnum í gær . 51 | Þetta var fjórði leikur Arsenal á tímabilinu og Mesut Özil hefur aldrei verið í hóp . 52 | Arteta svaraði að það yrði mjög erfitt fyrir Þjóðverjann að vinna sér sæti í leikmannahópnum . 53 | -------------------------------------------------------------------------------- /test/toktest_normal.txt: -------------------------------------------------------------------------------- 1 | Lýstar kröfur í búið námu rúmlega 71 milljón króna og lauk gjaldþrotaskiptum í lok nóvember, án þess að greiðsla fengist upp í kröfur. Verslunin var rekin í félaginu Ígló ehf. sem stofnað var af systrunum Helgu og Lovísu Ólafsdætrum árið 2008 og sérhæfði sig í hönnun barnafata. Helga, sem titluð var eigandi og yfirhönnuður, tjáði sig um gjaldþrotið á sínum tíma og sagði það hafa verið nokkuð áfall. Það hafi þurft þol og metnað til að hanna og framleiða þær 2500 mismunandi flíkur sem aðstandendur fyrirtækisins gerðu, meðan þess naut við. Fyrirtækið byrjaði að leggja meiri áherslu á erlenda markaði árið 2013 og voru vörur þeirra á tímabili seldar í yfir hundrað verslunum. „Það er flókið fyrir lítil hönnunarfyrirtæki á Íslandi að lifa af. Rekstrarumhverfið er ekki bara sveiflukennt heldur er mjög dýrt að þróa, framleiða, markaðssetja og selja íslenska hönnun hérlendis og erlendis,“ skrifaði Helga við fall fyrirtækisins. „Að hafa lifað af í þessu rekstrarumhverfi í ellefu ár er bara nokkuð gott ef miðað er við sambærileg fyrirtæki í þessum bransa. Það er samt engin huggun í því og að þurfa að kveðja á þennan hátt hefur verið ansi erfitt. Það er erfitt að sætta sig við það hvernig fór.“ Læknar á Landspítalanum eru mjög ósáttir við að laun þeirra lækki í niðurskurðaraðgerðum spítalans. Landspítalinn sagði upp óunninni yfirvinnu lækna sem hluta af aðgerðum til að draga úr kostnaði við rekstur sjúkrahússins. Reynir Arngrímsson, formaður Læknafélags Íslands, sagði í Morgunútvarpinu á Rás 2 að mikil óánægja væri vegna þessa. Félagið hefur mótmælt því að hluta ráðningarsamninga lækna sé sagt upp. Hann segir ólaunaða yfirvinnu í raun rangnefni. „Henni er ætlað að greiða fyrir tilfallandi yfirvinnu með jöfnum hætti sem þarf ekki að vera að tímamæla.“ Þetta eigi til dæmis við þegar læknir sé með veikan sjúkling og geti ekki farið, skurðlæknir sé í aðgerð eða læknir að innrita sjúkling fram yfir lok vaktar eða dagvinnu. „Í dag er þetta ekki greitt. Það fæst ekki greidd yfirvinna samkvæmt stimpilmælingunni. Þá hefur verið gripið til þess að hafa fasta yfirvinnu sem tekur á þessu að meðaltali.“ Reynir gagnrýnir að uppsögn óunninnar yfirvinnu nái bara til lækna. Samkvæmt nýjustu tölum sem Læknafélagið hefur hafi yfirstjórn haft tvöfalda óunna yfirvinnu á við lækna. „Við teljum mjög eðlilegt að starfsmenn í framlínu eins og læknar og hjúkrunarfræðingar fái þessa föstu yfirvinnu greidda því að þeir geta ekki hlaupið í burtu en það er mjög sérkennilegt að yfirstjórn spítalans þurfi að gera þetta og í raun og veru skammti sér 400 milljónir í fasta yfirvinnu.“ Reynir sagði uppsögn óunninnar yfirvinnu hafa slæm áhrif á starfsanda. Hann sagðist telja að flestir láti þetta yfir sig ganga en taldi að einhverjir kynnu að hætta. Tíst namibískrar konu á Twitter hefur vakið þó nokkra athygli íslenskra netverja. Konan, Ndilimeke, spyr hvort Jóhannes Stefánsson, fyrrverandi framkvæmdastjóri og stjórnarmaður Samherjafélaganna, sé einhleypur. Fjöldi Íslendinga hefur líkað við tíst Ndilimeke og hefur meðal annars Margrét Erla Maack endurbirt það á sinni Twitter-síðu. Ndilimeke mætti hugsanlega teljast sem namibískur áhrifavaldur, þar sem hún er með yfir 4300 fylgjendur á Instagram og 1200 fylgjendur á Twitter. Gleðifréttir fyrir Ndilimeke, en samkvæmt Facebook-síðu Jóhannesar er hann einhleypur. Árið 2018 kvaðst um þriðjungur íbúa á Íslandi stríða við langvarandi veikindi. Hlutfall fólks sem býr við takmarkanir í daglegu lífi sökum heilsufars hefur aukist undanfarin 10 ár. Árið 2008 var hlutfallið 16,1%, en það var 26,0% 10 árum seinna. Seinustu ár hefur aukningin ekki verið tölfræðilega marktæk á milli ára, en hlutfallið 2018 er þó marktækt hærra en fyrir fimm árum. Þetta er meðal þess sem kemur fram í niðurstöðum úr lífskjararannsókn Hagstofu Íslands. Hlutfallið á Íslandi árið 2018 er svipað og hlutfallið er að jafnaði í ríkjum Evrópusambandsins , en árið 2018 bjuggu að meðaltali 24,7% Evrópubúa við heilsufarslegar takmarkanir. Árið 2018 fundu fæstir fyrir heilsufarslegum takmörkunum á Möltu, eða 11,9%, en flestir í Lettlandi, eða 40,0%. Mælingarnar miða við bæði þá sem búa við nokkrar takmarkanir og verulegar takmarkanir vegna heilsubrests. Á Íslandi skiptist hlutfallið nokkuð jafnt á milli þeirra sem telja að heilsufar hamli daglegu lífi nokkuð og þeirra sem lifa með verulegar takmarkanir vegna heilsubrests. Langtímaveikindi eru algengari meðal kvenna og hafa verið það frá upphafi mælinga, árið 2004. Að sama skapi eru konur líklegri til að finna fyrir takmörkunum í daglegu lífi vegna heilsubrests, miðað við karla. Árið 2018 bjó ein af hverjum þremur konum við heilsufarslegar takmarkanir í daglegu lífi borið saman við einn af hverjum fimm körlum sama ár. Þeir sem eru eldri eiga frekar við langvarandi veikindi að stríða og búa við meiri heilsufarslegar takmarkanir en yngra fólkið. Til dæmis á þriðjungur í aldurshópnum 45-54 ára við langvarandi veikindi að stríða borið saman við nær helming í aldurshópnum 65 ára og eldri. Hærra hlutfall fólks með grunnmenntun býr við langvarandi veikindi og takmarkanir sökum heilsufars borið saman við fólk með háskólamenntun. Árið 2018 bjó til dæmis um einn af hverjum þremur með grunnmenntun við heilsufarslegar takmarkanir borið saman við einn af hverjum fimm með háskólamenntun. Hlutfall þeirra sem lifa með heilsufarslegar takmarkanir í daglegu lífi er einnig hærra meðal þeirra með lágar tekjur, en einn af hverjum þremur í lægsta og öðrum tekjufimmtungi bjó við takmarkanir vegna heilsufars árið 2018. Í hærri tekjufimmtungum hamlaði heilsufar einum af hverjum fjórum til einum af hverjum fimm. Niðurstöðurnar eru unnar úr lífskjararannsókn Hagstofu Íslands. Samhliða birtingu á niðurstöðum fyrir árin 2015-2018 hafa eldri niðurstöður verið endurskoðaðar til að samræmast reglugerðum Hagstofu Evrópusambandsins, Eurostat. Áhrif endurskoðunar er sú að hærra hlutfall telst með takmarkanir sökum heilsufars en áður frá og með árinu 2007. Hlutfall fólks sem býr við takmarkanir í daglegu lífi sökum heilsufars byggir á svörum fólks við eftirfarandi spurningu: Í sex mánuði samfleytt eða lengur, hefur heilsufar þitt hamlað eða takmarkað þig á einhvern hátt í einhverju sem reikna má með að flest fólk geti gert? Hlutfallið miðar við bæði þá sem takmarkast og takmarkast verulega sökum heilsufars. Lífskjararannsókn Hagstofunnar er langsniðsrannsókn þar sem haft er samband við hátt í 5 þúsund heimili árlega. Úrtak rannsóknarinnar er fengið á þann hátt að einstaklingar eru valdir með slembni úr þjóðskrá og þar með heimilið sem þeir tilheyra. Sá einstaklingur sem er valinn í úrtakið kallast valinn svarandi og veitir hann upplýsingar um aðstæður heimilis, sínar eigin og annarra heimilismeðlima. Hér er greiningin miðuð við valinn svaranda. Frekari upplýsingar um lífskjararannsóknina má lesa í lýsigögnum. Kostnaður vegna bílaleigubíla fyrir þingmenn nam rúmum 19 milljónum króna árið 2018 og nemur hann rétt rúmum sautján milljónum króna það sem af er þessu ári. Þetta kemur fram í Morgunblaðinu í dag en þar kemur fram að aksturskostnaður þingmanna hafi dregist mjög saman á undanförnum árum. Í fréttinni kemur fram að aksturskostnaður þingmanna hafi numið tæpum 68 milljónum króna árið 2013, 56 milljónum árið 2014, 50 milljónum árið 2015, 53 milljónum árið 2016, 43 milljónum árið 2017, 31 milljón í fyrra og 25 milljónum það sem af er þessu ári. Þennan samdrátt má meðal annars rekja til breytinga á reglum um þingfarakostnað í byrjun árs 2018. Í fréttinni segir að þó aksturskostnaður hafi dregist saman hefur kostnaður við bílaleigubíla aukist. Ásmundur Friðriksson er með hæsta kostnaðinn vegna bílaleigubíla en upphæðin nemur 2,8 milljónum króna á árinu. Bent er á það að kostnaðurinn hafi að jafnaði verið 100 til 200 þúsund krónur á mánuði en í október hafi hann verið ríflega 1,6 milljónir króna. „Ég lít aldrei á þetta, hvorki hjá mér né öðrum,“ hefur blaðið eftir Ásmundi sem sagðist ekki hafa skýringar á þessum kostnaði í október á reiðum höndum. Hann hefði þó nýlega skipt um bílaleigubíl og hugsanlega mætti rekja kostnaðinn til þess. Það staðfestir Eggert Jónsson, forstöðumaður hjá Alþingi, en hann segir að inni í leigunni sé 20 þúsund kílómetra akstur á ári. Gleymst hafi að lesa af mælinum og kostnaðurinn í október sé því umframakstur síðustu tveggja ára. Ekki sé hægt að kenna Ásmundi um það. Fimm þingmenn eru með langtímaleigu á bílaleigubílum en auk Ásmundar eru það Birgir Þórarinsson, Guðjón S. Brjánsson, Haraldur Benediktsson og Sigurður Páll Jónsson. Ver­um ábyrg og mun­um alltaf að skafa vel af öll­um rúðum öku­tæk­is áður en haldið er út í um­ferðina. Sköf­um af ljós­um og núm­era­plöt­um í leiðinni, minn­ir lög­regl­an á höfuðborg­ar­svæðinu á í færslu sem hún birti á Face­book. Skipulags- og samgönguráð Reykjavíkurborgar samþykkti á fundi sínum í gær að auglýsa nýtt deiliskipulag fyrir Hlemmsvæðið. Svæðið mun taka stakkaskiptum á næstu árum verði framkvæmdir að veruleika. Borgarráð þarf að staðfesta deiliskipulagstillöguna til auglýsingar. Í desember árið 2018 var efnt til svokallaðar hugmyndaleitar þar sem leitað var eftir hugmyndum um framtíðarútlit svæðisins. Alls bárust þrjár tillögur um framtíð svæðisins frá Arkitekastofunum Landslagi, DLD land design og Mandaworks. Á vef Reykjavíkurborgar segir að tillögur arkitektastofanna Mandaworks og DLD hafi verið valdar til áframhaldandi þróunarvinnu við Hlemm fyrir endurhönnun svæðisins og gerð nýs deiliskipulags. „Meginmarkmið breytinganna er að skapa gott vistvænt umhverfi á Hlemmi fyrir gangandi vegfarendur, stað sem mun gegna mikilvægu hlutverki fyrir mannlíf og samgöngur á höfuðborgarsvæðinu,“ segir á vef Reykjavíkurborgar. Hlemmur eigi að vera kjörstaður fyrir gangangi og hjólandi vegfarendur, eftirsóttur bíllaus staður. Þannig verði akandi umferð beint frá svæðinu, nýr hjólastígur verði lagður meðfram svæðinu og tengir Hverfisgötu og Laugaveg upp að Bríetartúni. Þá verður Hlemmtorg rammað inn af smærri byggingum sem afmarki nýtt almenningsrými í austurhluta miðborgarinnar. Umbreyting norðurhluta Hlemmtorgs felist meðal annars í meiri gróðri en þar er nú og setbekkjum. Þess má geta að tillaga Mandaworks gerði ráð fyrir borðtennisbar og tillaga DLD gerði ráð fyrir gróðurhúsum á svæðinu. Skoða má deiliskipulagstillöguna nánar hér. Þjálfarinn og íþróttasérfræðingurinn Simon Brundish fylgist vel með enska boltanum og þar helst sínu uppáhaldsliði, toppliði Liverpool. Þeir rauðklæddu unnu í gær nokkuð þægilegan sigur á grönnum sínum í Everton en Liverpool vann 5-2 sigur eftir að hafa verið 4-2 yfir eftir bráðfjörugan fyrri hálfleik. Divock Origi fékk tækifæri í byrjunarliði Liverpool og nýtti það heldur betur en hann skoraði tvö mörk í leiknum. Simon birti athyglisverða tölfræði á Twitter-síðu sinni þar sem hann segir frá því að Origi sé með 29 mörk og sjö stoðsendingar í leikjum Liverpool sem fara fram í flóðljósum. Tölfræði hans í dagsbirtu er allt önnur. Þar hefur Origi einungis skorað fimm mörk og gefið eina stoðsendingu en hann er greinilega leikmaður sem líkar við kvöldleikina. „Einhver annar sem hefur spáð í því að kannski er Origi vampíra,“ skrifaði Simon léttur í bragði á Twitter í gær enda hans menn með átta stiga forskot á toppi deildarinnar. Gróðureldarnir í Ástralíu undanfarna mánuði hafa valdið mikilli mengun meðfram austurströnd landsins. Eldar hafa logað á þessum slóðum frá því í september og greindu yfirvöld umhverfismála í Nýja Suður-Wales frá því í morgun að loftmengun af þeirra völdum hefði verið einhver sú mesta sem mælst hefði í Ástralíu. Loftgæði hefðu oft verið slæm í eldum á þessum slóðum, í gróðureldum við Sydney árið 1994 og í eldunum frá desember 2001 og fram í janúar 2002, en um þá hefur oft verið talað um svört jól. Ástandið núna hefði varað mun lengur og útbreiðsla eldanna meiri. Miklir eldar hafa verið nærri Sydney undanfarnar vikur og margar viðvaranir hafa verið gefnar úr um slæm loftgæði. Í morgun loguðu eldar á meira en tíu stöðum í nágrenni borgarinnar. Mjög hefur fjölgað þeim sem leitað hafa læknishjálpar vegna öndunarörðugleika í Sydney. 2 | -------------------------------------------------------------------------------- /test/toktest_normal_gold_expected.txt: -------------------------------------------------------------------------------- 1 | Lýstar kröfur í búið námu rúmlega 71 milljón króna og lauk gjaldþrotaskiptum í lok nóvember , án þess að greiðsla fengist upp í kröfur . 2 | Verslunin var rekin í félaginu Ígló ehf. sem stofnað var af systrunum Helgu og Lovísu Ólafsdætrum árið 2008 og sérhæfði sig í hönnun barnafata . 3 | Helga , sem titluð var eigandi og yfirhönnuður , tjáði sig um gjaldþrotið á sínum tíma og sagði það hafa verið nokkuð áfall . 4 | Það hafi þurft þol og metnað til að hanna og framleiða þær 2500 mismunandi flíkur sem aðstandendur fyrirtækisins gerðu , meðan þess naut við . 5 | Fyrirtækið byrjaði að leggja meiri áherslu á erlenda markaði árið 2013 og voru vörur þeirra á tímabili seldar í yfir hundrað verslunum . 6 | „ Það er flókið fyrir lítil hönnunarfyrirtæki á Íslandi að lifa af . 7 | Rekstrarumhverfið er ekki bara sveiflukennt heldur er mjög dýrt að þróa , framleiða , markaðssetja og selja íslenska hönnun hérlendis og erlendis , “ skrifaði Helga við fall fyrirtækisins . 8 | „ Að hafa lifað af í þessu rekstrarumhverfi í ellefu ár er bara nokkuð gott ef miðað er við sambærileg fyrirtæki í þessum bransa . 9 | Það er samt engin huggun í því og að þurfa að kveðja á þennan hátt hefur verið ansi erfitt . 10 | Það er erfitt að sætta sig við það hvernig fór . “ 11 | Læknar á Landspítalanum eru mjög ósáttir við að laun þeirra lækki í niðurskurðaraðgerðum spítalans . 12 | Landspítalinn sagði upp óunninni yfirvinnu lækna sem hluta af aðgerðum til að draga úr kostnaði við rekstur sjúkrahússins . 13 | Reynir Arngrímsson , formaður Læknafélags Íslands , sagði í Morgunútvarpinu á Rás 2 að mikil óánægja væri vegna þessa . 14 | Félagið hefur mótmælt því að hluta ráðningarsamninga lækna sé sagt upp . 15 | Hann segir ólaunaða yfirvinnu í raun rangnefni . 16 | „ Henni er ætlað að greiða fyrir tilfallandi yfirvinnu með jöfnum hætti sem þarf ekki að vera að tímamæla . “ 17 | Þetta eigi til dæmis við þegar læknir sé með veikan sjúkling og geti ekki farið , skurðlæknir sé í aðgerð eða læknir að innrita sjúkling fram yfir lok vaktar eða dagvinnu . 18 | „ Í dag er þetta ekki greitt . 19 | Það fæst ekki greidd yfirvinna samkvæmt stimpilmælingunni . 20 | Þá hefur verið gripið til þess að hafa fasta yfirvinnu sem tekur á þessu að meðaltali . “ 21 | Reynir gagnrýnir að uppsögn óunninnar yfirvinnu nái bara til lækna . 22 | Samkvæmt nýjustu tölum sem Læknafélagið hefur hafi yfirstjórn haft tvöfalda óunna yfirvinnu á við lækna . 23 | „ Við teljum mjög eðlilegt að starfsmenn í framlínu eins og læknar og hjúkrunarfræðingar fái þessa föstu yfirvinnu greidda því að þeir geta ekki hlaupið í burtu en það er mjög sérkennilegt að yfirstjórn spítalans þurfi að gera þetta og í raun og veru skammti sér 400 milljónir í fasta yfirvinnu . “ 24 | Reynir sagði uppsögn óunninnar yfirvinnu hafa slæm áhrif á starfsanda . 25 | Hann sagðist telja að flestir láti þetta yfir sig ganga en taldi að einhverjir kynnu að hætta . 26 | Tíst namibískrar konu á Twitter hefur vakið þó nokkra athygli íslenskra netverja . 27 | Konan , Ndilimeke , spyr hvort Jóhannes Stefánsson , fyrrverandi framkvæmdastjóri og stjórnarmaður Samherjafélaganna , sé einhleypur . 28 | Fjöldi Íslendinga hefur líkað við tíst Ndilimeke og hefur meðal annars Margrét Erla Maack endurbirt það á sinni Twitter-síðu . 29 | Ndilimeke mætti hugsanlega teljast sem namibískur áhrifavaldur , þar sem hún er með yfir 4300 fylgjendur á Instagram og 1200 fylgjendur á Twitter . 30 | Gleðifréttir fyrir Ndilimeke , en samkvæmt Facebook-síðu Jóhannesar er hann einhleypur . 31 | Árið 2018 kvaðst um þriðjungur íbúa á Íslandi stríða við langvarandi veikindi . 32 | Hlutfall fólks sem býr við takmarkanir í daglegu lífi sökum heilsufars hefur aukist undanfarin 10 ár . 33 | Árið 2008 var hlutfallið 16,1% , en það var 26,0% 10 árum seinna . 34 | Seinustu ár hefur aukningin ekki verið tölfræðilega marktæk á milli ára , en hlutfallið 2018 er þó marktækt hærra en fyrir fimm árum . 35 | Þetta er meðal þess sem kemur fram í niðurstöðum úr lífskjararannsókn Hagstofu Íslands . 36 | Hlutfallið á Íslandi árið 2018 er svipað og hlutfallið er að jafnaði í ríkjum Evrópusambandsins , en árið 2018 bjuggu að meðaltali 24,7% Evrópubúa við heilsufarslegar takmarkanir . 37 | Árið 2018 fundu fæstir fyrir heilsufarslegum takmörkunum á Möltu , eða 11,9% , en flestir í Lettlandi , eða 40,0% . 38 | Mælingarnar miða við bæði þá sem búa við nokkrar takmarkanir og verulegar takmarkanir vegna heilsubrests . 39 | Á Íslandi skiptist hlutfallið nokkuð jafnt á milli þeirra sem telja að heilsufar hamli daglegu lífi nokkuð og þeirra sem lifa með verulegar takmarkanir vegna heilsubrests . 40 | Langtímaveikindi eru algengari meðal kvenna og hafa verið það frá upphafi mælinga , árið 2004 . 41 | Að sama skapi eru konur líklegri til að finna fyrir takmörkunum í daglegu lífi vegna heilsubrests , miðað við karla . 42 | Árið 2018 bjó ein af hverjum þremur konum við heilsufarslegar takmarkanir í daglegu lífi borið saman við einn af hverjum fimm körlum sama ár . 43 | Þeir sem eru eldri eiga frekar við langvarandi veikindi að stríða og búa við meiri heilsufarslegar takmarkanir en yngra fólkið . 44 | Til dæmis á þriðjungur í aldurshópnum 45 - 54 ára við langvarandi veikindi að stríða borið saman við nær helming í aldurshópnum 65 ára og eldri . 45 | Hærra hlutfall fólks með grunnmenntun býr við langvarandi veikindi og takmarkanir sökum heilsufars borið saman við fólk með háskólamenntun . 46 | Árið 2018 bjó til dæmis um einn af hverjum þremur með grunnmenntun við heilsufarslegar takmarkanir borið saman við einn af hverjum fimm með háskólamenntun . 47 | Hlutfall þeirra sem lifa með heilsufarslegar takmarkanir í daglegu lífi er einnig hærra meðal þeirra með lágar tekjur , en einn af hverjum þremur í lægsta og öðrum tekjufimmtungi bjó við takmarkanir vegna heilsufars árið 2018 . 48 | Í hærri tekjufimmtungum hamlaði heilsufar einum af hverjum fjórum til einum af hverjum fimm . 49 | Niðurstöðurnar eru unnar úr lífskjararannsókn Hagstofu Íslands . 50 | Samhliða birtingu á niðurstöðum fyrir árin 2015 - 2018 hafa eldri niðurstöður verið endurskoðaðar til að samræmast reglugerðum Hagstofu Evrópusambandsins , Eurostat . 51 | Áhrif endurskoðunar er sú að hærra hlutfall telst með takmarkanir sökum heilsufars en áður frá og með árinu 2007 . 52 | Hlutfall fólks sem býr við takmarkanir í daglegu lífi sökum heilsufars byggir á svörum fólks við eftirfarandi spurningu : Í sex mánuði samfleytt eða lengur , hefur heilsufar þitt hamlað eða takmarkað þig á einhvern hátt í einhverju sem reikna má með að flest fólk geti gert ? 53 | Hlutfallið miðar við bæði þá sem takmarkast og takmarkast verulega sökum heilsufars . 54 | Lífskjararannsókn Hagstofunnar er langsniðsrannsókn þar sem haft er samband við hátt í 5 þúsund heimili árlega . 55 | Úrtak rannsóknarinnar er fengið á þann hátt að einstaklingar eru valdir með slembni úr þjóðskrá og þar með heimilið sem þeir tilheyra . 56 | Sá einstaklingur sem er valinn í úrtakið kallast valinn svarandi og veitir hann upplýsingar um aðstæður heimilis , sínar eigin og annarra heimilismeðlima . 57 | Hér er greiningin miðuð við valinn svaranda . 58 | Frekari upplýsingar um lífskjararannsóknina má lesa í lýsigögnum . 59 | Kostnaður vegna bílaleigubíla fyrir þingmenn nam rúmum 19 milljónum króna árið 2018 og nemur hann rétt rúmum sautján milljónum króna það sem af er þessu ári . 60 | Þetta kemur fram í Morgunblaðinu í dag en þar kemur fram að aksturskostnaður þingmanna hafi dregist mjög saman á undanförnum árum . 61 | Í fréttinni kemur fram að aksturskostnaður þingmanna hafi numið tæpum 68 milljónum króna árið 2013 , 56 milljónum árið 2014 , 50 milljónum árið 2015 , 53 milljónum árið 2016 , 43 milljónum árið 2017 , 31 milljón í fyrra og 25 milljónum það sem af er þessu ári . 62 | Þennan samdrátt má meðal annars rekja til breytinga á reglum um þingfarakostnað í byrjun árs 2018 . 63 | Í fréttinni segir að þó aksturskostnaður hafi dregist saman hefur kostnaður við bílaleigubíla aukist . 64 | Ásmundur Friðriksson er með hæsta kostnaðinn vegna bílaleigubíla en upphæðin nemur 2,8 milljónum króna á árinu . 65 | Bent er á það að kostnaðurinn hafi að jafnaði verið 100 til 200 þúsund krónur á mánuði en í október hafi hann verið ríflega 1,6 milljónir króna . 66 | „ Ég lít aldrei á þetta , hvorki hjá mér né öðrum , “ hefur blaðið eftir Ásmundi sem sagðist ekki hafa skýringar á þessum kostnaði í október á reiðum höndum . 67 | Hann hefði þó nýlega skipt um bílaleigubíl og hugsanlega mætti rekja kostnaðinn til þess . 68 | Það staðfestir Eggert Jónsson , forstöðumaður hjá Alþingi , en hann segir að inni í leigunni sé 20 þúsund kílómetra akstur á ári . 69 | Gleymst hafi að lesa af mælinum og kostnaðurinn í október sé því umframakstur síðustu tveggja ára . 70 | Ekki sé hægt að kenna Ásmundi um það . 71 | Fimm þingmenn eru með langtímaleigu á bílaleigubílum en auk Ásmundar eru það Birgir Þórarinsson , Guðjón S. Brjánsson , Haraldur Benediktsson og Sigurður Páll Jónsson . 72 | Verum ábyrg og munum alltaf að skafa vel af öllum rúðum ökutækis áður en haldið er út í umferðina . 73 | Sköfum af ljósum og númeraplötum í leiðinni , minnir lögreglan á höfuðborgarsvæðinu á í færslu sem hún birti á Facebook . 74 | Skipulags- og samgönguráð Reykjavíkurborgar samþykkti á fundi sínum í gær að auglýsa nýtt deiliskipulag fyrir Hlemmsvæðið . 75 | Svæðið mun taka stakkaskiptum á næstu árum verði framkvæmdir að veruleika . 76 | Borgarráð þarf að staðfesta deiliskipulagstillöguna til auglýsingar . 77 | Í desember árið 2018 var efnt til svokallaðar hugmyndaleitar þar sem leitað var eftir hugmyndum um framtíðarútlit svæðisins . 78 | Alls bárust þrjár tillögur um framtíð svæðisins frá Arkitekastofunum Landslagi , DLD land design og Mandaworks . 79 | Á vef Reykjavíkurborgar segir að tillögur arkitektastofanna Mandaworks og DLD hafi verið valdar til áframhaldandi þróunarvinnu við Hlemm fyrir endurhönnun svæðisins og gerð nýs deiliskipulags . 80 | „ Meginmarkmið breytinganna er að skapa gott vistvænt umhverfi á Hlemmi fyrir gangandi vegfarendur , stað sem mun gegna mikilvægu hlutverki fyrir mannlíf og samgöngur á höfuðborgarsvæðinu , “ segir á vef Reykjavíkurborgar . 81 | Hlemmur eigi að vera kjörstaður fyrir gangangi og hjólandi vegfarendur , eftirsóttur bíllaus staður . 82 | Þannig verði akandi umferð beint frá svæðinu , nýr hjólastígur verði lagður meðfram svæðinu og tengir Hverfisgötu og Laugaveg upp að Bríetartúni . 83 | Þá verður Hlemmtorg rammað inn af smærri byggingum sem afmarki nýtt almenningsrými í austurhluta miðborgarinnar . 84 | Umbreyting norðurhluta Hlemmtorgs felist meðal annars í meiri gróðri en þar er nú og setbekkjum . 85 | Þess má geta að tillaga Mandaworks gerði ráð fyrir borðtennisbar og tillaga DLD gerði ráð fyrir gróðurhúsum á svæðinu . 86 | Skoða má deiliskipulagstillöguna nánar hér . 87 | Þjálfarinn og íþróttasérfræðingurinn Simon Brundish fylgist vel með enska boltanum og þar helst sínu uppáhaldsliði , toppliði Liverpool . 88 | Þeir rauðklæddu unnu í gær nokkuð þægilegan sigur á grönnum sínum í Everton en Liverpool vann 5 - 2 sigur eftir að hafa verið 4 - 2 yfir eftir bráðfjörugan fyrri hálfleik . 89 | Divock Origi fékk tækifæri í byrjunarliði Liverpool og nýtti það heldur betur en hann skoraði tvö mörk í leiknum . 90 | Simon birti athyglisverða tölfræði á Twitter-síðu sinni þar sem hann segir frá því að Origi sé með 29 mörk og sjö stoðsendingar í leikjum Liverpool sem fara fram í flóðljósum . 91 | Tölfræði hans í dagsbirtu er allt önnur . 92 | Þar hefur Origi einungis skorað fimm mörk og gefið eina stoðsendingu en hann er greinilega leikmaður sem líkar við kvöldleikina . 93 | „ Einhver annar sem hefur spáð í því að kannski er Origi vampíra , “ skrifaði Simon léttur í bragði á Twitter í gær enda hans menn með átta stiga forskot á toppi deildarinnar . 94 | Gróðureldarnir í Ástralíu undanfarna mánuði hafa valdið mikilli mengun meðfram austurströnd landsins . 95 | Eldar hafa logað á þessum slóðum frá því í september og greindu yfirvöld umhverfismála í Nýja Suður-Wales frá því í morgun að loftmengun af þeirra völdum hefði verið einhver sú mesta sem mælst hefði í Ástralíu . 96 | Loftgæði hefðu oft verið slæm í eldum á þessum slóðum , í gróðureldum við Sydney árið 1994 og í eldunum frá desember 2001 og fram í janúar 2002 , en um þá hefur oft verið talað um svört jól . 97 | Ástandið núna hefði varað mun lengur og útbreiðsla eldanna meiri . 98 | Miklir eldar hafa verið nærri Sydney undanfarnar vikur og margar viðvaranir hafa verið gefnar úr um slæm loftgæði . 99 | Í morgun loguðu eldar á meira en tíu stöðum í nágrenni borgarinnar . 100 | Mjög hefur fjölgað þeim sem leitað hafa læknishjálpar vegna öndunarörðugleika í Sydney . 101 | -------------------------------------------------------------------------------- /test/toktest_sentences.txt: -------------------------------------------------------------------------------- 1 | Haraldur Beinteinsson próf. á átján ketti. 2 | Hann átti margar bækur, þ. á m. rauðar. 3 | Skoðum þetta t.a.m. Þetta gæti farið illa. 4 | Skoðum þetta o.s.frv. Þetta gæti farið illa. 5 | Það gæti t.d. verið lögg. bókari. 6 | Ég fékk mér lögg. Gunna fékk sér hins vegar mikið. 7 | Hér fann ég DV. 8 | Hér er hr. Friðrik Maríusson að gera óskunda. 9 | Hún á sveppi, gúrkur o.s.frv. Friðrik Maríusson gerir oft óskunda. 10 | Hann náði sér í Ph.D. gráðu í gær. 11 | Hún náði sér í Ph.D.-gráðu í gær. 12 | Hann er einnig kallaður dr. A.P.J. Maríusson. 13 | Hér er dót í C. American Continental búðinni. 14 | Hún á t.d. a.m.k. 3 hesta. 15 | 16 | 17 | Þegar talað var við CIAið, kom í ljós að þar hafði C-SPAN komið við sögu. 18 | Vatnið sauð þ.a. hann slökkti undir pottinum. 19 | Ég las fréttina um IBM t.d. á Mbl. 20 | Ég vil 5 stk. af smjöri. 21 | Hann vann hjá Mýrinni ehf. 22 | Áður fyrr kom fólk nk. sunnudag en ekki sl. viku. 23 | Ég held að BSRB hafi verið grænt á litinn. 24 | Ath. að taka öll útiföt með heim. 25 | Hún spurði hæstv. fmrh. hvort til stæði að breyta barnal. 26 | Hann var alg. alm. erl. fyrrn. núv. sk. sn. maður. 27 | 28 | Stóllinn er 12 cm. á breiddina. 29 | 30 | Hann kemur nk sunnudag. 31 | Þetta er td röng skammstöfun. 32 | Ég vil 5 stk af smjöri. 33 | 34 | Hér hef ég setið um langt skeið. 35 | Hér mun ég sitja allt mitt líf. 36 | Allar götur síðan ég settist niður. 37 | Gaman væri að hoppa þrisvar sinnum. 38 | 39 | Hann fæddist 27.5. kl. 00:34 um nóttina. 40 | Hún fæddist 25.10 á Syðri-Núpi. 41 | Hér er 1. desember 1958. Ég kem árið 2000 e.Kr. Í gær var 5 maí 1930. 42 | Klukkan er 13:45:29. Ég kem klukkan hálfátta. 43 | 44 | Lestin staðnæmdist kl. 13:35 eftir hádegi. 45 | Lestin staðnæmdist 13:45 hjá hliðinu. 46 | Klukkan 12.23 læddist púkinn upp í lestina. 47 | Lestin fór aftur af stað klukkan 12:23 eftir að hafa fyllt á. 48 | Hér komum við 13:45:43 til að breyta heiminum. 49 | Hér komum við 13:45 til að breyta heiminum. 50 | Hér komum við á 45:43 í mark til að breyta heiminum. 51 | Hér komum við 13:45:43,98 til að breyta heiminum. 52 | Hér komum við á 45:43,98 í mark til að breyta heiminum. 53 | Hér komum við á 43,98 sekúndum til að breyta heiminum. 54 | 55 | Hér komum við um áttaleytið til að breyta heiminum. 56 | Hér komum við hálfellefu til að breyta heiminum. 57 | Hér komum við kl. hálfellefu til að breyta heiminum. 58 | Hér komum við klukkan hálfellefu til að breyta heiminum. 59 | Hér komum við átta fyrir hádegi til að breyta heiminum. 60 | Hér komum við átta eftir hádegi til að breyta heiminum. 61 | Hér komum við átta f.h. til að breyta heiminum. 62 | Hér komum við átta e.h. til að breyta heiminum. 63 | 64 | Hér komum við 08:54 GMT til að breyta heiminum. 65 | Hér komum við 08:54 GMT+4 til að breyta heiminum. 66 | Hér komum við 08:54 UTC+1 til að breyta heiminum. 67 | 68 | 69 | Hér komum við 13.04 til að breyta heiminum. 70 | Hér komum við 15.45 til að breyta heiminum. 71 | Hér komum við 15.04 til að breyta heiminum. 72 | Hér komum við 15-04 til að breyta heiminum. 73 | Hér komum við 15/04 til að breyta heiminum. 74 | Hér komum við 27.04 til að breyta heiminum. 75 | Hér komum við 27-4 til að breyta heiminum. 76 | 77 | Hér komum við 15/04/1978 til að breyta heiminum. 78 | Hér komum við 15.04.1978 til að breyta heiminum. 79 | Hér komum við 15-04-1978 til að breyta heiminum. 80 | Hér komum við 15/4/1978 til að breyta heiminum. 81 | Hér komum við 15.4.1978 til að breyta heiminum. 82 | Hér komum við 15-4-1978 til að breyta heiminum. 83 | Hér komum við 15.04.78 til að breyta heiminum. 84 | Hér komum við 15/04/78 til að breyta heiminum. 85 | Hér komum við 15-04-78 til að breyta heiminum. 86 | Hér komum við 15.4.78 til að breyta heiminum. 87 | Hér komum við 15/4/78 til að breyta heiminum. 88 | Hér komum við 15-4-78 til að breyta heiminum. 89 | Hér komum við 04.78 til að breyta heiminum. 90 | Hér komum við 04/78 til að breyta heiminum. 91 | Hér komum við 04-78 til að breyta heiminum. 92 | Hér komum við 4.78 til að breyta heiminum. 93 | Hér komum við 4/78 til að breyta heiminum. 94 | Hér komum við 4-78 til að breyta heiminum. 95 | Hér komum við 12.78 til að breyta heiminum. 96 | Hér komum við 12/78 til að breyta heiminum. 97 | Hér komum við 12-78 til að breyta heiminum. 98 | Hér komum við 04.1978 til að breyta heiminum. 99 | Hér komum við 04/1978 til að breyta heiminum. 100 | Hér komum við 04-1978 til að breyta heiminum. 101 | Hér komum við 12.1978 til að breyta heiminum. 102 | Hér komum við 12/1978 til að breyta heiminum. 103 | Hér komum við 12-1978 til að breyta heiminum. 104 | Hér komum við 1978.04.15 til að breyta heiminum. 105 | Hér komum við 1978/04/15 til að breyta heiminum. 106 | Hér komum við 1978-04-15 til að breyta heiminum. 107 | Hér komum við 1978.4.15 til að breyta heiminum. 108 | Hér komum við 1978/4/15 til að breyta heiminum. 109 | Hér komum við 1978-4-15 til að breyta heiminum. 110 | Hér komum við 12.28 til að breyta heiminum. 111 | Hér komum við 12/28 til að breyta heiminum. 112 | Hér komum við 12-28 til að breyta heiminum. 113 | 114 | 115 | Hér komum við 3. mars 1937 til að breyta heiminum. 116 | Hér komum við þriðja mars 1937 til að breyta heiminum. 117 | Hér komum við þriðja jún. 1937 til að breyta heiminum. 118 | Hér komum við þriðja sep. 1937 til að breyta heiminum. 119 | Hér komum við á áttunda áratugnum til að breyta heiminum. 120 | Hér komum við á 8. áratugnum til að breyta heiminum. 121 | 122 | Hér komum við 4. maí 2002-2022 til að breyta heiminum. 123 | 124 | Hér komum við árið 23495 til að breyta heiminum. 125 | Hér komum við í nóvember 23495 til að breyta heiminum. 126 | Hér komum við 17/6/23495 til að breyta heiminum. 127 | 128 | Hér komum við á 20. öld til að breyta heiminum. 129 | Hér komum við árið 20 til að breyta heiminum. 130 | Hér komum við árið 20 f.Kr. til að breyta heiminum. 131 | Hér komum við árið 20 e.Kr. til að breyta heiminum. 132 | Hér komum við 20 f.Kr. til að breyta heiminum. 133 | Hér komum við 20 e.Kr. til að breyta heiminum. 134 | Hér komum við á árinu 23495 til að breyta heiminum. 135 | Hér komum við áramótin 2014-2015 til að breyta heiminum. 136 | Hér komum við áramótin 2014-15 til að breyta heiminum. 137 | Hér komum við mánaðamótin mars-apríl til að breyta heiminum. 138 | Hér komum við mánaðamótin nóv.-des. til að breyta heiminum. 139 | Hér komum við milli klukkan 13:00 og 15:00 alla virka daga til að breyta heiminum. 140 | 141 | Bréfið var dagsett 19/5 til að allt gengi upp. 142 | Bréfið var dags. 19/5 til að allt gengi upp. 143 | Bréfið var dags. 19. mars til að allt gengi upp. 144 | Bréfið var dags. þri. 19. nóv. 2018 til að allt gengi upp. 145 | 146 | Pakkinn kom kl 7:39 árdegis. 147 | Þetta var klukkan 13.49. 148 | Þetta var klukkan 6.12. 149 | 150 | Hér tefla Barack Obama og Jón Guðbjartsson af stakri snilld. 151 | Nú saknar Helgi Björns. 152 | Þetta var Díana Woodward að verki. 153 | Þetta var Derek Árnason að verki. 154 | Þar sáust Áki og Láki Brjánssynir að verki. 155 | Þar sáust Áki og Ína Brjánsbörn að verki. 156 | Eftir umdeilda Apple-sýningu gekk sameiningar-, yfirráða- og dómsvaldsstjórnin beint út úr sýningar- og gamansalnum í Gleðihöllinni. 157 | Hér sjá framsóknar-/sjálfstæðismenn um tösku- og hanskaframleiðslu. 158 | Syðra-Brunnholt stendur enn. 159 | Notum hægri/vinstri-regluna til að rata. 160 | Hér eru nágrannalönd og -þjóðir að rífast. 161 | Við notum Naive Bayes-flokkara til að flokka. 162 | Hér sést að álfabrunnfuglagarðurinn er samsett orð. 163 | 164 | Stóllinn var 12 cm á hæð. 165 | Stóllinn var 12cm á hæð. 166 | Stóllinn var 12 sentímetrar á hæð. 167 | Stóllinn var 12 cm. 168 | Stóllinn var 12cm. 169 | Stóllinn var 12 sentímetrar. 170 | Stillið ofninn á 200°C. 171 | Stillið ofninn á 200° C. 172 | Stillið ofninn á 200 °C. 173 | Hún á 14dl af 15 kg kaffi við 200°C eða 200° C eða 200 °C í gær. 174 | 175 | Ég þarf 14 km² af mold. 176 | Ég þarf 14 km³ af mold. 177 | Ég þarf 14 ha af mold. 178 | Ég þarf 14 m² af mold. 179 | Ég þarf 14 m³ af mold. 180 | Ég þarf 14 km/klst. af mold. 181 | Ég þarf 14 mph af mold. 182 | Ég þarf 14 ekrur af mold. 183 | Ég þarf 14" af mold. 184 | Ég þarf -14° af mold. 185 | Ég þarf 1024x768 af mold. 186 | Ég þarf ½ poka af mold. 187 | Ég þarf ½dl af mold. 188 | Ég þarf ½ dl af mold. 189 | Ég þarf ¼ poka af mold. 190 | Ég þarf ¼dl af mold. 191 | Ég þarf ¼ dl af mold. 192 | Ég þarf 122¼ lítra af mold. 193 | Ég þarf 122 ¼ lítra af mold. 194 | Hann á ½kg af mold og ¼ lítra af vatni. 195 | Byrjum á 2½ dl af rjóma því ¼-½ matskeið er ekki nóg. Helmingur er ½. Svarið er 42, ekki 41⅞. 196 | Þetta er 1¼ en ekki 2⅞ og heldur ekki 33⅗ til að það sé á hreinu. 197 | Þetta er 1 ¼ en ekki 2 ⅞ og heldur ekki 33 ⅗ til að það sé á hreinu. 198 | Hlutföllin eru 2:3 vatn á móti hveiti. 199 | Hlutföllin eru 2,5:1 vatn á móti hveiti. 200 | 201 | Hraðinn var 99 km/klst. Það var of hratt. 202 | 203 | Þetta voru 300 1000 kílóa pokar, og 4000 500 kílóa pokar. 204 | Einnig 932 800 kílóa pokar, svo og 177 4455 millilítra skammtar. 205 | 1.030 hPa lægð gengur yfir landið árið 2019 e.Kr. Jógúrtin inniheldur 80 kcal. 206 | Þessir 10Milljón vírar með 20A straum kostuðu 3000ISK og voru geymdir á Hagamel á 2.hæð. 207 | Hitinn í dag var 32°C en á morgun verður hann 33° C og svo 37 °C. 208 | Hitinn í dag var 100,3°F en á morgun verður hann 102,7 ° F og svo 99.88 °F. 209 | Ég tók stefnu 45° til suðurs og svo 70°N en eftir það 88 ° vestur. 210 | Hitinn var -7,4 gráður. 211 | 212 | Hún gaf myndinni 4,5/5 mögulegum. 213 | Hún gaf myndinni 4.5/5 mögulegum. 214 | Hún gaf myndinni **** og var hæstánægð. 215 | Hún borðaði 4/7 kökunnar en vildi svo 16/339 af kaffi. 216 | Loks gafst hann upp eftir 34 1/4 pylsur. 217 | 218 | Hann lék Nc6 en þá lék mótherjinn Rxc6. Skák og mát. 219 | 5/6^4. 220 | √4. 221 | 12³. 222 | 5/6ˆ3. 223 | √3. 224 | 100,8 / 2 = 50.4. 225 | 100,8/2=50.4. 226 | Það á að geyma mjólkina við 20 ± 3 °C. 227 | 228 | Almennt verð er krónur 9.900,- en kr. 8.000,- fyrir félagsmenn. Maður borgar 99 kr. 10 sinnum. 229 | Hann gekk alltaf með ISK 20.000 í vasanum. 230 | Hún fékk eina milljón USD, það var súrt. 231 | 232 | Hún fékk þrjár milljónir Bandaríkjadala fyrir svikin. 233 | Hún fékk eina milljón USD fyrir svikin. 234 | Hún fékk 100 kr. fyrir svikin. 235 | Hún fékk kr. 1.449 fyrir svikin. 236 | Hún fékk 3,5 mrð. USD fyrir svikin. 237 | Hún fékk 329 þús.kr. fyrir svikin. 238 | Hún fékk $2.349,34 fyrir svikin. 239 | Hún fékk $3.45 fyrir svikin. 240 | Hún fékk $5,32 fyrir svikin. 241 | Hún fékk HK$300 fyrir svikin. 242 | Hún fékk Y20.000 fyrir svikin. 243 | Hún fékk Y20,000 fyrir svikin. 244 | Hún fékk 394 þús.kr. fyrir svikin. 245 | Hún fékk 349 þús. kr. fyrir svikin. 246 | Hún fékk 493 þús. ISK. fyrir svikin. 247 | Hún fékk 493 þús. ISK fyrir svikin. 248 | Hún fékk ISK 493 þús. fyrir svikin. 249 | Hún fékk 2,5 mrð. USD. fyrir svikin. 250 | Hún fékk 2,5 mrð. USD fyrir svikin. 251 | Hún fékk USD 2,5 mrð. fyrir svikin. 252 | Hún fékk 3 m.kr. fyrir svikin. 253 | Hún fékk 3 ma.kr. fyrir svikin. 254 | Hún eyddi 39,3 mö.kr. fyrir svikin. 255 | Hún fékk UR 200 fyrir svikin. 256 | Hún fékk kr. 5.999 fyrir svikin. 257 | Hún fékk ¥212,11 fyrir svikin. 258 | Hún fékk $472,64 fyrir svikin. 259 | Hún fékk €472,64 fyrir svikin. 260 | Hún fékk £199,99 fyrir svikin. 261 | Hún fékk $472.64 fyrir svikin. 262 | Hún fékk €472.64 fyrir svikin. 263 | Hún fékk £199.99 fyrir svikin. 264 | Hún fékk $1,472.64 fyrir svikin. 265 | Hún fékk €3,472.64 fyrir svikin. 266 | Hún fékk £5,199.99 fyrir svikin. 267 | Hún fékk $1.472,64 fyrir svikin. 268 | Hún fékk €3.472,64 fyrir svikin. 269 | Hún fékk £5.199,99 fyrir svikin. 270 | Hún fékk $1,472 fyrir svikin. 271 | Hún fékk €3,472 fyrir svikin. 272 | Hún fékk £5,199 fyrir svikin. 273 | Hún fékk $1.472 fyrir svikin. 274 | Hún fékk €3.472 fyrir svikin. 275 | Hún fékk £5.199 fyrir svikin. 276 | 277 | Hún fékk 212,11¥ fyrir svikin. 278 | Hún fékk 472,64$ fyrir svikin. 279 | Hún fékk 472,64€ fyrir svikin. 280 | Hún fékk 199,99£ fyrir svikin. 281 | Hún fékk 472.64$ fyrir svikin. 282 | Hún fékk 472.64€ fyrir svikin. 283 | Hún fékk 199.99£ fyrir svikin. 284 | Hún fékk 1,472.64$ fyrir svikin. 285 | Hún fékk 3,472.64€ fyrir svikin. 286 | Hún fékk 5,199.99£ fyrir svikin. 287 | Hún fékk 1.472,64$ fyrir svikin. 288 | Hún fékk 3.472,64€ fyrir svikin. 289 | Hún fékk 5.199,99£ fyrir svikin. 290 | Hún fékk 1,472$ fyrir svikin. 291 | Hún fékk 3,472€ fyrir svikin. 292 | Hún fékk 5,199£ fyrir svikin. 293 | Hún fékk 1.472$ fyrir svikin. 294 | Hún fékk 3.472€ fyrir svikin. 295 | Hún fékk 5.199£ fyrir svikin. 296 | 297 | Hér eru 12 fílar. 298 | Hér eru 3429 fílar. 299 | Hér eru 3.429 fílar. 300 | Hér eru 3,429 fílar. 301 | Hér eru 3.429,15 fílar. 302 | Hér eru 3,429.15 fílar. 303 | Hér eru 3,429,15 fílar. 304 | Hér eru 3.429.15 fílar. 305 | Hér eru 2.3843,23 fílar. 306 | Hér eru 3.284.284.283,449 fílar. 307 | 308 | Hér eru -5 fílar. 309 | Hér eru -5.45 fílar. 310 | Hér eru -5,45 fílar. 311 | Hér eru +4 fílar. 312 | Hér eru +4,32 fílar. 313 | Hér eru +4.32 fílar. 314 | 315 | Atburðurinn verður í stofu 101. 316 | Atburðurinn verður í herbergi 293. 317 | 318 | Hann varð 34. í hlaupinu. 319 | Hann varð 12. 320 | Júlíus IV var frábær. 321 | Júlíus IV. var frábær. 322 | Hann hét Júlíus IV. Mér þótti mjög vænt um hann. 323 | Kindin kostaði tvö hundruð þúsund krónur á uppboðinu. 324 | Á borðinu voru tvö þúsund átta hundruð tuttugu og sjö krónur. 325 | Hann á 244USD og 233 ISK og 339 krónur. Hér eru 23$ fyrir þig. 326 | Hún á þrjú þúsund eitt hundrað og tuttugu hesta. 327 | Hann á þrjár milljónir og áttatíu hesta. 328 | 329 | Hér er 4ði hesturinn með 1sta barninu. 330 | Kötturinn var 4ra ára þegar 3ji hundurinn veiktist. 331 | Hann gat hoppað 2svar og 3svar sinnum í röð. 332 | Um 4ðu jólin varð yngri lundinn 2ja ára. 333 | 334 | 335 | Þar varð 123% hækkun. 336 | Þar varð 12,3% hækkun. 337 | Þar varð 123 % hækkun. 338 | Þar varð 123 prósent hækkun. 339 | Þar varð 123 prósenta hækkun. 340 | Hér eru 249% af kökunni. 341 | Hér eru 49 prósent af gleðinni. 342 | Þar varð -34% hækkun. 343 | Þar varð -34 % hækkun. 344 | Þar varð 3.4% hækkun. 345 | Þar varð 3,4% hækkun. 346 | 347 | Þar varð 123‰ hækkun. 348 | Þar varð 12,3‰ hækkun. 349 | Þar varð 123 ‰ hækkun. 350 | Þar varð 123 prósent hækkun. 351 | Þar varð 123 prósenta hækkun. 352 | Hér eru 249‰ af kökunni. 353 | Hér eru 49 prósent af gleðinni. 354 | Þar varð -34‰ hækkun. 355 | Þar varð -34 ‰ hækkun. 356 | Þar varð 3.4‰ hækkun. 357 | Þar varð 3,4‰ hækkun. 358 | 359 | #MeToo-byltingin er til staðar á Íslandsmóti #1. #12stig í Eurovision en #égerekkiaðfílaþað! #ruv50. 360 | Hér er #lifidergott og #mnmlsm-hreyfingin og #égermeðíslenskastafi. 361 | Lagið er í C#. 362 | 363 | 364 | Síminn er 844-4444 hjá henni. 365 | Síminn er 844 4444 hjá henni. 366 | Síminn er 8444444 hjá henni. 367 | Síminn er 844 2019 hjá henni. 368 | Síminn er +354 844 4444 hjá henni. 369 | Skoðaðu vörunúmerin 000-1224 eða 121-2233. Hafðu síðan samband í síma 692 2073. 370 | Þeir voru 313 2012 en 916 árið 2013. 371 | 372 | Netfangið er sdof@sdfir.com. 373 | Netfangið er fake@news.is. 374 | Netfangið er jon.jonsson.99@netfang.is. 375 | Netfangið er valid@my-domain.reallylongtld. 376 | 377 | Vefsíðan er vefur.meira.is og líka vefur.meira.is. 378 | Vefslóðin er http://www.vefur.is. 379 | Vefslóðin er https://vefur.is. 380 | Vefslóðin er https://pypi.org/project/tokenizer/. 381 | Vefslóðin er http://tiny.cc/28695y. 382 | Vefslóðin er www.greynir.is. 383 | Vefslóðin er RÚV.is. 384 | Vefslóðin er ílénumeruíslenskir.stafir-leyfilegir.net. 385 | Vefslóðin er /usr/local. 386 | Vefslóðin er https://docs.google.com/spreadsheets/d/1LjAl3RTxHqEL-FoS5vNRGHHcE6cud4A6cFGEJ_TYMsA/edit#gid=0. 387 | Vefslóðin er https://www.facebook.com/groups/maltaeknierfrabaer/?multi_permalinks=2515242103567086¬if_id=1572959359594650¬if_t=group_highlights. 388 | Vefslóðin er https://www.facebook.com/events/545271046499879/?acontext=%7B%22ref%22%3A%223%22%2C%22ref_newsfeed_story_type%22%3A%22regular%22%2C%22feed_story_type%22%3A%22361%22%2C%22action_history%22%3A%22%5B%7B%5C%22surface%5C%22%3A%5C%22newsfeed%5C%22%2C%5C%22mechanism%5C%22%3A%5C%22feed_story%5C%22%2C%5C%22extra_data%5C%22%3A%5B%5D%7D%5D%22%7D. 389 | Vefslóðin er https://l.facebook.com/l.php?u=https%3A%2F%2Fbuff.ly%2F32isgSM%3Ffbclid%3DIwAR3a-DCOwwdAqB2RhVFQ4zYL7jGiqe87wWaA5h7r9tqesX0yYAgaOh6fgJY&h=AT3v1q25-ehcvTBmfAalEa8tX41m5_KGNrB_f7Z5l9GuPW74Lu_muHwFKg2lF8fj0sGuyjYozehraCCek1glg7gj4JcH74lGzNQWi31IhysJzaIaSvVksuGzqcGtfbOXzAhu8yZxSlr_3T63XwV11pERbm6jC1l-3Nnip5URyBNXbY_WTDX9kHcb2VxDXlNIcYpllaVbs21yjad_0i6lhuFtw3HMW341dOqZQ6hLHYF5_w0nEt6tjj5nUzN6bxQlr8qVWogMH89ccLaNCcFxnYssfjOt80ikN0HXQ9nFJMconHHisu6rMZwaSrGfSn75BpHvYLI1taPEWkuhXDFWCoIVm0_GqF5a22tiN_BQoQ3TXlQk9RYQnQPPm-vZW1r_4-UmVf6ZZpcn9efmlanBaKpgX8i_slVUqEng2Yljxk4uWmk3Scf5d4Z7k4I_HYkz-aMJxQ4E7I5cSW7ab2hfHc4_NivV9ruwaWPDkxoNkhPI6_k5a1MIcLHaDSM9SkbVMomGmbejXPKYc6P1S13CzBT-qu4GEqR03J8GZtueoS4prvlZQJLHBcv6J1s3AsjdiCK7gIIJyzehLG1iQV3wM7ClrIbqILdGvUHWnW9poQ4looSEaS3ZmFni9Q. 390 | 391 | Í frumvarpi þessu eru lagðar til breytingar á lögum nr. 66/2003, um húsnæðissamvinnufélög, með síðari breytingum, en frumvarpið var samið í velferðarráðuneytinu að höfðu samráði við fulltrúa húsnæðissamvinnufélaganna Búseta hsf., Búseta á Norðurlandi hsf. 392 | En í 2. mgr. 29. gr. laganna kemur það fram. 393 | 394 | Efnasambandið H2O er til staðar. 395 | Efnasambandið 2O er til staðar. 396 | Efnasambandið CO2 er til staðar. 397 | Efnasambandið CsCl er til staðar. 398 | Efnasambandið H2S04 er til staðar. 399 | 400 | Hér er H2O. 401 | Hér er 2O. 402 | Hér er CO2. 403 | Hér er CsCl. 404 | Hér er H2S04. 405 | 406 | Eldavélin var 220V, en ofninn var 1500W. 407 | Lögnin var 10A, en ég átti 16A tengil. 408 | 409 | Ég bý í 52°22'N 3°54'E, Húsagötu 15, 108 Rvk., íbúð 104, Þjóðvegi 66. 410 | Ég bý í 109 Reykjavík. 411 | Ég bý í íbúð 201. 412 | Ég bý á Lækjartorgi 3a. 413 | Ég bý á Lækjartorgi 3l. 414 | Húsið er á Lækjartorgi 13A en líka Hlemmi 23f. 415 | Maður var lagður inn á deild 33C eftir handtöku á Bárugötu 14a þann nítjánda júlí 2016. 416 | 417 | Ég keypti Android 43.1, 5.3.alpha. 418 | Ég keypti Intel Xeon E5-2609 2,4GHz 32nm15MB. 419 | Ég keypti Boeing 747. 420 | Ég keypti Pentium i5. 421 | Ég keypti A4. 422 | 423 | Pin-númerið er 0000. 424 | CVV-númerið er 000. 425 | Kreditkortanúmerið er 0000-0000-0000-0000. 426 | Kreditkortanúmerið er 0000 0000 0000 0000. 427 | 428 | Flug AAL12 er á leiðinni. 429 | Flug 495 er á leiðinni. 430 | 431 | Kennitalan er 010101-0120 hjá þeim. 432 | S02E04 er besti þátturinn. 433 | Ýttu bara á Ctrl+C og það bjargar málunum. 434 | 435 | Hús 8a er til vinstri. 436 | Hús 33B er til vinstri. 437 | Hús 1129c er til vinstri. 438 | 439 | Í H&M er margt að finna. 440 | Það er skárra að skrifa sh*t en hitt. 441 | 442 | Sú fullyrðing að tunglið sé úr osti er sönn (Gissur Ísleifsson, 1837). 443 | Þetta er ekki sniðugt dæmi. (Friðrik Maríusson, 1849) Hann heldur því einnig fram að dæmið sé slæmt. 444 | 445 | Þetta var sniðugt, @notandi. 446 | @notandi er frábær. 447 | @notandi2 er betri en @notandi1. Langbestur er þó @notandi239. 448 | 449 | Til hammó með ammó <3 450 | Til hamingju með afmælið :) Hér er gjöfin ;-) 451 | 452 | Hvað segirðu,, skemmtirðu þér vel???! 453 | 454 | "Komið með strákana eins og skot!", sagði hann og hló. 455 | Öllum stöðum Dunkin‘ Donuts á Íslandi hefur nú verið lokað. 456 | 457 | Hann sagði: "Þú ert fífl"! Ég mótmælti því. 458 | Hann sagði: Þú ert "fífl"! Ég mótmælti því. 459 | Hann sagði: Þú ert «fífl»! Ég mótmælti því. 460 | Hann sagði: ´Þú ert fífl´! Farðu í 3ja sinn. 461 | Hann sagði: Þú ert (´fífl´)! Ég mótmælti því. 462 | 463 | Hér er úrfellingarmerki [...] og setningin heldur áfram. 464 | Hér er eins dæmi ... með fimmtán doppum. 465 | Loks kemur annað dæmi ... Hvernig veit ég að hér byrjar ný setning? 466 | Hér er gaman ... Og hér er gaman... 467 | Ég vildi [...] fara út. [...] Hann sá mig. 468 | Er hann nokkuð... ? 469 | Er hann nokkuð ... ? 470 | Er hann nokkuð...? 471 | Er hann nokkuð ...? 472 | Hann er helv... leiðindagaur. 473 | 474 | Hér er úrfellingarmerki […] og setningin heldur áfram. 475 | Hér er eins dæmi … með fimmtán doppum. 476 | Loks kemur annað dæmi … Hvernig veit ég að hér byrjar ný setning? 477 | Hér er gaman … Og hér er gaman… 478 | Ég vildi […] fara út. […] Hann sá mig. 479 | Er hann nokkuð… ? 480 | Er hann nokkuð … ? 481 | Er hann nokkuð…? 482 | Er hann nokkuð …? 483 | Hann er helv… leiðindagaur. 484 | 485 | Þetta er langdregið…………… 486 | Þetta er langdregið............ 487 | Þetta er langdregið............. 488 | Þetta er langdregið.............. 489 | 490 | Hér er tösku- og hanskaframleiðsla. 491 | Hér er tösku- og hanskaframleiðsla. 492 | Hér er tösku– og hanskaframleiðsla. 493 | Hér er tösku— og hanskaframleiðsla. 494 | 495 | Hann marg-ítrekaði skilyrðin. 496 | Hann marg-ítrekaði skilyrðin. 497 | Hann marg–ítrekaði skilyrðin. 498 | Hann marg—ítrekaði skilyrðin. 499 | 500 | Þar voru þingkonur og -menn. 501 | Þar voru þingkonur og -menn. 502 | Þar voru þingkonur og –menn. 503 | Þar voru þingkonur og —menn. 504 | 505 | Bíllinn var grænn - traktorinn var blár. 506 | Bíllinn var grænn - traktorinn var blár. 507 | Bíllinn var grænn – traktorinn var blár. 508 | Bíllinn var grænn — traktorinn var blár. 509 | 510 | Bíllinn var grænn - Traktorinn var blár. 511 | Bíllinn var grænn - Traktorinn var blár. 512 | Bíllinn var grænn – Traktorinn var blár. 513 | Bíllinn var grænn — Traktorinn var blár. 514 | 515 | Bíllinn var grænn - að ofan. 516 | Bíllinn var grænn - að ofan. 517 | Bíllinn var grænn – að ofan. 518 | Bíllinn var grænn — að ofan. 519 | 520 | Jón- sem var formaður — mótmælti málinu. 521 | 522 | Á meðal HTML-tákna eru <, & og ". 523 | 524 | 525 | 526 | Grasið óx um 3 msk. punkturinn gleymdist. 527 | "Þetta er vert íhugunar".Auk þess vantar bil. 528 | 529 | Hún komst í háskólann. þar gleymdist hástafurinn. 530 | 531 | Dæmi um kyrillíska stafi eru АБВГҐДЂЃЕЁЄЖZheЗЗ́ЅИІЇЙЈКЛЉМEmНЊОПРСС́ТЋЌУЎФХЦЧЏШЩЪЫЬЭЮYЯ. 532 | 533 | Lands\u00ADbank\u00ADinn er í 98\u200B,2 pró\u00ADsent eigu\u200B íslenska rík\uFEFFis\u00ADins. 534 | 535 | 536 | Hún sagði: 'Ekki koma með hundinn hingað inn!' Farðu ... 537 | Farðu á www.mbl.is?return=false. 538 | Áttu tvo dl. sykur? 539 | Hann hljóp inn í búðina ... Hver er þetta? 540 | 541 | 542 | Ég fann " hest " áðan. 543 | Ég fann " hest" áðan. 544 | Ég fann "hest " áðan. 545 | Ég fann "hest áðan. 546 | Ég fann "hest" áðan. 547 | Ég fann "hest' áðan. 548 | Ég fann "hest‚ áðan. 549 | Ég fann "hest‛ áðan. 550 | Ég fann "hest“ áðan. 551 | Ég fann "hest„ áðan. 552 | Ég fann ' hest ' áðan. 553 | Ég fann ' hest' áðan. 554 | Ég fann 'hest ' áðan. 555 | Ég fann 'hest áðan. 556 | Ég fann 'hest" áðan. 557 | Ég fann 'hest' áðan. 558 | Ég fann 'hest‚ áðan. 559 | Ég fann 'hest‛ áðan. 560 | Ég fann 'hest“ áðan. 561 | Ég fann 'hest„ áðan. 562 | Ég fann hest" áðan. 563 | Ég fann hest' áðan. 564 | Ég fann hest‚ áðan. 565 | Ég fann hest‛ áðan. 566 | Ég fann hest“ áðan. 567 | Ég fann hest„ áðan. 568 | Ég fann ‚ hest “ áðan. 569 | Ég fann ‚ hest“ áðan. 570 | Ég fann ‚hest áðan. 571 | Ég fann ‚hest “ áðan. 572 | Ég fann ‚hest" áðan. 573 | Ég fann ‚hest' áðan. 574 | Ég fann ‚hest‚ áðan. 575 | Ég fann ‚hest‛ áðan. 576 | Ég fann ‚hest“ áðan. 577 | Ég fann ‚hest„ áðan. 578 | Ég fann ‛hest áðan. 579 | Ég fann ‛hest" áðan. 580 | Ég fann ‛hest' áðan. 581 | Ég fann ‛hest‚ áðan. 582 | Ég fann ‛hest‛ áðan. 583 | Ég fann ‛hest“ áðan. 584 | Ég fann ‛hest„ áðan. 585 | Ég fann “hest áðan. 586 | Ég fann “hest" áðan. 587 | Ég fann “hest' áðan. 588 | Ég fann “hest‚ áðan. 589 | Ég fann “hest‛ áðan. 590 | Ég fann “hest“ áðan. 591 | Ég fann “hest„ áðan. 592 | Ég fann „hest áðan. 593 | Ég fann „hest" áðan. 594 | Ég fann „hest' áðan. 595 | Ég fann „hest‚ áðan. 596 | Ég fann „hest‛ áðan. 597 | Ég fann „hest“ áðan. 598 | Ég fann „hest„ áðan. 599 | --------------------------------------------------------------------------------